Agent-让大模型拥有双手

Agent 是什么

Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中主要的形态。

💡 本文中示例的代码片段详见:eino-examples/quickstart/todoagent

Agent 的核心组成

在 Eino 中,要实现 Agent 主要需要两个核心部分:ChatModel 和 Tool。

ChatModel

ChatModel 是 Agent 的大脑,它通过强大的语言理解能力来处理用户的自然语言输入。当用户提出请求时,ChatModel 会深入理解用户的意图,分析任务需求,并决定是否需要调用特定的工具来完成任务。在需要使用工具时,它能够准确地选择合适的工具并生成正确的参数。不仅如此,ChatModel 还能将工具执行的结果转化为用户易于理解的自然语言回应,实现流畅的人机对话。

更详细的 ChatModel 的信息,可以参考: Eino: ChatModel 使用说明

Tool

Tool 是 Agent 的执行器,提供了具体的功能实现。每个 Tool 都有明确的功能定义和参数规范,使 ChatModel 能够准确地调用它们。Tool 可以实现各种功能,从简单的数据操作到复杂的外部服务调用都可以封装成 Tool。

更详细关于 Tool 和 ToolsNode 的信息,可参考: Eino: ToolsNode 使用说明

Tool 的实现方式

在 Eino 中,我们提供了多种方式来实现 Tool。下面通过一个待办事项(Todo)管理系统的例子来说明。

方式一:使用 NewTool 构建

这种方式适合简单的工具实现,通过定义工具信息和处理函数来创建 Tool:

func getAddTodoTool() tool.InvokableTool {
    info := &schema.ToolInfo{
        Name: "add_todo",
        Desc: "Add a todo item",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "content": {
                Desc:     "The content of the todo item",
                Type:     schema.String,
                Required: true,
            },
            "started_at": {
                Desc: "The started time of the todo item, in unix timestamp",
                Type: schema.Integer,
            },
            "deadline": {
                Desc: "The deadline of the todo item, in unix timestamp",
                Type: schema.Integer,
            },
        }),
    }

    return utils.NewTool(info, AddTodoFunc)
}

这种方式虽然直观,但存在一个明显的缺点:需要在 ToolInfo 中手动定义参数信息(ParamsOneOf),和实际的参数结构(TodoAddParams)是分开定义的。这样不仅造成了代码的冗余,而且在参数发生变化时需要同时修改两处地方,容易导致不一致,维护起来也比较麻烦。

方式二:使用 InferTool 构建

这种方式更加简洁,通过结构体的 tag 来定义参数信息,就能实现参数结构体和描述信息同源,无需维护两份信息:

type TodoUpdateParams struct {
    ID        string  `json:"id" jsonschema:"description=id of the todo"`
    Content   *string `json:"content,omitempty" jsonschema:"description=content of the todo"`
    StartedAt *int64  `json:"started_at,omitempty" jsonschema:"description=start time in unix timestamp"`
    Deadline  *int64  `json:"deadline,omitempty" jsonschema:"description=deadline of the todo in unix timestamp"`
    Done      *bool   `json:"done,omitempty" jsonschema:"description=done status"`
}

// 使用 InferTool 创建工具
updateTool, err := utils.InferTool("update_todo", "Update a todo item, eg: content,deadline...", UpdateTodoFunc)

方式三:实现 Tool 接口

对于需要更多自定义逻辑的场景,可以通过实现 Tool 接口来创建:

type ListTodoTool struct {}

func (lt *ListTodoTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    return &schema.ToolInfo{
        Name: "list_todo",
        Desc: "List all todo items",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "finished": {
                Desc:     "filter todo items if finished",
                Type:     schema.Boolean,
                Required: false,
            },
        }),
    }, nil
}

func (lt *ListTodoTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
    // 具体的调用逻辑
}

方式四:使用官方封装的工具

除了自己实现工具,我们还提供了许多开箱即用的工具。这些工具经过充分测试和优化,可以直接集成到你的 Agent 中。以 Google Search 工具为例:

import (
    "github.com/bytedance/eino-ext/components/tool/googlesearch"
)

func main() {
    // 创建 Google Search 工具
    searchTool, err := googlesearch.NewGoogleSearchTool(ctx, &googlesearch.Config{
        APIKey:         os.Getenv("GOOGLE_API_KEY"),         // Google API Key
        SearchEngineID: os.Getenv("GOOGLE_SEARCH_ENGINE_ID"), // 自定义搜索引擎 ID
        Num:           5,      // 每次返回的结果数量
        Lang:          "zh-CN", // 搜索结果的语言
    })
    if err != nil {
        log.Fatal(err)
    }
}

使用 eino-ext 提供的工具不仅能避免重复开发的工作量,还能确保工具的稳定性和可靠性。这些工具都经过充分测试和持续维护,可以直接集成到项目中使用。

