Skip to content

Commit f9e3cc1

Browse files
committed
update langgraph demo
1 parent 404fa7c commit f9e3cc1

File tree

5 files changed

+899
-621
lines changed

5 files changed

+899
-621
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ openai_api/data/fine_food_reviews_with_embeddings_1k_2146.csv
167167
# files of Vector Stores, e.g. Chroma
168168
*.pkl
169169
*.bin
170+
*.db
170171

171172
langchain/openai-translator/flagged/*
172173
langchain/openai-translator/flask_temps/*
+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
## `functools.partial` 是什么
2+
3+
在 Python 中,`partial` 方法是 `functools` 模块中的一个功能,它用于创建一个**新的函数**,这个函数是基于原函数的**部分参数已经固定**的版本。这在需要重复调用同一函数,并且传递相同的某些参数时非常有用。
4+
5+
通过 `partial`,我们可以预先为函数的某些参数赋值,生成一个新的函数,这个新函数已经预先固定了部分参数,只需要再传递剩下的参数即可。
6+
7+
### `functools.partial``create_agent` 函数中的应用
8+
9+
在你提供的代码中,`partial` 是用在 `ChatPromptTemplate``from_messages` 函数生成的提示模板上,它通过 `partial` 方法预先固定了部分参数。
10+
11+
```python
12+
prompt = prompt.partial(system_message=system_message)
13+
prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))
14+
```
15+
16+
这里的 `partial` 用于创建一个新的提示模板对象,并为 `system_message``tool_names` 这两个参数提供了值。这相当于对提示模板的“定制”,预先指定了这些参数的值。
17+
18+
**`partial` 的具体作用:**
19+
20+
1. 调用 `prompt.partial(system_message=system_message)`,预先为 `system_message` 参数赋值,生成一个新的提示模板,固定了系统消息的内容。
21+
2. 调用 `prompt.partial(tool_names=", ".join([tool.name for tool in tools]))`,为 `tool_names` 参数赋值,将所有工具的名称合并成一个字符串,并固定在新的模板中。
22+
23+
通过这两步 `partial` 调用,`prompt` 对象中已经预先填入了 `system_message``tool_names` 这两个参数,简化了后续的调用过程。
24+
25+
--------------------
26+
27+
### `functools.partial``research_node` 定义时的应用
28+
29+
在这段代码中,`functools.partial` 用来创建一个新的函数,该函数已经预先绑定了部分参数。这种用法简化了后续调用函数时需要传递的参数,尤其是在重复使用某些固定参数时,`partial` 可以大大减少代码冗余。
30+
31+
```python
32+
research_node = functools.partial(agent_node, agent=research_agent, name="Researcher")
33+
```
34+
35+
这里的 `functools.partial` 创建了一个新的函数 `research_node`,该函数基于原始的 `agent_node` 函数,且已经为 `agent_node` 的部分参数(`agent``name`)预先设置了值。新的 `research_node` 函数只需要接收剩余的参数就可以正常运行。
36+
37+
**`partial` 的具体作用:**
38+
39+
1. **原始函数 `agent_node`**
40+
```python
41+
def agent_node(state, agent, name):
42+
# 函数体...
43+
```
44+
- `agent_node` 是一个接受 `state`, `agent`, 和 `name` 三个参数的函数。
45+
46+
2. **使用 `functools.partial`**
47+
```python
48+
research_node = functools.partial(agent_node, agent=research_agent, name="Researcher")
49+
```
50+
- 通过 `functools.partial`,我们创建了一个新的函数 `research_node`,它仍然是 `agent_node`,但 `agent` 参数和 `name` 参数已经被预先固定:
51+
- `agent=research_agent`
52+
- `name="Researcher"`
53+
- 也就是说,调用 `research_node` 时,只需要传递 `state` 参数,因为 `agent``name` 已经有默认值了。
54+
55+
**举个例子**
56+
57+
假设有一个函数 `agent_node`,你经常需要调用它并传递相同的 `agent``name`,那么每次调用时重复写这些参数会很冗余。使用 `partial` 可以避免这种重复。
58+
59+
```python
60+
# 原始函数定义
61+
def agent_node(state, agent, name):
62+
print(f"State: {state}, Agent: {agent}, Name: {name}")
63+
64+
# 预先设置 agent 和 name 参数
65+
research_node = functools.partial(agent_node, agent="research_agent_value", name="Researcher")
66+
67+
# 调用时只需要传递剩下的参数
68+
research_node(state="current_state")
69+
# 输出: State: current_state, Agent: research_agent_value, Name: Researcher
70+
```
71+
72+
### `functools.partial` 的优势
73+
74+
1. **减少重复代码**:在你需要多次调用同一个函数并且某些参数不变时,`partial` 可以避免每次都传递相同的参数。
75+
76+
2. **简化函数调用**:在需要频繁使用相同参数时,`partial` 提供了更简洁的写法,使代码更易于维护。
77+
78+
### 总结
79+
80+
在这段代码中,`functools.partial` 的用法预先为 `agent_node` 函数的部分参数(`agent``name`)赋值,创建了一个新函数 `research_node`。调用 `research_node` 时,只需要传递剩下的参数(`state`),从而简化了函数调用的流程。
81+
82+
-----------------
83+
84+
## 什么是 ToolNode?
85+
86+
**ToolNode** 是 LangChain 的一个预构建节点,它能够从图状态(`graph state`)中提取消息并调用指定的工具,最后将工具调用的结果反馈回图的状态中。ToolNode 非常适合与 LangGraph 中的 ReAct agent 协同工作,但也可以与任何 `StateGraph` 配合使用,只要状态中有 `messages` 键和合适的消息处理方式。
87+
88+
## ToolNode 的特点
89+
1. **工具调用**:ToolNode 可以根据状态中的消息自动调用指定的工具,并返回工具的执行结果。
90+
2. **兼容性**:可以与任意支持工具调用的 LangChain 模型配合使用。
91+
3. **并行工具调用**:支持同时调用多个工具,并处理工具返回的多个结果。
92+
4. **错误处理**:ToolNode 默认启用了错误处理,可以处理工具在执行过程中的异常情况。
93+
94+
### ToolNode 的使用步骤
95+
96+
### 1. 安装和环境设置
97+
首先,安装所需的包并设置 API 密钥:
98+
99+
```python
100+
%%capture --no-stderr
101+
%pip install --quiet -U langgraph langchain_anthropic
102+
import getpass
103+
import os
104+
105+
def _set_env(var: str):
106+
if not os.environ.get(var):
107+
os.environ[var] = getpass.getpass(f"{var}: ")
108+
109+
_set_env("ANTHROPIC_API_KEY")
110+
```
111+
112+
### 2. 定义工具
113+
114+
使用 `@tool` 装饰器定义可以被 ToolNode 调用的工具。下面的例子定义了两个工具:
115+
- `get_weather`:获取某个地点的天气。
116+
- `get_coolest_cities`:获取最酷的城市列表。
117+
118+
```python
119+
from langchain_core.messages import AIMessage
120+
from langchain_core.tools import tool
121+
from langgraph.prebuilt import ToolNode
122+
123+
@tool
124+
def get_weather(location: str):
125+
"""获取当前的天气。"""
126+
if location.lower() in ["sf", "san francisco"]:
127+
return "It's 60 degrees and foggy."
128+
else:
129+
return "It's 90 degrees and sunny."
130+
131+
@tool
132+
def get_coolest_cities():
133+
"""获取最酷的城市列表"""
134+
return "nyc, sf"
135+
136+
tools = [get_weather, get_coolest_cities] # 将定义的工具放入列表中
137+
tool_node = ToolNode(tools) # 使用工具列表初始化 ToolNode
138+
```
139+
140+
### 3. 手动调用 ToolNode
141+
142+
**ToolNode** 通过图状态操作消息列表,它期望列表中的最后一条消息是一个带有 `tool_calls` 参数的 `AIMessage`。以下是手动调用 ToolNode 的示例:
143+
144+
```python
145+
message_with_single_tool_call = AIMessage(
146+
content="",
147+
tool_calls=[
148+
{
149+
"name": "get_weather",
150+
"args": {"location": "sf"},
151+
"id": "tool_call_id",
152+
"type": "tool_call",
153+
}
154+
],
155+
)
156+
157+
tool_node.invoke({"messages": [message_with_single_tool_call]})
158+
# 返回: {'messages': [ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', tool_call_id='tool_call_id')]}
159+
```
160+
161+
### 4. 并行调用多个工具
162+
163+
ToolNode 也支持并行调用多个工具,只需在 `AIMessage``tool_calls` 参数中传入多个工具调用:
164+
165+
```python
166+
message_with_multiple_tool_calls = AIMessage(
167+
content="",
168+
tool_calls=[
169+
{
170+
"name": "get_coolest_cities",
171+
"args": {},
172+
"id": "tool_call_id_1",
173+
"type": "tool_call",
174+
},
175+
{
176+
"name": "get_weather",
177+
"args": {"location": "sf"},
178+
"id": "tool_call_id_2",
179+
"type": "tool_call",
180+
},
181+
],
182+
)
183+
184+
tool_node.invoke({"messages": [message_with_multiple_tool_calls]})
185+
# 返回:
186+
# {'messages': [ToolMessage(content='nyc, sf', name='get_coolest_cities', tool_call_id='tool_call_id_1'),
187+
# ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', tool_call_id='tool_call_id_2')]}
188+
```
189+
190+
### 与对话模型结合使用
191+
192+
在使用像 Anthropic 这样的对话模型时,模型可以自动生成带有 `tool_calls``AIMessage`,这样我们可以直接将模型生成的消息传给 ToolNode 来执行工具调用:
193+
194+
```python
195+
from langchain_anthropic import ChatAnthropic
196+
from langgraph.prebuilt import ToolNode
197+
198+
model_with_tools = ChatAnthropic(
199+
model="claude-3-haiku-20240307", temperature=0
200+
).bind_tools(tools)
201+
202+
tool_node.invoke({"messages": [model_with_tools.invoke("what's the weather in sf?")]})
203+
# 返回: {'messages': [ToolMessage(content="It's 60 degrees and foggy.", name='get_weather', tool_call_id='toolu_01LFvAVT3xJMeZS6kbWwBGZK')]}
204+
```
205+
206+
### ToolNode 与 ReAct Agent 结合
207+
208+
ReAct Agent 是 LangGraph 中的一种智能体,它会反复调用工具,直到收集到足够的信息来解决问题。以下是 ReAct Agent 的基本工作流,它通过工具节点来完成工具调用:
209+
210+
```python
211+
from typing import Literal
212+
from langgraph.graph import StateGraph, MessagesState
213+
214+
def should_continue(state: MessagesState) -> Literal["tools", "__end__"]:
215+
messages = state["messages"]
216+
last_message = messages[-1]
217+
if last_message.tool_calls:
218+
return "tools"
219+
return "__end__"
220+
221+
def call_model(state: MessagesState):
222+
messages = state["messages"]
223+
response = model_with_tools.invoke(messages)
224+
return {"messages": [response]}
225+
226+
# 创建状态图
227+
workflow = StateGraph(MessagesState)
228+
229+
# 定义两个节点:一个用于调用模型,一个用于调用工具
230+
workflow.add_node("agent", call_model)
231+
workflow.add_node("tools", tool_node)
232+
233+
workflow.add_edge("__start__", "agent") # 从 agent 节点开始
234+
workflow.add_conditional_edges("agent", should_continue) # 根据条件判断是否继续调用工具
235+
workflow.add_edge("tools", "agent") # 工具调用完成后,返回 agent 节点
236+
237+
app = workflow.compile() # 编译状态图
238+
```
239+
240+
### 例子:调用单个工具
241+
242+
当用户输入 `"what's the weather in sf?"` 时,智能体将调用 `get_weather` 工具并返回天气信息:
243+
244+
```python
245+
for chunk in app.stream(
246+
{"messages": [("human", "what's the weather in sf?")]}, stream_mode="values"
247+
):
248+
chunk["messages"][-1].pretty_print()
249+
```
250+
251+
### 例子:连续调用多个工具
252+
253+
当用户输入 `"what's the weather in the coolest cities?"` 时,智能体将依次调用 `get_coolest_cities``get_weather` 工具,返回所有城市的天气信息:
254+
255+
```python
256+
for chunk in app.stream(
257+
{"messages": [("human", "what's the weather in the coolest cities?")]},
258+
stream_mode="values",
259+
):
260+
chunk["messages"][-1].pretty_print()
261+
```
262+
263+
### 错误处理
264+
265+
ToolNode 默认启用了错误处理,可以处理工具执行中的异常情况。如果想禁用错误处理,可以设置 `handle_tool_errors=False`
266+
267+
### 总结
268+
269+
**ToolNode** 是一个非常强大的组件,它能够自动调用工具并将结果反馈回工作流。它可以处理单个或多个工具调用,并与 LangChain 模型紧密结合,使得在复杂的多步骤任务中能够更高效地调用外部 API 或工具。

0 commit comments

Comments
 (0)