服务通过 FastAPI 暴露,默认监听 http://localhost:8000。所有业务端点以 /api/v1 为前缀;交互式 Swagger UI 在 /docs。
改编范式为 AI 主导,规则兜底:每个改编 stage 默认调 LLM,失败时该 stage 自动落到对应规则路径,响应的 warnings[] 会追加 STAGE_DEGRADED 条目。无 LLM key 时所有 stage 一并降级,YAML 仍是 schema 合法的;配置 MIMO_API_KEY 或 DEEPSEEK_API_KEY 后,quality_metrics.ai_stages_applied 字段告诉前端哪几个 stage 这次完整跑通了 AI 路径。字段语义见 YAML_SCHEMA.md。
-
请求与响应内容类型:除上传端点为
multipart/form-data外均使用application/json。 -
字符编码:UTF-8。
-
错误响应:状态码 ≥ 400 时返回结构化 detail:
{ "detail": { "code": "ERROR_CODE", "message": "对应的中文说明" } }常见
code:code 含义 出现端点 EMPTY_TEXTtext 字段缺失或全为空白 /adaptEMPTY_FILE上传文件为空 /adapt/uploadMISSING_FILENAMEmultipart 未带 filename /adapt/uploadUNSUPPORTED_FORMAT后缀不在白名单 /adapt/uploadFILE_TOO_LARGE上传超过 8 MiB /adapt/uploadDECODE_FAILED非 UTF-8 / 损坏 DOCX /adapt/upload注:
FEW_CHAPTERS(章节不足 3 个)不再是 4xx 错误码,已降级为质量警告,出现在响应的warnings[]中。所有/adapt调用即便只识别到 1 个章节也会返回 200 + schema 合法 YAML。
以下环境变量在 uvicorn 启动前设置(或写入 .env,应用启动时通过 python-dotenv 加载)。所有端点都从环境读 key,请求 body 无 LLM 字段。
| env | 取值 | 默认 | 说明 |
|---|---|---|---|
DEEPSEEK_API_KEY |
sk-... |
unset | DeepSeek 默认 provider。OpenAI 兼容 /v1/chat/completions 协议。 |
MIMO_API_KEY |
sk-... / tp-... |
unset | 小米 MiMo 备选 provider。tp- 前缀自动路由到 https://token-plan-cn.xiaomimimo.com/v1;sk- 走 https://api.xiaomimimo.com/v1。两者都是 OpenAI 兼容协议。 |
STORY2SCRIPT_LLM_PROVIDER |
deepseek / mimo |
按 key 自动检测(两个 key 都配时选 deepseek) | 显式覆盖自动检测。 |
STORY2SCRIPT_LLM_MODEL |
provider 支持的模型名 | deepseek-v4-flash / mimo-v2.5-pro |
覆盖默认模型。 |
STORY2SCRIPT_AI_LED_STAGES |
all / none / 逗号分隔子集 |
all |
灰度控制哪些 stage 走 LLM。取值集:chapters, scenes, characters, beats, refine。未列出的 stage 强制走规则;不发警告(视作用户主动选择)。 |
stage 与失败行为对应表:
| stage | LLM 任务 | 失败时回退到 | 警告 |
|---|---|---|---|
chapters |
让模型按段落编号返回章节边界 + 标题 | detect_chapters(heading → regex → fallback) |
一条 [stage:chapters] |
scenes |
每章一次调用,返回 scenes [{time, location, summary, 段落范围}] | segment_scenes 时间/地点关键词切分 |
无 key 一条 summary;按章失败一条 per chapter + chapter_index |
characters |
整篇 digest 一次,返回 canonical + aliases + 首次出场 scene | extract_characters 姓氏正则 + 别名合并 |
一条 [stage:characters] |
beats |
每场景一次,返回 beats [{kind, text, speaker}] | extract_beats 引号扫描 + 说话动词窗口 |
无 key 一条 summary;多 scene 失败一条带总数 |
refine |
按 mode = {fidelity, expansion} 精修已切好的 beats |
无规则版精修;失败则保留原 beats 不动 | 默认 mode(medium/balanced)整 stage 短路,不发警告 |
存活探针。
响应 200:
{ "status": "ok", "version": "0.1.0" }JSON 提交原文,端到端跑改编流水线,返回 schema 合法 YAML + 质量报告。
请求体:
{
"text": "第一章 启程\n\n张三离开了小镇。\n\n...",
"source_format": "txt",
"title": "送别",
"mode": {
"fidelity": "medium",
"expansion": "balanced"
}
}| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
text |
string (≥ 1) | — | 原文。 |
source_format |
"txt"|"markdown"|"docx" |
"txt" |
仅决定段落规范化策略;DOCX 走 TXT 退化解析。真 DOCX 请用上传端点。 |
title |
string (1-200) | "未命名作品" |
写入 YAML metadata.title。 |
mode.fidelity |
"high"|"medium"|"low" |
"medium" |
保真度。 |
mode.expansion |
"minimal"|"balanced"|"rich" |
"balanced" |
扩写量。 |
响应 200:
{
"screenplay_yaml": "schema_version: 0.1.0\n...",
"schema_validation": { "valid": true, "errors": [] },
"quality_metrics": {
"chapter_count": 3,
"scene_count": 4,
"character_count": 2,
"detection_method": "llm",
"mean_chapter_confidence": 0.88,
"ai_stages_applied": ["chapters", "scenes", "characters", "beats"],
"llm_provider": "mimo"
},
"warnings": [
{ "code": "SHORT_CHAPTER", "message": "...", "scene_id": "scene_1" }
]
}quality_metrics 字段 |
类型 | 说明 |
|---|---|---|
chapter_count / scene_count / character_count |
int | 输出 YAML 中三个集合的元素数 |
detection_method |
string | chapters stage 的检测策略:headings / regex / fallback / llm |
mean_chapter_confidence |
number | 全部 chapter confidence 的算术平均,[0, 1] |
ai_stages_applied |
string[] | 端到端无任何 per-item 回退的 stage 名子集;空列表表示无 key 或全部降级 |
llm_provider |
string | null | provider 名("mimo" / "deepseek");无 key 时 null |
典型错误:
400 EMPTY_TEXT:空文本。- 章节不足 3 个不再 4xx;通过
warnings[].code == "FEW_CHAPTERS"提示。
multipart 上传,DOCX 二进制必走此端点。
Form 字段:
| name | required | 默认 | 说明 |
|---|---|---|---|
file |
是 | — | .txt / .text / .md / .markdown / .docx,≤ 8 MiB。 |
title |
否 | 文件名 stem | 若空白将自动回落到文件名(去掉后缀)。 |
fidelity |
否 | "medium" |
同上。 |
expansion |
否 | "balanced" |
同上。 |
响应:同 /adapt。
典型错误:
415 UNSUPPORTED_FORMAT:detail 含suffix。413 FILE_TOO_LARGE。400 EMPTY_FILE/MISSING_FILENAME/DECODE_FAILED。
把作者改过的 YAML 回传服务端做 JSON Schema 全量校验。永不 5xx:malformed YAML 也只返回 valid: false + 路径化错误,前端可原地高亮字段。
请求体:
{ "screenplay_yaml": "schema_version: 0.1.0\n..." }响应 200:
{
"valid": false,
"errors": [
{ "path": "/scenes/0/beats/2", "message": "'speaker' is a required property" }
]
}path 是 JSON Pointer 风格,从根 / 开始。valid: true 时 errors 必为空数组。
LLM 重写一段场景摘要,用于 /app YAML 编辑器下方的"AI 优化场景摘要"面板。需配 LLM key。
请求体:
{ "original_text": "张三准备离开小镇,李四前来送别。夕阳把候车厅染成暖橙色。" }| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
original_text |
string | 1 ≤ len ≤ 4000 | 待精修的原文段落或现有摘要 |
响应 200:
{
"refined": "张三在车站告别李四,独自登车而去。",
"model": "mimo-v2.5-pro",
"confidence": 0.75
}错误:
503 LLM_NOT_CONFIGURED:未设MIMO_API_KEY/DEEPSEEK_API_KEY。502 LLM_REFINE_FAILED:LLM 调用失败或返回为空。422:original_text为空或超过 4000 字符。
LLM 重写作者在 /editor 选中的一段文本,可携带场景上下文做语义对齐。需配 LLM key。
请求体:
{
"text": "天亮就走。",
"instruction": "改得更口语化",
"context": "张三和李四在候车厅道别,气氛沉重。"
}| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
text |
string | 1 ≤ len ≤ 2000 | 待精修的选中文本,进 <input> tag |
instruction |
string | 1 ≤ len ≤ 500 | 自然语言指令,例如"改得更书面化" / "换成李四的语气" |
context |
string | ≤ 2000,可省 | 可选场景上下文,进 <context> tag;LLM 只读不改 |
响应 200:
{
"refined": "明儿一早我就走。",
"model": "mimo-v2.5-pro",
"confidence": 0.75
}错误:与 /refine/scene-summary 一致(503 / 502 / 422)。
两个 refine 端点均不受
STORY2SCRIPT_AI_LED_STAGES控制——那个 flag 管的是/adapt流水线 5 个 stage 的灰度,refine 是作者主动触发的编辑动作,点了就跑。
整场景 AI 润色:用指定 mode = {fidelity, expansion} 重写一个 scene 的全部 beats。需配 LLM key。
请求体:
{
"screenplay_yaml": "schema_version: ...",
"scene_id": "scene_3",
"fidelity": "low",
"expansion": "rich"
}| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
screenplay_yaml |
string | len ≥ 1 | 当前完整 YAML(编辑器的 sessionStorage 草稿) |
scene_id |
string | len ≥ 1 | 目标场景 id(YAML 里 scenes[].id) |
fidelity |
high / medium / low |
enum | 保真度 |
expansion |
minimal / balanced / rich |
enum | 扩写量 |
响应 200:
{
"scene_id": "scene_3",
"beats": [
{ "kind": "stage_direction", "text": "INT. 候车厅 - 凌晨", "speaker": null, "source_type": "generated", "confidence": 0.85, "needs_review": false },
{ "kind": "dialogue", "text": "到了打电话。", "speaker": "char_001", "source_type": "generated", "confidence": 0.85, "needs_review": false }
],
"model": "deepseek-v4-flash"
}错误:
| 状态 | code | 说明 |
|---|---|---|
| 503 | NO_LLM_PROVIDER |
未配 LLM key |
| 400 | INVALID_YAML |
YAML 解析失败 |
| 404 | SCENE_NOT_FOUND |
scene_id 在 YAML 里找不到 |
| 422 | EMPTY_SCENE |
场景 beats 为空,无可润色内容 |
| 422 | INVALID_SCENE |
场景字段不合法(不满足 Scene pydantic 校验) |
| 502 | REFINE_FAILED |
LLM 跑完但返回不可用;详情在 logs/story2script.log stage=refine 行 |
{medium, balanced} 是 no-op 模式,前端按钮在该模式禁用;如绕过约束硬调,结果与 REFINE_FAILED 等价。
把当前 YAML 渲染成作者可读的剧本格式。不需要 LLM。
请求体:
{
"screenplay_yaml": "schema_version: ...",
"format": "fountain"
}| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
screenplay_yaml |
string | len ≥ 1 | 待导出 YAML(编辑器的当前草稿) |
format |
fountain / txt / md |
enum | 输出格式;PDF 由前端用浏览器打印生成,不在服务端 |
响应 200:
{
"content": "Title: 离镇\nNotes: AI mode = ...\n\n...",
"format": "fountain",
"mime_type": "text/x-fountain; charset=utf-8",
"suggested_filename": "离镇.fountain"
}| 格式 | MIME | 后缀 | 用途 |
|---|---|---|---|
fountain |
text/x-fountain; charset=utf-8 |
.fountain |
行业标准纯文本,Final Draft / WriterDuet / Highland 可直接读 |
txt |
text/plain; charset=utf-8 |
.txt |
居中标题 + 缩进对白 + 章节横线的可读版 |
md |
text/markdown; charset=utf-8 |
.md |
GitHub / Notion 预览友好 |
错误:
| 状态 | code | 说明 |
|---|---|---|
| 400 | INVALID_YAML |
YAML 解析失败 |
| 422 | INVALID_SCREENPLAY |
YAML 解析成功但不通过 schema 校验,detail.errors 列前 5 条 |
| 422 | (Pydantic) | format 不在枚举内 |
- CORS:当前仅放行
http://localhost:{5173,5174,5175}(Vite 三档端口);生产部署请按需收紧或开放。 - 超时:FastAPI 默认 worker timeout 由部署侧决定;规则路径 3 章短稿处理在毫秒级;AI 主导模式下
/adapt是 5 stage 串行调用 LLM(每 stage 1–N 次),3 章短稿 + 默认 mode 通常 5–15 s,长稿或mode != {medium, balanced}触发 refine stage 后会更慢。建议反代 timeout ≥ 60 s。 - 缓存:LLM 响应缓存写入
.cache/llm_responses.db(SQLite),按(provider, model, messages, params)哈希命中;同输入永不二次计费,跨重启保留。无 TTL 淘汰策略,需要时手动删库。
- 当前 API 版本
v1,schema0.1.0。 - 字段策略:只增不删;新字段在 schema 中通过
additionalProperties: false的局部放宽引入。废弃字段保留 ≥ 1 个 minor 版本周期。 - breaking change 必走
/api/v2。