跳转至

Tool 工具使用文档

Tool 工具系统是 tRPC-Agent-Go 框架的核心组件,为 Agent 提供了与外部服务和功能交互的能力。框架支持多种工具类型,包括函数工具和基于 MCP(Model Context Protocol)标准的外部工具集成。

概述

🎯 核心特性

  • 🔧 多类型工具:支持函数工具(Function Tools)和 MCP 标准工具
  • 🌊 流式响应:支持实时流式响应和普通响应两种模式
  • ⚡ 并行执行:工具调用支持并行执行以提升性能
  • 🔄 MCP 协议:完整支持 STDIO、SSE、Streamable HTTP 三种传输方式
  • 🛠️ 配置支持:提供配置选项和过滤器支持

核心概念

🔧 Tool(工具)

Tool 是单个功能的抽象,实现 tool.Tool 接口。每个 Tool 提供特定的能力,如数学计算、搜索、时间查询等。

1
2
3
4
5
6
7
8
type Tool interface {
    Declaration() *Declaration  // 返回工具元数据
}

type CallableTool interface {
    Call(ctx context.Context, jsonArgs []byte) (any, error)
    Tool
}

📦 ToolSet(工具集)

ToolSet 是一组相关工具的集合,实现 tool.ToolSet 接口。ToolSet 负责管理工具的生命周期、连接和资源清理。

type ToolSet interface {
    // 返回当前工具集内的工具
    Tools(context.Context) []tool.Tool

    // 释放工具集持有的资源
    Close() error

    // 返回该工具集的名称,用于标识与冲突处理
    Name() string
}

Tool 与 ToolSet 的关系:

  • 一个 "Tool" = 一个具体功能(如计算器)
  • 一个 "ToolSet" = 一组相关的 Tool(如 MCP 服务器提供的所有工具)
  • Agent 可以同时使用多个 Tool 和多个 ToolSet

🌊 流式工具支持

框架支持流式工具,提供实时响应能力:

// 流式工具接口
type StreamableTool interface {
    StreamableCall(ctx context.Context, jsonArgs []byte) (*StreamReader, error)
    Tool
}

// 流式数据单元
type StreamChunk struct {
    Content  any      `json:"content"`
    Metadata Metadata `json:"metadata,omitempty"`
}

流式工具特点:

  • 🚀 实时响应:数据逐步返回,无需等待完整结果
  • 📊 大数据处理:适用于日志查询、数据分析等场景
  • 用户体验:提供即时反馈和进度显示

工具类型说明

工具类型 定义 集成方式
Function Tools 直接调用 Go 函数实现的工具 Tool 接口,进程内调用
Agent Tool (AgentTool) 将任意 Agent 包装为可调用工具 Tool 接口,支持流式内部转发
DuckDuckGo Tool 基于 DuckDuckGo API 的搜索工具 Tool 接口,HTTP API
MCP ToolSet 基于 MCP 协议的外部工具集 ToolSet 接口,支持多种传输方式

📖 相关文档:Agent 间协作相关的 Agent Tool 和 Transfer Tool 请参考 多 Agent 系统文档

Function Tools 函数工具

Function Tools 通过 Go 函数直接实现工具逻辑,是最简单直接的工具类型。

基本用法

import "trpc.group/trpc-go/trpc-agent-go/tool/function"

// 1. 定义工具函数
func calculator(ctx context.Context, req struct {
    Operation string  `json:"operation"`
    A         float64 `json:"a"`
    B         float64 `json:"b"`
}) (map[string]interface{}, error) {
    switch req.Operation {
    case "add":
        return map[string]interface{}{"result": req.A + req.B}, nil
    case "multiply":
        return map[string]interface{}{"result": req.A * req.B}, nil
    default:
        return nil, fmt.Errorf("unsupported operation: %s", req.Operation)
    }
}

// 2. 创建工具
calculatorTool := function.NewFunctionTool(
    calculator,
    function.WithName("calculator"),
    function.WithDescription("执行数学运算"),
)

