📚 ReACT Agent 开发知识点总结
python langchain QW 文生图
一、核心概念
1. Agent 的两种创建方式
| 方法 | create_react_agent |
create_tool_calling_agent |
|---|---|---|
| 原理 | 文本解析(Thought/Action/Observation) | 原生工具调用(Function Calling) |
| 提示词 | 严格格式要求 | 简单通用 |
| 可靠性 | ⭐⭐⭐(易解析失败) | ⭐⭐⭐⭐⭐(结构化调用) |
| 模型要求 | 任意 LLM | 支持 Function Calling 的模型 |
| 推荐度 | 旧项目兼容 | ✅ 强烈推荐 |
2. 工作流程对比
markdown
ReAct Agent:
用户输入 → LLM 生成文本 → 正则解析 → 提取 Action → 执行工具 → 返回 Observation
↑ ↓
└─────────────────── 循环多次 ───────────────────────────┘
Tool Calling Agent:
用户输入 → LLM 结构化输出 → 自动匹配工具 → 执行 → 返回结果
(JSON 格式) ↓
自动生成回答
二、关键代码模式
✅ 正确的 Tool Calling Agent 实现
python
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.tools import Tool
from langchain_openai import ChatOpenAI
# 1. 定义工具
def search_func(query: str) -> str:
return f"搜索结果:{query}"
search_tool = Tool(
name="search",
description="搜索信息",
func=search_func,
)
tools = [search_tool]
# 2. 初始化 LLM
llm = ChatOpenAI(model="qwen3-max", api_key="...", base_url="...")
# 3. 🔑 关键步骤:绑定工具
llm_with_tools = llm.bind_tools(tools)
# 4. 创建 Agent
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手,可用工具:{tools}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(
llm=llm_with_tools, # ✅ 使用绑定工具的 LLM
tools=tools, # ✅ 工具列表
prompt=prompt
)
# 5. 创建执行器
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
max_iterations=5
)
# 6. ✅ 正确调用方式
result = executor.invoke({"input": "用户问题"})
print(result["output"])
三、常见错误与修复
❌ 错误 1:直接传字符串给 invoke
python
# ❌ 错误
executor.invoke("用户问题")
# ✅ 正确
executor.invoke({"input": "用户问题"})
❌ 错误 2:未绑定工具
python
# ❌ 错误
agent = create_tool_calling_agent(llm=llm, tools=tools, ...)
# ✅ 正确
llm_with_tools = llm.bind_tools(tools)
agent = create_tool_calling_agent(llm=llm_with_tools, tools=tools, ...)
❌ 错误 3:使用非标准 Tool 类
python
# ❌ 容易出错
google_serper = GoogleSerperRun(...)
# ✅ 推荐
google_serper = Tool(
name="google_serper",
func=google_serper_api.run,
description="..."
)
❌ 错误 4:Placeholder 格式错误
python
# ❌ 错误
("placeholder", "chat_history")
# ✅ 正确
MessagesPlaceholder(variable_name="chat_history", optional=True)
# 或
("placeholder", "{chat_history}") # 注意花括号
四、工具定义最佳实践
1. 简单工具 - 使用 Tool 包装
python
tool = Tool(
name="tool_name",
func=function,
description="清晰的用途说明",
)
2. 复杂参数 - 添加 args_schema
python
from pydantic.v1 import BaseModel, Field
class ArgsSchema(BaseModel):
query: str = Field(..., description="参数描述")
tool = Tool(
name="tool_name",
func=function,
args_schema=ArgsSchema,
description="..."
)
3. 自定义 API - 封装函数
python
def generate_image(prompt: str) -> str:
"""使用阿里云生成图片"""
try:
response = MultiModalConversation.call(...)
# ✅ 正确解析嵌套结构
if response.output and response.output.get('choices'):
content = response.output['choices'][0]['message']['content']
for item in content:
if 'image' in item:
return f"图片链接:{item['image']}"
return "生成失败"
except Exception as e:
return f"出错:{str(e)}"
image_tool = Tool(
name="image_generator",
func=generate_image,
description="根据描述生成图片",
args_schema=DallEArgsSchema
)
五、API 响应解析技巧
阿里云通义万相响应结构
json
{
"output": {
"choices": [
{
"message": {
"content": [
{"image": "https://..."} // 或 {"text": "..."}
]
}
}
]
}
}
安全解析代码
python
def parse_response(response):
try:
# 多层防御式解析
if response.output:
choices = response.output.get('choices', [])
if choices:
content = choices[0].get('message', {}).get('content', [])
for item in content:
if isinstance(item, dict) and 'image' in item:
return item['image']
# 备用方案
if response.output and response.output.get('text'):
return response.output['text']
return "无法解析响应"
except Exception as e:
return f"解析出错:{str(e)}"
六、提示词设计要点
优秀提示词模板
python
prompt = ChatPromptTemplate.from_messages([
("system",
"你是智能助手,可使用工具帮助用户。\n"
"\n"
"可用工具:\n"
"{tools}\n"
"\n"
"使用格式:\n"
"- Thought: 分析问题\n"
"- Action: 选择工具\n"
"- Action Input: 参数\n"
"- Observation: 结果\n"
"- Final Answer: 最终回答\n"
"\n"
"注意:\n"
"- 生成图片用 openai_dalle\n"
"- 搜索信息用 google_serper\n"
"- 英文描述图片效果更好"
),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
七、调试技巧
1. 启用详细日志
python
executor = AgentExecutor(verbose=True, ...)
2. 限制迭代次数
python
executor = AgentExecutor(max_iterations=5, ...) # 防止无限循环
3. 错误处理
python
executor = AgentExecutor(handle_parsing_errors=True, ...) # 自动修复
4. 打印中间结果
python
def generate_image(prompt):
print(f"收到请求:{prompt}")
response = ...
print(f"API 响应:{response}")
return result
八、完整流程图
css
用户提问
↓
┌─────────────────────────────────────┐
│ AgentExecutor.invoke({"input": ...}) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ create_tool_calling_agent │
│ - 分析是否需要工具 │
│ - 输出结构化 JSON │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 判断 Action │
│ - None → 直接回答 │
│ - tool_name → 执行工具 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Tool.run() │
│ - 调用实际函数 │
│ - 返回 Observation │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 循环或生成最终回答 │
└─────────────────────────────────────┘
九、核心要点速记
✅ 必须做的:
- 使用
bind_tools()绑定工具 invoke()传入字典{"input": "..."}- 使用
Tool包装自定义函数 - 添加
handle_parsing_errors=True - 设置
max_iterations防止死循环
❌ 避免做的:
- 不要直接传字符串给
invoke() - 不要使用非标准 Tool 类(如
GoogleSerperRun) - 不要忘记绑定工具就创建 Agent
- 不要使用错误的 Placeholder 格式
- 不要假设 API 响应结构固定
十、扩展学习
下一步可以学习:
- 🔄 添加对话历史记忆(Memory)
- 📊 多工具协同工作
- 🎯 自定义 Agent 路由逻辑
- 📝 更复杂的提示词工程
- 🔐 生产环境的错误处理和重试机制
十、核心代码
ini
import os
from dashscope import MultiModalConversation
import dotenv
from langchain_classic.agents import AgentExecutor, create_react_agent, create_tool_calling_agent
from langchain_community.tools import GoogleSerperRun
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import render_text_description_and_args, Tool
from langchain_openai import ChatOpenAI
from pydantic.v1 import Field, BaseModel
from langchain_community.utilities import GoogleSerperAPIWrapper
dotenv.load_dotenv()
class GoogleSerperArgsSchema(BaseModel):
query: str = Field(..., description="执行谷歌搜索的查询语句")
class DallEArgsSchema(BaseModel):
query: str = Field(..., description="输入是生成图像的文本提示(prompt)")
def generate_image(prompt: str) -> str:
"""使用阿里云通义万相生成图片"""
messages = [
{
"role": "user",
"content": [
{"text": prompt}
]
}
]
try:
response = MultiModalConversation.call(
api_key=os.getenv("OPENAI_API_KEY"),
model="qwen-image-2.0-pro",
messages=messages,
result_format='message',
stream=False,
watermark=False,
prompt_extend=True,
negative_prompt="低分辨率,低画质,肢体畸形,手指畸形,画面过饱和,蜡像感,人脸无细节,过度光滑,画面具有 AI 感。构图混乱。文字模糊,扭曲。",
size='2048*2048'
)
print("API 响应:", response)
# 从 output.choices 中获取图片
# {"status_code": 200, "request_id": "6ab051b0-6c8f-4675-974f-919143e91d98", "code": "", "message": "", "output": {"text": null, "finish_reason": null, "choices": [{"finish_reason": "stop", "message": {"role": "assistant", "content": [{"image": "https://dashscope-7c2c.oss-accelerate.aliyuncs.com/7d/c3/20260326/e74037e8/2d748881-31a4-4e0a-b2c3-1508ee17a3f1.png?Expires=1775104234&OSSAccessKeyId=LTAI5tPxpiCM2hjmWrFXrym1&Signature=4t53r7bOV3%2Fg01L%2FUPvouMgqKeQ%3D"}]}}], "audio": null}, "usage": {"input_tokens": 0, "output_tokens": 0, "characters": 0, "height": 2048, "image_count": 1, "width": 2048}}
if response.output and response.output.get('choices'):
choices = response.output['choices']
if len(choices) > 0:
message = choices[0].get('message', {})
content = message.get('content', [])
if isinstance(content, list) and len(content) > 0:
# 查找包含 image 的内容
for item in content:
if isinstance(item, dict) and 'image' in item:
image_url = item['image']
return f"图片已生成成功!\n图片链接:{image_url}"
# 如果没有找到 image,返回文本内容
text_content = content[0].get('text', '')
if text_content:
return f"生成结果:{text_content}"
# 备用方案:尝试从 output.text 获取
if response.output and response.output.get('text'):
return f"生成结果:{response.output['text']}"
return "图片生成失败,无法解析响应数据"
except Exception as e:
print(f"异常详情:{str(e)}")
return f"图片生成出错:{str(e)}"
google_serper = GoogleSerperRun(
name="google_serper",
description=(
"根据传入的搜索内容,返回搜索结果"
"谷歌搜索工具"
),
args_schema=GoogleSerperArgsSchema,
api_wrapper=GoogleSerperAPIWrapper(),
)
dalle = Tool(
name="openai_dalle",
description="根据传入的描述生成图片。当用户要求生成图像、创建图片、画图时使用此工具。输入应该是详细的图像描述。",
func=generate_image,
args_schema=DallEArgsSchema,
)
tools = [
google_serper,
dalle
]
prompt = ChatPromptTemplate.from_messages([
("system", "Answer the following questions as best you can. You have access to the following tools:"),
("placeholder", "{chat_history}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
llm = ChatOpenAI(
model="qwen3-max-2026-01-23",
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_API_BASE_URL")
)
# 绑定工具到 LLM
agent = create_tool_calling_agent(
llm=llm,
tools=tools,
prompt=prompt
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
print(agent_executor.invoke({"input": "帮我生成一张老爷爷爬山的图片, 要求是 亚洲脸"}))