init: consolidate all ephron.ren PRDs and docs
This commit is contained in:
511
prd-llm-profile-management.md
Normal file
511
prd-llm-profile-management.md
Normal file
@@ -0,0 +1,511 @@
|
||||
# LLM 多提供商配置管理 重构 PRD
|
||||
|
||||
> **版本**: v1.0
|
||||
> **日期**: 2026-05-09
|
||||
> **状态**: 📝 待评审
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与动机
|
||||
|
||||
### 1.1 现状分析
|
||||
|
||||
当前 `/admin/settings` 页面采用**按调用协议(Anthropic / OpenAI)硬编码**的方式管理 LLM 配置:
|
||||
|
||||
| 层 | 文件 | 现状 |
|
||||
|---|---|---|
|
||||
| 存储 | settings.py | 12 个 `llm.*` key,按协议前缀分组 |
|
||||
| 服务 | settings.py | `get_llm_config()` 硬编码两套,`get_active_provider_config()` 用 if/else 分支 |
|
||||
| 调用 | llm.py | `chat_completion()` 按 `config["provider"]` 分发到 `_call_anthropic` / `_call_openai` |
|
||||
| 路由 | admin.py | POST 接收 12 个独立 Form 字段 |
|
||||
| 前端 | settings.html | 两张固定的 provider-card,点击切换 |
|
||||
|
||||
### 1.2 缺失的能力
|
||||
|
||||
| 场景 | 当前行为 | 期望行为 |
|
||||
|------|----------|----------|
|
||||
| 临时切换到另一个提供商 | 手动改 URL + Key + 模型名(3 个字段) | 一键切换 |
|
||||
| 切换回来 | 再手动改 3 个字段 | 一键切换 |
|
||||
| 某提供商同时支持两种协议 | 无法在一个配置下管理 | 同一提供商下可混合不同协议的模型 |
|
||||
| 添加新的提供商 | 只能在 Anthropic 或 OpenAI 二选一 | 自定义添加任意数量的提供商 |
|
||||
|
||||
### 1.3 用户核心诉求
|
||||
|
||||
> "我配置了提供商 A 的模型,想暂时换用提供商 B,目前只能改 URL 和 Key,改了之后模型名称又对不上了。切回来又要再改一次。"
|
||||
|
||||
本质:**从"单套配置"变成"多套配置方案"的管理方式。**
|
||||
|
||||
---
|
||||
|
||||
## 二、功能定义
|
||||
|
||||
### 2.1 功能描述
|
||||
|
||||
将 LLM 设置从"按协议管理两套固定配置"重构为"按提供商管理多套自定义配置方案"。
|
||||
|
||||
核心变化:
|
||||
- **提供商(Profile)**:用户自定义的配置方案,包含名称、URL、API Key
|
||||
- **模型**:每个提供商下可配置多个模型,每个模型指定调用协议(anthropic / openai)
|
||||
- **全局参数**:temperature、max_tokens、timeout 等保持全局,不随提供商切换
|
||||
|
||||
### 2.2 用户故事
|
||||
|
||||
1. 作为管理员,我想保存多套 LLM 提供商配置,这样我可以快速在不同提供商之间切换,而不用每次手动改多个字段
|
||||
2. 作为管理员,我想给每个模型指定调用协议(Anthropic / OpenAI 兼容),这样同一个提供商下可以混合使用不同协议的模型
|
||||
3. 作为管理员,我想保留现有的配置数据,升级后自动迁移,不需要重新配置
|
||||
|
||||
### 2.3 交互设计
|
||||
|
||||
#### 页面布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ LLM 设置 [返回管理] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─ 提供商列表 ────┐ ┌─ 编辑区 ──────────────────────────────┐ │
|
||||
│ │ │ │ │ │
|
||||
│ │ 🔵 我的 Claude │ │ 名称: [我的 Claude ] │ │
|
||||
│ │ ○ DeepSeek │ │ Base URL: [https://api.anthropic...] │ │
|
||||
│ │ ○ 本地 Ollama │ │ API Key: [sk-ant-...] [👁] [测试] │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ [+ 新建提供商] │ │ ┌─ 模型列表 ─────────────────────┐ │ │
|
||||
│ │ │ │ │ claude-sonnet-4 Claude 4 Sonnet │ │ │
|
||||
│ │ │ │ │ 协议: [anthropic ▾] │ │ │
|
||||
│ │ │ │ │ claude-opus-4 Claude 4 Opus │ │ │
|
||||
│ │ │ │ │ 协议: [anthropic ▾] │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ [+ 添加模型] [删除当前] │ │ │
|
||||
│ │ │ │ └──────────────────────────────────┘ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ [保存提供商] [删除提供商] │ │
|
||||
│ └─────────────────┘ └───────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─ 全局参数 ──────────────────────────────────────────────────┐ │
|
||||
│ │ Temperature: [0.7] Max Tokens: [8000] Timeout: [120s] │ │
|
||||
│ │ 速率限制: [10] 次/分钟 [60] 次/小时 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [保存全局参数] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 交互流程
|
||||
|
||||
1. **切换提供商**:点击左侧列表中的提供商卡片 → 右侧编辑区显示该提供商的配置
|
||||
2. **新建提供商**:点击 `[+ 新建提供商]` → 创建空配置 → 自动选中 → 右侧可编辑
|
||||
3. **删除提供商**:点击 `[删除提供商]` → 确认弹窗 → 删除并切换到列表第一项(不允许删除最后一个)
|
||||
4. **添加模型**:在模型列表点击 `[+ 添加模型]` → 新增一行,协议默认 openai
|
||||
5. **删除模型**:点击模型行的 `×` 按钮
|
||||
6. **测试连接**:使用当前编辑区的 URL + Key + 第一个模型发起测试请求
|
||||
7. **保存**:提供商配置和全局参数分开保存,各自有独立的保存按钮
|
||||
|
||||
### 2.4 API 设计
|
||||
|
||||
#### GET /admin/settings
|
||||
|
||||
返回设置页面,传入:
|
||||
- `profiles`: 所有配置方案列表
|
||||
- `active_profile_id`: 当前激活的方案 ID
|
||||
- `global_config`: 全局参数(temperature, max_tokens, timeout, rate_limit)
|
||||
|
||||
#### POST /admin/settings/save-profiles
|
||||
|
||||
保存提供商配置。
|
||||
|
||||
**请求体**(Form):
|
||||
```
|
||||
csrf_token: string
|
||||
active_profile_id: string # 当前激活的方案 ID
|
||||
profiles_json: string # JSON 序列化的方案列表
|
||||
```
|
||||
|
||||
**profiles_json 结构**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "prof_1715234567_abc",
|
||||
"name": "我的 Claude",
|
||||
"base_url": "https://api.anthropic.com",
|
||||
"api_key": "sk-ant-...",
|
||||
"models": [
|
||||
{
|
||||
"id": "claude-sonnet-4-20250514",
|
||||
"alias": "Claude 4 Sonnet",
|
||||
"protocol": "anthropic"
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o",
|
||||
"alias": "GPT-4o",
|
||||
"protocol": "openai"
|
||||
}
|
||||
],
|
||||
"default_model_index": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**校验规则**:
|
||||
- `name` 必填,最长 50 字符
|
||||
- `base_url` 必填,必须以 `http://` 或 `https://` 开头
|
||||
- `api_key` 可选(某些本地模型不需要)
|
||||
- `models` 至少有一个模型
|
||||
- 每个模型的 `id` 必填,`protocol` 必须是 `anthropic` 或 `openai`
|
||||
- `profiles` 不能为空(至少保留一个方案)
|
||||
|
||||
**成功响应**:302 重定向到 `/admin/settings?success=1`
|
||||
|
||||
**错误响应**:302 重定向到 `/admin/settings?error={message}`
|
||||
|
||||
#### POST /admin/settings/save-global
|
||||
|
||||
保存全局参数。
|
||||
|
||||
**请求体**(Form):
|
||||
```
|
||||
csrf_token: string
|
||||
temperature: float (0-2)
|
||||
max_output_tokens: int (256-200000)
|
||||
request_timeout: int (10-600)
|
||||
rate_limit_per_minute: int (1-1000)
|
||||
rate_limit_per_hour: int (1-10000)
|
||||
```
|
||||
|
||||
#### POST /admin/settings/test-connection(可选增强)
|
||||
|
||||
测试提供商连接。
|
||||
|
||||
**请求体**(JSON):
|
||||
```json
|
||||
{
|
||||
"base_url": "https://api.anthropic.com",
|
||||
"api_key": "sk-ant-...",
|
||||
"model_id": "claude-sonnet-4-20250514",
|
||||
"protocol": "anthropic"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
// 成功
|
||||
{"success": true, "model": "claude-sonnet-4-20250514", "latency_ms": 1200}
|
||||
|
||||
// 失败
|
||||
{"success": false, "error": "Invalid API key"}
|
||||
```
|
||||
|
||||
### 2.5 数据模型
|
||||
|
||||
**不新建表**,继续使用现有的 `settings` key-value 表。
|
||||
|
||||
#### 存储结构
|
||||
|
||||
| Key | Value | 说明 |
|
||||
|-----|-------|------|
|
||||
| `llm.active_profile_id` | `"prof_xxx"` | 当前激活的方案 ID |
|
||||
| `llm.profiles` | `JSON array` | 所有方案的 JSON 序列化 |
|
||||
| `llm.temperature` | `"0.7"` | 全局默认 temperature |
|
||||
| `llm.max_output_tokens` | `"8000"` | 全局默认最大输出 tokens |
|
||||
| `llm.request_timeout` | `"120"` | 全局默认请求超时(秒) |
|
||||
| `llm.rate_limit_per_minute` | `"10"` | 每分钟速率限制 |
|
||||
| `llm.rate_limit_per_hour` | `"60"` | 每小时速率限制 |
|
||||
|
||||
#### Profile 数据结构
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "prof_1715234567_abc",
|
||||
"name": "我的 Claude",
|
||||
"base_url": "https://api.anthropic.com",
|
||||
"api_key": "sk-ant-...",
|
||||
"models": [
|
||||
{
|
||||
"id": "claude-sonnet-4-20250514",
|
||||
"alias": "Claude 4 Sonnet",
|
||||
"protocol": "anthropic"
|
||||
}
|
||||
],
|
||||
"default_model_index": 0
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | string | 自动生成 | 格式 `prof_{timestamp}_{random}` |
|
||||
| name | string | ✅ | 用户自定义名称,最长 50 字符 |
|
||||
| base_url | string | ✅ | API 端点地址 |
|
||||
| api_key | string | ❌ | API 密钥(本地模型可为空) |
|
||||
| models | array | ✅ | 模型列表,至少 1 个 |
|
||||
| models[].id | string | ✅ | 模型 ID,用于 API 调用 |
|
||||
| models[].alias | string | ❌ | 显示别名 |
|
||||
| models[].protocol | string | ✅ | `"anthropic"` 或 `"openai"` |
|
||||
| default_model_index | int | ❌ | 默认模型的索引,默认 0 |
|
||||
|
||||
#### 向后兼容迁移
|
||||
|
||||
在 `init_db()` 中检测旧 key 存在时自动转换:
|
||||
|
||||
```python
|
||||
def _migrate_llm_profiles(cursor):
|
||||
"""将旧的按协议存储的配置迁移到新的 profile 格式"""
|
||||
cursor.execute(
|
||||
"SELECT key, value FROM settings "
|
||||
"WHERE key LIKE 'llm.anthropic_%' OR key LIKE 'llm.openai_%' "
|
||||
"OR key = 'llm.active_provider'"
|
||||
)
|
||||
old = {row["key"]: row["value"] for row in cursor.fetchall()}
|
||||
|
||||
if not old:
|
||||
return # 已迁移或全新安装
|
||||
|
||||
# 检查是否已经迁移过
|
||||
cursor.execute("SELECT 1 FROM settings WHERE key = 'llm.profiles'")
|
||||
if cursor.fetchone():
|
||||
return
|
||||
|
||||
profiles = []
|
||||
import time, json
|
||||
|
||||
# 迁移 Anthropic 配置
|
||||
if old.get("llm.anthropic_api_key") or old.get("llm.anthropic_base_url"):
|
||||
models = json.loads(old.get("llm.anthropic_models_json", "[]"))
|
||||
profiles.append({
|
||||
"id": f"prof_{int(time.time())}_anthropic",
|
||||
"name": "Anthropic",
|
||||
"base_url": old.get("llm.anthropic_base_url", "https://api.anthropic.com"),
|
||||
"api_key": old.get("llm.anthropic_api_key", ""),
|
||||
"models": [{"id": m["id"], "alias": m.get("alias", ""), "protocol": "anthropic"} for m in models] or [
|
||||
{"id": "claude-sonnet-4-20250514", "alias": "Claude 4 Sonnet", "protocol": "anthropic"}
|
||||
],
|
||||
"default_model_index": 0,
|
||||
})
|
||||
|
||||
# 迁移 OpenAI 配置
|
||||
if old.get("llm.openai_api_key") or old.get("llm.openai_base_url"):
|
||||
models = json.loads(old.get("llm.openai_models_json", "[]"))
|
||||
profiles.append({
|
||||
"id": f"prof_{int(time.time())}_openai",
|
||||
"name": "OpenAI 兼容",
|
||||
"base_url": old.get("llm.openai_base_url", "https://api.openai.com/v1"),
|
||||
"api_key": old.get("llm.openai_api_key", ""),
|
||||
"models": [{"id": m["id"], "alias": m.get("alias", ""), "protocol": "openai"} for m in models] or [
|
||||
{"id": "gpt-4o", "alias": "GPT-4o", "protocol": "openai"}
|
||||
],
|
||||
"default_model_index": 0,
|
||||
})
|
||||
|
||||
if not profiles:
|
||||
return
|
||||
|
||||
# 确定激活的方案
|
||||
active_provider = old.get("llm.active_provider", "anthropic")
|
||||
active_profile = next(
|
||||
(p for p in profiles if any(m["protocol"] == active_provider for m in p["models"])),
|
||||
profiles[0]
|
||||
)
|
||||
|
||||
# 写入新格式
|
||||
cursor.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now'))",
|
||||
("llm.profiles", json.dumps(profiles, ensure_ascii=False))
|
||||
)
|
||||
cursor.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now'))",
|
||||
("llm.active_profile_id", active_profile["id"])
|
||||
)
|
||||
|
||||
# 清理旧 key
|
||||
old_keys = [
|
||||
"llm.active_provider",
|
||||
"llm.anthropic_base_url", "llm.anthropic_api_key", "llm.anthropic_models_json",
|
||||
"llm.openai_base_url", "llm.openai_api_key", "llm.openai_models_json",
|
||||
]
|
||||
for key in old_keys:
|
||||
cursor.execute("DELETE FROM settings WHERE key = ?", (key,))
|
||||
```
|
||||
|
||||
### 2.6 安全与限制
|
||||
|
||||
| 项目 | 策略 |
|
||||
|------|------|
|
||||
| 认证 | 需登录 + `prompt.entry.view_admin` 权限查看,`prompt.entry.edit_any` 权限修改 |
|
||||
| CSRF | 所有 POST 请求验证 CSRF token |
|
||||
| API Key 存储 | 明文存储在 SQLite(与现有行为一致,后续可考虑加密) |
|
||||
| 速率限制 | POST 接口 20 次/分钟(与现有行为一致) |
|
||||
| 输入校验 | URL 格式、temperature 范围、token 范围等 |
|
||||
| 最少方案数 | 不允许删除最后一个方案,至少保留一个 |
|
||||
|
||||
---
|
||||
|
||||
## 三、技术方案
|
||||
|
||||
### 3.1 架构变更
|
||||
|
||||
```
|
||||
改动前:
|
||||
settings.html → POST 12个字段 → admin.py → update_settings(12个key)
|
||||
→ get_llm_config() 硬编码两套
|
||||
→ get_active_provider_config() if/else
|
||||
→ llm.py 按 provider 分发
|
||||
|
||||
改动后:
|
||||
settings.html → POST profiles_json + global_params
|
||||
→ admin.py → save_profiles() / save_global_params()
|
||||
→ settings.py → get_active_profile() 动态获取
|
||||
→ get_active_provider_config() 从 profile 构建
|
||||
→ llm.py 不变(仍按 config["provider"] 分发)
|
||||
```
|
||||
|
||||
### 3.2 文件改动清单
|
||||
|
||||
| 文件 | 改动 | 说明 |
|
||||
|------|------|------|
|
||||
| `prompt/src/services/settings.py` | **中** | 新增 `get_all_profiles()`, `get_active_profile()`, `save_profiles()`;重构 `get_active_provider_config()`;保留 `get_llm_config()` 只返回全局参数 |
|
||||
| `prompt/src/services/db.py` | **小** | `init_db()` 末尾加迁移函数调用 |
|
||||
| `prompt/src/routes/admin.py` | **中** | settings 路由改为接收 `profiles_json`;新增 test-connection 路由 |
|
||||
| `prompt/templates/admin/settings.html` | **大** | 完全重写:左侧列表 + 右侧编辑区 + 模型列表动态增删 |
|
||||
| `prompt/src/services/llm.py` | **不动** | 只看 `config["provider"]`,不感知 profile 概念 |
|
||||
| `prompt/src/services/rate_limiter.py` | **不动** | 读全局 rate_limit |
|
||||
|
||||
### 3.3 settings.py 核心函数
|
||||
|
||||
```python
|
||||
import json
|
||||
import time
|
||||
import secrets
|
||||
|
||||
def get_all_profiles() -> list[dict]:
|
||||
"""获取所有配置方案"""
|
||||
raw = get_setting("llm.profiles") or "[]"
|
||||
try:
|
||||
profiles = json.loads(raw)
|
||||
return profiles if isinstance(profiles, list) else []
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
|
||||
def get_active_profile() -> dict | None:
|
||||
"""获取当前激活的配置方案"""
|
||||
profiles = get_all_profiles()
|
||||
if not profiles:
|
||||
return None
|
||||
active_id = get_setting("llm.active_profile_id")
|
||||
return next((p for p in profiles if p["id"] == active_id), profiles[0])
|
||||
|
||||
|
||||
def save_profiles(profiles: list[dict], active_id: str) -> bool:
|
||||
"""保存所有配置方案"""
|
||||
return update_setting("llm.profiles", json.dumps(profiles, ensure_ascii=False)) \
|
||||
and update_setting("llm.active_profile_id", active_id)
|
||||
|
||||
|
||||
def generate_profile_id() -> str:
|
||||
"""生成方案 ID"""
|
||||
ts = int(time.time())
|
||||
rand = secrets.token_hex(4)
|
||||
return f"prof_{ts}_{rand}"
|
||||
|
||||
|
||||
def get_active_provider_config() -> dict:
|
||||
"""从当前 profile 构建调用配置(返回格式不变,llm.py 无需改动)"""
|
||||
profile = get_active_profile()
|
||||
if not profile:
|
||||
raise LLMError("没有配置任何 LLM 提供商", "config_error")
|
||||
|
||||
models = profile.get("models", [])
|
||||
default_idx = profile.get("default_model_index", 0)
|
||||
|
||||
# 获取全局参数
|
||||
global_config = get_llm_config()
|
||||
|
||||
return {
|
||||
"provider": models[default_idx]["protocol"] if models and default_idx < len(models) else "openai",
|
||||
"base_url": profile["base_url"],
|
||||
"api_key": profile.get("api_key", ""),
|
||||
"default_model": models[default_idx]["id"] if models and default_idx < len(models) else "",
|
||||
"available_models": [m["id"] for m in models],
|
||||
"temperature": global_config["temperature"],
|
||||
"max_output_tokens": global_config["max_output_tokens"],
|
||||
"request_timeout": global_config["request_timeout"],
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 前端实现要点
|
||||
|
||||
1. **提供商列表**:用 JS 动态渲染,点击切换编辑目标
|
||||
2. **模型列表**:每个模型行包含 model_id、alias、protocol 下拉框
|
||||
3. **表单提交**:用隐藏字段 `profiles_json` 存储 JSON,JS 在 submit 前序列化
|
||||
4. **测试连接**:AJAX 请求,显示成功/失败结果
|
||||
5. **新建/删除**:纯前端操作,保存时一起提交
|
||||
|
||||
---
|
||||
|
||||
## 四、优先级与排期
|
||||
|
||||
| 阶段 | 内容 | 预计时间 | 依赖 |
|
||||
|------|------|----------|------|
|
||||
| P0 | settings.py:新增 profile CRUD 函数 | 0.5h | 无 |
|
||||
| P0 | db.py:迁移逻辑 | 0.5h | P0 settings.py |
|
||||
| P0 | admin.py:重写 settings 路由 | 1h | P0 settings.py |
|
||||
| P0 | settings.html:重写前端 | 3h | P0 admin.py |
|
||||
| P1 | 测试连接 API(AJAX) | 0.5h | P0 |
|
||||
| P1 | 迁移测试 + 功能测试 | 1h | P0 |
|
||||
| **总计** | | **6.5h** | |
|
||||
|
||||
---
|
||||
|
||||
## 五、技术风险与决策点
|
||||
|
||||
### 5.1 决策记录
|
||||
|
||||
| 决策点 | 选项 | 选择 | 理由 |
|
||||
|--------|------|------|------|
|
||||
| 存储方式 | A: JSON 单字段 / B: 新建表 | A | 不改表结构,迁移简单,对这个项目规模足够 |
|
||||
| 参数粒度 | A: 全局 / B: 方案级 / C: 模型级 | A | 大多数用户切换提供商时不需要改参数,减少复杂度 |
|
||||
| 协议位置 | A: 方案级 / B: 模型级 | B | 支持同一提供商下混合不同协议的模型 |
|
||||
| 速率限制 | A: 全局 / B: 方案级 | A | 安全措施,不应随提供商切换 |
|
||||
| llm.py | A: 改 / B: 不改 | B | `get_active_provider_config()` 返回格式不变,隔离变更 |
|
||||
|
||||
### 5.2 技术风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 迁移逻辑丢失旧数据 | 🔴 高 | 迁移前备份,迁移函数幂等(INSERT OR IGNORE) |
|
||||
| JSON 过大(100+ 方案) | 🟢 低 | 实际场景不可能超过 10 个方案 |
|
||||
| 并发编辑竞态 | 🟢 低 | 单管理员场景,SQLite 写锁保护 |
|
||||
| 前端 JSON 序列化错误 | 🟡 中 | 提交前校验 JSON 结构,错误时阻止提交并提示 |
|
||||
|
||||
---
|
||||
|
||||
## 六、附录
|
||||
|
||||
### A. 相关文件清单
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `prompt/src/services/settings.py` | 设置服务,核心改动 |
|
||||
| `prompt/src/services/db.py` | 数据库初始化,加迁移 |
|
||||
| `prompt/src/services/llm.py` | LLM 调用层,不改动 |
|
||||
| `prompt/src/services/rate_limiter.py` | 速率限制,不改动 |
|
||||
| `prompt/src/routes/admin.py` | 管理路由,改动 |
|
||||
| `prompt/templates/admin/settings.html` | 设置页面,重写 |
|
||||
| `prompt/src/config.py` | 配置,不改动 |
|
||||
|
||||
### B. 旧配置迁移映射
|
||||
|
||||
| 旧 Key | 迁移到 |
|
||||
|--------|--------|
|
||||
| `llm.active_provider` | `llm.active_profile_id`(通过匹配 protocol) |
|
||||
| `llm.anthropic_base_url` | profiles[0].base_url |
|
||||
| `llm.anthropic_api_key` | profiles[0].api_key |
|
||||
| `llm.anthropic_models_json` | profiles[0].models(每个 model 加 protocol="anthropic") |
|
||||
| `llm.openai_base_url` | profiles[1].base_url |
|
||||
| `llm.openai_api_key` | profiles[1].api_key |
|
||||
| `llm.openai_models_json` | profiles[1].models(每个 model 加 protocol="openai") |
|
||||
| `llm.temperature` | 保持不变(全局参数) |
|
||||
| `llm.max_output_tokens` | 保持不变(全局参数) |
|
||||
| `llm.request_timeout` | 保持不变(全局参数) |
|
||||
| `llm.rate_limit_per_minute` | 保持不变(全局参数) |
|
||||
| `llm.rate_limit_per_hour` | 保持不变(全局参数) |
|
||||
Reference in New Issue
Block a user