// 3. 集成到 Agent
agent := llmagent.New("math-assistant",
    llmagent.WithModel(model),
    llmagent.WithTools([]tool.Tool{calculatorTool}))

流式工具示例

// 1. 定义输入输出结构
type weatherInput struct {
    Location string `json:"location"`
}

type weatherOutput struct {
    Weather string `json:"weather"`
}

// 2. 实现流式工具函数
func getStreamableWeather(input weatherInput) *tool.StreamReader {
    stream := tool.NewStream(10)
    go func() {
        defer stream.Writer.Close()

        // 模拟逐步返回天气数据
        result := "Sunny, 25°C in " + input.Location
        for i := 0; i < len(result); i++ {
            chunk := tool.StreamChunk{
                Content: weatherOutput{
                    Weather: result[i : i+1],
                },
                Metadata: tool.Metadata{CreatedAt: time.Now()},
            }

            if closed := stream.Writer.Send(chunk, nil); closed {
                break
            }
            time.Sleep(10 * time.Millisecond) // 模拟延迟
        }
    }()

    return stream.Reader
}

// 3. 创建流式工具
weatherStreamTool := function.NewStreamableFunctionTool[weatherInput, weatherOutput](
    getStreamableWeather,
    function.WithName("get_weather_stream"),
    function.WithDescription("流式获取天气信息"),
)

// 4. 使用流式工具
reader, err := weatherStreamTool.StreamableCall(ctx, jsonArgs)
if err != nil {
    return err
}

// 接收流式数据
for {
    chunk, err := reader.Recv()
    if err == io.EOF {
        break // 流结束
    }
    if err != nil {
        return err
    }

    // 处理每个数据块
    fmt.Printf("收到数据: %v\n", chunk.Content)
}
reader.Close()

内置工具类型

DuckDuckGo 搜索工具

DuckDuckGo 工具基于 DuckDuckGo Instant Answer API,提供事实性、百科类信息搜索功能。

基础用法

1
2
3
4
5
6
7
8
9
import "trpc.group/trpc-go/trpc-agent-go/tool/duckduckgo"

// 创建 DuckDuckGo 搜索工具
searchTool := duckduckgo.NewTool()

// 集成到 Agent
searchAgent := llmagent.New("search-assistant",
    llmagent.WithModel(model),
    llmagent.WithTools([]tool.Tool{searchTool}))

高级配置

import (
    "net/http"
    "time"
    "trpc.group/trpc-go/trpc-agent-go/tool/duckduckgo"
)

// 自定义配置
searchTool := duckduckgo.NewTool(
    duckduckgo.WithBaseURL("https://api.duckduckgo.com"),
    duckduckgo.WithUserAgent("my-app/1.0"),
    duckduckgo.WithHTTPClient(&http.Client{
        Timeout: 15 * time.Second,
    }),
)

MCP Tools 协议工具

MCP(Model Context Protocol)是一个开放协议,标准化了应用程序向 LLM 提供上下文的方式。MCP 工具基于 JSON-RPC 2.0 协议,为 Agent 提供了与外部服务的标准化集成能力。

MCP ToolSet 特点:

  • 🔗 统一接口:所有 MCP 工具都通过 mcp.NewMCPToolSet() 创建
  • 🚀 多种传输:支持 STDIO、SSE、Streamable HTTP 三种传输方式
  • 🔧 工具过滤:支持包含/排除特定工具
  • 显式初始化:通过 (*mcp.ToolSet).Init(ctx),可以在应用启动阶段提前发现 MCP 连接/工具加载错误并快速失败

基本用法

import "trpc.group/trpc-go/trpc-agent-go/tool/mcp"

// 创建 MCP 工具集(以 STDIO 为例)
mcpToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "stdio",           // 传输方式
        Command:   "go",              // 执行命令
        Args:      []string{"run", "./stdio_server/main.go"},
        Timeout:   10 * time.Second,
    },
    mcp.WithToolFilter(mcp.NewIncludeFilter("echo", "add")), // 可选:工具过滤
)

