Agent 能力注册中心:把工具和技能当作微服务治理(续篇)

📅 2026/8/1 7:36:36
Agent 能力注册中心:把工具和技能当作微服务治理(续篇)
Agent 能力注册中心把工具和技能当作微服务治理续篇场景痛点Agent需要调用查询订单状态工具。开发者写了个order_query函数注册到Agent。一个月后订单服务API改了order_query返回格式变了。Agent不知道直接把旧格式的结果喂给下游步骤——整个工作流炸了。更常见的场景5个开发者各自写了查询用户信息的工具名字分别是user_info、get_user、query_user_detail、fetch_user_profile、user_lookup。功能重叠参数风格不同。Agent选择哪个随机选那结果不稳定。核心矛盾Agent的工具和能力缺乏统一的注册、发现、版本管理机制。开发者按自己的习惯命名和实现工具没有标准化的描述格式没有版本号没有健康状态监控。这和微服务早期的服务混乱问题一模一样——解决方案也一模一样注册中心。底层机制与原理剖析能力注册中心的本质是Agent能力的Service Registry。每个工具/技能注册时声明名称、版本、输入输出schema、能力描述、依赖关系、健康状态。Agent调用工具前先查注册中心——获取最新版本、校验输入参数、检查工具健康状态。关键机制Schema-driven描述。工具注册时必须声明JSON Schema格式的输入输出。Agent编排器根据schema校验参数、自动拼接步骤间的数据流。没有schema的工具不允许注册——就像没有API文档的微服务不允许上线。语义名称规范。工具名称遵循domain.action.target三段式命名order.query.status、user.create.profile、payment.process.refund。语义命名消除5个开发者写5个名字的混乱。注册中心检测名称冲突——同一语义已有注册工具时拒绝重复注册。版本管理。工具API变更时注册新版本v2旧版本v1标记为deprecated但仍可用。Agent编排器默认使用最新版本但可以指定版本号。deprecated版本30天后自动下线——给Agent足够的迁移窗口。健康检查。注册中心定时探测每个工具的可用性。HTTP工具发健康检查请求本地工具执行空参数校验。健康状态为down的工具Agent编排器不会选择——避免调用已故障的工具浪费时间和token。生产级代码实现CapabilityRegistry能力注册中心核心// registry/capability-registry.ts import { Redis } from ioredis; import { Ajv } from ajv; type HealthStatus healthy | degraded | down; type VersionStatus active | deprecated | offline; interface CapabilityDescriptor { // 语义名称domain.action.target name: string; version: string; // semver格式1.0.0 versionStatus: VersionStatus; description: string; // 人类可读描述 inputSchema: object; // JSON Schema outputSchema: object; // JSON Schema runtimeType: http | local | grpc; // 工具运行方式 endpoint?: string; // HTTP/GRPC工具的地址 handlerPath?: string; // 本地工具的函数路径 dependencies: string[]; // 依赖的其他能力名称 healthStatus: HealthStatus; healthCheckEndpoint?: string; registeredAt: string; deprecatedAt?: string; offlineAt?: string; // 计划下线时间 owner: string; // 负责人/团队 tags: string[]; // 搜索标签 } class CapabilityRegistry { private redis: Redis; private ajv: Ajv; private healthChecker: HealthChecker; constructor(redisUrl: string) { this.redis new Redis(redisUrl); this.ajv new Ajv({ strict: true }); this.healthChecker new HealthChecker(this); } // 注册新能力 async register(descriptor: CapabilityDescriptor): PromiseRegisterResult { // 1. 验证名称规范必须是domain.action.target三段式 // 为什么强制命名规范语义命名让Agent能根据意图自动匹配工具 // 自由命名导致Agent无法理解工具用途 const nameParts descriptor.name.split(.); if (nameParts.length ! 3) { return { success: false, error: 名称必须为domain.action.target三段式格式当前: ${descriptor.name} }; } // 2. 检查名称冲突同一语义名称已有活跃版本 const existing await this.findByName(descriptor.name); const activeVersions existing.filter( d d.versionStatus active d.version ! descriptor.version ); if (activeVersions.length 0) { // 同名称已有活跃版本但版本号不同——允许这是版本升级 // 同名称同版本——拒绝 const sameVersion activeVersions.find(d d.version descriptor.version); if (sameVersion) { return { success: false, error: 名称${descriptor.name}版本${descriptor.version}已注册 }; } } // 3. 校验input/output schema合法性 try { this.ajv.compile(descriptor.inputSchema); this.ajv.compile(descriptor.outputSchema); } catch (e: any) { return { success: false, error: Schema校验失败: ${e.message} }; } // 4. 存储到RegistryDB const key capability:${descriptor.name}:${descriptor.version}; descriptor.registeredAt new Date().toISOString(); descriptor.versionStatus active; descriptor.healthStatus healthy; await this.redis.set(key, JSON.stringify(descriptor)); // 5. 更新名称索引用于按名称搜索 await this.redis.sadd(capability_index:${descriptor.name}, descriptor.version); // 6. 启动健康检查 this.healthChecker.registerCheck(descriptor); return { success: true, descriptor }; } // 标记能力为deprecated async deprecate(name: string, version: string, offlineDate: string): Promisevoid { const descriptor await this.loadDescriptor(name, version); if (!descriptor) throw new Error(能力 ${name}:${version} 不存在); descriptor.versionStatus deprecated; descriptor.deprecatedAt new Date().toISOString(); descriptor.offlineAt offlineDate; // 30天后下线 await this.redis.set( capability:${name}:${version}, JSON.stringify(descriptor) ); // 通知订阅者工具版本变更 // 为什么通知而非让订阅者自己发现变更可能影响Agent编排器的步骤参数 // 及时通知允许编排器提前适配新版本 await this.publishVersionChange(name, version, deprecated); } // 能力发现Agent根据意图查询匹配的工具 async discover( intent: string, requiredTags?: string[] ): PromiseCapabilityDescriptor[] { // 1. 意图解析从自然语言意图提取domain和action // 为什么需要意图解析Agent说我想查询订单状态 // 注册中心需要映射到order.query.status const parsed this.parseIntent(intent); // 2. 按domainaction搜索 const candidates await this.findByDomainAction(parsed.domain, parsed.action); // 3. 过滤排除down/deprecated除非显式指定版本 // 为什么排除deprecated而非保留Agent默认使用最新版本 // deprecated版本只是过渡期的兼容选项 const filtered candidates.filter( d d.healthStatus ! down d.versionStatus active ); // 4. 标签匹配 if (requiredTags) { const tagged filtered.filter( d requiredTags.every(tag d.tags.includes(tag)) ); if (tagged.length 0) return tagged; // 标签不匹配时返回全部候选——标签是可选筛选条件不是硬性过滤 // 为什么不硬过滤标签是辅助分类核心匹配靠domainaction } // 5. 按版本排序最新版本优先 filtered.sort((a, b) b.version.localeCompare(a.version)); return filtered; } // 参数校验调用工具前校验输入参数是否符合schema async validateInput( name: string, version: string, input: any ): PromiseValidationResult { const descriptor await this.loadDescriptor(name, version); if (!descriptor) { return { valid: false, errors: [能力 ${name}:${version} 不存在] }; } const validate this.ajv.compile(descriptor.inputSchema); const valid validate(input); if (!valid) { return { valid: false, errors: validate.errors?.map(e ${e.instancePath}: ${e.message}) ?? [] }; } return { valid: true }; } // 按名称查找所有版本 private async findByName(name: string): PromiseCapabilityDescriptor[] { const versions await this.redis.smembers(capability_index:${name}); const descriptors: CapabilityDescriptor[] []; for (const version of versions) { const descriptor await this.loadDescriptor(name, version); if (descriptor) descriptors.push(descriptor); } return descriptors; } // 按domainaction查找模糊匹配 private async findByDomainAction( domain: string, action: string ): PromiseCapabilityDescriptor[] { // 遍历所有能力名称索引匹配domain和action const allNames await this.redis.keys(capability_index:*); const matchedNames allNames.filter(key { const name key.replace(capability_index:, ); const parts name.split(.); return parts[0] domain parts[1] action; }); const results: CapabilityDescriptor[] []; for (const nameKey of matchedNames) { const name nameKey.replace(capability_index:, ); const descriptors await this.findByName(name); results.push(...descriptors); } return results; } // 意图解析从自然语言提取domain和action private parseIntent(intent: string): { domain: string; action: string } { // 关键词映射表 const domainMap: Recordstring, string { 订单: order, 用户: user, 支付: payment, 商品: product, 库存: inventory, 通知: notification }; const actionMap: Recordstring, string { 查询: query, 创建: create, 更新: update, 删除: delete, 发送: send, 计算: compute }; let domain ; let action ; for (const [cn, en] of Object.entries(domainMap)) { if (intent.includes(cn)) domain en; } for (const [cn, en] of Object.entries(actionMap)) { if (intent.includes(cn)) action en; } // 未匹配到时使用通配符 if (!domain) domain *; if (!action) action *; return { domain, action }; } private async loadDescriptor(name: string, version: string): PromiseCapabilityDescriptor | null { const key capability:${name}:${version}; const data await this.redis.get(key); return data ? JSON.parse(data) : null; } private async publishVersionChange(name: string, version: string, changeType: string): void { await this.redis.publish( capability_changes, JSON.stringify({ name, version, changeType, timestamp: new Date().toISOString() }) ); } } interface RegisterResult { success: boolean; error?: string; descriptor?: CapabilityDescriptor; } interface ValidationResult { valid: boolean; errors?: string[]; }HealthChecker能力健康检查// registry/health-checker.ts import { CapabilityRegistry, CapabilityDescriptor, HealthStatus } from ./capability-registry; class HealthChecker { private checks: Mapstring, HealthCheckConfig new Map(); private intervalHandle: NodeJS.Timer | null null; constructor(private registry: CapabilityRegistry) {} // 注册健康检查配置 registerCheck(descriptor: CapabilityDescriptor): void { const key ${descriptor.name}:${descriptor.version}; this.checks.set(key, { type: descriptor.runtimeType, endpoint: descriptor.healthCheckEndpoint ?? descriptor.endpoint, handlerPath: descriptor.handlerPath, intervalMs: 30000, // 每30秒检查一次 timeoutMs: 5000, // 单次检查超时5秒 consecutiveFailures: 0, maxConsecutiveFailures: 3 // 连续3次失败标记为down }); } // 启动定期健康检查 start(): void { // 为什么用定时器而非事件驱动健康检查是周期性运维任务 // 不依赖外部事件触发 this.intervalHandle setInterval(() this.runAllChecks(), 30000); } stop(): void { if (this.intervalHandle) { clearInterval(this.intervalHandle); } } private async runAllChecks(): void { for (const [key, config] of this.checks) { const [name, version] key.split(:); try { const healthy await this.checkOnce(config); if (healthy) { config.consecutiveFailures 0; await this.registry.updateHealthStatus(name, version, healthy); } else { config.consecutiveFailures; if (config.consecutiveFailures config.maxConsecutiveFailures) { await this.registry.updateHealthStatus(name, version, down); } else { await this.registry.updateHealthStatus(name, version, degraded); } } } catch { // 健康检查自身失败网络中断等标记为degraded而非down // 为什么标记degraded检查失败可能是检查器自身问题不一定代表工具故障 config.consecutiveFailures; await this.registry.updateHealthStatus(name, version, degraded); } } } private async checkOnce(config: HealthCheckConfig): Promiseboolean { switch (config.type) { case http: // HTTP工具发健康检查请求 try { const response await fetch(config.endpoint!, { method: GET, signal: AbortSignal.timeout(config.timeoutMs) }); return response.ok; } catch { return false; } case local: // 本地工具执行空参数校验不实际调用只验证handler可用 try { // 检查handler函数是否可导入 // 为什么只校验导入而非实际执行空参数执行可能产生副作用 const module await import(config.handlerPath!); return typeof module.default function; } catch { return false; } case grpc: // GRPC工具检查连接是否可用 try { // 简化版检查endpoint是否可达 const response await fetch(http://${config.endpoint!.replace(:443, :80)}/health, { signal: AbortSignal.timeout(config.timeoutMs) } ); return response.ok; } catch { return false; } default: return false; } } } interface HealthCheckConfig { type: http | local | grpc; endpoint?: string; handlerPath?: string; intervalMs: number; timeoutMs: number; consecutiveFailures: number; maxConsecutiveFailures: number; }CapabilityVersionManager版本生命周期管理// registry/version-manager.ts class CapabilityVersionManager { private registry: CapabilityRegistry; constructor(registry: CapabilityRegistry) { this.registry registry; } // 自动下线检查每天扫描deprecated能力到期自动下线 // 为什么自动化而非人工操作人工下线容易遗漏过期工具调用失败影响Agent稳定性 async autoOfflineCheck(): PromiseOfflineReport { const report: OfflineReport { offlined: [], pending: [], errors: [] }; // 扫描所有deprecated能力 const allIndexKeys await this.registry.scanIndexKeys(); for (const key of allIndexKeys) { const name key.replace(capability_index:, ); const versions await this.registry.findByName(name); const deprecated versions.filter(v v.versionStatus deprecated); for (const cap of deprecated) { if (!cap.offlineAt) continue; const offlineDate new Date(cap.offlineAt); const now new Date(); if (now offlineDate) { // 到期下线 try { await this.registry.updateVersionStatus( cap.name, cap.version, offline ); report.offlined.push(${cap.name}:${cap.version}); } catch (e: any) { report.errors.push(${cap.name}:${cap.version} - ${e.message}); } } else { const daysLeft Math.ceil( (offlineDate.getTime() - now.getTime()) / 86400000 ); report.pending.push(${cap.name}:${cap.version} - ${daysLeft}天后下线); } } } return report; } // 版本升级辅助创建新版本并标记旧版本deprecated async upgradeVersion( name: string, oldVersion: string, newDescriptor: CapabilityDescriptor, transitionDays: number 30 ): Promisevoid { // 注册新版本 newDescriptor.name name; await this.registry.register(newDescriptor); // 标记旧版本deprecated const offlineDate new Date( Date.now() transitionDays * 86400000 ).toISOString().split(T)[0]; await this.registry.deprecate(name, oldVersion, offlineDate); } } interface OfflineReport { offlined: string[]; pending: string[]; errors: string[]; }边界分析与架构权衡注册中心与Agent编排器的耦合度Agent编排器调用工具前必须查注册中心。注册中心down了Agent无法发现工具整个系统瘫痪。解耦方案本地缓存。编排器定期从注册中心拉取能力列表本地缓存一份。注册中心down时使用缓存数据。代价是缓存数据可能过时工具版本变更、健康状态变化——但过时数据比完全没有数据好。缓存过期策略健康状态每30秒更新缓存5分钟过期。版本变更通过Redis pub/sub实时通知编排器——编排器收到通知后立即更新本地缓存。语义命名的灵活性限制domain.action.target三段式命名覆盖了80%的工具场景。但有些工具不符合这个模式translate_text翻译是action但target是什么语言方向validate_schema校验是action但domain和target都是schema解决方案允许四段式扩展domain.action.target.subtarget但强制前三段必须有意义。第四段可选。language.translate.text.en_to_zh比translate_text更有语义信息。Schema校验的性能开销每次工具调用前都要ajv校验输入参数。复杂schema嵌套对象、数组、条件判断校验耗时可达10ms。高频工具每秒100次调用校验开销1秒。缓解ajv编译schema后缓存validate函数。首次编译耗时约50ms后续调用1ms。编译结果缓存在内存中不重复编译。能力发现与LLM工具选择的分工注册中心负责结构化查询按domain/action/tags过滤。LLM负责语义理解从Agent意图映射到domain/action关键词。两者分工注册中心不理解自然语言只理解结构化关键词。LLM不做参数校验只做意图→关键词的翻译。把语义理解塞进注册中心用LLM做发现查询是过度设计——增加延迟、增加成本、增加故障点。保持注册中心纯结构化LLM负责翻译层。注册中心的权限控制谁可以注册工具谁可以deprecated谁可以下线三级权限开发者注册新能力、deprecated自己注册的能力。管理员deprecated任何能力、下线任何能力。Agent编排器只读权限查询和校验不允许修改注册信息。为什么开发者不能下线别人的能力能力可能有其他Agent在依赖。下线需要管理员确认影响范围。总结能力注册中心把Agent的工具管理从每人写一套变成统一注册、统一发现、统一版本管理。核心设计语义命名domain.action.target消除命名混乱。注册时检测冲突同一语义不允许重复注册。JSON Schema声明输入输出格式。调用前校验参数步骤间数据流自动拼接。版本管理新版本注册旧版本deprecated30天过渡期后自动下线。Agent默认用最新版本。健康检查HTTP工具探活、本地工具检查handler可用性。连续3次失败标记downAgent不选择down的工具。能力发现Agent意图→LLM翻译关键词→注册中心结构化查询→返回候选工具列表。编排器本地缓存解耦注册中心依赖。缓存5分钟过期版本变更实时通知。权限分级开发者注册/deprecated自己的能力管理员全局管控编排器只读。这套机制让Agent的工具生态从混沌态变成治理态——每个能力有名字、有版本、有健康状态、有负责人。工具不再是无文档的黑箱函数而是有完整元数据的微服务。资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0731 资料来源索引并在发布前将具体来源贴到对应断言之后。