Deep Agents 13. 构建可复核的企业数据分析智能体


前十二篇文章分别介绍了 Tool、Backend、Sandbox、Skills、Memory、SubAgent、Middleware、Rubric 和人工审批。本篇不再孤立演示某个 API,而是把这些能力组合成一个可以运行、复核和测试的销售数据分析流水线。

这里的“企业级”不是说几百行示例已经具备完整生产能力,而是指项目采用了企业系统应有的控制思路:模型不负责金额计算,数据和报告都有版本证据,语义修订后必须重新审核,外部发布必须经过人工批准。本项目仍是参考实现,不包含真实 IAM、不可变对象存储、集中审计平台、数据仓库和灾备系统。

本文确定性测试环境为 Python 3.12、deepagents==0.6.12。本地流水线和测试已经实际执行;Daytona 集成代码因测试环境没有 DAYTONA_API_KEY 而条件跳过;在线模型结果来自 OpenAI 兼容 Provider 测试。三类证据会分别标注,不把“代码存在”“离线组件通过”和“云端已经运行”混为一谈。

1. 项目目标与可信架构

1.1 从“生成报告”改成“交付可复核对象”

输入是一份小型销售 CSV,最终输出却不只是一篇 Markdown。完整产物包括:

output/
├── summary.json
├── report.md
├── review.json
├── review-final.json
├── audit.jsonl
└── published/
    └── approval-p13-001/
        ├── report.md
        ├── report.md.sha256
        └── manifest.json

这些文件承担不同职责:

产物 职责 正式 Gate 是否直接消费
summary.json 保存确定性指标、Schema 版本、分析器版本和源数据哈希
report.md 给人阅读的报告,同时嵌入机器可读 Evidence Block
review.json Rubric 前的初次确定性审核
review-final.json Rubric 修订后的最终确定性审核
audit.jsonl 教学用结构化事件和哈希链 否,不能替代不可变审计存储
manifest.json 连接源数据、摘要、最终审核、Rubric、审批和发布物

项目的非目标同样重要:它不尝试处理任意 CSV Schema、多币种折算、税额与退款、海量数据计算或真正的多租户权限。示例只处理单币种、已入账订单;汇率日、汇率来源和折算规则应在后续 Schema 版本中单独定义。

1.2 模型、Agent 和确定性程序的分工

系统遵循一个原则:

模型负责规划、选择、解释和语义修订;确定性程序负责校验、计算、对账、对象关联和副作用约束。

工作 主要执行者 为什么这样分配
拆解任务、选择专用 Agent Supervisor / LLM 任务表达和路由具有开放性
CSV 契约校验、金额计算 可信 Python 结果必须可重复且精确
报告解释和文字修订 LLM 语义质量难以完全规则化
核心指标复算和报告对账 Reviewer 程序 正式数值 Gate 不能依赖模型判断
是否允许外部发布 人工审批 这是业务授权,不是语言生成
复制发布物、生成 Manifest 受控 Tool 副作用必须受路径、哈希和幂等约束

write_todos 能让计划进入 Agent State 和事件流,但它是计划与 UI 工具,不是控制流 Gate。真正阻断流程的是 Python 条件、Reviewer 结果、Rubric 终态、HITL Interrupt 和发布 Tool 内的强制校验。

1.3 系统架构与执行边界

企业数据分析智能体架构

最容易混淆的是“SubAgent 在哪里运行”。本项目的 CompiledSubAgent 是 LangGraph 图,Node 中调用的是宿主机 Python 函数和本地子进程。StateBackend 只为 Deep Agents 文件工具提供虚拟文件视图,并不会自动拦截任意 Path.read_text()、subprocess.run() 或第三方 SDK 调用。因此:

  • analyst_node、reviewer_node 和本地 Skill CLI 属于 Host Trusted Python;
  • read_file、write_file 等 Agent 文件工具才经过 Backend;
  • 可选 run_daytona() 把脚本上传到 Daytona 的 /workspace 后执行;
  • 模型看到的 Tool Plane 不等于进程隔离边界;
  • Approval、Artifact Store 和 Audit Store 在生产中应是独立外部服务。

1.4 正确的质量门顺序

质量门与人工发布控制

本项目固定以下顺序:

