角色:黑盒计算 + 模型路由(无业务数据库)

端口 8011 · 仅内部调用 · 可 mock · 持有本地路由配置
agent_server 把所有 AI 相关计算收敛到一处。它不持有文章、用户或任务等业务数据,也没有任务 ID——编排与进度全部由控制器负责。它会持久化一份仅供自身使用的模型路由配置;整套系统仍可以用一个假的 agent 跑通(数据优先 + 黑盒原则)。

接口 /v1/agent/*

方法 / 路径用途输入 → 输出
GET /v1/agent/models可用模型目录→ {bindings[], default_binding_id, models, default}
POST /v1/agent/split断句{content, genre} → {sentences[]}
POST /v1/agent/build-prompt构造 prompt(不调用 LLM){intro, sentence, explanation_level?} → {translate_prompt, analysis_prompt}
POST /v1/agent/analyze-units批量翻译 + 讲解{intro, units[], explanation_level?, primary_binding_id?, primary_model?} → {units:[{sentence_index, translation, explanation, status}]}
POST /v1/agent/regenerate重新生成单句讲解{intro, sentence, explanation_level?, full_prompt?} → {explanation}
POST /v1/agent/tts合成音频{items:[{id, text, voice?}]} → {items:[{id, audio_b64, voice}]}
POST /v1/agent/transcribe媒体转文字(按路径){server_path, language, ...} → {raw_text}
POST /v1/agent/transcribe-upload媒体转文字(multipart 上传)multipart → {raw_text}
POST /v1/agent/polish润色转写文本{text, model?} → {polished_text}
POST /v1/agent/compliance/check合规检查{...} → {...}
POST /v1/agent/exercise/generate生成练习{...} → {...}
所有端点都需要 Bearer(本地校验共享 JWT)。
build-prompt 是个不调用 LLM 的纯计算端点:它让控制器拿到与原单体一致的 prompt 文案,从而把 prompt 逻辑的单一事实来源保留在 agent_server 内。
explanation_level 默认 N3,并由单句、批量、Prompt 预览和重新生成共用。N1 只讲 N1 级重点;N2N5 分别讲该级及更难的重点;es_lowes_highjhhs 则只讲超出日本小学低年级、小学高年级、中学和高校预期能力的内容。这个预设只调整讲解门槛与深度,不影响翻译、假名、摘要或转写;regenerate 的自定义 Prompt 优先于预设。

黑盒契约

控制器只关心产出,不关心怎么算出来的:
操作持久化的产出
analyze-units每句 translation / explanation / status
regenerateexplanation
tts音频字节(控制器存入 data_server 的对象存储)
transcribe / polish文本(成为文章 raw_content
splitsentences[](临时)

完整运行时,提供方按需初始化

标准安装默认包含 GiNZA/spaCy(断句)以及 OpenAI/Azure SDK。各提供方仍在函数内部延迟初始化,因此未配置外部服务 Key 时不会在启动阶段连接供应商;正则断句等降级行为继续保留。ffmpeg 是转写所需的独立系统程序。

配置:AI APIs 与 API Key

agent_server唯一持有 LLM / 转写 / TTS 密钥的服务。聊天模型不再从旧 YAML 的 PRIMARY_*FALLBACK_*MODEL_PROVIDERS 解析,而由管理控制台的 AI APIs → Models 配置:
  1. 新建 OpenAI-compatible Endpoint,填写名称、Base URL、API Key 和默认计费倍率;慢速/推理模型可启用 SSE 流式传输并把请求超时设为 600 秒。
  2. 获取远端模型列表或手工新建 ModelBinding,并按需设置温度、最大输出 Token、Thinking effort、可选价格和倍率覆盖;倍率留空时继承 Endpoint。
  3. 保存首个可用 Binding 后即可工作;可选 CallPlan 只用于指定不同模型名之间的默认模型与 Fallback 顺序。
Endpoint 的 Test connection 使用当前未保存候选值且不写文件;创建或保存 Endpoint 时,服务端会先对同一候选执行真实 /models 探测,只有连接成功才按 revision 原子写入。失败不会改变旧配置。控制台中的 API Key 留空表示候选没有 Key,填写即覆盖,不再提供单独的清除选项。 保存立即作用于后续调用,无需重启服务;进行中的调用继续使用开始时取得的不可变快照。API Key 只写入 agent_server 的路由文件,所有管理 API 只返回 key_configured,不会回显原值。CallPlan 为空时按 Binding 创建顺序使用首个可用模型;只有不存在“Binding、Endpoint、Key 均已启用”的组合时,聊天模型接口才返回 503 model_routing_not_configured/health 仍保持 200。 ModelBinding 的 thinking_effort 可选值为 disabledlowmediumhigh。留空(控制台中的 Provider default)时不向提供商发送推理控制字段,行为与升级前一致;disabled 发送 {"thinking":{"type":"disabled"}},其余值发送 reasoning_effort。不同提供商支持范围不同,因此 Model Test 与正式调用使用同一配置,让不兼容设置在投入文章处理前暴露。每次调用的实际预设会随 UsageRecord 保存,便于核对推理 Token、延迟和费用。
首次创建路由文档(以及 v1 → v2 迁移)时会预置 OpenRouter 与 AIHubMix 两个空 Endpoint 模板:不带 Binding、不进入 CallPlan,只有各自专用的旧 Key(OPENROUTER_API_KEY / AIHUBMIX_API_KEY)存在时才导入,绝不会复制 PRIMARY_API_KEY。模板只是省去手填 Base URL,本身不产生任何调用;系统仍不预置任何模型,也不回退到旧 YAML。首次运行后请在 http://localhost:8015 的 AI APIs → Models 至少配置一个 ModelBinding;CallPlan 可选。删除模板后不会重新生成。

文章选择模型与同名路由

文章/视频上传只选择 model_id,不会让用户选择 Endpoint 或 Binding。同一个 model_id 可以存在于多个 Endpoint;这些 Binding 自动组成同名路由组。GET /v1/agent/models 返回全部可用、去重后的模型名和结构化就绪状态,同时保留 Binding 字段供旧客户端与诊断使用:
{
  "bindings": [
    {"binding_id": "051b5447-…", "model_id": "gemini-3.5-flash", "label": "gemini-3.5-flash", "endpoint_label": "Geili-Gemini"}
  ],
  "default_binding_id": "051b5447-…",
  "models": ["gemini-3.5-flash"],
  "default": "gemini-3.5-flash",
  "routing_ready": true,
  "readiness_code": "ready"
}
models 是上传客户端的正式目录,default 是 CallPlan 的首个有效模型,或空 CallPlan 下首个可用模型。routing_ready=falsereadiness_code=no_eligible_model。内部接口仍接受 primary_binding_id 作为精确诊断/兼容选择,并保留模型名字段:
接口精确选择旧式模型名选择
analyze-units / regenerate / summarize-articleprimary_binding_idprimary_model
polishprimary_binding_idmodel
primary_binding_id 与对应模型名字段互斥,同时传入会返回 422primary_model 可选择任意可用模型,即使它未显式加入 CallPlan;服务端会把它的全部同名 Binding 放在最前,然后仅按 CallPlan 尝试其他模型名。精确 Binding 仍优先,但不会阻止同名 Binding 作为后续路由。 Controller 在文章任务开始时从 data_server 固定一次历史 Token 画像。某模型至少有 20 个成功完整样本时使用该模型画像,否则回退同 operation 最近 1,000 次全局样本,再无样本使用输入/输出 1:1。每个同名 Binding 用该画像乘 RateCard 与有效计费倍率估价:有完整价格的由低到高,缺价的排在其后;同价按管理员顺序和创建顺序稳定排序。这样只在同名模型间优先更便宜的 Endpoint,不会为了便宜自动切换到另一个模型。 路由配置默认保存在 agent_server/.data/model-routing.json,该文件被 Git 忽略,并以 0600 权限原子替换。容器部署使用 /data/model-routing.json 的持久卷。当前实现面向单个 agent_server worker/副本;多个副本不会自动同步本地文件。

Speech & automation

AI APIs → Speech & automation 管理 Whisper API Key / Base URL / Model 和 Azure TTS Key / Region / Voice。密钥只写不回显;空输入保留,显式清除用于禁用,逐字段恢复会删除后台覆盖。语音配置位于 agent_server/.data/speech-config.json,使用独立 revision、原子替换与 0600 权限,并在每个后续请求开始时读取快照。Whisper 测试接受不超过 5 MB 的常见音频并在临时文件清理前执行真实转写;Azure 测试合成固定日文短句并返回可播放 MP3。两者都使用未保存候选值、可能产生费用,且错误会移除密钥。 语音字段优先级为 后台运行时设置 > 环境变量 > config.local.yaml > config.yaml > 默认值。旧环境/YAML 值仅是部署兼容回退。
变量说明
JWT_SECRET校验 Bearer 的共享密钥(须与其余服务一致)
MODEL_ROUTING_CONFIG_PATH模型路由 JSON 路径;本地默认 .data/model-routing.json
SPEECH_RUNTIME_CONFIG_PATHWhisper/Azure 运行时 JSON;本地默认 .data/speech-config.json
WHISPER_API_KEY / WHISPER_BASE_URL转写的部署回退;标准入口是 AI APIs → Speech & automation
AZURE_TTS_KEY / AZURE_TTS_REGION语音合成的部署回退;自动 TTS 同页设置
LLM_REQUEST_TIMEOUT单次 LLM 读超时(秒),默认 300
LLM_CONNECT_TIMEOUT默认连接阶段超时(秒),Endpoint 可单独覆盖
管理链路是 manager_server /api/manager/{model-routing,speech-settings}*immersive_study_server /api/admin/*agent_server /v1/agent/admin/*。Endpoint test 使用 /models 做廉价连通性检查;Binding test 则复用生产单句 Rakull 提示词,验证翻译、假名、讲解和 JSON 结构,属于可能消耗数千 Token 的真实付费请求且不会自动重试。启用流式传输时,agent_server 按 UTF-8 读取 SSE、拼接 delta.content,并从最终 usage chunk 记录 Token;HTTP 524 或已开始输出后中断的请求不会在同一 Binding 上重复尝试,而是进入下一条同名路由或 CallPlan 中的下一模型。
配置错误不会重试。网络不可达时,Gateway 会跳过本次计划中同主机的后续 Binding,再尝试其他同名路由与 CallPlan 模型;普通模型错误继续下一条路由。所有项失败时返回 502。

前置依赖与网络

  • 语音 / 转写 SDK 默认安装:本地开发执行标准 cd rakull_server/agent_server && uv sync 即会安装 azure-cognitiveservices-speech(TTS)和 openai(Whisper);Alibaba Cloud Linux 原生部署的 prepare 与标准 Docker 构建同样安装完整运行时。原生安装器提供 Azure SDK 所需的 alsa-lib,且 prepare 会验证 Azure Speech、OpenAI 与 ALSA 均可加载。
  • 转写需要 ffmpegbrew install ffmpeg(或 conda install -c conda-forge ffmpeg)。缺失时 transcribe-upload 返回 Transcription failed: ... 'ffmpeg'
  • AIHubMix 网络可达性:某些网络会对 aihubmix.com 做 DNS 污染 / 拦截(表现为连接超时或证书不匹配),此时需挂能访问 aihubmix.com 的 VPN / 代理。OpenRouter、Azure 一般不受影响,因此可能出现「语音(TTS)正常,但翻译 / 转写超时」——这不是 Key 的问题,而是网络。不可达时 agent 会在 LLM_CONNECT_TIMEOUT 内返回 502,上传任务标为失败,而不会长时间卡住。

运行

cd rakull_server/agent_server
JWT_SECRET=dev-secret uv run uvicorn app.main:app --port 8011
也可用 bash scripts/run_all_servers.sh 启动整套服务:它会统一注入同一个 JWT_SECRET,并在启动前回收端口,避免残留旧进程造成密钥/数据库不一致。