Files
lingma-openai-gateway/.omc/plans/app-main-split-plan.md
GitHub Actions 0e146e60d9 refactor: extract Phase 1 gateway helpers
Move tool bridge and responses adapter helpers out of app.main so the main entrypoint can shrink without changing route orchestration behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 08:05:09 +08:00

11 KiB
Raw Permalink Blame History

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
    • 包括 settingspoolstats_collectorchat_guardsession_cachelifespan、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. 建议的目标结构

建议最终逐步演进到以下结构:

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. 每阶段的验证要求

每一阶段完成后,至少执行:

python3 -m unittest tests/test_tool_call_bridge.py
python3 -m unittest discover -s tests -p "test_*.py"

如果本地服务可启动,建议补一轮 smoke

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-1Helper 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就在本文件底部追加一段进展记录例如

## 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.pyapp/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.pypython3 -m unittest tests/test_tool_call_bridge.pypython3 -m unittest discover -s tests -p "test_*.py"