从一次半夜的线上事故说起凌晨两点报警电话把我从床上拽起来。生产环境上一个基于LangChain的AI Agent突然开始胡言乱语——用户问“帮我查一下昨天的订单”它居然回复“我无法访问数据库建议您联系客服”。更离谱的是同一个问题重试三次三次回答都不一样。我盯着日志看了半小时发现问题出在Prompt Template里。团队里新来的同学把用户输入直接拼到了系统提示词后面没有做任何格式约束。LLM在某个时刻把用户输入当成了新的指令覆盖了原有的系统设定。这就是典型的“提示注入”漏洞而根源在于对LangChain核心组件的理解只停留在“会用”层面。今天这篇笔记我把LangChain五个核心组件拆开揉碎结合踩过的坑说清楚它们到底怎么配合、哪里容易翻车。LLM别把它当黑盒很多人把LLM当成一个“输入字符串、输出字符串”的函数这是第一个大坑。LangChain里的LLM组件其实是个适配器层它封装了不同模型的调用差异但核心逻辑没变——你给什么它回什么。我见过最典型的错误写法# 别这样写这是灾难llmOpenAI(modelgpt-4)resultllm(帮我写一封邮件)这种写法等于把Prompt的控制权完全交给了LLM的默认行为。正确的做法是明确指定temperature、max_tokens这些参数尤其是temperature。生产环境里我通常把temperature设为0.1甚至0确保输出稳定性。只有做创意生成时才调到0.7以上。另一个容易忽略的点LLM组件支持流式输出。如果你做的是对话机器人一定要用streamTrue否则用户等十几秒才看到完整回复体验极差。我见过有人为了省事直接等完整结果再返回结果被用户投诉“卡死了”。Prompt Template模板不是字符串拼接Prompt Template是LangChain里最容易被低估的组件。很多人觉得“不就是f-string吗”然后直接写# 这里踩过坑千万别这样promptf你是一个客服助手。用户的问题是{user_input}问题在哪当user_input包含特殊字符、换行符、甚至恶意指令时LLM会把它当成Prompt的一部分解析。正确的做法是用LangChain的PromptTemplatefromlangchain.promptsimportPromptTemplate template你是一个客服助手请根据以下用户问题给出回答。 用户问题{question} 回答promptPromptTemplate.from_template(template)这样写的好处是LangChain会自动处理变量注入时的转义和格式化。更重要的是它支持部分变量填充——你可以先固定系统角色再动态注入用户输入避免提示注入。还有一个进阶用法FewShotPromptTemplate。当你需要给LLM提供示例时不要硬编码在字符串里用ExampleSelector动态选择最相关的示例。我做过一个测试同样的任务用动态示例比固定示例准确率提升了12%。Chain串联不是简单的函数调用Chain是LangChain的核心编排机制但很多人把它理解成了“函数链”。实际上Chain的精华在于状态传递和错误处理。看一个反面教材# 别这样写调试时会疯掉chain1LLMChain(llmllm,promptprompt1)chain2LLMChain(llmllm,promptprompt2)result1chain1.run(input)result2chain2.run(result1)这种写法的问题一旦result1格式不对chain2直接崩掉而且你根本不知道中间状态是什么。正确的做法是用SequentialChainfromlangchain.chainsimportSequentialChain chainSequentialChain(chains[chain1,chain2],input_variables[input],output_variables[final_output],verboseTrue# 这里一定要开调试神器)verboseTrue是救命稻草。生产环境里我建议所有Chain都开启verbose把中间结果打到日志里。线上出问题时你能快速定位到是哪一步出了问题。还有一个容易被忽略的Chain支持memory。如果你做多轮对话一定要用ConversationChain或者自己组合Memory组件。我见过有人手动拼接历史对话结果上下文窗口爆了LLM开始胡言乱语。Agent别让它裸奔Agent是LangChain里最酷也最危险的组件。它让LLM有了“行动能力”但同时也意味着LLM可以调用任何它想调用的工具——包括你不想让它调用的。我踩过最大的坑Agent在没有约束的情况下会反复调用同一个工具直到超时。有一次Agent为了回答“今天天气怎么样”连续调了20次天气API直接把API配额打爆了。解决方案是给Agent设置max_iterations和early_stoppingfromlangchain.agentsimportAgentExecutor agent_executorAgentExecutor(agentagent,toolstools,max_iterations3,# 最多调用3次工具early_stopping_methodgenerate,# 超时后直接生成回答handle_parsing_errorsTrue# 这里踩过坑一定要开)handle_parsing_errorsTrue是血泪教训。Agent的输出格式一旦不对整个流程就会卡死。开启这个选项后LangChain会自动重试或降级处理。另一个关键点Agent的Prompt里一定要明确告诉它“什么时候该停止”。我通常会在系统提示词里加一句“如果你已经得到答案直接输出结果不要继续调用工具。”Tool权限最小化原则Tool是Agent的“手”但很多人把Tool设计得过于强大。比如给Agent一个“执行任意SQL”的工具——这等于把数据库的钥匙交给了LLM。正确的做法是每个Tool只做一件事且权限最小化。# 好的设计classGetOrderTool(BaseTool):nameget_orderdescription根据订单ID查询订单信息输入参数为订单IDdef_run(self,order_id:str):# 这里只做查询不做修改returndb.query(fSELECT * FROM orders WHERE id {order_id})# 坏的设计classExecuteSQLTool(BaseTool):nameexecute_sqldescription执行任意SQL语句def_run(self,sql:str):# 别这样写会出大事returndb.execute(sql)还有一个细节Tool的description要写得足够详细因为Agent是根据description来决定调用哪个工具的。我见过有人写“这个工具用来查询数据”结果Agent在需要删除数据时也调用了它。五个组件如何配合理解了每个组件后关键是它们如何协同工作。我画一个典型的流程用户输入进入AgentAgent根据Prompt Template生成思考过程Agent决定调用哪个ToolTool执行并返回结果Agent将结果拼接到上下文继续推理直到Agent认为得到答案输出最终结果这个过程中Chain负责串联LLM调用LLM负责生成文本Prompt Template负责格式化输入Tool负责执行具体操作Agent负责决策。最容易出问题的地方是步骤2和3的衔接——Agent的思考结果必须格式化成Tool能识别的参数。我建议在Agent的Prompt里明确给出输出格式示例比如Action: get_order Action Input: {order_id: 12345}个人经验性建议永远不要信任LLM的输出。即使你用了最严格的PromptLLM也可能输出格式错误的内容。所有Agent的输出都要做校验和降级处理。日志是命根子。每个组件的输入输出都要打日志尤其是Agent的思考过程和Tool的调用结果。线上出问题时这些日志是你唯一的线索。从简单开始。不要一开始就搞复杂的多Agent协作。先用一个Agent两个Tool跑通流程再逐步增加复杂度。我见过太多人一上来就搞“Agent编排Agent”结果调试了三天还没跑通。成本控制。LLM调用很贵尤其是Agent多次调用Tool时。建议设置每次对话的token上限超过后强制结束。版本锁定。LangChain迭代很快API经常变。生产环境一定要锁定版本号不要用latest。我吃过这个亏升级后代码全崩。最后说一句LangChain是个好工具但它不是银弹。理解每个组件的设计意图和边界比会写代码更重要。下次遇到线上事故你至少知道该查哪里。