第9节 自定义工具与callback事件节点处理

在构建智能AI应用时,我们经常需要让AI能够调用外部工具或服务来获取信息或执行操作。例如,查询天气、搜索网页、调用API等。LangChain框架为我们提供了强大的工具机制,允许我们自定义工具并处理回调事件,从而扩展AI的能力。

什么是工具(Tool)
工具是AI代理(Agent)可以调用的函数或服务。每个工具都有一个名称、描述和实际的执行逻辑。当AI在处理用户请求时,如果需要外部信息,它会根据工具的描述选择合适的工具进行调用。

创建自定义工具
让我们通过一个实际的例子来学习如何创建自定义工具——天气查询工具。

1. 定义工具结构
首先,我们需要定义工具的结构:

type Tool struct {
    client *internal.Client
}

这里的Tool结构体包含了一个内部客户端,用于实际调用天气API。

2. 实现工具接口
一个工具需要实现几个关键方法:
Name() 方法

func (t Tool) Name() string {
    return "weather search"
}

这个方法返回工具的名称,AI会根据这个名称来识别和调用工具。
Description() 方法

func (t Tool) Description() string {
    return `
    "a weather query interface. "
    "use when you answer information about the weather. "
    "when you need to get city weather information, it is always the first option. "
    "The input should be {"sheng": xx, "place": xx,"day":"1-7"} which can be parsed by json "
    "give an example {"sheng": "四川", "place": "绵阳","day":"1"}"
    "day(非必需) 可传1到7,代表1到7天内天气预报,默认1"
    "hourtype (非必需) 是否返回时段天气预报,0=不返回,1=返回。默认0。"
    `
}

这个方法返回工具的详细描述,告诉AI在什么情况下使用这个工具以及如何使用它。描述应该足够详细,让AI能够正确地使用工具。

func (t Tool) Call(ctx context.Context, input string) (string, error) {
    // 解析输入参数
    sheng, place, day, hourtype, err := t.parseInput(input)
    if err != nil {
        return "", err
    }

    // 调用接口
    response, err := t.client.Search(sheng, place, day, hourtype)
    if err != nil {
        return "", err
    }

    // 解析并格式化输出
    return t.parseOutPut(response)
}

这是工具的核心方法,负责实际执行工具的功能。它接收输入参数,调用相应的服务,并返回结果。

 

3. 处理输入和输出

解析输入

func (t Tool) parseInput(input string) (sheng, place, day, hourtype string, err error) {
    var req Req
    err = json.Unmarshal([]byte(input), &req)
    if err != nil {
        return "", "", "", "", err
    }
    return req.Sheng, req.Place, req.Day, req.Place, err
}

格式化输出

func (t Tool) parseOutPut(resp *internal.Response) (string, error) {
    fmt.Println(resp)
    return fmt.Sprintf("天气信息:\n"+
        "天气状况:%s %s\n"+
        "温度:%s ~ %s\n"+
        "风向:%s ~ %s\n"+
        "风力:%s ~ %s\n"+
        "当前温度:%.1f°C\n"+
        "降水量:%.1f mm\n"+
        "湿度:%d%%\n"+
        "气压:%d hPa",
        resp.Weather1, resp.Weather2,
        resp.Wd1, resp.Wd2,
        resp.Winddirection1, resp.Winddirection2,
        resp.Windleve1, resp.Windleve2,
        resp.NowInfo.Temperature,
        resp.NowInfo.Precipitation,
        resp.NowInfo.Humidity,
        resp.NowInfo.Pressure), nil
}

Callback事件节点处理
在工具执行过程中,我们可能需要处理各种事件,比如记录日志、处理错误、监控性能等。Callback机制允许我们在工具执行的不同阶段插入自定义逻辑。
1. 工具调用前的处理
在调用工具之前,我们可以添加预处理逻辑:

 

func (t Tool) Call(ctx context.Context, input string) (string, error) {
    // 记录工具调用日志
    log.Printf("调用天气工具,输入参数: %s", input)
    
    // 解析输入参数
    sheng, place, day, hourtype, err := t.parseInput(input)
    if err != nil {
        return "", err
    }
    
    // 执行工具逻辑
    // ...
}

2. 工具调用后的处理
在工具执行完成后,我们可以处理结果或错误

func (t Tool) Call(ctx context.Context, input string) (string, error) {
    // ... 执行工具逻辑
    
    response, err := t.client.Search(sheng, place, day, hourtype)
    if err != nil {
        // 记录错误日志
        log.Printf("天气工具调用失败: %v", err)
        return "", err
    }
    
    result, err := t.parseOutPut(response)
    if err != nil {
        // 记录格式化错误
        log.Printf("结果格式化失败: %v", err)
        return "", err
    }
    
    // 记录成功日志
    log.Printf("天气工具调用成功,结果: %s", result)
    return result, nil
}

实际应用示例
让我们通过一个完整的示例来展示如何使用自定义工具:

// 创建天气工具实例
weatherTool := weather.NewTool(
    weather.WithAPIKey("your_api_key"),
    weather.WithAPIID("your_api_id"),
)

// 模拟AI调用工具
result, err := weatherTool.Call(context.Background(), `{"sheng": "北京", "place": "北京"}`)
if err != nil {
    log.Fatal(err)
}

fmt.Println(result)

最佳实践
1. 清晰的工具描述
确保工具的描述足够详细,让AI能够正确理解和使用工具。
2. 错误处理
始终处理可能出现的错误,并提供有意义的错误信息。
3. 输入验证
在处理输入之前,验证输入的格式和内容是否正确。
4. 日志记录
添加适当的日志记录,方便调试和监控工具的使用情况。
5. 结果格式化
确保工具的输出结果格式清晰、易于理解。
总结
通过本节的学习,我们了解了如何创建自定义工具以及如何处理回调事件。自定义工具是扩展AI能力的重要方式,它允许AI访问外部服务和数据。通过合理的工具设计和事件处理,我们可以构建出功能强大、稳定可靠的AI应用。
在下一节中,我们将学习如何将多个工具组合使用,构建更复杂的AI代理系统。

posted @ 2025-09-25 14:34  phpwyl  阅读(15)  评论(0)    收藏  举报