本示例构建一个可以使用外部工具(Tavily 搜索)的智能对话 Agent,本质上实现了:LLM ↔ 工具 ↔ LLM的循环,直到模型认为可以直接回答用户为止。代码取自《AI Agent智能体开发实践》第5章。
AI Agent智能体开发实践【行情 报价 价格 评测】-京东
示例代码如下:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
import os
# 设置API密钥(建议通过环境变量配置)
# 初始化Agent
# 1. 基础调用示例
#from langchain_community.chat_models.tongyi import ChatTongyi
#llm = ChatTongyi(model="qwen-turbo", temperature=0)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="qwen-turbo",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=os.getenv("DASHSCOPE_API_KEY"),
temperature=0,
)
from langchain_tavily import TavilySearch
import os
# 设置API密钥(建议通过环境变量配置)
os.environ["TAVILY_API_KEY"] = "tvly-dev-iWvU1YGG2SISN1RPOp"
tool = TavilySearch(max_results=2)
tools = [tool]
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
# Modification: tell the LLM which tools it can call
# highlight-next-line
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
import json
from langchain_core.messages import ToolMessage
class BasicToolNode:
"""A node that runs the tools requested in the last AIMessage."""
def __init__(self, tools: list) -> None:
self.tools_by_name = {tool.name: tool for tool in tools}
def __call__(self, inputs: dict):
if messages := inputs.get("messages", []):
message = messages[-1]
else:
raise ValueError("No message found in input")
outputs = []
for tool_call in message.tool_calls:
tool_result = self.tools_by_name[tool_call["name"]].invoke(
tool_call["args"]
)
outputs.append(
ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
return {"messages": outputs}
tool_node = BasicToolNode(tools=[tool])
graph_builder.add_node("tools", tool_node)
def route_tools(
state: State,
):
"""
Use in the conditional_edge to route to the ToolNode if the last message
has tool calls. Otherwise, route to the end.
"""
if isinstance(state, list):
ai_message = state[-1]
elif messages := state.get("messages", []):
ai_message = messages[-1]
else:
raise ValueError(f"No messages found in input state to tool_edge: {state}")
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return END
# The `tools_condition` function returns "tools" if the chatbot asks to use a tool, and "END" if
# it is fine directly responding. This conditional routing defines the main agent loop.
graph_builder.add_conditional_edges(
"chatbot",
route_tools,
# The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node
# It defaults to the identity function, but if you
# want to use a node named something else apart from "tools",
# You can update the value of the dictionary to something else
# e.g., "tools": "my_tools"
{"tools": "tools", END: END},
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")
graph = graph_builder.compile()
def stream_graph_updates(user_input: str):
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
for value in event.values():
print("Assistant:", value["messages"][-1].content)
while True:
try:
user_input = input("User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
stream_graph_updates(user_input)
except:
# fallback if input() is not available
user_input = "What do you know about LangGraph?"
print("User: " + user_input)
stream_graph_updates(user_input)
break
示例执行结果:
python
C:\Users\xiayu\miniconda3\envs\langchain03\python.exe C:\Users\xiayu\PyCharmMiscProject\LangChain官方示例代码学习\Basic\agent7.py
User: 你是谁
Assistant: 我是通义千问,由阿里巴巴集团旗下的通义实验室研发的超大规模语言模型。我可以帮助你解答问题、创作文字、表达观点、进行对话等。你可以问我任何问题,我会尽力提供帮助。
User: 简述LangGraph开发智能体的方法。
Assistant: LangGraph 是一个用于构建和训练智能体(agents)的框架,它允许开发者通过图结构来定义智能体的行为和决策过程。以下是 LangGraph 开发智能体的主要方法:
1. **定义节点(Nodes)**:
- 每个节点代表一个特定的任务或功能,例如数据处理、决策制定、执行操作等。
- 节点可以是简单的函数,也可以是复杂的机器学习模型。
2. **建立边(Edges)**:
- 边表示节点之间的连接,决定了智能体在不同任务之间的流转。
- 通过边,智能体可以根据当前状态或输入数据选择下一步要执行的操作。
3. **设计状态(State)**:
- 状态用于存储智能体在执行过程中需要保持的信息,例如当前的环境信息、历史操作记录等。
- 状态可以通过图结构传递,确保智能体在不同节点之间保持上下文。
4. **实现逻辑(Logic)**:
- 每个节点需要实现具体的逻辑,例如处理输入数据、调用外部 API、执行计算等。
- 逻辑可以是硬编码的,也可以是通过机器学习模型动态生成的。
5. **训练和优化(Training and Optimization)**:
- 使用强化学习或其他机器学习方法对智能体进行训练,以优化其决策过程。
- 训练过程中,智能体会根据反馈调整其行为,以更好地完成任务。
6. **集成和部署(Integration and Deployment)**:
- 将开发好的智能体集成到实际应用中,例如机器人、自动化系统或服务。
- 部署时需要考虑性能、可靠性和可扩展性。
通过这些步骤,LangGraph 提供了一种灵活且强大的方法来开发和管理智能体,使其能够适应各种复杂的应用场景。
User: 糖醋排骨的步骤
Assistant: 糖醋排骨是一道经典的中式菜肴,以下是制作糖醋排骨的步骤:
### 材料:
- 排骨 500 克
- 生姜 1 小块(切片)
- 大蒜 3 瓣(切末)
- 酱油 2 汤匙
- 醋 2 汤匙
- 糖 2 汤匙
- 料酒 1 汤匙
- 盐 适量
- 水 适量
- 淀粉 1 汤匙(用于勾芡)
### 步骤:
1. **准备排骨**:
- 将排骨洗净,放入锅中,加入足够的水,煮沸后撇去浮沫。
- 加入几片生姜和一些料酒,煮约5分钟,捞出排骨,用清水冲洗干净,沥干备用。
2. **煎排骨**:
- 在锅中加入适量油,加热至六成热,放入排骨,煎至两面金黄,盛出备用。
3. **炒香调料**:
- 锅中留少量油,加入生姜和大蒜炒香。
- 加入酱油、醋、糖和料酒,翻炒均匀。
4. **炖煮排骨**:
- 将煎好的排骨放入锅中,加入适量水(大约能没过排骨)。
- 大火煮开后转小火慢炖约30分钟,直到排骨变得软烂。
5. **收汁**:
- 待排骨炖好后,加入适量盐调味。
- 如果喜欢更浓郁的酱汁,可以用淀粉水勾芡,使汤汁浓稠。
6. **装盘**:
- 将糖醋排骨盛入盘中,撒上少许葱花或香菜点缀即可。
### 小贴士:
- 煎排骨时要注意火候,避免煎糊。
- 炖煮时间可以根据排骨的大小适当调整。
- 糖醋的比例可以根据个人口味进行调整。
希望你能成功做出美味的糖醋排骨!
User:
整体执行流程图
START
↓
Chatbot (LLM)
↓
是否调用工具?
├─ 是 → Tools Node → Chatbot
└─ 否 → END
这是一个循环图(Loop Graph)。
代码逐块讲解
1️⃣ 状态定义(State)
class State(TypedDict):
messages: Annotated[list, add_messages]
✅ 作用
-
定义整个图的"全局状态"
-
messages用来存储对话历史 -
add_messages确保新消息被追加,而不是覆盖
📌 所有节点都读写这个 State
2️⃣ 初始化 LLM(通义千问)
llm = ChatOpenAI(
model="qwen-turbo",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=os.getenv("DASHSCOPE_API_KEY"),
temperature=0,
)
✅ 使用的是 阿里云 DashScope 兼容 OpenAI 接口
✅ temperature=0保证回答稳定
3️⃣ 工具定义(Tavily 搜索)
tool = TavilySearch(max_results=2)
tools = [tool]
✅ Tavily:一个为 LLM 优化的实时搜索 API
✅ max_results=2:每次最多返回 2 条搜索结果
4️⃣ 绑定工具给 LLM
llm_with_tools = llm.bind_tools(tools)
✅ 这是关键一步
✅ 告诉模型:"你现在可以调用这些工具"
模型输出会变成类似:
{
"tool_calls": [
{
"name": "tavily_search",
"args": {"query": "LangGraph 是什么"}
}
]
}
5️⃣ Chatbot 节点(核心推理)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
✅ 每次调用 LLM:
-
读取历史消息
-
决定是否:直接回答或调用工具
6️⃣ 工具执行节点(BasicToolNode)
class BasicToolNode:
✅ 作用
-
解析 LLM 的
tool_calls -
实际执行工具
-
把结果封装成
ToolMessage
关键点:
tool_result = self.tools_by_name[tool_call["name"]].invoke(
tool_call["args"]
)
然后返回:
ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
📌 这一步让模型知道:"工具返回了什么结果"
7️⃣ 条件路由函数(route_tools)
def route_tools(state: State):
✅ 判断是否调用工具:
| 情况 | 去向 |
|---|---|
有 tool_calls |
"tools" |
| 没有 | END |
这是 LangGraph 的 conditional edge
8️⃣ 构建图(Graph)
graph_builder = StateGraph(State)
添加节点:
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
条件边(核心逻辑)
graph_builder.add_conditional_edges(
"chatbot",
route_tools,
{"tools": "tools", END: END},
)
工具执行后回到 chatbot
graph_builder.add_edge("tools", "chatbot")
启动入口
graph_builder.add_edge(START, "chatbot")
9️⃣ 编译并运行图
graph = graph_builder.compile()
🔟 流式对话循环
def stream_graph_updates(user_input: str):
for event in graph.stream(...):
...
✅ 每轮对话:
-
用户输入 → 放入
messages -
图开始执行
-
可能多次调用工具
-
最终输出 Assistant 回复

