feat(harness,rag): dual LLM audit fields, run conclusion, and hybrid quality

Persist provider reasoning and assistant text separately on agent_reasoning_audit
(DeepSeekAssistantMessage path), extract diagnosis_run.conclusion, enrich RAG
tool audit (step_id/query/qualityScore), gate empty mysql tools, drop devtools,
and align MVP docs after live E2E verification.
This commit is contained in:
zhuyongxin
2026-07-28 19:43:13 +08:00
parent 2f40536248
commit 7ae9707a3b
116 changed files with 8364 additions and 1141 deletions
@@ -0,0 +1,394 @@
# RAG 检索可观测性、审计与 Trace(现行)
**更新日期**:2026-07-28
**状态**:当前可运行
**关联**:`lookup_knowledge`、Harness `ToolBoundary`、`tool_invocation`、`DiagnosisTraceService`、离线 eval
---
## 1. 三层边界
```mermaid
flowchart TB
subgraph A["A. 请求内 Trace"]
LR[LookupResult<br/>retrievalTrace / rerankTrace / relevanceLevel]
end
subgraph B["B. 持久化审计 + Trace API"]
TI[tool_invocation 表]
DT[diagnosis_trace 事件摘要]
API["GET /api/diagnosis/{sessionId}/trace"]
end
subgraph C["C. 质量回归"]
EV[eval/rag-retrieval offline baseline]
end
subgraph agent["Agent 可见(非审计)"]
RT[RagToolResult<br/>evidence + optional relevance_level]
end
LK[LookupKnowledgeTool] --> LR
LR --> PROJ[RagResultProjector]
PROJ --> RT
LR --> BOUND[ToolBoundary audit]
BOUND --> TI
BOUND --> DT
TI --> API
DT --> API
EV -.->|不替代运行时 Trace| LK
```
| 层 | 完善度 | 说明 |
|----|--------|------|
| A 请求内 | 高 | attempt / fallback / quality 齐全 |
| B 持久化 + Trace API | 中高 | RAG 富字段入 `tool_invocation`,经 Trace API 回放 |
| C 离线 eval | 高 | hybrid fixtures 回归 |
**Agent 看到的不是完整 Trace。** 完整检索轨迹在 A/B;Agent 只拿投影后的证据契约。
---
## 2. 端到端:从 lookup 到 Trace API
```mermaid
sequenceDiagram
participant Agent
participant Adapter as RagToolAdapter
participant Bound as ToolBoundary
participant Tool as LookupKnowledgeTool
participant Sink as JpaToolInvocationAuditSink
participant DB as tool_invocation
participant Trace as DiagnosisTraceService
participant API as GET .../trace
Agent->>Adapter: lookup_knowledge(query)
Adapter->>Bound: execute(legacy, projector)
Bound->>Tool: execute(query)
Tool-->>Bound: raw LookupResult JSON
Note over Tool: 内含 retrievalTrace / rerankTrace / evidenceBlocks
Bound->>Bound: project → RagToolResult
Bound->>Sink: AuditEvent + rawResultJson + agentResultJson
Sink->>Sink: RagLookupAuditEnricher
Sink->>DB: 富字段行
Bound-->>Agent: 投影后 agent_result(无完整 trace)
API->>Trace: sessionId + optional runId
Trace->>DB: find tool_invocation by run/session
Trace-->>API: DiagnosisTraceResponse.toolInvocations[]
```
---
## 3. A 层:请求内 Trace(`LookupResult`)
一次成功的 `lookup_knowledge` 内部出口是 **`LookupResult`**(比 Agent 契约更富)。
### 3.1 结构总览
```mermaid
flowchart TB
LR[LookupResult]
LR --> F[found]
LR --> EB[evidenceBlocks[]]
LR --> CP[contextPack]
LR --> RT[retrievalTrace]
LR --> RR[rerankTrace]
LR --> RL[relevanceLevel]
LR --> CH[completenessHint]
LR --> CNT[evidenceCandidateCount / evidenceBlockCount]
RT --> ATT[attempts[]]
RT --> SEL[selectedAttempt]
RT --> FB[fallbackReason]
RT --> HINT[queryHints L0]
```
| 字段 | 含义 |
|------|------|
| `found` | 是否有可用证据块 |
| `evidenceBlocks` | 后处理后的 chunk 级证据(含 evidenceKey、source、content…) |
| `contextPack` | 字符预算打包文本(内部/审计用) |
| `retrievalTrace` | **检索路径 Trace**(见下) |
| `rerankTrace` | 后处理排序/quality 痕迹(现多为保序后的 quality) |
| `relevanceLevel` | PRECISE / REFERENCE / null |
| `completenessHint` | 给模型的天花板提示文案 |
### 3.2 `retrievalTrace`(检索路径)
| 字段 | 含义 |
|------|------|
| `originalQuery` | 原始查询 |
| `rewrittenQuery` | L0/变换后用于检索的 query |
| `categoryFilter` | 首次过滤的 category(可 null) |
| `selectedAttempt` | 最终采用的 attempt 名 |
| `fallbackReason` | 如 `filtered_vector_low_quality`;未降级为 null |
| `evidenceStatus` | 内部:`supported` / `no_evidence` 等 |
| `queryHints` | L0:domains、keywords、entities、l0_match_count… |
| `attempts[]` | 每次检索尝试快照 |
**常见 `selectedAttempt`:**
| 值 | 含义 |
|----|------|
| `FILTERED_VECTOR` | 带 category 的首次检索即采用 |
| `UNFILTERED_VECTOR` | 无 category,直接全库检索 |
| `UNFILTERED_VECTOR_RETRY` | filtered 低质/无证据后去掉 category 重试 |
**单次 `attempts[]` 元素:**
| 字段 | 含义 |
|------|------|
| `name` | attempt 名 |
| `query` | 该次实际检索句 |
| `categoryFilter` | 该次 filter |
| `candidateCount` | 召回候选数 |
| `usable` | 后处理阈值后是否可用 |
| `topScore` / `topSimilarity` | 引擎分 / 归一化 quality(0~1) |
| `durationMs` | 耗时 |
| `errorMessage` | 失败时 |
### 3.3 一次典型路径(含 filter fallback)
```mermaid
flowchart TB
Q[query] --> L0[L0 hint → 可选 categoryFilter]
L0 --> A1[attempt FILTERED_VECTOR]
A1 --> PQ{isLowQuality?}
PQ -->|否| USE1[selectedAttempt = FILTERED_VECTOR]
PQ -->|是| A2[attempt UNFILTERED_VECTOR_RETRY]
A2 --> USE2[selectedAttempt = RETRY<br/>fallbackReason = low_quality / no_evidence]
USE1 --> POST[PostProcess · evidenceBlocks · relevanceLevel]
USE2 --> POST
POST --> LR[LookupResult 完整 Trace]
```
### 3.4 与 Agent 投影的关系
```mermaid
flowchart LR
LR[LookupResult 全量 Trace] --> PROJ[RagResultProjector]
PROJ --> AG[RagToolResult]
AG --> F1[evidence_status]
AG --> F2[evidence excerpt]
AG --> F3[relevance_level 可选]
AG --> F4[truncated / returned_count]
LR -.->|不投影| X1[retrievalTrace]
LR -.->|不投影| X2[rerankTrace]
LR -.->|不投影| X3[raw scores / contextPack 全文]
```
人/系统要「为什么这样检索」→ 看 **A 全量** 或 **B 落库摘要**,不要只看 Agent 字段。
---
## 4. B 层:持久化 + Trace API
### 4.1 写入路径
| 组件 | 职责 |
|------|------|
| `ToolBoundary` | 执行后发 `ToolInvocationAuditEvent`(含 raw LookupResult JSON + agent JSON) |
| `RagLookupAuditEnricher` | 从 LookupResult 抽有界 RAG 字段 |
| `JpaToolInvocationAuditSink` | 写入 `tool_invocation` |
| `TraceAuditEvents.toolInvocation` | 另写一条 diagnosis_trace 摘要事件(不含全文 LookupResult) |
### 4.2 `tool_invocation` 列(RAG)
| 列 | lookup_knowledge | 其它工具 |
|----|------------------|----------|
| `tool_name` | `lookup_knowledge` | 各自工具名 |
| `retrieval_layer` | 通常 `L1` | `HARNESS` |
| `relevance_level` | **PRECISE / REFERENCE / …** | **null**(不再写 evidence_status) |
| `l0_match_count` | queryHints | null |
| `l1_match_count` | evidence 块数等 | null |
| `is_truncated` | 投影 truncated | false |
| `retrieval_details` | JSON `rag_lookup_v1` | 通用 status 元数据 |
| `output_preview` | level/attempt 摘要 | status=… |
| `duration_ms` / `success` | 有 | 有 |
### 4.3 `retrieval_details`(rag_lookup_v1)示例
```json
{
"audit_schema": "rag_lookup_v1",
"search_mode": "hybrid",
"selected_attempt": "UNFILTERED_VECTOR_RETRY",
"fallback_reason": "filtered_vector_low_quality",
"category_filter": "overfilter-decoy",
"evidence_keys": ["doc#chunk-0"],
"sources": ["doc"],
"evidence_candidate_count": 8,
"evidence_block_count": 2,
"l0_hints": { "domains": ["mysql"], "matched_keywords": ["pool"] },
"attempts": [
{
"name": "FILTERED_VECTOR",
"category_filter": "overfilter-decoy",
"candidate_count": 2,
"usable": false,
"top_similarity": 0.3,
"duration_ms": 12
},
{
"name": "UNFILTERED_VECTOR_RETRY",
"candidate_count": 5,
"usable": true,
"top_similarity": 0.9,
"duration_ms": 20
}
],
"truncated": false,
"returned_count": 2,
"evidence_status": "EVIDENCE_FOUND",
"invocation_status": "READY",
"tool_call_id": "call-…"
}
```
**默认不落库:** 原始 query 全文、chunk 正文 excerpt、完整 rerankTrace(体积与隐私)。
### 4.4 Trace API:人怎么读 RAG
**接口:**
```http
GET /api/diagnosis/{sessionId}/trace
GET /api/diagnosis/{sessionId}/trace?runId={runId}
```
**实现:** `DiagnosisTraceController` → `DiagnosisTraceService.getTrace`
按 `sessionId`(可选精确 `runId`)拉 run、steps、**toolInvocations**、摘要等。
**响应中与 RAG 相关的核心块:** `DiagnosisTraceResponse.toolInvocations[]`
| API 字段 | 来源列 | 读法 |
|----------|--------|------|
| `toolName` | `tool_name` | 是否为 `lookup_knowledge` |
| `retrievalLayer` | `retrieval_layer` | L1 / HARNESS |
| `relevanceLevel` | `relevance_level` | RAG 粗相关度(非 evidence_status) |
| `l0MatchCount` / `l1MatchCount` | 同名列 | L0/L1 规模提示 |
| `truncated` | `is_truncated` | 证据是否被投影截断 |
| `outputPreview` | `output_preview` | 一行摘要(level/attempt…) |
| `retrievalDetails` | 解析自 `retrieval_details` | **RAG Trace 主阵地** |
| `retrievalDetailsRaw` | 原始 JSON 字符串 | 调试 |
| `durationMs` / `success` / `errorMessage` | 同名列 | 耗时与成败 |
| `inputParams` | 通常仅 tool_call_id、request_bytes | **不含完整 query**(有意) |
```mermaid
flowchart TB
API["GET /api/diagnosis/{sessionId}/trace"] --> SVC[DiagnosisTraceService]
SVC --> ROW[tool_invocation 行]
ROW --> T1[列: relevanceLevel, L0/L1 count, layer…]
ROW --> T2[retrievalDetails Map]
T2 --> D1[search_mode]
T2 --> D2[selected_attempt / fallback_reason]
T2 --> D3[attempts[] / evidence_keys]
T2 --> D4[evidence_status 契约状态]
```
### 4.5 读 Trace 的推荐顺序(排查「这次知识库怎么检的」)
```mermaid
flowchart TB
S1[找到 toolName=lookup_knowledge 的 invocation] --> S2{success?}
S2 -->|否| E[看 errorMessage / evidence_status]
S2 -->|是| S3[看 retrievalDetails.search_mode]
S3 --> S4[看 selected_attempt + fallback_reason]
S4 --> S5[看 attempts[] 每次 candidate_count / top_similarity / usable]
S5 --> S6[看 evidence_keys / sources]
S6 --> S7[看 relevanceLevel 列]
S7 --> S8[需要原文?看 Agent 侧 evidence 或当时 canonical 存储 · 审计默认无 excerpt]
```
| 现象 | 优先看 |
|------|--------|
| 为何走了 retry | `fallback_reason` + 两次 `attempts` |
| 是否 hybrid | `search_mode` |
| 滤错域 | `category_filter` + L0 domains |
| 相关度档 | 列 `relevanceLevel`(PRECISE/REFERENCE) |
| 返回了哪些块 | `evidence_keys` / `sources`(无正文) |
| Agent 是否被截断 | `truncated` / `returned_count` |
### 4.6 diagnosis_trace 事件 vs tool_invocation 行
| 通道 | 内容 | 用途 |
|------|------|------|
| `tool_invocation` 行 | RAG 富字段完整摘要 | **主审计/回放** |
| `diagnosis_trace` 中 `TOOL_INVOCATION` | tool_call_id、status、字节数、`has_raw_result` 等薄摘要 | 时间线事件,**不含**完整 retrieval_details |
查 RAG 细节以 **`toolInvocations[].retrievalDetails`** 为准。
---
## 5. 与 Agent / Eval 的边界
```mermaid
flowchart LR
subgraph human["人 / 运维 / 评测"]
TRACE[Trace API]
EVAL[Offline eval]
end
subgraph model["模型"]
AGENT[RagToolResult only]
end
TI[(tool_invocation)] --> TRACE
FX[fixtures] --> EVAL
PROJ[Projector] --> AGENT
```
| 消费者 | 能看到 |
|--------|--------|
| Agent | evidence + 可选 relevance_level,无 attempt 细节 |
| Trace API | 落库摘要:mode/attempt/fallback/keys/level… |
| Offline eval | 冻结 fixture 全量 LookupResult(含 trace),与 golden 比对 |
---
## 6. 与旧文档差异
| 旧(archive `retrieval-observability`) | 现 |
|----------------------------------------|-----|
| `vector-store.mode` 多后端 | `search_mode` dense\|hybrid,单一 V2 store |
| sink 理想化未落地 | `RagLookupAuditEnricher` + 列回填 |
| `relevance_level` 混用 evidence_status | **列仅 RAG 等级**;契约状态在 details |
| 未写清 Trace API 读法 | 本文 §4.4–4.5 |
---
## 7. 代码锚点
| 职责 | 类 / 路径 |
|------|-----------|
| 内建 Trace | `LookupKnowledgeTool`、`RetrievalTrace`、`LookupResult` |
| 投影 | `RagResultProjector`、`RagToolResult` |
| 审计事件 | `ToolInvocationAuditEvent`、`ToolBoundary` |
| 富化 | `RagLookupAuditEnricher` |
| 落库 | `JpaToolInvocationAuditSink`、`ToolInvocation` |
| Trace API | `DiagnosisTraceController`、`DiagnosisTraceService`、`DiagnosisTraceResponse.ToolInvocationTrace` |
| 离线回归 | `eval/rag-retrieval/`、`scripts/eval_rag_retrieval.py` |
---
## 8. 已知限制
- 持久化 **不存** 完整 query/excerpt(有意);要正文需 Agent 侧证据或其它存储
- `dedup_reason` 列可能仍为空
- 非 `lookup_knowledge` 工具仍为薄审计
- **历史** `tool_invocation` 行可能仍把 evidence_status 写进 `relevance_level`(旧 sink)
- `diagnosis_trace` 时间线事件不替代 `retrieval_details`
---
## 9. 相关文档
| 文档 | 内容 |
|------|------|
| `mvp/architecture/RAG知识检索架构.md` | 检索主架构 |
| `docs/RAG-Agent如何读relevance_level.md` | Agent 如何读 level |
| `docs/RAG-Hybrid质量分与后处理.md` | quality / 排序闸门 |
| `docs/RAG离线评测-基线设计.md` | 离线评测(非运行时 Trace) |
| `mvp/architecture/session-trace-lifecycle.md` | 会话/run Trace 总览(若存在) |
@@ -168,7 +168,7 @@ sparse_vector -> SPARSE_INVERTED_INDEX + BM25
```yaml
retrieval:
search:
mode: hybrid # dense | hybrid
mode: hybrid # dense | hybrid(见 6.0 用途约定)
hybrid:
rrf-k: 60
kb-scope: ""
@@ -178,7 +178,29 @@ rag:
max-chunks-per-document: 2
```
### 6.1 dense
### 6.0 模式用途约定(保留双 mode 的原因)
知识库 **只维护一套** dense + BM25 schema 数据(默认 collection `biz`)。
`retrieval.search.mode` 切换的是**同库上的查询算法**,不是两套互斥索引、也不是两套写入路径。
| 模式 | 定位 | 说明 |
|---|---|---|
| **hybrid** | **线上主路径 / 默认** | dense ANN + 服务端 BM25 + RRF;`lookup_knowledge` 正式召回只认此模式 |
| **dense** | **对照 / 评测 / 排障** | 仅 dense ANN,用于和 hybrid 对比召回效果(命中文档/chunk、排名差异等) |
约定:
1. 生产配置保持 `mode: hybrid`;不要把 dense 当成第二套长期并行的线上策略。
2. 需要看「去掉 BM25+RRF 后召回差在哪」时,临时切 `mode: dense`,其它参数(`retrieve-k`、`return-n`、category filter、query 集)尽量固定,再切回 hybrid。
3. hybrid 入库的数据 **完全适用于** dense-only 查询:每条 chunk 都写了 `vector`;dense 模式只是不使用 `sparse_vector` / BM25 子路。
4. 代码里 `@Value` 在配置缺失时的兜底仍可能是 `dense`(历史兼容);**以 `application.yml` 的 hybrid 为准**。若做回归,确认运行配置而不是只看注解默认值。
不建议的用法:
- 按请求/按租户在 dense 与 hybrid 之间当产品功能随意切换(当前也无稳定的 per-call mode 覆盖)。
- 把 dense 模式的相关度表现直接当成 hybrid 的最终质量结论(hybrid 排序信 RRF,后处理分数仍多 L2 兼容,见下节)。
### 6.1 dense(对照基线)
```text
query
@@ -187,7 +209,9 @@ query
-> topK
```
### 6.2 hybrid(当前默认)
仅走 `vector` 字段的 L2 ANN。用于基线对比,不作为正式主路径。
### 6.2 hybrid(当前默认 / 主路径)
```text
query
@@ -197,11 +221,18 @@ query
-> topK fused hits
```
阈值兼容:
说明:hybrid **内部**的 dense 子路是融合的一部分,与配置项 mode=dense(整次检索只跑单路 ANN)不是同一概念。
- 后处理仍按 dense 兼容 L2 距离做 `normalizeL2`
- hybrid 命中若能从并行 dense 结果拿到同 id 的 L2,则回填该分数
- 仅 BM25 命中、无 dense 分时,按弱相关处理,避免虚高 `PRECISE`
分数与后处理(quality 统一,2026-07-28):
- 一级 scoreLabel 仅 **dense | hybrid**(旧别名 canonicalize)。
- **dense**:score = L2;qualityScore = 1 - clamp(L2)/maxL2Distance。
- **hybrid**:返回序 = RRF 序;qualityScore 由 **本轮 rank 线性映射**(不把 RRF 原分当 L2;不做 dense L2 回填覆盖主分;无 m25_only_* 一级 label)。
- 后处理:**统一**消费 qualityScore;排序主序 = originalRank;**不做** L0 关键词/domain contains 加分改序(重叠仅可写 hitReasons 解释)。
-
elevance_level / category 低质 unfiltered retry:只看 top qualityScore 与阈值。
- 实现:RetrievalScoreNormalizer、KnowledgeEvidencePostProcessor;详见 OpenSpec
ag-quality-score-unify。
### 6.3 category filter 与降级
@@ -266,6 +297,18 @@ Agent 只看到有界 `RagToolResult`:
完整内部结果仍在 `LookupResult` 中,供审计与调试使用。
### 9.1 Trace 与审计(现行入口)
请求内 `retrievalTrace` / 落库 `tool_invocation` / Trace API 读法见:
**[RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md)**
要点:
- Agent **看不到**完整 retrievalTrace;人通过 `GET /api/diagnosis/{sessionId}/trace` 的 `toolInvocations[].retrievalDetails` 回放。
- `relevance_level` 列存 RAG 等级(PRECISE/REFERENCE);`evidence_status` 在 details JSON。
- 默认审计不落原始 query 全文与 excerpt 正文。
## 10. 写入与重建
### 10.1 日常写入
@@ -326,3 +369,8 @@ POST /api/knowledge/rebuild-hybrid?confirm=REBUILD
- L0 关键词匹配仍较粗,只作 hint,不作主召回
- 尚未做邻块上下文自动扩展、cross-encoder rerank、真 query rewrite
- `totalVectors` 统计接口仍可能返回 0,不代表 collection 为空;以 rebuild/init 结果与检索命中为准
- `retrieval.search.mode=dense` 仅作召回对照,不是第二套主路径
- 同一 hybrid schema 数据可被 dense / hybrid 两种查询复用;从纯旧 dense-only collection 升级必须 rebuild
- hybrid 质量闸门优先用 `denseDistance` 绝对 L2;无 dense 时 rank 回退;排序仍跟 RRF
- 后处理不再用 L0 关键词 boost 改序;词面信号以库内 BM25+RRF 为准
- Trace/审计细节与限制见 [RAG检索可观测性与审计.md](./RAG检索可观测性与审计.md)
+5 -3
View File
@@ -12,8 +12,10 @@
| [harness-quality-gates.md](harness-quality-gates.md) | Run、Tool、Evidence、Semantic、Release 与 Trace Recorder 门禁 |
| [session-trace-lifecycle.md](session-trace-lifecycle.md) | sessionId/runId、SSE、统一 Timeline 和 reasoning audit 生命周期 |
| [diagnosis-information-gain-stop-architecture.md](diagnosis-information-gain-stop-architecture.md) | 已实施的信息增益评价、Harness 饱和检测、Draft 合同失败降级与证据不足停止设计 |
| [rag-knowledge-retrieval-architecture.md](rag-knowledge-retrieval-architecture.md) | 当前 `lookup_knowledge` 检索:MilvusClientV2 dense+BM25 hybrid、chunk 证据身份、重建运维 |
| [RAG知识检索架构.md](RAG知识检索架构.md) | 当前 `lookup_knowledge` 检索:MilvusClientV2 dense+BM25 hybrid、chunk 证据身份、重建运维 |
| [RAG检索可观测性与审计.md](RAG检索可观测性与审计.md) | RAG Trace / 审计:请求内 retrievalTrace、tool_invocation 富字段、Trace API 读法 |
2026-07-22 前的多角色编排、双入口和旧证据链文档已移动到 `archive/2026-07-22-legacy/`,仅用于历史决策追溯,不代表当前运行时。其中旧 RAG 描述(Spring AI VectorStore 主路径 + Milvus SDK fallback)已被当前 hybrid 实现取代,请以 [rag-knowledge-retrieval-architecture.md](rag-knowledge-retrieval-architecture.md) 为准。
2026-07-22 前的多角色编排、双入口和旧证据链文档已移动到 `archive/2026-07-22-legacy/`,仅用于历史决策追溯,不代表当前运行时。其中旧 RAG 描述(Spring AI VectorStore 主路径 + Milvus SDK fallback)已被当前 hybrid 实现取代,请以 [RAG知识检索架构.md](RAG知识检索架构.md) 为准。检索可观测与 Trace 以 [RAG检索可观测性与审计.md](RAG检索可观测性与审计.md) 为准(勿再依赖 archive 内旧 retrieval-observability)。
当前普通 Trace 与 Provider reasoning 审计使用独立存储和独立接口。Reasoning 访问控制、保留期限、加密要求以及真实 Provider/V015 验证仍由 ISS-015 跟踪,不能把“数据已分表”理解为“治理已经完成”。
当前普通 Trace 与 LLM 步骤审计(`agent_reasoning_audit`:`reasoning_content` + `assistant_text`)使用独立存储和独立接口。
DeepSeek thinking 捕获路径与 V015–V017 字段已 live 验证(2026-07-28)。Reasoning 访问控制、保留期限、加密要求仍由 ISS-015 跟踪,不能把“数据已分表 + 能抓到 thinking”理解为“治理已经完成”。
+30 -8
View File
@@ -56,14 +56,36 @@ sequenceDiagram
PreviousTurn 只来自同一 Session 最近一个 `DIAGNOSIS + SUCCESS + published_result`。Fallback、失败、取消、raw evidence 和完整历史都不能进入下一轮;字段与字节上限由 Harness 配置控制。
## 5. Provider Reasoning 审计
## 5. Provider Reasoning 与 Assistant 正文审计
`HarnessAgentAuditHook` 在每次模型步骤结束后检查 `AssistantMessage` metadata。当前识别 `reasoning_content`、`reasoningContent`、`reasoning` 和 `thinking`,但只接受 Provider 实际返回的非空文本:
`HarnessAgentAuditHook` 在每次模型步骤结束后写入独立表 `agent_reasoning_audit`,**同时**尝试捕获:
- 有内容时写入 `agent_reasoning_audit`,单条最多保留 32000 个字符,并记录 UTF-8 `content_bytes`。
- 无内容时写入 `reasoning_available=false`、`reasoning_content=NULL`、`content_bytes=0`,不得根据最终回答反推或生成 reasoning。
- `agent_step.thought` 始终为空;步骤表只记录 message count、roles、是否有文本、Tool names、reasoning availability 和字节数等 metadata。
- 普通 `diagnosis_trace_event` 的 `AGENT_MODEL_STEP` 只记录 reasoning availability/bytes,不保存 reasoning 原文。
- Reasoning 只用于受限审计,不进入 Agent 后续上下文,不参与 EvidenceGuard、SemanticGuard 或 Release Policy 的事实判断。
| 列 | 含义 |
|---|---|
| `reasoning_content` | Provider thinking / CoT |
| `assistant_text` | 本步 assistant 可见正文,和/或 tool-call **计划**(不含 tool 结果) |
| `content_source` | `PROVIDER_REASONING+ASSISTANT_TEXT` 等组合标记 |
| `content_bytes` | 两列截断后 UTF-8 字节合计 |
当前查询隔离已经实现,完整访问治理和真实 Provider 行为验证仍属于 ISS-015。
### 捕获路径(DeepSeek 生产)
当前 Chat 为 Spring AI 原生 `DeepSeekChatModel`(`deepseek-v4-flash`)。API 返回的 `message.reasoning_content` 被映射到 **`DeepSeekAssistantMessage.getReasoningContent()`**,而不是普通 `AssistantMessage.metadata`。Hook 优先读该专用字段,再反射 `getReasoningContent()`,最后才回退 metadata 键(`reasoning_content` / `thinking` 等)。
只接受 Provider 实际返回的非空文本;不得根据最终回答反推或生成伪 reasoning。
### 边界
- 单字段最多保留 32000 字符;tool **结果**只在 `tool_invocation`。
- `agent_step.model_*` 仍为有界 metadata(含 `reasoning_available` / bytes / `content_source`)。
- `agent_step.thought` 为兼容镜像:优先 reasoning,否则 assistant 正文;完整双字段以 `agent_reasoning_audit` 为准。
- 普通 Timeline 的 `AGENT_MODEL_STEP` 不保存 reasoning/assistant 原文。
- Reasoning / assistant 审计原文只用于受限审计 API,不进入 Agent 后续上下文,不参与 EvidenceGuard、SemanticGuard 或 Release Policy 的事实判断。
### 运行级结论读出
Run 结束时 `JpaChatRunStore` 从安全发布 JSON 提取 `diagnosis_run.conclusion`(与 `query` 并列),便于 Trace/DB 直接读结论;它不是 Provider thinking。
### 验证与治理
- **已 live 验证(2026-07-28)**:DeepSeek thinking 模式下 `reasoning_available=true`,且 reasoning 与 assistant 可同时非空。
- 查询隔离已实现;访问控制、保留期限、加密等完整治理仍属 ISS-015。
+11 -11
View File
@@ -7,7 +7,7 @@
SuperBizAgent 是面向故障诊断的可追踪 Agent 应用。当前系统只保留一个拥有 Tool loop 的 `Diagnosis Agent`;Harness 负责确定性的预算、取消、工具边界、证据验真、语义审查和安全发布。
知识检索当前为显式 `lookup_knowledge` 工具 + 单一 MilvusClientV2 后端(dense / dense+BM25 hybrid)。详细链路见 [rag-knowledge-retrieval-architecture.md](rag-knowledge-retrieval-architecture.md)。
知识检索当前为显式 `lookup_knowledge` 工具 + 单一 MilvusClientV2 后端(dense / dense+BM25 hybrid)。详细链路见 [RAG知识检索架构.md](RAG知识检索架构.md)。
## 2. 分层
@@ -86,7 +86,7 @@ Agent
- 证据按 chunk 级 `evidenceKey` 去重;Agent 侧 `document_id` 为 chunk 级身份。
- 知识库全量重建:`python scripts/rebuild_hybrid_knowledge.py --confirm REBUILD`,默认操作 collection `biz`。
完整 schema、模式、重建与历史差异见 [rag-knowledge-retrieval-architecture.md](rag-knowledge-retrieval-architecture.md)。
完整 schema、模式、重建与历史差异见 [RAG知识检索架构.md](RAG知识检索架构.md)。
## 5. Trace 与持久化
@@ -100,20 +100,20 @@ chat_session(sessionId)
```
- `chat_session` 是 JPA Run 目录与多轮 metadata,不保存完整对话历史。
- `diagnosis_run` 是 Run 状态、intent、release outcome、安全发布结果和预算汇总真理源。
- `agent_step` 只保存模型步骤 metadata,不保存 Prompt、消息正文、模型正文或 Thought。
- `diagnosis_run` 是 Run 状态、intent、release outcome、安全发布结果、预算汇总真理源;另含与 `query` 并列的提取字段 `conclusion`(业务结论读出,非 thinking)。
- `agent_step` 保存模型步骤有界 metadata;`thought` 可为 reasoning/assistant 的兼容镜像,完整双字段不在此表。
- `tool_invocation` 只保存 Tool durable audit metadata;完整调用由 Redis canonical store 短期保存。
- `diagnosis_trace_event` 是追加式统一 Timeline,记录 Run、Routing、Agent、Tool、Evidence、Semantic 和 Release 生命周期事件;`details` 只能保存有界安全 metadata。
- `agent_reasoning_audit` 与普通 Trace 分表,只保存 Provider 实际返回的 reasoning 或明确的 unavailable 记录;reasoning 不属于事实证据。
- `agent_reasoning_audit` 与普通 Trace 分表,按模型步骤保存 Provider `reasoning_content` 与 `assistant_text`(及 `content_source`),或明确的 unavailable;二者均不属于事实证据。
## 6. 公开 API
当前诊断执行入口只有 `POST /api/chat`。诊断审计读取分为:
- `GET /api/diagnosis/{sessionId}/trace?runId={runId}`:普通 Trace,返回 Run、步骤、Tool metadata 和统一 Timeline,不返回 reasoning 原文。
- `GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}`:独立 reasoning 审计读取,`runId` 必填并校验其属于 path `sessionId`。
- `GET /api/diagnosis/{sessionId}/trace?runId={runId}`:普通 Trace,返回 Run(含 `query`/`conclusion`)、步骤、Tool metadata 和统一 Timeline,**不**返回 reasoning/assistant 原文。
- `GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}`:独立 LLM 步骤审计读取(`reasoningContent` + `assistantText`),`runId` 必填并校验其属于 path `sessionId`。
Reasoning endpoint 是敏感审计面,不属于普通业务 API。当前已完成数据和查询隔离;认证授权、保留期限、加密要求及真实 Provider 验证仍由 ISS-015 收敛。Feedback、文档与检索 API 保持独立;已删除的旧诊断和 Redis conversation Session endpoint 不提供兼容分支。
Reasoning endpoint 是敏感审计面,不属于普通业务 API。数据和查询隔离、DeepSeek thinking 捕获路径已 live 验证;认证授权、保留期限、加密要求仍由 ISS-015 收敛。Feedback、文档与检索 API 保持独立;已删除的旧诊断和 Redis conversation Session endpoint 不提供兼容分支。
与知识检索相关的独立 API:
@@ -124,9 +124,9 @@ Reasoning endpoint 是敏感审计面,不属于普通业务 API。当前已完
## 7. 安全边界
- 普通 SSE、Trace、Evidence Snapshot、业务结果和应用日志不输出或保存 reasoning 原文。
- 仅当 Provider 在模型 metadata 中实际返回 reasoning 时,审计 Hook 才将其截断后写入独立表;Provider 未返回时不得伪造。
- Reasoning 不能作为事实证据,也不能绕过 EvidenceGuard 或 SemanticGuard。
- 普通 SSE、普通 Trace 的 steps/timeline、Evidence Snapshot、业务结果和应用日志不输出或保存 reasoning / assistant 审计原文。
- 审计 Hook 仅当 Provider **实际返回** thinking(DeepSeek:`DeepSeekAssistantMessage.reasoningContent`;其它:metadata 键)时写入 `reasoning_content`;未返回时不得伪造。`assistant_text` 来自本步 assistant 可见输出与 tool-call 计划,不含 tool 结果。
- Reasoning / assistant 审计原文不能作为事实证据,也不能绕过 EvidenceGuard 或 SemanticGuard。
- 不向 Agent 暴露 Redis、canonical key、完整 Tool 请求/响应或数据库凭据。
- EvidenceGuard 只接受当前 Run 的 READY canonical invocation。
- SemanticGuard 无 Tool、无记忆、无回调主 Agent 能力。
+7 -6
View File
@@ -37,7 +37,8 @@ disconnect、timeout 与 send failure 通过同一个 `ChatRunControl` 请求取
Redis canonical invocation 不是 Trace API 的长期响应内容;它只供当前 Run EvidenceGuard 验真。
普通 Trace 不读取 `agent_reasoning_audit`,也不返回 reasoning 原文。Agent 模型步骤和 Timeline 只暴露 `reasoning_available`、`reasoning_bytes` 等有界 metadata。
普通 Trace 不读取 `agent_reasoning_audit` 的 **原文**。Agent 模型步骤和 Timeline 只暴露 `reasoning_available`、`reasoning_bytes` / `assistant_bytes`、`content_source` 等有界 metadata。
普通 Trace 的 `run` 可返回 `query` 与提取后的 `conclusion`(业务结论读出,非 thinking)。
## 4. PreviousTurn
@@ -45,11 +46,11 @@ Redis canonical invocation 不是 Trace API 的长期响应内容;它只供当
## 5. Reasoning 审计查询
`GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}` 提供独立 reasoning 审计读取:
`GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}` 提供独立 LLM 步骤审计读取:
- `runId` 必填,服务端先验证 Run 存在且属于 path `sessionId`,禁止跨 Session 串读。
- 结果按 `step_index` 返回 Agent、reasoning availability、受限原文、字节数和创建时间。
- Provider 未返回 reasoning 时仍保留 unavailable 记录,以区分“没有返回”与“审计遗漏”。
- Reasoning 数据不回流到 PreviousTurn,不进入普通 Trace、SSE、Evidence Snapshot 或发布结果。
- 结果按 `step_index` 返回:`reasoningAvailable`、`reasoningContent`、`assistantText`、`contentSource`、`contentBytes`、创建时间等。
- Provider 未返回 reasoning 时仍保留记录(`reasoningAvailable=false`,可仍有 `assistantText`),以区分“没有 thinking”与“审计遗漏”。
- Reasoning / assistant 审计原文不回流到 PreviousTurn,不进入普通 Trace 的 steps/timeline 原文、SSE、Evidence Snapshot 或发布结果。
该端点属于敏感审计面。当前完成了分表、独立查询和归属校验;身份认证、权限模型、保留期限、加密及真实 Provider/V015 验证尚未完成,由 ISS-015 阶段 3 收敛。
该端点属于敏感审计面。分表、独立查询、归属校验,以及 DeepSeek 真实 thinking 捕获路径(`DeepSeekAssistantMessage.reasoningContent`)与 V015–V017 迁移已验证;身份认证、权限模型、保留期限、加密仍由 ISS-015 阶段 3 收敛。
+8 -5
View File
@@ -2,11 +2,14 @@
- [ ] SSE metadata 的 `session_id`、`run_id` 非空且与数据库完全一致。
- [ ] `diagnosis_run.intent=DIAGNOSIS`,status/release_outcome 与 done outcome 一致。
- [ ] `diagnosis_run.conclusion` 与发布结论一致(SUCCESS 时非空短结论;FALLBACK 可为 type/message 摘要)。
- [ ] `agent_step.agent_name` 只出现 `diagnosis_agent`。
- [ ] AgentStep model_input/model_output 只含 metadata,thought 为空。
- [ ] ToolInvocation 全部属于 exact runId,Tool 名在 ACI allowlist 内。
- [ ] ToolInvocation input/output/retrieval details 不含 SQL、日志 query、raw response 或 evidence body。
- [ ] AgentStep `model_input`/`model_output` 只含有界 metadata(可含 `reasoning_available`/`content_source`/bytes);**不含** reasoning/assistant 原文。
- [ ] `agent_step.thought` 若非空,应为 reasoning 或 assistant 的兼容镜像,不得替代 `/trace/reasoning` 双字段验收。
- [ ] `/trace/reasoning`:DeepSeek thinking 开启时应有 `reasoningAvailable=true` 且 `reasoningContent` 非空;`assistantText` 有正文和/或 tool-call 计划;`contentSource` 合理;**无** tool 结果体。
- [ ] ToolInvocation 全部属于 exact runId,Tool 名在 ACI allowlist 内;`step_id` 能挂到对应 `agent_step.id`。
- [ ] ToolInvocation input/output/retrieval details 不含 SQL 全文、raw response 或 evidence body 泄露(query 等允许的有界字段除外)。
- [ ] Run 的模型/Tool/Token/字节预算均未超过集中配置。
- [ ] content 只出现一次并来自 Release Policy;failure 与 content 互斥。
- [ ] `logs/application.log` 不含 Prompt、Thought、完整 Tool 参数、raw response、vendor exception 或 stack 泄漏。
- [ ] query_logs 标记为 Mock;query_mysql 只使用隔离只读 datasource contract。
- [ ] `logs/application.log` 不含 Prompt、完整 reasoning 原文、完整 Tool 参数、raw response、vendor exception 或 stack 泄漏。
- [ ] query_logs 标记为 Mock;query_mysql 仅在配置了隔离 datasource 时注册。
@@ -40,8 +40,8 @@ Fallback 必须在不暴露 Prompt、原始 Draft、原始 Tool 载荷和内部
|---|---|---|---|
| 阶段 1:Diagnosis Agent 硬停止策略 | 已完成 | ISS-016 已实现 Tool Scope 归一化与去重、`GAINED/NO_GAIN` 信息增益、连续 `NO_GAIN` 饱和停止、连续协议错误 `PROGRESS_PROTOCOL_VIOLATED` 受控停止、可修正协议 observation、受控 Fallback、回归测试和真实 E2E | 无;阶段 1 停止策略已收口 |
| 阶段 2:Evidence Repair Schema | 未完成 | Repair 仍保持无 Tool、有限重试、失败后安全降级;Diagnosis Agent 最终输出已使用真实 `DiagnosisDraft` Schema | Evidence Repair 自身尚未注入真实 `DiagnosisDraft` JSON Schema,仍主要依赖 Prompt 文本约束和严格解析 |
| 阶段 3:Reasoning 审计验证与治理 | 部分完成 | V015、独立 `agent_reasoning_audit`、审计 Hook、`reasoning_available=false`、受限查询接口和 `sessionId + runId` 归属校验已实现 | 真实 Provider reasoning metadata 行为、V015 真实迁移验收、访问控制、保留期限和加密要求尚未收敛 |
| 阶段 4:Fallback 信息质量与最终 E2E | 大部分完成 | 已实现有界 `observed_facts`、`validation_issues`、证据范围/缺口和差异化 Fallback;已完成 Diagnosis SUCCESS、信息不足 FALLBACK、Trace/Tool/Token 对账 E2E | reasoning unavailable 场景及 Reasoning Audit 跨表精确核验尚未完成;全部失败路径仍需最终综合验收 |
| 阶段 3:Reasoning 审计验证与治理 | 大部分完成 | V015–V017;`reasoning_content` + `assistant_text` + `content_source`;Hook 优先读 `DeepSeekAssistantMessage.getReasoningContent()`;受限查询返回双字段;`sessionId + runId` 归属校验;2026-07-28 DeepSeek live E2E 两步均 `reasoning_available=true` 且双字段非空;V016 `diagnosis_run.conclusion` | 访问控制、保留期限、加密;非 DeepSeek Provider 回归;reasoning unavailable 专项场景归档 |
| 阶段 4:Fallback 信息质量与最终 E2E | 大部分完成 | 已实现有界 `observed_facts`、`validation_issues`、证据范围/缺口和差异化 Fallback;已完成 Diagnosis SUCCESS、信息不足 FALLBACK、Trace/Tool/Token 对账 E2E;SUCCESS 路径已跨表核验 reasoning 双字段与 run.conclusion | reasoning unavailable 场景归档;全部失败路径仍需最终综合验收 |
本表是当前进度事实,下面各阶段条目仍保留为完整目标。ISS-016 完成的是阶段 1 和阶段 4 的主体能力,并新增模型 Token 与 Tool 拒绝审计;它不替代阶段 2 的 Repair Schema,也不代表阶段 3 的 Reasoning 治理已经完成。
@@ -70,18 +70,20 @@ Fallback 必须在不暴露 Prompt、原始 Draft、原始 Tool 载荷和内部
- 保持 Repair 无 Tool、最多一次、失败即安全 Fallback 的现有边界。
- 覆盖合法修复、Schema 非法、解析失败和二次 EvidenceGuard 失败。
### 阶段 3:Reasoning 审计验证与治理(部分完成)
### 阶段 3:Reasoning 审计验证与治理(大部分完成)
- 使用真实 Provider 验证 reasoning metadata 的键和返回行为。
- Provider 不返回 reasoning 时写入 `reasoning_available=false`,不得伪造内容。
- 验证 V015 数据库迁移和 `sessionId + runId` 精确 reasoning 查询。
- 明确 reasoning 审计接口的访问控制、保留期限和加密要求。
- [x] 使用真实 DeepSeek Provider 验证 thinking 返回路径:专用字段 `DeepSeekAssistantMessage.reasoningContent`(非 metadata)。
- [x] 同表同时落库 `assistant_text`(正文 / tool-call 计划)与 `content_source`(V017)。
- [x] Provider 不返回 reasoning 时写入 `reasoning_available=false`,不得伪造内容(单测覆盖;live unavailable 场景可再归档)。
- [x] 验证 V015–V017 迁移与 `sessionId + runId` 精确 reasoning 查询(含双字段 API)。
- [ ] 明确 reasoning 审计接口的访问控制、保留期限和加密要求。
### 阶段 4:Fallback 信息质量与最终 E2E(大部分完成)
- 工具成功但无法构造 verified snapshot 时,返回有界 `observed_facts` 和 `validation_issues`。
- 确保普通 Trace 只记录 `reasoning_available/reasoning_bytes`,不返回 reasoning 原文。
- 运行至少一个 Diagnosis SUCCESS、一个信息化 FALLBACK 和一个 reasoning unavailable 的真实 E2E。
- 确保普通 Trace 只记录 `reasoning_available/reasoning_bytes` 等有界 metadata,不返回 reasoning/assistant 原文;`run.conclusion` 可为业务结论读出。
- [x] Diagnosis SUCCESS 真实 E2E:SSE + Trace + Reasoning 双字段 + ToolInvocation + `diagnosis_run.conclusion` 对账(2026-07-28)。
- [ ] 再归档一个 reasoning unavailable 与信息化 FALLBACK 的跨表精确核验样本。
- 按 exact `sessionId + runId` 核对 SSE、Trace、Reasoning Audit、ToolInvocation 和 Run 终态。
## 6. 验收标准
@@ -1,13 +1,18 @@
# Agent 推理审计表:agent_reasoning_audit
**状态**:当前独立敏感审计表;治理待 ISS-015 收敛
**来源**:`V015__create_agent_reasoning_audit.sql`、`AgentReasoningAudit`
**状态**:当前独立敏感审计表;治理待 ISS-015 收敛
**来源**:`V015__create_agent_reasoning_audit.sql`、`V017__add_content_source_to_agent_reasoning_audit.sql`、`AgentReasoningAudit`、`HarnessAgentAuditHook`
## 定位
`agent_reasoning_audit` 保存模型 Provider 在 Agent 步骤 metadata 中实际返回的 reasoning 内容。它与普通 Trace、AgentStep、Evidence Snapshot 和业务发布结果物理分离,不能作为事实证据或诊断结论来源。
`agent_reasoning_audit` 按 **Diagnosis Agent 每个模型步骤** 保存两类 LLM 面向文本,并与普通 Trace、AgentStep、Evidence Snapshot、业务发布结果物理分离:
Provider 未返回 reasoning 时仍写入 unavailable 记录,防止把“没有返回”误判为“审计链路漏写”。系统不得从最终回答、Tool 调用或其他字段生成伪 reasoning。
1. **Provider reasoning / thinking**(`reasoning_content`)
2. **Assistant 可见正文**(`assistant_text`:最终 prose 和/或 tool-call **计划**)
它不能作为事实证据或诊断结论来源。Tool **结果**载荷不进本表(见 `tool_invocation`)。
Provider 未返回 reasoning 时仍写入记录(`reasoning_available=false`),防止把“没有返回”误判为“审计链路漏写”。系统不得从最终回答、Tool 调用或其他字段生成伪 reasoning。
## 字段
@@ -19,8 +24,10 @@ Provider 未返回 reasoning 时仍写入 unavailable 记录,防止把“没
| `step_index` | INT | 是 | Agent 模型步骤序号 |
| `agent_name` | VARCHAR(64) | 是 | Agent 身份,当前为 `diagnosis_agent` |
| `reasoning_available` | BOOLEAN | 是 | Provider 是否实际返回非空 reasoning |
| `reasoning_content` | LONGTEXT | 否 | Provider reasoning 原文;Hook 当前最多保留 32000 个字符 |
| `content_bytes` | INT | 是 | 截断后 reasoning 的 UTF-8 字节数;unavailable 时为 0 |
| `reasoning_content` | LONGTEXT | 否 | Provider CoT / thinking 原文;Hook 单字段最多保留 32000 字符 |
| `assistant_text` | LONGTEXT | 否 | 本步 assistant 可见正文;若有 tool_call,可附带 `tool_calls:` 计划预览(**不含** tool 结果) |
| `content_source` | VARCHAR(64) | 否 | 本步写入摘要:`PROVIDER_REASONING+ASSISTANT_TEXT` / `PROVIDER_REASONING` / `ASSISTANT_TEXT` / `TOOL_CALL_PLAN` / `NONE` |
| `content_bytes` | INT | 是 | 截断后 `reasoning_content` + `assistant_text` 的 UTF-8 字节合计 |
| `created_at` | DATETIME | 是 | 创建时间,默认当前时间 |
## 索引
@@ -36,15 +43,36 @@ Provider 未返回 reasoning 时仍写入 unavailable 记录,防止把“没
- `session_id` 逻辑关联 `chat_session.session_id`。
- 通过 `session_id + run_id + step_index` 与 `agent_step` 逻辑对应,不建立数据库外键。
## 写入规则
## 写入规则(HarnessAgentAuditHook)
- Provider 返回非空 reasoning:`reasoning_available=true`,保存截断后的原文及实际 UTF-8 字节数。
- Provider 未返回 reasoning:`reasoning_available=false`,`reasoning_content=NULL`,`content_bytes=0`。
- `agent_step.thought` 继续保持为空;普通 Trace 仅保留 availability/bytes metadata。
- Reasoning 不进入 SSE、PreviousTurn、Evidence Snapshot、发布结果或应用日志。
### 捕获来源(按优先级)
1. **DeepSeek 生产主路径**:`DeepSeekAssistantMessage.getReasoningContent()`
(Spring AI 将 API 的 `message.reasoning_content` 映射到该专用字段,**不是** `AssistantMessage.metadata`)
2. 反射 `getReasoningContent()`(兼容序列化/子类)
3. metadata 键兜底:`reasoning_content` / `reasoningContent` / `reasoning` / `thinking` / `reasoning_text` / `reasoningText`(单测与其它 Provider)
`assistant_text` 来自 `AssistantMessage.getText()`;若本步有 tool_calls,追加有界的 `tool_calls:` 名称与 args 预览。
### 落库语义
- 有 Provider reasoning:`reasoning_available=true`,写入截断后的 `reasoning_content`。
- 无 Provider reasoning:`reasoning_available=false`,`reasoning_content=NULL`;仍可写入 `assistant_text`。
- `content_source` 反映本步实际写入组合;两者皆空则为 `NONE`。
- `content_bytes` 为两列截断后文本 UTF-8 字节之和。
- **禁止**把 tool 执行结果、raw Tool response、用户 Prompt 全文写入本表。
### 与其它表的边界
- 普通 Trace / Timeline 只暴露 `reasoning_available`、`reasoning_bytes` / `assistant_bytes`、`content_source` 等有界 metadata,不返回 reasoning/assistant 原文。
- `agent_step.model_input` / `model_output` 仍为有界 metadata。
- `agent_step.thought` 可作为兼容镜像:优先 Provider reasoning,否则 assistant 正文;完整双字段以本表为准。
- Reasoning / assistant 审计原文不进入 SSE、PreviousTurn、Evidence Snapshot、发布结果或应用日志。
## 查询与治理
当前独立接口为 `GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}`。`runId` 必填,服务端校验其属于 path `sessionId`,并按 `step_index` 返回记录。
当前独立接口为 `GET /api/diagnosis/{sessionId}/trace/reasoning?runId={runId}`。`runId` 必填,服务端校验其属于 path `sessionId`,并按 `step_index` 返回记录(含 `reasoningContent`、`assistantText`、`contentSource`)。
分表和归属校验已经实现,但不能等同于完整安全治理。身份认证、角色授权、保留/删除期限、静态与传输加密以及真实 Provider/V015 验证仍由 ISS-015 阶段 3 跟踪;治理完成前不应将该接口暴露给普通业务用户。
**已验证(2026-07-28 live E2E,DeepSeek `deepseek-v4-flash`)**:thinking 模式下两步模型调用均可得到 `reasoning_available=true` 且 `content_source=PROVIDER_REASONING+ASSISTANT_TEXT`。
分表和归属校验已经实现,但不能等同于完整安全治理。身份认证、角色授权、保留/删除期限、静态与传输加密仍由 ISS-015 跟踪;治理完成前不应将该接口暴露给普通业务用户。
+11 -7
View File
@@ -1,11 +1,13 @@
# Agent 步骤表:agent_step
**状态**:当前表
**来源**:`V005__create_session_storage.sql`、`V006__fix_agent_step_json_to_text.sql`、`AgentStep`
**来源**:`V005__create_session_storage.sql`、`V006__fix_agent_step_json_to_text.sql`、`AgentStep`、`HarnessAgentAuditHook`
## 定位
`agent_step` 记录 Diagnosis Agent 模型步骤的有界审计 metadata。`run_id` 是执行隔离边界;当前写入不得保存 Prompt、消息正文、模型正文、Tool arguments 或 Thought。Provider reasoning 使用独立 `agent_reasoning_audit` 表,不复用历史 `thought` 字段。
`agent_step` 记录 Diagnosis Agent 模型步骤的有界审计 metadata。`run_id` 是执行隔离边界;当前写入不得保存完整 Prompt、完整消息列表、Tool 结果体。
**Provider reasoning 与 assistant 正文的完整双字段** 在独立表 `agent_reasoning_audit`;本表只保留步骤级摘要与兼容镜像。
## 字段
@@ -17,8 +19,8 @@
| `step_index` | INT | 是 | 步骤序号,从 0 开始 |
| `agent_name` | VARCHAR(32) | 是 | 当前 Harness 写入固定为 `diagnosis_agent` |
| `model_input` | TEXT | 否 | JSON metadata,仅包含 message count 与 roles |
| `model_output` | TEXT | 否 | JSON metadata,仅包含 text presence 与 Tool names |
| `thought` | TEXT | 否 | 当前 Harness 必须写空;字段仅保留历史兼容 |
| `model_output` | TEXT | 否 | JSON metadata:`has_text`、`tool_names`、`reasoning_available`、`reasoning_bytes`、`assistant_bytes`、`content_source` |
| `thought` | TEXT | 否 | 兼容镜像:优先 Provider reasoning,否则 assistant 正文;**完整双字段以 `agent_reasoning_audit` 为准** |
| `has_tool_call` | BOOLEAN | 否 | 本步骤是否触发工具调用 |
| `duration_ms` | INT | 否 | 本步骤耗时 |
| `token_count` | INT | 否 | 本步骤 Token 消耗 |
@@ -36,12 +38,14 @@
- `agent_step.run_id` 逻辑关联 `diagnosis_run.run_id`。
- `agent_step.session_id` 保留为 `chat_session.session_id` 的冗余关联,便于粗粒度过滤和兼容查询。
- `tool_invocation.step_id` 可关联 `agent_step.id`,但当前允许为空且不强制外键。
- `tool_invocation.step_id` 应关联当前模型步骤的 `agent_step.id`(由 `AgentStepAuditTracker` 在 beforeModel 绑定)。
- `agent_reasoning_audit` 通过相同的 `session_id + run_id + step_index` 逻辑定位模型步骤,不建立数据库外键。
## 注意点
- 前端展示步骤时应使用 Trace API 返回顺序;服务端会在同一 `run_id` 范围内整理步骤顺序。
- 新 Trace 和验收读路径必须按 exact `run_id` 取数,避免同一 `sessionId` 多次运行混入。
- 当前 Run 若出现 `diagnosis_agent` 之外的新写入,或 `thought` 非空,视为审计边界违规。
- `model_output` 可保存 `has_text`、`tool_names`、`reasoning_available` 和 `reasoning_bytes` 等有界 metadata,不得保存 reasoning 原文。
- 当前 Run 若出现 `diagnosis_agent` 之外的新写入,视为审计边界违规。
- `model_output` **不得**保存 reasoning/assistant 原文;只允许有界 metadata。
- `thought` 非空 **不再**视为违规:它是受限镜像,便于旧读路径一眼看到“本步在想什么/说什么”;敏感完整审计仍走 `/trace/reasoning`。
- 需要同时查看 thinking 与 assistant 正文时,必须查 `agent_reasoning_audit` 或 reasoning Trace API。
+5 -3
View File
@@ -1,7 +1,7 @@
# 诊断运行表:diagnosis_run
**状态**:当前诊断运行主表
**来源**:`V011__add_session_run_isolation.sql`、`DiagnosisRun`
**来源**:`V011__add_session_run_isolation.sql`、`V012__add_chat_release_contract.sql`、`V016__add_conclusion_to_diagnosis_run.sql`、`DiagnosisRun`
## 定位
@@ -15,9 +15,10 @@
| `run_id` | VARCHAR(64) | 是 | 运行唯一 ID,格式为 `run-` + UUID |
| `session_id` | VARCHAR(64) | 是 | 所属 `chat_session.session_id` |
| `query` | TEXT | 是 | 本次 Chat 用户问题 |
| `conclusion` | TEXT | 否 | 从安全发布内容提取的短结论,与 `query` 并列便于读出;非 reasoning 原文 |
| `status` | VARCHAR(16) | 否 | `PENDING`、`RUNNING`、`SUCCESS`、`FALLBACK`、`FAILED` 或 `CANCELLED` |
| `agent_flow` | VARCHAR(32) | 否 | 历史兼容字段;当前公开执行统一来自 Chat Harness |
| `answer` | LONGTEXT | 否 | 安全发布的最终文本兼容字段 |
| `answer` | LONGTEXT | 否 | 安全发布的最终内容 JSON/文本兼容字段 |
| `intent` | VARCHAR(32) | 否 | `SYSTEM_CHAT`、`KNOWLEDGE_QUERY` 或 `DIAGNOSIS` |
| `release_outcome` | VARCHAR(16) | 否 | `SUCCESS`、`FALLBACK`、`FAILED` 或 `CANCELLED` |
| `published_result` | JSON | 否 | Release Policy 允许发布的结构化安全结果 |
@@ -55,4 +56,5 @@
- latest run 排序使用 `created_at DESC, id DESC`,避免 feedback 或自评估更新 `updated_at` 后改变回放目标。
- 历史 `diagnosis_session` 会被迁移成兼容 run,但旧混合数据不能被还原成真实多轮边界。
- 当前诊断发布结果以 `release_outcome + published_result` 为准,不得从旧 self-evaluation 推断 Release Policy 结果。
- `diagnosis_run` 不保存 reasoning 原文;普通 Run/Trace 查询也不得通过聚合将其带出。
- `conclusion` 由 `RunConclusionExtractor` 在 `finish` 时从 `answer`(安全发布 JSON)提取:优先 `report.conclusion.text`,否则 fallback 的 `type + message` 等;最多约 4000 字符。它是 **业务结论读出字段**,不是 Provider thinking。
- `diagnosis_run` 不保存 reasoning 原文;普通 Run/Trace 查询也不得通过聚合 `agent_reasoning_audit` 将其带出。Trace 的 `run.conclusion` 可以返回上述短结论。