Skip to content

Commit f7fbdda

Browse files
authored
feat: add ZhuLinsen#1391 Phase 2 run diagnostic summaries (ZhuLinsen#1444)
* fix(issue-1412): [bug]-stock_list格式问题 * fix(review-feedback-1413): preserve exchange hint for dotted A-share inputs * fix(review-feedback-1413): Keep normalized A-share codes usable by market routing and preserve * fix(review-feedback-1413): Limit raw dotted codes to fetchers that can parse them * fix(review-feedback-1413): Keep Tushare daily input normalized for ETF detection * fix(review-feedback-1413): 澄清结构化检测中的外部模型/API 与运行时配置迁移风险 * fix(review-feedback-1413): 处理或明确确认该失败与本 PR 无关且已有维护者豁免依据 * fix: keep stock list input as bare codes * docs: add phase-0 run diagnostics contract * fix(review-feedback-1435): 修正描述并澄清/补齐运行时代码变更的验证证据 * fix(review-feedback-1435): 补齐前缀提示识别,并增加对应回归测试 * fix(review-feedback-1435): 修正 * fix(review-feedback-1435): 解决冲突并更新描述/验证记录后再合入 * fix(review-feedback-1435): 解决冲突,并在最终 head 上重新确认 python -m pytest tests/test a share fetcher code * fix(review-feedback-1435): 修复并补齐回归覆盖后再复核最终 head * fix(review-feedback-1435): data provider/baostock fetcher.py 的 convert stock code 只从 .SH/.SS/.SZ * fix: preserve A-share exchange hints * fix(review-feedback-1435): 修正 docs/run-diagnostics-p0.md 对 Tushare 本轮范围的矛盾描述 * feat: add phase 1 run diagnostics trace plumbing * feat: add phase 2 run diagnostic summaries * fix(review-feedback-1441): 打通 trace id 与数据源运行快照,改动目标明确 * fix(review-feedback-1444): 修复 Agent 模式新报告通过历史诊断 API 返回 unknown 的正确性问题 * fix(review-feedback-1444): Propagate diagnostics lookup errors instead of masking them * fix(review-feedback-1444): 补对应回归断言 * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突 * fix(review-feedback-1444): 解决冲突 * fix(review-feedback-1444): 解决 * fix(review-feedback-1444): 解决 * fix(review-feedback-1444): 解决冲突 * fix(review-feedback-1444): 解决 * fix(review-feedback-1444): 解决 * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突 * fix(review-feedback-1444): Derive news diagnostics from retrieval evidence * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突并基于解决后的最终 diff 重新确认 docs/CHANGELOG.md、诊断链路和测试结果 * fix(review-feedback-1444): 解决冲突 * fix(review-feedback-1444): 解决冲突后再合入 * fix(review-feedback-1444): 解决冲突并重新跑阻断型 CI,尤其是 backend-gate 和相关诊断/API/history 回归 * fix(review-feedback-1444): preserve report timestamp when enriching task results * fix: address run diagnostics review feedback * fix: redact diagnostic copy text secrets * fix(review-feedback-1444): 补一条多渠道部分失败的回归测试
1 parent d10070f commit f7fbdda

17 files changed

Lines changed: 2021 additions & 63 deletions

