Metadata-Version: 2.4
Name: trace-platform
Version: 0.4.5
Summary: Standalone trace query platform and Python SDK
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"

# Trace Platform

面向 XAgent 的轻量 Trace 查询平台和 Python SDK。平台使用一个 SQLite 数据库管理项目、部署环境、环境 Token 和最近 30 天的 Trace 数据。

## 核心模型

```text
项目 Project
  └─ 环境 Environment
       ├─ 唯一有效 Token
       └─ Trace
```

- 管理后台直接访问，不需要登录。
- 创建环境时自动生成一把环境 Token。
- XAgent 或 SDK 使用环境 Token 上报 Trace。
- 服务端根据 Token 自动确定项目和环境。
- 一个环境只保留一把有效 Token，重新生成后旧 Token 立即失效。

## 启动

```bash
python -m trace_platform --host 0.0.0.0 --port 18791 --db ./data/trace_platform.db
```

可选环境变量：

```bash
TRACE_PLATFORM_DB=./data/trace_platform.db
TRACE_PLATFORM_RETENTION_DAYS=7
TRACE_PLATFORM_INGEST_MAX_BATCH_SIZE=1000
TRACE_PLATFORM_INGEST_MAX_BYTES=33554432
TRACE_PLATFORM_RUNNING_TIMEOUT_SECONDS=1800
TRACE_PLATFORM_WRITER_QUEUE_CAPACITY=2048
TRACE_PLATFORM_WRITER_BATCH_SIZE=32
TRACE_PLATFORM_AUTH_TOUCH_INTERVAL_SECONDS=60
```

页面：

- 项目环境管理：`http://127.0.0.1:18791/admin`

## 使用管理后台

1. 打开 `/admin`。
2. 创建项目。
3. 在项目下创建环境。
4. 从环境卡片复制 Token。
5. 将 Token 配置到对应的 XAgent 或 SDK。
6. 点击环境卡片进入该环境的 Trace 总览。

环境卡片持续展示当前有效 Token。重新生成后卡片更新为新 Token，旧 Token 立即失效。

## API

管理 API：

- `GET/POST /api/admin/projects`
- `PATCH/DELETE /api/admin/projects/{project_id}`
- `GET/POST /api/admin/projects/{project_id}/environments`
- `PATCH/DELETE /api/admin/environments/{environment_id}`
- `POST /api/admin/environments/{environment_id}/regenerate-token`

Trace API：

- `POST /api/admin/trace-ingest`
- `GET /api/admin/trace-summary`
- `GET /api/admin/trace-summary/{trace_id}`
- `GET /api/admin/environments/{environment_id}/traces/{trace_id}`

## 写入 Trace

上报必须使用环境 Token：

```bash
curl -X POST http://127.0.0.1:18791/api/admin/trace-ingest \
  -H 'Authorization: Bearer tp_sk_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
    "traces": [
      {
        "protocol_version": 2,
        "trace_id": "req_xxx",
        "base_revision": 2,
        "revision": 3,
        "events_delta": [{
          "event_id": "req_xxx:3",
          "sequence": 3,
          "phase": "request",
          "event": "request_done"
        }],
        "summary_delta": {
          "trace_id": "req_xxx",
          "status": "success",
          "agent_duration_ms": 12345,
          "llm_ttft_ms": 1200,
          "final_output": "最终回答"
        },
        "terminal": true,
        "status": "success"
      }
    ]
  }'
```

请求体不包含项目或环境字段。服务端根据 Bearer Token 自动绑定环境。

平台只接受 `protocol_version=2`。缺失或其他版本返回 HTTP 400
`unsupported_trace_protocol`。状态统一为
`running/success/error/cancelled/aborted/partial`。同一环境内按
`trace_id` 幂等合并 delta；较旧的 `running` 不会覆盖终态。长期没有更新的
`running` 会按 `TRACE_PLATFORM_RUNNING_TIMEOUT_SECONDS` 转为 `aborted`。

