角色:黑盒计算 + 模型路由(无业务数据库)
端口 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 级重点;N2 至 N5 分别讲该级及更难的重点;es_low、es_high、jh、hs 则只讲超出日本小学低年级、小学高年级、中学和高校预期能力的内容。这个预设只调整讲解门槛与深度,不影响翻译、假名、摘要或转写;regenerate 的自定义 Prompt 优先于预设。
黑盒契约
控制器只关心产出,不关心怎么算出来的:
| 操作 | 持久化的产出 |
|---|
| analyze-units | 每句 translation / explanation / status |
| regenerate | explanation |
| tts | 音频字节(控制器存入 data_server 的对象存储) |
| transcribe / polish | 文本(成为文章 raw_content) |
| split | sentences[](临时) |
完整运行时,提供方按需初始化
标准安装默认包含 GiNZA/spaCy(断句)以及 OpenAI/Azure SDK。各提供方仍在函数内部延迟初始化,因此未配置外部服务 Key 时不会在启动阶段连接供应商;正则断句等降级行为继续保留。ffmpeg 是转写所需的独立系统程序。
配置:AI APIs 与 API Key
agent_server 是唯一持有 LLM / 转写 / TTS 密钥的服务。聊天模型不再从旧 YAML 的 PRIMARY_*、FALLBACK_* 或 MODEL_PROVIDERS 解析,而由管理控制台的 AI APIs → Models 配置:
- 新建 OpenAI-compatible Endpoint,填写名称、Base URL、API Key 和默认计费倍率;慢速/推理模型可启用 SSE 流式传输并把请求超时设为 600 秒。
- 获取远端模型列表或手工新建 ModelBinding,并按需设置温度、最大输出 Token、Thinking effort、可选价格和倍率覆盖;倍率留空时继承 Endpoint。
- 保存首个可用 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 可选值为 disabled、low、medium、high。留空(控制台中的 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=false 时 readiness_code=no_eligible_model。内部接口仍接受 primary_binding_id 作为精确诊断/兼容选择,并保留模型名字段:
| 接口 | 精确选择 | 旧式模型名选择 |
|---|
analyze-units / regenerate / summarize-article | primary_binding_id | primary_model |
polish | primary_binding_id | model |
primary_binding_id 与对应模型名字段互斥,同时传入会返回 422。primary_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_PATH | Whisper/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 均可加载。
- 转写需要 ffmpeg:
brew 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,并在启动前回收端口,避免残留旧进程造成密钥/数据库不一致。