api/v1/endpoints/analysis.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
DuplicateTaskError,
7171
TaskStatus as TaskStatusEnum,
7272
)
73+
from src.services.run_diagnostics import build_run_diagnostic_summary
7374
from src.utils.data_processing import (
7475
normalize_model_used,
7576
parse_json_field,
@@ -465,6 +466,7 @@ def _handle_sync_analysis(
465466
stock_code=result.get("stock_code", stock_code),
466467
stock_name=result.get("stock_name"),
467468
report=report.model_dump() if report else None,
469+
diagnostic_summary=result.get("diagnostic_summary"),
468470
created_at=datetime.now().isoformat()
469471
)
470472

@@ -728,6 +730,18 @@ def _extract_report_created_at(payload: Dict[str, Any]) -> Optional[str]:
728730
return _datetime_to_iso(meta.get("created_at"))
729731

730732

733+
def _prepare_report_for_task_enrichment(
734+
report_data: Dict[str, Any],
735+
created_at: Optional[str],
736+
) -> Dict[str, Any]:
737+
enriched_report = dict(report_data)
738+
meta = dict(enriched_report.get("meta") or {})
739+
if created_at and not _datetime_to_iso(meta.get("created_at")):
740+
meta["created_at"] = created_at
741+
enriched_report["meta"] = meta
742+
return enriched_report
743+
744+
731745
def _build_task_analysis_result(task: Any) -> AnalysisResultResponse:
732746
"""
733747
Normalize an in-memory completed task result to the public API contract.
@@ -766,7 +780,10 @@ def _build_task_analysis_result(task: Any) -> AnalysisResultResponse:
766780
if context_snapshot is not None or fundamental_snapshot is not None:
767781
try:
768782
report = _build_analysis_report(
769-
report_data,
783+
_prepare_report_for_task_enrichment(
784+
report_data,
785+
payload.get("created_at"),
786+
),
770787
query_id,
771788
stock_code,
772789
payload.get("stock_name") or getattr(task, "stock_name", None),
@@ -960,6 +977,13 @@ def get_analysis_status(task_id: str) -> TaskStatus:
960977
stock_code=record.code,
961978
stock_name=stock_name,
962979
report=report_dict,
980+
diagnostic_summary=build_run_diagnostic_summary(
981+
context_snapshot=context_snapshot,
982+
raw_result=raw_result,
983+
report_saved=True,
984+
query_id=task_id,
985+
stock_code=record.code,
986+
),
963987
created_at=record.created_at.isoformat() if record.created_at else datetime.now().isoformat()
964988
),
965989
error=None,

api/v1/endpoints/history.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ReportStrategy,
2929
ReportDetails,
3030
MarkdownReportResponse,
31+
RunDiagnosticSummaryResponse,
3132
)
3233
from api.v1.schemas.common import ErrorResponse
3334
from src.storage import DatabaseManager
@@ -329,6 +330,49 @@ def get_history_detail(
329330
)
330331

331332

333+
@router.get(
334+
"/{record_id}/diagnostics",
335+
response_model=RunDiagnosticSummaryResponse,
336+
responses={
337+
200: {"description": "运行诊断摘要"},
338+
404: {"description": "报告不存在", "model": ErrorResponse},
339+
500: {"description": "服务器错误", "model": ErrorResponse},
340+
},
341+
summary="获取历史报告运行诊断摘要",
342+
description="根据分析历史记录 ID 或 query_id 获取用户可读诊断摘要和脱敏复制文本。",
343+
)
344+
def get_history_diagnostics(
345+
record_id: str,
346+
db_manager: DatabaseManager = Depends(get_database_manager),
347+
) -> RunDiagnosticSummaryResponse:
348+
"""
349+
获取历史报告运行诊断摘要。
350+
"""
351+
try:
352+
service = HistoryService(db_manager)
353+
summary = service.resolve_and_get_diagnostics(record_id)
354+
if summary is None:
355+
raise HTTPException(
356+
status_code=404,
357+
detail={
358+
"error": "not_found",
359+
"message": f"未找到 id/query_id={record_id} 的分析记录",
360+
},
361+
)
362+
return RunDiagnosticSummaryResponse.model_validate(summary)
363+
except HTTPException:
364+
raise
365+
except Exception as e:
366+
logger.error(f"查询运行诊断摘要失败: {e}", exc_info=True)
367+
raise HTTPException(
368+
status_code=500,
369+
detail={
370+
"error": "internal_error",
371+
"message": f"查询运行诊断摘要失败: {str(e)}",
372+
},
373+
)
374+
375+
332376
@router.get(
333377
"/{record_id}/news",
334378
response_model=NewsIntelResponse,

api/v1/schemas/analysis.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class AnalysisResultResponse(BaseModel):
126126
stock_code: str = Field(..., description="股票代码")
127127
stock_name: Optional[str] = Field(None, description="股票名称")
128128
report: Optional[Any] = Field(None, description="分析报告")
129+
diagnostic_summary: Optional[Any] = Field(None, description="运行诊断摘要")
129130
created_at: str = Field(..., description="创建时间")
130131

131132
model_config = ConfigDict(json_schema_extra={

api/v1/schemas/history.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
2. 定义分析报告完整模型
1010
"""
1111

12-
from typing import Optional, List, Any
12+
from typing import Optional, List, Any, Dict
1313

1414
from pydantic import BaseModel, ConfigDict, Field
1515

@@ -200,3 +200,41 @@ class MarkdownReportResponse(BaseModel):
200200
"content": "# 📊 贵州茅台 (600519) 分析报告\n\n> 分析日期:**2024-01-01**\n\n..."
201201
}
202202
})
203+
204+
205+
class RunDiagnosticComponent(BaseModel):
206+
"""单个运行诊断组件摘要。"""
207+
208+
key: str = Field(..., description="组件键")
209+
label: str = Field(..., description="组件显示名称")
210+
status: str = Field(..., description="组件状态:ok/degraded/failed/unknown/not_configured/skipped")
211+
message: str = Field(..., description="用户可读摘要")
212+
details: Optional[Dict[str, Any]] = Field(None, description="折叠展示的诊断细节")
213+
214+
215+
class RunDiagnosticSummaryResponse(BaseModel):
216+
"""历史报告运行诊断摘要。"""
217+
218+
trace_id: Optional[str] = Field(None, description="诊断 trace ID")
219+
task_id: Optional[str] = Field(None, description="任务 ID")
220+
query_id: Optional[str] = Field(None, description="分析 query ID")
221+
stock_code: Optional[str] = Field(None, description="股票代码")
222+
trigger_source: Optional[str] = Field(None, description="触发来源")
223+
status: str = Field(..., description="总体状态:normal/degraded/failed/unknown")
224+
status_label: str = Field(..., description="总体状态中文标签")
225+
reason: str = Field(..., description="最主要的诊断原因")
226+
components: Dict[str, RunDiagnosticComponent] = Field(default_factory=dict, description="关键链路诊断组件")
227+
copy_text: str = Field(..., description="可复制的脱敏排障文本")
228+
229+
model_config = ConfigDict(json_schema_extra={
230+
"example": {
231+
"trace_id": "task_abc123",
232+
"query_id": "task_abc123",
233+
"stock_code": "600519",
234+
"status": "degraded",
235+
"status_label": "部分降级",
236+
"reason": "实时行情失败:timeout",
237+
"components": {},
238+
"copy_text": "trace_id: task_abc123\nstock_code: 600519\n...",
239+
}
240+
})

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
2929
- [修复] macOS 桌面端将运行时配置迁移到用户数据目录,并在旧 `.app` 包内文件仍可访问时迁移 `.env`、数据库和日志,避免后续替换升级后重新配置。
3030
- [改进] 新增运行态市场阶段上下文构造与降级测试。
3131
- [文档] 新增 AnalysisContextPack P0 上下文盘点,明确字段质量状态、现有状态映射和首版 pack 边界。
32+
- [新功能] 落地 #1391 Phase 2 运行诊断摘要:生成用户可读 RunDiagnosticSummary,提供历史报告诊断 API 与脱敏复制文本。
33+
- [文档] 明确 #1391 Phase 2 的结构化检测告警为非配置迁移信号:`agent_max_steps`/`agent_orchestrator_timeout_s` 非法值会 fallback 至默认并产生日志告警,新增诊断链路仅新增 `context_snapshot`/`RunDiagnosticSummary` 读写字段,不改写 `litellm_model``agent_litellm_model``openai_base_url`、LLM channel 路由或配置迁移语义。
3234
- [新功能] 新增 AnalysisContextPack P1 内部契约与脱敏序列化测试。
3335
- [修复] 恢复 Agent/历史兼容快照中的关联板块与板块联动字段提取,修复新版首页报告缺少“板块联动”的回归问题。
3436
- [修复] 修复 Web 中文设置页在数据源、通知、系统与 Agent 区域的配置标题、说明和关键下拉选项漏翻问题。

