{ "cells": [ { "cell_type": "code", "execution_count": 7, "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": 8, "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\", temperature=0.0)\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\", \"__end__\": END}\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"data collector\",\n", " router,\n", " {\"continue\": \"reporter\", \"call_tool\": \"call_tool\", \"__end__\": END}\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"reporter\",\n", " router,\n", " {\"continue\": \"data collector\", \"__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": 9, "metadata": {}, "outputs": [], "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": 10, "metadata": {}, "outputs": [], "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": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'FINAL ANSWER\\n\\nรายงานการวิเคราะห์ร้านกาแฟใกล้มาบุญครอง:\\n\\n### รายชื่อร้านกาแฟ\\n1. **ร้านกาแฟ A** - เมนูหลากหลาย บรรยากาศน่านั่ง\\n2. **ร้านกาแฟ B** - กาแฟสด ขนมเค้กอร่อย\\n3. **ร้านกาแฟ C** - Wi-Fi ฟรี พื้นที่นั่งทำงาน\\n4. **ร้านกาแฟ D** - กิจกรรมดนตรีสด\\n5. **ร้านกาแฟ E** - เมนูเอกลักษณ์ การตกแต่งสวยงาม\\n\\n### การวิเคราะห์\\n- **ประเภทของร้านกาแฟ**: มีความหลากหลาย เช่น บรรยากาศน่านั่ง, เน้นกาแฟสด, และการจัดกิจกรรม\\n- **กลุ่มเป้าหมาย**: นักศึกษา, คนทำงาน, นักท่องเที่ยว\\n- **โอกาสทางการตลาด**: สร้างความแตกต่าง, ใช้โซเชียลมีเดีย, จัดกิจกรรมพิเศษ\\n\\n### ข้อเสนอแนะ\\n- สร้างเอกลักษณ์เฉพาะตัว\\n- ใช้กลยุทธ์การตลาดที่เหมาะสม\\n- จัดกิจกรรมเพื่อดึงดูดลูกค้า\\n\\nหากต้องการข้อมูลเพิ่มเติมหรือรายละเอียดเฉพาะเจาะจงเกี่ยวกับร้านกาแฟใด ๆ โปรดแจ้งให้ฉันทราบ!'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "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 = list(events[-1].values())[0][\"messages\"][0]\n", " response = response.content\n", " response = response.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 }