Rl-Auto / README.md
Lazywords's picture
Deploy RL Auto Docker Space
c4ae742
|
Raw
History Blame Contribute Delete
20.8 kB
metadata
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_Storenul%USERPROFILE%* 已在 .gitignore 中兜底。

快速启动

需要 Docker(Desktop 或 Engine)。不再依赖 macOS start.command、Python static server 或浏览器 Babel/CDN。

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)

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 chatresponses 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 sqlitepostgres;多实例部署必须使用 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:1080http://... (空)

前端设置面板(持久化在浏览器 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。主模型 apiKeysavedModelCredentials 会按用户操作保存到浏览器 localStorage。API/MCP 分组默认走后端队列。Judge / Rubric Generation 的 apiKey 仍然只在后端 .env,从不出现在前端代码或响应里。


后端 API 契约

所有响应使用统一 envelope:

{ "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 = 8apps/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_readget/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.callschecker_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 兼容字段:metadatatask.instructiontask.networkRequeststask.subtaskstask.rubrics
  • 顶层嵌入 RL 字段:rule_profilerubric_checkersdbdiff_criteriapass_policygolden_trajectorynetworkvalidation
  • 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 的所有数据:

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.zipgroupMode: "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.jsoniteration-memory-iter-N.json,找出反复失败的维度;考虑改 optionsSnapshot.feedback 给模型更针对性的指令
MCP Trajectory-Task Alignment 仍 ≤ 1 检查录制是否真有跨 channel 的杂讯 / 噪音工具未被剔除(evidence-iter-N.jsonslim.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 仍后端独控。

许可

私有项目,未开源。