一、环境与依赖报错类

报错:create_react_agent 不识别 state_modifier 参数
问题:当前 langgraph-prebuilt==1.0.8 属于版本重构过渡期,仅支持 prompt 参数,未上线 state_modifier。
方案:将传参改为 prompt=系统提示字符串,不要用 SystemMessage 对象。


报错: NameError: name ‘Optional’ is not defined
问题:未导入 from typing import Optional,类型注解无法识别。
方案:导入对应类型包,补齐类型导入语句。


二、工具调用异常类

错误:常识问题(1+1)频繁错误调用知识库 RAG
初始问题:写死了前置强制 RAG 检索
方案:手动模拟agent,应该迁移到langchain的原生agentcreate_openai_tools_agentAgentExecutor

中间错误:ImportError: cannot import name ‘AgentExecutor’ from ‘langchain.agents’
原因:create_openai_tools_agentAgentExecutor已经从主包langchain 正式移除,被归类到遗留兼容包langchain-classic,官方不再推荐在新项目使用LangChain;仅适用于0.x版本的。
LangChain 1.x 官方主推两种方案
方案 A(兼容旧代码):安装langchain-classic拉回遗留 API;
方案 B(官方推荐、长期维护):迁移到langgraph.prebuilt.create_react_agent

后续错误:依旧没解决
问题:①仅单工具,开源模型习惯性优先调用工具;②约束规则同时写在系统提示词、工具描述两处,指令稀释;③温度存在随机性,指令遵循不稳定。
方案:规则统一放系统提示词,精简工具描述;temperature=0;增加接口前置拦截 + 工具内二次拦截双重兜底。


错误:明明写了禁止调用,模型仍触发工具链路,仅拦截了向量检索
问题:拦截逻辑写在工具函数内部,只能跳过数据库检索,无法阻止模型生成工具调用决策。
方案:在 Agent 执行入口最前端做正则拦截,命中数学类问题直接返回结果,不走 Agent 流程。(试了没用,未解决


错误:Agent 检索文档无结果,兜底 RAG 可以正常查到文档
问题:误用全局@tool定义工具,无法动态传入top_k、相似度阈值,只能使用默认参数,和 RAG 检索配置不一致。
方案:保留原有_run闭包 +StructuredTool.from_function写法,每次请求动态注入检索参数,保证两端配置完全一致。


误解:工混淆工具名称字符串与函数参数
问题:误以为工具名称字符串会继承底层search_knowledge_base多参数签名,担心参数不匹配。
方案:name仅为工具标识字符串,对外入参由_run函数决定,仅暴露 query,,其余业务参数通过闭包注入,对模型不可见。


三、提示词与模型输出幻觉类

错误: 模型【思考】文本写调用工具,实际未触发工具执行,(格式幻觉)
问题:强制要求模型在自然回复内手写工具调用 JSON,混淆文本输出和结构化 Function Calling,出现格式仿写幻觉。
方案:取消手写 JSON 格式要求,仅让模型用文字说明决策理由;以后端tool_used、检索耗时、文档来源作为是否调用工具的唯一判定依据。


错误: Python 通用编程问题本该调用工具却未触发
问题:通用编程属于全网公开知识,模型判定无需检索;叠加格式幻觉仅仿写调用话术,未发起结构化工具调用。
方案:提示词明确划分边界:仅企业内部私有业务文档允许调用工具,公开常识禁止调用;优化输出格式,避免格式类幻觉,禁止手写工具调用 JSON。



四、代码架构选型类

报错:大模型选型错误:ResponseError: registry.ollama.ai/library/llama3:instruct does not support tools (status code: 400)
原因:基础模型llama3:instruct 本身并不支持原生的工具调用(Function Calling / Tools)。它无法理解 API 请求中包含的 tools 参数,因此返回了 400 错误。
方案:把模型换为 llama3.1:8b(ollama拉取模型,修改代码)。 换模型之后不需要重新docker镜像,只需要重启 docker compose restart, 前提:代码是挂载形式的。


版本不同下所选择的agent实现方式不同
方案:langchain 0.x 的原生agent(create_openai_tools_agentAgentExecutor),
和LangGraph 1.x React Agent(langgraph.prebuilt.create_react_agent) 的区别

维度LangChain 0.x 原生 AgentLangGraph 1.x React Agent
架构单一 Runnable + Executor 循环状态机 + 可编程图工作流
可控性低(黑盒)高(白盒,可自定义节点/边)
扩展性差(难加分支/并行)强(天然支持复杂逻辑)
调用次数限制max_iterations参数直接配置通过recursion_limit递归上限控制
流程自定义弱,执行流程固定无法修改极强,可自定义节点、分支、重试、路由
流式能力间接(需 hack), 中间步骤流式封装不完善,原生深度支持stream,可实时捕获每一步事件
对话记忆简易内存记忆,扩展性差原生支持MemorySaver,可持久化到 Redis / 数据库
适用场景快速原型、简单任务生产级、需长期维护的 agent
项目定位简单 Demo、老项目维护企业级生产项目、复杂 Agent 业务
组件LangChain 0.x 原生 AgentLangGraph 1.x React Agent
核心调度组件AgentExecutor(执行器)StateGraph 状态图
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=3,
verbose=True)
graph = StateGraph(MessagesState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_conditional_edges("agent", route_func)
推理封装组件create_openai_tools_agent内置 agent/tools 节点
agent = create_openai_tools_agent(
llm=llm,
tools=tools,
prompt=prompt)
方式1(底层原生):
def agent_node(state):
resp = llm.bind_tools(tools).invoke(state["messages"])
tool_node = ToolNode(tools)

方式2(项目封装快捷版):
agent = create_react_agent(
model=llm,
tools=tools,
prompt=AGENT_SYSTEM_PROMPT)
数据存储载体intermediate_steps + outputmessages 状态列表
res = agent_executor.invoke({"input":"10+20等于多少"})
answer = res["output"]
step_info = res["intermediate_steps"]
res = app.invoke({"messages":[("user","10+20等于多少")]})
all_messages = res["messages"]
final_result = all_messages[-1].content
次数限流方式max_iterationsrecursion_limit
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=3)
res = app.invoke(
{"messages":[("user","10+20等于多少")]},
config={"recursion_limit":5})
langchain 0.x 原生agentLangGraph 1.x React Agent
核心结构create_openai_tools_agent:根据提示词、工具列表生成符合 OpenAI 工具调用格式的 Prompt 模板。

