221 lines
7.4 KiB
Markdown
221 lines
7.4 KiB
Markdown
# 项目记忆(LLM_Proxy)
|
||
|
||
适用范围:`LLM_Proxy` 仓库根目录
|
||
|
||
## 基础约束
|
||
- 所有文本文件使用 `UTF-8`,无 BOM。
|
||
- 所有文本文件统一使用 `LF` (`\n`),禁止 `CRLF` (`\r\n`)。
|
||
- 运行日志统一使用英文。
|
||
- 代码注释和 docstring 目标规范为中文。
|
||
- Python 字符串风格默认使用双引号 `"..."`。
|
||
- Python `docstring` 统一使用三重双引号 `"""..."""`。
|
||
- 只有在减少转义、提升可读性时,普通字符串才使用单引号 `'...'`。
|
||
- 涉及代码、配置、架构的修改时,优先保持控制平面与数据平面职责清晰。
|
||
|
||
## 项目定位
|
||
- 这是一个基于 Flask + gevent 的 OpenAI 兼容代理服务。
|
||
- 核心能力是将 `/v1/chat/completions` 请求路由到配置中的上游 Provider。
|
||
- 系统同时提供后台管理、认证、用户白名单、Provider 管理、模型探测和请求统计能力。
|
||
|
||
## 启动与装配链路
|
||
- 入口文件:`main.py`
|
||
- 启动关键点:
|
||
- 使用 `gevent.monkey.patch_all()`
|
||
- 支持 `--config` 指定配置文件,默认使用项目根目录 `config.yaml`
|
||
- 调用 `Application.run()`,由 gevent `WSGIServer` 提供服务
|
||
- 组合根:`src/application/application.py`
|
||
- 当前装配顺序:
|
||
- 初始化 `ConfigManager`
|
||
- 初始化应用日志和访问日志
|
||
- 创建 `AppContext`
|
||
- 初始化 SQLite 连接工厂和仓储
|
||
- 初始化 `ProviderManager` 并加载 Provider
|
||
- 组装 Service 和 Controller 并注册路由
|
||
|
||
## 当前分层
|
||
- `src/application`
|
||
- 组合根与运行时上下文
|
||
- `src/presentation`
|
||
- HTTP 路由、鉴权入口、页面渲染、请求和响应适配
|
||
- `src/services`
|
||
- 用例编排和业务逻辑
|
||
- 当前已拆分为:
|
||
- `AuthenticationService`
|
||
- `UserService`
|
||
- `ProviderService`
|
||
- `ModelDiscoveryService`
|
||
- `SettingsService`
|
||
- `ProxyService`
|
||
- `LogService`
|
||
- `src/repositories`
|
||
- SQLite 数据访问
|
||
- `src/config`
|
||
- 配置快照管理、显式 Provider schema / factory、Provider 运行时注册
|
||
- `src/external`
|
||
- 上游响应适配、流式探测、Provider 运行时对象
|
||
- `src/hooks`
|
||
- Hook 协议和动态扩展点
|
||
- `src/utils`
|
||
- 数据库、网络、IP 处理等通用工具
|
||
|
||
## 架构关键点
|
||
- 当前是单进程分层单体。
|
||
- 控制平面:
|
||
- 认证
|
||
- 用户管理
|
||
- Provider 配置管理
|
||
- 系统设置管理
|
||
- 数据平面:
|
||
- `/v1/chat/completions`
|
||
- `/v1/models`
|
||
- 模型路由
|
||
- 上游转发
|
||
- 响应适配
|
||
- 请求日志记录
|
||
|
||
## 配置与 Provider 运行时
|
||
- `ConfigManager`
|
||
- 提供配置快照读取
|
||
- 提供原子写回
|
||
- `get_raw_config()` 返回 `deepcopy`,避免调用方误改内部缓存
|
||
- `provider_config`
|
||
- 负责 `ProviderConfigSchema`、`RuntimeProviderSpec`、`ProviderRuntimeView`
|
||
- 负责批量 `build_provider_schemas(...)`、配置规范化和校验
|
||
- `ProviderManager`
|
||
- 只接收已构建好的 `ProviderConfigSchema`
|
||
- 负责把 schema 转成运行时 `LLMProvider`
|
||
- 维护 `model -> provider` 映射、只读 `ProviderRuntimeView` 注册表和 Hook 缓存
|
||
- 对外优先暴露只读接口:`get_provider_view()`、`list_provider_views()`、`list_model_names()`
|
||
- `LLMProvider`
|
||
- 运行时 Provider 对象
|
||
- 当前会复制 `model_list`,避免共享可变引用
|
||
|
||
## 核心请求流程(chat/completions)
|
||
1. `ProxyController.chat_completions` 接收 `POST /v1/chat/completions`
|
||
2. 标准化客户端 IP
|
||
3. 读取 `chat.whitelist_enabled`
|
||
4. 如果开启白名单,则按 IP 查询用户,并要求 `whitelist_access_enabled=1`
|
||
5. 校验请求体必须包含 `model`
|
||
6. 补齐 `stream_options.include_usage=true`
|
||
7. 用 `ProviderManager.get_provider_for_model(model)` 获取目标 Provider
|
||
8. `ProxyService.proxy_request(...)` 转发到上游
|
||
9. `response_adapter` 处理普通响应或 SSE 响应
|
||
10. 响应完成后通过回调写入 `request_logs` 并同步更新 `daily_request_stats`
|
||
|
||
## 模型路由规则
|
||
- 配置中每个 Provider 至少声明:
|
||
- `name`
|
||
- `api`
|
||
- `model_list`
|
||
- 系统内部模型 key 格式:
|
||
- `{provider_name}/{model_name}`
|
||
- 代理接口请求体中的 `model` 必须使用该格式
|
||
- 转发给上游前,`ProxyService` 会只移除 `provider_name/` 前缀,保留真实模型名剩余部分原样不变
|
||
|
||
## 模型探测链路
|
||
- `ProviderController.fetch_models()`
|
||
- `ModelDiscoveryService.fetch_models_preview(...)`
|
||
- 自动尝试候选端点:
|
||
- `/v1/models`
|
||
- `/models`
|
||
- 支持从 `chat/completions` 风格 URL 反推模型列表端点
|
||
|
||
## Hook 机制
|
||
- 配置字段:`providers[].hook`
|
||
- 路径规则:
|
||
- 绝对路径直接加载
|
||
- 相对路径默认相对项目根目录 `hooks/`
|
||
- Hook 模块必须导出 `Hook` 类
|
||
- 可选方法:
|
||
- `header_hook(ctx, headers) -> headers`
|
||
- `input_body_hook(ctx, body) -> body`
|
||
- `output_body_hook(ctx, body) -> body`
|
||
- `ProviderManager` 负责 Hook 动态加载和缓存
|
||
|
||
## 认证与会话
|
||
- 是否开启认证取决于 `admin.username` 和 `admin.password` 是否都存在
|
||
- `AuthenticationService` 当前使用进程内内存 Session
|
||
- 服务重启后 Session 全部失效
|
||
- 当前不适合多实例横向扩展
|
||
|
||
## 用户与白名单
|
||
- `UserService` 对按 IP 查询用户做了内存缓存
|
||
- 用户变更时会主动失效对应缓存
|
||
- 白名单控制依赖:
|
||
- `chat.whitelist_enabled`
|
||
- 用户 `whitelist_access_enabled`
|
||
|
||
## 日志与统计
|
||
- 访问日志写入 `logs/access.log`
|
||
- 应用日志写入 `logs/app.log`
|
||
- `LogRepository.insert()` 在一个事务内同时写:
|
||
- `request_logs`
|
||
- `daily_request_stats`
|
||
|
||
## 主要路由
|
||
- 代理接口:
|
||
- `POST /v1/chat/completions`
|
||
- `GET /v1/models`
|
||
- 认证接口:
|
||
- `GET /login`
|
||
- `POST /api/login`
|
||
- `GET /logout`
|
||
- `POST /api/logout`
|
||
- 用户管理:
|
||
- `GET /api/users`
|
||
- `POST /api/users`
|
||
- `GET /api/users/<id>`
|
||
- `PUT /api/users/<id>`
|
||
- `DELETE /api/users/<id>`
|
||
- `POST /api/users/<id>/toggle`
|
||
- Provider 管理:
|
||
- `GET /api/providers`
|
||
- `POST /api/providers`
|
||
- `GET /api/providers/<name>`
|
||
- `PUT /api/providers/<name>`
|
||
- `DELETE /api/providers/<name>`
|
||
- `GET /api/providers/fetch-models`
|
||
- `PUT /api/providers/chat-whitelist`
|
||
- 页面:
|
||
- `GET /`
|
||
- `GET /users`
|
||
- `GET /providers`
|
||
|
||
## 数据库结构
|
||
- 默认路径:`data/requests.db`
|
||
- `users`
|
||
- `id`
|
||
- `username`
|
||
- `ip_address`
|
||
- `whitelist_access_enabled`
|
||
- `created_at`
|
||
- `updated_at`
|
||
- `request_logs`
|
||
- `ip_address`
|
||
- `request_model`
|
||
- `response_model`
|
||
- `total_tokens`
|
||
- `prompt_tokens`
|
||
- `completion_tokens`
|
||
- `start_time`
|
||
- `end_time`
|
||
- `daily_request_stats`
|
||
- 唯一键:`(stat_date, ip_address, request_model, response_model)`
|
||
|
||
## 当前风险与注意事项
|
||
- 历史文件里有较多中文乱码的注释和 docstring,修改相关文件时优先逐步修复,避免继续扩散。
|
||
- `output_body_hook` 在非流式分支如果返回 `None`,可能把响应体变成 `null`;新增 Hook 时要注意返回值约定。
|
||
- 认证 Session、用户 IP 缓存、Provider 映射和 Hook 缓存都在单进程内存中,不适合多实例部署。
|
||
|
||
## 4+1 架构文档维护
|
||
- 4+1 Mermaid 文档文件:`docs/architecture-4plus1.md`
|
||
- 涉及以下变更时,必须同步更新该文档:
|
||
- 模块职责变化
|
||
- 分层依赖变化
|
||
- 主请求链路变化
|
||
- Provider 重载链路变化
|
||
- 运行时缓存或内存状态变化
|
||
- Hook 机制变化
|
||
- 部署拓扑变化
|
||
- 如果代码提交没有修改 4+1 图,变更说明中需要明确声明“架构未变化”。
|