企业 Agent 需求如果只写「实现智能问答」「自动处理销售线索」,开发阶段很快会遇到边界问题:输入格式不稳定、工具权限不明确、异常无法复现、验收没有统一标准。
解决方法不是继续补功能列表,而是先建立一份机器可读的场景契约,把业务材料转换成数据结构和验证规则。
1. 场景契约的数据结构
一条场景至少包含六组信息:流程、样例、规则、系统权限、知识来源、验收与恢复。
yaml
schema_version: "1.0"
scene_id: invoice_review
owner: finance_ops
trigger:
event: invoice.uploaded
input:
content_type: application/pdf
sample_dir: fixtures/invoices/
required_fields: [invoice_no, seller, buyer, amount]
output:
schema: InvoiceReviewV1
destination: finance.review_queue
rules:
deterministic: [field_required, amount_range]
model_assisted: [item_category, exception_summary]
permissions:
read: [document_store, vendor_directory]
write: [finance.review_queue]
forbidden: [payment.create, invoice.approve]
knowledge:
sources: [finance_policy, vendor_rules]
version_required: true
human_gate:
- confidence_below_threshold
- vendor_conflict
- amount_above_limit
acceptance:
field_accuracy: 0.98
p95_latency_seconds: 30
recovery:
checkpoint: before_business_write