Analyst
  -> Reviewer 1
  -> Rubric Evaluation / Revision
  -> Reviewer 2
  -> Approved Artifact Hash
  -> HITL
  -> Atomic Publish + Manifest

Rubric 会修改报告字节,所以不能拿修订前哈希批准修订后的文件。流水线实测中,初始报告与最终报告的 SHA-256 分别为:

initial_report_sha256 = cf34fff8e34fa506b021055a2faa741d8f13f8ab144f28b85d515cffc9915295
approved_report_sha256 = 86d63b2c58894464b6dd5dc604842663da7ae8ac76fedf258dd42bf47d8176a5

二者不同是预期行为。只有 Rubric 后的 Reviewer 2 通过,系统才计算 approved_report_sha256 并把它送入 HITL Proposal。

2. 数据契约与确定性计算

2.1 CSV 契约先于模型分析

示例数据为:

order_id,order_date,region,revenue,cost,currency,status
A001,2026-06-01,华东,120.00,75.00,CNY,booked
A002,2026-06-15,华南,80.00,50.00,CNY,booked
A003,2026-06-30,华北,100.00,65.00,CNY,booked

analyze.py 在读取业务行之前先检查文件级约束:文件不超过 5 MiB、最多 100000 行、UTF-8 且无 BOM、没有重复表头、必需列完整。随后逐行检查订单 ID 唯一、日期为 ISO 格式、文本非空且不超过 256 字符、金额非负有限、币种唯一、状态只能是 booked。

文本字段拒绝以 =+-@ 开头,数值字段拒绝公式前缀。它不能解决所有 CSV 注入问题,但可以避免示例产物被电子表格打开时把常见危险前缀解释为公式。生产系统还应在导出层执行与目标表格软件匹配的转义策略。

2.2 Decimal、精度和舍入策略

“使用 Decimal”仍不够,必须声明输入精度、聚合顺序和舍入方式。本项目的规则是:

  1. 输入金额最多两位小数,更多小数直接拒绝,不做静默截断;
  2. 每行合法金额量化为 0.01;
  3. 总收入和总成本对已经量化的行金额求和;
  4. 利润等于最终收入减最终成本;
  5. 比例量化为 0.0001;
  6. 全部使用 ROUND_HALF_UP。

对应代码是:

from decimal import ROUND_HALF_UP, Decimal

MONEY = Decimal("0.01")
RATIO = Decimal("0.0001")


def _money(value: Decimal) -> Decimal:
    return value.quantize(MONEY, rounding=ROUND_HALF_UP)


def _margin(profit: Decimal, revenue: Decimal) -> Decimal:
    if revenue == 0:
        raise DataContractError("total revenue must be greater than zero")
    return (profit / revenue).quantize(RATIO, rounding=ROUND_HALF_UP)

因此 110 / 300 的内部结果是 0.3667,展示为 36.67%。代码、Fixture 和在线 Prompt 都使用同一精度,不再出现 0.3666666667 与 0.3667 混用。

2.3 稳定对象、JSON Schema 与版本

SalesSummary 同时记录数据契约版本和分析器版本:

@dataclass(frozen=True)
class SalesSummary:
    schema_version: str
    analyzer_version: str
    rows: int
    period_start: date
    period_end: date
    currency: str
    revenue: Decimal
    cost: Decimal
    profit: Decimal
    margin: Decimal
    regions: tuple[RegionSummary, ...]
    source_sha256: str

落盘时 Decimal 序列化成字符串,并由 schemas/sales-summary-v1.schema.json 约束。金额正则要求两位小数,比例要求四位小数;schema_version 固定为 sales-summary/v1,analyzer_version 固定为 p13-analyzer/2.0。JSON Schema 验证的是传输结构,Dataclass 和业务函数验证的是 Python 类型与业务不变量,两者不能互相替代。

实测摘要核心内容:

{
  "schema_version": "sales-summary/v1",
  "analyzer_version": "p13-analyzer/2.0",
  "rows": 3,
  "currency": "CNY",
  "revenue": "300.00",
  "cost": "190.00",
  "profit": "110.00",
  "margin": "0.3667",
  "source_sha256": "7ea97537f761d1bbf4c2d2fff171027aac215c41123a0ad03a5c3fe6d277a31f"
}