docs/full-guide.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ daily_stock_analysis/
211211
### AI 模型配置
212212

213213
> 完整说明见 [LLM 配置指南](LLM_CONFIG_GUIDE.md)(三层配置、渠道模式、Vision、Agent、排错);常用服务商预设、Actions 变量对照和错误排障见 [LLM 服务商配置指南](llm-providers.md)
214-
> 兼容性说明(Issue #1306):本次改动只复用已有历史写入链路展示大盘复盘结果,不修改模型名、provider、Base URL、`LiteLLM` 清理/兼容语义。回退路径为回滚本版本。兼容验证来源见 `requirements.txt``litellm` 版本约束)、`docs/LLM_CONFIG_GUIDE*.md`,以及回归用例 `tests/test_analysis_api_contract.py``tests/test_analysis_history.py``tests/test_market_review.py`;官方源参考:[LiteLLM OpenAI-compatible](https://docs.litellm.ai/docs/providers/openai_compatible)[OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat)
214+
> 兼容性说明(Issue #1306/#1391):本次改动只复用已有历史写入链路展示大盘复盘结果,不修改模型名、provider、Base URL、`LiteLLM` 清理/兼容语义。回退路径为回滚本版本。兼容验证来源见 `requirements.txt``litellm` 版本约束)、`docs/LLM_CONFIG_GUIDE*.md`,以及回归用例 `tests/test_analysis_api_contract.py``tests/test_analysis_history.py``tests/test_market_review.py`;官方源参考:[LiteLLM OpenAI-compatible](https://docs.litellm.ai/docs/providers/openai_compatible)[OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat)
215+
> #1391 Phase 2 的结构化检测风险来自 `src/agent/factory.py``agent_max_steps` / `agent_orchestrator_timeout_s` int 安全兜底,属于配置读取侧的类型兼容增强,不会改写 `litellm_model``agent_litellm_model``openai_base_url``LLM_*` 路由状态;回归可复核 `tests/test_agent_pipeline.py::TestAgentConfig::test_build_agent_executor_does_not_mutate_llm_route_config``tests/test_agent_pipeline.py::TestAgentConfig::test_build_agent_executor_multi_arch_does_not_mutate_llm_route_config`。当配置值非法(如非数字)时,`src.agent.factory` 会记录 warning 并回退到默认值,便于排障与避免误判配置已生效。
215216
> 本节仅同步模型/渠道配置清单,不额外引入新的外部 provider / Base URL 兼容约定;兼容语义以当前仓库 `requirements.txt` 依赖约束和相关测试为准,历史回退路径见上述两份文档中“回退/恢复”说明。
216217
217218
| 变量名 | 说明 | 默认值 | 必填 |
@@ -1232,6 +1233,7 @@ FastAPI 提供 RESTful API 服务,支持配置管理和触发分析。
12321233
| `/api/v1/analysis/tasks/stream` | GET (SSE) | 订阅任务实时状态流 |
12331234
| `/api/v1/analysis/status/{task_id}` | GET | 查询任务状态 |
12341235
| `/api/v1/history` | GET | 查询分析历史 |
1236+
| `/api/v1/history/{record_id}/diagnostics` | GET | 查询历史报告运行诊断摘要与脱敏复制文本 |
12351237
| `/api/v1/usage/summary?period=today|month|all` | GET | 按调用类型与模型维度汇总 LLM 调用次数和 Token 用量 |
12361238
| `/api/v1/backtest/run` | POST | 触发回测 |
12371239
| `/api/v1/backtest/results` | GET | 查询回测结果(分页) |
@@ -1249,6 +1251,7 @@ FastAPI 提供 RESTful API 服务,支持配置管理和触发分析。
12491251
> 审计依据:优先级与回退语义以 `src/config.py` 的 `Config._load_from_env()` 为准(`LITELLM_CONFIG` > `LLM_CHANNELS` > legacy)。配套回归见 `tests/test_llm_channel_config.py`(配置源解析)与 `tests/test_market_review_runtime.py`(共享装配路径)。该接口当前仅提供单进程/单机级防重复能力,若为多实例部署需通过外部任务队列或分布式锁补齐全局幂等。
12501252
> 说明:`POST /api/v1/analysis/market-review` 触发后,报告会以 `report_type=market_review` 写入历史库;你可直接查询 `/api/v1/history` 或 `/api/v1/history/{record_id}` 获取历史 Markdown,避免再次触发分析重算。
12511253
> 说明:该端点若返回 `task_id`,WebUI 会轮询 `GET /api/v1/analysis/status/{task_id}` 展示状态。状态为 `completed` 时给出完成提示(报告已生成并按配置推送),状态为 `failed` 时在前端错误区域显示 `error` 原因。
1254+
> 说明:`GET /api/v1/history/{record_id}/diagnostics` 支持历史记录主键 ID 或 `query_id`,返回 `normal/degraded/failed/unknown` 摘要、关键链路组件和可复制的脱敏 `copy_text`;旧报告缺少诊断快照时返回 `unknown`,不影响报告读取。
12521255

12531256
> 兼容性审计证据:
12541257
> - 官方来源:LiteLLM OpenAI-compatible provider 文档 <https://docs.litellm.ai/docs/providers/openai_compatible>;OpenAI Chat API 文档 <https://platform.openai.com/docs/api-reference/chat/create>;DeepSeek API 文档 <https://api-docs.deepseek.com/>。

docs/full-guide_EN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ FastAPI provides RESTful API service for configuration management and triggering
10721072
| `/api/v1/analysis/tasks/stream` | GET (SSE) | Subscribe to realtime task updates |
10731073
| `/api/v1/analysis/status/{task_id}` | GET | Query task status |
10741074
| `/api/v1/history` | GET | Query analysis history |
1075+
| `/api/v1/history/{record_id}/diagnostics` | GET | Query a historical report run diagnostic summary and sanitized copy text |
10751076
| `/api/v1/usage/summary?period=today|month|all` | GET | Query LLM call counts and token usage grouped by call type and model |
10761077
| `/api/v1/backtest/run` | POST | Trigger backtest |
10771078
| `/api/v1/backtest/results` | GET | Query backtest results (paginated) |
@@ -1087,6 +1088,7 @@ FastAPI provides RESTful API service for configuration management and triggering
10871088
> Audit note: priority and fallback are defined by `Config._load_from_env()` in `src/config.py` (`LITELLM_CONFIG` > `LLM_CHANNELS` > legacy). Regression coverage is in `tests/test_llm_channel_config.py` (configuration source parsing) and `tests/test_market_review_runtime.py` (shared runtime assembly). The endpoint lock is process/host-level only; multi-instance deployments still need external distributed idempotency controls.
10881089
> Note: Once `/api/v1/analysis/market-review` completes, the report is persisted with `report_type=market_review`; open `/api/v1/history` and `/api/v1/history/{record_id}` (or Markdown history endpoints) to view it directly without re-running analysis.
10891090
> Note: when `/api/v1/analysis/market-review` returns a `task_id`, the WebUI polls `GET /api/v1/analysis/status/{task_id}`. The UI renders clear `pending/processing` progress, shows completion feedback when status becomes `completed`, and surfaces `error` content on `failed`.
1091+
> Note: `GET /api/v1/history/{record_id}/diagnostics` accepts either the history primary key ID or `query_id`, and returns a `normal/degraded/failed/unknown` summary, key pipeline components, and sanitized `copy_text`. Older reports without `context_snapshot.diagnostics` return `unknown` without affecting normal report reads.
10901092

10911093
> Compatibility audit evidence:
10921094
> - Official references: LiteLLM OpenAI-compatible provider documentation <https://docs.litellm.ai/docs/providers/openai_compatible>, OpenAI Chat API <https://platform.openai.com/docs/api-reference/chat/create>, and DeepSeek API docs <https://api-docs.deepseek.com/>.

docs/run-diagnostics-p2.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# 运行诊断与数据可靠性 1.0(Phase 2)
2+
3+
本文档记录 #1391 Phase 2 的后端落地范围:基于 Phase 1 的 `trace_id` 与 provider run 记录,生成用户可读的运行诊断摘要,并提供可复制的脱敏排障文本。
4+
5+
## 本轮范围
6+
7+
- 新增 `RunDiagnosticSummary` 聚合逻辑,输出总体状态:
8+
- `normal` / 正常
9+
- `degraded` / 部分降级
10+
- `failed` / 失败
11+
- `unknown` / 未知
12+
- 摘要覆盖以下关键链路:
13+
- 实时行情
14+
- 日线数据
15+
- 新闻搜索
16+
- LLM
17+
- 通知
18+
- 历史保存
19+
- `AnalysisService` 同步/异步任务结果追加可选 `diagnostic_summary`
20+
- 新增历史报告诊断 API:
21+
22+
```http
23+
GET /api/v1/history/{record_id}/diagnostics
24+
```
25+
26+
`record_id` 支持历史记录主键 ID 或 `query_id`,返回诊断摘要与 `copy_text`
27+
28+
## 复制排障信息
29+
30+
`copy_text` 是面向 issue/排障的纯文本,包含:
31+
32+
- `trace_id`
33+
- `query_id`
34+
- `stock_code`
35+
- `trigger_source`
36+
- 总体 `data_status`
37+
- 实时行情、日线、新闻、LLM、通知、历史保存的简短状态
38+
- 首要原因
39+
40+
生成前会复用运行诊断脱敏规则,避免输出 token、API key、Authorization、Cookie、webhook URL、邮箱密码、代理凭据等敏感信息。
41+
42+
## 兼容性边界
43+
44+
- 本轮不新增配置项,不改变数据源优先级,不改变 fallback 策略。
45+
- 本轮不改变任何 LLM/provider/Base URL/配置迁移语义,仅新增历史快照中的诊断字段与查询接口。
46+
- API 只追加可选字段和新增只读接口;旧客户端可忽略。
47+
- 旧报告没有 `context_snapshot.diagnostics` 时返回 `unknown`,不报错。
48+
- 通知诊断在当前任务上下文中记录;历史报告如果保存时尚无通知证据,会在摘要中显示通知结果未知。
49+
- 诊断摘要生成失败不得影响报告读取或分析主流程。
50+
51+
### 结构化检测告警澄清
52+
53+
- 自动化检测命中的“模型/provider/base URL 兼容风险”来源是:`src/agent/factory.py` 新增了 `agent_max_steps``agent_orchestrator_timeout_s`**数字安全兜底**`_coerce_config_int`),因此扫描可能将其误识别为配置敏感路径;该命中属于测试与路由保护触发,不是运行时配置或兼容语义变更。
54+
- 当数值配置存在非法值时,系统会记录 `warning``src.agent.factory` 日志(示例:`[AgentFactory] Invalid value for agent_max_steps...`),并回退到默认值;日志用于定位“参数未生效”类问题,与模型/provider/base URL 兼容性独立。
55+
- 本轮确认无静默迁移/清空/改写:
56+
- `src/core/pipeline.py``src/services/analysis_service.py` 仅新增诊断记录,不修改 `Config` 中任何 `litellm_model``agent_litellm_model``openai_base_url` 或 channel `LLM_*` 字段。
57+
- `src/agent/factory.py``_coerce_config_int` 只在构建执行参数时计算 `max_steps``timeout_seconds`,并且不写回到 `config` 对象;`litellm_model``agent_litellm_model``openai_base_url` 原值在构造链路中完整透传。
58+
- 本轮不触发 `Config` 的运行时清理、持久化回写或迁移流程,因此不存在写回导致运行时配置被重写的风险。
59+
- 回归验证:`tests/test_agent_pipeline.py::TestAgentConfig::test_build_agent_executor_does_not_mutate_llm_route_config``tests/test_agent_pipeline.py::TestAgentConfig::test_build_agent_executor_multi_arch_does_not_mutate_llm_route_config` 明确断言上述字段在 `build_agent_executor` 后保持原值。
60+
- 回退路径:如需恢复到旧行为,移除本轮相关提交;或将 `diag_*` 字段从 `context_snapshot`/`RunDiagnosticSummary` 的反序列化链路中移除。主链路与模型/provider 配置无需额外迁移或修复。
61+
62+
## 验证建议
63+
64+
```bash
65+
python -m pytest tests/test_run_diagnostics_p2.py tests/test_run_diagnostics_p1.py
66+
python -m py_compile src/services/run_diagnostics.py src/services/history_service.py api/v1/endpoints/history.py api/v1/schemas/history.py
67+
```

0 commit comments

Comments
 (0)