// (可选但推荐)显式初始化 MCP:建立连接 + 初始化会话 + 列工具
if err := mcpToolSet.Init(ctx); err != nil {
    log.Fatalf("初始化 MCP 工具集失败: %v", err)
}

// 集成到 Agent
agent := llmagent.New("mcp-assistant",
    llmagent.WithModel(model),
    llmagent.WithToolSets([]tool.ToolSet{mcpToolSet}))

传输方式配置

MCP ToolSet 通过 Transport 字段支持三种传输方式:

1. STDIO 传输

通过标准输入输出与外部进程通信,适用于本地脚本和命令行工具。

mcpToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "stdio",
        Command:   "python",
        Args:      []string{"-m", "my_mcp_server"},
        Timeout:   10 * time.Second,
    },
)
if err := mcpToolSet.Init(ctx); err != nil {
    return fmt.Errorf("初始化 STDIO MCP 工具集失败: %w", err)
}

2. SSE 传输

使用 Server-Sent Events 进行通信,支持实时数据推送和流式响应。

mcpToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "sse",
        ServerURL: "http://localhost:8080/sse",
        Timeout:   10 * time.Second,
        Headers: map[string]string{
            "Authorization": "Bearer your-token",
        },
    },
)
if err := mcpToolSet.Init(ctx); err != nil {
    return fmt.Errorf("初始化 SSE MCP 工具集失败: %w", err)
}

3. Streamable HTTP 传输

使用标准 HTTP 协议进行通信,支持普通 HTTP 和流式响应。

mcpToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "streamable_http",  // 注意:使用完整名称
        ServerURL: "http://localhost:3000/mcp",
        Timeout:   10 * time.Second,
    },
)
if err := mcpToolSet.Init(ctx); err != nil {
    return fmt.Errorf("初始化 Streamable MCP 工具集失败: %w", err)
}

会话重连支持

MCP ToolSet 支持自动会话重连,当服务器重启或会话过期时自动恢复连接。

1
2
3
4
5
6
7
8
9
// SSE/Streamable HTTP 传输支持会话重连
sseToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "sse",
        ServerURL: "http://localhost:8080/sse",
        Timeout:   10 * time.Second,
    },
    mcp.WithSessionReconnect(3), // 启用会话重连,最多尝试3次
)

重连特性:

  • 🔄 自动重连:检测到连接断开或会话过期时自动重建会话
  • 🎯 独立重试:每次工具调用独立计数,不会因早期失败影响后续调用
  • 🛡️ 保守策略:仅针对明确的连接/会话错误触发重连,避免配置错误导致的无限循环

MCP 工具的动态发现与更新(LLMAgent 配置项)

对于 MCP 工具集,服务器端的工具列表是可以变化的(例如在运行 过程中新增了一个 MCP 工具)。如果希望 LLMAgent 在每次调用 时自动看到最新的工具列表,可以在使用 WithToolSets 的同时, 开启 llmagent.WithRefreshToolSetsOnRun(true)

LLMAgent 配置示例

import (
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/model/openai"
    "trpc.group/trpc-go/trpc-agent-go/tool"
    "trpc.group/trpc-go/trpc-agent-go/tool/mcp"
)

// 1. 创建 MCP 工具集(可以是 STDIO、SSE 或 Streamable HTTP)
mcpToolSet := mcp.NewMCPToolSet(connectionConfig)

// 2. 创建 LLMAgent,并开启 ToolSets 的自动刷新
agent := llmagent.New(
    "mcp-assistant",
    llmagent.WithModel(openai.New("gpt-4o-mini")),
    llmagent.WithToolSets([]tool.ToolSet{mcpToolSet}),
    llmagent.WithRefreshToolSetsOnRun(true),
)

当启用 WithRefreshToolSetsOnRun(true) 时:

  • LLMAgent 在构造工具列表时,会再次调用 ToolSet.Tools(context.Background())
  • 如果 MCP 服务器新增或删除了工具,该 Agent 下一次执行 时, 会自动使用更新后的工具列表。

