# Backend 后端指引

本文件适用于 backend/ 下的后端实现。进入 backend/tests/ 后，以子目录 AGENTS.md 为准。

## 后端职责

- 对外提供补全、取消补全、OCR、文档转换和 TTS 相关接口。
- 组织 Prompt，上下文清洗，调用 Ollama 模型。
- 负责 API Key 校验、日志记录和部分启动预热逻辑。

## 先看哪里

- API 入口和路由：main.py
- Ollama 调用封装：llm.py
- Prompt 清洗和拼装：prompt.py
- 数据模型：models.py
- 地理位置：geoip.py
- TTS 路由：tts_asr.py
- Prompt 模板：prompts/
- 后端测试：tests/

## 当前接口面

- POST /v1/completions
- POST /v1/completions/cancel
- POST /v1/ocr
- POST /v1/convert
- /v1/tts-asr/* 由 tts_asr.py 延迟注册

## 请求流转

### /v1/completions

- 读取或生成 request_id。
- privacy_mode 为 false 时，尝试根据客户端 IP 生成 location 文本。
- 调用 prepare_prompt_context 清洗 prefix 和 suffix。
- 调用 build_completion_prompts 生成 system_prompt 和 user_prompt。
- 创建异步任务调用 call_ollama。
- 用 request_id 把任务登记到 ACTIVE_COMPLETIONS。
- 成功时返回 JSON：content 和 request_id。
- finally 中清理当前 request_id 对应任务。

### /v1/completions/cancel

- 通过 request_id 在 ACTIVE_COMPLETIONS 中查找任务。
- 未找到返回 not_found。
- 已完成返回 already_done。
- 仍在执行则调用 task.cancel() 并返回 ok。

### /v1/ocr

- 把 base64 图片解码成字节。
- 调用 call_vlm_ocr。
- 返回识别文本和原始文件名。

### /v1/convert

- 接收 base64 文件内容和文件名。
- 当前允许的扩展名只有 txt、docx、pptx、pdf。
- txt 直接解码后清洗。
- 其他格式写入临时文件，用 MarkItDown 转换，再做 Markdown 清洗。
- 清洗逻辑会移除图片 Markdown 和 img HTML 标签，并压缩多余空行。

### /v1/tts-asr/*

- 通过 _register_tts_asr_routes 延迟导入并挂到主应用。
- 当前代码里的 tts_asr.py 主要是 TTS 能力，不要自行假设存在完整 ASR 实现。

## 开发命令

- 安装依赖：pip install -r backend/requirements.txt
- 启动：python backend/main.py
- 开发启动：uvicorn backend.main:app --reload --port 8001
- 路由相关测试：
	- pytest backend/tests/test_main_endpoints.py -v
	- pytest backend/tests/test_main_cancel.py -v
- Prompt 测试：
	- pytest backend/tests/test_prompt.py -v
	- pytest backend/tests/test_prompt_extended.py -v
- LLM 测试：
	- pytest backend/tests/test_llm.py -v
	- pytest backend/tests/test_llm_extended.py -v

## 编码约定

- Python 使用 4 空格缩进。
- 函数、变量使用 snake_case，类使用 PascalCase。
- 新逻辑优先保留显式类型和明确的输入输出。
- 异步边界要清晰；阻塞操作优先放进 asyncio.to_thread，而不是直接阻塞事件循环。
- 异常要么转成 HTTPException，要么转成结构化 JSONResponse；不要静默吞掉后端错误。
- 日志尽量带 request_id 或短 tag，便于把前后端一次请求串起来。

## 容易误判的点

- 补全接口当前不是流式响应，不要按 SSE 方式改造周边代码。
- ACTIVE_COMPLETIONS 在补全和取消路径里都被读写，任务生命周期要谨慎处理。
- main.py 里虽然有 _convert_docx_to_pdf 辅助函数，但当前 /v1/convert 路径实际走的是 MarkItDown，不要误以为 DOCX 转 PDF 桥接脚本已接入主流程。
- API_KEY 存在占位默认值，这更像本地开发兜底，不是推荐的安全模式。
- 历史 TTS/ASR 文档和部分测试覆盖的是旧实现；代码与文档冲突时，先确认产品方向，再决定修代码还是修文档。

## 改动时的定位建议

- 如果问题是补全结果不对，先查 prompt.py，再查 llm.py，不要只盯着 main.py。
- 如果问题是取消不生效，先查 main.py 里的 request_id 生命周期，再对照前端的 X-Request-Id 和 cancel 调用。
- 如果问题是 OCR 识别为空，先看 main.py 的 base64 解码，再看 llm.py 的 call_vlm_ocr。
- 如果问题是转换结果脏，重点看 main.py 里的 _sanitize_converted_markdown。
- 如果问题是 TTS 行为和文档不一致，以 tts_asr.py 为准，不要以 README 为准。

## 测试映射

- 路由主行为：tests/test_main_endpoints.py
- 取消逻辑：tests/test_main_cancel.py
- Prompt 逻辑：tests/test_prompt.py、tests/test_prompt_extended.py
- LLM 包装层：tests/test_llm.py、tests/test_llm_extended.py
- GeoIP：tests/test_geoip.py
- TTS 相关：tests/test_tts_asr_*.py

## 文档使用原则

- README.md、TTS_ASR_MACOS_FIX.md、tests/TESTING_GUIDE.md 可以作为背景材料。
- 一旦这些文档和 main.py、llm.py、prompt.py、tts_asr.py 冲突，以代码为准。