2.4 对账、输入哈希和报告 Evidence Block

分析器分别计算总体与区域结果,并强制断言区域收入、成本之和等于总体值。源文件 SHA-256 标识参与计算的字节对象,但哈希本身不证明业务数据正确,也不会自动形成证据链。要可复核,还必须记录每个阶段消费了哪个哈希,并校验阶段之间的引用关系。

报告既有面向人的指标,也包含边界明确的机器可读 JSON:

<!-- p13-evidence:start -->
```json
{"analyzer_version":"p13-analyzer/2.0","cost":"190.00","currency":"CNY","margin":"0.3667","profit":"110.00","revenue":"300.00","rows":3,"schema_version":"sales-summary/v1","source_sha256":"7ea97537f761d1bbf4c2d2fff171027aac215c41123a0ad03a5c3fe6d277a31f"}
```
<!-- p13-evidence:end -->

Reviewer 同时检查可读指标行和 Evidence Block。这样既能发现正文数字被篡改,也能发现隐藏证据被替换。这里刻意没有把 Markdown 当作唯一数据库,summary.json 仍是结构化事实对象。

从数据到发布清单的可复核流水线

3. Skill、Analyst 与 Reviewer

3.1 Agent Skill 是可执行工作流

分析能力按 Agent Skills 目录组织:

skills/sales-summary/
├── SKILL.md
├── scripts/
│   └── analyze_sales.py
└── references/
    └── data-contract.md

SKILL.md 描述何时使用 Skill、按什么顺序读取数据契约和执行脚本,以及哪些数字禁止模型自由改写。真正决定计算结果的是可测试脚本,而不是“请认真计算”这类 Prompt。

脚本暴露稳定 CLI:

python scripts/analyze_sales.py \
  --input /workspace/input/sales.csv \
  --summary /workspace/output/summary.json \
  --report /workspace/output/report.md

本地运行时路径不是 /workspace,执行器传入宿主机绝对路径;Daytona 路径才位于 /workspace。同一 CLI 在两个文件空间工作,但应用层负责映射路径。

3.2 本地子进程不继承模型密钥

本地执行器通过当前锁定解释器启动真实 Skill CLI,并且只传递白名单环境变量:

def build_child_env(package_parent: Path) -> dict[str, str]:
    return {
        "PATH": os.environ.get("PATH", ""),
        "PYTHONPATH": str(package_parent),
        "LANG": os.environ.get("LANG", "C.UTF-8"),
        "LC_ALL": os.environ.get("LC_ALL", "C.UTF-8"),
    }

不能使用 {**os.environ, …} 把 DEEPAGENT_API_KEY、DAYTONA_API_KEY 或云凭据一起交给不需要网络的分析脚本。测试明确断言常见 API Key 不会进入子进程环境。

PYTHONPATH 必须显式指向 examples/ 包根目录,否则从深层 Skill 目录执行脚本会得到 ModuleNotFoundError。这属于运行契约,不应依赖 IDE 帮忙注入路径。

3.3 Analyst SubAgent 与 task Tool 的证据边界

项目使用 SubAgentMiddleware 注册 sales-analyst 和 report-reviewer 两个 CompiledSubAgent,并调用中间件生成的真实 task Tool。Tool Schema 暴露 description 与 subagent_type,共享 State 由运行时传递。

这可以证明:

  • Deep Agents 的 task Tool 契约实际可用;
  • Command Update 能把子图结果合并回父 State;
  • Analyst 和 Reviewer 的上下文可以分离;
  • private_state_keys 能阻止指定父 State 字段复制给 SubAgent。

但直接调用 Tool 或使用 ScriptedChatModel 不能证明在线模型“自主决定先规划、再选择 Analyst、再选择 Reviewer”。自主路由必须由独立在线行为测试观察 Tool Call 序列。本文把两种结论分开:默认套件验证组件和控制流契约,在线套件验证概率性的模型行为。

3.4 Private State 不是完整权限系统

父 State 中的 tenant_id 和 approval_token 被标记为私有后,示例断言 SubAgent 看不到对应值。但隔离范围只覆盖父子 State 传播,不包括:

