LangChain学习

实战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) 的局限性:

  1. 单步执行: 你直接调用 agent.invoke()agent.ainvoke() 时,它通常只执行一步。对于像 create_tool_calling_agent 生成的 agent 来说,这一步就是:
    • 接收输入(包括历史消息和 agent_scratchpad)。
    • 让 LLM 决定是给出最终答案 (AgentFinish) 还是调用工具 (AgentAction)。
    • 返回这个决定。
  2. 工具调用需要手动处理: 如果 LLM 决定调用工具(返回 AgentAction),需要负责:
    • 从返回的 AgentAction 中找出工具名称和输入参数。
    • 在你的工具列表中找到对应的工具。
    • 执行这个工具。
    • 获取工具的输出(Observation)。
    • 再次手动调用 agent.invoke(...),把工具的输出(通常需要格式化成 ToolMessage)放回 agent_scratchpadintermediate_steps 中。
    • 重复这个过程,直到 agent 最终返回 AgentFinish

使用 AgentExecutor 的优势:

AgentExecutor 就是为了解决上述问题而设计的。它本质上是一个自动化的执行引擎,为你管理整个 Agent 的思考-行动-观察循环。

  1. 自动化循环: 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 并不会自动执行工具,所以你必须:

  1. 手动执行工具(或让 AgentExecutor 帮你执行)。
  2. 把执行结果拼回对话(作为 ToolMessage)。
  3. 再次调用 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 对象主要包含两个部分:

  1. page_content (字符串)

    • 这是文档对象的核心,存储了原始的文本内容。例如,如果加载一个 Markdown 文件, page_content 就会包含该文件的所有文本。
  2. 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