# 问题排查助手模块 — 会话交接文档（trouble_handoff）

> 模块：问题排查助手（troubleshoot） ｜ 分支：troubleshoot-ai-assistant
> 交接日期：2026-07-17 ｜ 上次会话末态 commit：`b94d0230`（离线方案落地）
> 范围说明：本文档**仅覆盖问题排查助手模块**。service_monitor / service_manage 模块的进展见各自交接文档。命名 `trouble_handoff.md` 以区分模块。

---

## 1. 模块定位

问题排查助手是运行维护平台的三个模块之一，其余两个为服务管理、服务监测。

- **核心功能**：用户输入问题描述 → TF-IDF 搜索知识库（357 条历史案例）→ 调 Claude API 生成只读排查步骤与根因分析
- **本次交付**：在 `OFFLINE_MODE=true` 时跳过 Claude API，直接返回 TF-IDF 匹配结果（离线 Q&A 模式）+ Docker 容器化部署能力
- **运行环境**：Flask 进程，与 service_monitor / service_manage 同进程共 Blueprint（见 §6 隔离边界）

---

## 2. 本期完成事项（b94d0230 提交内容）

### 2.1 离线 Q&A 模式
| 文件 | 变更 | 说明 |
|------|------|------|
| `skill/code/web/utils/offline_config.py` | 新增 | `is_offline_mode()` 读 `OFFLINE_MODE` env，进程级缓存；`reset_for_test()` 测试重置 |
| `skill/code/web/services/ai_service.py` | 修改 | 新增 `build_offline_response(matched_cases)`，兼容两种输入格式（原始 record / 已格式化 dict） |
| `skill/code/web/routes/troubleshoot.py` | 修改 | 4 处离线分支：`/api/troubleshoot`、`/api/analyze`、`/api/analyze/stream`、`/api/health` |

**关键设计决策**（务必记住，避免下次返工）：
1. **离线搜索用 TF-IDF，不用向量搜索**——向量预计算要联网调 `/v1/embeddings` API 扣费，离线环境做不了。TF-IDF 纯本地零费用。
2. **流式接口 `/api/analyze/stream` 保留 SSE 协议**——离线模式单帧推送完整结果后 `close`，前端解析逻辑无需改动。
3. **离线跳过缓存读写**——离线 <1s 响应，`cache_manager` 空跑无意义，离线分支直接 return 不走 cache。
4. **`OFFLINE_MODE` 走环境变量**（与 SECRET_KEY 一致），不从 config.json 读。启动时读一次，改了要重启容器。
5. **`build_offline_response` 接收「已格式化 dict 列表」**——troubleshoot 路由先调 `format_matched_cases()` 再传入；analyze 路由入参已是格式化格式，直接传入。两路由复用同一函数，不各自重写。

### 2.2 容器化部署
| 文件 | 说明 |
|------|------|
| `Dockerfile` | python:3.10-slim，WORKDIR `/app/web`，gunicorn `-w 4` 加载 `server:app` |
| `docker-compose.yml` | restart always，records/users/config 只读挂载，logs 持久化 |
| `.dockerignore` | **排除 `搜索向量.json`**（强制容器走 TF-IDF，避免 SearchEngine 静默走向量模式）+ 排除测试/缓存/临时 |
| `requirements.txt`（仓库根） | 镜像依赖，含 cryptography/paramiko（见 §6） |
| `.env.example` | 补 `OFFLINE_MODE=true` 说明 |

### 2.3 文档
| 文件 | 说明 |
|------|------|
| `Docs/离线部署方案/PRD_需求文档_离线部署方案_问题排查助手.md` | PRD v2.2（含 §4.6 同进程依赖耦合说明） |
| `Docs/离线部署方案/PRD_需求文档_离线部署方案_问题排查助手_计划执行.md` | 执行计划，T1-T8 任务表 |

### 2.4 测试
- 新增 `skill/code/tests/test_offline_mode.py`，**22 用例全绿**
- 覆盖：offline_config 解析（11 参数化用例）/ build_offline_response（4 用例）/ 路由离线分支（含 SSE）/ 在线模式回归
- 全量回归 **218 用例全绿**（问题排查助手 167 + service_monitor 51）

---

## 3. 当前状态快照

### 3.1 代码层
- `server.py:107` 已有模块级 `app = create_app()` → gunicorn `server:app` 可直接加载，无需改
- 三个被测模块（`search_engine.py` / `safety_filter.py` / `cache_manager.py`）公开接口**未动**
- 离线改造**未碰** `service_manage.py` / `service_monitor.py`（隔离红线遵守）

### 3.2 测试基线
```
cd skill/code && python -m pytest -v
# 期望：218 passed
# 离线专项：python -m pytest tests/test_offline_mode.py -v  → 22 passed
```

### 3.3 Git 状态
- 分支 `troubleshoot-ai-assistant`
- 最近提交：
  - `b94d0230` feat(offline): 问题排查助手离线 Q&A 模式和容器化部署 ← **本模块本次交付**
  - `c552a6b3` feat(service-monitor): 落地服务监测模块（service_monitor 组，非本模块）
- 工作区：本次交接时干净

---

## 4. 待办 / 下一步

