| --- |
| title: RL Auto |
| sdk: docker |
| app_port: 3001 |
| pinned: false |
| --- |
| |
| # RL-Auto / Task优化 |
|
|
| 本地部署的 **RL 任务包生成 + 校验** 工具。从 echo-extension 录制包(`.zip`)出发,自动产出可直接导入插件评测的任务包:Instruction、Recommended Groups、Rubrics、Checker Catalog、DB Diff Criteria、Pass Policy、Golden Trajectory。 |
|
|
| > **当前架构**:Docker 单端口前后端分离。`docker compose up --build` 是唯一启动方式。 |
| > 已退役:`start.command` / Python static server / `proxy.mjs` CORS 桥。 |
|
|
| 命名对齐 echo-extension-05061347 — subtask 分组模式: |
|
|
| - **API 分组模式**:按 HTTP endpoint 分组,输出 `groupMode: "api"` + 完整 `apiCalls` 对象 |
| - **MCP 分组模式**:按 MCP Tool 分组,输出 `groupMode: "mcp"` + 原始 tool 名(不会被改写为短名) |
|
|
| 模式由后端在上传时根据 ZIP 内容自动识别(含 `mcp_calls.json` / `session.json` → mcp,否则 → api)。前端也可在 `optionsSnapshot.requestedMode` 中显式覆盖。 |
|
|
| --- |
|
|
| ## 工程结构 |
|
|
| ``` |
| . |
| ├── apps/ |
| │ ├── api/ # Node + TS 后端 (Fastify + better-sqlite3 + undici) |
| │ │ └── src/ |
| │ │ ├── server.ts # 启动 + 静态托管 web-legacy + Fastify 路由 |
| │ │ ├── routes/{health,analysis}.ts |
| │ │ ├── queue/ # Store 抽象 + SQLite/Postgres 仓储 + worker pool |
| │ │ ├── analyzers/detect.ts # ZIP → mode 自动识别 |
| │ │ ├── ai/client.ts # 共享 AI client,多 provider 兼容 |
| │ │ ├── judge/{runner,prompt}.ts # 6 维 Judge + pass policy |
| │ │ ├── config.ts # .env 解析 |
| │ │ └── log.ts # pino,自动屏蔽 apiKey/Authorization |
| │ └── web-legacy/ # 当前 SPA(React + Vite,构建后由 Fastify 托管) |
| │ ├── src/ # 任务润色 + API/MCP 分组页面 |
| │ ├── index.html |
| │ └── package.json |
| ├── packages/ |
| │ └── core/ # 平台无关纯逻辑(TS, ESM) |
| │ └── src/ |
| │ ├── importers/{api,mcp}.ts # 解析录制 ZIP → Recording |
| │ ├── prompts/{shared,api,mcp}.ts # 系统 prompt + slim evidence |
| │ ├── rules/{api,mcp}.ts # 本地规则 flag(DSL 校验 + trajectory 卫生) |
| │ ├── rl-env.ts # buildTaskPackage() |
| │ └── zip-export.ts # 写出最终 *.task-package.zip |
| ├── data/ # Docker volume:SQLite + 上传/导出(已 .gitignore) |
| ├── docs/ # 长文档/设计资料(新增文档优先放这里) |
| ├── Dockerfile # 多阶段 node:20 + pnpm,构建 web/core/api |
| ├── docker-compose.yml |
| ├── .env.example |
| └── pnpm-workspace.yaml |
| ``` |
|
|
| `slack-cli/` 与 `discord-cli/` 是独立 npm 子项目,不在本工程范围内。 |
|
|
| ### 本地目录卫生 |
|
|
| - **源码只放这些地方**:`apps/`、`packages/`、根目录配置文件、`README.md`。新增长文档优先放 `docs/`,避免根目录继续变胖。 |
| - **运行数据只放 `data/`、`.data/` 或 `.local-data/`**:上传 ZIP、导出包、SQLite 文件都属于本地运行状态,已被 `.gitignore` 忽略。 |
| - **构建产物不入库**:`apps/api/dist/`、`packages/core/dist/`、`coverage/`、`.vitest/`、`tmp/` 都是可再生成内容。 |
| - **本地私密配置不入库**:`.env`、`.claude/` 保持本机私有;提交前只看 `.env.example`。 |
| - **清理命令**:用 `pnpm clean` 删除 TypeScript 构建产物;系统垃圾如 `.DS_Store`、`nul`、`%USERPROFILE%*` 已在 `.gitignore` 中兜底。 |
|
|
| --- |
|
|
| ## 快速启动 |
|
|
| 需要 Docker(Desktop 或 Engine)。**不再依赖** macOS `start.command`、Python static server 或浏览器 Babel/CDN。 |
|
|
| ```bash |
| git clone <repo> |
| cd Rl-Auto |
| cp .env.example .env |
| # 编辑 .env:填入 DEFAULT_API_KEY / JUDGE_API_KEY / RUBRIC_GEN_API_KEY |
| docker compose up --build |
| ``` |
|
|
| 首次构建 3-5 分钟(拉镜像 + pnpm 装依赖 + 编译 TS)。后续 `docker compose up` 秒级。 |
|
|
| 启动后访问: |
| - 前端:<http://localhost:3001/> |
| - 健康检查:`curl http://localhost:3001/api/health` → `{"code":0,"msg":"ok",…}` |
|
|
| ### 本地开发(绕开 Docker) |
|
|
| ```bash |
| pnpm install |
| pnpm build |
| mkdir -p .local-data/uploads .local-data/exports |
| SQLITE_PATH=$PWD/.local-data/tasks.db \ |
| UPLOAD_DIR=$PWD/.local-data/uploads \ |
| EXPORT_DIR=$PWD/.local-data/exports \ |
| PORT=3001 pnpm dev:api # tsx watch,改 TS 自动重启 |
| ``` |
|
|
| 前端是 Vite 构建产物,由 Fastify 从 `apps/web-legacy/dist/` 托管。修改前端源码后重新运行 `pnpm --filter @task-optimizer/web-legacy build`,再刷新浏览器。 |
|
|
| --- |
|
|
| ## 配置说明 |
|
|
| 配置分两类:**后端 `.env`(部署时设定)** 和 **前端设置面板(用户运行时改)**。API/MCP 分组启动 job 时会随 `optionsSnapshot` 发送前端值,主模型 Key 留空则回退到 `.env` 默认值;首页任务润色固定浏览器直连,必须使用前端填写或个人设置保存的 Key。 |
|
|
| ### 后端 `.env` |
|
|
| | 变量 | 说明 | 默认 | |
| |------|------|------| |
| | `PORT` | 监听端口 | `3001` | |
| | `LOG_LEVEL` | pino 日志级别 | `info` | |
| | `DEFAULT_MODEL` | 主分析模型默认值 | `deepseek-v4-pro` | |
| | `DEFAULT_BASE_URL` | 主分析模型默认 base URL | `https://api.deepseek.com` | |
| | `DEFAULT_API_KEY` | 主分析模型密钥(**前端可覆盖**) | *(必填)* | |
| | `DEFAULT_API_MODE` | `chat` 或 `responses` | `chat` | |
| | `JUDGE_MODEL` / `JUDGE_BASE_URL` / `JUDGE_API_KEY` / `JUDGE_API_MODE` | Judge 模型配置(**只在后端**) | `gpt-5.5` / `https://app-hk.ppapi.ai/v1/chat/completions` | |
| | `RUBRIC_GEN_*` | Rubric 自动补全模型(后端专用) | `gpt-5.5` / `https://app-hk.ppapi.ai/v1/chat/completions` | |
| | `STORE_BACKEND` | `sqlite` 或 `postgres`;多实例部署必须使用 `postgres` | `sqlite` | |
| | `DATABASE_URL` | PostgreSQL 连接串;存在时后端自动使用 Postgres store | *(空)* | |
| | `SQLITE_PATH` | SQLite 路径(仅本地/单实例推荐) | `/app/data/tasks.db` | |
| | `UPLOAD_DIR` / `EXPORT_DIR` | 上传 / 导出目录 | `/app/data/{uploads,exports}` | |
| | `MAX_UPLOAD_MB` | 单文件最大尺寸 | `100` | |
| | `MAX_BATCH_FILES` | 单批最大文件数 | `20` | |
| | `ITEM_MAX_ATTEMPTS` | 单个 item 的 transient 失败自动重试上限 | `3` | |
| | `GLOBAL_LLM_CONCURRENCY` | 单进程 LLM 调用并发上限;`0` 表示不额外限制 | `0` | |
| | `UPSTREAM_PROXY` | 出站代理 URI(替代旧 `proxy.mjs`),如 `socks5://127.0.0.1:1080` 或 `http://...` | *(空)* | |
|
|
| ### 前端设置面板(持久化在浏览器 localStorage) |
|
|
| | 字段 | 说明 | |
| |------|------| |
| | `ruleProfile` | `echo-05061347` / `rl-env-4.30` / `echo-04281753` | |
| | `polishTransport` | 首页任务润色调用路径;当前固定为 `browser` 浏览器直连 | |
| | `analysisTransport` | API/MCP 分组路径;当前固定为 `backend` 队列 | |
| | `apiMode` | `chat` / `responses` | |
| | `baseURL` | 主分析模型 endpoint | |
| | **`apiKey`** | 浏览器直连 API Key | |
| | `savedModelCredentials` / `selectedModelCredentialId` | 个人保存的 Base URL + Key 列表,以及当前选中的连接 | |
| | `modelPreset` / `customModel` | 主模型名 | |
| | `concurrency` | 单 job 内并发 worker 数(1-8) | |
| | `temperature` / `topP` / `maxTokens` | 主模型采样参数 | |
| | `enableThinking` / `clearThinking` | 主模型 thinking 行为开关 | |
| | `optimizeTask` / `feedback` | 任务润色开关 + 用户反馈文本 | |
| | `strictFullMarks` | 严格满分开关(影响 pass policy) | |
| | `ignoreTaskComplexityForFullMarks` | 严格满分时允许 `TASK COMPLEXITY=1/2`,只要求其他 5 个 Judge 维度满分 | |
| | `judge.{baseURL, model, promptTemplate}` | Judge 配置(**仅 baseURL/model/template 可前端改;apiKey 仍只在 .env**) | |
| | `rubricGeneration.{baseURL, model}` | Rubric 自动补全配置(同上) | |
|
|
| > **关于密钥与 localStorage**:首页任务润色固定走 `browser` 直连,必须填写浏览器 API Key,或在个人设置里选择已保存的 Base URL + Key。主模型 `apiKey` 和 `savedModelCredentials` 会按用户操作保存到浏览器 localStorage。API/MCP 分组默认走后端队列。Judge / Rubric Generation 的 apiKey 仍然只在后端 `.env`,从不出现在前端代码或响应里。 |
|
|
| --- |
|
|
| ## 后端 API 契约 |
|
|
| 所有响应使用统一 envelope: |
|
|
| ```json |
| { "code": 0, "msg": "ok", "data": { /* ... */ } } |
| ``` |
|
|
| 非 200 时 `code` 为非 0 整数,`msg` 描述错误。 |
|
|
| | 方法 | 路径 | 说明 | |
| |------|------|------| |
| | `GET` | `/api/health` | 健康检查 | |
| | `POST` | `/api/polish` | 首页任务润色接口;body 为 `{ raw, options? }`,返回 `{ zh, en, notes }` | |
| | `POST` | `/api/analysis/jobs` | multipart 上传 1..N 个 `.zip`,自动识别 mode;返回 `{ jobId, items[] }` | |
| | `GET` | `/api/analysis/jobs/:jobId` | 当前 job + 所有 item 状态 | |
| | `POST` | `/api/analysis/jobs/:jobId/start` | 开始处理;body 含 `optionsSnapshot` | |
| | `POST` | `/api/analysis/jobs/:jobId/cancel` | 当前 in-flight item 跑完后停后续 | |
| | `POST` | `/api/analysis/jobs/:jobId/items/:itemId/retry` | 重置单项为 queued 重新跑 | |
| | `GET` | `/api/analysis/jobs/:jobId/items/:itemId/download` | 流式下载该项 `*.task-package.zip` | |
| | `GET` | `/api/analysis/jobs/:jobId/download-all` | 流式打包所有成功项 → `all-results-{jobId}.zip` | |
|
|
| - **Item status**: `queued | running | judging | completed | failed | stopped` |
| - **Job status**: `pending | running | completed | cancelled` |
|
|
| --- |
|
|
| ## 处理流水线 |
|
|
| 每次上传 ZIP → 一个 item,每个 item 流程如下: |
|
|
| ``` |
| parse (importer) ← packages/core/importers/{api,mcp}.ts |
| ↓ |
| buildEvidence + runLocalRules ← packages/core/rules/{api,mcp}.ts |
| ↓ |
| ┌─────────────────────────────────────────────────────────┐ |
| │ for iter = 1..MAX_JUDGE_ITERATIONS (=5): │ |
| │ buildSlimEvidence + buildUserPrompt │ |
| │ ↓ (上一轮 judge 反馈拼进 prompt) │ |
| │ 主模型 requestJson → analyzerResult │ |
| │ ↓ │ |
| │ buildTaskPackage (含 golden_trajectory) │ |
| │ ↓ │ |
| │ runJudge → 6 维评分 + verdict │ |
| │ ↓ │ |
| │ isJudgeTargetSatisfied? │ |
| │ ✓ → break (PASS) │ |
| │ ✗ → 更新迭代记忆,下一轮带着累计修正继续 │ |
| │ │ |
| │ 跟踪 best score;每轮快照 evidence/task-package/judge │ |
| │ 落盘到 data/exports/{jobId}/{itemId}/ │ |
| └─────────────────────────────────────────────────────────┘ |
| ↓ (PASS) → buildAnalysisZipBuffer → output.zip |
| ↓ (5 轮仍未通过) → 写入 best 那次产物 + 失败信息 |
| ``` |
|
|
| --- |
|
|
| ## Judge 迭代 |
|
|
| `apps/api/src/queue/runner.ts` 内每个 item 最多跑 **`MAX_JUDGE_ITERATIONS = 5`** 轮。每轮: |
|
|
| 1. 把上一轮 `judgeResult.rationale` + 0/1 分维度的 explanation 拼成 `=== JUDGE FEEDBACK ===` 段 |
| 2. 后端迭代记忆模块会合并重复问题、清理已修复维度,并生成下一轮累计反馈 |
| 3. 与 `optionsSnapshot.feedback`(用户输入)合并塞进下一轮 user prompt |
| 4. 模型据此修订 `recommended_groups` / `recommended_rubrics` / `recommended_instruction` |
|
|
| 通过条件(`isJudgeTargetSatisfied`): |
|
|
| - **默认**:`total_score >= 10 AND has_zeros === false` → PASS |
| - **`strictFullMarks` 开启**:`total_score === sum(rubrics[].max_score) AND has_zeros === false` → PASS |
| - **`strictFullMarks` + `ignoreTaskComplexityForFullMarks` 开启**:除 `task_complexity` 外其他 5 个维度都为 `2/2`,且 `task_complexity >= 1/2` → PASS |
|
|
| 满 5 轮仍未通过:用最高分那次的产物写持久化,抛错带 "已迭代 N/5 轮,最佳 X 分"。 |
|
|
| 每轮快照:`data/exports/{jobId}/{itemId}/{evidence,task-package,judge-result,iteration-memory}-iter-N.json`,便于追溯。 |
|
|
| --- |
|
|
| ## 并发模型 |
|
|
| ``` |
| Job (上传 5 个 ZIP) |
| ├─ Worker #1 ─→ takeNextQueuedItem() → item A → 5 轮 judge 迭代 → export |
| ├─ Worker #2 ─→ takeNextQueuedItem() → item B → 5 轮 judge 迭代 → export |
| ├─ Worker #3 ─→ takeNextQueuedItem() → item C → ... |
| └─ ... (worker 数 = optionsSnapshot.concurrency, 1-8) |
| ``` |
|
|
| - **per-job 内并发**:`concurrency` 个 worker 并行 drain 同一 job 的 queue。SQLite 本地模式使用事务原子抢取;Postgres 多实例模式使用 `FOR UPDATE SKIP LOCKED`,避免不同 API 实例重复消费同一个 item。 |
| - **跨 job 并发**:旧版有 `workerLock` 跨 job 串行链,已撤销。多用户/多次上传可同时跑。 |
| - **硬上限**:`MAX_CONCURRENCY = 8`(`apps/api/src/queue/runner.ts`),保护上游模型 API rate limit。 |
| - **LLM 全局上限**:`GLOBAL_LLM_CONCURRENCY` 可限制单进程内 analyzer/judge 同时调用模型的数量。 |
| - **失败重试**:`ITEM_MAX_ATTEMPTS` 只重试 transient AI/network 错误;ZIP 格式错误、严格 MCP tools 缺失、Judge 质量未达标会直接 failed。 |
| - **配置**:前端"并发数"输入框(设置面板),默认 3。 |
|
|
| --- |
|
|
| ## MCP 严格 trajectory 卫生 |
|
|
| 为避免 Judge 在 Trajectory-Task Alignment 维度扣分(典型问题:跨 channel 的 list_messages、`start_typing` 噪音、`get_unread` 出现在 `mark_read` 之前),有三层防御: |
|
|
| ### 第 1 层:本地规则 flag(`packages/core/src/rules/mcp.ts`) |
|
|
| `runMcpRules` 主动检测并 flag: |
|
|
| - `cross_entity_channelId` / `cross_entity_guildId` / `cross_entity_messageId` / ...:录制涉及多个不同实体 ID |
| - `unread_check_before_mark_read`:`get/list_unread` 出现在 `mark_*_read` 之前 |
| - `noise_tools_present`:背景调用(typing/presence/heartbeat/auth/telemetry/...) |
|
|
| ### 第 2 层:evidence 过滤(`isSignificantMcpCall`) |
|
|
| Slim evidence 阶段直接剔除噪音工具,模型甚至看不到: |
|
|
| ``` |
| login | logout | refresh_token | get_session | whoami | get_current_user | get_self | me | default_config |
| start_typing | stop_typing | set_typing | presence | heartbeat | ping |
| get_pending_* | list_pending_* | telemetry_* | page_load | page_view | init_* | bootstrap_* |
| *_typing_* | auth_* | session_* | *_telemetry | *_heartbeat | *_ping |
| ``` |
|
|
| ### 第 3 层:System prompt 强约束(`MCP_TRAJECTORY_RULES`) |
|
|
| 写进 `MCP_SYSTEM_PROMPT`,模型必须遵守: |
|
|
| 1. **ENTITY CONSISTENCY**:所有 group + rubric 引用的 calls 必须共享同一个 channel/guild/message ID。识别主线实体 = 最多写操作命中的那个 ID。 |
| 2. **NOISE BLACKLIST**:明确列出禁用工具,不能进 `recommended_groups.calls` 或 `checker_key`。 |
| 3. **ORDER CONSTRAINTS**: |
| - `mark_*_read` 必须在 `get_unread_*` / `list_unread_*` 之前 |
| - `create_*` 必须在 `list_*` / `get_*` 验证调用之前 |
| - `send_message` 必须在 `list_messages` / `search_*_messages` 之前 |
| 4. **STRAY-CALL REJECTION**:违反以上的 stray 调用一律剔除,可记入 `evidence_limits`。 |
|
|
| --- |
|
|
| ## 输出 ZIP 格式 |
|
|
| 输出 ZIP 与上传 ZIP **完全同结构**,仅 `task.json` 被重写为含完整任务包字段: |
|
|
| - 顶层保留 echo-extension 兼容字段:`metadata`、`task.instruction`、`task.networkRequests`、`task.subtasks`、`task.rubrics` |
| - 顶层嵌入 RL 字段:`rule_profile`、`rubric_checkers`、`dbdiff_criteria`、`pass_policy`、`golden_trajectory`、`network`、`validation` |
| - API 模式:`task.subtasks[*].groupMode === "api"`,`apiCalls` 是完整对象(`{ id, name, type, input, output, metadata }`) |
| - MCP 模式:`task.subtasks[*].groupMode === "mcp"`,tool name 严格保留 |
| - `network.json` / `mcp_calls.json` / `session.json` / `dbdiff.json` 等按原始字节保留 |
|
|
| --- |
|
|
| ## 数据持久化 |
|
|
| `./data/` 是 Docker volume mount 目标: |
|
|
| ``` |
| data/ |
| ├── tasks.db # SQLite jobs/items |
| ├── uploads/{jobId}/{itemId}.zip # 上传原始 |
| └── exports/{jobId}/{itemId}/ |
| ├── evidence.json # 最终(best)证据 |
| ├── task-package.json # 最终任务包 |
| ├── judge-result.json # 最终 judge 结果 |
| ├── evidence-iter-N.json # 每轮迭代快照 |
| ├── task-package-iter-N.json |
| ├── judge-result-iter-N.json |
| ├── output.zip # 仅 PASS 时生成 |
| └── error.txt # 失败时 |
| ``` |
|
|
| 清理某个 job 的所有数据: |
|
|
| ```bash |
| docker compose down |
| rm -rf ./data/uploads/<jobId> ./data/exports/<jobId> |
| sqlite3 ./data/tasks.db "DELETE FROM items WHERE job_id='<jobId>'; DELETE FROM jobs WHERE id='<jobId>';" |
| docker compose up |
| ``` |
|
|
| --- |
|
|
| ## 验收清单 |
|
|
| 1. `docker compose up --build` 一条命令启动成功 |
| 2. `http://localhost:3001` UI 显示正常 |
| 3. 单 API ZIP → 分析 → 下载 `*.task-package.zip`:`groupMode: "api"` + `apiCalls` 完整对象 |
| 4. 单 MCP ZIP → 同上:`groupMode: "mcp"`,tool name 严格保留,无跨实体 / 噪音 / 顺序问题 |
| 5. 一次拖 N 个混合 ZIP → 按 `concurrency` 并发处理,可中途取消 |
| 6. 杀容器再 `docker compose up` → 队列从 SQLite 恢复,未完成 item 状态可见 |
| 7. 主模型 apiKey:前端填入或选择个人保存的 Base URL + Key → 浏览器直连立即生效 |
| 8. devtools Network 抓包:首页任务润色直接命中所选模型 Base URL;API/MCP 分组只命中 `localhost:3001/api/*` |
| 9. 严格满分开关:开启 → 须 `total = max_score`;关闭 → `total >= 10`;同时开启“忽略 Complexity”时允许 `TASK COMPLEXITY=1/2` |
| 10. Judge 失败时自动迭代下一轮,最多 5 轮,每轮都会更新后端迭代记忆并写进下轮 prompt(容器日志可见 `Iteration start` / `Judge target not yet satisfied`) |
|
|
| --- |
|
|
| ## 故障排查 |
|
|
| | 现象 | 排查 | |
| |------|------| |
| | `docker compose up` 卡在装 `better-sqlite3` | Dockerfile 已含 native deps;本地 `pnpm install` 失败需先装 Xcode CLT (`xcode-select --install`) | |
| | `/api/health` 返回 502 / 连接拒绝 | `docker compose ps` 看 service 状态;`docker compose logs -f app` 查启动日志 | |
| | 大 ZIP 上传 413 | 调大 `MAX_UPLOAD_MB` 后重启容器 | |
| | AI 调用失败 401 / 429 | 检查相应 KEY;后端日志会用安全 preview 显示模型返回(自动屏蔽 `Authorization` / `apiKey`) | |
| | Judge 一直 NEEDS_REVISION 跑满 5 轮 | 看 `data/exports/{jobId}/{itemId}/judge-result-iter-N.json` 和 `iteration-memory-iter-N.json`,找出反复失败的维度;考虑改 `optionsSnapshot.feedback` 给模型更针对性的指令 | |
| | MCP Trajectory-Task Alignment 仍 ≤ 1 | 检查录制是否真有跨 channel 的杂讯 / 噪音工具未被剔除(`evidence-iter-N.json` 里 `slim.callInstances`) | |
| | 主模型 apiKey 改了不生效 | 浏览器刷新;确认设置面板里 "浏览器 API Key" 输入框值已保存,或重新选择个人设置里的连接 | |
| |
| --- |
| |
| ## 设计决策与历史 |
| |
| - **退役 `proxy.mjs`**:原本浏览器直连模型 endpoint 通过 Node CORS proxy 中转。后端化之后,所有 AI 调用走 `apps/api/src/ai/client.ts`,不需要 CORS。如需出站代理(公司网络)用 `UPSTREAM_PROXY` 环境变量(基于 undici `ProxyAgent`)。 |
| - **Pass policy 阈值从 9 升到 10**:旧 echo-extension Judge prompt 里 PASS=9,但实际经验下 9 分包仍有较多噪音。运行时由后端 `isJudgeTargetSatisfied` 决定,模型 prompt 不变。 |
| - **iteration 循环搬到后端**:原先在 vanilla JS 前端做;后端化后,前端只负责 polling 状态,循环完整运行在 `runner.ts`。 |
| - **主模型 apiKey 前端化**:早期为防止 localStorage 泄漏,所有 apiKey 都不允许前端持有。现在主模型 key 已开放(用户自主选择),Judge / Rubric Generation 的 key 仍后端独控。 |
|
|
| --- |
|
|
| ## 许可 |
|
|
| 私有项目,未开源。 |
|
|