Browse Source

文档:记录可观测性接入边界与实施方案

整理 Global Data Run、LangGraph 节点、Agent 模型调用和正式 Delivery 的观测对象、生命周期与字段投影。

明确 opt-in 配置、脱敏策略、非阻塞失败语义、SDK 接线位置和本地 metrics 的职责边界。

记录测试矩阵、数据安全约束和后续可视化消费方式,避免把观测记录误当作正式业务合同或恢复依据。
SamLee 2 ngày trước cách đây
mục cha
commit
41ff33cfe4
1 tập tin đã thay đổi với 1084 bổ sung0 xóa
  1. 1084 0
      可视化接入.md

+ 1084 - 0
可视化接入.md

@@ -0,0 +1,1084 @@
+# VideoImageProductionBuild 可视化接入
+
+> 文档状态:接入设计稿
+> 当前项目协议:Production 0.7
+> 当前验证日期:2026-07-29
+> 观测台地址:<http://8.147.104.190:8931/>
+> 当前私有源最新 SDK:`obagent-sdk 0.5.2`
+
+## 1. 文档目标
+
+本文说明如何把 `obagent-sdk` 接入 VideoImageProductionBuild,使一次真实 Production
+运行可以在观测台中按 Run、Workflow、Agent、模型调用、工具调用和确定性代码阶段查看。
+
+本文覆盖:
+
+- 本地环境和依赖配置;
+- SDK 的主要接口能力;
+- 当前项目的模块边界与接入位置;
+- Production Graph、Segment Loop、恢复运行和 completed no-op 的表达方式;
+- 输入、输出、工具、媒体和血缘的申报约定;
+- 失败降级、隐私、安全和并发注意事项;
+- 分阶段实施与验收方案。
+
+本文是接入方案,不代表业务代码已经完成接入。当前项目环境已安装并验证
+`obagent-sdk 0.5.2`,但源码中尚未加入 `configure()`、`observe.run()` 或
+`observe.module()`。
+
+## 2. 两套“可视化”的职责边界
+
+当前项目会同时存在两套互补能力,不能互相替代。
+
+| 能力 | 数据来源 | 主要用途 | 是否业务事实 |
+|---|---|---|---|
+| 仓库内 `visualization/` | Run 目录、正式 JSON Contract、Journal、Artifact | 离线回放正式 Production 事实,查看 Plan、Segment、Artifact 和验收结果 | 是,以落盘合同为准 |
+| `obagent-sdk` 观测台 | 运行时主动上报、LangChain/LangGraph 自动采集 | 查看实时调用树、模型推理、工具调用、模块输入输出、耗时和 token | 是运行遥测,但不能代替正式合同 |
+
+必须遵守以下边界:
+
+1. 正式交付状态仍以 Production Contract、Checkpoint、Journal 和 Artifact 为准。
+2. obagent 上报失败不能让 Production 失败,也不能改变正式业务结论。
+3. 观测台不能根据“调用完成”自行推断 Segment PASS 或 Production COMPLETED。
+4. `CONTRACT_INVALID`、`OUTCOME_UNKNOWN`、未知副作用等正式语义必须原样上报,不能归并成普通失败或普通运行中。
+5. 不为“看起来完整”而制造不存在的模块执行、工具调用、轮次、Artifact 或成功结论。
+
+## 3. 已验证的运行环境
+
+当前项目环境:
+
+```text
+项目根目录  /Users/samlee/Documents/works/VideoImageProductionBuild
+Python      3.12.13
+虚拟环境    .venv
+SDK         obagent-sdk 0.5.2
+服务端      obagent-server 0.1.0
+```
+
+已完成的连通性验证:
+
+- `pip index` 确认私有源最新版本为 `0.5.2`;
+- SDK `doctor` 通过;
+- `GET /api/health` 返回 HTTP 200;
+- 本机 `~/.obagent/wal` 无积压;
+- 测试 Run 已成功落库并在页面渲染;
+- 测试 Run 服务端 ID 为 `258`,客户端 UID 为 `dffee26724b64578`;
+- 页面显示 Run、模块定义、运行实例、输入槽、过程记录和结构化输出;
+- 浏览器控制台无 warning/error。
+
+## 4. 安装与版本管理
+
+### 4.1 安装到当前虚拟环境
+
+必须使用项目 `.venv` 的 Python,避免安装到系统 Python:
+
+```bash
+cd /Users/samlee/Documents/works/VideoImageProductionBuild
+
+.venv/bin/python -m pip install -U obagent-sdk \
+  -i https://pypi.aiddit.com/repository/pypi-group/simple
+```
+
+确认版本:
+
+```bash
+.venv/bin/python -m pip show obagent-sdk
+
+.venv/bin/python -m pip index versions obagent-sdk \
+  -i https://pypi.aiddit.com/repository/pypi-group/simple
+```
+
+### 4.2 正式纳入项目依赖
+
+当前 SDK 只安装在本地 `.venv`,尚未写入 `pyproject.toml` 和锁文件。正式接入时应把
+SDK 纳入项目依赖并更新锁文件,建议先约束在兼容版本范围:
+
+当前仓库使用 PEP 621 的 `project.dependencies` 数组,建议加入:
+
+```toml
+dependencies = [
+    # ...现有依赖...
+    "obagent-sdk>=0.5.2,<0.6",
+]
+```
+
+不要只依赖某台机器上已存在的 `.venv`。CI、服务器和其他开发机必须能通过项目依赖声明复现。
+
+## 5. 环境配置
+
+### 5.1 建议的环境变量
+
+SDK 本身不会自动读取环境变量或 `.env`。以下变量是本项目适配层的约定,必须由项目代码读取后
+显式传给 `configure()`。
+
+```dotenv
+# 观测总开关。建议本地和测试环境先开启。
+OBAGENT_ENABLED=true
+
+# 当前已验证的观测台。
+OBAGENT_ENDPOINT=http://8.147.104.190:8931
+
+# 稳定项目标识。用于筛选和 module_key 前缀,不要随显示文案变化。
+OBAGENT_PROJECT=video_image_production_build
+
+# 观测台启用鉴权时填写;当前未配置时留空。
+OBAGENT_API_KEY=
+
+# 可选参数。
+OBAGENT_TIMEOUT=10
+OBAGENT_BATCH_SIZE=200
+OBAGENT_FLUSH_INTERVAL=0.5
+```
+
+推荐固定:
+
+```text
+project       video_image_production_build
+project_name  视频图片制作闭环
+agent         production_build
+```
+
+`project`、`agent` 和 `module_key` 是跨 Run 聚合的稳定身份,不应包含 thread ID、Plan
+版本、Segment ID 或时间戳。这些逐次变化的信息应放入 `meta`、`payload`、`branch_key`
+或输入槽。
+
+### 5.2 配置加载时机
+
+`run_production.py` 当前会在 `run_full_production()` 内调用:
+
+```python
+load_dotenv(PROJECT_ROOT / ".env", override=False)
+```
+
+因此 `configure()` 必须在这一步之后、第一次 `observe.run()` 之前调用。不要在模块 import
+阶段读取 `.env` 并配置 SDK,否则运行时 `.env` 还没有加载。
+
+建议未来增加集中适配器:
+
+```text
+production_build_agents/observability.py
+```
+
+适配器负责:
+
+- 读取和校验本项目的 `OBAGENT_*` 配置;
+- 调用一次 `configure()`;
+- 生成稳定的 Run 参数;
+- 把 Pydantic Model、Path、Enum 转成可上报 JSON;
+- 对敏感字段和超大字段做裁剪;
+- 提供不抛异常的辅助函数;
+- 提供客户端 UID 到服务端 Run ID 的只读查询。
+
+建议的配置函数:
+
+```python
+from __future__ import annotations
+
+import os
+
+from obagent_sdk import configure
+
+
+def configure_observability() -> None:
+    enabled = os.getenv("OBAGENT_ENABLED", "true").lower() in {
+        "1", "true", "yes", "on",
+    }
+    configure(
+        endpoint=os.getenv(
+            "OBAGENT_ENDPOINT",
+            "http://8.147.104.190:8931",
+        ),
+        api_key=os.getenv("OBAGENT_API_KEY", ""),
+        project=os.getenv(
+            "OBAGENT_PROJECT",
+            "video_image_production_build",
+        ),
+        timeout=float(os.getenv("OBAGENT_TIMEOUT", "10")),
+        batch_size=int(os.getenv("OBAGENT_BATCH_SIZE", "200")),
+        flush_interval=float(
+            os.getenv("OBAGENT_FLUSH_INTERVAL", "0.5")
+        ),
+        enabled=enabled,
+    )
+```
+
+配置解析错误应在启动时给出清晰提示;远端上报失败则必须降级,不得破坏业务运行。
+
+## 6. SDK 接口能力
+
+### 6.1 正确导入方式
+
+```python
+from obagent_sdk import configure, observe
+from obagent_sdk.observe import InputBlock
+from obagent_sdk.integrations.langgraph import graph_spec
+```
+
+也可以使用 `observe.InputBlock`。`InputBlock` 不能从 `obagent_sdk` 包顶层直接导入。
+
+### 6.2 `configure()`:进程级配置
+
+```python
+configure(
+    endpoint="http://8.147.104.190:8931",
+    project="video_image_production_build",
+    api_key="",
+    timeout=10.0,
+    batch_size=200,
+    flush_interval=0.5,
+    enabled=True,
+)
+```
+
+关键规则:
+
+- 必须在第一次 `observe.run()` 前调用;
+- SDK 不自动读取环境变量;
+- 没有 endpoint 时 SDK 会降级为空存储并告警,业务继续;
+- 一般一个进程只配置一次;
+- 上报失败的数据会进入本地 WAL,后续可重放。
+
+### 6.3 `observe.run()`:一次顶层业务运行
+
+```python
+with observe.run(
+    agent="production_build",
+    objective="完成指定 Global Data Delivery 的视频图片生产",
+    project="video_image_production_build",
+    project_name="视频图片制作闭环",
+    model_name="",
+    payload={...},
+    meta={
+        "run_name": "Production · <thread_id>",
+        "thread_id": "<thread_id>",
+        "protocol_version": "0.7",
+        "execution_mode": "fresh",
+    },
+    auto_collect=True,
+    round_anchor=None,
+) as run_handle:
+    result = run_production_graph(...)
+    run_handle.finish(
+        final_output=result,
+        ok=result.get("status") == "COMPLETED",
+    )
+```
+
+主要参数:
+
+| 参数 | 用途 |
+|---|---|
+| `agent` | 顶层业务 Agent 的稳定名称 |
+| `objective` | 本次运行目标 |
+| `project` | 项目标识;未传时使用 `configure(project=...)` |
+| `project_name` | 页面显示名 |
+| `model_name` | 主模型,用于成本归集 |
+| `payload` | 初始业务入参,原样 JSON 化保存 |
+| `meta` | thread ID、协议版本、环境、显示名等业务元数据 |
+| `auto_collect` | 是否自动采集当前上下文中的 LangChain 调用 |
+| `round_anchor` | 循环 Run 的轮次切分规则 |
+| `spec` | 外层 Workflow 结构定义 |
+
+`run_handle.run_id` 是 SDK 生成的客户端 UID,不是服务端数据库数字 ID。
+
+`run_handle.finish()` 可以提前声明最终产物;`with` 退出时 SDK 仍会幂等收尾并关闭上报线程。
+未捕获异常会继续向外抛,同时 Run 自动标记为 failed。
+
+### 6.4 `observe.module()`:业务模块边界
+
+```python
+with observe.module(
+    "segment_executor",
+    kind=observe.KIND_AGENT,
+    title="Segment Executor",
+    module_key="segment_executor",
+    summary="根据 SegmentPackage 制作并封印 SegmentDelivery",
+    branch_key=package.segment_id,
+) as ctx:
+    ...
+```
+
+支持四种 `kind`:
+
+| 常量 | 值 | 用途 |
+|---|---|---|
+| `observe.KIND_AGENT` | `agent` | 带工具、可能循环的 Agent |
+| `observe.KIND_LLM` | `llm` | 单次模型调用、无工具无循环 |
+| `observe.KIND_CODE` | `code` | 确定性代码阶段 |
+| `observe.KIND_WORKFLOW` | `workflow` | 只负责组织其他模块的工作流 |
+
+模块嵌套就是调用关系。内层模块会自动成为外层模块运行流程中的 call 节点,不需要手工连线。
+
+### 6.5 `ctx.declare()`:声明结构与输入
+
+```python
+user_content = ctx.declare(
+    system_prompt=system_prompt,
+    blocks=[
+        InputBlock(
+            "Segment Package",
+            key="segment_package",
+            value=package.model_dump(mode="json"),
+            optional=False,
+        ),
+        InputBlock(
+            "Verified Dependencies",
+            key="verified_dependencies",
+            value=verified_dependencies,
+        ),
+    ],
+    tools=resolved_tools,
+    model=model_name,
+    runtime={"model": model_name},
+    source_fn=run_segment_executor,
+)
+```
+
+`declare()` 负责:
+
+- 声明 system prompt;
+- 声明稳定输入槽;
+- 声明真实工具对象及其源码版本;
+- 声明模型和运行参数;
+- 声明确定性代码实现;
+- 返回按输入槽拼好的 prompt 文本。
+
+如果接入处已经有固定的多模态消息且暂时无法拆槽,可使用:
+
+```python
+InputBlock.from_content(user_content)
+```
+
+这只能作为第一阶段脚手架。正式接入应把输入拆成稳定、具名、逐次不变的语义槽。
+
+### 6.6 `InputBlock`:输入槽
+
+常用字段:
+
+| 字段 | 说明 |
+|---|---|
+| `name` | 页面显示名 |
+| `key` | 稳定键,建议使用英文 snake_case |
+| `text` | 文本值 |
+| `images` | 图片 URL 列表 |
+| `items` | 结构化条目 |
+| `item_type` | 条目类型,供前端选择渲染器 |
+| `optional` | 是否可选 |
+| `value` | 普通值或带血缘的 `OutRef` |
+| `origin` | 显式来源引用 |
+| `source` | 人类可读的来源说明 |
+
+输入槽属于模块结构,不能因为某一轮没有值就删除。空值时保留同一个槽,避免同一模块每次运行都生成
+不同结构版本。
+
+### 6.7 `ctx.set_output()`:模块产物
+
+```python
+ctx.set_output(
+    delivery.model_dump(mode="json"),
+    ok=True,
+    images=produced_image_urls,
+)
+```
+
+规则:
+
+- `data` 只放客观业务产物;
+- token、耗时和步骤数由 SDK 自动采集,不应重复写入;
+- `ok=True` 表示模块本身成功完成;
+- `ok=False` 表示模块执行失败;
+- 不传 `ok` 表示未知,不等于失败;
+- Validator 成功产出 `FAIL` 报告时,Validator 模块本身仍可为 `ok=True`;
+- 只有真正产出图片的模块才传 `images`;
+- 评估模块不能把被评图片声明为自己的产图。
+
+### 6.8 手工补录
+
+未走 LangChain 或需要记录确定性过程时,可使用:
+
+```python
+ctx.record_llm(...)
+ctx.record_tool(...)
+ctx.record_note(...)
+ctx.record_messages(...)
+ctx.record_stage(...)
+```
+
+对应能力:
+
+| 接口 | 用途 |
+|---|---|
+| `record_llm` | 手工记录一次模型调用、reasoning、tool calls 和 token |
+| `record_tool` | 记录不经模型触发的工具调用 |
+| `record_note` | 记录过程说明 |
+| `record_messages` | 保存一次模型调用的消息原件 |
+| `record_stage` | 把确定性代码工序记录成子调用 |
+
+确定性步骤优先使用 `record_stage(fn=真实函数)`,让源码进入模块定义和版本指纹,不要用散文描述代替
+真实规则。
+
+### 6.9 输出血缘
+
+```python
+planner_output = planner_ctx.out()
+
+InputBlock(
+    "Production Plan",
+    key="production_plan",
+    value=planner_output,
+)
+```
+
+`ctx.out()` 返回带来源引用的 `OutRef`。下游在构造 `InputBlock` 时通过 `value=` 接收,
+观测台即可绘制跨模块血缘。
+
+注意:
+
+- 必须在构造 `InputBlock` 时传 `value=`;
+- `.value` 用于取得裸值;
+- `.field("path")` 用于引用输出中的特定字段。
+
+### 6.10 LangGraph 结构
+
+当前 Production 0.7 的唯一正式控制流位于:
+
+```text
+production_build_agents/production/graph.py
+```
+
+应使用 SDK 的 LangGraph 集成读取真实拓扑:
+
+```python
+from obagent_sdk.integrations.langgraph import graph_spec
+
+graph = create_production_graph(...)
+
+with observe.module(
+    "production_graph",
+    kind=observe.KIND_WORKFLOW,
+    title="Production 0.7",
+    module_key="production_graph",
+    spec=graph_spec(graph),
+) as ctx:
+    result = graph.invoke(...)
+    ctx.set_output(result, ok=...)
+```
+
+`graph_spec(graph)` 会从编译后的图中提取节点、条件边、分支和回边。不要手工复制第二份 Graph
+拓扑,否则业务图改变后可视化会漂移。
+
+LangGraph 节点会被 SDK 自动采集为模块。不要再给每个 Graph 节点机械地包一层
+`observe.module()`;只在节点内部存在独立业务 Agent、模型语义或确定性子阶段时增加申报。
+
+### 6.11 并发
+
+SDK 使用上下文维护父子模块关系。普通线程池不会自动继承上下文。
+
+项目当前在 `production_build_agents/tools/discovery.py` 使用 `ThreadPoolExecutor`。正式接入时
+必须检查该并发是否发生在活跃观测上下文中。如果需要保留父模块,应使用:
+
+```python
+observe.spawn(executor, fn, *args, **kwargs)
+```
+
+或:
+
+```python
+observe.run_parallel(functions, max_workers=8)
+```
+
+一份定义并发执行多个候选时可以使用 `ctx.map(...)`,并为每一路提供稳定的 `branch_key`。
+
+### 6.12 视图契约
+
+SDK 支持 `ViewSpec`、`ViewField` 和 `ViewItems`,用于明确结构化输出如何展示:
+
+- `ViewField(..., as_="text")`
+- `ViewField(..., as_="md")`
+- `ViewField(..., as_="code")`
+- `ViewField(..., as_="json")`
+- `ViewItems(...)`
+
+第一阶段不应为了美化页面提前设计大量 View。先确保真实模块、输入、过程和输出正确,再为
+ProductionPlan、SegmentDelivery、ValidationReport、Artifact 等稳定合同增加专用 View。
+
+## 7. 当前项目的接入架构
+
+### 7.1 总体调用关系
+
+```mermaid
+flowchart TD
+    CLI["run_production.py<br/>加载 .env 与 CLI 参数"]
+    OBS["production_build_agents/observability.py<br/>配置、脱敏、Run 参数"]
+    RUN["observe.run<br/>一次 Production 请求"]
+    GRAPH["production_graph<br/>KIND_WORKFLOW + graph_spec"]
+    PLAN["Production Planner<br/>INITIAL / ADAPT / REPLAN"]
+    SEGEXEC["Segment Executor<br/>工具循环与媒体制作"]
+    SEGVAL["Segment Validator<br/>证据收集与语义验收"]
+    PROGRESS["Progress Review<br/>CONTINUE / ADAPT / REPLAN"]
+    ASSEMBLY["Production Assembly"]
+    READY["Readiness Check"]
+    PRODVAL["Production Validator"]
+    FINAL["Finalize"]
+    STORE["正式 Run 文件、Checkpoint、Journal、Artifact"]
+    REMOTE["obagent 观测台"]
+
+    CLI --> OBS
+    OBS --> RUN
+    RUN --> GRAPH
+    GRAPH --> PLAN
+    GRAPH --> SEGEXEC
+    GRAPH --> SEGVAL
+    GRAPH --> PROGRESS
+    GRAPH --> ASSEMBLY
+    GRAPH --> READY
+    GRAPH --> PRODVAL
+    GRAPH --> FINAL
+    PLAN --> STORE
+    SEGEXEC --> STORE
+    SEGVAL --> STORE
+    ASSEMBLY --> STORE
+    PRODVAL --> STORE
+    FINAL --> STORE
+    RUN -. "旁路遥测" .-> REMOTE
+    STORE -. "只读回放" .-> CLI
+```
+
+### 7.2 顶层 Run 边界
+
+推荐在 `production_build_agents/production/runtime.py` 的 `run_production_graph()` 内部、
+完成恢复状态读取之后创建观测 Run,而不是只在 CLI `main()` 外围包装。
+
+原因:
+
+- `run_production_graph()` 同时服务 CLI、测试和程序调用;
+- 它掌握 fresh、resume、terminal no-op 的真实状态;
+- 它知道 `graph.invoke()` 是否实际发生;
+- 它可以准确设置 `execution_mode`;
+- 不会把读取到终态 Checkpoint 误画成重新执行了 Production Graph。
+
+建议的 execution mode:
+
+| 值 | 含义 |
+|---|---|
+| `fresh` | 没有既有 Checkpoint,从初始状态执行 |
+| `resume` | 从非终态 Checkpoint 恢复并继续执行 |
+| `completed_noop` | 已完成,只读验证后返回,没有再次执行 Graph |
+| `failed_noop` | 已进入正式失败终态,只读验证后返回 |
+
+completed no-op 可以有一条“本次调用”的 Run 记录,但必须:
+
+- `meta.execution_mode="completed_noop"`;
+- 不创建假的 Planner、Executor、Validator 实例;
+- 不创建新的 Segment 轮次;
+- 输出指向既有正式状态;
+- 明确 `graph_invoked=false`;
+- 不把只读重验描述为重新生产。
+
+### 7.3 建议的模块清单
+
+| 稳定 `module_key` | 标题 | `kind` | 真实边界 |
+|---|---|---|---|
+| `production_graph` | Production 0.7 | `workflow` | 编译后的唯一 LangGraph |
+| `production_planner` | Production Planner | `agent` | INITIAL、ADAPT、REPLAN 共用正式 Planner |
+| `segment_executor` | Segment Executor | `agent` | 每个真实 Segment 的工具循环和 Delivery 生成 |
+| `segment_validator_evidence` | Segment Validator Evidence | `code` | 只读证据收集和预校验 |
+| `segment_validator` | Segment Validator | `llm` | 独立语义判断 |
+| `production_progress_review` | Production Progress Review | `llm` 或 `agent` | 正式进度决策 |
+| `production_assembly` | Production Assembly | `agent` 或 `code` | 依据实际工具调用形态确定 |
+| `production_readiness` | Production Readiness | `code` | 确定性媒体就绪检查 |
+| `production_validator` | Production Validator | `agent` | 终片证据与语义验收 |
+| `production_finalize` | Production Finalize | `code` | 正式终态封印 |
+
+`module_key` 不包含 Segment ID。每个 Segment 的区分通过 `branch_key=segment_id`、输入槽和输出
+合同完成。这样同一模块才能跨 Run、跨 Segment 聚合比较。
+
+### 7.4 Segment 轮次
+
+当前 Graph 的 Segment 主循环大致为:
+
+```text
+prepare_next_segment
+  → execute_segment
+  → validate_segment
+  → review_production_progress
+  → prepare_next_segment / adapt_production / replan / assemble
+```
+
+第一阶段建议先不设置 `round_anchor`,跑一个最小真实 Run,确认 SDK 自动生成的模块局部名和父容器。
+确认后再考虑:
+
+```python
+round_anchor={
+    "in": "production_graph",
+    "on": ["prepare_next_segment"],
+}
+```
+
+该配置只是候选值,必须以真实页面中的调用层级为准。轮语义会随 Run 持久化,不能靠事后修改修复
+历史 Run,所以不能未经验证直接固化。
+
+Segment ID、Plan Version、Attempt 和 ordinal 应作为结构化输入或 branch key 上报,不能靠解析标题。
+
+## 8. 各业务模块的输入输出方案
+
+### 8.1 Production Planner
+
+建议输入槽:
+
+- `planning_package`
+- `planning_package_ref`
+- `production_brief`
+- `global_data_delivery`
+- `production_input_catalog`
+- `previous_plan`
+- `failure_report`
+- `progress_decision`
+- `mode`
+
+INITIAL、ADAPT、REPLAN 使用同一个 `module_key="production_planner"`,输入槽取三种入口的并集。
+当前模式没有值的槽保留为空,不能拆成三个互不相干的模块。
+
+输出:
+
+```text
+ProductionPlan
+```
+
+`_record_observability()` 当前记录的是本项目自己的落盘 Agent Audit,不是 obagent SDK。正式接入时
+应保留这套审计,不要覆盖、删除或用远端观测替代。
+
+### 8.2 Segment Executor
+
+建议输入槽:
+
+- `segment_package`
+- `segment_id`
+- `plan_version`
+- `package_record_path`
+- `verified_dependencies`
+- `shared_visual_anchor`
+- `allowed_tools`
+- `resume_count`
+- `recovered_tool_progress`
+
+工具必须把实际 Tool 对象传给 `declare(tools=...)`,不能只传工具名字。
+
+输出建议保持正式合同结构:
+
+```json
+{
+  "candidate": {},
+  "delivery": {},
+  "attempt": {},
+  "outcome": "..."
+}
+```
+
+只有真实产出的图片或可远端访问的媒体 URL 才通过 `images=` 声明。远端观测台无法直接访问开发机的
+本地绝对路径;本地 Artifact 路径应作为结构化数据保存,由仓库内 `visualization/` 提供媒体路由。
+
+### 8.3 Segment Validator
+
+建议拆成两个真实阶段:
+
+1. `segment_validator_evidence`:预校验、媒体检查、证据收集,`kind=code`;
+2. `segment_validator`:无工具的语义判断,`kind=llm`。
+
+输出应包含:
+
+- `SegmentValidatorCandidate`
+- `SegmentValidationReport`
+- 六项 Criterion;
+- Evidence;
+- 代码重算后的正式 verdict。
+
+Validator 成功地判定业务失败时:
+
+```text
+模块执行 ok = true
+业务 verdict = FAIL
+```
+
+只有 Validator 自身异常、合同不合法或未能形成正式报告时,模块执行才是 `ok=false`。
+
+### 8.4 Production Assembly 与 Final Validation
+
+阶段 13、14 的业务代码已经在 Production 0.7 Graph 中出现,但其可视化申报仍必须以真实实现为准:
+
+- 有模型和工具循环时使用 `agent`;
+- 纯工具/确定性编排使用 `code` 或 `workflow`;
+- 不预先伪造未来不存在的字段;
+- 只有正式 Assembly Delivery、Readiness Report、Production Validation Report 出现后才声明产物;
+- Finalize 只投影真实终态,不自行推断完成。
+
+## 9. 状态、异常和恢复语义
+
+### 9.1 状态映射
+
+建议同时上报两个维度:
+
+```json
+{
+  "execution_ok": true,
+  "business_status": "COMPLETED",
+  "verdict": "PASS",
+  "outcome": "KNOWN"
+}
+```
+
+不能只用一个布尔值承载所有语义。
+
+### 9.2 `OUTCOME_UNKNOWN`
+
+当远端工具可能已经产生副作用但本地没有确定结果时:
+
+- 模块 `ok` 不应写成 `True`;
+- 输出明确记录 `outcome="OUTCOME_UNKNOWN"`;
+- 保留 operation scope、tool call ID、Attempt、ordinal 和已知证据;
+- 不重发已经可能成功的付费生成请求;
+- 观测台只展示事实,不替业务层决定恢复策略。
+
+### 9.3 异常
+
+`observe.run()` 和 `observe.module()` 不应吞业务异常。正确模式:
+
+```python
+with observe.module(...) as ctx:
+    result = business_call()
+    ctx.set_output(result, ok=True)
+```
+
+如果 `business_call()` 抛异常:
+
+- SDK 自动把模块标为失败并记录错误;
+- 异常继续向外抛;
+- 原有 Production 恢复、重试、Checkpoint 和退出码逻辑继续生效。
+
+不要为了上报而新增捕获后返回成功的代码。
+
+## 10. 隐私、安全与数据量
+
+### 10.1 自动采集范围
+
+`auto_collect=True` 会自动采集当前上下文中的 LangChain/LangGraph:
+
+- 模型输入消息;
+- system prompt;
+- 模型输出和 reasoning;
+- tool calls;
+- 工具入参和结果;
+- token、耗时、模型信息;
+- 调用时序。
+
+这意味着接入前必须确认 Production 数据允许发送到该观测台。
+
+### 10.2 当前地址是 HTTP
+
+当前观测台地址为:
+
+```text
+http://8.147.104.190:8931
+```
+
+它不是 HTTPS。不要在未经授权的情况下上报:
+
+- API Key、Cookie、Token、密码;
+- 用户个人信息;
+- 未脱敏的内部 URL 查询参数;
+- Base64 媒体正文;
+- 本地 `.env` 内容;
+- 不必要的完整文件内容;
+- 第三方受限素材。
+
+生产环境正式启用前,应确认网络边界、访问控制、保留周期和鉴权配置。
+
+### 10.3 脱敏和裁剪
+
+建议适配层统一提供:
+
+```python
+jsonable_for_observation(value)
+redact_observation_value(value)
+compact_observation_value(value, max_chars=...)
+```
+
+原则:
+
+- 正式合同保留关键 ID、状态、hash、版本和引用;
+- 超长正文给摘要和正式 Artifact/Record 引用;
+- 不重复上传媒体二进制;
+- 不修改业务对象本身;
+- 脱敏逻辑只作用于观测副本。
+
+## 11. 健康检查、查询和排障
+
+### 11.1 SDK 体检
+
+```bash
+.venv/bin/python -m obagent_sdk doctor \
+  --endpoint http://8.147.104.190:8931
+```
+
+该命令只验证:
+
+- 当前机器到观测台是否可达;
+- 服务端版本是否能读取;
+- 本地 WAL 是否有积压。
+
+它是独立进程,看不到应用代码中 `configure()` 的实际配置。
+
+### 11.2 HTTP 健康检查
+
+```bash
+curl -fsS http://8.147.104.190:8931/api/health
+```
+
+已验证返回:
+
+```json
+{"ok": true, "version": "0.1.0"}
+```
+
+### 11.3 查询 Run
+
+以下是当前服务端已验证的只读接口:
+
+```bash
+curl -fsS \
+  'http://8.147.104.190:8931/api/runs?project=video_image_production_build&page=1&page_size=30'
+
+curl -fsS \
+  'http://8.147.104.190:8931/api/runs/<server_run_id>'
+```
+
+这些接口适合诊断和把 SDK 客户端 UID 映射到服务端数字 ID。业务正确性不能依赖观测台查询成功。
+
+### 11.4 WAL
+
+默认 WAL 目录:
+
+```text
+~/.obagent/wal
+```
+
+上报失败时可检查并重放:
+
+```bash
+.venv/bin/python -m obagent_sdk replay \
+  --endpoint http://8.147.104.190:8931
+```
+
+重放只处理遥测,不得重新执行 Production 业务步骤。
+
+### 11.5 离线 SDK 文档
+
+```bash
+.venv/bin/python -m obagent_sdk docs
+.venv/bin/python -m obagent_sdk docs quickstart
+.venv/bin/python -m obagent_sdk docs integrating
+.venv/bin/python -m obagent_sdk docs api
+.venv/bin/python -m obagent_sdk docs troubleshooting
+.venv/bin/python -m obagent_sdk docs changelog
+```
+
+## 12. 最小连通性测试
+
+以下测试只验证 SDK 到观测台的写入和展示,不调用模型或媒体生成服务:
+
+```python
+from obagent_sdk import configure, observe
+
+
+configure(
+    endpoint="http://8.147.104.190:8931",
+    project="video_image_production_build",
+)
+
+with observe.run(
+    agent="sdk_connectivity_test",
+    objective="obagent-sdk 接入连通性测试",
+    project_name="VideoImageProductionBuild",
+    payload={"test": True},
+    meta={
+        "run_name": "SDK 连通性测试 · VideoImageProductionBuild",
+        "temporary": True,
+    },
+) as rh:
+    with observe.module(
+        "connectivity_probe",
+        kind=observe.KIND_CODE,
+        title="SDK 上报链路探针",
+        module_key="connectivity_probe",
+    ) as ctx:
+        ctx.declare(
+            blocks=[
+                observe.InputBlock(
+                    "检测目标",
+                    "http://8.147.104.190:8931",
+                    key="endpoint",
+                    optional=False,
+                ),
+            ],
+        )
+        ctx.record_note("SDK 写入链路已执行", ok=True)
+        ctx.set_output(
+            {"endpoint_reachable": True},
+            ok=True,
+        )
+    rh.finish(
+        final_output={"status": "passed"},
+        ok=True,
+    )
+```
+
+## 13. 分阶段实施方案
+
+### 阶段 A:依赖与集中配置
+
+改动范围:
+
+- 将 `obagent-sdk` 加入项目依赖和锁文件;
+- 新增集中观测适配器;
+- 在 `.env.example` 或配置文档中声明 `OBAGENT_*`;
+- 添加配置、禁用和降级测试。
+
+验收:
+
+- `doctor` 全绿;
+- 禁用时无远端写入;
+- endpoint 不可达时业务仍按原逻辑完成或失败;
+- WAL 行为符合预期。
+
+### 阶段 B:零申报 Run
+
+只增加顶层 `observe.run()`,暂不增加大量业务模块申报。
+
+验收:
+
+- fresh、resume、completed no-op 可以区分;
+- Run 的 objective、thread ID、协议版本、耗时和最终状态正确;
+- LangChain/LangGraph 自动采集存在;
+- 不改变正式 Run 目录和文件 hash。
+
+### 阶段 C:Production Graph
+
+增加:
+
+- `production_graph` workflow;
+- `graph_spec(compiled_graph)`;
+- 条件边和回边的页面验证;
+- 候选 `round_anchor` 的真实 Run 验证。
+
+验收:
+
+- 页面拓扑与 `graph.py` 一致;
+- 未走分支仍显示为定义,但不伪造运行实例;
+- Loop 次数与真实 Segment 推进一致;
+- Graph 节点没有被重复包装成两套模块。
+
+### 阶段 D:阶段 12 业务模块
+
+优先接入:
+
+- Production Planner;
+- Segment Executor;
+- Segment Validator Evidence;
+- Segment Validator;
+- Progress Review。
+
+验收:
+
+- 每个真实 Segment 有独立 branch/实例;
+- 多 Segment 工具调用不会串线;
+- Package、Candidate、Delivery、Report 和 Evidence 是结构化 JSON;
+- 工具对象可以查看真实定义;
+- PASS、FAIL、CONTRACT_INVALID、OUTCOME_UNKNOWN 和恢复 Attempt 都保持原语义。
+
+### 阶段 E:阶段 13、14
+
+在真实实现稳定后接入:
+
+- Assembly;
+- Readiness;
+- Production Validator;
+- Finalize;
+- 最终媒体和 Artifact View。
+
+不为未来阶段预造虚假模块或合同字段。
+
+### 阶段 F:视图优化
+
+数据正确后再增加:
+
+- ProductionPlan View;
+- Segment Timeline View;
+- Criterion/Evidence View;
+- Artifact View;
+- 图片、视频、音频、字幕和 JSON 的专用展示。
+
+视图优化不能改变上报事实和业务状态。
+
+## 14. 测试矩阵
+
+| 场景 | 预期 |
+|---|---|
+| SDK 禁用 | 无远端数据,业务行为不变 |
+| endpoint 不可达 | 业务继续,遥测进 WAL 或降级 |
+| Plan-only | 只有真实 Planner/Plan 实例 |
+| Segment Package-only | 不伪造 Executor 已运行 |
+| Executor 运行中 | 展示真实工具过程,不提前出现 Delivery |
+| Delivery 待验收 | Executor 成功,Validator 尚无终态 |
+| Segment PASS | Report 与 Summary 为正式 PASS |
+| Segment FAIL | Validator 执行成功,业务 verdict 为 FAIL |
+| CONTRACT_INVALID | 显示合同错误,不改写为普通模型失败 |
+| OUTCOME_UNKNOWN | 显示未知副作用,不自动重试付费工具 |
+| resume | 使用同一业务 thread 身份,显示恢复信息 |
+| completed no-op | 不出现新业务模块实例和新 Segment 轮次 |
+| 多 Segment | branch、tool call、Artifact 和 ordinal 隔离 |
+| Assembly/Final Validation | 只显示真实执行过的阶段 |
+
+## 15. 最终验收清单
+
+- [ ] SDK 版本与项目依赖、锁文件一致。
+- [ ] `configure()` 在 `.env` 加载后、第一次 Run 前执行。
+- [ ] endpoint、project、enabled 和 api_key 均由本项目配置体系显式传入。
+- [ ] 顶层 Run 能区分 fresh、resume 和 terminal no-op。
+- [ ] `graph_spec()` 来自真实编译 Graph,没有第二份手写拓扑。
+- [ ] 模块边界是业务边界,没有机械重复包装 LangGraph 节点。
+- [ ] 同一业务模块跨 Run 使用稳定 `module_key`。
+- [ ] Segment ID 使用 `branch_key` 或结构化字段,不写进 `module_key`。
+- [ ] 输入是稳定具名槽,空槽不会被删除。
+- [ ] 工具传真实对象,不传名字字符串。
+- [ ] 输出为真实 JSON Contract,不塞重复 token/耗时。
+- [ ] Validator 的执行成功与业务 verdict 分开表达。
+- [ ] 图片只挂在真实产图模块。
+- [ ] completed no-op 不伪造重新执行。
+- [ ] OUTCOME_UNKNOWN 保留未知副作用语义。
+- [ ] 现有 Agent Audit、Checkpoint、Journal 和 Artifact 不受影响。
+- [ ] 上报失败不改变业务返回值、异常、退出码或落盘文件。
+- [ ] 并发任务保持正确父子上下文。
+- [ ] 敏感数据、密钥和媒体正文没有被上报。
+- [ ] 页面模块、输入、过程、输出、分支和 Loop 与真实 Run 一致。
+- [ ] 浏览器控制台无 obagent 相关错误。
+- [ ] 本地 WAL 无非预期积压。
+
+## 16. 参考实现
+
+已参考同仓库族中的成熟接入:
+
+```text
+/Users/samlee/Documents/works/image_article_comprehension/aiddit/production_destruction_agent/framework/observability.py
+```
+
+该项目的关键做法:
+
+- 由宿主读取 `OBAGENT_ENDPOINT`,再显式调用 `configure()`;
+- 用 `observe.run()` 包裹完整业务 Run;
+- 在公共 Agent 基类中统一接入 `observe.module()`;
+- 子类只覆盖标题、输入槽、dispatch node 和输出媒体;
+- LangChain 模型与工具过程交给 SDK 自动采集;
+- `set_output()` 和 `finish()` 只声明作者知道而 SDK 无法推断的业务事实;
+- 上报和服务端 ID 查询均采用旁路容错,不影响主业务。
+
+VideoImageProductionBuild 应复用上述原则,但模块划分必须以自身 Production 0.7 Graph、正式
+Contract、恢复语义和 Segment 工具边界为准,不能直接照搬对方的模块名称或输入结构。