Files
ai-agent-dev/22_任务调度.ipynb
2026-07-08 10:09:42 +08:00

605 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 22_任务调度型智能体\n",
"\n",
"## 学习目标\n",
"1. 理解任务调度型智能体的设计思路\n",
"2. 掌握使用 LangGraph 实现任务分解、执行与结果汇总\n",
"3. 能够构建简单的自动化任务处理智能体\n",
"\n",
"本节课会用一个通俗例子来理解任务调度:**把一个大任务拆成多个小任务,再安排合适的节点依次完成,最后汇总结果**。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 什么是任务调度型智能体\n",
"\n",
"任务调度型智能体可以理解为一个“项目经理型”智能体。用户只提出一个目标,它不会马上盲目执行,而是先思考:\n",
"\n",
"1. 这个目标可以拆成哪些步骤?\n",
"2. 哪些步骤需要先做,哪些步骤可以后做?\n",
"3. 每一步应该交给哪个工具、函数或智能体执行?\n",
"4. 每一步完成后,如何把结果合并成最终答案?\n",
"\n",
"例如用户说:\n",
"\n",
"> 帮我分析一个产品的用户反馈,并给出改进建议。\n",
"\n",
"任务调度型智能体可能会拆成:\n",
"\n",
"1. 收集用户反馈\n",
"2. 对反馈进行分类\n",
"3. 提取高频问题\n",
"4. 生成改进建议\n",
"5. 输出结构化报告\n",
"\n",
"这类智能体的核心不是“单次回答”,而是“组织多个步骤完成复杂任务”。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. 任务调度的基本流程\n",
"\n",
"一个典型任务调度型智能体通常包含 4 个环节:\n",
"\n",
"| 环节 | 作用 | 通俗理解 |\n",
"| --- | --- | --- |\n",
"| 任务输入 | 接收用户目标 | 用户告诉智能体要做什么 |\n",
"| 任务分解 | 把大任务拆成小任务 | 项目经理制定待办清单 |\n",
"| 任务执行 | 按顺序执行每个小任务 | 不同同事完成各自工作 |\n",
"| 结果汇总 | 整合所有执行结果 | 写成最终报告交给用户 |\n",
"\n",
"在 LangGraph 中,我们可以把每个环节看成一个 **节点**,节点之间通过 **边** 连接起来,形成一个工作流。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. 先不用 LangGraph用普通 Python 理解任务调度\n",
"\n",
"在学习 LangGraph 前,先用普通 Python 模拟一个最小任务调度流程。这个例子不依赖任何外部库,更容易理解“任务分解、执行、汇总”的本质。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def split_task(user_goal):\n",
" \"\"\"把用户的大目标拆成多个小任务。\"\"\"\n",
" tasks = [\n",
" f\"理解用户目标:{user_goal}\",\n",
" \"收集与目标相关的信息\",\n",
" \"分析关键信息\",\n",
" \"生成最终建议\",\n",
" ]\n",
" return tasks\n",
"\n",
"\n",
"def execute_task(task):\n",
" \"\"\"模拟执行一个小任务。\"\"\"\n",
" return f\"已完成:{task}\"\n",
"\n",
"\n",
"def summarize_results(results):\n",
" \"\"\"把多个小任务的执行结果汇总成最终输出。\"\"\"\n",
" report = \"任务执行报告:\\n\"\n",
" for index, result in enumerate(results, start=1):\n",
" report += f\"{index}. {result}\\n\"\n",
" return report\n",
"\n",
"\n",
"user_goal = \"为一家咖啡店设计会员运营方案\"\n",
"tasks = split_task(user_goal)\n",
"results = [execute_task(task) for task in tasks]\n",
"final_report = summarize_results(results)\n",
"\n",
"print(final_report)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码展示了任务调度的最小模型:\n",
"\n",
"1. `split_task(user_goal)` 负责拆解任务。它接收用户的大目标,返回一个任务列表。\n",
"2. `execute_task(task)` 负责执行单个小任务。这里为了演示只返回“已完成”的文字。真实项目中这里可以调用搜索工具、数据库、LLM 或其他业务函数。\n",
"3. `summarize_results(results)` 负责汇总结果。它把每个小任务的输出整理成最终报告。\n",
"4. `results = [execute_task(task) for task in tasks]` 是任务调度的执行阶段,会逐个执行任务列表里的每一项。\n",
"\n",
"这个例子虽然简单,但已经包含任务调度型智能体最重要的 3 个动作:**拆、做、汇总**。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. LangGraph 的核心概念\n",
"\n",
"LangGraph 是一个用“图”组织智能体流程的框架。\n",
"\n",
"可以把它想象成一张流程图:\n",
"\n",
"```text\n",
"开始 -> 任务分解 -> 任务执行 -> 结果汇总 -> 结束\n",
"```\n",
"\n",
"在 LangGraph 中常见概念如下:\n",
"\n",
"| 概念 | 含义 | 通俗理解 |\n",
"| --- | --- | --- |\n",
"| State | 工作流中流动的数据 | 一个不断更新的任务档案袋 |\n",
"| Node | 处理数据的函数 | 流程图中的一个步骤 |\n",
"| Edge | 节点之间的连接 | 告诉程序下一步去哪里 |\n",
"| Graph | 完整工作流 | 整张流程图 |\n",
"\n",
"接下来我们用 LangGraph 实现同样的“拆、做、汇总”流程。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. 安装依赖\n",
"\n",
"如果当前环境没有安装 LangGraph可以先运行下面的命令。已经安装过的环境可以跳过。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 如果没有安装 LangGraph取消下一行注释后运行\n",
"# %pip install langgraph\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"`%pip install langgraph` 是 Jupyter Notebook 中安装 Python 包的写法。\n",
"\n",
"这里默认把安装命令注释掉,是为了避免重复安装。如果运行后提示找不到 `langgraph`,再去掉前面的 `#` 执行即可。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. 定义工作流状态 State\n",
"\n",
"State 是 LangGraph 中非常重要的概念。它表示工作流运行过程中一直携带和更新的数据。\n",
"\n",
"本例中,我们希望 State 保存 4 类信息:\n",
"\n",
"1. `goal`:用户输入的大目标\n",
"2. `tasks`:拆分后的小任务列表\n",
"3. `results`:每个小任务的执行结果\n",
"4. `final_answer`:最终汇总答案\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from typing import List, TypedDict\n",
"\n",
"\n",
"class TaskState(TypedDict):\n",
" goal: str\n",
" tasks: List[str]\n",
" results: List[str]\n",
" final_answer: str\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码定义了工作流中的数据结构:\n",
"\n",
"1. `TypedDict` 用来描述字典里应该有哪些字段。它不会改变程序运行方式,但能让代码更清晰。\n",
"2. `TaskState` 表示整个任务调度过程中共享的数据。每个节点都会读取它,也可以返回新的字段值更新它。\n",
"3. `goal: str` 表示用户目标是字符串。\n",
"4. `tasks: List[str]` 表示任务列表,里面每一项都是字符串。\n",
"5. `results: List[str]` 表示执行结果列表。\n",
"6. `final_answer: str` 表示最终回答。\n",
"\n",
"可以把 `TaskState` 理解为一张任务表,工作流每走一步,就在这张表上补充一些内容。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. 编写节点函数\n",
"\n",
"节点函数就是工作流中的一个步骤。每个节点接收当前 `state`,处理后返回要更新的字段。\n",
"\n",
"下面定义 3 个节点:\n",
"\n",
"1. `planner_node`:任务分解节点\n",
"2. `worker_node`:任务执行节点\n",
"3. `summary_node`:结果汇总节点\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def planner_node(state: TaskState):\n",
" goal = state[\"goal\"]\n",
" tasks = [\n",
" f\"明确目标:{goal}\",\n",
" \"列出目标用户和使用场景\",\n",
" \"设计可执行的行动步骤\",\n",
" \"整理成清晰的建议清单\",\n",
" ]\n",
" return {\"tasks\": tasks}\n",
"\n",
"\n",
"def worker_node(state: TaskState):\n",
" results = []\n",
" for task in state[\"tasks\"]:\n",
" result = f\"完成任务:{task}\"\n",
" results.append(result)\n",
" return {\"results\": results}\n",
"\n",
"\n",
"def summary_node(state: TaskState):\n",
" lines = [\"最终任务调度结果:\"]\n",
" for index, result in enumerate(state[\"results\"], start=1):\n",
" lines.append(f\"{index}. {result}\")\n",
" final_answer = \"\\n\".join(lines)\n",
" return {\"final_answer\": final_answer}\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码定义了 3 个节点函数:\n",
"\n",
"1. `planner_node(state)`:从 `state[\"goal\"]` 读取用户目标,然后生成 `tasks` 任务列表。返回 `{\"tasks\": tasks}` 表示把任务列表写回 State。\n",
"2. `worker_node(state)`:读取 `state[\"tasks\"]`,循环执行每个任务。这里用字符串模拟执行结果,真实项目中可以替换为调用工具、调用模型、查询数据库等操作。\n",
"3. `summary_node(state)`:读取 `state[\"results\"]`,把多个结果拼接成一段最终答案。\n",
"\n",
"需要注意:节点函数通常不需要返回完整 State只返回自己负责更新的字段即可。LangGraph 会把这些字段合并回当前 State。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 8. 用 LangGraph 连接节点\n",
"\n",
"现在有了节点函数,下一步就是把这些节点连成一张图。\n",
"\n",
"流程如下:\n",
"\n",
"```text\n",
"START -> planner -> worker -> summary -> END\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import END, START, StateGraph\n",
"\n",
"\n",
"workflow = StateGraph(TaskState)\n",
"\n",
"workflow.add_node(\"planner\", planner_node)\n",
"workflow.add_node(\"worker\", worker_node)\n",
"workflow.add_node(\"summary\", summary_node)\n",
"\n",
"workflow.add_edge(START, \"planner\")\n",
"workflow.add_edge(\"planner\", \"worker\")\n",
"workflow.add_edge(\"worker\", \"summary\")\n",
"workflow.add_edge(\"summary\", END)\n",
"\n",
"app = workflow.compile()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码把节点组织成可运行的工作流:\n",
"\n",
"1. `StateGraph(TaskState)` 创建一张图,并说明这张图中的数据结构是 `TaskState`。\n",
"2. `add_node(\"planner\", planner_node)` 把 `planner_node` 注册成名为 `planner` 的节点。\n",
"3. `add_edge(START, \"planner\")` 表示工作流从 `planner` 节点开始。\n",
"4. `add_edge(\"planner\", \"worker\")` 表示任务分解完成后,进入任务执行节点。\n",
"5. `add_edge(\"worker\", \"summary\")` 表示任务执行完成后,进入结果汇总节点。\n",
"6. `add_edge(\"summary\", END)` 表示汇总完成后,工作流结束。\n",
"7. `workflow.compile()` 会把流程图编译成一个可以调用的应用对象 `app`。\n",
"\n",
"这一步相当于把流程图画好,并告诉程序每一步应该怎么走。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 9. 运行任务调度智能体\n",
"\n",
"工作流编译完成后,就可以传入用户目标并运行了。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"initial_state = {\n",
" \"goal\": \"为一家咖啡店设计会员运营方案\",\n",
" \"tasks\": [],\n",
" \"results\": [],\n",
" \"final_answer\": \"\",\n",
"}\n",
"\n",
"final_state = app.invoke(initial_state)\n",
"\n",
"print(final_state[\"final_answer\"])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码负责真正运行工作流:\n",
"\n",
"1. `initial_state` 是初始状态,里面放入用户目标 `goal`,其余字段先给空值。\n",
"2. `app.invoke(initial_state)` 会启动 LangGraph 工作流。数据会依次经过 `planner`、`worker`、`summary` 三个节点。\n",
"3. 运行结束后返回 `final_state`,它包含整个流程运行后的完整状态。\n",
"4. `final_state[\"final_answer\"]` 取出最终汇总结果并打印。\n",
"\n",
"从这个例子可以看到LangGraph 的优势是把复杂流程拆成清晰节点,每个节点只负责一件事。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 10. 加入条件判断:根据任务复杂度选择路径\n",
"\n",
"真实任务中,不是所有问题都需要复杂调度。\n",
"\n",
"例如:\n",
"\n",
"- 简单问题:直接回答即可\n",
"- 复杂问题:先拆分,再执行,再汇总\n",
"\n",
"LangGraph 支持条件边,可以根据 State 的内容决定下一步走哪条路径。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
"\n",
"\n",
"def judge_complexity(state: TaskState) -> Literal[\"simple\", \"complex\"]:\n",
" goal = state[\"goal\"]\n",
" if len(goal) <= 12:\n",
" return \"simple\"\n",
" return \"complex\"\n",
"\n",
"\n",
"def direct_answer_node(state: TaskState):\n",
" answer = f\"这是一个简单任务,可以直接处理:{state['goal']}\"\n",
" return {\"final_answer\": answer}\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码新增了两个函数:\n",
"\n",
"1. `judge_complexity(state)` 用来判断任务复杂度。这里为了教学简单,用目标文字长度作为判断标准:短任务走 `simple`,长任务走 `complex`。真实项目中可以让大模型判断复杂度。\n",
"2. `Literal[\"simple\", \"complex\"]` 表示这个函数只会返回两个固定值之一,方便我们理解后续条件分支。\n",
"3. `direct_answer_node(state)` 是简单任务的处理节点,不做任务分解,直接生成最终答案。\n",
"\n",
"这个例子说明:任务调度不一定永远走同一条流程,可以根据任务情况动态选择路径。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 11. 构建带条件分支的工作流\n",
"\n",
"下面把简单路径和复杂路径组合到同一张图里。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"branch_workflow = StateGraph(TaskState)\n",
"\n",
"branch_workflow.add_node(\"direct_answer\", direct_answer_node)\n",
"branch_workflow.add_node(\"planner\", planner_node)\n",
"branch_workflow.add_node(\"worker\", worker_node)\n",
"branch_workflow.add_node(\"summary\", summary_node)\n",
"\n",
"branch_workflow.add_conditional_edges(\n",
" START,\n",
" judge_complexity,\n",
" {\n",
" \"simple\": \"direct_answer\",\n",
" \"complex\": \"planner\",\n",
" },\n",
")\n",
"\n",
"branch_workflow.add_edge(\"direct_answer\", END)\n",
"branch_workflow.add_edge(\"planner\", \"worker\")\n",
"branch_workflow.add_edge(\"worker\", \"summary\")\n",
"branch_workflow.add_edge(\"summary\", END)\n",
"\n",
"branch_app = branch_workflow.compile()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码构建了一个带分支的任务调度工作流:\n",
"\n",
"1. `add_conditional_edges` 表示添加条件边。它会先调用 `judge_complexity`,再根据返回值选择下一步。\n",
"2. 如果返回 `simple`,流程进入 `direct_answer` 节点,然后直接结束。\n",
"3. 如果返回 `complex`,流程进入 `planner` 节点,再继续执行 `worker` 和 `summary`。\n",
"4. 这张图有两条路径:一条适合简单任务,一条适合复杂任务。\n",
"\n",
"这就是任务调度型智能体的一个关键能力:**不是固定执行所有步骤,而是根据任务情况选择合适流程**。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 12. 测试条件分支效果\n",
"\n",
"分别输入一个简单任务和一个复杂任务,观察工作流走向的差异。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"simple_state = {\n",
" \"goal\": \"写标题\",\n",
" \"tasks\": [],\n",
" \"results\": [],\n",
" \"final_answer\": \"\",\n",
"}\n",
"\n",
"complex_state = {\n",
" \"goal\": \"为一家咖啡店设计会员运营方案,并给出执行步骤\",\n",
" \"tasks\": [],\n",
" \"results\": [],\n",
" \"final_answer\": \"\",\n",
"}\n",
"\n",
"print(\"简单任务结果:\")\n",
"print(branch_app.invoke(simple_state)[\"final_answer\"])\n",
"\n",
"print(\"\\n复杂任务结果\")\n",
"print(branch_app.invoke(complex_state)[\"final_answer\"])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码测试了条件分支:\n",
"\n",
"1. `simple_state` 的目标是“写标题”,文字较短,所以 `judge_complexity` 返回 `simple`,流程会直接进入 `direct_answer`。\n",
"2. `complex_state` 的目标更长,所以返回 `complex`,流程会进入 `planner -> worker -> summary`。\n",
"3. 两次都调用 `branch_app.invoke(...)`,但由于输入不同,工作流自动选择了不同执行路径。\n",
"\n",
"这说明任务调度型智能体可以做到“简单任务快速处理,复杂任务分步处理”。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 13. 小结\n",
"\n",
"本节课学习了任务调度型智能体的基本设计方法:\n",
"\n",
"1. 任务调度型智能体适合处理多步骤、复杂目标。\n",
"2. 它的核心流程是:任务输入、任务分解、任务执行、结果汇总。\n",
"3. LangGraph 可以把每个步骤定义成节点,并用边连接成工作流。\n",
"4. State 是工作流中流动的数据,节点通过读取和更新 State 协同完成任务。\n",
"5. 条件边可以让智能体根据任务情况选择不同执行路径。\n",
"\n",
"掌握这些内容后,就可以继续扩展:把 `worker_node` 替换成真实工具调用、搜索引擎调用、数据库查询或大模型调用,从而构建更实用的自动化任务处理智能体。\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 14. 练习题\n",
"\n",
"1. 修改 `planner_node`,让它针对“写一篇公众号文章”生成任务列表。\n",
"2. 修改 `worker_node`,让不同任务返回不同格式的结果。\n",
"3. 修改 `judge_complexity`,不要用文字长度判断,而是根据是否包含“方案”“分析”“计划”等关键词判断复杂度。\n",
"4. 尝试新增一个 `review_node`,在最终输出前检查结果是否完整。\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
}