AgentExecutor:Agent 执行器,负责循环调用 LLM、执行工具、限制最大迭代次数、错误处理,驱动整个工具调用流程
底层基于 状态(StateGraph) 实现:固定节点循环:调用LLM → 判断是否需要调用工具 → 执行工具 → 回到LLM;

create_react_agent只是官方封装好的开箱即用的 React 模板,底层依然是 LangGraph 状态机。
适合版本j仅 LangChain 0.x 可用 (在1.x版本已移除被归类到遗留兼容包langchain-classicLangChain 1.x + LangGraph 0.x/1.x 官方主推方案
实现方式单轮循环驱动:由 AgentExecutor 控制整个流程。状态机 + 图工作流驱动:基于 LangGraph 的有向图(DAG) 构建。
执行流程LLM 输出一个 带工具调用格式的字符串(如 {“tool”: “search”, “args”: {…}});

AgentExecutor 解析该字符串 → 调用对应工具;将工具结果拼回提示词 → 再次输入 LLM;

重复直到 LLM 输出最终答案(无工具调用)。
初始化 state(含用户消息);进入循环:
调用 call_model → 检查输出是否有 tool call;
若有 → 执行 call_tool → 更新 state → 回到 call_model;
若无 → 执行 finish 节点 → 输出最终 answer。
返回数据特点返回结果自带intermediate_steps:每一轮llm思考 工具调用 工具返回 的中间步骤列表,方便直接提取思考过程 工具调用记录。有统一 output 字段 直接拿到最终回答文本。

整体结构封装度高,上手简单,但底层流程被封装死,自定义流程能力弱
① 没有intermediate_steps、output字段,所有历史交互全部存在 messages 消息列表(AIMessage/ToolMessage/HumanMessage);
② 需要遍历全部messages,区分消息类型来提取思考过程、工具调用记录、最终回复;
③ 可以搭配MemorySaver实现持久化对话记忆,支持断点续跑、流式分步捕获每一步事件
优点①入门简单,中间步骤直接通过intermediate_step获取,不用遍历消息数组 ;
②可以通过max_iterations 直接限制最大工具调用轮次,防止死循环;
③老项目生态成熟,网上教程多
①官方未来主推技术栈,长期维护、无版本废弃风险;
②扩展性极强:可以自定义节点、条件分支、失败重试、权限拦截、多 Agent 协作;
③ 完美支持流式stream,可以实时捕获每一步思考、工具调用事件,适配前端打字输出 + 过程可视化;
④ 状态隔离完善,并发场景下请求上下文互不污染
缺点① LangChain 1.x 不再原生维护,只能依赖langchain-classic兼容包,长期有废弃风险;
② 流程固定,无法自由编排分支、循环、条件判断(比如工具失败重试、多分支路由);
③ 内存、状态管理弱,复杂多轮对话容易上下文错乱
①上手门槛略高,需要自己遍历消息数组解析中间步骤,没有现成的intermediate_steps;
② 最大调用轮次需要通过recursion_limit配置,需要理解状态机递归原理
适用场景①老旧 LangChain 0.x 线上项目维护;
② 简单单工具 RAG 场景,不需要复杂流程编排;
③ 快速验证工具调用 Demo,不想自己遍历消息提取中间步骤
① 需要流式输出、前端展示思考过程、工具调用过程;
② 未来可能扩展多工具、多智能体、复杂业务流程编排;
③企业级线上 RAG、Agent 生产系统

注意:questionquery的区别
Query:通常指 发送给搜索引擎/数据库 的指令。它可能经过改写(Query Rewriting),比如用户问“怎么修电脑”,Query 可能是“computer repair guide”。
。LangChain 的许多组件(如 Retriever、VectorStore)强制使用 query 作为参数名。

Question:通常指 用户的原始提问 或 LLM 需要回答的问题。


两种工具定义方式
@tool 装饰器 和 闭包 + StructuredTool.from_function 的区别

@tool 装饰器(简洁声明式写法)闭包 + StructuredTool.from_function(灵活工厂写法)
原理@tool 是语法糖,底层会自动把函数转换成 StructuredTool,自动解析函数签名、参数、函数文档字符串作为工具描述,一行代码完成工具注册。手动通过工厂外层函数接收每次请求的动态配置,利用闭包把top_k、similarity_threshold、tool_state绑定到内部_run函数,再手动封装为标准工具。对外仅暴露query一个参数给 LLM,其余业务配置对模型完全透明。
适用版本LangChain 0.1.x ~ 1.x 全版本兼容LangChain 0.x 旧版 → LangChain 1.x 新版全兼容,兼容性最强,没有版本废弃风险。
适合场景工具参数固定,不需要每次请求动态配置(固定 top_k、固定阈值,不会随接口入参变化);无运行时上下文需要保存:不需要 tool_state 记录检索耗时、上次检索结果;工具逻辑简单:只做一次查询、没有前置拦截、日志埋点、权限校验等复杂逻辑;多工具批量快速开发、脚本类小项目。需要每次请求动态变更工具配置:比如前端可配置 Top-K、相似度阈值;需要保存单次请求运行时状态:记录检索耗时、上次检索结果、调用次数;工具链路复杂:需要前置关键词拦截、日志打印、异常捕获、结果格式化;多用户并发场景,需要请求之间配置隔离,不能全局共用一套检索参数;企业级后端项目、RAG+Agent 线上业务系统。
✅ 优点代码极简、可读性高、快速上手,原生适配 Function Calling 参数校验动态参数注入,完美兼容前端可变配置;请求级上下文隔离,不会出现不同用户配置互相污染;工具执行链路完全可控,方便埋点、拦截、统一异常处理;工具名称、描述集中配置,脱离函数文档字符串,方便统一管理。
❌ 缺点无法直接注入每次请求的动态参数(不能用闭包传 top_k、相似度阈值);全局定义的@tool工具会单例常驻,所有请求共用一套配置,无法隔离每次请求的运行时状态;不方便做请求级别的上下文埋点、单次调用的状态记录。代码量更多,需要手动管理闭包与工具封装,入门门槛略高。

避坑补充:
①两种方式不能混用:同一个工具不要既写@tool又手动封装StructuredTool,会造成冗余、逻辑混淆;
②无论哪种写法,对外暴露给 LLM 的入参只能有 query业务配置必须通过闭包 / 全局固定配置隐藏,不能交给大模型传参;
③name参数只是工具调用时的字符串标识,不会绑定底层函数的参数签名,两种方式都不会出现参数不匹配问题。


纠结:展现大模型思考过程:agent.invoke非流式, 或 stream流式
问题:非流式需要等待全流程结束才能返回结果,无法实时查看思考过程、中间步骤。
方案:需要前端实时打字输出、展示分步思考过程则改用stream;仅后端批量测试可保留invoke。
示例:

#非流式:
# 提取完整思考过程
think_steps = []
for idx, msg in enumerate(all_messages):
    if isinstance(msg, AIMessage) and msg.content.strip():
        think_steps.append(f"步骤{idx+1}:{msg.content}")
# 把思考过程塞进返回体,前端可以展示
return {
    "reply": reply,
    "think_process": "\n".join(think_steps), # 新增思考过程字段
    "sources": last_result.get("sources") or [],
    "search_ms": float(tool_state.get("search_ms") or 0.0),
    "llm_ms": round(llm_ms, 2),
    "doc_group": last_result.get("doc_group"),
    "context": last_result.get("context") or "",
    "mode": "agent",
    "tool_used": tool_used,
}

五.渲染类

错误:代码内容渲染乱码
问题:知识库原始内容携带 HTML 标签,未经清洗直接输入 LLM,导致输出中出现 HTML 实体编码和残留标签。
方案:在工具结果格式化环节增加 HTML 反转义与正则去标签清洗步骤。


六.其他

错误: isinstance(observation, dict) 是死代码
原因:LangGraph 的 ToolMessage.content永远是字符串(即使工具返回字典,也会被序列化为 JSON 字符串)。
详述:① 普通字符串(99.9% 场景,create_react_agent 默认行为)。
② 多模态结构化字典列表 [{“type”:“text”,“text”:“xxx”}]。
langgraph.prebuilt.create_react_agent内置的 ToolNode内部会执行 str_output() 自动对工具返回值做强制序列化
- - 如果你工具 _run 返回 dict,框架内部会自动用 json.dumps 序列化为JSON 字符串塞进ToolMessage.content
- - 只有你手动直接构造 ToolMessage(content=[dict]) 时,content 才会是字典列表.


七.性能优化

可优化点_run工具函数内部重复实例化ChatOllama
优化原因:每次 Agent 请求、每次_run工具函数内部重复实例化ChatOllama,会频繁创建 LLM 连接、重复加载模型上下文,带来大量重复初始化开销,造成接口响应慢、资源冗余占用。
优化方案:采用全局单例懒加载方式,把 LLM 实例放到全局作用域,只初始化一次:
定义全局私有变量_llm_instance: Optional[ChatOllama] = None
封装_get_llm()函数,只有首次调用时才创建一次模型实例,后续所有请求全部复用同一个 LLM 对象
彻底避免每次请求反复创建 LLM,减少 TCP 连接、模型上下文重复加载的性能损耗


待优化:
检索参数统一:Agent 与兜底 RAG 共用同一套top_k、相似度阈值,避免参数错误导致无效向量检索、空查询浪费算力;

前置拦截优化:在接口最开始拦截数学类常识问题,直接跳过完整的 Agent 工具调用链路,省去 LLM 推理、向量编码的耗时;

精简提示词:删除两处重复的工具约束描述,减少 LLM 的上下文输入长度,降低大模型推理耗时;

选用非流式invoke/ 流式stream按需选型,避免无用的全链路等待造成的用户体感性能差。


更多推荐