通道 private_state_keys 能否保护
父 State 指定字段
Runtime Context 不能据此推断
Backend / Store 中的文件与记录 不能
Host 环境变量 不能
外部 API、数据库和 Secret Store 不能

因此还要最小化 Tool、Backend 路由、运行身份和网络权限。本项目通过子进程环境白名单减少一种泄露面,但它不是通用权限方案。

3.5 Reviewer 的独立程度

Reviewer 读取原始 CSV、summary.json 和 report.md。核心收入、成本、利润和行数由 _independent_source_evidence() 使用独立的 csv.DictReader + Decimal 逻辑重算,不再调用 Analyst 的 analyze()。它还检查:

  1. 源文件哈希与摘要引用一致;
  2. 报告四个指标行与摘要完全一致;
  3. Evidence Block 与摘要一致;
  4. 五个必需章节齐全;
  5. 区域收入和成本与总体对账。

review-final.json 为每项检查保存 name、expected、actual 和 passed,比只保存一个字符串列表更适合定位差异。

这里的“独立”仍有边界:核心汇总使用了独立实现,但日期、状态、币种等完整业务口径没有实现第二套会计引擎。生产系统可使用独立 SQL、仓库物化结果或另一套审核服务复核关键指标,并由数据治理团队维护口径。

篡改测试会把正文中的 总收入:300.00 改为 999.00,Reviewer 必须失败;另一组测试篡改机器可读 Evidence Block,同样必须失败。

4. Rubric 与报告质量门

4.1 确定性 Reviewer 和 LLM Grader 不做同一件事

检查内容 确定性 Reviewer Rubric Grader
金额是否精确相等 正式 Gate 不承担
区域是否对账 正式 Gate 不承担
Source / Summary / Report 是否关联 正式 Gate 不承担
限制与风险是否解释清楚 只能做基础结构检查 适合
方法是否容易理解 不擅长 适合
报告是否面向目标读者 不擅长 适合

模型可以辅助发现语义问题,但正式数值 Gate 仍依赖确定性 Reviewer。即使 Grader 说“全部通过”,只要 review-final.json 有一项失败,就不得生成 Approved Hash。

4.2 Rubric 修订循环必须真正修改对象

RubricMiddleware 需要在输入 State 中收到 rubric,仅构造中间件不会自动评分:

result = agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "根据已审核证据生成最终报告"}
        ],
        "rubric": REPORT_RUBRIC,
    },
    config=config,
)

离线集成测试使用实现了 LangChain BaseChatModel、Tool Calling 和结构化输出协议的有限响应模型。第一次报告缺少限制说明,Grader 返回 needs_revision;第二次报告加入 Rubric 修订记录,返回 satisfied。这条路径可重复验证真实 Middleware 状态迁移,但不是线上模型自主写作的证明。

4.3 Rubric 后重新审核,再生成 Approved Hash

关键代码顺序如下:

rubric = run_rubric_gate(reviewed_report, thread_id="p13-rubric-001")
if rubric.status != "satisfied":
    raise RuntimeError(f"rubric gate failed with status {rubric.status}")

report_path.write_text(rubric.final_report, encoding="utf-8")
final_review = review_report(dataset, Path(state["summary_path"]), report_path)
save_review(final_review, output_dir / "review-final.json")
if not final_review.passed:
    raise RuntimeError("post-rubric deterministic review failed")

approved_report_sha256 = sha256_file(report_path)

质量门只接受 satisfied。max_iterations_reached 表示在迭代上限内仍未满足 Rubric,不能当作“有结果所以通过”。在线测试断言为 assert status == “satisfied”。

4.4 在线模型实测与 Grader 独立性

在线测试使用 OpenAI 兼容 Provider,主模型和 Grader 都解析为 gpt-5.5。运行边界如下:

项目 实测配置
Provider 用户配置的第三方 OpenAI 兼容 Gateway,不是 OpenAI 官方 Endpoint
Endpoint 由 DEEPAGENT_BASE_URL 注入,结果和文章不保存实际地址
Integration langchain-openai 的 ChatOpenAI
主模型 / Grader gpt-5.5 / gpt-5.5
Prompt Cache 未显式启用,也未单独验证命中情况

实际结果为:

