636 lines
20 KiB
Plaintext
636 lines
20 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "cc615887",
|
||
"metadata": {},
|
||
"source": [
|
||
"# 16 状态管理\n",
|
||
"\n",
|
||
"## 学习目标\n",
|
||
"1. 理解 LangGraph 中状态(State)的设计与作用\n",
|
||
"2. 掌握使用 TypedDict 定义图状态\n",
|
||
"3. 学会在节点间传递、读取和更新状态\n",
|
||
"4. 理解多字段状态在实际流程中的用法\n",
|
||
"5. 避免状态设计中的常见问题"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "dfebe943",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 1. 什么是状态管理\n",
|
||
"\n",
|
||
"在 LangGraph 中,**状态(State)** 可以理解为“流程运行时一直随身携带的一份数据”。\n",
|
||
"\n",
|
||
"前一节我们已经接触过状态,例如:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"{'value': 3}\n",
|
||
"```\n",
|
||
"\n",
|
||
"或者:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"{'score': 85, 'result': '成绩合格,顺利通过'}\n",
|
||
"```\n",
|
||
"\n",
|
||
"这些数据会在图的不同节点之间传来传去。\n",
|
||
"\n",
|
||
"状态管理要解决的问题,其实很简单:\n",
|
||
"\n",
|
||
"- 当前流程已经走到哪一步了?\n",
|
||
"- 前面节点产出的结果,后面节点怎么接着用?\n",
|
||
"- 某个节点更新了数据,后续节点如何读取最新值?\n",
|
||
"\n",
|
||
"你可以把状态想象成一张‘流程记录卡’:\n",
|
||
"\n",
|
||
"- 每经过一个节点,就在卡上补充信息\n",
|
||
"- 后面的节点只需要看这张卡,就知道前面发生了什么\n",
|
||
"\n",
|
||
"所以,**状态管理的本质,就是管理这张共享记录卡。**"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a40150ac",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 2. 为什么状态很重要\n",
|
||
"\n",
|
||
"如果没有状态,节点之间就很难协作。\n",
|
||
"\n",
|
||
"例如一个流程要完成下面几步:\n",
|
||
"\n",
|
||
"1. 接收用户输入\n",
|
||
"2. 提取关键词\n",
|
||
"3. 根据关键词生成结论\n",
|
||
"\n",
|
||
"那么问题来了:\n",
|
||
"\n",
|
||
"- 第二步提取出来的关键词,怎么交给第三步?\n",
|
||
"- 第一节点记录的原始问题,第三步还能不能访问?\n",
|
||
"- 如果流程还要继续加步骤,数据还能不能统一管理?\n",
|
||
"\n",
|
||
"答案就是:都放进状态里。\n",
|
||
"\n",
|
||
"状态的几个核心价值:\n",
|
||
"\n",
|
||
"- **统一传递数据**:所有节点都从同一个地方读取信息\n",
|
||
"- **减少参数混乱**:不用手动给每个函数传很多单独变量\n",
|
||
"- **便于扩展流程**:后面增加新节点时,只要继续读写状态即可\n",
|
||
"- **便于调试**:看状态就能知道流程执行到了什么程度"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "644129aa",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 3. 用 TypedDict 定义状态\n",
|
||
"\n",
|
||
"在 LangGraph 中,我们通常用 `TypedDict` 来定义状态结构。\n",
|
||
"\n",
|
||
"这样做有两个好处:\n",
|
||
"\n",
|
||
"1. 让自己清楚状态里有哪些字段\n",
|
||
"2. 让代码更容易维护和理解\n",
|
||
"\n",
|
||
"下面先看一个最简单的例子。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "08a43781",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing_extensions import TypedDict\n",
|
||
"\n",
|
||
"class UserState(TypedDict):\n",
|
||
" name: str\n",
|
||
" age: int\n",
|
||
" city: str\n",
|
||
"\n",
|
||
"example_state: UserState = {\n",
|
||
" 'name': '张三',\n",
|
||
" 'age': 20,\n",
|
||
" 'city': '北京'\n",
|
||
"}\n",
|
||
"\n",
|
||
"print(example_state)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "4cdd8784",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这段代码本身还没有进入 LangGraph,只是在说明状态应该怎样定义。\n",
|
||
"\n",
|
||
"#### `TypedDict` 是什么\n",
|
||
"`TypedDict` 可以理解为‘带字段说明的字典’。\n",
|
||
"\n",
|
||
"普通字典也能写成:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"{'name': '张三', 'age': 20, 'city': '北京'}\n",
|
||
"```\n",
|
||
"\n",
|
||
"但如果项目变复杂,单纯靠大家记忆“这个字典里应该有哪些字段”会很容易出错。\n",
|
||
"\n",
|
||
"`TypedDict` 的作用就是把这种约定明确写出来。\n",
|
||
"\n",
|
||
"#### `class UserState(TypedDict)`\n",
|
||
"这表示我们定义了一种状态结构,名字叫 `UserState`。\n",
|
||
"\n",
|
||
"它要求状态中有三个字段:\n",
|
||
"\n",
|
||
"- `name`:字符串\n",
|
||
"- `age`:整数\n",
|
||
"- `city`:字符串\n",
|
||
"\n",
|
||
"#### `example_state: UserState = {...}`\n",
|
||
"这表示我们创建了一个符合 `UserState` 结构的状态对象。\n",
|
||
"\n",
|
||
"从教学角度看,这一步很重要,因为它让你意识到:\n",
|
||
"\n",
|
||
"- 状态本质上仍然是字典\n",
|
||
"- `TypedDict` 只是帮助我们把字典结构写清楚\n",
|
||
"\n",
|
||
"在 LangGraph 中,节点之间传递的就是这种结构化字典。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "dceebf80",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 4. 读取状态:节点如何使用已有数据\n",
|
||
"\n",
|
||
"定义完状态之后,下一步就是在节点中读取它。\n",
|
||
"\n",
|
||
"下面这个例子演示:节点如何从状态里取出数据并生成一句自我介绍。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cb6536ba",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing_extensions import TypedDict\n",
|
||
"from langgraph.graph import StateGraph, START, END\n",
|
||
"\n",
|
||
"class ProfileState(TypedDict):\n",
|
||
" name: str\n",
|
||
" age: int\n",
|
||
" city: str\n",
|
||
" intro: str\n",
|
||
"\n",
|
||
"def create_intro(state: ProfileState):\n",
|
||
" intro_text = f'大家好,我叫{state[\"name\"]},今年{state[\"age\"]}岁,来自{state[\"city\"]}。'\n",
|
||
" return {'intro': intro_text}\n",
|
||
"\n",
|
||
"builder = StateGraph(ProfileState)\n",
|
||
"builder.add_node('create_intro', create_intro)\n",
|
||
"builder.add_edge(START, 'create_intro')\n",
|
||
"builder.add_edge('create_intro', END)\n",
|
||
"\n",
|
||
"graph = builder.compile()\n",
|
||
"result = graph.invoke({\n",
|
||
" 'name': '李雷',\n",
|
||
" 'age': 18,\n",
|
||
" 'city': '上海',\n",
|
||
" 'intro': ''\n",
|
||
"})\n",
|
||
"\n",
|
||
"print(result)\n",
|
||
"print(result['intro'])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "120b0a74",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这个例子重点不是图结构本身,而是“节点如何读取状态中的已有字段”。\n",
|
||
"\n",
|
||
"#### 状态结构\n",
|
||
"这里的状态有四个字段:\n",
|
||
"\n",
|
||
"- `name`\n",
|
||
"- `age`\n",
|
||
"- `city`\n",
|
||
"- `intro`\n",
|
||
"\n",
|
||
"前三个字段是输入信息,最后一个字段是我们希望在流程中生成的新结果。\n",
|
||
"\n",
|
||
"#### `create_intro(state)`\n",
|
||
"这个节点做的事情很直观:\n",
|
||
"\n",
|
||
"1. 从状态中读取 `name`\n",
|
||
"2. 从状态中读取 `age`\n",
|
||
"3. 从状态中读取 `city`\n",
|
||
"4. 组装成一句完整介绍\n",
|
||
"5. 把结果写回 `intro` 字段\n",
|
||
"\n",
|
||
"也就是说,这个节点没有改变原来的 `name`、`age`、`city`,只是新增或更新了 `intro`。\n",
|
||
"\n",
|
||
"#### 节点返回值为什么只写 `{'intro': intro_text}`\n",
|
||
"这是 LangGraph 状态管理里非常重要的一点:\n",
|
||
"\n",
|
||
"**节点只需要返回自己负责修改的字段。**\n",
|
||
"\n",
|
||
"原来的字段不会凭空消失,而是继续保留在状态里。\n",
|
||
"\n",
|
||
"所以最终 `result` 中既有原始输入,也有新生成的 `intro`。\n",
|
||
"\n",
|
||
"#### 这个例子的意义\n",
|
||
"它展示了状态管理的最基础工作方式:\n",
|
||
"\n",
|
||
"- 输入状态中先有一部分信息\n",
|
||
"- 节点读取这些信息进行加工\n",
|
||
"- 再把加工结果写回状态\n",
|
||
"\n",
|
||
"这就是后面所有复杂流程的基础。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "28600cec",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 5. 更新状态:后面的节点继续接着用\n",
|
||
"\n",
|
||
"状态管理真正强大的地方,不在于‘一个节点能读写状态’,而在于**前一个节点更新的内容,后一个节点可以继续使用**。\n",
|
||
"\n",
|
||
"下面这个例子演示两个节点接力处理状态:\n",
|
||
"\n",
|
||
"1. 第一个节点生成问候语\n",
|
||
"2. 第二个节点在问候语后面再补一句欢迎词"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "98e52d28",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing_extensions import TypedDict\n",
|
||
"from langgraph.graph import StateGraph, START, END\n",
|
||
"\n",
|
||
"class GreetingState(TypedDict):\n",
|
||
" name: str\n",
|
||
" greeting: str\n",
|
||
" final_message: str\n",
|
||
"\n",
|
||
"def make_greeting(state: GreetingState):\n",
|
||
" return {'greeting': f'你好,{state[\"name\"]}!'}\n",
|
||
"\n",
|
||
"def make_final_message(state: GreetingState):\n",
|
||
" final_text = state['greeting'] + ' 欢迎来到状态管理课程。'\n",
|
||
" return {'final_message': final_text}\n",
|
||
"\n",
|
||
"builder = StateGraph(GreetingState)\n",
|
||
"builder.add_node('make_greeting', make_greeting)\n",
|
||
"builder.add_node('make_final_message', make_final_message)\n",
|
||
"\n",
|
||
"builder.add_edge(START, 'make_greeting')\n",
|
||
"builder.add_edge('make_greeting', 'make_final_message')\n",
|
||
"builder.add_edge('make_final_message', END)\n",
|
||
"\n",
|
||
"graph = builder.compile()\n",
|
||
"result = graph.invoke({\n",
|
||
" 'name': '小王',\n",
|
||
" 'greeting': '',\n",
|
||
" 'final_message': ''\n",
|
||
"})\n",
|
||
"\n",
|
||
"print(result)\n",
|
||
"print(result['final_message'])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "70fa1f92",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这个例子体现了状态在多个节点之间的‘接力传递’。\n",
|
||
"\n",
|
||
"#### 第一个节点 `make_greeting`\n",
|
||
"它只做一件事:根据 `name` 生成一句问候语,写入 `greeting`。\n",
|
||
"\n",
|
||
"例如:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"{'name': '小王'}\n",
|
||
"```\n",
|
||
"\n",
|
||
"会生成:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"{'greeting': '你好,小王!'}\n",
|
||
"```\n",
|
||
"\n",
|
||
"#### 第二个节点 `make_final_message`\n",
|
||
"它并不重新根据 `name` 生成问候,而是直接读取上一个节点已经写入状态的 `greeting`。\n",
|
||
"\n",
|
||
"然后在它后面拼上一句:\n",
|
||
"\n",
|
||
"```text\n",
|
||
"欢迎来到状态管理课程。\n",
|
||
"```\n",
|
||
"\n",
|
||
"最后写入 `final_message`。\n",
|
||
"\n",
|
||
"#### 这说明了什么\n",
|
||
"这说明状态不仅能存放“输入数据”,还能存放“中间结果”。\n",
|
||
"\n",
|
||
"这是状态管理特别重要的一点:\n",
|
||
"\n",
|
||
"- 输入数据:用户最初给的信息\n",
|
||
"- 中间结果:某个节点处理出来的阶段性结果\n",
|
||
"- 最终结果:流程结束时输出的内容\n",
|
||
"\n",
|
||
"如果没有状态,你就得手动把这些内容一级一级传下去;有了状态,流程自然就串起来了。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f28536c4",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 6. 多字段状态:让流程更接近真实业务\n",
|
||
"\n",
|
||
"真实项目里的状态,通常不会只有 1 个或 2 个字段。\n",
|
||
"\n",
|
||
"例如一个订单处理流程,可能需要同时记录:\n",
|
||
"\n",
|
||
"- 用户姓名\n",
|
||
"- 商品名称\n",
|
||
"- 数量\n",
|
||
"- 总价\n",
|
||
"- 订单状态\n",
|
||
"\n",
|
||
"下面我们用一个简单的订单示例,演示多字段状态的管理方式。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "b3d1077b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing_extensions import TypedDict\n",
|
||
"from langgraph.graph import StateGraph, START, END\n",
|
||
"\n",
|
||
"class OrderState(TypedDict):\n",
|
||
" customer_name: str\n",
|
||
" product_name: str\n",
|
||
" price: float\n",
|
||
" quantity: int\n",
|
||
" total_price: float\n",
|
||
" order_status: str\n",
|
||
"\n",
|
||
"def calculate_total(state: OrderState):\n",
|
||
" total = state['price'] * state['quantity']\n",
|
||
" return {'total_price': total}\n",
|
||
"\n",
|
||
"def confirm_order(state: OrderState):\n",
|
||
" status = f'订单已确认:{state[\"customer_name\"]} 购买了 {state[\"quantity\"]} 件 {state[\"product_name\"]},总价 {state[\"total_price\"]} 元。'\n",
|
||
" return {'order_status': status}\n",
|
||
"\n",
|
||
"builder = StateGraph(OrderState)\n",
|
||
"builder.add_node('calculate_total', calculate_total)\n",
|
||
"builder.add_node('confirm_order', confirm_order)\n",
|
||
"\n",
|
||
"builder.add_edge(START, 'calculate_total')\n",
|
||
"builder.add_edge('calculate_total', 'confirm_order')\n",
|
||
"builder.add_edge('confirm_order', END)\n",
|
||
"\n",
|
||
"graph = builder.compile()\n",
|
||
"result = graph.invoke({\n",
|
||
" 'customer_name': '王芳',\n",
|
||
" 'product_name': '机械键盘',\n",
|
||
" 'price': 299.0,\n",
|
||
" 'quantity': 2,\n",
|
||
" 'total_price': 0.0,\n",
|
||
" 'order_status': ''\n",
|
||
"})\n",
|
||
"\n",
|
||
"print(result)\n",
|
||
"print(result['order_status'])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "ad35700b",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 代码解释\n",
|
||
"\n",
|
||
"这个例子很适合用来理解‘多字段状态为什么有必要’。\n",
|
||
"\n",
|
||
"#### 状态里有哪些信息\n",
|
||
"这里的状态已经不只是一个简单变量,而是一组完整业务数据:\n",
|
||
"\n",
|
||
"- 谁在下单:`customer_name`\n",
|
||
"- 买的是什么:`product_name`\n",
|
||
"- 单价是多少:`price`\n",
|
||
"- 买了几件:`quantity`\n",
|
||
"- 总价是多少:`total_price`\n",
|
||
"- 当前订单描述:`order_status`\n",
|
||
"\n",
|
||
"#### `calculate_total` 节点\n",
|
||
"这个节点只负责计算总价:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"总价 = 单价 * 数量\n",
|
||
"```\n",
|
||
"\n",
|
||
"它返回 `{'total_price': total}`,把结果写回状态。\n",
|
||
"\n",
|
||
"#### `confirm_order` 节点\n",
|
||
"这个节点读取多个字段:\n",
|
||
"\n",
|
||
"- `customer_name`\n",
|
||
"- `quantity`\n",
|
||
"- `product_name`\n",
|
||
"- `total_price`\n",
|
||
"\n",
|
||
"然后把这些信息拼成完整订单说明,再写入 `order_status`。\n",
|
||
"\n",
|
||
"#### 这个例子传达的核心思想\n",
|
||
"状态不是只给“一个节点”准备的,而是给“整个流程”准备的。\n",
|
||
"\n",
|
||
"每个节点只关心自己需要的部分字段,但所有字段加起来,构成了完整的业务上下文。\n",
|
||
"\n",
|
||
"这也是为什么状态设计要尽量清晰:因为它决定了整个流程的数据组织方式。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "4858b6f3",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 7. 状态设计的常见原则\n",
|
||
"\n",
|
||
"写状态时,建议遵循下面几个原则。\n",
|
||
"\n",
|
||
"### 7.1 字段名要清楚\n",
|
||
"不要用太模糊的名字,例如:\n",
|
||
"\n",
|
||
"- `data`\n",
|
||
"- `info`\n",
|
||
"- `result1`\n",
|
||
"\n",
|
||
"更好的方式是:\n",
|
||
"\n",
|
||
"- `user_question`\n",
|
||
"- `retrieved_docs`\n",
|
||
"- `final_answer`\n",
|
||
"\n",
|
||
"字段名越清楚,后面越不容易乱。\n",
|
||
"\n",
|
||
"### 7.2 一个字段只表达一种含义\n",
|
||
"例如不要今天把 `result` 用来存字符串,明天又用来存字典。\n",
|
||
"\n",
|
||
"字段含义最好稳定,不要随流程变化得太厉害。\n",
|
||
"\n",
|
||
"### 7.3 把中间结果保留下来\n",
|
||
"有些同学会只保留最终结果,把中间结果全部覆盖掉。这样虽然表面简洁,但调试时会非常痛苦。\n",
|
||
"\n",
|
||
"如果中间结果后面可能还会用到,或者你希望排查流程问题,就应该保留。\n",
|
||
"\n",
|
||
"### 7.4 不要把无关信息全塞进去\n",
|
||
"状态不是越大越好。\n",
|
||
"\n",
|
||
"只保留这个流程真正需要的数据,避免状态越来越臃肿。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "d701714f",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 8. 一个常见误区:以为节点要返回完整状态\n",
|
||
"\n",
|
||
"很多初学者会误以为:每个节点都必须返回整份状态,例如:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"return {\n",
|
||
" 'name': state['name'],\n",
|
||
" 'age': state['age'],\n",
|
||
" 'city': state['city'],\n",
|
||
" 'intro': intro_text\n",
|
||
"}\n",
|
||
"```\n",
|
||
"\n",
|
||
"其实在很多情况下,这么写没有必要。\n",
|
||
"\n",
|
||
"更简洁的方式通常是:\n",
|
||
"\n",
|
||
"```python\n",
|
||
"return {'intro': intro_text}\n",
|
||
"```\n",
|
||
"\n",
|
||
"原因是:LangGraph 会自动把更新结果合并回原状态。\n",
|
||
"\n",
|
||
"所以,除非你确实要同时改很多字段,否则通常只返回你真正改动的那部分。\n",
|
||
"\n",
|
||
"这会让代码更短,也更不容易出错。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a28700b1",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 9. 状态管理和后续 Agent 的关系\n",
|
||
"\n",
|
||
"后面学 Agent、RAG、多轮工具调用时,你会越来越频繁地用到状态。\n",
|
||
"\n",
|
||
"例如一个复杂 Agent 的状态里,可能会放这些字段:\n",
|
||
"\n",
|
||
"- 用户问题 `user_query`\n",
|
||
"- 历史消息 `messages`\n",
|
||
"- 工具调用结果 `tool_result`\n",
|
||
"- 检索到的资料 `retrieved_docs`\n",
|
||
"- 当前决策 `decision`\n",
|
||
"- 最终回答 `final_answer`\n",
|
||
"\n",
|
||
"所以可以说:\n",
|
||
"\n",
|
||
"**图结构解决的是“流程怎么走”,状态管理解决的是“数据怎么跟着流程走”。**\n",
|
||
"\n",
|
||
"这两者是 LangGraph 最核心的两根主线。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2a08da16",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 10. 本节小结\n",
|
||
"\n",
|
||
"本节你需要重点记住以下几点:\n",
|
||
"\n",
|
||
"1. **状态本质上是共享字典**,只是通常用 `TypedDict` 把结构定义清楚\n",
|
||
"2. **节点通过读取状态拿到已有数据**\n",
|
||
"3. **节点通过返回字典更新状态**\n",
|
||
"4. **后面的节点可以继续读取前面节点写入的中间结果**\n",
|
||
"5. **好的状态设计,会让整个图流程更清晰、更稳定、更容易调试**\n",
|
||
"\n",
|
||
"理解了状态管理,后面再看复杂流程时,你就不会只盯着‘节点怎么连’,而会同时关注‘数据怎么流动’。"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "caa87287",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 11. 本节练习\n",
|
||
"\n",
|
||
"1. 修改 `ProfileState` 示例,在状态中增加 `hobby` 字段,并把爱好加入自我介绍\n",
|
||
"2. 修改 `GreetingState` 示例,让第二个节点在最终消息后面再追加一句祝福\n",
|
||
"3. 修改 `OrderState` 示例,增加一个 `discount` 字段,并让总价计算支持折扣\n",
|
||
"4. 思考:哪些字段属于输入数据,哪些字段属于中间结果,哪些字段属于最终输出?\n",
|
||
"5. 思考:如果一个 Agent 需要反复调用工具,你觉得状态里至少应该保留哪些字段?"
|
||
]
|
||
}
|
||
],
|
||
"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
|
||
}
|