1 工具 Tool

1.1 设置

需要设定如下内容,以便和 LLM 交互,名称、描述和 JSON 都在提示中使用。

  • 名称(必需)
  • 描述(可选)
  • 输入的结构(可选)
  • 函数的具体实现(需要在 langchain 中定义)
  • 其它参数,如:是否将工具结果直接返回给用户

1.2 自定义工具

创建实现具体功能的函数。

1
2
3
4
5
6
7
8
9
10
from langchain.tools import tool

@tool
def search(query: str) -> str:
"""Look up things online."""
return "LangChain"

print(search.name)
print(search.description)
print(search.args)

用 tool 装饰器,能自动生成名字描述等内容。这是最简单的一种方法,其它方法见:参考文档的自定义工具链接。

建议开始用 tool 装饰器,后面复杂了再切换到 BaseTool 方法。

2 Agent 原理

Agent 使用 LLM 来选择执行操作的顺序。在 Chain 模式中,操作序列是硬编码的,而在 Agent 模式中,语言模型作为推理引擎,决定采取哪些操作以及按什么顺序执行。

流程:选择工具 → 执行工具 → 处理结果 → 返回用户。在选择工具和处理结果时,都使用大模型。

  • 根据用户问题,通过 LLM 选择工具
  • 执行工具并得到结果
  • 将用户问题和执行结果传给 LLM,由其判断是否进一步选择工具或直接回答用户

这样可能会引发循环调用工具,浪费 token。可以通过设置 agent 的 max_iterations 来控制迭代次数,默认值为 15 次。

3 实例

3.1 代码

这里实现了一个计算文本长度的工具。在 LLM 对话过程中,当需要统计长度时,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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from langchain_openai import ChatOpenAI
from langchain.agents import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents.format_scratchpad.openai_tools import format_to_openai_tool_messages
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser
from langchain.agents import AgentExecutor

@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
#get_word_length.invoke("abc") # for test

tools = [get_word_length]
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are very powerful assistant, but don't know current events",
),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm_with_tools = llm.bind_tools(tools)

agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(
x["intermediate_steps"]
),
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
print(list(agent_executor.stream({"input": "How many letters in the word eudca"}))) # 运行方法一
print(llm.invoke("How many letters in the word educa")) # 运行方法二
print(llm.invoke("hello")) # 无关tool的运行

3.2 看更多打印信息

1
2
import logging
logging.basicConfig(level=logging.DEBUG)

3.3 相关概念

  • Tools:工具是 Agent 可以调用的函数。
  • Toolkits:LangChain 提供了工具包的概念——完成特定目标所需的 3-5 个工具组。
  • Agent:负责决定下一步采取什么步骤的链。通常由语言模型、提示符和输出解析器提供支持。
    • agent = tools + llm_chain + output_parser
    • llm_chain = llm + prompt
  • AgentExecutor:代理执行程序是代理的运行时,实际调用代理、执行其选择的操作、将操作输出传递回代理并重复此过程。

3.4 常用问题

  • 跟踪工具函数的具体输出内容
    • 实现 BaseCallbackHandler,记录工具的输入输出参数(内部逻辑和数据不建议过于依赖 Langchain 工具)。
  • 如何实现默认信息
    • 当所有工具都不匹配时,默认会返回 LLM 的回复。如果希望返回默认信息(比如在登录前,所有问题都回复“请先登录”),可以重写 OpenAIToolsAgentOutputParser 的 parse_result 方法。
  • 有些问题在 Langchain Agent 的结构下很难解决,需要继承工具类并修改其内部的复杂逻辑。

3.5 langchain agent 核心代码逻辑

  • 在 langchain/agents/agent.py 文件中,查看 AgentExecutor 类。
    • 工具的实际运行在 _perform_agent_action() 方法中。
    • 单步执行在 _take_next_stop() 方法,通过调用 _iter_next_step() 完成。处理工具结果也是一个步骤。
    • 与 LLM 的交互在 plan() 方法中进行,该方法在构造输入时会结合问题和之前调用工具的结果。

如果想了解主流程,建议打断点并跟踪代码,这样就能明白整个过程。

3.6 优缺点

  • 优点
    • 肯定比完全自己写要强多了
    • 调用工具很方便
    • 解决了很多接口问题
  • 缺点
    • Langchain 太过复杂,以至于我不知道是我没用对,还是它就没有这个功能,学习成本高。
    • Langchain 更新太快,很多之前的例程都不能用了。有时候找一个功能要花很长时间。
    • 即使在一个版本中,工具调用也可做到不同层次,学习成本高。
    • 框架逻辑复杂,为了一个小功能把 Agent 整个加进来,确实有点重。
    • 虽然 Langchain 后期做了分块,但每个功能做得不够精细。
  • 总结
    • Agent 的主要用途是与用户进行更平滑的交互。
    • 多数逻辑需要通过 Prompt 来处理。
    • Langchain Agent 可以看作一个分流器,根据提供的工具,对用户意图进行分流。相对 Chain,它的数据流结构更为灵活。
    • LangChain 提供的 Agent 更多用于处理单步任务和工具调用,不太适合复杂任务。
  • 注意事项
    • 不需要花太多时间彻底了解每个工具,理解其原理并能够使用即可。
    • 不要将所有逻辑都写在里面,只需将 langchain 作为一个中间封装层,随时可替换。
  • 思考
    • 有效地解决了接口问题。原先实现功能需要明确定义,如函数名、参数类型等等。而现在通过 agent,可以有更大的功能调用余地和容错率。在对话中,只需用语言大致描述其功能,就能找到相应的函数进行调用,并以聊天形式返回给用户。

4 问题与解答

  • 如何看具体步骤
    • 如果想看具体步骤,用 stream 方式调用,整个输入输出都能保留,以方便追踪每步的结果。
  • 便宜的模型能不能满足对工具的使用
    • gpt-3.5 和 deepseek 都可用
    • 用 gpt-4o 效果明显好于 gpt-3.5,后者很多时候会选错工具,需要花很多时间调 prompt。
    • 工具的输入输出描述非常重要,不要只当注释写,如果与实现对不上,会影响结果。
    • 提示词改一点都不行。
  • 复杂功能能否实现
    • langchain agent 有很多层次,只要做得足够低层,或者说选对自己合适的层次,理论上可以实现,但比较复杂。
  • 一般的 prompt 怎么写,或者主要用哪个模板
    • 这里不能像 demo 写的那么简单,尤其要把输出结果定义清楚,以使后续操作更加顺利。
    • 叠加存储每一步的思考,不是只能存最后一步。
    • 可参考 ReactAgent。
  • agent 如何构造
    • 有多种构造方法。
  • langchain 是如何选择工具的
    • openai 新 api 提供 tool 的接口,所以是通过 tools 参数直接把函数传给 openai,而不是组成 prompt 再传。
    • 默认情况下,每一步 agent 与 llm 交互都把最后一次的结果和用户问题一起组装成 prompt 传给 llm。
    • prompt 和函数描述尽量详细。

5 参考

5.1 工具文档

5.2 代理文档