这个配置项的侧重点是动态发现工具。如果你还需要基于 context.Context每次请求动态 HTTP 请求头(例如从上下文 中提取认证信息),仍然可以参考 examples/mcptool/http_headers 示例,手动调用 toolSet.Tools(ctx),然后配合 WithTools 使用。

Agent 工具 (AgentTool)

AgentTool 允许把一个现有的 Agent 以工具的形式暴露给上层 Agent 使用。相比普通函数工具,AgentTool 的优势在于:

  • ✅ 复用:将复杂 Agent 能力作为标准工具复用
  • 🌊 流式:可选择将子 Agent 的流式事件“内联”转发到父流程
  • 🧭 控制:通过选项控制是否跳过工具后的总结补全、是否进行内部转发

基本用法

import (
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/model"
    "trpc.group/trpc-go/trpc-agent-go/tool"
    agenttool "trpc.group/trpc-go/trpc-agent-go/tool/agent"
)

// 1) 定义一个可复用的子 Agent(可配置为流式)
mathAgent := llmagent.New(
    "math-specialist",
    llmagent.WithModel(modelInstance),
    llmagent.WithInstruction("你是数学专家..."),
    llmagent.WithGenerationConfig(model.GenerationConfig{Stream: true}),
)

// 2) 包装为 Agent 工具
mathTool := agenttool.NewTool(
    mathAgent,
    agenttool.WithSkipSummarization(false), // 可选,默认 false,当设置为 true 时会跳过外层模型总结,在 tool.response 后直接结束本轮
    agenttool.WithStreamInner(true),        // 开启:把子 Agent 的流式事件转发给父流程
)

// 3) 在父 Agent 中使用该工具
parent := llmagent.New(
    "assistant",
    llmagent.WithModel(modelInstance),
    llmagent.WithGenerationConfig(model.GenerationConfig{Stream: true}),
    llmagent.WithTools([]tool.Tool{mathTool}),
)

流式内部转发详解

WithStreamInner(true) 时,AgentTool 会把子 Agent 在运行时产生的事件直接转发到父流程的事件流中:

  • 转发的事件本质是子 Agent 里的 event.Event,包含增量内容(choice.Delta.Content
  • 为避免重复,子 Agent 在结束时产生的“完整大段内容”不会再次作为转发事件打印;但会被聚合到最终 tool.response 的内容里,供下一次 LLM 调用作为工具消息使用
  • UI 层建议:展示“转发的子 Agent 增量内容”,但默认不重复打印最终聚合的 tool.response 内容(除非用于调试)

示例:仅在需要时显示工具片段,避免重复输出

if ev.Response != nil && ev.Object == model.ObjectTypeToolResponse {
    // 工具响应(包含聚合后的内容),默认不打印,避免和子 Agent 转发的内容重复
    // ...仅在调试或需要展示工具细节时再打印
}

// 子 Agent 转发的流式增量(作者不是父 Agent)
if ev.Author != parentName && len(ev.Choices) > 0 {
    if delta := ev.Choices[0].Delta.Content; delta != "" {
        fmt.Print(delta)
    }
}

选项说明

  • WithSkipSummarization(bool):

    • false(默认):允许在工具结果后继续一次 LLM 调用进行总结/回答
    • true:外层 Flow 在 tool.response 后直接结束本轮(不再额外总结)
  • WithStreamInner(bool):

    • true:把子 Agent 的事件直接转发到父流程(强烈建议父/子 Agent 都开启 GenerationConfig{Stream: true}
    • false:按“仅可调用工具”处理,不做内部事件转发
  • WithHistoryScope(HistoryScope):
    • HistoryScopeIsolated(默认):保持子调用完全隔离,只读取本次工具参数(不继承父历史)。
    • HistoryScopeParentBranch:通过分层过滤键 父键/子名-UUID(Universally Unique Identifier,通用唯一识别码) 继承父会话历史;内容处理器会基于前缀匹配纳入父事件,同时子事件仍写入独立子分支。典型场景:基于上一轮产出进行“编辑/优化/续写”。

示例:

1
2
3
4
5
6
child := agenttool.NewTool(
    childAgent,
    agenttool.WithSkipSummarization(false),
    agenttool.WithStreamInner(true),
    agenttool.WithHistoryScope(agenttool.HistoryScopeParentBranch),
)

注意事项

  • 事件完成信号:工具响应事件会被标记 RequiresCompletion=true,Runner 会自动发送完成信号,无需手工处理
  • 内容去重:如果已转发子 Agent 的增量内容,默认不要再把最终 tool.response 的聚合内容打印出来
  • 模型兼容性:一些模型要求工具调用后必须跟随工具消息,AgentTool 已自动填充聚合后的工具内容满足此要求

工具集成与使用

创建 Agent 与工具集成

import (
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/tool"
    "trpc.group/trpc-go/trpc-agent-go/tool/function"
    "trpc.group/trpc-go/trpc-agent-go/tool/duckduckgo"
    "trpc.group/trpc-go/trpc-agent-go/tool/mcp"
)

// 创建函数工具
calculatorTool := function.NewFunctionTool(calculator,
    function.WithName("calculator"),
    function.WithDescription("执行基础数学运算"))

timeTool := function.NewFunctionTool(getCurrentTime,
    function.WithName("current_time"),
    function.WithDescription("获取当前时间"))

// 创建内置工具
searchTool := duckduckgo.NewTool()

// 创建 MCP 工具集(不同传输方式的示例)
stdioToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "stdio",
        Command:   "python",
        Args:      []string{"-m", "my_mcp_server"},
        Timeout:   10 * time.Second,
    },
)

sseToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "sse",
        ServerURL: "http://localhost:8080/sse",
        Timeout:   10 * time.Second,
    },
)

streamableToolSet := mcp.NewMCPToolSet(
    mcp.ConnectionConfig{
        Transport: "streamable_http",
        ServerURL: "http://localhost:3000/mcp",
        Timeout:   10 * time.Second,
    },
)

// 创建 Agent 并集成所有工具
agent := llmagent.New("ai-assistant",
    llmagent.WithModel(model),
    llmagent.WithInstruction("你是一个有帮助的AI助手,可以使用多种工具协助用户"),
    // 添加单个工具(Tool 接口)
    llmagent.WithTools([]tool.Tool{
        calculatorTool, timeTool, searchTool,
    }),
    // 添加工具集(ToolSet 接口)
    llmagent.WithToolSets([]tool.ToolSet{
        stdioToolSet, sseToolSet, streamableToolSet,
    }),
)

MCP 工具过滤器

MCP 工具集支持在创建时过滤工具。推荐使用统一的 tool.FilterFunc 接口:

import (
    "trpc.group/trpc-go/trpc-agent-go/tool"
    "trpc.group/trpc-go/trpc-agent-go/tool/mcp"
)

// ✅ 推荐:使用统一的过滤接口
includeFilter := tool.NewIncludeToolNamesFilter("get_weather", "get_news", "calculator")
excludeFilter := tool.NewExcludeToolNamesFilter("deprecated_tool", "slow_tool")

// 应用过滤器
toolSet := mcp.NewMCPToolSet(
    connectionConfig,
    mcp.WithToolFilterFunc(includeFilter),
)

运行时工具过滤

  • 方式一:运行时工具过滤允许在每次 runner.Run 调用时动态控制工具可用性,无需修改 Agent 配置。这是一个"软约束"机制,用于优化 token 消耗和实现基于角色的工具访问控制。针对所有agent生效
  • 方式二:通过llmagent.WithToolFilter配置运行时过滤function, 只对当前agent生效

核心特性:

  • 🎯 Per-Run 控制:每次调用独立配置,不影响 Agent 定义
  • 💰 成本优化:减少发送给 LLM 的工具描述,降低 token 消耗
  • 🛡️ 智能保护:框架工具(transfer_to_agentknowledge_search)自动保留,永不被过滤
  • 🔧 灵活定制:支持内置过滤器和自定义 FilterFunc

基本用法