用 Chain 构建 Agent

在构建 Agent 时,ToolsNode 是一个核心组件,它负责管理和执行工具调用。ToolsNode 可以集成多个工具,并提供统一的调用接口。它支持同步调用(Invoke)和流式调用(Stream)两种方式,能够灵活地处理不同类型的工具执行需求。

要创建一个 ToolsNode,你需要提供一个工具列表配置:

func main() {
    conf := &compose.ToolsNodeConfig{
        Tools: []tool.BaseTool{tool1, tool2},  // 工具可以是 InvokableTool 或 StreamableTool
    }
    toolsNode, err := compose.NewToolNode(ctx, conf)    
}

下面是一个完整的 Agent 示例,它使用 OpenAI 的 ChatModel 并结合了上述的 Todo 工具:

func main() {
    // 初始化 tools
    todoTools := []tool.BaseTool{
        getAddTodoTool(),                                // 使用 NewTool 方式
        updateTool,                                     // 使用 InferTool 方式
        &ListTodoTool{},
        searchTool,                                 // 使用结构体实现方式, 此处未实现底层逻辑
    }

    // 创建并配置 ChatModel
    temp := float32(0.7)
    chatModel, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{
        Model:       "gpt-4",
        APIKey:      os.Getenv("OPENAI_API_KEY"),
        Temperature: &temp,
    })
    if err != nil {
        log.Fatal(err)
    }

    // 获取工具信息, 用于绑定到 ChatModel
    toolInfos := make([]*schema.ToolInfo, 0, len(todoTools))
    for _, tool := range todoTools {
        info, err := tool.Info(ctx)
        if err != nil {
            log.Fatal(err)
        }
        toolInfos = append(toolInfos, info)
    }

    // 将 tools 绑定到 ChatModel
    err = chatModel.BindTools(toolInfos)
    if err != nil {
        log.Fatal(err)
    }


    // 创建 tools 节点
    todoToolsNode, err := compose.NewToolNode(context.Background(), &compose.ToolsNodeConfig{
        Tools: todoTools,
    })
    if err != nil {
        log.Fatal(err)
    }

    // 构建完整的处理链
    chain := compose.NewChain[*schema.Message, []*schema.Message]()
    chain.
        AppendChatModel(chatModel, compose.WithNodeName("chat_model")).
        AppendToolsNode(todoToolsNode, compose.WithNodeName("tools"))

    // 编译并运行 chain
    agent, err := chain.Compile(ctx)
    if err != nil {
        log.Fatal(err)
    }

    // 运行示例
    resp, err := agent.Invoke(context.Background(), &schema.Message{
        Content: "帮我创建一个明天下午3点截止的待办事项:准备Eino项目演示文稿",
    })
    if err != nil {
        log.Fatal(err)
    }

    // 输出结果
    for _, msg := range resp {
        fmt.Println(msg.Content)
    }
}

这个示例有一个假设,也就是 ChatModel 一定会做出 tool 调用的决策。实际上这个例子是 tool calling agent 的一个简化版本。更完整的 toolcalling agent 可以参考: Tool Calling Agent

使用其他方式构建 Agent

除了上述使用 Chain/Graph 构建的 agent 之外,Eino 还提供了常用的 Agent 模式的封装。

ReAct Agent

ReAct(Reasoning + Acting)Agent 结合了推理和行动能力,通过思考-行动-观察的循环来解决复杂问题。它能够在执行任务时进行深入的推理,并根据观察结果调整策略,特别适合需要多步推理的复杂场景。

更详细的 react agent 可以参考: Eino: React Agent 使用手册

Multi Agent

Multi Agent 系统由多个协同工作的 Agent 组成,每个 Agent 都有其特定的职责和专长。通过 Agent 间的交互与协作,可以处理更复杂的任务,实现分工协作。这种方式特别适合需要多个专业领域知识结合的场景。

更详细的 multi agent 可以参考: Eino Tutorial: Host Multi-Agent

总结

介绍了使用 Eino 框架构建 Agent 的基本方法。通过 Chain、Tool Calling 和 ReAct 等不同方式,我们可以根据实际需求灵活地构建 AI Agent。

Agent 是 AI 技术发展的重要方向。它不仅能够理解用户意图,还能主动采取行动,通过调用各种工具来完成复杂任务。随着大语言模型能力的不断提升,Agent 将在未来扮演越来越重要的角色,成为连接 AI 与现实世界的重要桥梁。我们期待 Eino 能为用户带来更强大、易用的 agent 构建方案,推动更多基于 Agent 的应用创新。

关联阅读


最后修改 January 9, 2025 : feat: add eino cn docs (#1182) (ad75444)