| # | 事项 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | **本地容器构建验证（T6）** | 中 | 需要 Docker 环境。`docker build -t troubleshoot:offline .` → run → 验 `/api/health` 返回 `offline_mode:true` 且 `search.mode:tfidf` |
| 2 | **部署到 5.60（T7）** | 中 | 5.60 已装 Docker，但当前会话网络不可达。`docker save | gzip` → scp → `docker load` → `docker compose up -d` |
| 3 | **前端适配 `offline:true`** | 低 | 后端已就绪，前端 index.html 需读 `offline:true` 显示「当前为离线模式」横幅；`analysis:null` 显示「无 AI 分析」提示。本期后端就绪即可，前端另行处理 |
| 4 | **gunicorn WORKDIR 实测** | 中 | Dockerfile WORKDIR 设为 `/app/web` 是推算值，T6 构建后看 `docker run` 启动日志确认能加载 `server` 模块 |
| 5 | **requirements 版本在 5.60 实测** | 低 | 现版本 `cryptography==46.0.7` / `paramiko==4.0.0` 以本地实测为准，5.60 构建时若解析失败再调 |

---

## 5. 关键坑点（踩过，不要再踩）

1. **向量文件静默降级**：`搜索向量.json` 存在时 SearchEngine 会静默走 vector 模式，与「离线用 TF-IDF」矛盾。离线镜像**必须排除该文件**（.dockerignore 已处理）。部署后务必查 `/api/health` 的 `search.mode` 确认是 `tfidf`。详见 CLAUDE.md 避坑第 7 条。
2. **同进程依赖连累**：service_monitor 的 `utils/crypto.py` 模块级 `from cryptography.fernet import Fernet`，在 `create_app()` 阶段执行。镜像若缺 cryptography，**问题排查助手也起不来**。故 requirements 必须含 cryptography/paramiko（已补，见 §6）。
3. **analyze 与 troubleshoot 的 matched_cases 格式不同**：analyze 收前端传来的已格式化 dict（含 rank/score/project/title/file/phenomenon，**无 solution**）；troubleshoot 是 engine.search() 原始结构（含 record，有 solution）。`build_offline_response` 已兼容两者，但改这个函数时要注意两条路径都测。
4. **`/api/analyze/stream` 是 GET 不是 POST**：参数走 URL query string。PRD 早期误写成 POST `/api/analyze_stream`，实际路由是 GET `/api/analyze/stream`。改路由时别改错。
5. **gunicorn 找模块靠 WORKDIR**：`server:app` 要求 gunicorn 进程 CWD 在含 `server.py` 的目录（`/app/web`）。WORKDIR 设错会报 `Failed to find application object 'app'`。
6. **offline_config 是进程级缓存**：改 `OFFLINE_MODE` env 后，已运行的进程不会感知，必须重启。测试时用 `reset_for_test()` 刷新。

---

## 6. 模块隔离边界（与 service_monitor 的关系）

**部署层隔离，非进程隔离**——这是最重要的架构事实。

- **代码层**：问题排查助手（`routes/troubleshoot.py` + `services/ai_service.py` + `utils/offline_config.py`）与 service_monitor 子包**无任何相互 import**。删掉 service_monitor，离线测试 22 用例不受影响（已验证）。
- **运行时**：同处一个 Flask 进程（`server.py::create_app()` 一次性注册全部 Blueprint）。邻居模块导入失败会连累整个进程启动。
- **依赖**：service_monitor 的 cryptography（模块级）/ paramiko（延迟）依赖必须随镜像一起提供，已补入仓库根 `requirements.txt`。
- **PRD §4.6** 如实记录了这个设计妥协：部署层隔离 ≠ 进程隔离。

**隔离红线**（下次改离线功能时仍要遵守）：
1. 改动范围仅限 `routes/troubleshoot.py`、`services/ai_service.py`、`utils/offline_config.py`
2. `OFFLINE_MODE` 只作用于问题排查助手路由，不影响 service_manage / service_monitor
3. 共享 `utils/*` / `auth.py` / `platform.py` 改动须对三模块无差别兼容
4. **禁止改** `service_manage.py` / `service_monitor.py`

---

## 7. 关键文件速查

```
问题排查助手代码：
  skill/code/web/utils/offline_config.py        # 离线开关
  skill/code/web/services/ai_service.py         # build_offline_response()
  skill/code/web/routes/troubleshoot.py         # 4 处离线分支
  skill/code/tests/test_offline_mode.py         # 22 用例

容器化：
  Dockerfile / docker-compose.yml / requirements.txt / .dockerignore  (仓库根)

文档：
  Docs/离线部署方案/PRD_需求文档_离线部署方案_问题排查助手.md        # PRD v2.2
  Docs/离线部署方案/PRD_需求文档_离线部署方案_问题排查助手_计划执行.md  # 执行计划
```

---

## 8. 环境变量清单（问题排查助手相关）

| 变量 | 用途 | 默认 |
|------|------|------|
| `OFFLINE_MODE` | 离线开关（true/1/yes → 离线） | 未设=在线 |
| `SECRET_KEY` | Flask session 密钥 | 未设则随机生成（生产必须设） |
| `CLAUDE_API_BASE` | Claude API 地址（在线模式用） | — |
| `CLAUDE_API_KEY` | Claude API key（在线模式用） | — |
| `FLASK_DEBUG` | 调试模式（1=开） | 1 |

---

## 9. 上次会话遗留的待确认项

- **无未决问题**。离线方案所有设计决策已在 PRD v2.2 闭合，代码已提交 b94d0230，测试全绿。
- 唯一悬而未决的是 **T6/T7 的部署验证**——等 Docker 环境就绪即可执行，不阻塞代码层。

---

> 本文档由问题排查助手模块开发者视角输出，便于下次会话追溯。service_monitor 模块的进展不在此列。
