破冰 API
前缀: /api/icebreaker
新用户引导("破冰")相关端点。破冰是一段引导对话,而非小游戏:它拥有独立的生命周期状态,与游戏 route 生命周期相互隔离。破冰可以追加上下文、播报固定的引导台词,但它绝不会让 /api/game/route/active 报告存在一个已打开的小游戏窗口。
INFO
所有写操作端点(/route/start、/route/end、/context、/choice、/speak)都是本地写操作端点,受与后端其余部分相同的 CSRF / 本地请求校验保护。校验失败返回 { "ok": false, "reason": "csrf_validation_failed" }。
INFO
lanlan_name 标识当前角色,在写操作 POST 端点上为必填。/route/start、/route/end、/speak 会先从 Body 解析,解析不到时回退到当前选中的角色(当前猫娘);若仍无法解析出角色,则返回 { "ok": false, "reason": "missing_lanlan_name" }。/context 更严格:它要求 Body 中必须带非空 lanlan_name(不回退),缺失或为空时返回 { "ok": false, "reason": "missing_lanlan_name" }。
Route 生命周期
POST /api/icebreaker/route/start
为某个角色激活破冰 route,并将其绑定到一个 session。
Body:
{
"lanlan_name": "character_name",
"session_id": "session-uuid"
}session_id 为必填;缺失时返回 { "ok": false, "reason": "missing_session_id" }。
Response:
{
"ok": true,
"state": {
"icebreaker_active": true,
"lanlan_name": "character_name",
"session_id": "session-uuid",
"started_at": 0.0,
"last_activity": 0.0,
"source": "new_user_icebreaker"
}
}POST /api/icebreaker/route/end
结束当前激活的破冰 route。
Body:
{
"lanlan_name": "character_name",
"session_id": "session-uuid",
"reason": "icebreaker_end"
}reason 可选,默认为 icebreaker_end。
Response: { "ok": true, "state": <route 状态> }。
INFO
若传入了 session_id 但与当前激活 route 的 session 不一致(例如第二个标签页开启了更新的 session),则拒绝该请求且不结束 route:
{
"ok": false,
"reason": "session_id_mismatch",
"handled": false,
"lanlan_name": "character_name",
"method": "route_end",
"state": "<route 状态>"
}GET /api/icebreaker/route/state
读取某个角色当前的破冰 route 状态。
Query: lanlan_name —— 角色名(可选;回退到当前选中的角色)。
Response: 公开的 route 状态。无激活 route 时,state 为 { "icebreaker_active": false }。
{ "ok": true, "state": { "icebreaker_active": false } }引导交互
POST /api/icebreaker/context
向项目 session 历史追加一行引导上下文(user 或 assistant)。该文本同时会被缓存进记忆系统。需要存在一个绑定到相同 session 的激活 route。
Body:
{
"lanlan_name": "character_name",
"session_id": "session-uuid",
"role": "assistant",
"text": "欢迎!我们来帮你完成设置。"
}role必须为assistant或user,否则返回{ "ok": false, "reason": "invalid_role" }。text为必填(missing_text),且上限为 2000 个字符(invalid_text_length)。- 可选的
request_id(也接受event.request_id)用于去重。
Response:
{
"ok": true,
"method": "project_session_history",
"lanlan_name": "character_name",
"source": "new_user_icebreaker",
"session_id": "session-uuid",
"memory_cached": true
}重复追加会返回相同结构并带 "deduped": true。
若无激活 route,则返回:
{
"ok": false,
"reason": "route_not_active",
"lanlan_name": "character_name",
"source": "new_user_icebreaker",
"method": "project_session_history"
}若存在激活 route 但传入的 session_id 与之不匹配(陈旧或被顶替的 session),则跳过本次追加:
{
"ok": true,
"skipped": "stale_session",
"reason": "session_id_mismatch",
"handled": false,
"lanlan_name": "character_name",
"method": "project_session_history",
"state": "<route 状态>"
}POST /api/icebreaker/choice
将一次有效的教程选择持久化到持久化选项池中,使其跨 session 留存。需要存在一个绑定到相同 session 的激活 route。
INFO
该端点目前为只写:记录的选择不会进入记忆系统,也不会影响模型。它与 /context(供给临时 session 历史)刻意分开,使该选项池保持为一个独立信号,可在后续逐步消费。
Body:
{
"lanlan_name": "character_name",
"session_id": "session-uuid",
"day": 1,
"node_id": "intro",
"choice": "option_a",
"label": "多说一点",
"handoff": false,
"completed": false,
"seq": 0
}Response: 来自选项池的持久化结果,其中 source 被设为 new_user_icebreaker。若无激活 route,则返回 { "ok": false, "reason": "route_not_active", ... }。
POST /api/icebreaker/speak
通过项目 TTS 管线播报一行固定的引导台词(并镜像到聊天界面)。需要存在一个绑定到相同 session 的激活 route。
Body:
{
"lanlan_name": "character_name",
"session_id": "session-uuid",
"line": "很高兴见到你!",
"mirror_text": true,
"emit_turn_end": true,
"interrupt_audio": false
}line为必填(missing_line);类 SSML 标签会被剥除,台词截断至 240 个字符。mirror_text与emit_turn_end默认为true;interrupt_audio默认为false。
Response: TTS 结果结构,包含 method: "project_tts" 以及 voice_source 字段:
{
"ok": true,
"lanlan_name": "character_name",
"method": "project_tts",
"voice_source": { "provider": "project_tts", "method": "project_tts" }
}若无激活 route,则返回:
{
"ok": false,
"reason": "route_not_active",
"lanlan_name": "character_name",
"source": "new_user_icebreaker",
"method": "project_tts",
"audio_sent": false
}若存在激活 route 但传入的 session_id 与之不匹配(陈旧或被顶替的 session),则不会播报台词:
{
"ok": true,
"skipped": "stale_session",
"reason": "session_id_mismatch",
"handled": false,
"lanlan_name": "character_name",
"method": "project_tts",
"state": "<route 状态>",
"audio_sent": false,
"audio_committed": false,
"voice_source": {
"provider": "project_tts",
"method": "project_tts",
"skipped": "stale_session"
}
}