能力的三层拆分
解析生产级 Agent 架构的三层设计哲学:提示策略层、确定性工具层与受限沙箱执行层的职责边界与解耦方案。
在传统的单体 AI 脚本中,开发者往往直接在大模型工具函数(Tool)内部编写具体的操作系统命令或网络请求。例如在 execute_sql 工具中直接引入 pg 客户端并执行查询。这种设计一旦面对多环境切换(如云端 PostgreSQL 与本地 SQLite)、Mock 测试或安全沙箱隔离时,就会导致严重的强耦合问题。
DeepSeek Harness 确立了业界领先的 “三层拆分架构(Three-Role Capability Design)”:通过将智能体的每一个能力解耦为 Seam(抽象缝隙)、Provider(底层实现) 与 Tool(模型工具) 三个独立的 npm 包,实现了极致的可插拔性与工程健壮度。
本文将以一个 “智能体数据库操作能力(Database Capability)” 为例,深度剖析三层拆分设计模式的实战实现。
架构透视:三层拆分的核心分工与拓扑
┌─────────────────────────────────────────────────────────────┐
│ DeepSeek 三层拆分架构拓扑 │
├─────────────────────────────────────────────────────────────┤
│ 第 3 层:Tool (面向大模型) │
│ • 包名:dsh-tool-sql │
│ • 职责:导出 defineTool、参数 Schema 校验、返回结果渲染 │
│ • 依赖:只依赖 Seam 抽象,绝对不引入具体数据库驱动 │
├──────────────────────────────▲──────────────────────────────┤
│ │ 消费 ctx.dbDriver 服务 │
├──────────────────────────────┴──────────────────────────────┤
│ 第 1 层:Seam (抽象扩展缝隙) │
│ • 包名:dsh-database-seam │
│ • 职责:定义 IDatabaseDriver 接口与 declare module 服务契约│
│ • 依赖:纯 TypeScript 类型与抽象类,零外部依赖 │
├──────────────────────────────▲──────────────────────────────┤
│ │ 实现并挂载 ctx.dbDriver │
├──────────────────────────────┴──────────────────────────────┤
│ 第 2 层:Provider (底层物理实现) │
│ • 包名:dsh-database-postgres (或 dsh-database-sqlite) │
│ • 职责:引入底层驱动,实现真实 SQL 执行与连接池生命周期管理│
└─────────────────────────────────────────────────────────────┘
步骤一:编写 Seam 抽象层 (dsh-database-seam)
Seam 层是连接 Tool 与 Provider 的唯一契约。它绝不包含具体实现代码,只包含接口定义与微内核服务的类型挂载:
// packages/dsh-database-seam/src/index.ts
import { Service, type Context } from '@deepseek-ai/cordis';
// 1. 核心业务接口契约
export interface QueryResult {
columns: string[];
rows: Record<string, any>[];
rowCount: number;
}
export interface IDatabaseDriver {
query(sql: string, params?: any[]): Promise<QueryResult>;
ping(): Promise<boolean>;
}
// 2. TypeScript 模块声明合并
declare module '@deepseek-ai/cordis' {
interface Context {
dbDriver: IDatabaseDriver;
}
}
// 3. 导出抽象基类
export abstract class DatabaseDriverSeam extends Service implements IDatabaseDriver {
constructor(ctx: Context) {
super(ctx, 'dbDriver');
}
abstract query(sql: string, params?: any[]): Promise<QueryResult>;
abstract ping(): Promise<boolean>;
}
步骤二:编写 Provider 实现层 (dsh-database-postgres)
Provider 层负责实现 Seam 中定义的接口,并引入底层的重量级依赖(如 pg 客户端):
// packages/dsh-database-postgres/src/index.ts
import type { Context } from '@deepseek-ai/cordis';
import { DatabaseDriverSeam, type QueryResult } from 'dsh-database-seam';
export const name = 'postgres-database-provider';
class PostgresDriverImpl extends DatabaseDriverSeam {
// 模拟真实数据库连接池
async query(sql: string, params: any[] = []): Promise<QueryResult> {
console.log(`[Postgres] 执行 SQL 查询: ${sql}`);
return {
columns: ['id', 'user_name', 'status'],
rows: [
{ id: 1, user_name: 'Alice', status: 'active' },
{ id: 2, user_name: 'Bob', status: 'pending' }
],
rowCount: 2
};
}
async ping(): Promise<boolean> {
return true;
}
}
export function apply(ctx: Context) {
// 挂载 Provider 实例
ctx.plugin(PostgresDriverImpl);
}
步骤三:编写 Tool 工具层 (dsh-tool-sql)
Tool 层面向大模型。它通过 inject: ['dbDriver'] 注入抽象服务,并通过 output.render 将查询结果格式化为 Markdown 表格:
// packages/dsh-tool-sql/src/index.ts
import type { Context } from '@deepseek-ai/cordis';
import { defineTool } from '@deepseek-ai/dsh-tools';
import 'dsh-database-seam'; // 引入 Seam 提供的类型支持
export const name = 'sql-query-tool';
export const inject = ['tools', 'dbDriver'];
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'execute_sql_query',
description: '执行只读 SQL 查询,并返回格式化的数据行。用于检索数据库元数据或业务指标。',
parameters: {
sql: {
type: 'string',
required: true,
description: '待执行的标准 SELECT SQL 语句'
}
},
output: {
schema: { type: 'object' },
render: (_args, result) => [
{
type: 'text',
text: `[SQL 执行结果 (共 ${result.rowCount} 条)]
` +
`| ${result.columns.join(' | ')} |
` +
`| ${result.columns.map(() => '---').join(' | ')} |
` +
result.rows.map((r: any) => `| ${Object.values(r).join(' | ')} |`).join('
')
}
]
},
async execute(args) {
// 直接调用 ctx.dbDriver,完全不需要知道底层是 Postgres 还是 SQLite
return await ctx.dbDriver.query(args.sql);
}
}));
}
三层拆分架构的三大核心工程收益
┌─────────────────────────────────────────────────────────────┐
│ 三层拆分核心工程价值 │
├─────────────────────┬───────────────────────────────────────┤
│ 1. Provider 自由置换│ 本地测试使用 SQLite Provider,生产环境 │
│ │ 无缝切为 Postgres Provider,Tool 零改动│
├─────────────────────┼───────────────────────────────────────┤
│ 2. 依赖彻底解耦 │ Tool 客户端包只有几 KB,无需安装重量级│
│ │ C++ 原生驱动二进制包 │
├─────────────────────┼───────────────────────────────────────┤
│ 3. 独立版本迭代 │ 底层驱动版本升级不影响大模型的 Tool │
│ │ 提示词与 Schema 契约 │
└─────────────────────┴───────────────────────────────────────┘
常见问题解答 (FAQ)
Q1: 为什么不能直接将 Provider 与 Tool 写在同一个 npm 包中?
解答:如果将两者揉合,当需要在 WebAssembly、Docker 容器或轻量端侧切换驱动时,整个 Tool 也必须重写;同时还会将繁重的底层驱动依赖强行引入到不需要该驱动的环境中。
Q2: 如果当前运行环境中只挂载了 Tool,忘记挂载 Provider 会发生什么?
解答:Tool 声明了 inject = ['dbDriver']。由于缺少提供 dbDriver 服务的 Provider,Tool 会自动停留在 PENDING 挂起状态,绝不会在运行时因访问未定义的属性而抛出崩溃异常。
Q3: 官方仓库中的 Bash 工具是如何遵循三层拆分的?
解答:官方将 Bash 能力拆分为:dsh-shell(Seam 接口契约)、dsh-bash-local(Provider 本地进程实现)、dsh-tool-bash(Tool 大模型交互入口)。这也是 Harness 体系最标准的基准范式。
Q4: 如何为同一个 Seam 同时注册多个不同名称的 Provider 实例?
解答:可以在 Seam 中设计注册中心模式(如 ctx.dbManager.registerDriver('prod', driver)),并在 cordis.yml 中配置路由选择策略。