# app/main.py 渐进拆分计划 - 日期:2026-04-21 - 目标文件:`app/main.py` - 当前判断:**适合拆分,但不适合一次性大拆;建议按阶段渐进拆分**。 ## 1. 目标 把 `app/main.py` 从“单文件总编排”逐步收敛为“组合根 + 路由/辅助模块”,在不破坏以下关键行为的前提下,降低文件复杂度并提高后续维护性: - OpenAI / Anthropic / Responses 三条协议路径行为一致 - session cache 命中、回写、失效语义保持不变 - 单请求固定实例绑定不变 - streaming 路径中的 in-flight ticket 释放语义不变 - SSE 帧格式、finish reason / stop reason 行为不变 - 现有测试尽量少改,尤其避免首轮就大面积修改对 `app.main` 的 patch 点 ## 2. 当前结构判断 `app/main.py` 当前可以分成这些职责块: 1. **应用启动与全局装配** - `app/main.py:46-154` - 包括 `settings`、`pool`、`stats_collector`、`chat_guard`、`session_cache`、`lifespan`、middleware 2. **鉴权包装与告警** - `app/main.py:157-196` 3. **健康检查与通用请求辅助逻辑** - `app/main.py:199-353` 4. **共享 tool / stream / bridge helper** - `app/main.py:356-752` 5. **OpenAI Chat 主编排** - `app/main.py:769-1192` 6. **Responses API 适配层** - `app/main.py:1197-1640` 7. **Anthropic Messages 适配层** - `app/main.py:1679-2180` 8. **admin / internal / metrics 路由** - `app/main.py:2183-2356` ## 3. 风险判断 ### 3.1 高风险区域(第一阶段不要碰) 以下区域**不建议作为第一刀拆分目标**: 1. `app/main.py:906` 左右的 OpenAI streaming generator 2. `app/main.py:1886` 左右的 Anthropic streaming generator 3. `v1_chat_completions` 主编排逻辑 4. `v1_messages` 主编排逻辑 5. session cache lookup / write-back / invalidate 的共享编排逻辑 ### 3.2 原因 这些区域都同时依赖: - route-local 状态 - `pool` / `chat_guard` / `session_cache` / `stats_collector` - session continuity - 流式 finally 中的 ticket 释放与写回时机 - OpenAI / Anthropic / Responses 之间的共享行为约束 这类代码即使功能不变,单纯移动位置也容易引发细微回归。 ## 4. 建议的目标结构 建议最终逐步演进到以下结构: ```text app/ main.py # 组合根:app 创建、lifespan、router 注册、共享单例 http/ lifecycle.py # middleware / startup posture / pool guards(可后置) chat_shared.py # 跨协议的 prompt/tool/stream helper openai_chat.py # /v1/chat/completions openai_responses.py # /responses 与 /v1/responses anthropic_messages.py # /v1/messages* 与 anthropic helper admin_routes.py # /internal/*, /metrics, /healthz, /v1/models(按需要划分) ``` > 注意:这个结构是**目标结构**,不是第一阶段必须一步到位完成的结构。 ## 5. 分阶段执行计划 ### Phase 0:保护性准备(只做分析,不改行为) 目标:为后续拆分建立安全边界。 动作: 1. 梳理并固定当前回归验证命令 - `python3 -m unittest tests/test_tool_call_bridge.py` - `python3 -m unittest discover -s tests -p "test_*.py"` 2. 在实际动代码前,对准备修改的关键符号做 impact analysis - 尤其是: - `v1_chat_completions` - `v1_messages` - `_messages_to_prompt` - `_responses_to_chat_request` - `_openai_tool_call` - `_anthropic_tool_use_block` 3. 先确认测试里对 `app.main` 的 patch 点,避免首轮拆分后直接把测试打碎 完成标准: - 有固定回归命令 - 清楚哪些符号必须在首轮保留兼容出口 --- ### Phase 1:提取纯 helper(最低风险) 目标:在不改主路由编排的前提下,先减轻 `app/main.py` 的噪音和长度。 建议新文件: #### 1) `app/http/tool_bridge.py` 建议迁移函数: - `_json_string` - `_openai_forced_tool_name` - `_anthropic_forced_tool_name` - `_json_object_from_text` - `_tool_code_single_arg_name` - `_tool_code_object_from_text` - `_forced_tool_event_from_text` - `_openai_tool_call` - `_anthropic_tool_use_block` - `_anthropic_tool_result_block` #### 2) `app/http/responses_adapter.py` 建议迁移函数: - `_responses_input_to_messages` - `_responses_to_chat_request` - `_responses_id_from_chat_id` - `_responses_usage_from_chat` - `_responses_non_stream_from_chat_payload` - `_sse_data` #### 3) `app/http/tool_policy.py`(可选) 如果首轮还想再减一点,可迁移: - `_include_usage` - `_tool_allowlist` - `_openai_tool_name` - `_anthropic_tool_name` - `_filter_allowed_tools` - `_ensure_tool_choice_allowed` - `_openai_tool_config` - `_anthropic_tool_config` - `_openai_has_tooling_context` - `_anthropic_content_has_tool_blocks` - `_anthropic_has_tooling_context` - `_resolve_ask_mode` 首轮兼容策略: - `app.main` 中先保留同名导入出口,例如: - `from .http.tool_bridge import _openai_tool_call, ...` - 这样即使测试仍然 patch `app.main._openai_tool_call`,改动面也最小 完成标准: - `app/main.py` 明显变短 - 路由逻辑不变 - 现有测试全过 - 首轮不改 streaming 主体 --- ### Phase 2:提取 Responses 路由(低到中风险) 目标:把 `/responses` 和 `/v1/responses` 的适配层单独放出去。 建议新文件: - `app/http/openai_responses.py` 建议包含: - `v1_responses` - `_responses_stream_from_chat_stream` - 以及它依赖的 responses helper(如果 Phase 1 已迁移则直接复用) 注意事项: - `v1_responses` 当前是直接包装 `v1_chat_completions` - 拆分时优先保持这个关系不变,不要同步重构 chat 主路径 - 如果测试直接 patch `main.v1_chat_completions`,则需要确保新模块仍从 `app.main` 可拿到兼容入口,或同步最小化调整测试 完成标准: - `/responses` 逻辑从 `main.py` 分离 - `v1_chat_completions` 仍保持原行为 - responses 相关测试不回归 --- ### Phase 3:提取 admin / health / metrics 路由(低风险) 目标:把非核心协议路径先搬走。 建议新文件: - `app/http/admin_routes.py` 可迁移内容: - `healthz` - `v1_models`(可按需一起搬) - `/internal/auto-login/*` - `/internal/session/export` - `/internal/models/raw` - `/internal/stats` - `/metrics` 注意事项: - 这些路由依赖全局 `settings` / `pool` / 鉴权 wrapper - 首轮可以通过“从 `main` 注入依赖”或“保留共享单例模块”来降低改动面 完成标准: - 运营/admin 路由从主文件剥离 - 对 chat/messages 主编排零行为影响 --- ### Phase 4:提取 Anthropic 路由与 helper(中风险) 目标:将 `/v1/messages*` 独立为单独模块。 建议新文件: - `app/http/anthropic_messages.py` 建议迁移: - `_anthropic_error` - `_anthropic_stop_reason` - `v1_messages_count_tokens` - `v1_messages` 前提: - Phase 1 已把共享 tool / prompt / policy helper 先抽出 - 已明确哪些共享状态通过参数传入,哪些保持模块共享 注意: - 暂时不重构 Anthropic stream generator 内部逻辑,只做“整体迁移”而不是“逻辑改写” 完成标准: - Anthropic 适配层从主文件分离 - 与 OpenAI 的共享行为仍保持一致 --- ### Phase 5:最后再考虑提取 OpenAI Chat 主路由(最高风险) 目标:在前几阶段都稳定之后,再处理核心编排。 建议新文件: - `app/http/openai_chat.py` 建议迁移: - `v1_chat_completions` - 仅与其强耦合、且不适合保留在 `main.py` 的少量辅助逻辑 关键原则: - 不要在这一阶段同时改 session/cache/streaming 逻辑 - 只做“位置迁移 + 依赖显式化” - 如需引入 service 层,也要在这个阶段之后再单独评估,不要和文件拆分绑定进行 完成标准: - `app/main.py` 基本收敛为组合根 - 主编排仍行为一致 - 全量测试通过 ## 6. 每阶段的验证要求 每一阶段完成后,至少执行: ```bash python3 -m unittest tests/test_tool_call_bridge.py python3 -m unittest discover -s tests -p "test_*.py" ``` 如果本地服务可启动,建议补一轮 smoke: ```bash uvicorn app.main:app --reload --port 8317 curl -s http://127.0.0.1:8317/healthz ``` 如果是改动了 `/responses` 或 `/v1/messages` 路径,应额外做协议 smoke,确认: - SSE 帧格式不变 - stop reason / finish reason 不变 - tool call / tool_use bridge 不变 ## 7. 兼容策略 为减少首轮测试与调用方震荡,建议: 1. **先迁移实现,再从 `app.main` re-export 同名符号** - 例如:`from .http.responses_adapter import _responses_to_chat_request` 2. 首轮不要改函数名 3. 首轮不要顺手重命名模块级全局变量 4. 首轮不要引入新的抽象层(例如 service / manager / context object) 原则: - 第一轮目标是“降噪和减重”,不是“顺便重构架构” ## 8. 不建议做的事 以下动作不建议与本次拆分绑定: - 同时重写 streaming generator 内部结构 - 同时改 session cache 语义 - 同时改 pool / guard / stats 注入方式 - 同时大改测试结构 - 同时引入新的 service 层 / context 容器 / 抽象基类 这些都应该是后续独立变更,不要混在第一次拆分里。 ## 9. 推荐的首个落地 PR 范围 如果要开始实际实施,**建议第一批只做一个小 PR**: ### PR-1:Helper extraction only 内容: - 新增 `app/http/tool_bridge.py` - 新增 `app/http/responses_adapter.py` - `app/main.py` 改为导入这些 helper - 保留 `app.main` 的兼容出口 - 不动 `v1_chat_completions` / `v1_messages` 的主逻辑 预期收益: - `app/main.py` 先减少几百行 - 风险最可控 - 为后续路由级拆分打基础 ## 10. 后续记录方式 建议后续每完成一个 phase,就在本文件底部追加一段进展记录,例如: ```md ## Progress Log - 2026-04-21: 创建拆分计划 - 2026-04-22: 完成 Phase 1,抽离 responses helper 与 tool bridge helper - 2026-04-23: 运行全量 unittest 通过 ``` 这样后续可以持续在同一份计划上回填,不需要再重新整理上下文。 ## Progress Log - 2026-04-21: 创建拆分计划。 - 2026-04-21: 完成 Phase 1 helper extraction,新增 `app/http/tool_bridge.py`、`app/http/responses_adapter.py`,并在 `app.main` 保留兼容导入出口。 - 2026-04-21: 修复 Phase 1 后暴露的 tool bridge 回归;放宽 tool event allow 判断,仅在存在显式 tool 列表时做名称过滤,并保留 forced-tool 回退语义。 - 2026-04-21: 调整 OpenAI 流式 forced-tool 回退,先缓冲 `tool_code` 文本,能解析为结构化 tool call 时只输出 `tool_calls` chunk,不能解析时再回放文本。 - 2026-04-21: 验证通过:`python3 -m py_compile app/main.py app/http/tool_bridge.py app/http/responses_adapter.py`、`python3 -m unittest tests/test_tool_call_bridge.py`、`python3 -m unittest discover -s tests -p "test_*.py"`。