尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LangChain02 Agent核心双翼

LangChain02 Agent核心双翼 LangChain02 Agent核心双翼核心目标掌握Tool Calling工具调用和RAG检索增强生成。这是让大模型“长出手脚”和“拥有外部大脑”的绝对核心也是后续学习 LangGraph 的底层基石。核心心法理解底层闭环逻辑拒绝黑盒手写循环拥抱 LCEL。一、Tool Calling让模型“长出手脚”核心认知大模型不会直接执行 Python 代码。它只负责“决定”调用哪个工具并提取参数真正的执行由后端代码完成。1. 工具的三要素使用tool装饰器定义工具模型主要依靠以下三要素来决定是否调用name工具名称。description (docstring)工具能力与适用场景写得越清晰调用越准。args schema参数类型与说明。2. 核心代码骨架fromlangchain_core.toolsimporttoolfromlangchain_core.messagesimportHumanMessage,ToolMessage# 1. 定义工具tooldefget_weather(city:str)-str:查询指定城市的天气。当用户询问天气时使用。returnf{city}今天晴天26度。tools[get_weather]# 2. 绑定工具给模型llm_with_toolsllm.bind_tools(tools)# 3. 调用模型并检查 tool_calls (核心)responsellm_with_tools.invoke([HumanMessage(content北京天气如何)])# 判断模型是否决定调用工具ifresponse.tool_calls:fortool_callinresponse.tool_calls:tool_nametool_call[name]tool_argstool_call[args]tool_idtool_call[id]# 4. 后端执行工具resultget_weather.invoke(tool_args)# 5. 包装为 ToolMessage 并喂回模型 (必须带 tool_call_id)tool_msgToolMessage(contentresult,tool_call_idtool_id)# 6. 再次调用模型生成最终自然语言回答final_responsellm_with_tools.invoke([HumanMessage(...),response,tool_msg])3. 手写 Agent 闭环 (LangGraph ToolNode 的底层原理)用户提问 - LLM 判断 - 有 tool_calls? ├── Yes - 执行 Python 函数 - 构造 ToolMessage - 追加到 messages - 回到 LLM └── No - 输出最终回答 (response.content)4.最终代码importsysfromlangchain_core.messagesimportHumanMessage,ToolMessagefrompathlibimportPath# 将项目根目录加入 sys.path以便导入 day01 目录中的模块sys.path.insert(0,str(Path(__file__).resolve().parent.parent))fromlangchain_core.toolsimporttoolfromday01.day01importllmtooldefget_weather(city:str)-str: 查询指定城市的天气。 当用户询问某个城市天气时使用。 参数: city: 城市名称例如“北京”、“上海”。 weather_data{北京:晴天气温 26 度,上海:多云气温 24 度,广州:小雨气温 28 度,焦作:晴天气温30度,临沂:多云气温20度}returnweather_data.get(city,f暂时无法查询{city}的天气)tooldefcalculate_math(expression:str)-str: 计算数学表达式。 当用户需要进行数学计算时使用。 参数: expression: 合法的数学表达式例如 2 3 * 4。 try:# 学习演示用生产环境不要直接用 evalresulteval(expression,{__builtins__:{}},{})returnstr(result)exceptExceptionase:returnf计算失败{e}# # 主程序入口# if __name__ __main__: 1. 用户提问 2. 模型判断需要工具 3. 模型返回 tool_calls 4. 代码根据 tool name 找到对应工具 5. 代码执行工具得到结果 6. 把结果包装成 ToolMessage 7. 把 ToolMessage 加回 messages 8. 再次调用模型 9. 模型根据工具结果生成最终回答 tools[get_weather,calculate_math]# 2. 建立工具名到工具对象的映射tools_by_name{t.name:tfortintools}# 3. 绑定工具给模型llm_with_toolsllm.bind_tools(tools)# 4. 手写 Agent 循环defrun_agent(question:str,max_steps:int5)-str:messages[HumanMessage(contentquestion)]forstepinrange(max_steps):print(f\n 第{step1}步调用模型 )ai_messagellm_with_tools.invoke(messages)messages.append(ai_message)print(模型返回 content,ai_message.content)print(模型返回 tool_calls,ai_message.tool_calls)# 如果没有工具调用说明模型已经给出最终回答ifnotai_message.tool_calls:returnai_message.content# 如果有工具调用就执行工具fortool_callinai_message.tool_calls:tool_nametool_call[name]tool_argstool_call[args]tool_call_idtool_call[id]print(f模型决定调用工具{tool_name})print(f工具参数{tool_args})selected_tooltools_by_name[tool_name]tool_resultselected_tool.invoke(tool_args)print(f工具执行结果{tool_result})# 把工具结果包装成 ToolMessagemessages.append(ToolMessage(contentstr(tool_result),tool_call_idtool_call_id))print(最新的消息为{messages})return达到最大执行步数任务未完成。# 5. 测试answerrun_agent(焦作今天天气怎么样)print(\n最终回答,answer)二、RAG让模型“拥有外部大脑”核心认知将私有知识向量化存储用户提问时检索最相关的片段Top-K塞入 Prompt 让模型基于上下文回答从而解决幻觉和知识滞后问题。1. RAG 核心流程与组件步骤核心组件 / 方法关键参数 / 作用1. 加载原生open()Document替代废弃的TextLoader避免编码警告。2. 切分RecursiveCharacterTextSplitterchunk_size(块大小),chunk_overlap(重叠防语义断裂)。3. 向量化Embeddings(如 DashScope/OpenAI)将文本转为高维向量。4. 存储FAISS/Chroma本地轻量级向量数据库。5. 检索vectorstore.as_retriever()search_kwargs{k: 3}(返回最相关的3个块)。2. 核心难点RunnableLambda格式化检索器返回的是[Document, Document]对象列表必须用RunnableLambda将其转为带编号的字符串以便省 Token 且支持引用。fromlangchain_core.runnablesimportRunnableLambdadefformat_docs(docs):return\n\n.join(f[{i}]{doc.page_content}fori,docinenumerate(docs,1))format_runnableRunnableLambda(format_docs)3. LCEL 组装 RAG Chain使用字典和RunnablePassthrough并行传递context和question。fromlangchain_core.runnablesimportRunnablePassthrough rag_chain({context:retriever|format_runnable,# 检索并格式化question:RunnablePassthrough()# 原样传递用户问题}|rag_prompt# 包含 {context} 和 {question} 的 Prompt|llm)answerrag_chain.invoke(学生请假流程是什么)4. 防幻觉 Prompt 设计 (必加)你必须只根据上下文回答问题。 如果上下文中没有答案请回答根据上下文无法确定。 不要编造信息。回答时请使用引用编号例如 [1]、[2]。5.RAG最终代码importosfromdotenvimportload_dotenvfromlangchain_core.documentsimportDocumentfromlangchain_openaiimportChatOpenAI# from langchain_community.document_loaders import TextLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_community.embeddingsimportDashScopeEmbeddingsfromlangchain_community.vectorstoresimportFAISSfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.runnablesimportRunnableLambda,RunnablePassthrough load_dotenv()# 1. 初始化 LLMllmChatOpenAI(modelkimi-k2.7-code,base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1,api_keyos.getenv(DASHSCOPE_API_KEY),temperature1,)# 2. 初始化 EmbeddingsembeddingsDashScopeEmbeddings(modelos.getenv(embedding_name),dashscope_api_keyos.getenv(DASHSCOPE_API_KEY))# 3. 加载文档# (1) 用 Python 原生方式读取文件file_pathhandbook.txtwithopen(file_path,r,encodingutf-8)asf:textf.read()# (2) 手动包装成 LangChain 的 Document 对象documents[Document(page_contenttext,metadata{source:file_path}# metadata 用于记录来源RAG 中很有用)]# 4. 切分文档splitterRecursiveCharacterTextSplitter(chunk_size300,chunk_overlap60)chunkssplitter.split_documents(documents)print(f切分文档后chunks的类型是{type(chunks)})# 5. 存入向量数据库vectorstoreFAISS.from_documents(documentschunks,embeddingembeddings)# 6. 创建检索器retrievervectorstore.as_retriever(search_typesimilarity,search_kwargs{k:3})# 7. 格式化检索结果defformat_docs(docs):return\n\n.join(f[{i}]{doc.page_content.strip()}fori,docinenumerate(docs,1))# 8. 定义 Promptrag_promptChatPromptTemplate.from_messages([(system, 你是一个本地文档问答助手。 你必须只根据上下文回答问题。 如果上下文中没有答案请回答根据上下文无法确定。 不要编造信息。 回答时请使用引用编号例如 [1]、[2]。 上下文 {context} ),(human,{question})])# 9. 构建 RAG Chainrag_chain({context:retriever|RunnableLambda(format_docs),question:RunnablePassthrough()}|rag_prompt|llm)# 10. 测试question硕士1班有什么学生学号是多少responserag_chain.invoke(question)print(response.content)三、避坑指南 (Day 2 Don’ts)❌不要看response.content来判断工具调用必须看response.tool_calls。❌不要漏掉tool_call_idToolMessage必须带上 ID否则模型不知道结果对应哪次调用。❌不要用老版TextLoader会报 DeprecationWarning直接用 Pythonopen()Document。❌不要学老版RetrievalQA直接用 LCEL 自己拼装 RAG Chain。❌不要碰复杂的 Memory 类如ConversationBufferMemoryLangGraph 中用messages状态和Checkpointer管理记忆。❌不要一上来学高级 RAG先跑通基础 Top-K 检索不要碰 Parent Document、Hybrid Search 等。四、面试QAQ1Function Calling 的本质是什么模型真的执行代码了吗A:模型没有执行代码。本质是模型根据用户输入和工具的 description/schema生成一个结构化的 JSON 请求包含工具名、参数和 ID。后端代码解析这个请求执行真正的 Python 函数然后将结果通过ToolMessage喂回给模型模型再基于结果生成自然语言回答。Q2为什么 RAG 中需要chunk_overlap文本重叠A:因为机械切分文本可能会把完整的一句话或一个逻辑段落从中间切断导致语义丢失。设置chunk_overlap让相邻的文本块保留一部分重叠内容可以保证上下文语义的连贯性提高检索准确率。Q3在 LCEL 中构建 RAG 时为什么要用RunnableLambdaA:因为 Retriever 检索出来的是Document对象列表而 Prompt 需要的是纯文本字符串。RunnableLambda允许我们将自定义的 Python 格式化函数如拼接带[1] [2]编号的字符串包装成 LCEL 兼容的组件无缝接入管道|中。Q4手写的 Tool Calling while 循环和 LangGraph 有什么关系A:手写的 while 循环就是 LangGraph 中 Agent 节点和 Tool 节点交互的底层逻辑。在 LangGraph 中这个循环被抽象成了状态图Agent 节点判断是否有tool_calls如果有通过条件边Conditional Edge路由到 Tool 节点执行执行完后再路由回 Agent 节点直到没有工具调用为止。
返回列表