V2 客户端使用 `protocol_version=2`、`base_revision`、`revision`、
`events_delta` 和 `summary_delta`。事件必须携带稳定 `event_id/sequence`；重复
revision 幂等 ACK，缺口返回 HTTP 409 `revision_gap`。SDK 会从本地最新检查点
自动恢复。终态使用独立高优先级 delta，同一 Trace 仍严格按 revision 顺序发送。

平台将 V2 摘要保存在 `trace_query_index`，原始事件保存在 `trace_events`；详情 API
按 sequence 重建 events/segments/spans/lifecycle 展示语义。索引顶层时序字段仅为
`agent_duration_ms/llm_ttft_ms`，不再读写旧字段别名。

父子链路可提交 `trace_kind/parent_trace_id/parent_span_id/tool_call_id`。时间戳统一
以 UTC 入库和 API 返回，管理页面固定按 `Asia/Shanghai` 展示。

完整观测快照还支持以下增量字段，并按稳定 ID 在 `revision` 更新中 upsert：

- `tools`：arguments/result/stdout/stderr/exit_code/http_status/artifacts/retry/truncated。
- `skills`：skill_key/name/version/category/trust/source_repo/owner/pinned/selection_mode/repo_commit/content_sha256/entrypoint/input/output。
- `spans/subagents/artifacts/deliveries/lifecycle/events`：保留 span/parent/turn/iteration/batch/agent/subagent/tool_call/delivery 关系；Delivery/client 独立展示，不进入 Agent 执行耗时。
- `duration_breakdown`：默认页面展示 Agent 前台及已上报的 Agent 后处理分段；WS send、client ACK、delivery 和浏览器时间不进入 Agent 耗时瀑布。平台 Agent 指标不代表渠道客户端端到端耗时。

平台在 ingest、SQLite 入库和 API 输出三个边界递归脱敏；SDK 在 outbox 和上传前也会脱敏。
历史数据库可先 dry-run，再原地清洗：

```bash
python -m trace_platform.redact_history --db ./data/trace_platform.db --dry-run
python -m trace_platform.redact_history --db ./data/trace_platform.db
```

## SDK 示例

```python
from trace_sdk import TraceClient

client = TraceClient(
    host="http://127.0.0.1:18791",
    token="tp_sk_xxxxxxxxx",
    spool_dir="./data/trace-sdk-spool",
)

trace = client.trace(
    trace_id="req_123",
    user_input="帮我分析今天的任务",
    session_id="web:abc",
)

with trace.span("tool.search", tool_name="search"):
    pass

trace.generation(
    model="glm-5-turbo",
    endpoint="zhipuA",
    first_token_ms=500,
    input_tokens=100,
    output_tokens=30,
)

trace.score("task_success", 1)
trace.end(final_output="完成", status="success")
client.flush()
```

也可以将已经聚合好的 Trace 直接交给 SDK：

```python
client.ingest({
    "protocol_version": 2,
    "trace_id": "req_456",
    "base_revision": 0,
    "revision": 1,
    "events_delta": [{"event_id": "req_456:1", "sequence": 1}],
    "summary_delta": {
        "trace_id": "req_456",
        "user_input": "已经聚合好的请求",
        "status": "success",
    },
    "terminal": True,
    "status": "success",
})
```

SDK 0.4.1 使用 SQLite outbox 持久化每个待上传快照，提供幂等键、批次确认、指数退避、
终态 revision 优先、持续批量排空、dead-letter、queue/retry/drop/upload latency 指标和明确 stderr 日志。
网络超时和 5xx 会一直保留在 pending 队列重试，只有非瞬时 HTTP 拒绝才进入 dead-letter；
`queue_size` 包含 pending 和 dead-letter，不能再把 dead-letter 误报成队列归零。也可用
`TRACE_SDK_SPOOL_DIR` 配置 spool。进程退出前仍应调用 `flush()` 或 `close()`；未确认的
记录会在下次进程启动时继续上传。