{
  "scene": "p13_enterprise_report_quality",
  "status": "passed",
  "model": "gpt-5.5",
  "grader_model": "gpt-5.5",
  "latency_ms": 76396,
  "deterministic_assertions": {
    "revenue": "300.00",
    "cost": "190.00",
    "profit": "110.00"
  },
  "rubric_verdicts": ["needs_revision", "satisfied"],
  "rubric_status": "satisfied",
  "usage": {
    "input_tokens": 24570,
    "output_tokens": 2396,
    "total_tokens": 26966
  }
}

这次实测证明了真实 Provider、报告生成、Rubric 调用、反馈修订和终态读取可以串通,但不证明 Grader 独立,因为生成器和裁判是同一模型 ID。生产建议使用独立模型、独立 Prompt、只读验证 Tool 和隔离的 Trace 权限。

保存的结果只包含总耗时 76396 ms,无法可靠还原 Draft、Grader 1、Revision、Grader 2 各自耗时,因此本文不伪造拆分数据。live_demo.py 为主模型和 Grader 分别安装脱敏 Callback,在线运行时输出每次请求的 role、call、started_ms、latency_ms 和 Token Usage,同时保留 grader_model_is_distinct 与评分完成时间线。Callback 不保存 Prompt、请求头或密钥。

5. HITL、原子发布与证据清单

5.1 发布是受控副作用

publish_report 不是让模型回答“已发布”,而是一个会创建发布目录的真实 Tool。它接收:源路径、目标路径、最终报告哈希、源/摘要/审核哈希、Schema 和分析器版本、代码版本、租户、Rubric Run ID、Approval ID 与 Operation ID。

传给 build_publish_agent(root, proposal) 的 root 来自可信应用配置 output_dir,不是模型或用户 Prompt。Tool 只接受相对路径,并通过解析后的 Path.is_relative_to(root) 阻止 ../ 越界。若生产应用允许模型控制 Root,这个安全结论就不成立。示例把 Root 收紧到本次运行的输出目录,发布 Tool 不需要读取项目源码或其他运行目录。

5.2 审批 Proposal 必须让人看懂在批准什么

HITL 中断描述至少展示租户、审批 ID 和最终哈希:

请审批最终报告发布:
tenant=tenant-demo
approval=approval-p13-001
sha256=86d63b2c58894464b6dd5dc604842663da7ae8ac76fedf258dd42bf47d8176a5

完整 Action Args 还保留目标路径和全部证据引用。审批者不是笼统批准“生成一份报告”,而是在批准一个明确租户、明确对象内容和明确目标位置的副作用。

批准前 Tool 不执行。approve 恢复原 Thread 后才运行 Tool;reject 不创建目标目录。LangChain ToolMessage 在拒绝分支会表现为未执行/错误状态,但业务流水线把它映射成正常终态 rejected,而不是系统故障。

5.3 原子、不覆盖和幂等策略

发布 Tool 先校验源报告哈希,然后:

  1. 拒绝已经存在的 approval-p13-001 发布目录;
  2. 创建同一父目录下的隐藏暂存目录;
  3. 复制报告并重新计算哈希;
  4. 写入 manifest.json 和 .sha256;
  5. 使用 os.replace(staging_dir, release_dir) 一次提交完整目录;
  6. 任何异常都清理暂存目录。

示例采用不覆盖策略:重复 Operation 不会悄悄替换已经发布的对象。真正的分布式对象存储还应使用条件写、对象版本或事务服务,而不能假设本地 os.replace 的原子语义跨主机成立。

5.4 Manifest 连接所有证据

发布证据关系

实际 Manifest 核心内容:

{
  "manifest_schema_version": "p13-publication-manifest/v1",
  "artifact_name": "report.md",
  "artifact_sha256": "86d63b2c58894464b6dd5dc604842663da7ae8ac76fedf258dd42bf47d8176a5",
  "source_sha256": "7ea97537f761d1bbf4c2d2fff171027aac215c41123a0ad03a5c3fe6d277a31f",
  "summary_sha256": "8f10e954187660e5f26d5952f465e6237f50a6f7a84d0b964eb2f677feca92e3",
  "review_sha256": "92cab34aa1582e98cafec8bed768c0a6b3fea6fe93a811d2699bcaf471191020",
  "code_revision": "p13-example/2.0",
  "tenant_id": "tenant-demo",
  "approval_id": "approval-p13-001",
  "rubric_run_id": "p13-rubric-001",
  "operation_id": "publish-p13-001"
}

