扩展 Tools 与 Integrations
最近更新:
扩展前先确定要增加什么:一个具体操作可以写成 Tool,一组任务说明和专属工具可以通过 Skill 提供,需要用户连接外部账号的能力则适合 Integration。
先阅读模块边界,确认能力由哪个模块负责。本页说明放置代码、注册能力和验证调用的顺序;具体声明写法可参考文末的工具示例。
工具扩展路径
- 手写的
IAgwTool、IContextualTool和IToolBlock实现放在Agw.Tools,全局目录只扫描这个程序集中的手写工具。业务工具放在所属模块的Application/Tools,以 Attribute 容器声明或通过 Skill 提供,DTO 放Contracts/Tools。 - 引用
Agw.Tools.Abstractions,需要 Attribute 声明时将Agw.Tools.Generators作为 Analyzer 引用。 - 显式声明权限、参数说明和返回类型。独立工具及使用 Attribute 声明的工具容器不能保存会话状态;状态放在 Provider、会话对象或所属存储中。
- 在所属模块注册所需服务与生成声明。选择通过 Skill 提供,或显式加入全局目录。
- 验证工具发现、参数、权限、项目绑定和错误映射。
生成器输出元数据、JSON Schema 和直接调用委托。不要加入运行时反射扫描兜底。Skill 工具有两个来源:IAgentSkillRegistration.Tools 提供手写的 IProjectScopedAgwTool 实例,ToolTypes 提供 Attribute 容器类型(由 IAgwToolSet<T> 生成)。它们在执行时绑定 Project,不因注册生成模块就自动进入全局目录。
集成扩展路径
IPluginCatalog 拥有 Plugin、Connector、认证和能力源定义。定义是代码/内容资产;用户设置是 PluginInstallation,可选账号或端点是 Connection。不要将它们合成一张全局配置表。
先增加目录定义和必要的工具来源,再验证用户完成设置、账号进入 Ready 状态、绑定 Agent 和实际调用的全过程。凭据由 Infrastructure 加密保存并在调用时读取;读取和执行都要检查账号归属。通过 HTTP/SSE 发送凭据时使用 HTTPS。
选择全局工具还是 Skill 专属工具
如果工具是可单独选用的通用操作,可显式加入全局目录。如果它只服务于某个 Skill,就通过该 Skill 注册,使说明和工具一起提供给 Agent。例如,agw-job 的任务管理工具属于 Jobs 模块,并随 Skill 提供。
工具成功编译后,还要确认 Agent 实际能发现它。若目录中没有出现,应先检查生成声明和注册位置;若调用时失败,再检查 Project 绑定、权限及参数。编译通过并不等于已经完成运行时接入。
方式一:通过接口定义 Tool
适合一个类负责一个可独立调用的操作。实现 IAgwTool,声明稳定名称、分类、Plan 可用性和权限,并实现唯一必需的成员 ToAITool()。仓库工具的写法是把操作放在 Execute 方法中,再在 ToAITool() 中用 AgwAIFunctionFactory.CreateParameterObjectFunction 包装成模型可调用的函数;这个工厂是 Agw.Tools 的内部类型,所以示例放在 Agw.Tools 中。下面是一个不读写外部资源的最小示例:
IAgwTool 继承元数据接口 IAgwToolMeta,ToAITool() 把执行方法包装成模型可调用的函数。参数 DTO 和方法上的 Description 帮助模型理解何时调用、如何填参。实际异步 I/O 应参考仓库工具使用异步方法并传递 CancellationToken;业务 DTO 放在所属模块的 Contracts/Tools。
接入与使用
- 将实现放到
Agw.Tools/Impl/Tools。全局目录只扫描Agw.Tools程序集中的手写工具,放在业务模块中的IAgwTool不会被发现;业务模块改用方式二的 Attribute 容器,或把IProjectScopedAgwTool实例放进 Skill 的IAgentSkillRegistration.Tools。声明对象保持无状态,不在字段中保存当前 Project、用户或会话数据。 - 在
src/server/Agw.Shared/Tooling/ToolValueObject.cs中把工具名称加入ToolDefinitionNames及其All列表,并加入具体ToolDefinition、[JsonDerivedType]名称映射和空的或实际的 Options 类型,保持定义与实现一一对应。名称没有登记时,注册会报错 “does not have a registered ToolDefinition”。 - 在所属模块 DI 入口登记依赖,确认工具出现在
/api/tools;工具绑定到 Agent 或 Project 后,才供该执行目标使用。 - 在 Chat 让 Agent 调用该操作,检查输入和输出。直接调用 C# 方法适合单元测试,但不能证明运行时权限和 Project 绑定已接通。
有 Project 或运行时目录依赖的独立工具,应参考 IContextualTool.MaterializeAsync(接口位于 Agw.Tools/Contracts/Abstractions,与其他手写工具一样只从 Agw.Tools 程序集发现),把已校验的上下文绑定到本次贡献的函数中。不要让模型通过一个随意填写的 Project ID 决定资源归属。
方式二:通过 Attribute 定义 Tool
适合把一个服务中的多个操作声明为工具,也适合随 Skill 提供的业务能力。先添加抽象项目和生成器引用,路径按项目位置调整:
下面与接口示例实现同一个 echo 操作,二选一使用,不要同时注册同名工具:
示例容器是普通的 sealed class,其中的方法都是静态方法;后面的 IAgwToolSet<TextTools> 要求类型参数是非静态类,所以容器不能声明为 static class。AgwToolContainer 批量选择类型直接声明的 public 普通方法;AgwTool 指定名称和权限;AgwToolIgnore 排除辅助方法。没有显式名称时,默认使用方法名并移除末尾的 Async。容器及每个操作都应明确声明或继承权限;AllowInPlanMode 是独立设置。
实例容器使用显式构造函数注入,并在 DI 中注册实例类型。静态方法可通过标记 [AgwToolService] 的参数取得服务。服务参数与 CancellationToken 不进入模型可填写的参数 Schema;每次调用都有独立的异步 DI scope。
生成、注册与选用
编译器生成元数据、输入输出 Schema 和直接调用委托。以程序集 My.Module 为例,在模块组合入口登记生成模块;需要全局选用时再选择容器类型:
上述代码片段需要生成声明命名空间及所属注册扩展的引用。只包含静态方法的容器不必登记实例;包含实例方法的容器还需 services.AddScoped<YourToolContainer>()。生成模块只登记声明,不会自动公开全部工具。全局工具仍要完成具体 ToolDefinition 和 JSON 多态映射。
若工具只服务于 Skill,让 Skill 注册类使用 partial 并实现 IAgwToolSet<TextTools>,生成器补齐 ToolTypes;同时按 IAgentSkillRegistration 完成 Id、说明和创建逻辑。在所属模块的 DI 入口用 services.AddSingleton<IAgentSkillRegistration, YourSkillRegistration>() 注册 Skill,并登记生成模块和实例容器,写法参考 Jobs 模块的 JobManagementSkillRegistration。将该 Skill 绑定到 Agent/Project 后,其工具才参与运行时组合。手写的 Skill 工具实现 IProjectScopedAgwTool,放进 IAgentSkillRegistration.Tools,不应额外放进全局目录。
生成失败时先处理编译诊断:不支持的签名是 AGWTOOL001,无效声明是 AGWTOOL002。不要用运行时反射绕过诊断。完整的实例容器和 Skill 示例见 工具抽象说明。
ToolBlock:定义共享状态的一组工具
当多个操作需要维护同一份状态,例如添加 Todo、完成 Todo 和查看待办列表,应把它们作为一个 ToolBlock 整组选用。Attribute 容器只是声明方式,本身不提供会话状态隔离。
下面是仓库的 TodoToolBlock 完整实现:
Todo 的状态与生命周期
Descriptor.Members声明五个成员、各自权限和 Plan 可用性。成员不能单独注册成全局工具。MaterializeAsync创建本次能力组合使用的AgwTodoProvider,放进ToolContribution.ContextProviders,同时提供循环检查器。AgwTodoProvider把AgwTodoState保存到当前AgentSession.StateBag,并通过StateKeys声明状态键。调用时从当前 session 读取、修改并保存,不能用 static 列表或共享单例保存所有人的 Todo。- 同一 session 的后续调用可继续使用已有 Todo;不同 session 的状态分开。持久化恢复依赖外层会话保存与恢复流程,不能仅因为 Provider 中有字段就认为状态可以恢复。
TodoCompletionLoopEvaluator检查待办是否完成;与 Mode 一起启用时只在 Execute 阶段参与检查。自定义 ToolBlock 不一定需要循环检查器,按具体任务添加。
开发自己的有状态 ToolBlock
- 定义数据对象和状态范围:回合、会话或项目长期存储。会话状态参考
AgwTodoState,项目长期状态参考 Project Memory。 - 在
Agw.Shared/Tooling/ToolValueObject.cs中把名称加入ToolBlockDefinitionNames及其All列表,增加具体ToolBlockDefinition、Options 和[JsonDerivedType]名称映射,再在ToolBlockNames中加入引用该常量的运行时名称。启动时的覆盖检查会拒绝缺少定义或缺少实现的 ToolBlock。 - 实现
IToolBlock,一次声明全部成员,明确每个成员的权限与allowInPlanMode。 - 在
MaterializeAsync创建 Provider,将函数和状态操作绑定到当前上下文;把生命周期交给ToolContribution,不要缓存跨用户的 Provider 或 scoped 服务。 - 在目录和定义解析流程接通整组选用,再测试:新增、完成、移除、不同 session 隔离、保存恢复、Plan 限制与审批。
参考 Todo Provider 和 Todo 状态,不要只复制描述符而遗漏状态的读取与保存。
Plugin:从目录定义到实际调用
这里的 Plugin 是 AGW 集成目录中的代码与内容定义。当前内建示例是 GitHub;新增 Plugin 需要修改并构建服务端,不是上传任意插件包即可运行。界面用 Available integrations 展示目录,用 Configured integrations 展示用户配置的账号或端点。
| 开发对象 | 定义内容 |
|---|---|
PluginDefinition | 稳定 Id、版本、显示名、Connector 和可选 Skill 内容 |
ConnectorDefinition | 服务或协议变体,例如 GitHub Cloud |
AuthSchemeDefinition | 认证方式、用户配置字段及 OAuth 流程设置 |
CapabilitySourceDefinition | 工具由内部 C# Provider 创建,或从 MCP 服务取得 |
PluginInstallation | 当前用户的安装设置,例如 OAuth Client ID/Secret |
Connection | 当前用户配置的具体账号、凭据和 Ready 状态,绑定时使用 ConnectionId |
定义目录与认证
下面是现有 GitHub 目录实现,可作为开发新集成时的完整结构参考。OAuth 地址、scope 和字段需按目标服务的协议设置,不能照搬 GitHub 的值:
新增定义时保持 Plugin、Connector、认证方式和能力源的 Id 稳定,并让 PluginCatalogValidator 验证整个目录。InstallationFields 用于每用户的安装配置;账号级字段应放到相应认证方案的连接字段中。秘密字段使用 Secret 类型,不能把真实凭据写入目录定义。
实现能力源
Native:定义中的 Provider = "github" 对应 IConnectionNativeCapabilityProvider.Provider。实现 CreateTools(ConnectionNativeCapabilityContext),用已解析的 ConnectionId、Alias 和 ProjectId 创建工具。工具名称通常为 {alias}__{operation},防止多个账号的工具混淆。
参考 GitHubConnectionNativeCapabilityProvider:生成函数绑定账号和 Project,实际调用时创建 scope、解析 IGitHubConnectionInvoker;Invoker 再执行账号归属、Ready 状态与凭据检查。不得让模型指定任意 ConnectionId,或在单例 Provider 中缓存账号密钥。为新增操作接入当前能力源的权限元数据与执行审批流程。
MCP:使用 McpCapabilitySourceDefinition,选择 stdio、HTTP 或 SSE Transport,再通过 CredentialBindings 把安装字段、连接字段或 OAuth Token 注入所需环境变量或 HTTP Header。经过网络注入凭据时使用 HTTPS;字段引用必须与认证定义匹配。MCP 路径复用连接授权与运行时校验,不是直接把未验证的 URL 交给 Agent。
注册、内容与测试
- 在 Integrations 的 DI 入口维护目录注册;Native Provider 注册为
IConnectionNativeCapabilityProvider,调用服务按其生命周期登记,例如 GitHub 的 scoped Invoker。 - 若附带 Skill,将内容放入 Plugin 内容目录,并通过
PluginSkillDefinition.ContentPath指向SKILL.md。它提供使用说明,不自动执行第三方脚本;同时确认构建产物包含内容文件。 - 在 Available integrations 找到定义,为测试用户配置安装字段和账号。完成认证后确认 Ready,再绑定 Agent 或 Project。
- 验证一次读取和一次受控写入,检查工具名称、参数、权限及错误处理。测试使用真实实现,不使用 mock 或 fake 实现;需要真实账号或 OAuth 授权的测试不放进默认测试套件。
- 覆盖跨用户 ConnectionId、未就绪账号、失效凭据、目录字段错误、同名工具和配置变更。修改用户安装设置只应影响该用户的账号连接。
当前没有远程 Marketplace 的下载、签名与自动升级机制。完整调用链参考 GitHub Native Provider、GitHub Invoker 和 能力源定义。
验证
至少覆盖成功调用、非法参数、权限不足、外来 Connection 和未就绪 Connection。保持编译期诊断有效。测试使用真实实现,不使用 mock 或 fake 实现;依赖真实账号或外部 CLI 的测试需要显式开启,不放进默认测试套件。