实战demo
agent实战
langchain的agent与langgraph的agent主要差异点在create_openai_functions_agent,
AgentExecutor这两个函数
前者的作用类似构建 Runnable
链 ,返回一个RunnablePassthrough.assign(...)|prompt|llm_with_tools|ToolsAgentOutputParser(),但其invoke仅能完成单步的调用,而AgentExecutor
会自动完成3 步循环(调用工具→拼回结果→再次调用 LLM),直到任务结束。
以下为ai的解释
直接使用 agent (Runnable) 的局限性:
单步执行 : 你直接调用 agent.invoke() 或
agent.ainvoke()
时,它通常只执行一步 。对于像
create_tool_calling_agent 生成的 agent
来说,这一步就是:
接收输入(包括历史消息和 agent_scratchpad)。
让 LLM 决定是给出最终答案 (AgentFinish) 还是调用工具
(AgentAction)。
返回这个决定。
工具调用需要手动处理 : 如果 LLM 决定调用工具(返回
AgentAction),你 需要负责:
从返回的 AgentAction 中找出工具名称和输入参数。
在你的工具列表中找到对应的工具。
执行这个工具。
获取工具的输出(Observation)。
再次手动调用
agent.invoke(...) ,把工具的输出(通常需要格式化成
ToolMessage)放回 agent_scratchpad 或
intermediate_steps 中。
重复这个过程,直到 agent 最终返回
AgentFinish。
使用 AgentExecutor 的优势:
AgentExecutor
就是为了解决上述问题而设计的。它本质上是一个自动化的执行引擎 ,为你管理整个
Agent 的思考-行动-观察循环。
自动化循环 : AgentExecutor
内部会自动运行那个循环:
调用 agent (Runnable)。
检查返回的是 AgentAction 还是
AgentFinish。
如果是 AgentAction,它会自动根据你提供的
tools 列表找到并执行对应的工具。
它会自动将工具的输出(Observation)记录下来,并作为下一步的输入(放入
agent_scratchpad)再次调用 agent。
这个过程会一直重复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from langchain.agents import create_openai_functions_agent, AgentExecutor from langchain.tools import tool from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field # 1. 定义工具 class WeatherInput(BaseModel): location: str = Field(description="城市名称") @tool("get_weather", args_schema=WeatherInput) def get_weather(location: str) -> str: """查询城市天气""" return f"{location} 今天是晴天,25°C" # 2. 创建Agent llm = ChatOpenAI(model="gpt-4") tools = [get_weather] prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个助手,可以调用工具"), ("human", "{input}") ]) agent = create_openai_functions_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # 3. 执行 result = agent_executor.invoke({"input": "北京天气如何?"}) print(result["output"])
工具调用
利用bind_tools绑定工具,当大模型需要调用工具的时候,会返回工具信息,tool_calls,如下
[{‘name’: ‘add_numbers’, ‘args’: {‘a’: 15, ‘b’: 27}, ‘id’:
‘4e7b261cce6d4e3da09134086c704c3c’, ‘type’: ‘tool_call’}]
llm_with_tools.invoke(...)
只是一个单步调用 ,LLM
返回的是“我想调用哪个工具、传什么参数” (即
tool_calls)。 但 LLM
并不会自动执行工具 ,所以你必须:
手动执行工具 (或让 AgentExecutor 帮你执行)。
把执行结果拼回对话 (作为
ToolMessage)。
再次调用
LLM ,让它基于工具返回的结果生成最终答案。
这里展示的是手动拼接,并传给大模型,如下
1 2 3 4 5 6 7 # 将工具的输出发送回LLM,让LLM基于结果生成最终回答 # 这是一个关键步骤,通常在Agent中自动处理。这里手动演示。 final_response = llm_with_tools.invoke([ HumanMessage(content="What is 15 + 27?"), AIMessage(content="", tool_calls=[tool_call]), # 告知LLM它之前建议的工具调用 ToolMessage(content=str(result), tool_call_id=tool_call['id']) # 告知LLM工具的执行结果 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 from langchain_core.tools import tool from typing import Literal from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage, ToolMessage # 定义一个加法工具 @tool def add_numbers(a: float, b: float) -> float: """ Adds two numbers together. Args: a: The first number. b: The second number. """ return a + b # 我们可以定义更多的工具,例如一个乘法工具 @tool def multiply_numbers(a: float, b: float) -> float: """ Multiplies two numbers together. Args: a: The first number. b: The second number. """ return a * b # 将我们定义的工具放在一个列表中 tools = [add_numbers, multiply_numbers] # 初始化LLM llm = ChatOpenAI( temperature=0.5, model_name="deepseek-v3-0324", # 聊天模型通常使用"gpt-3.5-turbo"或"gpt-4" openai_api_base="https://api.qnaigc.com/v1", # 例如,您可以指定base_url openai_api_key="sk-" # 直接在此处设置API密钥,或者通过环境变量设置 ) # 将工具绑定到LLM # LLM现在知道了add_numbers和multiply_numbers这两个工具及其功能 llm_with_tools = llm.bind_tools(tools) # 场景一:LLM直接回答,不需要工具 print("--- 场景一:LLM直接回答 ---") response1 = llm_with_tools.invoke([HumanMessage(content="Hello, what's your name?")]) print(response1.content) # LLM直接生成文本回复 print("\n--- 场景二:LLM决定调用工具 ---") # 场景二:LLM决定调用工具 # 当LLM的响应中包含tool_calls时,意味着它想要调用一个或多个工具 response2 = llm_with_tools.invoke([HumanMessage(content="What is 15 + 27?")]) print(response2.tool_calls) # 打印LLM决定调用的工具信息 # 检查并执行LLM建议的工具调用 if response2.tool_calls: for tool_call in response2.tool_calls: if tool_call['name'] == "add_numbers": # 提取LLM为工具调用生成的参数 args = tool_call['args'] result = add_numbers.invoke(args) # 执行工具 print(f"Tool call: add_numbers({args['a']}, {args['b']}) = {result}") # 将工具的输出发送回LLM,让LLM基于结果生成最终回答 # 这是一个关键步骤,通常在Agent中自动处理。这里手动演示。 final_response = llm_with_tools.invoke([ HumanMessage(content="What is 15 + 27?"), AIMessage(content="", tool_calls=[tool_call]), # 告知LLM它之前建议的工具调用 ToolMessage(content=str(result), tool_call_id=tool_call['id']) # 告知LLM工具的执行结果 ]) print("Final LLM response based on tool output:") print(final_response.content)
概念扫盲
Document 对象
Document 对象是 LangChain
用来封装和处理文本数据的基本单位。无论您是从 PDF、Markdown
文件、网站还是数据库加载数据,LangChain 都会将这些数据转换成一个或多个
Document 对象,以便在后续的流程中使用。
一个 Document 对象主要包含两个部分:
page_content (字符串)
这是文档对象的核心,存储了原始的文本内容。例如,如果加载一个
Markdown 文件, page_content 就会包含该文件的所有文本。
metadata (字典)
这是一个字典,用于存储关于文档的“元数据”或附加信息。这些信息对于过滤、追踪或增强文档处理流程非常有用。常见的元数据包括:
source :文档的来源,比如文件名、URL等。
page :如果文档来自多页文件(如PDF),这里可以存储页码。
其他自定义信息:您可以添加任何有助于您应用的信息,如作者、创建日期等。
除了通过文档加载器(Loaders)自动创建,您也可以手动创建一个 Document
对象。这在测试或处理简单文本时非常方便。
1 2 3 4 5 6 7 8 9 # 创建一个简单的 Document 对象 doc = Document( page_content="这是文档的主要内容。LangChain 真酷!", metadata={ 'source': 'my_notebook.ipynb', 'author': 'AI Assistant', 'chapter': 2 } )
Runnable协议
“Runnable” 协议
参考资料
2025最新版!langchain入门到精通实战教程!结合实战案例,干货拉满!99%的人不知道的暴利玩法,学完敢谷歌工程师叫板!_哔哩哔哩_bilibili
introduction |
LangChain中文网
跟着官网学langchain2025(version
0.3)_哔哩哔哩_bilibili
LangGraph
Platform - Docs by LangChain