From 9404dbe084b95e6380fef0c6f23c9056ef33962b Mon Sep 17 00:00:00 2001 From: ds-sebastian-chwilczynski Date: Tue, 7 May 2024 15:01:50 +0200 Subject: [PATCH 1/4] new langgraph tutorial --- docs/tutorials/langgraph_tools.py | 210 ++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 docs/tutorials/langgraph_tools.py diff --git a/docs/tutorials/langgraph_tools.py b/docs/tutorials/langgraph_tools.py new file mode 100644 index 00000000..04da5a94 --- /dev/null +++ b/docs/tutorials/langgraph_tools.py @@ -0,0 +1,210 @@ +import os + +print("Installing dependencies...") +os.system("pip install -U langgraph langchain langchain_openai langchain_experimental dbally[openai,langsmith]") +print("Dependencies installed") + +from urllib import request +from sqlalchemy import create_engine, text + +import asyncio + +from dbally import Collection + +import dbally +from dbally.views.freeform.text2sql import configure_text2sql_auto_discovery, Text2SQLFreeformView + +from langchain_openai import ChatOpenAI + +from langgraph.graph import END, StateGraph + +from typing import Optional +from dbally.llm_client.openai_client import OpenAIClient +from langgraph.prebuilt import ToolNode, tools_condition +from langchain.tools import BaseTool +from langchain.pydantic_v1 import BaseModel, Field +from langchain_core.prompts import ChatPromptTemplate +from langchain.callbacks.manager import CallbackManagerForToolRun +from dbally.utils.errors import UnsupportedQueryError + +from langgraph.graph.message import add_messages, AnyMessage +from typing_extensions import TypedDict +from typing import Annotated, Type +from langchain_core.runnables import Runnable + +from langgraph.checkpoint.sqlite import SqliteSaver + +from uuid import uuid4 + +unique_id = uuid4().hex[0:8] + +# UNCOMMENT THIS TO USE LANGSMITH! +# os.environ["LANGCHAIN_TRACING_V2"] = "true" +# os.environ["LANGCHAIN_API_KEY"] = "" +# os.environ["LANGCHAIN_PROJECT"] = f"langgraph-dbally - {unique_id}" +# os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com" + +################################################################################ +#Dbally Tool definition +################################################################################ + + +class DatabaseQuery(BaseModel): + query: str = Field(description="should be a query to the database in the natural language.") + + +class DballyTool(BaseTool): + name = "dbally" + description: str + collection: Collection + args_schema: Type[BaseModel] = DatabaseQuery + + def _run( + self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None + ) -> str: + """Use the tool asynchronously.""" + try: + result = asyncio.run(self.collection.ask(query)) + + if result.textual_response is not None: + return result.textual_response + else: + return result.results + except UnsupportedQueryError: + return "database master can't answer this question" + +################################################################################## +# Helper function to print chat messages taken from https://github.com/langchain-ai/langgraph/blob/e0770d68b31b3fbf38d091857c86017ab2d9c853/examples/customer-support/customer-support.ipynb +################################################################################## + +def _print_event(event: dict, _printed: set, max_length=1500): + current_state = event.get("dialog_state") + if current_state: + print(f"Currently in: ", current_state[-1]) + message = event.get("messages") + if message: + if isinstance(message, list): + message = message[-1] + if message.id not in _printed: + msg_repr = message.pretty_repr(html=True) + if len(msg_repr) > max_length: + msg_repr = msg_repr[:max_length] + " ... (truncated)" + print(msg_repr) + _printed.add(message.id) + +################################################################################### +# Database definition +################################################################################## + +def set_database(): + print("Downloading the HR database") + request.urlretrieve('https://drive.google.com/uc?export=download&id=1zo3j8x7qH8opTKyQ9qFgRpS3yqU6uTRs', 'recruitment.db') + print("Database downloaded") + print("Creating the database") + + db_engine = create_engine("sqlite:///recruitment.db") + + print("Displaying the first 5 rows of the candidate table") + + with db_engine.connect() as conn: + rows = conn.execute(text("SELECT * from candidate LIMIT 5")).fetchall() + + print(rows) + + return db_engine + +################################################################################### +#Definition of: +# 1. State of the Graph +# 2. Assistant Node +# 3. Assistant's prompt +################################################################################### + + +class State(TypedDict): + messages: Annotated[list[AnyMessage], add_messages] + + +class Assistant: + def __init__(self, runnable: Runnable): + self.runnable = runnable + + def __call__(self, state: State): + result = self.runnable.invoke(state) + return {"messages": result} + + +primary_assistant_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + "You are a helpful talent aquisition assistant " + " Use the provided tools to search for candidates, job offers, and other information to assist the user's queries. " + ), + ("placeholder", "{messages}"), + ] +) +################################################################################### +# Creating nodes of the graph and connecting them +################################################################################### + + +def build_graph(recruitment_db: Collection): + llm = ChatOpenAI(model="gpt-3.5-turbo") + + tools = [DballyTool(collection=recruitment_db, description="useful for when you need to gather some HR data")] + runnable = primary_assistant_prompt | llm.bind_tools(tools) + + tool_node = ToolNode(tools) + assistant_node = Assistant(runnable) + + graph = StateGraph(State) + graph.add_node("assistant", assistant_node) + graph.add_node("action", tool_node) + graph.set_entry_point("assistant") + + graph.add_edge("action", "assistant") + graph.add_conditional_edges( + "assistant", + tools_condition, + # "action" calls one of our tools. END causes the graph to terminate (and respond to the user) + {"action": "action", END: END}, + ) + + memory = SqliteSaver.from_conn_string(":memory:") + app = graph.compile(checkpointer=memory) + + return app + +def main(): + db_engine = set_database() + os.environ["OPENAI_API_KEY"] = input("Enter your OpenAI API Key: ") + + # Configure the text2sql view + view_config = asyncio.run(configure_text2sql_auto_discovery(db_engine).discover()) + recruitment_db = dbally.create_collection("recruitment", llm_client=OpenAIClient()) + recruitment_db.add(Text2SQLFreeformView, lambda: Text2SQLFreeformView(db_engine, view_config)) + + #Build the graph + app = build_graph(recruitment_db=recruitment_db) + + #Set thread id to remember entire conversation + graph_config = { + "configurable": { + "thread_id": unique_id, + } + } + + #Run the assistant loop + _printed = set() + while True: + question = input("Enter your message: ") + events = app.stream( + {"messages": ("user", question)}, graph_config, stream_mode="values" + ) + for event in events: + _print_event(event, _printed) + + +if __name__ == "__main__": + main() \ No newline at end of file From ea0ca1aa714144c3b8d70bf8a73b583982b1a0db Mon Sep 17 00:00:00 2001 From: ds-sebastian-chwilczynski Date: Wed, 8 May 2024 08:58:58 +0200 Subject: [PATCH 2/4] add pretty print --- docs/tutorials/langgraph_tools.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/docs/tutorials/langgraph_tools.py b/docs/tutorials/langgraph_tools.py index 04da5a94..dcc4eca3 100644 --- a/docs/tutorials/langgraph_tools.py +++ b/docs/tutorials/langgraph_tools.py @@ -73,25 +73,6 @@ def _run( except UnsupportedQueryError: return "database master can't answer this question" -################################################################################## -# Helper function to print chat messages taken from https://github.com/langchain-ai/langgraph/blob/e0770d68b31b3fbf38d091857c86017ab2d9c853/examples/customer-support/customer-support.ipynb -################################################################################## - -def _print_event(event: dict, _printed: set, max_length=1500): - current_state = event.get("dialog_state") - if current_state: - print(f"Currently in: ", current_state[-1]) - message = event.get("messages") - if message: - if isinstance(message, list): - message = message[-1] - if message.id not in _printed: - msg_repr = message.pretty_repr(html=True) - if len(msg_repr) > max_length: - msg_repr = msg_repr[:max_length] + " ... (truncated)" - print(msg_repr) - _printed.add(message.id) - ################################################################################### # Database definition ################################################################################## @@ -195,15 +176,13 @@ def main(): } } - #Run the assistant loop - _printed = set() while True: question = input("Enter your message: ") events = app.stream( {"messages": ("user", question)}, graph_config, stream_mode="values" ) for event in events: - _print_event(event, _printed) + event["messages"][-1].pretty_print() if __name__ == "__main__": From 7fb2897250c6e8cf2cf6afb02040a083fc0842d8 Mon Sep 17 00:00:00 2001 From: ds-sebastian-chwilczynski Date: Thu, 9 May 2024 13:25:47 +0200 Subject: [PATCH 3/4] add jupyter tutorial --- docs/tutorials/langgraphXdbally2.ipynb | 380 +++++++++++++++++++++++++ docs/tutorials/langgraph_tools.py | 16 +- 2 files changed, 388 insertions(+), 8 deletions(-) create mode 100644 docs/tutorials/langgraphXdbally2.ipynb diff --git a/docs/tutorials/langgraphXdbally2.ipynb b/docs/tutorials/langgraphXdbally2.ipynb new file mode 100644 index 00000000..3188ce36 --- /dev/null +++ b/docs/tutorials/langgraphXdbally2.ipynb @@ -0,0 +1,380 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Talk with your Database\n", + "\n", + "\n", + "Databases are an inavitable part of every company infrastructure. Chatbot capable of interacting with database can free up teams' time by handling novel user queries.\n", + "\n", + "In this tutorial, we will build an agent with access to the database tool, being able to ground its answers with data stored there. Along the way we will create:\n", + "1. Custom LangChain tool.\n", + "2. Assistant agent with access to database tool.\n", + "3. Tool agent, specialized in executing calls returned by assistant.\n", + "4. Graph of connected agents.\n", + "5. Persistent storage component.\n", + "\n", + "By the end, you'll be able to mix this simple strategy with other even more powerful LangGraph concepts.\n", + "\n", + "\n", + "## Prerequisites\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, set up your environment. We'll install this tutorial's prerequisites, download the test DB, and define the tools we will reuse in each section." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -U langgraph langchain langchain_openai langchain_experimental dbally[openai,langsmith] nest_asyncio" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Populate the database\n", + "\n", + "Here, we just fill a dummy database containing some fixtional HR information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from urllib import request\n", + "from sqlalchemy import create_engine, text\n", + "\n", + "print(\"Downloading the HR database\")\n", + "request.urlretrieve('https://drive.google.com/uc?export=download&id=1zo3j8x7qH8opTKyQ9qFgRpS3yqU6uTRs', 'recruitment.db')\n", + "print(\"Database downloaded\")\n", + "print(\"Creating the database\")\n", + "\n", + "db_engine = create_engine(\"sqlite:///recruitment.db\")\n", + "\n", + "print(\"Displaying the first 5 rows of the candidate table\")\n", + "\n", + "with db_engine.connect() as conn:\n", + " rows = conn.execute(text(\"SELECT * from candidate LIMIT 5\")).fetchall()\n", + "\n", + "print(rows)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Database Tool\n", + "\n", + "Next, define our [assistant database tool](https://python.langchain.com/v0.1/docs/modules/tools/) to help it answer any questions concerning HR. Under the hood it uses [db-ally](https://db-ally.deepsense.ai/) database framework. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.pydantic_v1 import BaseModel, Field\n", + "from typing import Optional, Type\n", + "from langchain.callbacks.manager import CallbackManagerForToolRun\n", + "from langchain.tools import BaseTool\n", + "\n", + "from dbally import Collection\n", + "from dbally.utils.errors import UnsupportedQueryError\n", + "\n", + "import asyncio\n", + "import nest_asyncio\n", + "\n", + "nest_asyncio.apply()\n", + "\n", + "class DatabaseQuery(BaseModel):\n", + " query: str = Field(description=\"should be a query to the database in the natural language.\")\n", + "\n", + "\n", + "class DballyTool(BaseTool):\n", + " name = \"dbally\"\n", + " description: str\n", + " collection: Collection\n", + " args_schema: Type[BaseModel] = DatabaseQuery\n", + "\n", + "\n", + " def _run(\n", + " self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None\n", + " ) -> str:\n", + " \"\"\"Use the tool synchronously.\"\"\"\n", + " try:\n", + " result = asyncio.run(self.collection.ask(query))\n", + "\n", + " if result.textual_response is not None:\n", + " return result.textual_response\n", + " else:\n", + " return result.results\n", + " except UnsupportedQueryError:\n", + " return \"database master can't answer this question\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's test our tool. If everything goes correctly you should see `[{'COUNT(*)': 10}]`. In case it doesn't first make sure that provided `OPENAI KEY` is correct. If it didn't help neither, [let the db-ally team now by creating an issue](https://github.com/deepsense-ai/db-ally/issues)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dbally.views.freeform.text2sql import configure_text2sql_auto_discovery, Text2SQLFreeformView\n", + "from dbally.llm_client.openai_client import OpenAIClient\n", + "import dbally\n", + "\n", + "view_config = await configure_text2sql_auto_discovery(db_engine).discover()\n", + "recruitment_db = dbally.create_collection(\"recruitment\", llm_client=OpenAIClient())\n", + "recruitment_db.add(Text2SQLFreeformView, lambda: Text2SQLFreeformView(db_engine, view_config))\n", + "\n", + "DATABASE_TOOL = DballyTool(collection=recruitment_db, description=\"useful for when you need to gather some HR data\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DATABASE_TOOL._run(\"How many job offers from Apple do we have?\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### State\n", + "\n", + "Next, we define our agentic system's state as a typed dictionary containing an append-only list of messages. These messages form the chat history, which is all the state our simple assistant needs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated\n", + "\n", + "from typing_extensions import TypedDict\n", + "\n", + "from langgraph.graph.message import AnyMessage, add_messages\n", + "\n", + "class State(TypedDict):\n", + " messages: Annotated[list[AnyMessage], add_messages]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Assistant Agent\n", + "\n", + "Next, define the assistant agent. This simply takes the graph state, and then calls an LLM for it to predict the best response. The most important thing is that we give the access to use the database tool to our assistant." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import Runnable\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "class Assistant:\n", + " def __init__(self, runnable: Runnable):\n", + " self.runnable = runnable\n", + "\n", + " def __call__(self, state: State):\n", + " result = self.runnable.invoke(state)\n", + " return {\"messages\": result}\n", + "\n", + "\n", + "primary_assistant_prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are a helpful talent aquisition assistant \"\n", + " \" Use the provided tools to search for candidates, job offers, and other information to assist the user's queries. \"\n", + " ),\n", + " (\"placeholder\", \"{messages}\"),\n", + " ]\n", + ")\n", + "\n", + "assistant_llm = ChatOpenAI(model=\"gpt-3.5-turbo\")\n", + "tools = [DATABASE_TOOL]\n", + "assistant_runnable = primary_assistant_prompt | assistant_llm.bind_tools(tools)\n", + "assistant_agent = Assistant(assistant_runnable)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see how this agent works in separation. It is expected to see the Tool Call message generated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "response = assistant_agent({\"messages\": [\"Do we have any software engineers?\"]})\n", + "response['messages'].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But, assistant don't now how to execute the tools. This is why we need to finish our system by connecting all building blocks into the graph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define Graph\n", + "\n", + "Here we connect our previously generated agent by using [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph), [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), [persistent memory](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to build our final application" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import END, StateGraph\n", + "from langgraph.prebuilt import ToolNode, tools_condition\n", + "from langgraph.checkpoint.sqlite import SqliteSaver\n", + "\n", + "\n", + "tool_node = ToolNode(tools)\n", + "\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"assistant\", assistant_agent)\n", + "builder.add_node(\"action\", tool_node)\n", + "builder.set_entry_point(\"assistant\")\n", + "\n", + "builder.add_edge(\"action\", \"assistant\")\n", + "builder.add_conditional_edges(\n", + " \"assistant\",\n", + " tools_condition,\n", + " # \"action\" calls one of our tools. END causes the graph to terminate (and respond to the user)\n", + " {\"action\": \"action\", END: END},\n", + ")\n", + "\n", + "memory = SqliteSaver.from_conn_string(\":memory:\")\n", + "graph = builder.compile(checkpointer=memory)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example conversation\n", + "\n", + "Now it's time to try out our mighty chatbot!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid4\n", + "\n", + "unique_id = uuid4().hex[0:8]\n", + "\n", + "tutorial_questions = [\n", + " \"Hi do we have any software engineers?\",\n", + " \"Describe me the first candidate, please.\",\n", + "]\n", + "\n", + "graph_config = {\n", + " \"configurable\": {\n", + " \"thread_id\": unique_id,\n", + " }\n", + "}\n", + "\n", + "for question in tutorial_questions:\n", + " events = graph.stream(\n", + " {\"messages\": (\"user\", question)}, graph_config, stream_mode=\"values\"\n", + " )\n", + " for event in events:\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Congratulations! Together, we built an agentic system capable of querying the database. Good job!\n", + "\n", + "The full code that you can just copy and paste to use is available in langgraph_tut.py" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "dbally", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials/langgraph_tools.py b/docs/tutorials/langgraph_tools.py index dcc4eca3..63288d43 100644 --- a/docs/tutorials/langgraph_tools.py +++ b/docs/tutorials/langgraph_tools.py @@ -139,13 +139,13 @@ def build_graph(recruitment_db: Collection): tool_node = ToolNode(tools) assistant_node = Assistant(runnable) - graph = StateGraph(State) - graph.add_node("assistant", assistant_node) - graph.add_node("action", tool_node) - graph.set_entry_point("assistant") + builder = StateGraph(State) + builder.add_node("assistant", assistant_node) + builder.add_node("action", tool_node) + builder.set_entry_point("assistant") - graph.add_edge("action", "assistant") - graph.add_conditional_edges( + builder.add_edge("action", "assistant") + builder.add_conditional_edges( "assistant", tools_condition, # "action" calls one of our tools. END causes the graph to terminate (and respond to the user) @@ -153,9 +153,9 @@ def build_graph(recruitment_db: Collection): ) memory = SqliteSaver.from_conn_string(":memory:") - app = graph.compile(checkpointer=memory) + graph = builder.compile(checkpointer=memory) - return app + return graph def main(): db_engine = set_database() From a4b967d986702af2fdb8ee9fc0e436ccc864b449 Mon Sep 17 00:00:00 2001 From: ds-sebastian-chwilczynski Date: Thu, 9 May 2024 13:36:36 +0200 Subject: [PATCH 4/4] fix issues --- docs/tutorials/langgraphXdbally2.ipynb | 46 +++++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/tutorials/langgraphXdbally2.ipynb b/docs/tutorials/langgraphXdbally2.ipynb index 3188ce36..a4024d01 100644 --- a/docs/tutorials/langgraphXdbally2.ipynb +++ b/docs/tutorials/langgraphXdbally2.ipynb @@ -7,12 +7,12 @@ "# Talk with your Database\n", "\n", "\n", - "Databases are an inavitable part of every company infrastructure. Chatbot capable of interacting with database can free up teams' time by handling novel user queries.\n", + "Databases are an ineavitable part of every company's infrastructure. Chatbots capable of interacting with databases can free up teams' time by handling novel user queries.\n", "\n", "In this tutorial, we will build an agent with access to the database tool, being able to ground its answers with data stored there. Along the way we will create:\n", "1. Custom LangChain tool.\n", "2. Assistant agent with access to database tool.\n", - "3. Tool agent, specialized in executing calls returned by assistant.\n", + "3. Tool agent, specialized in executing calls returned by an assistant.\n", "4. Graph of connected agents.\n", "5. Persistent storage component.\n", "\n", @@ -47,10 +47,12 @@ "import getpass\n", "import os\n", "\n", + "\n", "def _set_env(var: str):\n", " if not os.environ.get(var):\n", " os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", + "\n", "_set_env(\"OPENAI_API_KEY\")" ] }, @@ -60,7 +62,7 @@ "source": [ "### Populate the database\n", "\n", - "Here, we just fill a dummy database containing some fixtional HR information." + "Here, we just fill a dummy database containing some fictional HR information." ] }, { @@ -73,7 +75,9 @@ "from sqlalchemy import create_engine, text\n", "\n", "print(\"Downloading the HR database\")\n", - "request.urlretrieve('https://drive.google.com/uc?export=download&id=1zo3j8x7qH8opTKyQ9qFgRpS3yqU6uTRs', 'recruitment.db')\n", + "request.urlretrieve(\n", + " \"https://drive.google.com/uc?export=download&id=1zo3j8x7qH8opTKyQ9qFgRpS3yqU6uTRs\", \"recruitment.db\"\n", + ")\n", "print(\"Database downloaded\")\n", "print(\"Creating the database\")\n", "\n", @@ -93,7 +97,7 @@ "source": [ "## Database Tool\n", "\n", - "Next, define our [assistant database tool](https://python.langchain.com/v0.1/docs/modules/tools/) to help it answer any questions concerning HR. Under the hood it uses [db-ally](https://db-ally.deepsense.ai/) database framework. " + "Next, define our [assistant database tool](https://python.langchain.com/v0.1/docs/modules/tools/) to help it answer any questions concerning HR. Under the hood, it uses [db-ally](https://github.com/deepsense-ai/db-ally) database framework. " ] }, { @@ -115,6 +119,7 @@ "\n", "nest_asyncio.apply()\n", "\n", + "\n", "class DatabaseQuery(BaseModel):\n", " query: str = Field(description=\"should be a query to the database in the natural language.\")\n", "\n", @@ -125,10 +130,7 @@ " collection: Collection\n", " args_schema: Type[BaseModel] = DatabaseQuery\n", "\n", - "\n", - " def _run(\n", - " self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None\n", - " ) -> str:\n", + " def _run(self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None) -> str:\n", " \"\"\"Use the tool synchronously.\"\"\"\n", " try:\n", " result = asyncio.run(self.collection.ask(query))\n", @@ -145,7 +147,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's test our tool. If everything goes correctly you should see `[{'COUNT(*)': 10}]`. In case it doesn't first make sure that provided `OPENAI KEY` is correct. If it didn't help neither, [let the db-ally team now by creating an issue](https://github.com/deepsense-ai/db-ally/issues)." + "Now, let's test our tool. If everything goes correctly, you should see `[{'COUNT(*)': 10}]`. In case it doesn't, first make sure that provided `OPENAI KEY` is correct." ] }, { @@ -195,6 +197,7 @@ "\n", "from langgraph.graph.message import AnyMessage, add_messages\n", "\n", + "\n", "class State(TypedDict):\n", " messages: Annotated[list[AnyMessage], add_messages]" ] @@ -205,7 +208,7 @@ "source": [ "### Assistant Agent\n", "\n", - "Next, define the assistant agent. This simply takes the graph state, and then calls an LLM for it to predict the best response. The most important thing is that we give the access to use the database tool to our assistant." + "Next, define the assistant agent. This simply takes the graph state and then calls an LLM for it to predict the best response. The most important thing is that we give access to the database tool to our assistant." ] }, { @@ -233,7 +236,7 @@ " (\n", " \"system\",\n", " \"You are a helpful talent aquisition assistant \"\n", - " \" Use the provided tools to search for candidates, job offers, and other information to assist the user's queries. \"\n", + " \" Use the provided tools to search for candidates, job offers, and other information to assist the user's queries. \",\n", " ),\n", " (\"placeholder\", \"{messages}\"),\n", " ]\n", @@ -259,14 +262,14 @@ "outputs": [], "source": [ "response = assistant_agent({\"messages\": [\"Do we have any software engineers?\"]})\n", - "response['messages'].pretty_print()" + "response[\"messages\"].pretty_print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "But, assistant don't now how to execute the tools. This is why we need to finish our system by connecting all building blocks into the graph" + "But, assistant doesn't know how to execute the tools. This is why we need to finish our system by connecting all building blocks to the graph" ] }, { @@ -275,7 +278,7 @@ "source": [ "### Define Graph\n", "\n", - "Here we connect our previously generated agent by using [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph), [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), [persistent memory](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to build our final application" + "Here we connect our previously generated agent by using [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph), [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode), and [persistent memory](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to build our final application" ] }, { @@ -305,7 +308,7 @@ ")\n", "\n", "memory = SqliteSaver.from_conn_string(\":memory:\")\n", - "graph = builder.compile(checkpointer=memory)\n" + "graph = builder.compile(checkpointer=memory)" ] }, { @@ -339,9 +342,7 @@ "}\n", "\n", "for question in tutorial_questions:\n", - " events = graph.stream(\n", - " {\"messages\": (\"user\", question)}, graph_config, stream_mode=\"values\"\n", - " )\n", + " events = graph.stream({\"messages\": (\"user\", question)}, graph_config, stream_mode=\"values\")\n", " for event in events:\n", " event[\"messages\"][-1].pretty_print()" ] @@ -352,8 +353,13 @@ "source": [ "Congratulations! Together, we built an agentic system capable of querying the database. Good job!\n", "\n", - "The full code that you can just copy and paste to use is available in langgraph_tut.py" + "The full code that you can just copy and paste to use is available in langgraph_tools.py" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": {