# 操作级 Telemetry 参考

操作级 telemetry 用来让 OpenViking 在请求结果里额外返回一份结构化摘要，帮助你了解这次操作实际发生了什么，例如耗时、token 消耗、向量检索情况、队列处理进度，以及资源导入阶段统计。

适合这些场景：

- 排查请求为什么变慢
- 观察 token 或检索行为
- 把结构化执行摘要接入你自己的日志或观测系统

更完整的观测入口说明，包括健康检查、`ov tui` 和 `OpenViking Console`，请先看 [可观测性与排障](05-observability.md)。

## 基本说明

Telemetry 是按需返回的。只有你显式请求时，OpenViking 才会在响应顶层返回 `telemetry` 字段。

典型响应结构如下：

```json
{
  "status": "ok",
  "result": {"...": "..."},
  "telemetry": {
    "id": "tm_xxx",
    "summary": {
      "operation": "search.find",
      "status": "ok",
      "duration_ms": 31.2,
      "tokens": {
        "total": 24,
        "llm": {
          "input": 12,
          "output": 6,
          "total": 18
        }
      },
      "vector": {
        "searches": 3,
        "scored": 26,
        "passed": 8,
        "returned": 5
      }
    }
  }
}
```

说明：

- `telemetry.id` 是不透明的关联 ID
- `telemetry.summary` 是面向调用方的结构化摘要
- 只有本次操作实际产出的分组才会返回
- 数值型 `0` 默认不会出现在响应里

## 当前支持范围

### HTTP API

当前这些接口支持 operation telemetry：

- `POST /api/v1/search/find`
- `POST /api/v1/search/search`
- `POST /api/v1/resources/temp_upload`
- `POST /api/v1/resources`
- `POST /api/v1/skills`
- `POST /api/v1/sessions`
- `POST /api/v1/sessions/{session_id}/messages`
- `POST /api/v1/sessions/{session_id}/commit`

### Python SDK

Python 客户端里，下面这些调用支持相同的 telemetry 语义：

- `add_resource(...)`
- `add_skill(...)`
- `find(...)`
- `search(...)`
- `create_session(...)`
- `add_message(...)`
- `commit_session(...)`
- `Session.commit(...)`

## 如何请求 telemetry

### JSON 请求

对于 JSON body，`telemetry` 支持下面两种常用写法：

```json
{"telemetry": true}
```

```json
{"telemetry": {"summary": true}}
```

`true` 和 `{"summary": true}` 的效果相同，都会返回 `telemetry.id + telemetry.summary`。

对象形态当前只开放 `summary` 这个开关。

如果不想返回 telemetry，可以省略该字段，或者显式传：

```json
{"telemetry": false}
```

```json
{"telemetry": {"summary": false}}
```

### Multipart 上传请求

`POST /api/v1/resources/temp_upload` 是 multipart form 接口。这个接口需要把 telemetry 当作表单字段传入：

```bash
curl -X POST http://localhost:1933/api/v1/resources/temp_upload \
  -H "X-API-Key: your-key" \
  -F "file=@./notes.md" \
  -F "telemetry=true"
```

这个接口当前只支持布尔形态的表单参数。
这个接口的 `upload_mode` 也是表单字段；默认值为 `local`，只有在明确需要分布式共享临时上传时，才应设置为 `shared`。Python HTTP client / CLI 用户也可以通过 `ovcli.conf` 的 `upload.mode = "shared"` 达到同样效果。

## 常见 summary 分组

summary 顶层这 3 个基础字段总会存在：

- `operation`
- `status`
- `duration_ms`

根据不同操作，还可能出现这些分组：

- `tokens`：LLM 和 embedding 的 token 统计
- `vector`：向量检索与过滤统计
- `resource`：资源导入与处理阶段摘要
- `queue`：等待模式下的队列处理统计
- `semantic_nodes`：语义节点提取统计
- `memory`：记忆提取或去重摘要
- `errors`：聚合后的错误信息

如果某个分组对本次操作不适用，就不会返回。

## 字段说明

只有这次操作实际产出的字段才会返回。某个分组缺失时，应理解为“不适用”，而不是默认等于 0。

### 顶层 telemetry 字段

| 字段 | 含义 |
| --- | --- |
| `telemetry.id` | 本次操作的不透明关联 ID |
| `summary.operation` | 操作名，例如 `search.find`、`resources.add_resource`、`session.commit` |
| `summary.status` | telemetry 最终状态，通常是 `ok` 或 `error` |
| `summary.duration_ms` | 本次操作的端到端总耗时，单位毫秒 |

### `summary.tokens`

| 字段 | 含义 |
| --- | --- |
| `summary.tokens.total` | 本次操作累计 token 总量 |
| `summary.tokens.llm.input` | LLM 输入 token 总量 |
| `summary.tokens.llm.output` | LLM 输出 token 总量 |
| `summary.tokens.llm.total` | LLM token 总量 |
| `summary.tokens.embedding.total` | embedding 模型 token 总量 |

### `summary.vector`

| 字段 | 含义 |
| --- | --- |
| `summary.vector.searches` | 向量检索调用次数 |
| `summary.vector.scored` | 被打分的候选数量 |
| `summary.vector.passed` | 通过阈值或后续过滤的候选数量 |
| `summary.vector.returned` | 最终返回给上层逻辑的结果数量 |
| `summary.vector.scanned` | 底层实际扫描的向量数量 |
| `summary.vector.scan_reason` | 本次扫描策略或扫描原因说明 |

### `summary.resource`

这个分组常见于 `resources.add_resource` 这类资源导入操作。