0.4.1 增加认证/瞬时错误 circuit breaker、后台 worker 异常自愈、紧凑且有界的
dead-letter，以及 collector cursor/per-Trace state 的原子分页恢复公开 API。永久失败
revision 不会自动重放，也不会阻塞同一 Trace 后续 pending revision；后续 revision 可通过
V2 checkpoint gap recovery 继续收敛。

0.4.3 增加 SDK Outbox 空间维护：启动时先 truncate 历史 WAL，再启动上传 worker；
默认每 30 分钟执行一次有界维护。历史数据库达到 128 MiB，且空闲比例达到 50% 或
可回收页达到 128 MiB 时，SDK 在磁盘空间足够的前提下执行一次 full VACUUM 并切换为
incremental auto-vacuum，后续每次最多回收 16384 页。空间不足时只 checkpoint，所有
pending、dead-letter、checkpoint 和 collector recovery 记录均保留。阈值可通过
`TraceClient` 的 `spool_*` 构造参数调整。

0.4.3 同时隔离平台 SQLite 读写路径：summary/dashboard 使用短生命周期只读 WAL
连接，趋势和评分改为 SQL 聚合，不再持有 ingest writer 锁并全量加载时间窗数据；
retention 清理只在 writer 连续空闲后按 1000 条有界执行。平台 ingest 指标新增
`queue_wait_ms`、`writer_queue_depth` 和 `writer_batch_size`，用于区分平台排队、
SQLite 写入与网络耗时。

0.4.4 为直连 HTTPS 上传复用持久连接，并将 3 秒建连超时与完整请求超时分离；
0.4.5 对本地恢复 checkpoint 做兼容旧数据的 gzip 压缩，避免大上下文在 SQLite
Outbox 与恢复状态中临时保存两份，并补齐带业务前缀的 token/cookie 脱敏；
失效连接会幂等重连一次。SDK 上传日志和指标新增 `connection_reused`、
`server_duration_ms`、`transport_overhead_ms` 与 `payload_bytes`，用于把平台处理耗时
和网络/Ingress 建连耗时分开。平台批量事件写入改为 `executemany`；LLM 预览不再
展开 base64 图片，完整内容页面仅直接渲染内联 `data:image/...`，外部图片 URL
保持隐藏且不会由管理员浏览器自动请求。

V2 恢复检查点按 Trace 覆盖存放在 SDK SQLite 的 `trace_checkpoints` 表，不重复嵌入
每条正常 outbox payload；收到 `revision_gap` 时读取最新检查点，最后一个 outstanding
revision ACK 后自动清理。XAgent 与 SDK 只发送 V2；
`XAGENT_TRACE_PLATFORM_MAX_DELTA_EVENTS` 默认 100。

平台 ingest 使用有界单 writer 微批提交：同一 Trace 的 load/merge/write 串行执行，
SQLite commit 成功后才返回 ACK；队列满返回可重试的 HTTP 503。鉴权仍逐请求校验 Token，
但 `last_used_at` 按窗口节流落库，且 ingest 直接复用已鉴权 environment。响应中的
`ingest_metrics` 和 slow log 包含 auth、payload bytes、Trace/Event 数、load、normalize、
redact、merge、serialize、SQLite write/commit 与 total。

## SQLite 约束

- 平台按单实例运行，不要让多个进程或 Pod 同时写同一个 SQLite 文件。
- SQLite 使用 WAL 模式和 5 秒 `busy_timeout`。
- Trace 默认保留 30 天。
- 不同环境通过 `environment_id` 隔离，同一个 `trace_id` 可以存在于不同环境。
- 首次使用 V2-only 代码打开旧 SQLite 时，平台会先通过 SQLite 在线备份生成
  `trace_platform.db.legacy-时间戳.bak`，再清空重建 Trace 索引/事件表；已有
  projects/environments 及 token 会保留。没有控制面表的更老 schema 才会全量重建。

## 开发验证

```bash
python -m pytest -q
python -m compileall -q trace_platform trace_sdk
$env:PYTHONPATH=(Get-Location).Path; python tests\process_ingest_query_e2e.py --base-dir .tmp\process-e2e
```
