773 lines
27 KiB
Plaintext
773 lines
27 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# 25_项目实战二:多工具协作型智能体\n",
|
||
"\n",
|
||
"## 学习目标\n",
|
||
"1. 掌握多工具协作型智能体的项目开发方法\n",
|
||
"2. 实现结合搜索引擎、计算工具和文件读取的复合型智能体\n",
|
||
"3. 学会调试和优化智能体的执行流程\n",
|
||
"\n",
|
||
"本节课会完成一个小型项目:构建一个可以根据用户问题自动选择工具的智能体。它可以模拟搜索资料、执行计算、读取文件,并把结果整理成最终回答。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 1. 什么是多工具协作型智能体\n",
|
||
"\n",
|
||
"普通问答智能体主要依靠大模型自身回答问题,但大模型并不适合完成所有事情。\n",
|
||
"\n",
|
||
"例如:\n",
|
||
"\n",
|
||
"- 想知道最新信息,需要搜索工具\n",
|
||
"- 想做精确数学计算,需要计算工具\n",
|
||
"- 想分析本地资料,需要文件读取工具\n",
|
||
"- 想保存结果,需要写文件工具\n",
|
||
"\n",
|
||
"多工具协作型智能体就像一个会安排工作的助理:它先理解用户问题,再判断该用哪个工具,最后把工具结果整理成答案。\n",
|
||
"\n",
|
||
"通俗地说,它的核心流程是:\n",
|
||
"\n",
|
||
"```text\n",
|
||
"用户问题 -> 判断需要什么工具 -> 调用工具 -> 整理工具结果 -> 返回答案\n",
|
||
"```\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 2. 项目需求分析\n",
|
||
"\n",
|
||
"本项目要实现一个教学版多工具智能体。为了让代码容易运行,我们先不用真实搜索引擎和真实文件系统,而是用 Python 函数模拟工具。\n",
|
||
"\n",
|
||
"项目需求如下:\n",
|
||
"\n",
|
||
"| 需求 | 说明 |\n",
|
||
"| --- | --- |\n",
|
||
"| 搜索工具 | 根据关键词返回模拟搜索结果 |\n",
|
||
"| 计算工具 | 执行简单数学表达式 |\n",
|
||
"| 文件读取工具 | 从模拟文件中读取内容 |\n",
|
||
"| 工具选择 | 根据用户问题判断应该调用哪个工具 |\n",
|
||
"| 结果汇总 | 把工具返回结果整理为自然语言答案 |\n",
|
||
"\n",
|
||
"第一版项目重点不是做得复杂,而是把“工具定义、工具选择、工具调用、结果汇总”的完整流程跑通。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 3. 项目模块划分\n",
|
||
"\n",
|
||
"我们把项目拆成 5 个模块:\n",
|
||
"\n",
|
||
"1. 定义工具:准备搜索、计算、文件读取函数\n",
|
||
"2. 注册工具:把工具放入统一的工具表\n",
|
||
"3. 选择工具:根据用户问题决定用哪个工具\n",
|
||
"4. 执行工具:调用对应函数并拿到结果\n",
|
||
"5. 汇总答案:把工具执行结果整理给用户\n",
|
||
"\n",
|
||
"这种拆法的好处是每个模块只负责一件事,后续调试和扩展都更方便。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 4. 第一步:定义搜索工具\n",
|
||
"\n",
|
||
"真实项目中,搜索工具可能会调用搜索引擎 API。这里为了教学简单,用一个字典模拟搜索数据库。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"search_database = {\n",
|
||
" \"LangChain\": \"LangChain 是用于开发大模型应用的框架,常用于提示词、链、工具和 RAG 应用开发。\",\n",
|
||
" \"LangGraph\": \"LangGraph 是用于构建有状态、多步骤、可分支智能体流程的框架。\",\n",
|
||
" \"RAG\": \"RAG 是检索增强生成技术,先检索资料,再基于资料生成答案。\",\n",
|
||
"}\n",
|
||
"\n",
|
||
"\n",
|
||
"def search_tool(query):\n",
|
||
" for keyword, result in search_database.items():\n",
|
||
" if keyword.lower() in query.lower():\n",
|
||
" return result\n",
|
||
" return \"没有找到相关搜索结果。\"\n",
|
||
"\n",
|
||
"\n",
|
||
"print(search_tool(\"请介绍 LangGraph\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码定义了一个模拟搜索工具:\n",
|
||
"\n",
|
||
"1. `search_database` 是一个字典,用来模拟搜索引擎中的资料。\n",
|
||
"2. 字典的 key 是关键词,例如 `LangChain`、`LangGraph`、`RAG`。\n",
|
||
"3. 字典的 value 是搜索结果,也就是工具返回给智能体的信息。\n",
|
||
"4. `search_tool(query)` 接收用户查询内容。\n",
|
||
"5. `keyword.lower() in query.lower()` 用来做不区分大小写的关键词匹配。\n",
|
||
"6. 如果命中关键词,就返回对应搜索结果;如果没有命中,就返回“没有找到相关搜索结果”。\n",
|
||
"\n",
|
||
"这个工具虽然简单,但已经体现了搜索工具的基本思想:输入查询,返回相关资料。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 5. 第二步:定义计算工具\n",
|
||
"\n",
|
||
"大模型有时会算错数,所以精确计算应该交给计算工具。\n",
|
||
"\n",
|
||
"下面实现一个简单计算器,只允许安全的数学字符,避免执行危险代码。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def calculator_tool(expression):\n",
|
||
" allowed_chars = set(\"0123456789+-*/(). \" )\n",
|
||
" if not set(expression) <= allowed_chars:\n",
|
||
" return \"表达式中包含不支持的字符。\"\n",
|
||
"\n",
|
||
" try:\n",
|
||
" result = eval(expression)\n",
|
||
" return f\"计算结果是:{result}\"\n",
|
||
" except Exception as error:\n",
|
||
" return f\"计算失败:{error}\"\n",
|
||
"\n",
|
||
"\n",
|
||
"print(calculator_tool(\"128 * 36 + 50\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码定义了一个计算工具:\n",
|
||
"\n",
|
||
"1. `calculator_tool(expression)` 接收一个数学表达式字符串。\n",
|
||
"2. `allowed_chars` 定义允许出现的字符,包括数字、加减乘除、小括号、小数点和空格。\n",
|
||
"3. `set(expression) <= allowed_chars` 用来检查表达式中的字符是否都在允许范围内。\n",
|
||
"4. `eval(expression)` 会执行数学表达式并得到结果。\n",
|
||
"5. `try...except` 用来捕获计算错误,例如表达式格式不正确。\n",
|
||
"\n",
|
||
"注意:真实项目中要谨慎使用 `eval`。这里已经做了简单字符限制,但生产环境通常会使用更安全的数学表达式解析库。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 6. 第三步:定义文件读取工具\n",
|
||
"\n",
|
||
"文件读取工具适合回答和本地资料相关的问题。这里用字典模拟文件内容。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"fake_files = {\n",
|
||
" \"project_plan.txt\": \"项目计划:第一周完成需求分析,第二周完成原型开发,第三周完成测试和优化。\",\n",
|
||
" \"meeting_notes.txt\": \"会议纪要:团队决定优先开发知识库问答功能,然后再增加多工具调用能力。\",\n",
|
||
"}\n",
|
||
"\n",
|
||
"\n",
|
||
"def file_reader_tool(file_name):\n",
|
||
" if file_name in fake_files:\n",
|
||
" return fake_files[file_name]\n",
|
||
" return \"没有找到这个文件。\"\n",
|
||
"\n",
|
||
"\n",
|
||
"print(file_reader_tool(\"project_plan.txt\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码定义了一个模拟文件读取工具:\n",
|
||
"\n",
|
||
"1. `fake_files` 是模拟文件系统,key 是文件名,value 是文件内容。\n",
|
||
"2. `file_reader_tool(file_name)` 接收文件名。\n",
|
||
"3. 如果文件名存在于 `fake_files` 中,就返回对应文件内容。\n",
|
||
"4. 如果文件名不存在,就返回提示信息。\n",
|
||
"\n",
|
||
"真实项目中,这个工具可以改成读取本地文件、数据库记录、对象存储文件或企业文档系统。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 7. 第四步:注册工具\n",
|
||
"\n",
|
||
"如果工具越来越多,不能每次都手动写大量 if 语句。更好的方式是把工具统一注册到一个工具表中。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"tools = {\n",
|
||
" \"search\": {\n",
|
||
" \"name\": \"搜索工具\",\n",
|
||
" \"description\": \"用于查询 LangChain、LangGraph、RAG 等知识。\",\n",
|
||
" \"function\": search_tool,\n",
|
||
" },\n",
|
||
" \"calculator\": {\n",
|
||
" \"name\": \"计算工具\",\n",
|
||
" \"description\": \"用于执行数学表达式计算。\",\n",
|
||
" \"function\": calculator_tool,\n",
|
||
" },\n",
|
||
" \"file_reader\": {\n",
|
||
" \"name\": \"文件读取工具\",\n",
|
||
" \"description\": \"用于读取模拟文件内容。\",\n",
|
||
" \"function\": file_reader_tool,\n",
|
||
" },\n",
|
||
"}\n",
|
||
"\n",
|
||
"print(tools.keys())\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码把所有工具放入统一的 `tools` 字典:\n",
|
||
"\n",
|
||
"1. `search`、`calculator`、`file_reader` 是工具编号,后续智能体会用编号选择工具。\n",
|
||
"2. `name` 是工具的中文名称,方便展示给用户或调试时阅读。\n",
|
||
"3. `description` 是工具说明,真实智能体可以根据说明判断工具用途。\n",
|
||
"4. `function` 保存真正要调用的 Python 函数。\n",
|
||
"5. `tools.keys()` 可以查看当前注册了哪些工具。\n",
|
||
"\n",
|
||
"工具注册表的好处是:新增工具时,只需要往表里加一项,不需要大幅修改主流程。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 8. 第五步:实现工具选择器\n",
|
||
"\n",
|
||
"工具选择器负责判断用户问题应该交给哪个工具。\n",
|
||
"\n",
|
||
"真实项目中,工具选择可以由大模型完成。这里为了便于理解,先用关键词规则实现。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def select_tool(user_input):\n",
|
||
" if any(word in user_input for word in [\"计算\", \"加\", \"减\", \"乘\", \"除\", \"+\", \"-\", \"*\", \"/\"]):\n",
|
||
" return \"calculator\"\n",
|
||
"\n",
|
||
" if any(word in user_input for word in [\"文件\", \"计划\", \"会议\", \"project_plan\", \"meeting_notes\"]):\n",
|
||
" return \"file_reader\"\n",
|
||
"\n",
|
||
" return \"search\"\n",
|
||
"\n",
|
||
"\n",
|
||
"print(select_tool(\"帮我计算 12 * 8\"))\n",
|
||
"print(select_tool(\"读取项目计划文件\"))\n",
|
||
"print(select_tool(\"LangChain 是什么\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码实现了一个规则版工具选择器:\n",
|
||
"\n",
|
||
"1. `select_tool(user_input)` 接收用户输入。\n",
|
||
"2. 如果问题中包含计算相关词语或数学符号,就选择 `calculator`。\n",
|
||
"3. 如果问题中包含文件、计划、会议等词语,就选择 `file_reader`。\n",
|
||
"4. 如果前面规则都没有命中,默认选择 `search`。\n",
|
||
"\n",
|
||
"这种规则方法简单直观,适合教学和小项目。但当问题复杂时,可以让大模型根据工具描述自动选择工具。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 9. 第六步:为不同工具提取参数\n",
|
||
"\n",
|
||
"选择工具之后,还需要给工具传入合适的参数。\n",
|
||
"\n",
|
||
"例如计算工具需要数学表达式,文件读取工具需要文件名,搜索工具需要搜索关键词。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import re\n",
|
||
"\n",
|
||
"\n",
|
||
"def extract_tool_input(user_input, tool_name):\n",
|
||
" if tool_name == \"calculator\":\n",
|
||
" match = re.search(r\"[0-9+\\-*/(). ]+\", user_input)\n",
|
||
" return match.group().strip() if match else user_input\n",
|
||
"\n",
|
||
" if tool_name == \"file_reader\":\n",
|
||
" if \"meeting\" in user_input or \"会议\" in user_input:\n",
|
||
" return \"meeting_notes.txt\"\n",
|
||
" return \"project_plan.txt\"\n",
|
||
"\n",
|
||
" return user_input\n",
|
||
"\n",
|
||
"\n",
|
||
"print(extract_tool_input(\"帮我计算 128 * 36 + 50\", \"calculator\"))\n",
|
||
"print(extract_tool_input(\"读取会议纪要\", \"file_reader\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码负责从用户输入中提取工具参数:\n",
|
||
"\n",
|
||
"1. `import re` 引入正则表达式模块。\n",
|
||
"2. `extract_tool_input(user_input, tool_name)` 根据工具类型提取不同参数。\n",
|
||
"3. 对计算工具,`re.search(r\"[0-9+\\-*/(). ]+\", user_input)` 会从文本中找出数学表达式。\n",
|
||
"4. `match.group().strip()` 取出匹配结果并去掉前后空格。\n",
|
||
"5. 对文件读取工具,如果用户提到会议,就读取 `meeting_notes.txt`;否则默认读取 `project_plan.txt`。\n",
|
||
"6. 对搜索工具,直接把用户输入作为查询内容。\n",
|
||
"\n",
|
||
"参数提取是工具调用中很重要的一步。工具选对了,但参数给错了,结果也会不准确。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 10. 第七步:实现智能体主流程\n",
|
||
"\n",
|
||
"现在把工具选择、参数提取、工具调用和答案汇总连接起来,形成完整智能体。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def multi_tool_agent(user_input):\n",
|
||
" tool_name = select_tool(user_input)\n",
|
||
" tool_info = tools[tool_name]\n",
|
||
" tool_input = extract_tool_input(user_input, tool_name)\n",
|
||
" tool_result = tool_info[\"function\"](tool_input)\n",
|
||
"\n",
|
||
" final_answer = f\"用户问题:{user_input}\\n\"\n",
|
||
" final_answer += f\"选择工具:{tool_info['name']}\\n\"\n",
|
||
" final_answer += f\"工具输入:{tool_input}\\n\"\n",
|
||
" final_answer += f\"工具结果:{tool_result}\"\n",
|
||
" return final_answer\n",
|
||
"\n",
|
||
"\n",
|
||
"print(multi_tool_agent(\"帮我计算 128 * 36 + 50\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码实现了智能体主流程:\n",
|
||
"\n",
|
||
"1. `multi_tool_agent(user_input)` 是智能体入口函数。\n",
|
||
"2. `select_tool(user_input)` 判断应该使用哪个工具。\n",
|
||
"3. `tools[tool_name]` 从工具注册表中取出工具信息。\n",
|
||
"4. `extract_tool_input(user_input, tool_name)` 为工具准备输入参数。\n",
|
||
"5. `tool_info[\"function\"](tool_input)` 调用真正的工具函数。\n",
|
||
"6. 最后把用户问题、选择的工具、工具输入和工具结果拼接成最终答案。\n",
|
||
"\n",
|
||
"这就是多工具协作型智能体的最小可用版本。它的重点不是某个工具有多复杂,而是能自动完成“选工具、调工具、汇总结果”。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 11. 测试多个问题\n",
|
||
"\n",
|
||
"下面用不同类型的问题测试智能体,观察它是否能选择正确工具。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"test_questions = [\n",
|
||
" \"LangChain 是什么?\",\n",
|
||
" \"帮我计算 25 * 4 + 18\",\n",
|
||
" \"读取项目计划文件\",\n",
|
||
" \"会议纪要里说了什么?\",\n",
|
||
"]\n",
|
||
"\n",
|
||
"for question in test_questions:\n",
|
||
" print(\"=\" * 40)\n",
|
||
" print(multi_tool_agent(question))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码用于批量测试:\n",
|
||
"\n",
|
||
"1. `test_questions` 保存多个测试问题。\n",
|
||
"2. 第一个问题适合搜索工具。\n",
|
||
"3. 第二个问题适合计算工具。\n",
|
||
"4. 第三个和第四个问题适合文件读取工具。\n",
|
||
"5. `for question in test_questions` 逐个运行智能体。\n",
|
||
"6. `print(\"=\" * 40)` 用分隔线区分不同测试结果。\n",
|
||
"\n",
|
||
"测试时要重点观察两点:工具是否选对,工具输入是否提取正确。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 12. 加入调试日志\n",
|
||
"\n",
|
||
"智能体项目经常需要调试,因为错误可能发生在工具选择、参数提取或工具执行阶段。\n",
|
||
"\n",
|
||
"下面给主流程增加调试日志,方便定位问题。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def debug_multi_tool_agent(user_input, verbose=True):\n",
|
||
" if verbose:\n",
|
||
" print(f\"[调试] 用户输入:{user_input}\")\n",
|
||
"\n",
|
||
" tool_name = select_tool(user_input)\n",
|
||
" if verbose:\n",
|
||
" print(f\"[调试] 选择工具:{tool_name}\")\n",
|
||
"\n",
|
||
" tool_input = extract_tool_input(user_input, tool_name)\n",
|
||
" if verbose:\n",
|
||
" print(f\"[调试] 工具输入:{tool_input}\")\n",
|
||
"\n",
|
||
" tool_result = tools[tool_name][\"function\"](tool_input)\n",
|
||
" if verbose:\n",
|
||
" print(f\"[调试] 工具结果:{tool_result}\")\n",
|
||
"\n",
|
||
" return f\"最终回答:{tool_result}\"\n",
|
||
"\n",
|
||
"\n",
|
||
"print(debug_multi_tool_agent(\"帮我计算 10 + 20 * 3\"))\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码增加了调试能力:\n",
|
||
"\n",
|
||
"1. `verbose=True` 表示默认打印调试信息。\n",
|
||
"2. 第一条日志显示用户原始输入。\n",
|
||
"3. 第二条日志显示智能体选择了哪个工具。\n",
|
||
"4. 第三条日志显示传给工具的输入参数。\n",
|
||
"5. 第四条日志显示工具返回结果。\n",
|
||
"6. 如果不想看日志,可以调用 `debug_multi_tool_agent(question, verbose=False)`。\n",
|
||
"\n",
|
||
"调试日志能帮助我们判断问题到底出在哪里:是工具没选对,还是参数没提对,还是工具本身执行失败。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 13. 用 LangGraph 表达多工具流程\n",
|
||
"\n",
|
||
"当前流程是普通 Python 函数。实际项目中,如果流程变复杂,可以用 LangGraph 把步骤拆成节点。\n",
|
||
"\n",
|
||
"工作流可以设计为:\n",
|
||
"\n",
|
||
"```text\n",
|
||
"START -> 选择工具 -> 执行工具 -> 汇总答案 -> END\n",
|
||
"```\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing import TypedDict\n",
|
||
"\n",
|
||
"\n",
|
||
"class AgentState(TypedDict):\n",
|
||
" user_input: str\n",
|
||
" tool_name: str\n",
|
||
" tool_input: str\n",
|
||
" tool_result: str\n",
|
||
" final_answer: str\n",
|
||
"\n",
|
||
"\n",
|
||
"def choose_tool_node(state: AgentState):\n",
|
||
" tool_name = select_tool(state[\"user_input\"])\n",
|
||
" tool_input = extract_tool_input(state[\"user_input\"], tool_name)\n",
|
||
" return {\"tool_name\": tool_name, \"tool_input\": tool_input}\n",
|
||
"\n",
|
||
"\n",
|
||
"def execute_tool_node(state: AgentState):\n",
|
||
" tool_result = tools[state[\"tool_name\"]][\"function\"](state[\"tool_input\"])\n",
|
||
" return {\"tool_result\": tool_result}\n",
|
||
"\n",
|
||
"\n",
|
||
"def summarize_node(state: AgentState):\n",
|
||
" tool_label = tools[state[\"tool_name\"]][\"name\"]\n",
|
||
" final_answer = f\"我使用了{tool_label},得到结果:{state['tool_result']}\"\n",
|
||
" return {\"final_answer\": final_answer}\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码定义了 LangGraph 版本需要的 State 和节点:\n",
|
||
"\n",
|
||
"1. `AgentState` 表示工作流中流动的数据。\n",
|
||
"2. `user_input` 保存用户问题。\n",
|
||
"3. `tool_name` 保存选择出的工具编号。\n",
|
||
"4. `tool_input` 保存传给工具的参数。\n",
|
||
"5. `tool_result` 保存工具执行结果。\n",
|
||
"6. `final_answer` 保存最终回答。\n",
|
||
"7. `choose_tool_node` 负责工具选择和参数提取。\n",
|
||
"8. `execute_tool_node` 负责调用工具。\n",
|
||
"9. `summarize_node` 负责把工具结果整理成用户能看懂的回答。\n",
|
||
"\n",
|
||
"用 LangGraph 后,每个步骤变成独立节点,流程更清晰,也更容易扩展。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 14. 构建 LangGraph 工作流\n",
|
||
"\n",
|
||
"如果当前环境没有安装 LangGraph,可以先运行:`%pip install langgraph`。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# 如果没有安装 LangGraph,取消下一行注释后运行\n",
|
||
"# %pip install langgraph\n",
|
||
"\n",
|
||
"from langgraph.graph import END, START, StateGraph\n",
|
||
"\n",
|
||
"\n",
|
||
"workflow = StateGraph(AgentState)\n",
|
||
"\n",
|
||
"workflow.add_node(\"choose_tool\", choose_tool_node)\n",
|
||
"workflow.add_node(\"execute_tool\", execute_tool_node)\n",
|
||
"workflow.add_node(\"summarize\", summarize_node)\n",
|
||
"\n",
|
||
"workflow.add_edge(START, \"choose_tool\")\n",
|
||
"workflow.add_edge(\"choose_tool\", \"execute_tool\")\n",
|
||
"workflow.add_edge(\"execute_tool\", \"summarize\")\n",
|
||
"workflow.add_edge(\"summarize\", END)\n",
|
||
"\n",
|
||
"agent_app = workflow.compile()\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码把节点连接成完整工作流:\n",
|
||
"\n",
|
||
"1. `StateGraph(AgentState)` 创建一张流程图,并指定状态结构。\n",
|
||
"2. `add_node` 注册节点函数。\n",
|
||
"3. `START -> choose_tool` 表示从工具选择节点开始。\n",
|
||
"4. `choose_tool -> execute_tool` 表示选好工具后执行工具。\n",
|
||
"5. `execute_tool -> summarize` 表示拿到工具结果后进行汇总。\n",
|
||
"6. `summarize -> END` 表示汇总完成后结束。\n",
|
||
"7. `compile()` 把流程图编译成可运行对象。\n",
|
||
"\n",
|
||
"这和前面的普通 Python 主流程做的是同一件事,只是 LangGraph 更适合管理复杂流程。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 15. 运行 LangGraph 版智能体\n",
|
||
"\n",
|
||
"现在用一个问题测试 LangGraph 版多工具智能体。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"initial_state = {\n",
|
||
" \"user_input\": \"会议纪要里说了什么?\",\n",
|
||
" \"tool_name\": \"\",\n",
|
||
" \"tool_input\": \"\",\n",
|
||
" \"tool_result\": \"\",\n",
|
||
" \"final_answer\": \"\",\n",
|
||
"}\n",
|
||
"\n",
|
||
"final_state = agent_app.invoke(initial_state)\n",
|
||
"print(final_state[\"final_answer\"])\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码运行 LangGraph 工作流:\n",
|
||
"\n",
|
||
"1. `initial_state` 是初始状态,只有 `user_input` 有真实内容,其他字段先留空。\n",
|
||
"2. `agent_app.invoke(initial_state)` 启动工作流。\n",
|
||
"3. 工作流会依次执行工具选择、工具调用和结果汇总。\n",
|
||
"4. `final_state` 是运行结束后的完整状态。\n",
|
||
"5. `final_state[\"final_answer\"]` 取出最终答案。\n",
|
||
"\n",
|
||
"这个例子说明:当一个智能体需要多个步骤协作时,LangGraph 可以让流程更有结构。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 16. 调试和优化建议\n",
|
||
"\n",
|
||
"多工具智能体常见问题包括:\n",
|
||
"\n",
|
||
"| 问题 | 可能原因 | 优化方法 |\n",
|
||
"| --- | --- | --- |\n",
|
||
"| 工具选错 | 工具描述不清或规则太简单 | 优化工具描述,增加分类规则或让模型判断 |\n",
|
||
"| 参数提错 | 用户表达复杂 | 增加参数抽取逻辑或使用结构化输出 |\n",
|
||
"| 工具报错 | 输入格式不符合要求 | 在调用前做参数校验 |\n",
|
||
"| 答案不清晰 | 只返回原始工具结果 | 增加结果解释和格式化输出 |\n",
|
||
"| 流程难排查 | 缺少中间状态 | 增加调试日志或保存执行轨迹 |\n",
|
||
"\n",
|
||
"开发智能体时,不要只看最终答案,也要观察每一步的中间状态。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 17. 小结\n",
|
||
"\n",
|
||
"本节课完成了一个多工具协作型智能体项目。\n",
|
||
"\n",
|
||
"你需要重点掌握:\n",
|
||
"\n",
|
||
"1. 多工具智能体的核心是根据任务选择合适工具。\n",
|
||
"2. 工具通常包含名称、描述和函数。\n",
|
||
"3. 工具调用前要先选择工具,再提取参数。\n",
|
||
"4. 调试时要观察工具选择、工具输入和工具结果。\n",
|
||
"5. LangGraph 可以把复杂工具调用流程拆成清晰节点。\n",
|
||
"\n",
|
||
"掌握这个项目后,就可以继续接入真实搜索 API、真实文件读取、大模型工具调用和更复杂的工作流。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 18. 练习题\n",
|
||
"\n",
|
||
"1. 新增一个 `weather_tool`,模拟查询天气。\n",
|
||
"2. 修改 `select_tool`,让它可以根据“天气”关键词选择天气工具。\n",
|
||
"3. 修改 `calculator_tool`,支持平方和取余计算。\n",
|
||
"4. 给 `multi_tool_agent` 增加错误处理,当工具不存在时返回友好提示。\n",
|
||
"5. 在 LangGraph 工作流中新增一个 `debug_node`,专门输出当前 State。\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": ".venv",
|
||
"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.14.4"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
}
|