在这里插入图片描述
欢迎关注公众号: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进行修改,实现对话记录在全节点上的透传

小坑

  1. 没有prehandler会导致最后输出的结果很奇怪

  2. 调用WithTools方法进行工具和模型的绑定时,这个方法会返回新的model,注意看官方的函数说明
    // WithTools returns a new ToolCallingChatModel instance with the specified tools bound. // This method does not modify the current instance, making it safer for concurrent use.

    运行结果

    工具调用结果

    在这里插入图片描述

    最终结果

    在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