1. 排除特定工具(Exclude Filter)

使用黑名单方式排除不需要的工具:

1
2
3
4
5
6
7
import "trpc.group/trpc-go/trpc-agent-go/tool"

// 排除 text_tool,其他工具都可用
filter := tool.NewExcludeToolNamesFilter("text_tool", "dangerous_tool")
eventChan, err := runner.Run(ctx, userID, sessionID, message,
    agent.WithToolFilter(filter),
)

2. 只允许特定工具(Include Filter)

使用白名单方式只允许指定的工具:

// 方式一:
// 只允许使用计算器和时间工具
filter := tool.NewIncludeToolNamesFilter("calculator", "time_tool")
eventChan, err := runner.Run(ctx, userID, sessionID, message,
    agent.WithToolFilter(filter),
)

// 方式二:
agent := llmagent.New("ai-assistant",
    llmagent.WithModel(model),
    llmagent.WithInstruction("你是一个有帮助的AI助手,可以使用多种工具协助用户"),
    // 添加单个工具(Tool 接口)
    llmagent.WithTools([]tool.Tool{
        calculatorTool, timeTool, searchTool,
    }),
    // 添加工具集(ToolSet 接口)
    llmagent.WithToolSets([]tool.ToolSet{
        stdioToolSet, sseToolSet, streamableToolSet,
    }),
    llmagent.WithToolFilter(filter),
)

3. 自定义过滤逻辑(Custom FilterFunc)

实现自定义过滤函数以支持复杂的过滤逻辑:

// 方式一:
// 自定义过滤函数:只允许名称以 "safe_" 开头的工具
filter := func(ctx context.Context, t tool.Tool) bool {
    declaration := t.Declaration()
    if declaration == nil {
        return false
    }
    return strings.HasPrefix(declaration.Name, "safe_")
}

eventChan, err := runner.Run(ctx, userID, sessionID, message,
    agent.WithToolFilter(filter),
)

// 方式二:
agent := llmagent.New("ai-assistant",
    llmagent.WithModel(model),
    llmagent.WithInstruction("你是一个有帮助的AI助手,可以使用多种工具协助用户"),
    // 添加单个工具(Tool 接口)
    llmagent.WithTools([]tool.Tool{
        calculatorTool, timeTool, searchTool,
    }),
    // 添加工具集(ToolSet 接口)
    llmagent.WithToolSets([]tool.ToolSet{
        stdioToolSet, sseToolSet, streamableToolSet,
    }),
    llmagent.WithToolFilter(filter),
)

4. Agent 粒度过滤(Per-Agent Filtering)

通过 agent.InvocationFromContext 实现不同 Agent 使用不同工具:

// 为不同 Agent 定义允许的工具
agentAllowedTools := map[string]map[string]bool{
    "math-agent": {
        "calculator": true,
    },
    "time-agent": {
        "time_tool": true,
    },
}

// 自定义过滤函数:根据当前 Agent 名称过滤
filter := func(ctx context.Context, t tool.Tool) bool {
    declaration := t.Declaration()
    if declaration == nil {
        return false
    }
    toolName := declaration.Name

    // 从 context 获取当前 Agent 信息
    inv, ok := agent.InvocationFromContext(ctx)
    if !ok || inv == nil {
        return true // fallback: 允许所有工具
    }

    agentName := inv.AgentName

    // 检查该工具是否在当前 Agent 的允许列表中
    allowedTools, exists := agentAllowedTools[agentName]
    if !exists {
        return true // fallback: 允许所有工具
    }

    return allowedTools[toolName]
}

eventChan, err := runner.Run(ctx, userID, sessionID, message,
    agent.WithToolFilter(filter),
)

完整示例: 参见 examples/toolfilter/ 目录

智能过滤机制

框架会自动区分用户工具框架工具,只过滤用户工具:

工具分类 包含的工具 是否被过滤
用户工具 通过 WithTools 注册的工具
通过 WithToolSets 注册的工具
✅ 受过滤控制
框架工具 transfer_to_agent(多 Agent 协调)
knowledge_search(知识库检索)
agentic_knowledge_search
❌ 永不过滤,自动保留