哈希提供内容身份,Manifest 提供对象关系。要形成更强证据,生产系统还应验证源对象版本、存储桶版本 ID、代码镜像 Digest、审批者身份、签名和时间戳,而不只是把字符串写进 JSON。

6. Audit、Daytona 与多租户边界

6.1 教学 Audit Hash Chain 能证明什么

AuditTrail 为每个事件写入:

  • event_schema_version、event_id 和本地递增 sequence;
  • UTC 时间、租户和 Actor;
  • Stage、Status 和结构化 Evidence;
  • 前一个事件哈希;
  • 当前事件规范化 JSON 的 SHA-256。

事件顺序为 plan -> analyst -> reviewer -> rubric -> reviewer_final -> publish。测试会重新计算每个事件哈希,并断言 previous_event_sha256 串联正确。

但本地 audit.jsonl 是可修改文件:拥有写权限的人可以重写整个文件和整条哈希链。它只是教学用防意外篡改结构,不是不可变企业审计。生产中应把事件发送到追加写日志平台、WORM 对象存储或签名账本,并由外部序列、身份系统和保留策略约束。

6.2 三种路径空间必须分开

空间 示例路径 谁能访问 强制边界
Host Project FS …/p13_enterprise_agent/data/sales.csv 可信 Node、本地子进程 OS 权限、应用代码、子进程环境
Deep Agents VFS /data/sales.csv 等 Backend 路由 Agent 文件 Tool Backend、文件权限规则
Daytona Sandbox FS /workspace/input/sales.csv Sandbox 进程、Daytona Backend Sandbox 生命周期和 Provider 策略

路径长得相似不代表同一个文件。应用负责上传 Host 数据到 Daytona,再下载 summary.json 和 report.md。StateBackend 中的虚拟文件不会自动同步到 /workspace。

6.3 Daytona 集成代码与尚未验证的边界

run_daytona() 已实现以下真实 SDK 调用路径:创建 Sandbox、上传五个文件、在 /workspace 执行固定 CLI、下载两个产物、限制单个产物不超过 1 MiB、写入本地输出目录,并在 finally 中删除 Sandbox。

Shell 命令中的路径全部是可信常量。若未来把租户 ID、文件名或模型参数拼入 Shell 字符串,必须改用参数数组、严格引用或 Provider 提供的非 Shell 执行接口,不能直接字符串拼接。

尚未云端验证:当前环境没有 DAYTONA_API_KEY,所以 Daytona 测试结果为条件跳过。代码路径通过静态检查和缺 Key 负向测试,不等于账号、镜像、网络、上传下载和销毁都已成功。

此外,示例没有实现“默认拒绝网络出口”。限制公网、CPU、内存、磁盘、TTL 和并发是生产建议,不是本文已测试能力。分析脚本不需要模型 Key,因此本地和 Sandbox 都不应注入该密钥。

6.4 多租户与大数据扩展

多租户隔离必须贯穿输入对象前缀、运行身份、Sandbox、输出对象、Checkpoint Namespace、Trace 和审批者权限。只在 Prompt 里写“不要读取其他租户”没有强制力。发布 Root 也必须由认证后的租户配置映射,而不是让模型提交任意绝对路径。

示例的 stdlib csv 适合小文件。当数据达到百万行时,Agent 应生成受限查询计划,由查询验证器检查表、列、时间范围和预计成本,再交给 DuckDB、Spark 或数据仓库执行。Reviewer 使用独立查询或物化结果复核,模型只接收聚合证据与 Query ID,不把原始大表塞进上下文。

7. 测试、版本矩阵与生产验收

7.1 可复现命令和当前结果

在示例目录执行:

cd source/_posts/deepagent/examples

.venv_deepagent/bin/python -m compileall -q p13_enterprise_agent
.venv_deepagent/bin/ruff check p13_enterprise_agent
.venv_deepagent/bin/python -m pytest p13_enterprise_agent -q

# 发布采用不覆盖策略,因此端到端演示使用一个新的输出目录
mkdir -p /tmp/p13-enterprise-run-001
.venv_deepagent/bin/python -m p13_enterprise_agent.run_pipeline \
  --output-dir /tmp/p13-enterprise-run-001

