LangChain最佳实践,我踩过的10个坑和解决方案
LangChain最佳实践我踩过的10个坑和解决方案用LangChain做项目快一年了从一开始照着官方文档抄代码到后来被各种隐坑折腾得半夜睡不着也算攒下了一本血泪账。今天挑10个让我印象最深的坑出来聊一聊希望能帮你少走点弯路。坑一、版本升级直接炸裂最早我用的还是0.1那套写法from langchain.chat_models import ChatOpenAI跑得好好的。某天手贱升到0.3一运行满屏ImportError。原因很简单LangChain把模型相关的类拆到了独立包里chat_models这个路径已经废弃。0.2之后LLMChain标记成deprecated0.3动得更大。解决办法是按新规范导入ChatOpenAI走langchain_openaiPrompt走langchain_core.prompts。fromlangchain_openaiimportChatOpenAIfromlangchain_core.promptsimportChatPromptTemplate llmChatOpenAI(modelgpt-4o-mini,temperature0)promptChatPromptTemplate.from_messages([(system,你是一个Python助手),(human,{question}),])chainprompt|llm升级前先看一眼CHANGELOG别像我一样直接pip install -U然后懵圈。我还建议把依赖版本锁死在requirements.txt里团队里别人拉代码不会因为版本不一致跑出不一样的结果。0.1和0.3的差别大到能把你的项目重写一遍这一点我有切肤之痛。坑二、temperature乱设一通我刚开始所有场景都默认temperature0.7结果做JSON抽取的时候模型开始发挥创造力字段名都能给你改了。temperature控制输出的随机性。数值越低越确定越高越发散。代码生成、信息抽取、分类这种要稳定结果的活老老实实设0。写文案、头脑风暴再往上调。# 抽取/分类场景extract_llmChatOpenAI(modelgpt-4o-mini,temperature0)# 创意场景creative_llmChatOpenAI(modelgpt-4o-mini,temperature0.9)我还踩过一个隐形坑同一个llm对象被多个chain复用以为改了参数其实改的是同一个实例。要复用就传参新建别图省事。坑三、Prompt全靠字符串拼接第一版代码里我的prompt全是f请回答{question}问号、引号、特殊字符一进来就乱套还经常把用户输入直接塞进去注入风险拉满。正经做法是用ChatPromptTemplate变量用花括号占位框架帮你转义。fromlangchain_core.promptsimportChatPromptTemplate promptChatPromptTemplate.from_template(请总结以下内容字数不超过{max_words}字。\n\n内容{content})formattedprompt.invoke({max_words:100,content:user_input})复杂一点的prompt我建议单独存成文件或者常量别散落在业务代码里改起来要命。坑四、错误处理全靠运气有阵子我的chain跑着跑着就挂要么API超时要么限流429直接抛异常整个流程断掉前面算的全白费。LangChain的底层invoke不会自动重试你得自己包一层。生产环境务必加上重试和超时。fromlangchain_core.runnablesimportRunnableLambdaimporttimedefsafe_invoke(chain,inputs,retries3):foriinrange(retries):try:returnchain.invoke(inputs,config{timeout:30})exceptExceptionase:ifiretries-1:raisetime.sleep(2**i)with_config里也能塞max_concurrency和重试策略比手写循环优雅我后来都改用它。坑五、Token超限静默截断做长文档总结时我把整篇文章塞进prompt模型没报错但返回的总结明显缺了后半段。查了半天才发现是上下文超了模型默默把后面的内容丢了。很多模型超长输入会截断而不报错。解决办法是先算token数超了就分段处理或者用map-reduce。fromlangchain_text_splittersimportRecursiveCharacterTextSplitter splitterRecursiveCharacterTextSplitter(chunk_size2000,chunk_overlap200,separators[\n\n,\n,。,],)chunkssplitter.split_text(long_text)用tiktoken预先估算token也行心里有数比事后抓瞎强。我后来养成的习惯是凡是处理超过两千字的内容一律先过一遍splitter宁可多调几次模型也别赌它不会截断。坑六、工具描述写得太随意做Agent的时候工具的description我一开始就写搜索网页四个字结果模型根本不知道啥时候该用要么乱调要么死活不用。工具描述就是模型看的使用说明要写清楚这个工具干什么、什么时候用、输入是什么格式。描述写得越具体Agent调用越准。fromlangchain_core.toolsimporttooltooldefsearch_web(query:str)-str:当需要查找最新资讯、事实性数据或问题超出知识范围时使用。 输入应为简洁的中文搜索关键词不要包含问号。returnresults我后来养成习惯工具名用动词描述里写清触发条件和输入规范调用量蹭蹭涨。坑七、Agent陷入死循环有个ReAct Agent跑着跑着就开始反复调用同一个工具同样的输入调了十几遍还不收敛token烧得我心疼。死循环一般有几个诱因。工具返回的内容太模糊模型觉得没解决就反复试。或者prompt里没限制最大迭代次数。解决办法是给Agent加max_iterations再让工具返回更结构化的结果。fromlangchain.agentsimportcreate_react_agent,AgentExecutor agentcreate_react_agent(llm,tools,prompt)executorAgentExecutor(agentagent,toolstools,max_iterations5,early_stopping_methodgenerate,handle_parsing_errorsTrue,)handle_parsing_errorsTrue这个参数救过我很多次解析失败时不会直接崩会给模型一次纠错机会。坑八、输出解析一直翻车我用PydanticOutputParser解析模型输出模型偶尔会在JSON外面加一句好的结果是解析直接挂。模型的输出很难百分百干净尤其是让它直接生成结构化数据时。OutputParser的get_format_instructions能把格式要求塞进prompt但还不够保险。更稳的做法是搭配with_structured_output直接让模型按schema返回。frompydanticimportBaseModel,FieldclassSummary(BaseModel):title:strField(description标题)keywords:list[str]Field(description关键词)structured_llmllm.with_structured_output(Summary)resultstructured_llm.invoke(总结一下RAG的原理)# result直接是Summary对象这招比手写正则提取稳太多我现在能结构化的全用它。坑九、API Key硬编码进仓库别笑我真干过把api_keysk-xxx写进代码然后push到GitHub的事第二天就收到OpenAI邮件说key泄露了。环境变量必须走配置文件或者系统环境变量LangChain默认就会读OPENAI_API_KEY。importosfromdotenvimportload_dotenv load_dotenv()# 从.env文件加载llmChatOpenAI()# 自动读取OPENAI_API_KEY.env加进.gitignore团队里用.env.example做模板谁也别图省事把密钥写死。坑十、调试全靠printchain一长中间出了问题根本不知道是哪一步挂的我一开始只会到处加print代码乱得没法看。LangChain的Runnable天然支持流式调试用全局verbose或者debug开关就能看到执行步骤。fromlangchain.globalsimportset_debug,set_verbose set_verbose(True)# 打印chain的执行步骤set_debug(True)# 更详细的调试信息# 或者单步查看中间结果fromlangchain_core.runnablesimportRunnablePassthrough chain({context:retriever,question:RunnablePassthrough()}|prompt|llm)正式上线前记得把debug关掉日志量很大。我调试阶段开verbose定位问题够用了。小结这10个坑里版本兼容和错误处理是那种不出事不知道、一出事要命的类型temperature和工具描述则是效果好坏的分水岭。写LangChain应用多花点时间在prompt和参数调校上比堆功能实在得多。我自己最大的教训是太迷信官方示例照着抄完就上线出了问题才发现人家示例里省掉了重试、超时、token校验这些保命逻辑。到这里LangChain本身的实践就聊得差不多了。但你会发现真要做复杂的多步Agent流程单纯靠Chain拼装还是有点吃力状态管理、循环控制、人在回路这些需求处理起来挺别扭。下一篇文章我们进入LangGraph篇看看它是怎么把Agent编排这件事做得更顺手的。