插件配置与校验

插件通常需要读取部署环境相关的参数(例如超时阈值、服务地址或默认选项)。Tether 坚持配置大声报错(Fail Loud)原则:配置错误在加载阶段立即被拦截,绝不在运行时静默降级或吞掉错误。

配置模型定义

在 C# 版 Cordis 中,配置类使用标准的 C# 属性与 System.ComponentModel.DataAnnotations 特性定义校验规则;插件如何收到这个配置对象取决于它以内建还是树外方式接入(见下一节):

using System.ComponentModel.DataAnnotations;

namespace MyPlugins;

/// <summary>
/// 问候工具的配置选项
/// </summary>
public sealed class GreetConfig
{
    [Required(ErrorMessage = "DefaultGreeting 是必填项")]
    [StringLength(50, MinimumLength = 1, ErrorMessage = "问候语长度必须在 1 到 50 之间")]
    public string DefaultGreeting { get; set; } = "Hello";

    [Range(1, 300, ErrorMessage = "超时时间必须在 1 到 300 秒之间")]
    public int TimeoutSeconds { get; set; } = 30;

    public bool EnableTimestamp { get; set; } = false;
}

编写带配置的插件

配置对象是在插件实例化之前物化的:加载器先把 YAML 的 config 节点绑定到配置类型并跑完 DataAnnotations 校验,再把结果交给插件。因此内建插件的常见写法是构造函数接收配置,catalog.Register<TConfig> 的工厂把物化好的实例传进来:

using Cordis;
using Microsoft.Extensions.AI;
using Tether.Core;

namespace MyPlugins;

[Inject(typeof(IToolRegistry))]
public sealed class ConfigurableGreetPlugin(GreetConfig config) : Plugin
{
    protected override void Apply(Context context)
    {
        var tools = context.Require<IToolRegistry>();

        var greetFunc = AIFunctionFactory.Create(
            (string name) =>
            {
                var timePrefix = config.EnableTimestamp
                    ? $"[{DateTime.UtcNow:HH:mm:ss}] "
                    : string.Empty;

                return $"{timePrefix}{config.DefaultGreeting}, {name}!";
            },
            name: "greet",
            description: "根据当前配置的问候语向用户问好");

        context.Effect(tools.Register(greetFunc).Dispose);
    }
}

对应的 catalog 注册(src/Tether.Bundles/BundleCatalogs.cs 中 persistent-shell-tools 等行同此模式):

catalog.Register<GreetConfig>("custom-greet", config => new ConfigurableGreetPlugin(config));

树外插件包(由 Tether.PluginPackages 加载的独立程序集)没有 catalog 工厂,配置经另一条路径注入:插件要么实现 IConfigurablePlugin.AcceptConfig(object?)(无参构造 + 接收配置),要么提供恰好一个能接收配置类型的公共构造函数。约定不满足时加载直接失败。


在 YAML 中注入配置

在 cordis.yml 或组合文件中,将参数写入对应插件的 config 节点:

plugins:
  - { id: tools, name: tools }
  - id: custom-greet
    name: custom-greet
    config:
      defaultGreeting: "欢迎来到 Tether"
      timeoutSeconds: 60
      enableTimestamp: true

行的合法键只有 id、name、disabled、config;name 对应 catalog 注册名(内建为 tools 这类固定名,树外插件包则是 packageId. 前缀的名字)。其它键(比如 package)会让加载器抛 CompositionException。

环境变量替换

YAML 配置支持通过 ${ENV_NAME} 语法引用环境变量:

plugins:
  - id: custom-greet
    name: custom-greet
    config:
      defaultGreeting: "${CUSTOM_GREETING_PREFIX:-欢迎}"
      timeoutSeconds: 30

校验失败的处理机制

如果 YAML 中的配置违反了 DataAnnotations 规则(例如传入了负数超时或遗漏了必填字段):

plugins:
  - id: custom-greet
    name: custom-greet
    config:
      timeoutSeconds: -10  # 违反 [Range(1, 300)]

Cordis 加载器会立即中止加载,抛出 CompositionException,明确输出出错的字段名与校验错误信息:

Unhandled exception: Cordis.Loader.CompositionException:
Invalid config for plugin 'custom-greet' in row 'custom-greet':
  - TimeoutSeconds: 超时时间必须在 1 到 300 秒之间

这种机制杜绝了由于拼写错误或非法值导致的隐蔽运行时缺陷。


下一步

在 GitHub 上编辑此页