示例:

// Agent 注册了多个工具
agent := llmagent.New("assistant",
    llmagent.WithTools([]tool.Tool{
        calculatorTool,  // 用户工具
        textTool,        // 用户工具
    }),
    llmagent.WithSubAgents([]agent.Agent{subAgent1, subAgent2}), // 自动添加 transfer_to_agent
    llmagent.WithKnowledge(kb),                                   // 自动添加 knowledge_search
)

// 运行时过滤:只允许 calculator
filter := tool.NewIncludeToolNamesFilter("calculator")
runner.Run(ctx, userID, sessionID, message,
    agent.WithToolFilter(filter),
)

// 实际发送给 LLM 的工具:
// ✅ calculator        - 用户工具,在允许列表中
// ❌ textTool          - 用户工具,被过滤
// ✅ transfer_to_agent - 框架工具,自动保留
// ✅ knowledge_search  - 框架工具,自动保留

注意事项

⚠️ 安全提示: 运行时工具过滤是"软约束",主要用于优化和用户体验。工具内部仍需实现自己的鉴权逻辑:

1
2
3
4
5
6
7
8
9
func sensitiveOperation(ctx context.Context, req Request) (Result, error) {
    // ✅ 必须:工具内部鉴权
    if !hasPermission(ctx, req.UserID, "sensitive_operation") {
        return nil, fmt.Errorf("permission denied")
    }

    // 执行操作
    return performOperation(req)
}

原因: LLM 可能从上下文或记忆中知道工具的存在和用法,并尝试调用。工具过滤减少了这种可能性,但不能完全防止。

并行工具执行

1
2
3
4
5
6
7
// 启用并行工具执行(可选,用于性能优化)
agent := llmagent.New("ai-assistant",
    llmagent.WithModel(model),
    llmagent.WithTools(tools),
    llmagent.WithToolSets(toolSets),
    llmagent.WithEnableParallelTools(true), // 启用并行执行
)

Graph 工作流下也可以在工具节点开启并行:

stateGraph.AddToolsNode("tools", tools, graph.WithEnableParallelTools(true))

并行执行效果:

# 并行执行(启用时)
Tool 1: get_weather     [====] 50ms
Tool 2: get_population  [====] 50ms
Tool 3: get_time       [====] 50ms
总时间: ~50ms(同时执行)

# 串行执行(默认)
Tool 1: get_weather     [====] 50ms
Tool 2: get_population       [====] 50ms
Tool 3: get_time                  [====] 50ms
总时间: ~150ms(依次执行)

运行时 ToolSet 动态管理

WithToolSets 是一种静态配置方式:在创建 Agent 时一次性注入 ToolSet。很多实际场景下,你希望在运行时动态增删 ToolSet,而不必重建 Agent。

LLMAgent 提供了三个与 ToolSet 相关的运行时方法:

  • AddToolSet(toolSet tool.ToolSet) —— 按 ToolSet.Name() 添加或替换同名 ToolSet
  • RemoveToolSet(name string) bool —— 按名称移除所有同名 ToolSet,返回是否确实删除
  • SetToolSets(toolSets []tool.ToolSet) —— 以给定切片整体替换当前所有 ToolSet

这些方法是并发安全的,并会自动重新计算:

  • 聚合后的工具列表(显式 WithTools 工具 + ToolSet 工具 + 知识检索工具 + Skills 工具)
  • “用户工具”跟踪信息(用于前文介绍的智能过滤机制)

典型使用方式:

// 1. 初始只挂基础工具
agent := llmagent.New("dynamic-assistant",
    llmagent.WithModel(model),
    llmagent.WithTools([]tool.Tool{calculatorTool}),
)

// 2. 运行时挂载一个 MCP ToolSet
mcpToolSet := mcp.NewMCPToolSet(connectionConfig)
if err := mcpToolSet.Init(ctx); err != nil {
    return fmt.Errorf("初始化 MCP ToolSet 失败: %w", err)
}
agent.AddToolSet(mcpToolSet)

