DeepSeek Harness 系列(四):接入真实业务的工具化方法
Agent 进入企业系统时,最危险的做法是把数据库、HTTP 接口和内部文档一股脑交给模型,让模型“自己判断”。这不是智能,是把业务边界挖穿。
DeepSeek Harness 更适合的接入方式是:把业务能力包装成插件和工具,让模型只能通过受约束的能力完成任务。工具参数要清楚,输出要结构化,写操作要经过审批,结果要进入会话日志,审计要能复盘。
本文用一个经过匿名化处理的真实业务模式说明这件事:业务系统会创建 AI 审核任务,正常路径依赖外部 AI 回调写入结果;当外部结果已经产生但回调没有触达业务系统时,业务系统内任务可能长期停留在运行中状态。下面不会展开某个内部系统的实现细节,而是抽出适合迁移到 Harness 的架构模式。
业务背景
这个业务的核心问题是:业务会签系统会创建 AI 审核任务,正常情况下由外部 AI 回调写入结果。如果 AI 侧已经有结果,但推送或回调没有触达业务系统,业务系统内任务就可能长期停留在 RUNNING。
现有业务实现提供了两类能力。
| 能力 | 已有业务入口 |
|---|---|
| 运行中任务补偿 | 业务系统提供受控的补偿调度接口 |
| 异常巡检查询 | 查询失败任务和超时运行中的当前 AI 任务 |
在这个匿名化模式里,补偿接口会调用业务服务层的“补偿运行中任务”能力。服务层会扫描状态为 RUNNING 的当前 AI 任务,主动查询外部 AI 状态,并在任务进入终态后复用正常回调路径保存任务结果与规则结果。只有任务仍是当前有效任务,并且成功结果完整,系统才会触发质量字段覆盖、风险通知等后续逻辑。
这个业务链路很适合拿来讲 Agent 接入,因为它同时包含只读查询、写操作、幂等、当前/历史任务判断、外部 AI 状态、业务尾处理和运维可观测性。
不要让 Agent 直接碰表
如果把这个能力接给 Agent,最直接但不推荐的做法是给它一个 SQL 工具,然后提示它:
帮我查一下 AI 失败和超时任务,如果能补偿就调用接口。这个方式的问题很明显:
- 模型可能改写 SQL 条件,误把历史任务当当前异常。
- 模型可能绕开正常回调保存路径,直接更新任务表或质量字段。
- 模型可能重复触发补偿,造成通知或状态处理异常。
- 审计日志很难回答“这次补偿为什么执行、查到了什么、改了什么”。
更好的做法是把业务系统已有边界保留下来。
Agent 只接触两个工具,不接触表结构细节。工具背后仍然走业务已有接口和服务层。这样,模型可以参与排查和决策,但业务规则仍由业务系统执行。
把真实业务拆成 Harness 能力
这个场景至少可以拆成三个工具。
| 工具 | 类型 | 作用 | 风险等级 |
|---|---|---|---|
query_ai_task_anomalies | 只读 | 查询失败和超时的当前 AI 任务。 | 低 |
preview_ai_task_compensation | 只读 | 预览本次最多会处理多少运行中任务。 | 中 |
compensate_ai_tasks | 写操作 | 调用补偿接口,触发业务服务层。 | 高 |
低风险查询可以直接执行。高风险补偿必须走审批、限流和审计,并且最好要求明确的 batchSize、业务说明和操作者意图。
这个流程把“智能判断”和“业务动作”分开了。Agent 可以整理异常、解释风险、建议下一步;真正改变状态的动作,必须穿过工具流水线。
工具定义示意
下面是一个 Harness 工具插件的示意写法。它不是可以直接复制运行的完整包,而是展示边界应该怎么设计。
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'ai-task-risk-control-tools'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'query_ai_task_anomalies',
description: 'Query AI audit tasks that are failed or running longer than the configured threshold.',
parameters: {
limit: {
type: 'integer',
description: 'Maximum number of anomaly rows to return.',
},
},
output: {
schema: {
type: 'object',
additionalProperties: false,
properties: {
total: { type: 'integer' },
rows: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
properties: {
businessObjectId: { type: 'string' },
aiTaskId: { type: 'string' },
ownerName: { type: 'string' },
anomalyType: { type: 'string' },
occurredAt: { type: 'string' },
},
},
},
},
},
render: (_args, value) => [{
type: 'text',
text: `Found ${value.total} AI task anomaly record(s).`,
}],
},
async execute(args, exec) {
return queryAiTaskAnomalies({
limit: args.limit ?? 20,
signal: exec.signal,
})
},
}))
}这个工具有几个设计点:
- 参数是受控的,只有
limit,不暴露任意 SQL。 - 输出是结构化 JSON,模型不用从自然语言里解析 id。
execute()接收exec.signal,可以响应取消。render()只负责模型可见摘要,完整结构化值仍给程序化消费方使用。
写操作工具也类似,但要更保守。
ctx.tools.register(defineTool({
name: 'compensate_ai_tasks',
description: 'Run the approved AI task compensation endpoint.',
parameters: {
batchSize: {
type: 'integer',
required: true,
description: 'Number of RUNNING tasks to compensate. Must be between 1 and 500.',
},
reason: {
type: 'string',
required: true,
description: 'Operator-visible reason for this compensation run.',
},
},
output: {
schema: {
type: 'object',
additionalProperties: false,
properties: {
success: { type: 'boolean' },
message: { type: 'string' },
totalCount: { type: 'integer' },
succeededStatusCount: { type: 'integer' },
failedStatusCount: { type: 'integer' },
updatedCount: { type: 'integer' },
skippedCount: { type: 'integer' },
errorCount: { type: 'integer' },
},
},
render: (_args, value) => [{
type: 'text',
text: value.message,
}],
},
async execute(args, exec) {
if (args.batchSize < 1 || args.batchSize > 500) {
throw new Error('batchSize must be between 1 and 500.')
}
return callAiTaskCompensationEndpoint({
batchSize: args.batchSize,
reason: args.reason,
signal: exec.signal,
})
},
}))注意这里仍然没有让工具直接更新表。它调用的是现有业务接口。原因很简单:现有业务接口已经知道怎么复用正常回调保存路径,怎么判断当前任务,怎么处理终态和失败,怎么保持和正常回调路径一致。
用工具流水线承载审批
补偿工具属于写操作,不能只靠提示词提醒模型谨慎。应该把审批做成工具策略。
策略插件应该看工具名、参数、会话来源和当前权限模式,再决定是否 allow、deny 或 ask。这样审批不是写在某个业务工具里的孤岛,而是所有高风险工具共享的制度。
补偿类工具还建议增加几条守卫:
| 守卫 | 原因 |
|---|---|
batchSize 上限 | 防止一次补偿影响过大。业务接口和工具侧都应设置最大处理量。 |
必填 reason | 让会话日志里留下操作者意图,便于审计。 |
| 禁止并发补偿 | 业务服务层应保持互斥,工具侧仍应避免模型连续发起多次。 |
| 只允许指定环境 | 避免测试会话误打生产接口。 |
| 结果脱敏 | 不把敏感用户、合同正文或内部链接过度暴露给模型。 |
让 Agent 输出有用结果
业务工具接上后,不要让 Agent 只返回“调用成功”。更有用的输出应该包括:
- 发现了多少失败和超时任务。
- 哪些是当前任务,哪些只是历史留痕。
- 本次补偿读取了多少运行中任务。
- AI 侧返回了多少成功终态和失败终态。
- 更新了多少任务,跳过了多少任务,异常多少条。
- 哪些残余风险需要人工或测试库验证。
这正好对应补偿接口应该返回的统计维度:总扫描数、成功终态数、失败终态数、已更新数、已跳过数、异常数,以及是否因为已有补偿运行而跳过。
这类输出能真正帮人工作。Agent 不只是执行器,它把业务状态、工具结果和残余风险组织成可读判断。
真实业务里的边界设计
把这个案例迁移到 Harness 时,建议保留以下边界。
| 边界 | 保留方式 |
|---|---|
| 当前任务判断 | 仍由业务系统根据当前 AI 任务标识判断。 |
| 任务状态枚举 | 工具只接受业务已有的 RUNNING、SUCCEEDED、FAILED。 |
| 成功结果完整性 | 只有整体结论和规则结果完整时才允许后续逻辑。 |
| 失败处理 | FAILED 只保存失败状态和原因,不触发质量字段覆盖。 |
| 异常巡检 | SQL 或只读 API 只查询当前任务,排除已归档、作废、终止对象。 |
| 幂等与互斥 | 业务服务层保持互斥,工具侧避免连续高风险调用。 |
| 审计 | 工具参数、审批原因和结果统计进入 Harness 会话日志。 |
这些边界的共同点是:Agent 参与的是编排和解释,不取代业务系统的权威判断。
一个更完整的接入架构
如果要在企业内部试点,可以按下面的架构推进。
这里最好加一层内部 API 网关,而不是让 Harness 插件直接连生产库。网关可以承担认证、环境隔离、频率限制和审计编号;Harness 负责 Agent 侧的工具参数、审批和会话日志。
接入 Checklist
落地前可以用这份清单自检。
| 检查项 | 通过标准 |
|---|---|
| 工具是否结构化 | 参数和输出都有 schema,不依赖自然语言解析 id。 |
| 是否避免任意 SQL | Agent 不能直接拼接业务 SQL。 |
| 写操作是否审批 | 高风险工具经过 tools/pre-execute 审批或拒绝。 |
| 是否复用业务入口 | 写状态仍走现有服务层/API,不绕过业务规则。 |
| 是否有幂等保护 | 业务侧和工具侧都能处理重复调用。 |
| 是否记录意图 | 写操作要求 reason,并进入会话日志。 |
| 是否能回放 | 工具结果能从 tool/result 解释本次发生了什么。 |
| 是否脱敏 | 合同正文、用户信息、内部链接按最小必要输出。 |
| 是否区分环境 | 测试、预发、生产 endpoint 不靠模型判断。 |
小结
DeepSeek Harness 接企业业务,不应该从“让模型拥有更多权限”开始,而应该从“把业务能力变成更清晰的工具边界”开始。
这个匿名化案例说明了一条比较稳的路线:业务系统继续拥有任务状态、当前任务判断、结果保存和尾处理;Harness 负责把异常巡检、补偿执行、审批审计和结果解释组合成 Agent 工作流。这样做不会削弱业务规则,反而能让 Agent 的每一步都落在可追踪、可审批、可复盘的工程边界里。
系列导航:上一篇:从源码运行 Web UI。
