`create_agent` 没报错,但 Agent 就是不调工具

学会写 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 本质上是一个循环------模型决定是继续推理还是调用工具。模型做决定时,主要依据是:

  1. system prompt 里的指令
  2. Tool 的 name 和 description
  3. 当前对话上下文

如果 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 的返回内容可能不会被自动塞进 Answersummary 字段。response_format 约束的是最终回复,不是中间 Tool 的结果。

要解决这个问题,我学会了分两步:

  1. 先让 Agent 调用 Tool 拿到结果
  2. 再用 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 的顺序变了:

  1. 先想清楚要 Agent 做什么
  2. 再设计 Tool,每个 Tool 职责单一
  3. 写 system prompt 明确调用规则
  4. 最后加 response_format 约束输出

这个顺序比直接 create_agent(model=..., tools=...) 稳得多。


下一篇我会写记忆------为什么我加了 InMemorySaver,Agent 还是不记得上一句说了什么。

相关推荐
不会c+2 小时前
02-SpringBoot配置文件
java·spring boot·后端
AI 大模型学习不踩坑2 小时前
OpenClaw 完整教程:从安装到使用(官方脚本版)
java·人工智能·神经网络·机器学习·计算机视觉·自然语言处理·openclaw
dog2502 小时前
从重尾到截断流量模型的演进
开发语言·php
qq_401700412 小时前
Qt QSS 完全入门写出漂亮界面以及解决样式不生效问题
开发语言·qt
Listen·Rain3 小时前
数据库流式查询
java·数据库
彦为君3 小时前
算法思维与经典智力题
java·前端·redis·算法
翔云 OCR API3 小时前
慧视扫描王-财务少加班
java·自动化
雨辰AI3 小时前
生产级实战:人大金仓 V9 标准化运维手册(日常巡检 + 监控告警 + 应急处置)
java·运维·数据库·后端
我是一颗柠檬3 小时前
【Java项目技术亮点】覆盖索引与索引下推优化
android·java·开发语言
云道轩3 小时前
比较IBM Transformation Advisor 和WebSphere Application Server Migration Toolkit
java·jakarta ee·open liberty·应用迁移