学会写 Tool 之后,我以为 Agent 会顺理成章地工作:把模型和工具丢给 create_agent,它就能自己决定什么时候调用什么。
结果它根本没按我想的走。有时候自己硬答,有时候调了 Tool 但参数不对,有时候甚至连 Tool 看都不看一眼。create_agent 明明没报错,但效果就是不对。
这篇文章记录我理解 Agent 到底怎么"思考"的过程。
一、我当时想干什么
我已经有了两个 Tool:
add(a, b):做加法search(query):模拟搜索
我想让 Agent 完成这样的任务:
"3 + 5 等于几?再搜索一下 LangChain 最新动态。"
期望 Agent 自己判断:先调用 add,再调用 search,最后综合回答。
我写的代码:
python
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
@tool
def add(a: int, b: int) -> int:
"""计算两个数的和"""
return a + b
@tool
def search(query: str) -> str:
"""搜索信息"""
return f"关于 {query} 的搜索结果..."
llm = ChatOpenAI(
model="deepseek-chat",
temperature=0,
openai_api_key="你的 api key",
openai_api_base="https://api.deepseek.com/v1",
)
agent = create_agent(
model=llm,
tools=[add, search],
)
看起来没问题,但运行后 Agent 有时候直接心算 3+5=8,根本不调 add。
二、我写的代码
后来我加了一个 system_prompt,事情开始好转:
python
agent = create_agent(
model=llm,
tools=[add, search],
system_prompt="你是一个助手,遇到数学问题必须调用 add 工具,遇到信息查询必须调用 search 工具。",
)
result = agent.invoke({
"messages": [{"role": "user", "content": "3 + 5 等于几?再搜索一下 LangChain 最新动态。"}]
})
print(result["messages"][-1].content)
Agent 开始按规则调用工具了。
我还试了结构化输出:
python
from pydantic import BaseModel
class Answer(BaseModel):
"""答案结构定义"""
summary: str
confidence: float
agent = create_agent(
model=llm,
tools=[search],
response_format=Answer,
)
result = agent.invoke({
"messages": [{"role": "user", "content": "总结 AI 最新趋势"}]
})
answer = result["structured_response"]
print(answer.summary, answer.confidence)
这样 Agent 的输出就被约束成了固定格式,不会胡说八道。
三、我遇到的坑
坑 1:以为 Agent 会"自觉"调 Tool
我最开始的代码没写 system_prompt,以为 Agent 看到 add 工具就会用。结果模型有时候直接心算答案。
后来我理解了:Agent 本质上是一个循环------模型决定是继续推理还是调用工具。模型做决定时,主要依据是:
- system prompt 里的指令
- Tool 的 name 和 description
- 当前对话上下文
如果 system prompt 不明确,模型就会"偷懒",用自己内部知识回答。
坑 2:Tool 描述和实际功能不匹配
我早期写 search 的 docstring 是:
python
@tool
def search(query: str) -> str:
"""搜索"""
return f"关于 {query} 的搜索结果..."
然后问"今天天气怎么样",Agent 调用了 search("今天天气"),但返回的是模拟数据。我想让它返回真实天气,但工具本身不支持。
这教会我一件事:Agent 的能力边界 = Tool 的能力边界。 你给它的工具只能做模拟搜索,它就不可能给你真实天气。不要期望 Agent 弥补工具的缺陷。
坑 3:response_format 和 Tool 调用冲突
我一开始想同时做两件事:让 Agent 调用 search,然后返回一个 Answer 结构。
代码类似这样:
python
agent = create_agent(
model=llm,
tools=[search],
response_format=Answer, # 希望最终输出结构化
)
结果发现,如果 Agent 调用了 Tool,Tool 的返回内容可能不会被自动塞进 Answer 的 summary 字段。response_format 约束的是最终回复,不是中间 Tool 的结果。
要解决这个问题,我学会了分两步:
- 先让 Agent 调用 Tool 拿到结果
- 再用 Chain 或第二次调用把结果整理成结构化输出
坑 4:模型本身不支持 function calling
我用某个模型测试时,Agent 死活不调工具。换到 deepseek-chat 后正常。
原因是:不是所有模型都擅长 function calling。 有些小模型或旧模型看不懂 Tool 的 schema,自然不会按规则调用。
如果你的 Agent 不调工具,先确认模型是否支持工具调用。
四、搞清楚后的结论
create_agent 不是魔法,它只是搭了一个循环框架。真正决定 Agent 行为的是四个东西:
#mermaid-svg-MuPtCLKVdTAzRJBp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MuPtCLKVdTAzRJBp .error-icon{fill:#552222;}#mermaid-svg-MuPtCLKVdTAzRJBp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MuPtCLKVdTAzRJBp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MuPtCLKVdTAzRJBp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MuPtCLKVdTAzRJBp .marker.cross{stroke:#333333;}#mermaid-svg-MuPtCLKVdTAzRJBp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MuPtCLKVdTAzRJBp p{margin:0;}#mermaid-svg-MuPtCLKVdTAzRJBp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster-label text{fill:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster-label span{color:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster-label span p{background-color:transparent;}#mermaid-svg-MuPtCLKVdTAzRJBp .label text,#mermaid-svg-MuPtCLKVdTAzRJBp span{fill:#333;color:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp .node rect,#mermaid-svg-MuPtCLKVdTAzRJBp .node circle,#mermaid-svg-MuPtCLKVdTAzRJBp .node ellipse,#mermaid-svg-MuPtCLKVdTAzRJBp .node polygon,#mermaid-svg-MuPtCLKVdTAzRJBp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MuPtCLKVdTAzRJBp .rough-node .label text,#mermaid-svg-MuPtCLKVdTAzRJBp .node .label text,#mermaid-svg-MuPtCLKVdTAzRJBp .image-shape .label,#mermaid-svg-MuPtCLKVdTAzRJBp .icon-shape .label{text-anchor:middle;}#mermaid-svg-MuPtCLKVdTAzRJBp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MuPtCLKVdTAzRJBp .rough-node .label,#mermaid-svg-MuPtCLKVdTAzRJBp .node .label,#mermaid-svg-MuPtCLKVdTAzRJBp .image-shape .label,#mermaid-svg-MuPtCLKVdTAzRJBp .icon-shape .label{text-align:center;}#mermaid-svg-MuPtCLKVdTAzRJBp .node.clickable{cursor:pointer;}#mermaid-svg-MuPtCLKVdTAzRJBp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MuPtCLKVdTAzRJBp .arrowheadPath{fill:#333333;}#mermaid-svg-MuPtCLKVdTAzRJBp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MuPtCLKVdTAzRJBp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MuPtCLKVdTAzRJBp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MuPtCLKVdTAzRJBp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MuPtCLKVdTAzRJBp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MuPtCLKVdTAzRJBp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster text{fill:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp .cluster span{color:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MuPtCLKVdTAzRJBp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MuPtCLKVdTAzRJBp rect.text{fill:none;stroke-width:0;}#mermaid-svg-MuPtCLKVdTAzRJBp .icon-shape,#mermaid-svg-MuPtCLKVdTAzRJBp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MuPtCLKVdTAzRJBp .icon-shape p,#mermaid-svg-MuPtCLKVdTAzRJBp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MuPtCLKVdTAzRJBp .icon-shape .label rect,#mermaid-svg-MuPtCLKVdTAzRJBp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MuPtCLKVdTAzRJBp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MuPtCLKVdTAzRJBp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MuPtCLKVdTAzRJBp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
System Prompt
行为准则
Agent 决策
Model
推理能力
Tools
可调用的工具
Response Format
输出约束
调用工具?
执行 Tool
Tool 结果回到上下文
直接回答
这四个参数的关系:
| 参数 | 作用 | 如果配错会怎样 |
|---|---|---|
model |
负责推理的大脑 | 模型不支持工具调用,Agent 不会调 Tool |
tools |
能调用的外部能力 | 工具描述不清,Agent 乱调或漏调 |
system_prompt |
告诉 Agent 什么时候该调工具 | 不写的话,Agent 会"偷懒" |
response_format |
约束最终输出格式 | 不能替代 Tool 调用后的二次加工 |
五、我的收获
Agent 不是有了工具就会用,是你得告诉它"什么时候用、为什么用"。
我以前觉得,写好 Tool、传给 create_agent,剩下的事交给模型就行。实际不是。
Agent 就像一个刚入职的员工:你给了它电话、电脑、系统账号,但它不一定会按流程办事。你需要写清楚 SOP(system prompt),给它能力匹配的工具(tools),还要选一个有判断力的"大脑"(model)。
理解这一点后,我写 Agent 的顺序变了:
- 先想清楚要 Agent 做什么
- 再设计 Tool,每个 Tool 职责单一
- 写 system prompt 明确调用规则
- 最后加 response_format 约束输出
这个顺序比直接 create_agent(model=..., tools=...) 稳得多。
下一篇我会写记忆------为什么我加了 InMemorySaver,Agent 还是不记得上一句说了什么。