// 3. 从配置中心下发一整套 ToolSet(声明式控制)
toolSetsFromConfig := []tool.ToolSet{mcpToolSet, fileToolSet}
agent.SetToolSets(toolSetsFromConfig)

// 4. 按名称下线某个 ToolSet(例如回滚某个集成)
removed := agent.RemoveToolSet(mcpToolSet.Name())
if !removed {
    log.Printf("未找到 ToolSet %q", mcpToolSet.Name())
}

运行时 ToolSet 更新会自动与前文的工具过滤机制协同工作:

  • 通过 WithTools 和所有 ToolSet(包括动态添加的 ToolSet)注册的工具都视为用户工具,会受到 WithToolFilter 以及每次调用的运行时过滤控制。
  • 框架工具(transfer_to_agentknowledge_searchagentic_knowledge_search)仍然永远不被过滤,始终对 Agent 可用。

快速开始

环境准备

# 设置 API 密钥
export OPENAI_API_KEY="your-api-key"

简单示例

package main

import (
    "context"
    "fmt"

    "trpc.group/trpc-go/trpc-agent-go/runner"
    "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
    "trpc.group/trpc-go/trpc-agent-go/model/openai"
    "trpc.group/trpc-go/trpc-agent-go/model"
    "trpc.group/trpc-go/trpc-agent-go/tool/function"
)

func main() {
    // 1. 创建简单工具
    calculatorTool := function.NewFunctionTool(
        func(ctx context.Context, req struct {
            Operation string  `json:"operation"`
            A         float64 `json:"a"`
            B         float64 `json:"b"`
        }) (map[string]interface{}, error) {
            var result float64
            switch req.Operation {
            case "add":
                result = req.A + req.B
            case "multiply":
                result = req.A * req.B
            default:
                return nil, fmt.Errorf("unsupported operation")
            }
            return map[string]interface{}{"result": result}, nil
        },
        function.WithName("calculator"),
        function.WithDescription("简单计算器"),
    )

    // 2. 创建模型和 Agent
    llmModel := openai.New("DeepSeek-V3-Online-64K")
    agent := llmagent.New("calculator-assistant",
        llmagent.WithModel(llmModel),
        llmagent.WithInstruction("你是一个数学助手"),
        llmagent.WithTools([]tool.Tool{calculatorTool}),
        llmagent.WithGenerationConfig(model.GenerationConfig{Stream: true}), // 启用流式输出
    )

    // 3. 创建 Runner 并执行
    r := runner.NewRunner("math-app", agent)

    ctx := context.Background()
    userMessage := model.NewUserMessage("请计算 25 乘以 4")

    eventChan, err := r.Run(ctx, "user1", "session1", userMessage)
    if err != nil {
        panic(err)
    }

    // 4. 处理响应
    for event := range eventChan {
        if event.Error != nil {
            fmt.Printf("错误: %s\n", event.Error.Message)
            continue
        }

        // 显示工具调用
        if len(event.Response.Choices) > 0 && len(event.Response.Choices[0].Message.ToolCalls) > 0 {
            for _, toolCall := range event.Response.Choices[0].Message.ToolCalls {
                fmt.Printf("🔧 调用工具: %s\n", toolCall.Function.Name)
                fmt.Printf("   参数: %s\n", string(toolCall.Function.Arguments))
            }
        }

        // 显示流式内容
        if len(event.Response.Choices) > 0 {
            fmt.Print(event.Response.Choices[0].Delta.Content)
        }

        if event.Done {
            break
        }
    }
}

运行示例

# 进入工具示例目录
cd examples/tool
go run .

# 进入 MCP 工具示例目录
cd examples/mcp_tool

# 启动外部服务器
cd streamalbe_server && go run main.go &

# 运行主程序
go run main.go -model="deepseek-chat"

总结

Tool 工具系统为 tRPC-Agent-Go 提供了丰富的扩展能力,支持函数工具、DuckDuckGo 搜索工具和 MCP 协议工具。