本文确定性测试结果:

All checks passed!
23 passed, 1 skipped

跳过项是需要 DAYTONA_API_KEY 的云端集成,不是在线模型测试。23 个通过项覆盖:

  • Decimal 指标、区域对账、JSON Schema 和版本;
  • 缺列、重复 ID、非法状态、多币种、精度、公式前缀、BOM 和大小限制;
  • Skill CLI 真实子进程和环境白名单;
  • task Tool、CompiledSubAgent、Private State 和 write_todos 契约;
  • Reviewer 正向、正文篡改和 Evidence 篡改;
  • Rubric 修订后再次审核;
  • HITL 批准、拒绝和批准前无副作用;
  • 路径逃逸、哈希不匹配、重复发布和原子 Manifest;
  • Audit Hash Chain 和完整流水线。

7.2 证据等级与负向测试

等级 本文验证内容 不能推出的结论
确定性单元/集成 业务公式、文件、Tool、Middleware、Interrupt、恢复和副作用 在线模型会自主选择正确路径
在线模型 Provider 调用、生成、Rubric 反馈和 satisfied 终态 同模型 Grader 具有独立性
Daytona 条件测试 SDK 集成代码存在、无 Key 时安全失败 云端执行已经成功

负向行为比“Happy Path 能跑”更重要。测试会故意提交 ../ 路径、错误报告哈希、已存在发布目录、被篡改报告、包含公式前缀的数据和拒绝审批。生产在线 E2E 还应测试模型跳过 Reviewer、编造 KPI、选择错误 SubAgent、在批准前尝试发布以及 Rubric 达到迭代上限等行为。

7.3 版本矩阵

对象 本文版本/状态 说明
Python 3.12 锁定虚拟环境
Deep Agents 0.6.12 主线稳定版
Summary Schema sales-summary/v1 JSON Schema 已加入测试
Analyzer p13-analyzer/2.0 显式 Decimal 和输入限制
Example Code p13-example/2.0 写入 Manifest
Audit Event p13-audit-event/v1 本地教学哈希链
Publication Manifest p13-publication-manifest/v1 原子发布目录
在线模型 gpt-5.5 OpenAI 兼容 Provider 实测
Daytona 未云端验证 需要独立凭据后执行条件测试

7.5 示例文件索引

examples/p13_enterprise_agent/
├── analyze.py                    # 数据契约和确定性计算
├── reporting.py                  # 报告和独立 Reviewer
├── orchestration.py              # Todo、task 与 CompiledSubAgent
├── quality.py                    # Rubric、HITL 和原子发布
├── sandbox_runner.py             # 本地子进程与 Daytona 待验收路径
├── run_pipeline.py               # 强制顺序和 Audit
├── live_demo.py                  # 在线生成与 Grader
├── schemas/
│   └── sales-summary-v1.schema.json
├── skills/sales-summary/
└── test_pipeline.py

8. 总结

一个可信的数据分析 Agent,不是“让更强模型把 CSV 读得更仔细”,而是让开放式智能和确定性控制各自承担适合的工作。

本项目形成的闭环是:数据契约拒绝不合法输入;Skill 用 Decimal 生成带版本的结构化摘要;Reviewer 用独立核心汇总和机器可读 Evidence 复核报告;Rubric 只处理语义质量;任何修订都经过第二次确定性 Reviewer;系统为最终对象生成 Approved Hash;HITL 批准明确对象;发布 Tool 以原子、不覆盖方式生成报告、校验文件和结构化 Manifest;Audit 记录事件关系,但如实承认本地 JSONL 不是不可变审计系统。

更重要的是,本文把证据边界写清楚:真实 Tool 调用不等于模型自主规划,StateBackend 不会隔离任意 Host Python,存在 Daytona 代码不等于完成云端验收,同一模型完成生成和评分也不等于独立 Grader。只有把这些边界保留下来,后续服务化、可观测性和前端建设才不会建立在错误的安全假设上。

下一篇将把 Agent 发布成可调用服务,继续讨论 langgraph.json、Agent Server、Thread 恢复、多租户认证、LangSmith Trace 和 React 前端。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录