开发一个 Tool
在 Tether 中,暴露给大模型的工具(Tool)都是通过插件向 IToolRegistry 注册的。本篇通过一个 greet 工具示例,讲解如何在 C# 中定义工具、注入依赖并纳入生命周期管理。
核心机制
- 工具模型:Tether 采用
Microsoft.Extensions.AI.AIFunction作为工具的标准抽象。 - Schema 推导:通过
AIFunctionFactory.Create(...),从 C# 方法签名、参数类型与 XML/参数特性自动生成模型可见的 JSON Schema。 - 注册即回收:调用
tools.Register(tool)返回IDisposable,通过context.Effect(registration.Dispose)绑定到当前插件的作用域(Effect只接受Action/Func<ValueTask>回调)。当插件卸载或重载时,工具自动从大模型视图中注销。
编写工具插件
创建一个继承 Plugin 基类的类,用 [Inject(...)] 特性声明依赖 IToolRegistry:
using System.ComponentModel;
using Cordis;
using Microsoft.Extensions.AI;
using Tether.Core;
namespace MyPlugins;
[Inject(typeof(IToolRegistry))]
public sealed class GreetToolPlugin : Plugin
{
protected override void Apply(Context context)
{
var tools = context.Require<IToolRegistry>();
// 定义供模型调用的委托与元数据
var greetFunction = AIFunctionFactory.Create(
[Description("根据姓名向用户打招呼")]
(
[Description("要打招呼的目标用户名")] string name,
[Description("打招呼使用的语言,默认 zh-CN")] string? language = "zh-CN"
) =>
{
return language switch
{
"en-US" => $"Hello, {name}!",
"ja-JP" => $"こんにちは、{name}さん!",
_ => $"你好,{name}!"
};
},
name: "greet",
description: "向指定的人发送问候语");
// 注册工具并挂载到 effect 作用域
context.Effect(tools.Register(greetFunction).Dispose);
}
}
代码要点说明
[Inject(typeof(IToolRegistry))]:Plugin基类会读取该特性(Plugin.Inject从InjectAttribute聚合),通知 Cordis 只有在IToolRegistry存在时才激活当前插件。[Description(...)]:参数特性中的描述会直接写入大模型看到的 Tool Parameter 描述中,指导模型如何填参;委托上的[Description]在没有显式传description:时会成为工具级描述(显式传入时以description:为准)。context.Effect(tools.Register(...).Dispose):这是 Cordis 的核心规则——Register返回IDisposable,而Effect只接受Action/Func<ValueTask>回调,所以要传registration.Dispose(或先存成变量再传registration.Dispose)。绝不要丢弃这个返回值,否则插件卸载时工具会残留并在后续调用中产生悬空引用。
配置并发与执行选项
默认情况下,工具是以 独占模式(Exclusive) 执行的,构成执行屏障。如果你的工具是只读的、无副作用且支持并发调用(如搜索、查表),可以通过 ToolRegistrationOptions 明确声明:
var options = new ToolRegistrationOptions
{
// 当参数表明是只读操作时,允许与其他只读工具并发执行
IsConcurrencySafe = args => true
};
context.Effect(tools.Register(greetFunction, scope: null, options).Dispose);
挂载与验证
将插件编译并在宿主或 YAML 组合中启用:
# composition.yml
plugins:
- { id: tools, name: tools } # Tether.Core.Tools 的 ToolRegistryPlugin,提供 IToolRegistry
- { id: my-greet-tool, name: my-greet-tool } # 你自己的插件 row(name 对应 BundleCatalog 注册名)
通过 CLI 运行:
dotnet run --project apps/Tether.Cli -- \
--send "请使用 greet 工具向 Alice 打个招呼(用英文)"
大模型将生成如下工具调用并在获得返回值后组织回复:
{
"name": "greet",
"arguments": { "name": "Alice", "language": "en-US" }
}