经典Agent架构实战之工具使用 (Tool Use)
本文介绍了工具使用架构在大型语言模型(LLM)中的应用。该架构通过调用外部API或函数,克服了LLM知识的静态局限性,使其能够获取实时信息。文章详细阐述了工具使用架构的工作流程:接收查询、决策是否需要工具、执行工具调用、整合结果并生成最终回答。与React架构相比,工具使用架构适合一次性操作或简单工作流。作者还提供了Go语言实现的核心代码,包括prompt模板和编排逻辑,并指出了开发过程中需要注意

欢迎关注公众号:AI开发的后端厨师,知乎:巴塞罗那的风
及时获取更新内容,每周更新一个经典Agent架构
介绍
工具使用架构是连接大型语言模型(LLM)推理能力与真实、动态世界的 桥梁。它赋予智能体查询 API、搜索数据库和访问实时信息的能力,从而克服了 LLM 知识的静态局限性。
定义
工具使用 架构为 LLM 驱动的智能体配备了调用外部函数或 API(即 “工具”)的能力。智能体能够自主判断用户查询是否需要外部信息,并决定调用哪个工具来获取所需数据。
工作流程
接收查询 (Receive Query): 智能体接收用户的请求。
决策 (Decision): 智能体分析查询和可用工具,判断是否需要工具。
行动 (Action): 如果需要,智能体格式化对工具的调用(例如,带正确参数的特定函数)。
观察 (Observation): 系统执行工具调用,并将结果(“观察结果”)返回给智能体。
合成 (Synthesis): 智能体将工具的输出整合到其推理过程中,生成一个最终的、有事实依据 的答案。
和react的区别
大家都是要调工具,都是要总结合成,二者的区别在哪?tool use架构是只调用一次工具,不需要把调用的结果再交给模型进行下一步动作的思考,换句话说就是tool use适合一锤子买卖或者是普通的work flow;react适合更复杂问题的解决
代码
本文章是对这篇公众号文章的复刻
完整代码已上传github:地址
核心代码解释
prompt
func DraftCodeTemplate() prompt.DefaultChatTemplate {
systemTpl := `
角色:你是一个精于解决用户问题的问题解答师。
任务:根据用户的需求,回答问题,必要时使用提供给你的工具进行问题回答。
要求:如果你已知了工具的查询结果,则无需继续调用工具,而应该结果整合进最终的回答中,确保回答的准确性和完整性。
`
chatTpl := prompt.FromMessages(schema.FString,
schema.SystemMessage(systemTpl),
schema.UserMessage("{user_query}"),
)
return *chatTpl
}
告诉模型职责,限制仅调用一次工具,不过这里更应在使用场景上进行限制
编排部分
type state struct {
Messages []*schema.Message
}
func GetToolUseRunnable() (compose.Runnable[map[string]any, *schema.Message], error) {
sg := compose.NewGraph[map[string]any, *schema.Message](compose.WithGenLocalState(func(ctx context.Context) *state {
return &state{Messages: make([]*schema.Message, 0)}
}))
ctx := context.Background()
model, err := GetModel()
if err != nil {
return nil, err
}
tools := GetBaiDuMapTool(ctx, []string{MapServer})
toolNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
Tools: tools,
})
if err != nil {
return nil, err
}
toolsInfo, err := genToolInfos(ctx, tools)
if err != nil {
return nil, err
}
model, err = model.WithTools(toolsInfo)
if err != nil {
return nil, err
}
modelPreHandle := func(ctx context.Context, input []*schema.Message, state *state) ([]*schema.Message, error) {
state.Messages = append(state.Messages, input...)
return state.Messages, nil
}
toolsNodePreHandle := func(ctx context.Context, input *schema.Message, state *state) (*schema.Message, error) {
if input == nil {
return state.Messages[len(state.Messages)-1], nil // used for rerun interrupt resume
}
state.Messages = append(state.Messages, input)
return input, nil
}
makeAnswerTemplate := DraftCodeTemplate()
sg.AddChatTemplateNode("MakeAnswerTemplate", &makeAnswerTemplate, compose.WithNodeName("MakeAnswerTemplate"))
sg.AddChatModelNode("MakeAnswerModel", model, compose.WithNodeName("MakeAnswerModel"), compose.WithStatePreHandler(modelPreHandle))
sg.AddToolsNode("ToolsNode", toolNode, compose.WithNodeName("ToolsNode"), compose.WithStatePreHandler(toolsNodePreHandle))
sg.AddChatModelNode("Synthesis", model, compose.WithNodeName("Synthesis"), compose.WithStatePreHandler(modelPreHandle))
sg.AddEdge(compose.START, "MakeAnswerTemplate")
sg.AddEdge("MakeAnswerTemplate", "MakeAnswerModel")
sg.AddEdge("MakeAnswerModel", "ToolsNode")
sg.AddEdge("ToolsNode", "Synthesis")
sg.AddEdge("Synthesis", compose.END)
reflectionRunnable, err := sg.Compile(context.Background())
return reflectionRunnable, err
}
最终实现了如下的graph
MakeAnswerTemplate -> MakeAnswerModel -> ToolsNode -> Synthesis
handler的作用
可以看到在调用模型及工具之前写了两个handler,作用都是追加对话历史,如果没有这两个handler会出现什么问题呢?
工具调用完整,进行总结的时候 模型仅仅拿到tool call的结果,但是他不知道自己是谁,不知道自己该干什么
所以这里通过两个prehandler对state进行修改,实现对话记录在全节点上的透传
小坑
更多推荐



所有评论(0)