# DeciderGate **Repository Path**: q2918007/decider-gate ## Basic Information - **Project Name**: DeciderGate - **Description**: DeciderGate 是一个**模型无关的 Jev 范式决策网关**,对外暴露统一的 `/v1/systemone` 接口,内部通过可插拔后端抽象层,无缝切换不同的 System1 决策模型。 它为上层 RAG/Agent 业务屏蔽底层模型差异:业务方只需要一次接入,后续可以根据效果、成本、硬件资源随时切换或灰度不同决策模型,无需修改业务代码。 - **Primary Language**: Python - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-17 - **Last Updated**: 2026-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DeciderGate 面向教学知识库的智能路由**网关**:以统一接口托管多种决策模型(Decision Model),完成"这条提问该去哪个知识库"的高频判断,并将低置信度请求拦截转发给全库检索或大模型兜底。 所有后端模型遵循 Jev `/v1/systemone` 协议,DeciderGate 在上层提供统一的请求格式、置信度门控和兜底策略——**切换模型是配置变更,不是代码变更**。 ## 为什么用决策模型做路由 - **快**:一次前向计算并行产出所有答案,无自回归解码,端到端毫秒级 - **省**:输出免费、按输入计费,高频路由场景成本比 LLM 低 1–2 个数量级 - **稳**:输出永远落在预定义选项集内,不存在格式解析失败 - **可兜底**:低置信请求自动升级,错误路由不会静默放大 - **可替换**:模型迭代、许可证变化、硬件变更都不影响上层业务 ## 架构 ``` 学生提问 │ ▼ ┌────────────────────────────────────────────┐ │ DeciderGate │ │ 统一 API (state + 选项集 + 问题) │ │ 置信度门控 & 兜底策略(按模型标定) │ │ ┌──────────────────────────────────────┐ │ │ │ Backend Adapter 层 │ │ │ │ Intern-Decision │ Kev │ │ │ │ Clef-flash │ Laya │ │ │ │ (任何 /v1/systemone 模型) │ │ │ └──────────────────────────────────────┘ │ └──────────────────────────┬─────────────────┘ │ ┌───────────────┴───────────────┐ │ │ 高置信度 (≥阈值) 低置信度 (<阈值) │ │ ▼ ▼ 路由到目标知识库 全库检索 / 大模型兜底 ``` ### 后端模型一览 | 后端 | 提供方 | 档位 | 硬件 | 微调 | 许可 | 特点 | | --------------- | ----------- | ---------- | ----------------- | ----------------- | ------------- | -------------------------- | | Intern-Decision | 上海 AI 实验室 | 0.8B/2B/4B | GPU | 需自建管线 | TODO: 以 HF 为准 | 校准最优(ECE 0.089)、多模态、中文本土 | | Kev | jaredpalmer | 0.8B–27B | GPU/Apple Silicon | **本地 LoRA 管线最成熟** | Apache-2.0 | 微调成本约 $1/次,校准一般 | | Clef-flash | Cloudflare | 9B | ≥8GB 显存 | 仅云端 RL 服务 | Apache-2.0 | 分类成绩强、多模态、Ollama/llama.cpp | | Laya | 社区 | 0.4B | **纯 CPU** | 不建议 | Apache-2.0 | 轻量兜底、zh-CN 偏弱 | 模型切换、灰度对比(如 Intern-Decision 与 Kev 同题评测)、按流量分 backend,均在配置中完成。 ## 快速开始 ### 本地(开发) ```bash # 1. 安装依赖(需要 Python >= 3.12) # 依赖已锁定(uv.lock / requirements.lock.txt),推荐用 uv sync 安装以保证可复现: # pip install uv && uv sync pip install -e ".[dev]" # 2. 启动所选后端的推理服务(vLLM / kev.serve / llama.cpp / Ollama) # 无真实后端时可用 mock:在 config/backend.yaml 中将 backend 设为 mock # 3. 配置并启动网关 export DECIDERGATE_CONFIG=config/backend.yaml uvicorn decidergate.main:app --port 8000 ``` ### 调用示例 ```bash curl -X POST http://localhost:8000/v1/systemone \ -H 'content-type: application/json' \ -d '{"state":{},"options":["kb_math","kb_phy"],"question":"勾股定理是什么?"}' ``` ### 生产(单点内网,推荐 systemd) ```ini # /etc/systemd/system/decidergate.service [Unit] Description=DeciderGate routing gateway After=network-online.target [Service] User=decidergate WorkingDirectory=/opt/decidergate Environment=DECIDERGATE_CONFIG=/etc/decidergate/backend.yaml ExecStart=/opt/decidergate/.venv/bin/python -m uvicorn decidergate.main:app --host 127.0.0.1 --port 8000 Restart=on-failure [Install] WantedBy=multi-user.target ``` ```bash systemctl enable --now decidergate systemctl restart decidergate # 改配置后重启生效 ``` ### Docker 单容器即可(无需 compose 编排): ```bash docker run -d --restart unless-stopped \ -p 127.0.0.1:8000:8000 \ -v /etc/decidergate/backend.yaml:/etc/decidergate/backend.yaml:ro \ -e DECIDERGATE_CONFIG=/etc/decidergate/backend.yaml \ decidergate:0.1.0 ``` > 仅本地开发需顺带起 Ollama 等后端时,才用 docker-compose 多服务编排。 > 详细部署、网络隔离、回滚与灾备见 [docs/deployment-plan.md](docs/deployment-plan.md)。 ### 服务端点 | 方法 | 路径 | 用途 | | --- | --- | --- | | POST | `/v1/systemone` | 路由判定(state + options + question -> target / 兜底) | | GET | `/healthz` | 存活探针 | | GET | `/readyz` | 就绪探针(配置已加载、主 backend 探活;backend 挂时返回 degraded 仍可服务兜底) | | GET | `/metrics` | Prometheus 文本指标(QPS / 延迟分布 / 兜底率),便于 scrape | **配置加载**:`backend.yaml` 于网关**启动时一次性加载并校验**;编辑配置后需**重启服务**生效 (单点内网部署,无热加载)。阈值、路由策略、兜底目标均为配置驱动。 **可观测(§6.1)**:`observability.log_level` 与 `observability.metrics_enabled` 已接线—— 前者作用于整个 `decidergate` 包与 uvicorn 日志,后者控制 `/metrics` 是否采集。 `/metrics` 输出 **Prometheus 文本格式**(histogram + counter),单点内网以**单 uvicorn worker** 运行即可 直接被 Prometheus/Grafana scrape;若扩多 worker 需开启 `PROMETHEUS_MULTIPROC_DIR`(多进程模式)。 **接入层防护(GAP-9)**:`observability.ratelimit_*` 控制每客户端 IP 固定窗口限流(探针路径豁免), `observability.max_body_bytes` 限制请求体大小(超出返回 413)。单点内网也建议开启,抵御异常/恶意流量。 > 模型切换、灰度对比、按流量分 backend,均在 `config/backend.yaml` 中完成(详见 docs/)。 > 置信度阈值必须按各自评测集单独标定,LoRA 微调后需重新标定。 ### 运行测试 ```bash pip install -e ".[dev]" pytest -q ``` 兜底未配置 endpoint 时返回明确占位串(如 `[fallback:llm] no downstream configured`),不会静默挂起(AC3); 配置了下游但调用失败会按 `fallback_config` 重试,最终仍失败则降级为明确错误串而非 500。 **指向真实服务**:把 `backends.*.endpoint` 与 `fallback_endpoints.*` 填为可达地址即可联通—— 网关按 `/v1/systemone` 协议与之通信(请求体含 state/options/question/model,响应为 `{target, confidence}` 或 `{scores: {option: raw}}`)。`adapters/` 与 `core/fallback.py` 已通过 ASGI-transport 集成测试验证真实 HTTP 路径(见 tests/test_*_integration.py)。 ## 集成契约(网关 = 分类器) 网关只做路由**判定**:返回命中知识库的标识 `target`(`null` 表示进入兜底),**不代发**对知识库的查询请求。调用方凭 `target` 自行访问对应知识库服务;`config/backend.yaml` 中已移除历史上的 `knowledge_bases` 注册表(死配置)。 ## 离线标定(§8 / Phase 6) 置信度阈值必须按各自评测集单独标定。提供离线工具(**不进入线上请求路径**)拟合 Platt 归一化并搜索阈值: ```bash # 评测集 JSONL:每行 { backend, raw_score, correct } 或 { backend, predicted_option, gold_option } python -m decidergate.calibration --input eval.jsonl --backend intern-decision ``` 输出可直接贴入 `backend.yaml` 的片段,例如: ```yaml # backend=intern-decision 标定结果(n=200,Brier 0.21xx -> 0.03xx) # normalize: { method: platt, a: 1.83, b: -0.42 } # confidence.threshold.intern-decision: 0.71 ``` - 拟合:Platt scaling `conf = sigmoid(a*raw + b)`(标准牛顿法,纯 Python,无 numpy 依赖)。 - 阈值:在评测集上使 F1 最大(支持 `--min-recall` 约束),平局优先更低阈值(更多请求命中模型、更少走兜底)。 - 模型迭代 / LoRA 微调 / 数据漂移后须重新标定(§8 第 5 步)。 ### 配置示例 ```yaml # config/backend.yaml backend: intern-decision # kev | clef-flash | laya | ... backends: intern-decision: endpoint: http://localhost:8000/v1/systemone model: intern-decision-4b kev: endpoint: http://localhost:8500/v1/systemone model: kev-4b # 每个 backend 还可选配:headers(含鉴权,支持 ${ENV} 插值)、timeout、max_connections、 # breaker_enabled / breaker_threshold / breaker_cooldown(轻量熔断,见 GAP-5/6) # 置信度门控(⚠️ 每个模型的阈值必须用自有评测集单独标定, # LoRA 微调后需重新标定) confidence: threshold: { default: 0.65 } fallback: full-corpus-retrieval # 接入层可观测与防护(均带默认值,可省略) observability: log_level: INFO metrics_enabled: true # false 时 /metrics 不采集 log_file: null # 可选:决策日志持久化路径(JSONL),作为离线标定输入(GAP-12) ratelimit_enabled: true # 每客户端 IP 固定窗口限流 ratelimit_rate: 120 # 窗口内最大请求数 ratelimit_window: 60 # 窗口长度(秒) max_body_bytes: 1048576 # 请求体上限 1 MiB,超出返回 413 # 接入层鉴权(OP-5):启用后 /v1/systemone 与 /metrics 需校验请求头; # /healthz、/readyz 探针豁免。scheme=apikey 接受裸 token 或 "ApiKey "。 auth: enabled: false scheme: apikey token: ${DECIDERGATE_API_TOKEN} # 经环境变量注入,不落仓库 header: Authorization # 跨域(GAP-15):仅当学生前端为浏览器直连时启用;默认关闭。 cors: enabled: false allow_origins: [] ``` ## 本地模型(进程内运行,Path B) 除把 KEV / Intern-Decision 当作远端服务(官方 server 说 `/v1/systemone`,用现有 `remote` adapter 指向它)之外,也可以让网关**在自己的进程内直接加载并运行模型**。 - 配置:`BackendConfig.kind: local` + `local: { engine, checkpoint, ... }`。 `engine` 目前支持 `kev` 与 `intern_decision`,各自通过官方 Python API 调用 (详见 `docs/dev/plan-local-backend.md`,含真实签名与安装步骤)。 - 单进程只跑**一个**本地模型:配置校验强制**至多一个 `kind: local` 的 backend**, 切换模型 = 改 `backend:` + 重启(设计目标是切换而非并行)。 - 模型权重与重型依赖(torch / kev / Intern-Decision 仓库)按官方说明单独安装; 网关仅在真正构建本地 backend 时才惰性 import 这些依赖,基础镜像与 CI 不受影响。 - 启动时 `Router.warmup()` 会预先加载 active 本地模型,加载失败即快速报错。