Agent 与自定义工具

Agent 与固定流程的链不同:模型根据任务自主决定调用哪些工具、调用几次,直到得出答案。langchaingo 提供 ReAct 风格的 Agent 与工具抽象。

内置工具与 One-Shot Agent

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/tmc/langchaingo/agents"
    "github.com/tmc/langchaingo/chains"
    "github.com/tmc/langchaingo/llms/openai"
    "github.com/tmc/langchaingo/tools"
)

func main() {
    if err := run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run() error {
    llm, err := openai.New()
    if err != nil {
        return err
    }

    // 内置工具:计算器等
    agentTools := []tools.Tool{
        tools.Calculator{},
    }

    agent := agents.NewOneShotAgent(
        llm,
        agentTools,
        agents.WithMaxIterations(3), // 限制最大推理轮数,防止失控
    )
    executor := agents.NewExecutor(agent)

    question := "37 乘以 42 再减去 19 等于多少?"
    answer, err := chains.Run(context.Background(), executor, question)
    if err != nil {
        return err
    }
    fmt.Println(answer)
    return nil
}

执行流程即 ReAct 循环:模型产出"思考 → 行动(选工具)→ 观察结果",循环直至给出最终答案。

自定义工具

任何 Go 能力都可以封装为工具。实现 tools.Tool 接口的三个方法即可:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

// WeatherTool:查询天气的工具,封装 HTTP 调用
type WeatherTool struct {
    Client *http.Client
}

func (w *WeatherTool) Name() string {
    return "weather"
}

func (w *WeatherTool) Description() string {
    // 描述写给出模型看:清晰说明用途与输入格式
    return "查询指定城市当前天气。输入为城市名称,例如:Beijing"
}

func (w *WeatherTool) Call(ctx context.Context, input string) (string, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet,
        "https://wttr.in/"+input+"?format=j1", nil)
    if err != nil {
        return "", err
    }

    resp, err := w.Client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    var data map[string]any
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return "", err
    }

    // 提取关键字段,压缩成模型友好的简短描述
    if areas, ok := data["current_condition"].([]any); ok && len(areas) > 0 {
        cur := areas[0].(map[string]any)
        return fmt.Sprintf("%s 当前温度 %s°C,天气 %s",
            input, cur["temp_C"], cur["weatherDesc"].([]any)[0].(map[string]any)["value"]), nil
    }
    return "未查询到天气数据", nil
}

func main() {
    _ = WeatherTool{Client: &http.Client{Timeout: 10 * time.Second}}
}

将自定义工具加入 Agent:

agentTools := []tools.Tool{
    tools.Calculator{},
    &WeatherTool{Client: &http.Client{Timeout: 10 * time.Second}},
}

agent := agents.NewOneShotAgent(llm, agentTools, agents.WithMaxIterations(5))
executor := agents.NewExecutor(agent)

answer, _ := chains.Run(ctx, executor, "上海现在多少度?适合穿外套吗?")

工具设计要点

:::tip 工具的质量决定 Agent 的上限:

  • Name 使用小写动词性短语,稳定且唯一。
  • Description 是模型的"说明书",需说明用途、输入格式与输出含义——模型完全依据它决定何时调用。
  • Call 返回简短、结构化的文本;错误以 error 返回而不是吞掉。
  • 工具内部做好超时与重试,遵循错误处理惯例。 :::

Agent 与 HTTP 服务结合

每个请求构造独立的执行器,共享 LLM 实例;配合 context 超时控制总时长:

package main

import (
    "context"
    "encoding/json"
    "net/http"
    "time"

    "github.com/tmc/langchaingo/agents"
    "github.com/tmc/langchaingo/chains"
    "github.com/tmc/langchaingo/llms/openai"
    "github.com/tmc/langchaingo/tools"
)

func main() {
    llm, _ := openai.New()

    http.HandleFunc("POST /api/agent", func(w http.ResponseWriter, r *http.Request) {
        var in struct {
            Question string `json:"question"`
        }
        _ = json.NewDecoder(r.Body).Decode(&in)

        agent := agents.NewOneShotAgent(llm,
            []tools.Tool{tools.Calculator{}},
            agents.WithMaxIterations(3))
        executor := agents.NewExecutor(agent)

        ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
        defer cancel()

        answer, err := chains.Run(ctx, executor, in.Question)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        _ = json.NewEncoder(w).Encode(map[string]string{"answer": answer})
    })

    _ = http.ListenAndServe(":8080", nil)
}

:::warning 安全边界:Agent 的能力等于其工具的能力。给 Agent 提供执行 SQL、读写文件、调用内部 API 等强权限工具时,必须:

  • 限制最大迭代次数(WithMaxIterations),控制成本与失控风险。
  • 工具内部实施权限校验与资源配额,禁止模型输入直接拼接 SQL 或 shell 命令。
  • 记录完整的工具调用审计日志。 :::

小结

  • ReAct Agent 通过"思考—行动—观察"循环自主调用工具,agents.NewOneShotAgent + agents.NewExecutor 是标准组合。
  • 自定义工具只需实现 Name/Description/Call 三个方法,Description 面向模型编写。
  • 工具权限、最大迭代与超时构成 Agent 的安全边界。