(四)LangChain大模型应用开发:聊天机器人
1设置 LangSmith 环境变量用于追踪模型执行importos# 1. 设置 LangSmith 环境变量 (用于追踪)os.environ[LANGSMITH_TRACING]trueos.environ[LANGSMITH_API_KEY]API_Key# 请替换为真实的 Keyos.environ[LANGSMITH_ENDPOINT]https://api.smith.langchain.com# 建议检查 endpointos.environ[LANGSMITH_PROJECT]llm_server# 替换为真实项目名称2创建模型实例fromlangchain_ollama.chat_modelsimportChatOllama# 创建模型实例modelChatOllama(modelqwen3.5:latest,base_urlhttp://IP:11434)3创建会话存储fromlangchain_core.chat_historyimportBaseChatMessageHistory,InMemoryChatMessageHistory# 创建会话存储store{}defget_session_history(session_id:str)-BaseChatMessageHistory:ifsession_idnotinstore:store[session_id]InMemoryChatMessageHistory()returnstore[session_id]4创建提示词模板fromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholder# 创建提示词模板promptChatPromptTemplate.from_messages([(system,You are a helpful assistant.,),#历史消息占位符MessagesPlaceholder(variable_namehistory),(human,{humanMsg}),])5对话历史裁剪方法ConversationBufferMemory在 LangChain 的新版0.3.x 及以后中已被废弃改用 LCELLangChain Expression Language构建流程通过RunnableWithMessageHistory管理会话状态一般正常的对话模型都会有很多很多轮的对话所以我们要调整的方向就是在全量记忆的基础上将对话裁剪只保留最近的 n 轮对话其余的就丢弃掉。但是 LangChain 里的一轮并不是说一次输入和一次输出为一轮而是说输入所一轮输出也算一轮。所以我们假如让 n 1 的时候下一次我们提出问题的时候传入的只会是上一次的回复连上次的提问都不会传入的。LangChain 的记忆裁剪工具trim_messagestrim_messages本身提供的策略是按照消息数或按照 token 数来裁剪使用strategyfirst或strategylast历史消息。参数类型默认值功能 / 控制点messagesSequence[MessageLikeRepresentation]None—max_tokensint—裁剪后的最大 “token 数” 或者最大 “消息数” 取决于 token_countertoken_counter函数 或者 LLM 对象—用于计算消息历史中 token 的数量或消息条数。可以是一个模型对象支持 get_num_tokens_from_messages方法也可以是自定义函数。也可以用 len 来粗略按消息数计数。 (http://python.langchain.com)strategystr“last”裁剪策略 —— 是保留最近的消息还是最早的消息。可选典型值“first” 或 “last”(http://api.python.langchain.com)allow_partialboolFalse是否允许“部分消息”被截断。即某条消息内容如果过长是否可以只取一部分内容放进保留区域取头或取尾。(http://api.python.langchain.com)end_on消息类型 或 类型列表 或字符串或字符串列表None要求裁剪结果 在某些类型消息的之后停止。例如如果 end_on“human”那么所有最后一次出现的 Human 类型消息之后的消息将被忽略。具体行为取决于 strategy 是 first还是 “last”。 (http://api.python.langchain.com)start_on消息类型 或 类型列表 或字符串或字符串列表None只在 strategy“last” 情况下有意义要求裁剪结果 从某种类型消息首次出现的位置开始保留。即在保留最近的消息前先丢弃那种类型消息之前的内容如果设置了 start_on。但如果 include_systemTrue 并且系统消息在最前面则不受 start_on 的限制。(http://api.python.langchain.com)include_systemboolFalse是否保留首条 SystemMessage如果原始消息开头有 SystemMessage。这是因为 SystemMessage 通常包含对模型行为的指令等很重要的信息。只在 strategy“last” 下有意义。(http://api.python.langchain.com)text_splitter函数 或 TextSplitter 对象默认按换行符分割split on newlines当 allow_partialTrue 并且某条消息内容需要被截断时用来切割内容将一条消息拆成多个片段以取部分内容。(http://api.python.langchain.com)kwargs其他附加参数—可以接收额外参数但文档里没详细列出特别常用的。(http://api.python.langchain.com)1、保存前n轮对话内容# 对话历史裁剪保留前n轮对话# 按对话段数裁剪历史每段 Human AI 两条消息# 默认只保留最近 max_tokens 段trimmertrim_messages(max_tokens3,strategylast,token_counterlambdamsgs:len(msgs),include_systemTrue,allow_partialFalse,start_onhuman,)2、保存指定 token 数量的对话内容使用该方法裁剪内容需要知道 token 要怎么来具体计算其实这方面的知识很复杂具体计算 token 的方法有很多这里我们就不具体详细的讲了下面使用一个粗略的计算方法大概估算 token 数量。优先使用tiktoken库的cl100k_base编码进行精确计算若失败则通过字符数除以2进行粗略估算#定义token数量计算方法importtiktokenfromlangchain_core.messagesimportAIMessagedefcount_qwen_tokens(messages):encodingtiktoken.get_encoding(cl100k_base)num_tokens0formsginmessages:ifisinstance(msg,AIMessage):num_tokensmsg.usage_metadata[total_tokens]# 从大模型返回中获取token总数continueelse:num_tokenslen(encoding.encode(msg.content))# 使用tiktoken进行计算continuereturnnum_tokens# 对话历史裁剪保留指定token数量的历史消息trimmertrim_messages(max_tokens3,strategylast,token_countercount_qwen_tokens,include_systemTrue,allow_partialFalse,start_onhuman,)3、总结后保存不能直接使用 trim_messages的方法来实现需要调整历史记录的函数实现逻辑为先获取最后一条的信息然后交给大模型进行总结总结完以后将总结的信息替换原本的信息再传给大模型fromlangchain_core.promptsimportChatPromptTemplatedefsummarize_text_with_llm(text:str,max_len:int50)-str:用大模型总结文本控制在 max_len 字符内promptChatPromptTemplate.from_template(请在不超过{max_len}个字内简要总结以下内容\n\n{text})summaryllm.invoke(prompt.format(texttext,max_lenmax_len))returnsummary.contentifhasattr(summary,content)elsestr(summary)defget_session_history(session_id:str,summary_max_len:int50):获取会话历史并将最后一条AIMessage总结后替换ifsession_idnotinsession_store:session_store[session_id]ChatMessageHistory()historysession_store[session_id]# 如果最后一条是AI消息就做总结替换ifhistory.messagesandisinstance(history.messages[-1],AIMessage):last_ai_msghistory.messages[-1]summarizedsummarize_text_with_llm(last_ai_msg.content,max_lensummary_max_len)history.messages[-1]AIMessage(contentsummarized)# 替换为总结内容returnhistory6构建带历史记录的 Chain1、 使用trim_messages裁剪方法适用 1 和 2 两种裁剪方法chainRunnablePassthrough.assign(messagesitemgetter(history)|trimmer)|prompt|model with_message_historyRunnableWithMessageHistory(chain,get_session_history,input_messages_keyhumanMsg,history_messages_keyhistory,)2、使用总结后保存裁剪方法适用 3 总结后保存的裁剪方法chainprompt|model with_message_historyRunnableWithMessageHistory(runnablechain,get_session_historyget_session_history,input_messages_keyhumanMsg,history_messages_keyhistory)7使用 LangServe 部署应用提供 API 接口fromfastapiimportFastAPIfromlangserveimportadd_routesfromfastapi.middleware.corsimportCORSMiddleware# 1、创建appappFastAPI(titleLangChain Server,description聊天机器人,version0.0.1,)# 2、添加路由add_routes(app,with_message_history,path/robot)# 3、跨域支持app.add_middleware(CORSMiddleware,allow_origins[*],# 生产环境请指定具体域名allow_credentialsTrue,allow_methods[*],allow_headers[*],)if__name____main__:importuvicorn uvicorn.run(app,host0.0.0.0,port8000)8API 测试启动服务python server.py如图显示则表示服务启动成功使用接口测试工具访问8完整代码fromlangchain_ollama.chat_modelsimportChatOllamafromlangchain_core.chat_historyimportBaseChatMessageHistory,InMemoryChatMessageHistoryfromlangchain_core.runnables.historyimportRunnableWithMessageHistoryfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.messagesimporttrim_messages,AIMessagefromlangchain_core.runnablesimportRunnablePassthroughfromoperatorimportitemgetterimporttiktokenimportos# 1. 设置 LangSmith 环境变量 (用于追踪)os.environ[LANGSMITH_TRACING]trueos.environ[LANGSMITH_API_KEY]API_Key# 请替换为真实的 Keyos.environ[LANGSMITH_ENDPOINT]https://api.smith.langchain.com# 建议检查 endpointos.environ[LANGSMITH_PROJECT]study-projectdefcount_qwen_tokens(messages):encodingtiktoken.get_encoding(cl100k_base)num_tokens0formsginmessages:ifisinstance(msg,AIMessage):num_tokensmsg.usage_metadata[total_tokens]# 从大模型返回中获取token总数continueelse:num_tokenslen(encoding.encode(msg.content))# 使用tiktoken进行计算continuereturnnum_tokens# 创建模型实例modelChatOllama(modelqwen3.5:latest,base_urlhttp://IP:11434)# 创建会话存储store{}defget_session_history(session_id:str)-BaseChatMessageHistory:ifsession_idnotinstore:store[session_id]InMemoryChatMessageHistory()returnstore[session_id]# 创建提示词模板system_template你是一个乐于助人的助手请用50字以内回答问题。promptChatPromptTemplate.from_messages([(system,system_template),#历史消息占位符MessagesPlaceholder(variable_namehistory),(human,{humanMsg}),])# 对话历史裁剪保留指定token数量的历史消息trimmertrim_messages(max_tokens2500,strategylast,token_countercount_qwen_tokens,include_systemTrue,allow_partialFalse,start_onhuman,)# # 对话历史裁剪保留前n轮对话# trimmer trim_messages(# max_tokens3,# strategylast,# token_counterlambda msgs: len(msgs),# include_systemTrue,# allow_partialFalse,# start_onhuman,# )chainRunnablePassthrough.assign(messagesitemgetter(history)|trimmer)|prompt|model with_message_historyRunnableWithMessageHistory(chain,get_session_history,input_messages_keyhumanMsg,history_messages_keyhistory,)fromfastapiimportFastAPIfromlangserveimportadd_routesfromrobotimportwith_message_history# 4、创建appappFastAPI(titleLangChain Server,description聊天机器人,version0.0.1,)# 5、添加路由add_routes(app,with_message_history,path/robot)if__name____main__:importuvicorn uvicorn.run(app,host0.0.0.0,port8000)