update 增加4+1试图,优化提供商界面
This commit is contained in:
1 parent
b8ab461c4c
commit
fdf77abebc
4 files changed
+622
-123
No files matched your search
+186
-116
@@ -1,147 +1,217 @@
|
||||
# 项目记忆(ai_proxy)
|
||||
适用范围:`d:\001Code\008llm\003ai_proxy`
|
||||
# 项目记忆(LLM_Proxy)
|
||||
|
||||
适用范围:`d:\001Code\008llm\003LLM_Proxy`
|
||||
|
||||
## 基础约束
|
||||
- 所有文本文件使用 `UTF-8`(无 BOM)。
|
||||
- 运行日志统一英文。
|
||||
- 所有文本文件使用 `UTF-8`,无 BOM。
|
||||
- 运行日志统一使用英文。
|
||||
- 代码注释和 docstring 目标规范为中文。
|
||||
- Python 字符串风格默认使用双引号 `"..."`。
|
||||
- Python `docstring` 统一使用三重双引号 `"""..."""`。
|
||||
- 只有在减少转义、提升可读性时,普通字符串才使用单引号 `'...'`。
|
||||
- 涉及代码、配置、架构的修改时,优先保持控制平面与数据平面职责清晰。
|
||||
|
||||
## 项目定位
|
||||
- 这是一个 Flask + gevent 的 OpenAI 兼容代理服务。
|
||||
- 核心能力是将 `/v1/chat/completions` 请求路由到配置中的上游 Provider,并支持 Hook 扩展、白名单控制、后台管理和日志统计。
|
||||
- 这是一个基于 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`
|
||||
- 初始化日志(`app.log`、`access.log`)
|
||||
- 注册 `before_request` 访问日志钩子(记录 `ip + url`)
|
||||
- 创建 `AppContext`
|
||||
- 初始化 SQLite 连接工厂与仓储
|
||||
- 加载 Provider 与 Hook
|
||||
- 组装 Service 与 Controller,并注册路由
|
||||
- 使用 `gevent.monkey.patch_all()`
|
||||
- 支持 `--config` 指定配置文件,默认使用项目根目录 `config.yaml`
|
||||
- 调用 `Application.run()`,由 gevent `WSGIServer` 提供服务
|
||||
- 组合根:`src/application/application.py`
|
||||
- 当前装配顺序:
|
||||
- 初始化 `ConfigManager`
|
||||
- 初始化应用日志和访问日志
|
||||
- 创建 `AppContext`
|
||||
- 初始化 SQLite 连接工厂和仓储
|
||||
- 初始化 `ProviderManager` 并加载 Provider
|
||||
- 组装 Service 和 Controller 并注册路由
|
||||
|
||||
## 分层职责
|
||||
- `src/presentation`:HTTP 路由与输入输出校验。
|
||||
- `src/services`:业务逻辑编排(认证、用户、代理、日志)。
|
||||
- `src/repositories`:SQLite 数据访问。
|
||||
- `src/config`:配置读取与 provider/hook 装载。
|
||||
- `src/external`:上游 LLM provider 抽象与输出处理。
|
||||
- `src/common`:上下文对象与类型协议。
|
||||
- `src/utils`:数据库连接、IP 处理等通用工具。
|
||||
## 当前分层
|
||||
- `src/application`
|
||||
- 组合根与运行时上下文
|
||||
- `src/presentation`
|
||||
- HTTP 路由、鉴权入口、页面渲染、请求和响应适配
|
||||
- `src/services`
|
||||
- 用例编排和业务逻辑
|
||||
- 当前已拆分为:
|
||||
- `AuthenticationService`
|
||||
- `UserService`
|
||||
- `ProviderService`
|
||||
- `ModelDiscoveryService`
|
||||
- `SettingsService`
|
||||
- `ProxyService`
|
||||
- `LogService`
|
||||
- `src/repositories`
|
||||
- SQLite 数据访问
|
||||
- `src/config`
|
||||
- 配置快照管理、Provider 配置规范化、Provider 运行时注册
|
||||
- `src/external`
|
||||
- 上游响应适配、流式探测、Provider 运行时对象
|
||||
- `src/hooks`
|
||||
- Hook 协议和动态扩展点
|
||||
- `src/utils`
|
||||
- 数据库、网络、IP 处理等通用工具
|
||||
|
||||
## 架构关键点
|
||||
- 当前是单进程分层单体。
|
||||
- 控制平面:
|
||||
- 认证
|
||||
- 用户管理
|
||||
- Provider 配置管理
|
||||
- 系统设置管理
|
||||
- 数据平面:
|
||||
- `/v1/chat/completions`
|
||||
- `/v1/models`
|
||||
- 模型路由
|
||||
- 上游转发
|
||||
- 响应适配
|
||||
- 请求日志记录
|
||||
|
||||
## 配置与 Provider 运行时
|
||||
- `ConfigManager`
|
||||
- 提供配置快照读取
|
||||
- 提供原子写回
|
||||
- `get_raw_config()` 返回 `deepcopy`,避免调用方误改内部缓存
|
||||
- `provider_config`
|
||||
- 负责 Provider 配置规范化和校验
|
||||
- `ProviderManager`
|
||||
- 负责把配置中的 Provider 转成运行时 `LLMProvider`
|
||||
- 维护 `model -> provider` 映射
|
||||
- 维护 Hook 缓存
|
||||
- `LLMProvider`
|
||||
- 运行时 Provider 对象
|
||||
- 当前会复制 `model_list`,避免共享可变引用
|
||||
|
||||
## 核心请求流程(chat/completions)
|
||||
1. `ProxyController.chat_completions` 接收 `POST /v1/chat/completions`。
|
||||
2. 标准化客户端 IP(`normalize_ip`)。
|
||||
3. 若 `chat.whitelist_enabled=true`,按 IP 查用户且要求 `whitelist_access_enabled=1`。
|
||||
4. 校验请求体含 `model`。
|
||||
5. 强制补齐 `stream_options.include_usage=true`。
|
||||
6. 用 `ProviderManager.find_provider_by_model(model)` 找 provider。
|
||||
7. 调用 `ProxyService.proxy_request(...)` 转发到上游。
|
||||
8. 响应完成后通过回调写入 `request_logs` 并更新 `daily_request_stats`。
|
||||
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.find_provider_by_model(model)` 获取目标 Provider
|
||||
8. `ProxyService.proxy_request(...)` 转发到上游
|
||||
9. `response_adapter` 处理普通响应或 SSE 响应
|
||||
10. 响应完成后通过回调写入 `request_logs` 并同步更新 `daily_request_stats`
|
||||
|
||||
## 模型路由规则
|
||||
- Provider 在配置中声明 `name` 与 `model_list`。
|
||||
- 系统内部模型 key 格式是:`{provider_name}/{model_name}`。
|
||||
- 代理接口请求体中的 `model` 必须匹配上述 key(例如 `volc/glm-4-7-251222`)。
|
||||
- 转发给上游前,`ProxyService` 会将模型名改为去前缀后的真实模型名(仅保留 `/` 后最后一段)。
|
||||
- 配置中每个 Provider 至少声明:
|
||||
- `name`
|
||||
- `api`
|
||||
- `model_list`
|
||||
- 系统内部模型 key 格式:
|
||||
- `{provider_name}/{model_name}`
|
||||
- 代理接口请求体中的 `model` 必须使用该格式
|
||||
- 转发给上游前,`ProxyService` 会把模型名裁剪成最后一段真实模型名
|
||||
|
||||
## 模型探测链路
|
||||
- `ProviderController.fetch_models()`
|
||||
- `ModelDiscoveryService.fetch_models_preview(...)`
|
||||
- 自动尝试候选端点:
|
||||
- `/v1/models`
|
||||
- `/models`
|
||||
- 支持从 `chat/completions` 风格 URL 反推模型列表端点
|
||||
|
||||
## Hook 机制
|
||||
- 在 `config.yaml` 的 provider 项可配置:
|
||||
- `header_hook`
|
||||
- `input_body_hook`
|
||||
- `output_body_hook`
|
||||
- Hook 路径规则:
|
||||
- 绝对路径直接加载。
|
||||
- 相对路径默认相对项目根的 `hooks/` 目录加载。
|
||||
- `ProviderManager` 对 hook 模块做缓存,避免重复导入。
|
||||
- Hook 函数约定:
|
||||
- `header_hook(ctx, headers) -> headers`
|
||||
- `input_body_hook(ctx, body) -> body`
|
||||
- `output_body_hook(ctx, data) -> Optional[data]`
|
||||
- `ctx` 常用字段:
|
||||
- `retry`(当前重试序号,从 0 开始)
|
||||
- `ip_address`
|
||||
- `user_id`
|
||||
- 配置字段:`providers[].hook`
|
||||
- 路径规则:
|
||||
- 绝对路径直接加载
|
||||
- 相对路径默认相对项目根目录 `hooks/`
|
||||
- Hook 模块必须导出 `Hook` 类
|
||||
- 可选方法:
|
||||
- `header_hook(ctx, headers) -> headers`
|
||||
- `input_body_hook(ctx, body) -> body`
|
||||
- `output_body_hook(ctx, body) -> body`
|
||||
- `ProviderManager` 负责 Hook 动态加载和缓存
|
||||
|
||||
## 流式与非流式输出处理
|
||||
- 输出处理入口:`LLMProvider.apply_output_body_hook(...)`。
|
||||
- 非流式:
|
||||
- 读取完整 JSON,提取 `model` 与 `usage`(`total/prompt/completion_tokens`)。
|
||||
- 若配置了输出 hook,则以 hook 返回内容作为响应体。
|
||||
- 流式(SSE):
|
||||
- 按事件块解析 `data:` 行,逐块执行输出 hook(若存在)。
|
||||
- 识别 `[DONE]`。
|
||||
- 在流结束时触发 `on_complete`,用于日志落库与统计。
|
||||
## 认证与会话
|
||||
- 是否开启认证取决于 `admin.username` 和 `admin.password` 是否都存在
|
||||
- `AuthenticationService` 当前使用进程内内存 Session
|
||||
- 服务重启后 Session 全部失效
|
||||
- 当前不适合多实例横向扩展
|
||||
|
||||
## 重试与网络行为
|
||||
- `ProxyService` 使用 `requests.Session`。
|
||||
- 每个 provider 可配置:
|
||||
- `timeout_seconds`(默认 300)
|
||||
- `max_retries`(默认 3)
|
||||
- `verify_ssl`(默认 false)
|
||||
- 仅对 `requests.exceptions.RequestException` 进行重试。
|
||||
## 用户与白名单
|
||||
- `UserService` 对按 IP 查询用户做了内存缓存
|
||||
- 用户变更时会主动失效对应缓存
|
||||
- 白名单控制依赖:
|
||||
- `chat.whitelist_enabled`
|
||||
- 用户 `whitelist_access_enabled`
|
||||
|
||||
## 认证与权限
|
||||
- 认证是否启用取决于 `admin.username` 和 `admin.password` 是否都存在。
|
||||
- 启用后:
|
||||
- 页面接口未登录重定向 `/login`。
|
||||
- API 未登录返回 `401 {"error":"Unauthorized"}`。
|
||||
- 会话存储在内存(`AuthenticationService._sessions`),服务重启后会话失效。
|
||||
- cookie 默认:
|
||||
- `httponly=true`
|
||||
- `samesite=Lax`
|
||||
- `secure=false`
|
||||
- `max_age=86400`
|
||||
## 日志与统计
|
||||
- 访问日志写入 `logs/access.log`
|
||||
- 应用日志写入 `logs/app.log`
|
||||
- `LogRepository.insert()` 在一个事务内同时写:
|
||||
- `request_logs`
|
||||
- `daily_request_stats`
|
||||
|
||||
## 主要路由
|
||||
- 代理接口:
|
||||
- `POST /v1/chat/completions`
|
||||
- `GET /v1/models`
|
||||
- `POST /v1/chat/completions`
|
||||
- `GET /v1/models`
|
||||
- 认证接口:
|
||||
- `GET /login`
|
||||
- `POST /api/login`
|
||||
- `GET /logout`
|
||||
- `POST /api/logout`
|
||||
- `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`
|
||||
- 统计与日志:
|
||||
- `GET /api/statistics`
|
||||
- `GET /api/request-logs`
|
||||
- `GET /api/usernames`
|
||||
- `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 /`
|
||||
- `GET /users`
|
||||
- `GET /providers`
|
||||
|
||||
## 数据库结构(SQLite)
|
||||
- 默认路径:`data/requests.db`(可由 `database.path` 覆盖)。
|
||||
- 表:
|
||||
## 数据库结构
|
||||
- 默认路径:`data/requests.db`
|
||||
- `users`
|
||||
- 字段:`id`, `username`, `ip_address(unique)`, `whitelist_access_enabled`, `created_at`, `updated_at`
|
||||
- `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`
|
||||
- `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)`
|
||||
- 用于按天聚合请求次数与 token 数据
|
||||
- 唯一键:`(stat_date, ip_address, request_model, response_model)`
|
||||
|
||||
## 关键实现细节
|
||||
- `ProxyController` 会过滤 hop-by-hop 请求头后再转发。
|
||||
- `ProxyService` 会过滤 hop-by-hop 响应头返回给客户端。
|
||||
- `UserService` 对按 IP 查用户做了内存缓存,并在用户变更时失效缓存。
|
||||
- `UserRepository.get()` 会关联 `daily_request_stats` 返回用户累计请求/Token统计。
|
||||
- `LogRepository.insert()` 在单事务内同时写明细日志与日聚合统计。
|
||||
## 当前风险与注意事项
|
||||
- 历史文件里有较多中文乱码的注释和 docstring,修改相关文件时优先逐步修复,避免继续扩散。
|
||||
- `output_body_hook` 在非流式分支如果返回 `None`,可能把响应体变成 `null`;新增 Hook 时要注意返回值约定。
|
||||
- 认证 Session、用户 IP 缓存、Provider 映射和 Hook 缓存都在单进程内存中,不适合多实例部署。
|
||||
|
||||
## 当前开发注意事项
|
||||
- 代码中存在较多中文注释/docstring 乱码现象,修改相关文件时优先一并修复,避免继续扩散。
|
||||
- `output_body_hook` 在非流式分支若返回 `None`,当前会把响应体变成 `null`;新增 hook 时应明确返回原对象或改造此行为。
|
||||
- 会话是进程内存态,不适合多实例部署;如果未来需要横向扩展,应改为共享会话存储。
|
||||
## 4+1 架构文档维护
|
||||
- 4+1 Mermaid 文档文件:`docs/architecture-4plus1.md`
|
||||
- 涉及以下变更时,必须同步更新该文档:
|
||||
- 模块职责变化
|
||||
- 分层依赖变化
|
||||
- 主请求链路变化
|
||||
- Provider 重载链路变化
|
||||
- 运行时缓存或内存状态变化
|
||||
- Hook 机制变化
|
||||
- 部署拓扑变化
|
||||
- 如果代码提交没有修改 4+1 图,变更说明中需要明确声明“架构未变化”。
|
||||
@@ -0,0 +1,390 @@
|
||||
# LLM_Proxy 4+1 Architecture View
|
||||
|
||||
本文档描述当前代码实现对应的 4+1 架构视图,作为后续重构、评审和需求变更时的基线。
|
||||
|
||||
维护约束:
|
||||
- 涉及模块职责、依赖关系、主请求链路、运行时状态、配置加载/重载机制、Hook 机制、部署拓扑的修改时,需要同步更新本文件。
|
||||
- 如果代码实现与本图不一致,以代码为准,但提交中应补齐文档更新。
|
||||
|
||||
## 0. 架构摘要
|
||||
|
||||
系统当前是一个单进程、分层式单体应用,按职责分成两条主轴:
|
||||
- 控制平面:后台管理、认证、用户、Provider 配置、系统设置。
|
||||
- 数据平面:OpenAI 兼容代理、模型路由、上游请求转发、响应适配、日志落库。
|
||||
|
||||
关键代码位置:
|
||||
- 启动与装配:[main.py](/d:/001Code/008llm/003LLM_Proxy/main.py)
|
||||
- 组合根:[application.py](/d:/001Code/008llm/003LLM_Proxy/src/application/application.py)
|
||||
- 配置与 Provider 运行时:[config_manager.py](/d:/001Code/008llm/003LLM_Proxy/src/config/config_manager.py) [provider_manager.py](/d:/001Code/008llm/003LLM_Proxy/src/config/provider_manager.py)
|
||||
- 代理主链路:[proxy_controller.py](/d:/001Code/008llm/003LLM_Proxy/src/presentation/proxy_controller.py) [proxy_service.py](/d:/001Code/008llm/003LLM_Proxy/src/services/proxy_service.py)
|
||||
|
||||
## 1. Logical View
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser[浏览器管理端]
|
||||
ApiClient[OpenAI 兼容 API Client]
|
||||
|
||||
subgraph App["LLM_Proxy 应用"]
|
||||
subgraph Presentation["Presentation"]
|
||||
WebCtl[WebController]
|
||||
AuthCtl[AuthenticationController]
|
||||
UserCtl[UserController]
|
||||
ProviderCtl[ProviderController]
|
||||
ProxyCtl[ProxyController]
|
||||
end
|
||||
|
||||
subgraph Services["Services"]
|
||||
AuthSvc[AuthenticationService]
|
||||
UserSvc[UserService]
|
||||
ProviderSvc[ProviderService]
|
||||
DiscoverySvc[ModelDiscoveryService]
|
||||
SettingsSvc[SettingsService]
|
||||
ProxySvc[ProxyService]
|
||||
LogSvc[LogService]
|
||||
end
|
||||
|
||||
subgraph ConfigRuntime["Config / Provider Runtime"]
|
||||
ConfigMgr[ConfigManager]
|
||||
ProviderCfg[provider_config]
|
||||
ProviderMgr[ProviderManager]
|
||||
LLMProvider[LLMProvider]
|
||||
end
|
||||
|
||||
subgraph Persistence["Persistence"]
|
||||
UserRepo[UserRepository]
|
||||
LogRepo[LogRepository]
|
||||
SQLite[(SQLite)]
|
||||
end
|
||||
|
||||
subgraph Integration["External Integration"]
|
||||
Hooks[Hook Modules]
|
||||
Adapter[response_adapter]
|
||||
Probe[stream_probe]
|
||||
Upstream[Upstream LLM Providers]
|
||||
end
|
||||
end
|
||||
|
||||
Browser --> WebCtl
|
||||
Browser --> AuthCtl
|
||||
Browser --> UserCtl
|
||||
Browser --> ProviderCtl
|
||||
ApiClient --> ProxyCtl
|
||||
|
||||
AuthCtl --> AuthSvc
|
||||
UserCtl --> UserSvc
|
||||
ProviderCtl --> ProviderSvc
|
||||
ProviderCtl --> DiscoverySvc
|
||||
ProviderCtl --> SettingsSvc
|
||||
ProxyCtl --> ProxySvc
|
||||
ProxyCtl --> UserSvc
|
||||
ProxyCtl --> ProviderMgr
|
||||
ProxyCtl --> LogSvc
|
||||
WebCtl --> ConfigMgr
|
||||
|
||||
AuthSvc --> ConfigMgr
|
||||
ProviderSvc --> ConfigMgr
|
||||
ProviderSvc --> ProviderCfg
|
||||
DiscoverySvc --> Upstream
|
||||
SettingsSvc --> ConfigMgr
|
||||
ProxySvc --> LLMProvider
|
||||
ProxySvc --> Adapter
|
||||
ProxySvc --> Probe
|
||||
ProviderMgr --> ProviderCfg
|
||||
ProviderMgr --> LLMProvider
|
||||
ProviderMgr --> Hooks
|
||||
Adapter --> Hooks
|
||||
|
||||
UserSvc --> UserRepo
|
||||
LogSvc --> LogRepo
|
||||
UserRepo --> SQLite
|
||||
LogRepo --> SQLite
|
||||
ConfigMgr --> ConfigFile[(config.yaml)]
|
||||
```
|
||||
|
||||
逻辑划分:
|
||||
- `Presentation` 负责 HTTP 路由、鉴权入口、页面渲染和请求/响应封装。
|
||||
- `Services` 负责用例编排,不直接持有全局可变配置。
|
||||
- `Config / Provider Runtime` 负责 YAML 配置快照、Provider 规范化、模型到 Provider 的运行时映射。
|
||||
- `Persistence` 负责 SQLite 读写。
|
||||
- `External Integration` 负责上游协议适配、流式探测、Hook 扩展和上游 Provider 集成。
|
||||
|
||||
## 2. Development View
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Main[main.py]
|
||||
|
||||
subgraph Src["src/"]
|
||||
Application["application/"]
|
||||
Presentation["presentation/"]
|
||||
Services["services/"]
|
||||
Repositories["repositories/"]
|
||||
Config["config/"]
|
||||
External["external/"]
|
||||
Hooks["hooks/"]
|
||||
Utils["utils/"]
|
||||
end
|
||||
|
||||
Main --> Application
|
||||
Main --> Utils
|
||||
|
||||
Application --> Presentation
|
||||
Application --> Services
|
||||
Application --> Repositories
|
||||
Application --> Config
|
||||
Application --> Utils
|
||||
|
||||
Presentation --> Application
|
||||
Presentation --> Services
|
||||
Presentation --> Config
|
||||
|
||||
Services --> Application
|
||||
Services --> Repositories
|
||||
Services --> Config
|
||||
Services --> External
|
||||
Services --> Utils
|
||||
|
||||
Config --> Application
|
||||
Config --> External
|
||||
Config --> Utils
|
||||
|
||||
Repositories --> Utils
|
||||
|
||||
External --> Hooks
|
||||
```
|
||||
|
||||
开发视图解读:
|
||||
- `application` 是组合根,不承载具体业务规则。
|
||||
- `presentation -> services` 是主要调用方向。
|
||||
- `services -> repositories/config/external` 是业务侧依赖方向。
|
||||
- `config` 既不是传统 repository,也不是 service,更像运行时配置与 Provider 注册子系统。
|
||||
|
||||
## 3. Process View
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Process["单个 Python 进程"]
|
||||
WSGI[gevent WSGIServer]
|
||||
FlaskApp[Flask App]
|
||||
|
||||
subgraph RuntimeState["进程内运行时状态"]
|
||||
SessionState[AuthenticationService\n内存 Session]
|
||||
UserCache[UserService\nIP -> User 缓存]
|
||||
ConfigCache[ConfigManager\n配置快照缓存]
|
||||
ProviderRegistry[ProviderManager\nmodel -> provider 映射\nhook cache]
|
||||
HttpLocal[ProxyService\nthread-local requests.Session]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph Local["本地资源"]
|
||||
ConfigFile[(config.yaml)]
|
||||
DB[(data/requests.db)]
|
||||
HooksDir[(hooks/*.py)]
|
||||
LogFiles[(logs/*.log)]
|
||||
end
|
||||
|
||||
subgraph Remote["外部网络资源"]
|
||||
Upstream[Upstream LLM Providers]
|
||||
end
|
||||
|
||||
WSGI --> FlaskApp
|
||||
FlaskApp --> SessionState
|
||||
FlaskApp --> UserCache
|
||||
FlaskApp --> ConfigCache
|
||||
FlaskApp --> ProviderRegistry
|
||||
FlaskApp --> HttpLocal
|
||||
|
||||
ConfigCache <--> ConfigFile
|
||||
UserCache <--> DB
|
||||
ProviderRegistry <--> HooksDir
|
||||
FlaskApp --> LogFiles
|
||||
HttpLocal <--> Upstream
|
||||
```
|
||||
|
||||
进程视图要点:
|
||||
- 当前是单进程单实例内存态设计。
|
||||
- `AuthenticationService` 的 Session 存在进程内存中,重启后失效。
|
||||
- `UserService` 有 IP 维度缓存。
|
||||
- `ProviderManager` 持有运行时模型路由表和 Hook 缓存。
|
||||
- `ProxyService` 维护 thread-local `requests.Session`。
|
||||
- 如果未来引入多实例部署,这些内存态要重新设计。
|
||||
|
||||
## 4. Physical View
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Clients["客户端"]
|
||||
Browser[浏览器管理端]
|
||||
SDK[SDK / Curl / OpenAI Client]
|
||||
end
|
||||
|
||||
subgraph Host["部署主机"]
|
||||
subgraph Service["LLM_Proxy 服务进程"]
|
||||
Entry[main.py]
|
||||
Server[gevent WSGIServer]
|
||||
App[Flask + Controllers + Services]
|
||||
end
|
||||
|
||||
ConfigFile[(config.yaml)]
|
||||
DB[(data/requests.db)]
|
||||
HooksDir[(hooks/*.py)]
|
||||
Logs[(logs/*.log)]
|
||||
end
|
||||
|
||||
subgraph Providers["外部 Provider"]
|
||||
P1[Provider A]
|
||||
P2[Provider B]
|
||||
PN[Provider N]
|
||||
end
|
||||
|
||||
Browser <--HTTP/HTTPS--> Server
|
||||
SDK <--HTTP/HTTPS--> Server
|
||||
|
||||
App --> ConfigFile
|
||||
App --> DB
|
||||
App --> HooksDir
|
||||
App --> Logs
|
||||
|
||||
App <--HTTPS--> P1
|
||||
App <--HTTPS--> P2
|
||||
App <--HTTPS--> PN
|
||||
```
|
||||
|
||||
物理视图要点:
|
||||
- 部署结构目前非常简单,单机即可运行。
|
||||
- 本地状态包括配置文件、SQLite、日志文件、Hook 文件。
|
||||
- 外部依赖主要是上游模型 Provider 的 HTTP API。
|
||||
|
||||
## 5. Scenario View
|
||||
|
||||
### 5.1 管理员登录
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Admin as 管理员浏览器
|
||||
participant AuthCtl as AuthenticationController
|
||||
participant AuthSvc as AuthenticationService
|
||||
participant ConfigMgr as ConfigManager
|
||||
|
||||
Admin->>AuthCtl: POST /api/login
|
||||
AuthCtl->>AuthSvc: authenticate(username, password)
|
||||
AuthSvc->>ConfigMgr: get_admin_config()
|
||||
ConfigMgr-->>AuthSvc: admin credentials
|
||||
AuthSvc-->>AuthCtl: authenticated / rejected
|
||||
|
||||
alt 认证成功
|
||||
AuthCtl->>AuthSvc: create_session(username)
|
||||
AuthSvc-->>AuthCtl: session token
|
||||
AuthCtl-->>Admin: Set-Cookie + 200
|
||||
else 认证失败
|
||||
AuthCtl-->>Admin: 401
|
||||
end
|
||||
```
|
||||
|
||||
### 5.2 Provider 配置变更并自动重载
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Admin as 管理员浏览器
|
||||
participant ProviderCtl as ProviderController
|
||||
participant ProviderSvc as ProviderService
|
||||
participant ConfigMgr as ConfigManager
|
||||
participant App as Application
|
||||
participant ProviderMgr as ProviderManager
|
||||
participant ProviderCfg as provider_config
|
||||
participant Hooks as hooks/*.py
|
||||
|
||||
Admin->>ProviderCtl: PUT /api/providers/{name}
|
||||
ProviderCtl->>ProviderSvc: update_provider(payload)
|
||||
ProviderSvc->>ConfigMgr: get_raw_config()
|
||||
ConfigMgr-->>ProviderSvc: config snapshot
|
||||
ProviderSvc->>ProviderCfg: normalize_provider_payload()
|
||||
ProviderSvc->>ProviderCfg: validate_provider_definitions()
|
||||
ProviderSvc->>ConfigMgr: write_raw_config(updated config)
|
||||
ProviderSvc->>App: reload_providers()
|
||||
App->>ConfigMgr: reload()
|
||||
App->>ConfigMgr: get_raw_config()
|
||||
App->>ProviderMgr: load_providers(providers_config)
|
||||
ProviderMgr->>ProviderCfg: normalize_runtime_provider_config()
|
||||
ProviderMgr->>Hooks: load hook modules if configured
|
||||
ProviderMgr-->>App: provider registry refreshed
|
||||
ProviderSvc-->>ProviderCtl: updated provider
|
||||
ProviderCtl-->>Admin: 200 OK
|
||||
```
|
||||
|
||||
### 5.3 后台探测上游模型列表
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Admin as 管理员浏览器
|
||||
participant ProviderCtl as ProviderController
|
||||
participant DiscoverySvc as ModelDiscoveryService
|
||||
participant Upstream as Upstream Provider
|
||||
|
||||
Admin->>ProviderCtl: GET /api/providers/fetch-models?api=...
|
||||
ProviderCtl->>DiscoverySvc: fetch_models_preview(...)
|
||||
DiscoverySvc->>Upstream: GET /v1/models or /models
|
||||
Upstream-->>DiscoverySvc: JSON models payload
|
||||
DiscoverySvc-->>ProviderCtl: fetched_models
|
||||
ProviderCtl-->>Admin: 200 JSON
|
||||
```
|
||||
|
||||
### 5.4 代理一次 `/v1/chat/completions`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Client as API Client
|
||||
participant ProxyCtl as ProxyController
|
||||
participant ConfigMgr as ConfigManager
|
||||
participant UserSvc as UserService
|
||||
participant ProviderMgr as ProviderManager
|
||||
participant ProxySvc as ProxyService
|
||||
participant Hook as Hook Module
|
||||
participant Upstream as Upstream Provider
|
||||
participant Adapter as response_adapter
|
||||
participant LogSvc as LogService
|
||||
participant LogRepo as LogRepository
|
||||
|
||||
Client->>ProxyCtl: POST /v1/chat/completions
|
||||
ProxyCtl->>ConfigMgr: is_chat_whitelist_enabled()
|
||||
|
||||
alt 白名单开启
|
||||
ProxyCtl->>UserSvc: get_user_by_ip(ip, require_whitelist_access=true)
|
||||
UserSvc-->>ProxyCtl: user / None
|
||||
end
|
||||
|
||||
ProxyCtl->>ProviderMgr: find_provider_by_model(model)
|
||||
ProviderMgr-->>ProxyCtl: LLMProvider
|
||||
ProxyCtl->>ProxySvc: proxy_request(provider, body, headers, on_complete)
|
||||
ProxySvc->>Hook: header_hook / input_body_hook
|
||||
ProxySvc->>Upstream: POST provider.api
|
||||
Upstream-->>ProxySvc: response or SSE stream
|
||||
ProxySvc->>Adapter: build_proxy_response(...)
|
||||
Adapter->>Hook: output_body_hook
|
||||
Adapter-->>Client: Flask Response
|
||||
Adapter->>LogSvc: on_complete(meta)
|
||||
LogSvc->>LogRepo: insert(...)
|
||||
```
|
||||
|
||||
## 6. 当前架构判断
|
||||
|
||||
当前架构优点:
|
||||
- 模块划分清楚,学习成本低。
|
||||
- 单进程单文件配置的运维复杂度很低。
|
||||
- 控制平面和数据平面在职责上已经开始分离。
|
||||
- Hook 扩展点足够轻量,适合小规模快速演进。
|
||||
|
||||
当前架构边界:
|
||||
- 运行时状态仍集中在单进程内存中,不适合多实例横向扩展。
|
||||
- `presentation` 层仍承担部分接口协议细节和错误映射责任。
|
||||
- `config` 子系统目前同时承担配置快照、配置规范化、Provider 注册三类职责。
|
||||
- Hook 是运行时动态加载,扩展灵活,但也引入了热更新和可观测性上的复杂性。
|
||||
|
||||
## 7. 后续演进建议
|
||||
|
||||
如果后面继续演进,优先级建议是:
|
||||
- 把 `provider_config` 进一步收敛成显式 schema / factory。
|
||||
- 为 `ProviderManager` 增加更清晰的只读运行时接口。
|
||||
- 评估是否把 Session、Provider Registry、User IP 缓存从单进程内存态解耦。
|
||||
- 补一份 ADR,说明为什么当前仍选择单体 + 单进程 + SQLite。
|
||||
@@ -600,6 +600,24 @@
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.providers-page .btn-field-secondary {
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
border-color: var(--line-strong);
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.providers-page .btn-field-secondary:hover,
|
||||
.providers-page .btn-field-secondary:focus {
|
||||
border-color: var(--primary);
|
||||
background: #ffffff;
|
||||
color: var(--primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.providers-page .field-help-button {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
@@ -728,7 +746,7 @@
|
||||
}
|
||||
|
||||
.providers-page .fetch-model-list {
|
||||
max-height: min(52vh, 480px);
|
||||
height: min(52vh, 480px);
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
border: 1px solid var(--line);
|
||||
@@ -736,6 +754,15 @@
|
||||
background: rgba(17, 24, 39, 0.02);
|
||||
}
|
||||
|
||||
.providers-page .fetch-model-list > .empty-state {
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 12px;
|
||||
}
|
||||
|
||||
.providers-page .fetch-model-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -840,6 +867,19 @@
|
||||
color: #c7e0ff;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .providers-page .btn-field-secondary {
|
||||
border-color: #3b4654;
|
||||
background: linear-gradient(180deg, rgba(28, 36, 49, 0.98), rgba(20, 27, 38, 0.98));
|
||||
color: #bcc8d8;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .providers-page .btn-field-secondary:hover,
|
||||
:root[data-theme="dark"] .providers-page .btn-field-secondary:focus {
|
||||
border-color: #5f8fbe;
|
||||
background: linear-gradient(180deg, rgba(35, 46, 62, 1), rgba(24, 33, 46, 1));
|
||||
color: #d9e7f7;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .providers-page .field-help-button {
|
||||
border-color: #3b4654;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<span class="field-meta-badge" id="providerModelCount">0 个模型</span>
|
||||
</div>
|
||||
<div class="field-header-actions">
|
||||
<button type="button" class="btn-inline-secondary" onclick="tidyModelList()">整理</button>
|
||||
<button type="button" class="btn btn-secondary btn-field-secondary" onclick="tidyModelList()">整理</button>
|
||||
<button type="button" class="btn btn-secondary btn-fetch-inline" id="fetchModelsBtn" onclick="fetchModels()">拉取模型</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -399,6 +399,7 @@
|
||||
|
||||
renderFetchModelPicker();
|
||||
fetchModelPickerModalInstance.show();
|
||||
return normalizedFetchedModels.length;
|
||||
}
|
||||
|
||||
function toggleFetchedModelSelection(encodedModel, checked, inputElement) {
|
||||
@@ -568,10 +569,7 @@
|
||||
}
|
||||
|
||||
function canFetchModels() {
|
||||
return !!(
|
||||
document.getElementById('providerName').value.trim() &&
|
||||
document.getElementById('providerApi').value.trim()
|
||||
);
|
||||
return !!document.getElementById('providerApi').value.trim();
|
||||
}
|
||||
|
||||
function updateFetchModelsButtonState() {
|
||||
@@ -754,7 +752,8 @@
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || '拉取模型失败');
|
||||
}
|
||||
openFetchModelPicker(result.fetched_models);
|
||||
const fetchedCount = openFetchModelPicker(result.fetched_models);
|
||||
showMessage(`已获取到 ${fetchedCount} 个模型`, 'success');
|
||||
} catch (error) {
|
||||
console.error('拉取模型失败:', error);
|
||||
showMessage(error.message || '拉取模型失败', 'error');
|
||||
|
||||
Reference in new issue
Block a user