2. 从契约生成执行链
#mermaid-svg-MpFMk43GrMKpGaAO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MpFMk43GrMKpGaAO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MpFMk43GrMKpGaAO .error-icon{fill:#552222;}#mermaid-svg-MpFMk43GrMKpGaAO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MpFMk43GrMKpGaAO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MpFMk43GrMKpGaAO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MpFMk43GrMKpGaAO .marker.cross{stroke:#333333;}#mermaid-svg-MpFMk43GrMKpGaAO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MpFMk43GrMKpGaAO p{margin:0;}#mermaid-svg-MpFMk43GrMKpGaAO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MpFMk43GrMKpGaAO .cluster-label text{fill:#333;}#mermaid-svg-MpFMk43GrMKpGaAO .cluster-label span{color:#333;}#mermaid-svg-MpFMk43GrMKpGaAO .cluster-label span p{background-color:transparent;}#mermaid-svg-MpFMk43GrMKpGaAO .label text,#mermaid-svg-MpFMk43GrMKpGaAO span{fill:#333;color:#333;}#mermaid-svg-MpFMk43GrMKpGaAO .node rect,#mermaid-svg-MpFMk43GrMKpGaAO .node circle,#mermaid-svg-MpFMk43GrMKpGaAO .node ellipse,#mermaid-svg-MpFMk43GrMKpGaAO .node polygon,#mermaid-svg-MpFMk43GrMKpGaAO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MpFMk43GrMKpGaAO .rough-node .label text,#mermaid-svg-MpFMk43GrMKpGaAO .node .label text,#mermaid-svg-MpFMk43GrMKpGaAO .image-shape .label,#mermaid-svg-MpFMk43GrMKpGaAO .icon-shape .label{text-anchor:middle;}#mermaid-svg-MpFMk43GrMKpGaAO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MpFMk43GrMKpGaAO .rough-node .label,#mermaid-svg-MpFMk43GrMKpGaAO .node .label,#mermaid-svg-MpFMk43GrMKpGaAO .image-shape .label,#mermaid-svg-MpFMk43GrMKpGaAO .icon-shape .label{text-align:center;}#mermaid-svg-MpFMk43GrMKpGaAO .node.clickable{cursor:pointer;}#mermaid-svg-MpFMk43GrMKpGaAO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MpFMk43GrMKpGaAO .arrowheadPath{fill:#333333;}#mermaid-svg-MpFMk43GrMKpGaAO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MpFMk43GrMKpGaAO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MpFMk43GrMKpGaAO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MpFMk43GrMKpGaAO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MpFMk43GrMKpGaAO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MpFMk43GrMKpGaAO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MpFMk43GrMKpGaAO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MpFMk43GrMKpGaAO .cluster text{fill:#333;}#mermaid-svg-MpFMk43GrMKpGaAO .cluster span{color:#333;}#mermaid-svg-MpFMk43GrMKpGaAO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MpFMk43GrMKpGaAO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MpFMk43GrMKpGaAO rect.text{fill:none;stroke-width:0;}#mermaid-svg-MpFMk43GrMKpGaAO .icon-shape,#mermaid-svg-MpFMk43GrMKpGaAO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MpFMk43GrMKpGaAO .icon-shape p,#mermaid-svg-MpFMk43GrMKpGaAO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MpFMk43GrMKpGaAO .icon-shape .label rect,#mermaid-svg-MpFMk43GrMKpGaAO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MpFMk43GrMKpGaAO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MpFMk43GrMKpGaAO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MpFMk43GrMKpGaAO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拒绝
通过
通过
异常
业务事件
Schema 校验
权限预检
人工处理
规则执行
知识检索
Agent 计划
Skill Gateway
结果验证
业务写入
审计与恢复
确定性规则放在模型之前,减少状态数量。Agent 只负责非结构化理解与动作选择,Skill Gateway 负责参数、权限、超时和幂等。
3. Python 契约校验
python
from dataclasses import dataclass
@dataclass(frozen=True)
class Contract:
scene_id: str
owner: str
sample_count: int
output_schema: str
read_permissions: tuple[str, ...]
write_permissions: tuple[str, ...]
forbidden_actions: tuple[str, ...]
human_gates: tuple[str, ...]
acceptance_metrics: tuple[str, ...]
recovery_checkpoint: str
def validate_contract(contract: Contract) -> list[str]:
errors: list[str] = []
required_text = {
"scene_id": contract.scene_id,
"owner": contract.owner,
"output_schema": contract.output_schema,
"recovery_checkpoint": contract.recovery_checkpoint,
}
for field, value in required_text.items():
if not value.strip():
errors.append(f"{field} is required")
if contract.sample_count < 3:
errors.append("at least 3 sanitized samples are required")
if not contract.write_permissions:
errors.append("write permission scope is required")
if not contract.forbidden_actions:
errors.append("forbidden action list is required")
if not contract.human_gates:
errors.append("human takeover conditions are required")
if not contract.acceptance_metrics:
errors.append("acceptance metrics are required")
overlap = set(contract.write_permissions) & set(contract.forbidden_actions)
if overlap:
errors.append(f"permission conflict: {sorted(overlap)}")
return errors
契约校验通过只代表材料齐全,不代表允许上线。风险评审和真实样例测试仍然是独立步骤。
4. 用数据库保存幂等动作
sql
CREATE TABLE agent_action (
id INTEGER PRIMARY KEY,
request_id TEXT NOT NULL,
action_name TEXT NOT NULL,
target_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL UNIQUE,
input_hash TEXT NOT NULL,
status TEXT NOT NULL,
external_request_id TEXT,
result_json TEXT,
updated_at TEXT NOT NULL
);
幂等键应由请求、动作和目标共同生成。同一个动作发生超时时,先通过外部请求 ID 查询真实结果,再决定是否重试。
5. 状态机必须可恢复
text
RECEIVED -> VALIDATED -> PLANNED -> EXECUTING -> VERIFYING -> SUCCEEDED
| | | |
+--------> HUMAN_REQUIRED <----------+
|
+-> RESUMING -> EXECUTING
+-> CANCELLED

状态写入持久化存储后再执行外部动作。进程重启时读取当前状态、外部请求 ID 和幂等键,不能依赖内存变量判断是否继续。
6. 验收测试清单
| 场景 | 预期结果 |
|---|---|
| 输入缺字段 | Schema 校验拒绝,不调用模型 |
| 权限不足 | 策略层阻断,不进入工具执行 |
| 相同请求重复到达 | 唯一幂等键阻止第二次写入 |
| 外部接口超时但实际成功 | 查询外部状态,不重复调用 |
| 回调乱序 | 暂存事件,等待合法状态 |
| 进程崩溃 | 从持久化检查点恢复 |
| 人工接管 | 自动 Worker 暂停同对象写权限 |
| Skill 或知识版本升级 | 旧结果仍能按原版本追溯 |
需求阶段把这些失败路径写清楚,可以减少上线后才增加临时状态和补丁的情况。
本文由天远大数据根据企业 Agent 需求评估与工程实践整理。