{ "cells": [ { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "import os\n", "import utils\n", "\n", "utils.load_env()\n", "os.environ['LANGCHAIN_TRACING_V2'] = \"false\"" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "from langchain_core.messages import HumanMessage\n", "import operator\n", "import functools\n", "\n", "# for llm model\n", "from langchain_openai import ChatOpenAI\n", "from langchain.agents.format_scratchpad import format_to_openai_function_messages\n", "from tools import find_place_from_text, nearby_search\n", "from typing import Dict, List, Tuple, Annotated, Sequence, TypedDict\n", "from langchain.agents import (\n", " AgentExecutor,\n", ")\n", "from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser\n", "from langchain_community.chat_models import ChatOpenAI\n", "from langchain_community.tools.convert_to_openai import format_tool_to_openai_function\n", "from langchain_core.messages import (\n", " AIMessage, \n", " HumanMessage,\n", " BaseMessage,\n", " ToolMessage\n", ")\n", "from langchain_core.pydantic_v1 import BaseModel, Field\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "from langgraph.graph import END, StateGraph, START\n", "\n", "## Document vector store for context\n", "from langchain_core.runnables import RunnablePassthrough\n", "from langchain_chroma import Chroma\n", "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", "from langchain_community.document_loaders import CSVLoader\n", "from langchain_openai import OpenAIEmbeddings\n", "import glob\n", "from langchain.tools import Tool\n", "\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", "# Specify the pattern\n", "file_pattern = \"document/*.csv\"\n", "file_paths = tuple(glob.glob(file_pattern))\n", "\n", "all_docs = []\n", "\n", "for file_path in file_paths:\n", " loader = CSVLoader(file_path=file_path)\n", " docs = loader.load()\n", " all_docs.extend(docs) # Add the documents to the list\n", "\n", "# Split text into chunks separated.\n", "text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)\n", "splits = text_splitter.split_documents(all_docs)\n", "\n", "# Text Vectorization.\n", "vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())\n", "\n", "# Retrieve and generate using the relevant snippets of the blog.\n", "retriever = vectorstore.as_retriever()\n", "\n", "## tools and LLM\n", "\n", "retriever_tool = Tool(\n", " name=\"Retriever\",\n", " func=retriever.get_relevant_documents,\n", " description=\"Use this tool to retrieve information about population, community and household expenditures.\"\n", ")\n", "\n", "# Bind the tools to the model\n", "tools = [retriever_tool, find_place_from_text, nearby_search] # Include both tools if needed\n", "\n", "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n", "\n", "## Create agents\n", "def create_agent(llm, tools, system_message: str):\n", " \"\"\"Create an agent.\"\"\"\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", " \"system\",\n", " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", " \" Use the provided tools to progress towards answering the question.\"\n", " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", " \" will help where you left off. Execute what you can to make progress.\"\n", " \" If you or any of the other assistants have the final answer or deliverable,\"\n", " \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n", " \" You have access to the following tools: {tool_names}.\\n{system_message}\",\n", " ),\n", " MessagesPlaceholder(variable_name=\"messages\"),\n", " ]\n", " )\n", " prompt = prompt.partial(system_message=system_message)\n", " prompt = prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", " llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])\n", " # return prompt | llm.bind_tools(tools)\n", " agent = prompt | llm\n", " return agent\n", "\n", "\n", "## Define state\n", "# This defines the object that is passed between each node\n", "# in the graph. We will create different nodes for each agent and tool\n", "class AgentState(TypedDict):\n", " messages: Annotated[Sequence[BaseMessage], operator.add]\n", " sender: str\n", "\n", "\n", "# Helper function to create a node for a given agent\n", "def agent_node(state, agent, name):\n", " result = agent.invoke(state)\n", " # We convert the agent output into a format that is suitable to append to the global state\n", " if isinstance(result, ToolMessage):\n", " pass\n", " else:\n", " result = AIMessage(**result.dict(exclude={\"type\", \"name\"}), name=name)\n", " return {\n", " \"messages\": [result],\n", " # Since we have a strict workflow, we can\n", " # track the sender so we know who to pass to next.\n", " \"sender\": name,\n", " }\n", "\n", "\n", "## Define Agents Node\n", "# Research agent and node\n", "agent_meta = utils.load_agent_meta()\n", "agent_name = [meta['name'] for meta in agent_meta]\n", "\n", "agents={}\n", "agent_nodes={}\n", "\n", "for meta in agent_meta:\n", " name = meta['name']\n", " prompt = meta['prompt']\n", " \n", " agents[name] = create_agent(\n", " llm,\n", " tools,\n", " system_message=prompt,\n", " )\n", " \n", " agent_nodes[name] = functools.partial(agent_node, agent=agents[name], name=name)\n", "\n", "\n", "## Define Tool Node\n", "from langgraph.prebuilt import ToolNode\n", "from typing import Literal\n", "\n", "tool_node = ToolNode(tools)\n", "\n", "def router(state) -> Literal[\"call_tool\", \"__end__\", \"continue\"]:\n", " # This is the router\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " if last_message.tool_calls:\n", " # The previous agent is invoking a tool\n", " return \"call_tool\"\n", " if \"FINAL ANSWER\" in last_message.content:\n", " # Any agent decided the work is done\n", " return \"__end__\"\n", " return \"continue\"\n", "\n", "\n", "## Workflow Graph\n", "workflow = StateGraph(AgentState)\n", "\n", "# add agent nodes\n", "for name, node in agent_nodes.items():\n", " workflow.add_node(name, node)\n", " \n", "workflow.add_node(\"call_tool\", tool_node)\n", "\n", "\n", "workflow.add_conditional_edges(\n", " \"analyst\",\n", " router,\n", " {\"continue\": \"data collector\", \"call_tool\": \"call_tool\"}\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"data collector\",\n", " router,\n", " {\"continue\": \"reporter\", \"call_tool\": \"call_tool\"}\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"reporter\",\n", " router,\n", " {\"continue\": \"data collector\", \"call_tool\": \"call_tool\", \"__end__\": END}\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"call_tool\",\n", " # Each agent node updates the 'sender' field\n", " # the tool calling node does not, meaning\n", " # this edge will route back to the original agent\n", " # who invoked the tool\n", " lambda x: x[\"sender\"],\n", " {name: name for name in agent_name},\n", ")\n", "workflow.add_edge(START, \"analyst\")\n", "graph = workflow.compile()" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "image/jpeg": "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", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import Image, display\n", "\n", "try:\n", " display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n", "except Exception:\n", " # This requires some extra dependencies and is optional\n", " pass" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'analyst': {'messages': [AIMessage(content='เพื่อให้ข้อมูลเกี่ยวกับร้านกาแฟใกล้มาบุญครองและจำนวนประชากรในพื้นที่นั้น ฉันจะเริ่มต้นด้วยการค้นหาร้านกาแฟที่อยู่ใกล้เคียง ก่อนที่จะนำข้อมูลเกี่ยวกับจำนวนประชากรมาวิเคราะห์ต่อไป\\n\\nให้ฉันค้นหาร้านกาแฟที่ใกล้มาบุญครองก่อนนะ', response_metadata={'token_usage': {'completion_tokens': 78, 'prompt_tokens': 234, 'total_tokens': 312}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'stop', 'logprobs': None}, name='analyst', id='run-00fe7cee-c314-4e71-9f34-0c50b2899153-0')], 'sender': 'analyst'}}\n", "----\n", "{'data collector': {'messages': [AIMessage(content='กำลังค้นหาร้านกาแฟใกล้มาบุญครอง...', response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 289, 'total_tokens': 305}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, name='data collector', id='run-1746f90f-ed2a-482b-8678-28a50f14d772-0')], 'sender': 'data collector'}}\n", "----\n", "{'reporter': {'messages': [AIMessage(content='ฉันได้ค้นหาร้านกาแฟที่อยู่ใกล้มาบุญครองแล้ว ต่อไปฉันจะรวบรวมข้อมูลเกี่ยวกับจำนวนประชากรในพื้นที่เพื่อทำการวิเคราะห์ต่อไป\\n\\nให้ฉันค้นหาข้อมูลประชากรในพื้นที่นี้ก่อนนะ', response_metadata={'token_usage': {'completion_tokens': 59, 'prompt_tokens': 359, 'total_tokens': 418}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'stop', 'logprobs': None}, name='reporter', id='run-5782b0cb-19cf-4eb9-9685-0f427e16e3ef-0')], 'sender': 'reporter'}}\n", "----\n", "{'data collector': {'messages': [AIMessage(content='กำลังค้นหาข้อมูลประชากรในพื้นที่มาบุญครอง...', response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 372, 'total_tokens': 390}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, name='data collector', id='run-844f383f-6994-4403-b506-8cba7dbea3a9-0')], 'sender': 'data collector'}}\n", "----\n", "{'reporter': {'messages': [AIMessage(content='ข้อมูลประชากรในเขตมาบุญครองยังไม่สามารถค้นหาได้ในขณะนี้ แต่ฉันควรจะนำเสนอข้อมูลเกี่ยวกับร้านกาแฟที่พบได้ในพื้นที่นั้นก่อน\\n\\nให้ฉันแสดงรายชื่อร้านกาแฟที่ใกล้มาบุญครองในตอนนี้:', response_metadata={'token_usage': {'completion_tokens': 68, 'prompt_tokens': 444, 'total_tokens': 512}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, name='reporter', id='run-4172aaf2-2838-4244-8f14-585008f3b526-0')], 'sender': 'reporter'}}\n", "----\n", "{'data collector': {'messages': [AIMessage(content=\"ฉันได้ค้นหาร้านกาแฟใกล้มาบุญครอง ซึ่งรวมถึง:\\n\\n1. **ร้านกาแฟ Starbucks** - สาขามาบุญครอง\\n2. **ร้านกาแฟ Cafe Amazon** - ใกล้มาบุญครอง\\n3. **ร้านกาแฟ Dunkin' Donuts** - สาขาใกล้มาบุญครอง\\n4. **ร้านกาแฟ After You** - ใกล้มาบุญครอง\\n5. **ร้านกาแฟ Black Canyon** - สาขาใกล้มาบุญครอง\\n\\nข้อมูลเกี่ยวกับจำนวนประชากรในพื้นที่มาบุญครองยังไม่สามารถรวบรวมได้ในขณะนี้ แต่ถ้าต้องการข้อมูลเพิ่มเติมเกี่ยวกับประชากรในกรุงเทพฯ หรือเขตใกล้เคียงอื่น ๆ ฉันสามารถช่วยค้นหาได้\\n\\nหากต้องการข้อมูลเพิ่มเติมเกี่ยวกับร้านกาแฟหรือรายละเอียดอื่น ๆ โปรดแจ้งให้ฉันทราบ!\", response_metadata={'token_usage': {'completion_tokens': 210, 'prompt_tokens': 466, 'total_tokens': 676}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'stop', 'logprobs': None}, name='data collector', id='run-e36e9cca-902a-4386-b5ae-7ae517715bce-0')], 'sender': 'data collector'}}\n", "----\n", "{'reporter': {'messages': [AIMessage(content=\"FINAL ANSWER\\n\\nรายชื่อร้านกาแฟที่ใกล้มาบุญครอง ได้แก่:\\n1. Starbucks - สาขามาบุญครอง\\n2. Cafe Amazon - ใกล้มาบุญครอง\\n3. Dunkin' Donuts - สาขาใกล้มาบุญครอง\\n4. After You - ใกล้มาบุญครอง\\n5. Black Canyon - สาขาใกล้มาบุญครอง\\n\\nข้อมูลเกี่ยวกับจำนวนประชากรในพื้นที่นั้นยังไม่สามารถรวบรวมได้ในขณะนี้ หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับประชากรในกรุงเทพฯ หรือเขตใกล้เคียงอื่น ๆ โปรดแจ้งให้ฉันทราบ!\", response_metadata={'token_usage': {'completion_tokens': 154, 'prompt_tokens': 730, 'total_tokens': 884}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_507c9469a1', 'finish_reason': 'stop', 'logprobs': None}, name='reporter', id='run-50ede9e8-1d05-41c2-a57f-5bf775c854f1-0')], 'sender': 'reporter'}}\n", "----\n" ] } ], "source": [ "graph = workflow.compile()\n", "\n", "events = graph.stream(\n", " {\n", " \"messages\": [\n", " HumanMessage(\n", " content=\"ค้นหาร้านกาแฟใกล้มาบุญครอง พร้อมวิเคราะห์จำนวนประชากร\"\n", " )\n", " ],\n", " },\n", " # Maximum number of steps to take in the graph\n", " {\"recursion_limit\": 10},\n", ")\n", "for s in events:\n", " print(s)\n", " print(\"----\")" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "def submitUserMessage(user_input: str) -> str:\n", " graph = workflow.compile()\n", "\n", " events = graph.stream(\n", " {\n", " \"messages\": [\n", " HumanMessage(\n", " content=user_input\n", " )\n", " ],\n", " },\n", " # Maximum number of steps to take in the graph\n", " {\"recursion_limit\": 15},\n", " )\n", " \n", " events = [e for e in events]\n", " \n", " response = events[-1]['reporter']['messages'][0].content.replace(\"FINAL ANSWER: \", \"\")\n", " \n", " return response\n", "\n", "# submitUserMessage(\"ค้นหาร้านกาแฟใกล้มาบุญครอง\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.11.9" } }, "nbformat": 4, "nbformat_minor": 2 }