| 字段 | 含义 |
| --- | --- |
| `summary.resource.request.duration_ms` | add-resource 请求主流程总耗时 |
| `summary.resource.process.duration_ms` | 资源处理主流程耗时 |
| `summary.resource.process.parse.duration_ms` | 资源解析阶段耗时 |
| `summary.resource.process.parse.warnings_count` | 解析阶段 warning 数量 |
| `summary.resource.process.finalize.duration_ms` | 资源树 finalize 阶段耗时 |
| `summary.resource.process.summarize.duration_ms` | summarize 或 vectorize 阶段耗时 |
| `summary.resource.wait.duration_ms` | `wait=true` 时等待下游处理完成的耗时 |
| `summary.resource.watch.duration_ms` | 创建、更新或移除 watch 任务的耗时 |
| `summary.resource.flags.wait` | 本次请求是否使用了 `wait=true` |
| `summary.resource.flags.build_index` | 本次请求是否启用了 `build_index` |
| `summary.resource.flags.summarize` | 本次请求是否显式启用了 `summarize` |
| `summary.resource.flags.watch_enabled` | 本次请求是否启用了 watch 管理 |

### `summary.queue`

这个分组常见于需要等待队列任务完成的操作。

| 字段 | 含义 |
| --- | --- |
| `summary.queue.semantic.processed` | 已处理的 semantic queue 消息数 |
| `summary.queue.semantic.error_count` | semantic queue 错误数 |
| `summary.queue.embedding.processed` | 已处理的 embedding queue 消息数 |
| `summary.queue.embedding.error_count` | embedding queue 错误数 |

### `summary.semantic_nodes`

| 字段 | 含义 |
| --- | --- |
| `summary.semantic_nodes.total` | DAG 或语义节点总数 |
| `summary.semantic_nodes.done` | 已完成节点数 |
| `summary.semantic_nodes.pending` | 待处理节点数 |
| `summary.semantic_nodes.running` | 正在处理中的节点数 |

### `summary.memory`

这个分组常见于 `session.commit` 这类记忆提取流程。

| 字段 | 含义 |
| --- | --- |
| `summary.memory.extracted` | 本次操作最终抽取出的 memory 数量 |
| `summary.memory.extract.duration_ms` | memory extract 主流程总耗时 |
| `summary.memory.extract.candidates.total` | 最终动作执行前的候选总数 |
| `summary.memory.extract.candidates.standard` | 普通 memory candidate 数量 |
| `summary.memory.extract.candidates.tool_skill` | tool 或 skill candidate 数量 |
| `summary.memory.extract.actions.created` | 新建 memory 数量 |
| `summary.memory.extract.actions.merged` | 合并到已有 memory 的次数 |
| `summary.memory.extract.actions.deleted` | 删除旧 memory 的次数 |
| `summary.memory.extract.actions.skipped` | 被跳过的 candidate 数量 |
| `summary.memory.extract.stages.prepare_inputs_ms` | 提取前准备输入数据的耗时 |
| `summary.memory.extract.stages.llm_extract_ms` | 调用 LLM 做提取的耗时 |
| `summary.memory.extract.stages.normalize_candidates_ms` | 解析并归一化候选的耗时 |
| `summary.memory.extract.stages.tool_skill_stats_ms` | 聚合 tool 或 skill 统计的耗时 |
| `summary.memory.extract.stages.profile_create_ms` | 创建或更新 profile memory 的耗时 |
| `summary.memory.extract.stages.tool_skill_merge_ms` | 合并 tool 或 skill memory 的耗时 |
| `summary.memory.extract.stages.dedup_ms` | candidate 去重耗时 |
| `summary.memory.extract.stages.create_memory_ms` | 创建新 memory 的耗时 |
| `summary.memory.extract.stages.merge_existing_ms` | 合并到已有 memory 的耗时 |
| `summary.memory.extract.stages.delete_existing_ms` | 删除旧 memory 的耗时 |
| `summary.memory.extract.stages.create_relations_ms` | 创建 used-uri relations 的耗时 |
| `summary.memory.extract.stages.flush_semantic_ms` | flush semantic queue 的耗时 |

### `summary.errors`

| 字段 | 含义 |
| --- | --- |
| `summary.errors.stage` | 记录错误时所在的逻辑阶段 |
| `summary.errors.error_code` | 错误码或异常类型 |
| `summary.errors.message` | 人类可读的错误描述 |

## 示例

### 带 telemetry 的检索请求

```bash
curl -X POST http://localhost:1933/api/v1/search/find \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "query": "memory dedup",
    "limit": 5,
    "telemetry": true
  }'
```

### 导入资源并返回 telemetry

```bash
curl -X POST http://localhost:1933/api/v1/resources \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{
    "path": "./docs/readme.md",
    "reason": "telemetry demo",
    "wait": true,
    "telemetry": true
  }'
```

### Python SDK

```python
from openviking import AsyncOpenVikingClient

client = AsyncOpenVikingClient(config_path="/path/to/config.yaml")
await client.initialize()

result = await client.find("memory dedup", telemetry=True)
print(result["telemetry"]["summary"]["operation"])
print(result["telemetry"]["summary"]["duration_ms"])
```

## 限制与注意事项

- 当前对外只提供 summary-only telemetry
- `{"telemetry": {"events": true}}` 不是当前支持的公开请求形态
- 事件流风格的选择参数不属于当前公开接口
- `session.commit` 只有在 `wait=true` 时才支持 telemetry
- 如果 `session.commit` 使用 `wait=false` 并请求 telemetry，服务端会返回 `INVALID_ARGUMENT`
- telemetry 的顶层结构稳定，但具体有哪些 summary 分组取决于实际操作

## 相关文档

- [可观测性与排障](05-observability.md)
- [认证](04-authentication.md)
- [系统 API](../api/07-system.md)
