1. 传统软件设计中的"抽取公共层"原则
在传统软件开发领域,"抽取公共层"(也称为DRY原则 - Don't Repeat Yourself)被视为核心设计美德。这个原则要求开发者将重复的逻辑、功能或代码抽象出来,形成可复用的公共组件或服务层。
1.1 DRY原则的价值体现
当我们在传统软件系统中实施DRY原则时,通常会获得以下收益:
- 维护成本降低:当业务逻辑需要修改时,只需在一个地方调整,所有使用该逻辑的地方自动更新
- 一致性保障:避免了相同功能在不同地方实现不一致的问题
- 代码简洁性:消除了重复代码,使代码库更精简
- 性能优化集中:可以对公共组件进行针对性优化,所有调用方受益
典型的公共层抽取包括:
- 数据库访问层(DAO)
- 业务逻辑服务层
- 工具函数库
- 通用UI组件
1.2 公共层设计的工程实践
在实际工程中,我们通常会遵循以下模式来设计公共层:
java复制// 传统Java服务层设计示例
public class OrderService {
private OrderRepository orderRepo; // 公共数据访问层
private PaymentService paymentService; // 公共支付服务
public void processOrder(Order order) {
// 使用公共层组件完成业务逻辑
orderRepo.save(order);
paymentService.charge(order);
sendNotification(order); // 可能调用公共通知服务
}
}
这种设计在单体架构或服务边界清晰的微服务架构中表现良好,因为:
- 系统边界和职责划分明确
- 变更影响范围可预测
- 性能特征相对稳定
- 调用链路可追踪
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Agent/Skills架构的范式转变
当我们转向Agent和Skills架构时,整个系统的设计范式发生了根本性变化,这使得传统公共层设计的假设不再成立。
2.1 Agent系统的核心特征
现代Agent/Skills系统通常具有以下特征:
- 动态组合性:Skills在运行时按需组合,而非编译时静态绑定
- 上下文敏感性:同一Skill在不同上下文中的行为可能完全不同
- 非确定性:LLM的参与使得输出具有概率性特征
- 渐进式披露:信息按需加载,而非一次性全部暴露
2.2 技能(Skill)的本质解析
在Agent架构中,Skill不是简单的函数或服务,而是包含多个维度的复合体:
code复制一个完整的Skill通常包含:
1. 元数据(描述、适用场景、输入输出)
2. 执行逻辑(可能是确定性的代码或非确定性的LLM推理)
3. 上下文要求
4. 示例和反例
5. 故障处理模式
这种复杂性使得简单的"抽取公共层"变得困难,因为:
- 所谓的"公共"部分可能只在表面相似,实际需要的上下文完全不同
- 过度抽象会丢失重要的领域特异性信息
- 性能特征难以预测,可能引入意外的瓶颈
3. 公共层在Agent架构中的毒性表现
在实际的Agent/Skills工程实践中,我们观察到过度抽取公共层会导致多种问题。
3.1 上下文污染问题
当多个Skills共享一个公共层时,容易发生上下文污染:
code复制假设我们为"客户支持"和"技术文档"两个Skills抽取了公共的"文本理解层":
1. 客户支持Skill需要理解情绪化、非结构化的用户反馈
2. 技术文档Skill需要精确解析结构化技术内容
当这两个Skills共享同一个文本理解层时:
- 要么客户支持变得过于机械
- 要么技术文档解析变得不够精确
3.2 技能边界模糊
公共层会导致Skills之间的边界模糊,产生两种反模式:
1. 技能粘连(Skill Coupling)
code复制多个Skills通过公共层产生隐式依赖,
修改一个Skill可能意外影响其他Skill
2. 技能贫血(Skill Anemia)
code复制过多的逻辑被下放到公共层,
导致单个Skill变得过于单薄,
失去独立演化的能力
3.3 性能不确定性放大
在传统系统中,公共层通常能提供稳定的性能提升。但在Agent架构中却可能适得其反:
code复制案例:三个Skills共享"数据查询"公共层
[公共数据查询层]
/ | \
[销售分析] [库存管理] [客户洞察]
问题出现:
- 销售分析需要实时响应
- 库存管理需要批量处理
- 客户洞察需要复杂聚合
强行共享同一查询层会导致:
1. 无法为不同场景优化
2. 资源竞争加剧
3. 难以诊断性能问题
4. Agent架构下的设计新原则
基于上述问题,我们需要重新思考Agent/Skills架构的设计原则。
4.1 技能完备性原则
每个Skill应该尽可能自包含:
code复制理想的Skill结构:
[Skill边界]
├── 元数据
├── 执行逻辑
├── 所需上下文
├── 示例库
└── 故障处理
而不是:
[Skill边界]
├── 元数据
└── 公共层调用
4.2 渐进式抽象策略
不同于传统软件的一次性抽象,Agent架构更适合渐进式抽象:
code复制抽象演进路径:
1. 首先实现具体、完整的Skills
2. 观察实际使用中的重复模式
3. 仅在以下条件都满足时抽取公共部分:
- 多个Skills确实有相同的核心需求
- 抽象不会丢失重要上下文
- 性能特征相似
- 变更频率一致
4.3 上下文隔离设计
为每个Skill维护独立的上下文空间:
code复制实现方式:
1. 为每个Skill分配独立的向量存储空间
2. 使用Skill-specific的提示词模板
3. 保持Skill间的最小必要接口
5. 实践中的平衡艺术
完全否定公共层在Agent架构中的价值也是不合理的,关键在于找到合适的平衡点。
5.1 可安全抽象的领域
在Agent系统中,以下方面通常适合抽取公共层:
- 基础设施接入:数据库连接、API客户端等
- 监控和日志:统一的遥测数据收集
- 安全控制:认证授权等横切关注点
- 基础工具集:与业务无关的纯工具函数
5.2 混合架构案例
一个电商客服Agent的合理分层可能如下:
code复制[业务Skills层]
├── 订单查询 (自包含)
├── 退货处理 (自包含)
└── 产品推荐 (自包含)
[有限公共层]
├── 用户认证
├── 订单数据访问
└── 日志监控
[基础设施层]
├── 数据库连接池
└── HTTP客户端
关键区别在于:
- 业务Skills保持高内聚
- 公共层仅包含真正通用的部分
- 各层之间通过精简接口通信
5.3 性能优化技巧
当确实需要共享逻辑时,可以采用以下模式减少负面影响:
1. 适配器模式
code复制每个Skill定义自己的接口适配器,
公共层实现核心逻辑但允许Skill-specific调整
2. 上下文注入
code复制公共操作暴露上下文注入点,
允许Skills传入特定参数和提示词片段
3. 装饰器模式
code复制基础公共操作+Skill-specific装饰逻辑,
而不是硬编码的业务规则
6. 工程实践中的经验教训
在实际项目中,我们总结了以下关键经验:
6.1 过早抽象的代价
在一个客户服务Agent项目中,我们过早抽象了"问题分类"公共层,结果发现:
- 不同渠道(邮件、聊天、语音)需要完全不同的分类策略
- 试图用一个模型满足所有场景导致准确率下降30%
- 最终解耦为渠道特定的分类Skills后效果显著改善
6.2 技能版本化的必要性
当不得不共享某些公共组件时,严格的版本控制至关重要:
code复制实践方案:
1. 每个Skill声明依赖的公共层版本
2. 公共层变更需要兼容旧版本
3. 提供迁移期和自动化测试
6.3 监控与可观测性
在Agent架构中,需要更细致的监控:
code复制关键指标:
1. 每个Skill的独立性能指标
2. 公共层调用来源追踪
3. 上下文传递完整性检查
工具选择上,OpenTelemetry等支持分布式追踪的工具特别有价值。
7. 未来演进方向
Agent架构的设计原则仍在快速演进中,有几个值得关注的方向:
7.1 动态组合技术
新兴的"微技能"(Micro-Skills)概念提倡:
- 更小粒度的技能单元
- 运行时动态组合
- 基于上下文的自动适配
这可能提供比静态公共层更灵活的复用方式。
7.2 分层缓存策略
针对不同层次的共享需求设计差异化缓存:
- 基础设施层:长期缓存
- 有限公共层:短期缓存
- 业务Skills层:基本不缓存
7.3 智能路由架构
Instead of hard-coded abstractions, use learned routing:
code复制[输入] → [路由决策] → [最匹配Skill]
↑
[持续学习路由策略]
这种架构可能从根本上改变我们处理"公共性"的方式。
