Files
ai-agent-dev/23_人机协作.ipynb
2026-07-08 10:09:42 +08:00

835 lines
26 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": [
"# 23 人机协作\n",
"\n",
"## 学习目标\n",
"1. 理解人机协作Human-in-the-loop在智能体中的重要性\n",
"2. 掌握 LangGraph 中暂停(`interrupt`)和恢复(`resume`)机制\n",
"3. 能够设计需要人工确认或补充输入的智能体流程\n",
"4. 理解检查点在暂停与恢复中的作用\n",
"5. 能够构建带人工审批环节的基础智能体工作流"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 什么是人机协作\n",
"\n",
"人机协作,也叫 Human-in-the-loop意思是**智能体不是从头到尾完全自动执行,而是在关键步骤暂停下来,让人参与判断、确认或补充信息。**\n",
"\n",
"普通自动流程可能是这样:\n",
"\n",
"```\n",
"输入 -> 智能体处理 -> 自动执行 -> 输出结果\n",
"```\n",
"\n",
"人机协作流程更像这样:\n",
"\n",
"```\n",
"输入 -> 智能体处理 -> 暂停等待人工确认 -> 继续执行 -> 输出结果\n",
"```\n",
"\n",
"也就是说,人不是在流程外面旁观,而是成为流程中的一个关键环节。\n",
"\n",
"在人机协作中AI 适合做:\n",
"\n",
"- 整理信息\n",
"- 生成草稿\n",
"- 给出建议\n",
"- 自动处理低风险步骤\n",
"\n",
"人更适合做:\n",
"\n",
"- 最终确认\n",
"- 风险判断\n",
"- 补充缺失信息\n",
"- 决定是否继续执行\n",
"\n",
"这就是人机协作的核心思想:**让 AI 提高效率,让人把控关键决策。**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. 为什么智能体需要人工介入\n",
"\n",
"很多智能体任务不能完全交给模型自动完成。原因主要有三类。\n",
"\n",
"### 2.1 有些操作风险较高\n",
"例如:\n",
"\n",
"- 发送正式邮件\n",
"- 删除数据\n",
"- 提交订单\n",
"- 执行付款\n",
"- 修改线上配置\n",
"\n",
"这些操作一旦执行,可能造成真实影响,所以最好先让人确认。\n",
"\n",
"### 2.2 有些信息 AI 不知道\n",
"例如用户说:\n",
"\n",
"```text\n",
"帮我给客户写一封邮件。\n",
"```\n",
"\n",
"但 AI 可能不知道客户姓名、邮件目的、语气要求、是否要附带报价。\n",
"\n",
"这时流程应该暂停,让用户补充信息。\n",
"\n",
"### 2.3 有些判断需要人负责\n",
"例如 AI 生成了一个方案,但最终是否采用,需要人做业务判断。\n",
"\n",
"所以,人机协作不是因为 AI 不够强,而是因为很多场景天然需要人工负责。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. LangGraph 中的人机协作机制\n",
"\n",
"在 LangGraph 中,人机协作通常依赖两个核心动作:\n",
"\n",
"| 动作 | 含义 | 作用 |\n",
"| --- | --- | --- |\n",
"| `interrupt(...)` | 暂停流程 | 把需要人工处理的信息抛出来 |\n",
"| `Command(resume=...)` | 恢复流程 | 把人工输入传回图中,继续执行 |\n",
"\n",
"可以这样理解:\n",
"\n",
"- `interrupt` 像是智能体举手说:‘这里需要人来决定一下’\n",
"- `Command(resume=...)` 像是人回复:‘我决定好了,你继续吧’\n",
"\n",
"不过要注意暂停和恢复必须依赖检查点checkpoint。\n",
"\n",
"因为图暂停后,需要记住自己停在了哪里。等人给出答案后,才能从正确的位置继续执行。\n",
"\n",
"所以,人机协作一般会和 `MemorySaver` 一起使用。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. 第一个例子:人工确认后再继续\n",
"\n",
"先从最简单的例子开始:智能体准备执行一个操作,但在执行前需要人工确认。\n",
"\n",
"流程如下:\n",
"\n",
"```\n",
"START -> request_approval -> perform_action -> END\n",
"```\n",
"\n",
"其中 `request_approval` 会暂停,等待用户输入 `yes` 或 `no`。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7085b2d5",
"metadata": {},
"outputs": [],
"source": [
"from typing_extensions import TypedDict\n",
"from langgraph.graph import StateGraph, START, END\n",
"from langgraph.types import interrupt, Command\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"\n",
"class ApprovalState(TypedDict):\n",
" task: str\n",
" approved: bool\n",
" result: str\n",
"\n",
"def request_approval(state: ApprovalState):\n",
" answer = interrupt({\n",
" 'question': '是否允许执行这个任务?',\n",
" 'task': state['task'],\n",
" 'options': ['yes', 'no']\n",
" })\n",
" return {'approved': answer == 'yes'}\n",
"\n",
"def perform_action(state: ApprovalState):\n",
" if state['approved']:\n",
" return {'result': f'任务已执行:{state[\"task\"]}'}\n",
" return {'result': f'任务已取消:{state[\"task\"]}'}\n",
"\n",
"builder = StateGraph(ApprovalState)\n",
"builder.add_node('request_approval', request_approval)\n",
"builder.add_node('perform_action', perform_action)\n",
"\n",
"builder.add_edge(START, 'request_approval')\n",
"builder.add_edge('request_approval', 'perform_action')\n",
"builder.add_edge('perform_action', END)\n",
"\n",
"memory = MemorySaver()\n",
"graph = builder.compile(checkpointer=memory)\n",
"\n",
"config = {'configurable': {'thread_id': 'approval-demo'}}\n",
"\n",
"first_result = graph.invoke(\n",
" {'task': '发送项目周报邮件', 'approved': False, 'result': ''},\n",
" config=config\n",
")\n",
"\n",
"print(first_result)"
]
},
{
"cell_type": "markdown",
"id": "07b99ca8",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这个例子第一次使用了 `interrupt`,所以需要重点理解。\n",
"\n",
"#### `ApprovalState`\n",
"状态里有三个字段:\n",
"\n",
"- `task`:准备执行的任务\n",
"- `approved`:人工是否批准\n",
"- `result`:最终执行结果\n",
"\n",
"也就是说,整张图围绕一个任务展开,先确认,再执行或取消。\n",
"\n",
"#### `request_approval` 节点\n",
"这个节点的核心代码是:\n",
"\n",
"```python\n",
"answer = interrupt({...})\n",
"```\n",
"\n",
"当代码执行到这里时,图不会继续往下走,而是暂停下来。\n",
"\n",
"`interrupt` 里面传入的是要展示给人的信息。这里包括:\n",
"\n",
"- 问题:是否允许执行这个任务\n",
"- 任务内容:`state['task']`\n",
"- 可选项:`yes` 或 `no`\n",
"\n",
"#### 为什么 `interrupt` 后面还有 return\n",
"这行代码:\n",
"\n",
"```python\n",
"return {'approved': answer == 'yes'}\n",
"```\n",
"\n",
"不是第一次暂停时立刻执行完成的。\n",
"\n",
"第一次运行到 `interrupt` 时,图会停住。等我们用 `Command(resume='yes')` 恢复时,`interrupt` 才会把 `'yes'` 作为返回值交给 `answer`,然后继续执行后面的 `return`。\n",
"\n",
"#### `perform_action` 节点\n",
"这个节点根据 `approved` 决定结果:\n",
"\n",
"- 如果 `approved=True`,说明人工批准,任务执行\n",
"- 如果 `approved=False`,说明人工拒绝,任务取消\n",
"\n",
"#### 为什么需要 `MemorySaver`\n",
"因为图会暂停。暂停之后,系统必须记住:\n",
"\n",
"- 当前执行到哪个节点\n",
"- 当前状态是什么\n",
"- 后面应该从哪里继续\n",
"\n",
"这些信息都需要检查点保存。`MemorySaver` 就是一个内存版检查点保存器。\n",
"\n",
"#### 第一次运行会得到什么\n",
"第一次 `invoke` 不会直接得到最终结果,而是会得到一个包含中断信息的结果。\n",
"\n",
"这表示图已经暂停,正在等待人工输入。"
]
},
{
"cell_type": "markdown",
"id": "185c4df9",
"metadata": {},
"source": [
"## 5. 恢复流程:用 `Command(resume=...)` 传回人工决定\n",
"\n",
"上一步图已经暂停。现在我们模拟人工选择 `yes`,让流程继续执行。\n",
"\n",
"恢复时要使用同一个 `config`,尤其是同一个 `thread_id`。\n",
"\n",
"因为 LangGraph 需要根据 `thread_id` 找回刚才暂停的那一次运行。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0c18357d",
"metadata": {},
"outputs": [],
"source": [
"second_result = graph.invoke(\n",
" Command(resume='yes'),\n",
" config=config\n",
")\n",
"\n",
"print(second_result)"
]
},
{
"cell_type": "markdown",
"id": "31abd5f8",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这一段代码负责恢复刚才暂停的流程。\n",
"\n",
"#### `Command(resume='yes')`\n",
"它的意思是:把人工输入 `'yes'` 传回给刚才的 `interrupt`。\n",
"\n",
"也就是说,前面这行代码:\n",
"\n",
"```python\n",
"answer = interrupt(...)\n",
"```\n",
"\n",
"会在恢复后得到:\n",
"\n",
"```python\n",
"answer = 'yes'\n",
"```\n",
"\n",
"然后节点继续执行:\n",
"\n",
"```python\n",
"return {'approved': answer == 'yes'}\n",
"```\n",
"\n",
"于是 `approved` 会变成 `True`。\n",
"\n",
"#### 为什么还要传 `config=config`\n",
"因为恢复流程必须知道要恢复哪一次暂停。\n",
"\n",
"`thread_id='approval-demo'` 就像这次流程的编号。\n",
"\n",
"如果换了另一个 `thread_id`LangGraph 就找不到刚才暂停的位置。\n",
"\n",
"#### 恢复后的执行路径\n",
"恢复后流程会继续:\n",
"\n",
"```\n",
"request_approval -> perform_action -> END\n",
"```\n",
"\n",
"最终会得到:\n",
"\n",
"```text\n",
"任务已执行:发送项目周报邮件\n",
"```\n",
"\n",
"这就是最基本的人机协作流程。"
]
},
{
"cell_type": "markdown",
"id": "1fd00b84",
"metadata": {},
"source": [
"## 6. 第二个例子:人工补充缺失信息\n",
"\n",
"人机协作不只是审批,也可以用来补充信息。\n",
"\n",
"例如用户想让智能体写邮件,但没有提供收件人。\n",
"\n",
"流程可以这样设计:\n",
"\n",
"```\n",
"START -> check_info -> draft_email -> END\n",
"```\n",
"\n",
"如果缺少收件人,`check_info` 就暂停,让用户补充。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4000512e",
"metadata": {},
"outputs": [],
"source": [
"from typing_extensions import TypedDict\n",
"from langgraph.graph import StateGraph, START, END\n",
"from langgraph.types import interrupt, Command\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"\n",
"class EmailState(TypedDict):\n",
" topic: str\n",
" recipient: str\n",
" email_draft: str\n",
"\n",
"def check_info(state: EmailState):\n",
" if state['recipient']:\n",
" return {}\n",
"\n",
" recipient = interrupt({\n",
" 'question': '请补充邮件收件人',\n",
" 'current_topic': state['topic']\n",
" })\n",
" return {'recipient': recipient}\n",
"\n",
"def draft_email(state: EmailState):\n",
" draft = f'''收件人:{state[\"recipient\"]}\n",
"主题:{state[\"topic\"]}\n",
"\n",
"你好,\n",
"这里是一封关于“{state[\"topic\"]}”的邮件草稿。\n",
"请根据具体情况补充正文细节。\n",
"'''\n",
" return {'email_draft': draft}\n",
"\n",
"builder = StateGraph(EmailState)\n",
"builder.add_node('check_info', check_info)\n",
"builder.add_node('draft_email', draft_email)\n",
"\n",
"builder.add_edge(START, 'check_info')\n",
"builder.add_edge('check_info', 'draft_email')\n",
"builder.add_edge('draft_email', END)\n",
"\n",
"email_graph = builder.compile(checkpointer=MemorySaver())\n",
"email_config = {'configurable': {'thread_id': 'email-demo'}}\n",
"\n",
"first_result = email_graph.invoke(\n",
" {'topic': '下周项目会议安排', 'recipient': '', 'email_draft': ''},\n",
" config=email_config\n",
")\n",
"\n",
"print(first_result)"
]
},
{
"cell_type": "markdown",
"id": "84056ad6",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这个例子展示了人机协作的另一种常见用途:补充缺失信息。\n",
"\n",
"#### `EmailState`\n",
"状态包含三个字段:\n",
"\n",
"- `topic`:邮件主题\n",
"- `recipient`:邮件收件人\n",
"- `email_draft`:生成的邮件草稿\n",
"\n",
"#### `check_info` 节点\n",
"这个节点负责检查信息是否完整。\n",
"\n",
"如果 `recipient` 已经有值,就返回 `{}`,表示不需要修改状态。\n",
"\n",
"如果 `recipient` 为空,就执行:\n",
"\n",
"```python\n",
"recipient = interrupt({...})\n",
"```\n",
"\n",
"这会暂停流程,并向用户请求收件人信息。\n",
"\n",
"#### 为什么这里不直接报错\n",
"在真实智能体中,缺少信息不一定是错误。\n",
"\n",
"更好的做法是:停下来问人。\n",
"\n",
"这就是人机协作比普通异常处理更适合智能体流程的地方。\n",
"\n",
"#### `draft_email` 节点\n",
"这个节点依赖完整信息生成邮件草稿。\n",
"\n",
"它会读取:\n",
"\n",
"- `state['recipient']`\n",
"- `state['topic']`\n",
"\n",
"然后写入 `email_draft`。\n",
"\n",
"#### 第一次运行为什么会暂停\n",
"因为初始状态中:\n",
"\n",
"```python\n",
"'recipient': ''\n",
"```\n",
"\n",
"所以流程会停在 `check_info`,等待人补充收件人。"
]
},
{
"cell_type": "markdown",
"id": "cf5e9b4b",
"metadata": {},
"source": [
"## 7. 恢复并补充收件人\n",
"\n",
"现在模拟人工输入收件人,例如:`张经理`。\n",
"\n",
"恢复后,流程会继续进入 `draft_email`,并生成邮件草稿。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "081941b0",
"metadata": {},
"outputs": [],
"source": [
"final_result = email_graph.invoke(\n",
" Command(resume='张经理'),\n",
" config=email_config\n",
")\n",
"\n",
"print(final_result['email_draft'])"
]
},
{
"cell_type": "markdown",
"id": "8c7dade8",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这一段代码把人工补充的信息传回给图。\n",
"\n",
"#### `Command(resume='张经理')`\n",
"它会让刚才暂停的 `interrupt` 返回 `'张经理'`。\n",
"\n",
"于是这行代码:\n",
"\n",
"```python\n",
"recipient = interrupt(...)\n",
"```\n",
"\n",
"恢复后就相当于:\n",
"\n",
"```python\n",
"recipient = '张经理'\n",
"```\n",
"\n",
"然后节点返回:\n",
"\n",
"```python\n",
"return {'recipient': recipient}\n",
"```\n",
"\n",
"状态中的收件人就被补上了。\n",
"\n",
"#### 后续为什么能生成邮件草稿\n",
"恢复后,图会继续执行 `draft_email`。\n",
"\n",
"这时状态已经包含:\n",
"\n",
"- `topic='下周项目会议安排'`\n",
"- `recipient='张经理'`\n",
"\n",
"所以就可以生成完整的邮件草稿。\n",
"\n",
"#### 这个例子的重点\n",
"人机协作不是只能做‘同意/不同意’。\n",
"\n",
"它也可以用来让人补充缺失信息,然后让图继续完成后续任务。"
]
},
{
"cell_type": "markdown",
"id": "6090d74f",
"metadata": {},
"source": [
"## 8. 第三个例子:人工修改 AI 草稿\n",
"\n",
"在很多真实场景中AI 不一定直接执行最终结果,而是先生成一个草稿,让人修改或确认。\n",
"\n",
"例如:\n",
"\n",
"- AI 生成邮件草稿,人修改后再发送\n",
"- AI 生成计划,人确认后再执行\n",
"- AI 生成 SQL人审核后再运行\n",
"\n",
"下面我们用一个简单的任务计划示例。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88a81e02",
"metadata": {},
"outputs": [],
"source": [
"from typing_extensions import TypedDict\n",
"from langgraph.graph import StateGraph, START, END\n",
"from langgraph.types import interrupt, Command\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"\n",
"class PlanState(TypedDict):\n",
" goal: str\n",
" draft_plan: str\n",
" final_plan: str\n",
"\n",
"def create_draft_plan(state: PlanState):\n",
" draft = f'''目标:{state[\"goal\"]}\n",
"步骤 1明确当前需求\n",
"步骤 2拆分任务并安排执行顺序\n",
"步骤 3检查结果并总结经验\n",
"'''\n",
" return {'draft_plan': draft}\n",
"\n",
"def review_plan(state: PlanState):\n",
" revised_plan = interrupt({\n",
" 'question': '请审核并修改计划草稿',\n",
" 'draft_plan': state['draft_plan']\n",
" })\n",
" return {'final_plan': revised_plan}\n",
"\n",
"builder = StateGraph(PlanState)\n",
"builder.add_node('create_draft_plan', create_draft_plan)\n",
"builder.add_node('review_plan', review_plan)\n",
"\n",
"builder.add_edge(START, 'create_draft_plan')\n",
"builder.add_edge('create_draft_plan', 'review_plan')\n",
"builder.add_edge('review_plan', END)\n",
"\n",
"plan_graph = builder.compile(checkpointer=MemorySaver())\n",
"plan_config = {'configurable': {'thread_id': 'plan-demo'}}\n",
"\n",
"review_result = plan_graph.invoke(\n",
" {'goal': '准备一次 LangGraph 分享', 'draft_plan': '', 'final_plan': ''},\n",
" config=plan_config\n",
")\n",
"\n",
"print(review_result)"
]
},
{
"cell_type": "markdown",
"id": "88b97a79",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这个例子展示的是AI 先生成草稿,人再修改确认’的模式。\n",
"\n",
"#### `PlanState`\n",
"状态中有三个字段:\n",
"\n",
"- `goal`:目标\n",
"- `draft_plan`AI 或程序生成的草稿计划\n",
"- `final_plan`:人工确认后的最终计划\n",
"\n",
"#### `create_draft_plan` 节点\n",
"这个节点根据目标生成一个初步计划。\n",
"\n",
"这里为了教学简单,计划是用模板字符串生成的。\n",
"\n",
"在真实项目中,这一步也可以换成大模型生成。\n",
"\n",
"#### `review_plan` 节点\n",
"这个节点调用 `interrupt`,把草稿交给人审核。\n",
"\n",
"传给人的信息包括:\n",
"\n",
"- 问题:请审核并修改计划草稿\n",
"- 当前草稿:`state['draft_plan']`\n",
"\n",
"人可以直接接受,也可以修改后再返回。\n",
"\n",
"#### 为什么返回 `final_plan`\n",
"AI 生成的是草稿,不一定等于最终结果。\n",
"\n",
"人工修改后的内容才写入 `final_plan`。\n",
"\n",
"这能清楚地区分:\n",
"\n",
"- AI 草稿是什么\n",
"- 人工最终确认的版本是什么\n",
"\n",
"这在真实工作流中非常重要,因为最终责任通常属于人工确认后的结果。"
]
},
{
"cell_type": "markdown",
"id": "f0da01c5",
"metadata": {},
"source": [
"## 9. 恢复并提交人工修改后的计划\n",
"\n",
"现在模拟人工审核后,提交一个修改版计划。"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "31d862b2",
"metadata": {},
"outputs": [],
"source": [
"human_revised_plan = '''目标:准备一次 LangGraph 分享\n",
"步骤 1先介绍图结构、状态和边的概念\n",
"步骤 2演示一个最小可运行的 LangGraph 示例\n",
"步骤 3重点讲解人机协作中的 interrupt 和 resume\n",
"步骤 4最后总结适合人机协作的真实业务场景\n",
"'''\n",
"\n",
"final_plan_result = plan_graph.invoke(\n",
" Command(resume=human_revised_plan),\n",
" config=plan_config\n",
")\n",
"\n",
"print(final_plan_result['final_plan'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 代码解释\n",
"\n",
"这段代码模拟人工修改草稿后,把最终版本交回给图。\n",
"\n",
"#### `human_revised_plan`\n",
"这是人工修改后的计划。\n",
"\n",
"它和原始草稿相比,更具体,也更贴近分享主题。\n",
"\n",
"#### `Command(resume=human_revised_plan)`\n",
"这表示把人工修改后的计划传回给 `interrupt`。\n",
"\n",
"恢复后,`review_plan` 节点会继续执行:\n",
"\n",
"```python\n",
"return {'final_plan': revised_plan}\n",
"```\n",
"\n",
"于是最终状态中就有了人工确认后的 `final_plan`。\n",
"\n",
"#### 这个模式有什么用\n",
"这类流程非常适合高风险或高要求任务。\n",
"\n",
"例如:\n",
"\n",
"- AI 生成合同条款,人审核后确认\n",
"- AI 生成客户邮件,人修改后发送\n",
"- AI 生成执行计划,人确认后落地\n",
"\n",
"核心思想是AI 负责初稿,人负责把关。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 10. 人机协作流程的关键点\n",
"\n",
"设计人机协作流程时,要特别注意下面几点。\n",
"\n",
"### 10.1 暂停点要放在关键位置\n",
"不是每一步都需要人确认。\n",
"\n",
"一般只在这些地方暂停:\n",
"\n",
"- 信息缺失\n",
"- 风险较高\n",
"- 需要业务判断\n",
"- 执行前需要最终确认\n",
"\n",
"### 10.2 给人的信息要清楚\n",
"`interrupt` 传出的内容应该让人一眼看懂:\n",
"\n",
"- 当前要做什么\n",
"- 为什么要暂停\n",
"- 希望人提供什么\n",
"- 可选项有哪些\n",
"\n",
"### 10.3 恢复时必须使用同一个线程\n",
"恢复流程时,必须使用同一个 `thread_id`。\n",
"\n",
"否则图找不到之前暂停的位置。\n",
"\n",
"### 10.4 不要滥用人工确认\n",
"如果每一步都让人确认,智能体就会变得很繁琐。\n",
"\n",
"合理做法是:低风险步骤自动执行,高风险步骤人工把关。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 11. 人机协作和普通 input() 的区别\n",
"\n",
"你可能会问:为什么不直接用 Python 的 `input()`\n",
"\n",
"因为 LangGraph 的 `interrupt` 不只是简单输入,它更适合智能体工作流。\n",
"\n",
"| 对比项 | `input()` | `interrupt` |\n",
"| --- | --- | --- |\n",
"| 是否适合 notebook / 服务端流程 | 不太适合 | 更适合 |\n",
"| 是否能保存暂停状态 | 不能 | 可以,依赖检查点 |\n",
"| 是否能恢复到图中原位置 | 不能 | 可以 |\n",
"| 是否适合多用户多会话 | 弱 | 强,配合 `thread_id` |\n",
"| 是否和 LangGraph 状态集成 | 否 | 是 |\n",
"\n",
"所以在人机协作智能体中,推荐使用 `interrupt`,而不是普通 `input()`。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 12. 本节小结\n",
"\n",
"本节最重要的内容有五点:\n",
"\n",
"1. **人机协作是让人在关键节点参与智能体流程**\n",
"2. **`interrupt` 用于暂停流程,并把需要人工处理的信息交出去**\n",
"3. **`Command(resume=...)` 用于把人工输入传回图中,并恢复执行**\n",
"4. **暂停和恢复必须依赖检查点,通常会配合 `MemorySaver` 使用**\n",
"5. **人工介入适合审批、补充信息、修改草稿和高风险操作确认**\n",
"\n",
"掌握人机协作后,智能体就不再只是自动执行脚本,而可以变成一个真正能和人配合工作的流程系统。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 13. 本节练习\n",
"\n",
"1. 修改第一个示例,把任务改成‘删除临时文件’,观察批准和拒绝的结果\n",
"2. 修改第二个示例,让缺少邮件主题时也触发一次人工补充\n",
"3. 修改第三个示例,让人工可以输入 `accept` 表示直接接受草稿\n",
"4. 思考:哪些智能体操作必须经过人工确认?\n",
"5. 思考:如果一个系统有多个用户同时使用,为什么 `thread_id` 很重要?"
]
}
],
"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": 5
}