新增 Tool 实战手册
本手册面向需要向 Tether 注入新工具的开发者。无论是一个轻量的单个函数工具,还是带有文件交互的重型系统工具,都可以在 5 分钟内快速上线。
快速步骤
- 确认工具所依赖的能力接口(例如
IFileSystem或自定义服务)。 - 在插件类上声明
[Inject(...)]特性并继承Plugin基类。 - 使用
AIFunctionFactory.Create构建强类型工具。 - 调用
context.Effect(tools.Register(...).Dispose)把注册随作用域回收。
示例:编写 fetch_url_summary 工具
假设我们需要一个抓取网页文本并返回摘要的工具:
using System.ComponentModel;
using Cordis;
using Microsoft.Extensions.AI;
using Tether.Core;
using Tether.Web;
namespace MyPlugins.Cookbook;
[Inject(typeof(IWebService), typeof(IToolRegistry))]
public sealed class WebSummaryToolPlugin : Plugin
{
protected override void Apply(Context context)
{
var web = context.Require<IWebService>();
var tools = context.Require<IToolRegistry>();
var summaryTool = AIFunctionFactory.Create(
async (
[Description("完整的 HTTP/HTTPS 地址")] string url,
[Description("最大提取字符数,默认 2000")] int maxChars = 2000,
CancellationToken cancellationToken = default
) =>
{
var result = await web.FetchAsync(url, cancellationToken);
var content = result.Text;
if (content.Length > maxChars)
{
content = content[..maxChars] + "\n...(内容已截断)";
}
return new
{
finalUrl = result.FinalUrl,
kind = result.Kind.ToString(),
contentLength = content.Length,
text = content
};
},
name: "fetch_url_summary",
description: "抓取指定 URL 网页的正文内容并提取关键文本");
// 声明该工具为只读无副作用,允许与其他只读工具并发执行
var options = new ToolRegistrationOptions
{
IsConcurrencySafe = _ => true
};
// 注册随插件作用域回收
context.Effect(tools.Register(summaryTool, scope: null, options).Dispose);
}
}
最佳实践清单
- 参数描述必须清晰:每个参数必须带有
[Description(...)]特性,说明含义与边界条件(如单位是秒还是毫秒、路径是否必须为绝对路径)。 - 取消令牌传递:异步方法参数中包含
CancellationToken,大模型不会在入参中看到它,但运行时会自动注入当前 turn/step 的取消令牌。 - 结构化返回值:尽量返回强类型匿名对象或 DTO,而不是拼接的大段不规则字符串。这便于模型进行精准的 JSON 字段抽取。
- 异常处理由管线兜底:工具方法内抛出的未捕获异常会被 Tether 的工具管线截获,包装为稳定的
TOOL_ERROR失败码并向模型返回友好错误信息,不会导致 Agent 主循环崩溃。