跳转到正文

后端 API 文档 ​

Afterglow 后端是一个 FastAPI 服务。主要集成入口是 OpenAI 兼容的 /v1/chat/completions;其它接口用于主动发起话题、记忆调试、本地初始化、 表情包、图片、文档提取和诊断。

客户端调用 Afterglow 的入口协议与主模型上游协议相互独立:客户端可以调用 /v1/chat/completions 或 /v1/responses,后端再按 CHAT_API_PROTOCOL 使用上游 /chat/completions 或 /responses 生成最终文本。

默认地址:

text
http://127.0.0.1:8000

默认强制 API key 鉴权。除 /healthz 存活检查外,其它接口都需要先在后端 .env 设置 XUWEN_API_KEY,并在请求里带以下任一鉴权头:

http
Authorization: Bearer <XUWEN_API_KEY>
x-api-key: <XUWEN_API_KEY>

如果 API_AUTH_REQUIRED=true 但没有配置 XUWEN_API_KEY,受保护接口会返回 503 xuwen.auth_config。API_AUTH_REQUIRED=false 只建议纯本地开发/测试时临时使用。

所有请求都会带响应头 x-request-id。聊天、记忆检索和主动话题接口还会在响应体里返回 trace_id,用于在 /debug/stats 里追踪完整模型调用链路。

HTTP 文案和分析生成输出使用 Accept-Language: en 或 zh-CN,支持简繁中文标签及 q 权重,无支持项回退后端 XUWEN_LANGUAGE(默认英文)。主动消息始终使用后端配置,普通聊天尊重对话及明确语言要求。错误码、枚举、字段名、原文证据和已持久化内容不翻译;缺翻译有固定可读兜底。配置向导使用独立会话 token。

核心接口 ​

POST /v1/chat/completions ​

OpenAI 兼容聊天接口,也是第三方程序最应该接入的主接口。

相比普通 OpenAI Chat Completions,它额外做了这些事:

  • 从 LanceDB 检索相关历史记忆。
  • 注入 persona、关系记忆、真实当前时间、AI 生活状态、可选联网检索摘要和可选 URL 网页读取结果。
  • 在启用视觉配置时支持图片输入。
  • 传入 conversation_id 时,会把完整一轮对话写回 live memory。
  • 可选传入 caller_id / client_message_id,用于还原 IM 里用户短时间连续发多条消息的打断与合并语义。

关于 model 字段: Afterglow 的模型选择是后端运维决策(你在 .env 里通过 CHAT_MODEL 配置), 不应由客户端控制。请求体里的 model 字段作为 OpenAI 协议占位接受但完全忽略—— 不管你传什么字符串,后端都会用 .env 配的模型。响应体里的 model 字段会返回实际使用的模型名。

关于 policy 字段: 响应体顶层附带非 OpenAI 字段 policy(含 should_reply / reply_mode / user_state / risk_level / reason / reply_delay_seconds / reply_delay_reason),让调用方识别本轮决策。 reply_delay_seconds 是建议客户端延迟展示回复内容的秒数;后端不再为了拟人化延迟阻塞请求。 OpenAI 官方 SDK 会忽略它,不影响兼容性。流式 chat 首个 chunk 也会带 policy,方便客户端在内容到达前先拿到延迟。 当 AI 主动选择不回复时(用户说"别说话"、决策层认为不应继续刺激用户等场景), 响应会带 finish_reason="silenced" + content="[silent]"(sentinel 可通过 SILENCE_RESPONSE_SENTINEL 配置)+ policy.should_reply=false。

关于 schedule_tasks 字段: 响应体顶层另一非 OpenAI 字段,类型为 list[ScheduleTask] | null。 仅在 SCHEDULE_EXTRACT_ENABLED=true 且 AI 解析到用户的定时任务请求时返回;其它情况是 null。 第三方程序(IM bot / 自动化脚本 / 桌面通知)可凭此字段把"明天早上7点叫我起床"这类自然语言意图直接转为定时任务。 每条 ScheduleTask 字段:

  • id (string):短随机 ID,便于第三方做幂等去重,例如 "t_a1b2c3"
  • trigger_at (string):ISO 8601 含时区的【绝对】首次触发时间,例如 "2026-06-01T07:00:00+08:00"
  • recurrence (string | null):iCalendar RRULE 子集,例如 "FREQ=DAILY;BYHOUR=7;BYMINUTE=0";null 表示一次性
  • message (string):届时要发送给用户的内容
  • title (string):简短标题(可选)
  • source (string):"extractor"(默认,时间线小模型解析)或 "main"(主聊天模型直出,目前未启用)

解析流程:主聊天模型在回复里输出 <schedule-hint>明天早上7点叫我起床</schedule-hint>(自然语言意图,对用户不可见), 后端调用时间线小模型把每条 hint 转为结构化 ScheduleTask。失败/超时/未启用时直接返回 null,不影响主回复。 小模型配置见 .env.example 的 SCHEDULE_* 段;留空时依次复用 LABEL_* / RESPONSE_POLICY_* / LIFE_* / 主 LLM。

严格 enum 校验的 OpenAI SDK 可在 .env 里把 SILENCE_FINISH_REASON=stop 退回标准协议。

关于用户连发消息(caller_id / client_message_id): 这是 Afterglow 的非 OpenAI 扩展字段,默认不启用;旧客户端不传时保持普通 Chat Completions 行为。

  • caller_id:调用方稳定 ID,例如一个前端会话、一个 IM bot 会话、一个外部程序实例。只有同一个 caller_id 的请求会互相取消;不同 caller_id 可以并行。
  • client_message_id:调用方给“这一条用户消息”生成的 ID,用于幂等排队。未传时后端会生成临时 ID, 但调用方重试去重能力会变弱。

推荐接入方式:用户每发一条气泡就立即请求后端,不做 1 秒级 debounce。若同一 caller_id 的上一轮 还没完成,新请求会把上一轮标记为取消,并把之前尚未成功回复的用户消息与当前消息合并成这一轮的 current_user_text,中间用双换行分隔。成功回复后,这些 client_message_id 会被确认并从队列移除。

使用该模式时,请求体里的 messages 建议只放“已经完成回复的历史 + 当前这一条新用户消息”。 不要把尚未被回复的旧用户气泡也重复放进 messages,否则它们会同时出现在历史和后端未完成队列里。

被取消的旧请求不会写入 live memory / relationship memory,也不会确认未完成队列。流式旧请求会尽快以 finish_reason="cancelled" 收尾;非流式旧请求如果已经进入上游模型调用,后端不能强行中断远端推理, 但返回前会检查自己是否仍是最新 generation,若已被取代则返回空内容 + finish_reason="cancelled"。

关于多条消息分条(QQ / 微信式"连续发好几条"): Afterglow 通过 persona 模板约束主模型把 "本轮想分多条发出去的内容"用双换行 \n\n 作为分条分隔符写在同一个 assistant message 里, 而不是返回多个 choices 或多次 API 调用 —— 这样才能保持 OpenAI 协议 1:1 兼容。 单换行 \n 仍然是同一条消息内的换行(用于诗、列表、代码等),不构成分条。

调用方需要自行处理这件事:

  • 想还原 QQ/微信"分多条气泡"效果:按 \n\n split content,每段当独立消息渲染, 段与段之间加 1.5-3 秒随机延迟(模拟人打字的节奏,不要瞬间全部 push)。建议每段渲染前 先校验当前会话还没被切走,避免延迟期间用户切走会话后老内容污染新会话。 frontend/src/stores/chat.ts 的 finishAssistantMessage 是一个可参考的实现。
  • 不需要分条效果(CLI / 单气泡 UI / 第三方机器人转发):直接把整段 content 渲染出去即可, \n\n 在 markdown 渲染器里本就是段落分隔,体验不会比单段差。
  • 流式 SSE:\n\n 可能跨多个 delta chunk 出现(主模型逐 token 输出),客户端应该 先把所有 delta 拼起来、流结束后再 split,不要在每个 delta 内做 split(会把"段中间"误判成分条)。
  • policy.reply_delay_seconds 是整条回复展示前的延迟(拟人化"看了一会儿才回"), 与上面"段间延迟"是两层不同的延迟,互不冲突;客户端先等 reply_delay_seconds 再开始逐段播放。

关于 life 状态自更新(LIFE_MARKER_UPDATE_ENABLED=true,默认开): 主模型在回复末尾可输出隐藏标记块 <life-event>准备去吃饭,暂时忙一会儿</life-event>, 后端会先从用户可见回复中剥离,再异步交给 Life 模型归一化并写入时间线。旧 <life-update> 仅作输入兼容,不再直接修改状态。 流式过程中标记块也不会被切到中间发出去(output_filter 会缓冲到块结束再统一过滤)。 关闭此开关时只剥离不应用,避免前端看到内部协议。

请求示例:

json
{
  "model": "gpt-4o-mini",
  "messages": [
    {"role": "user", "content": "在吗"}
  ],
  "stream": false,
  "temperature": 0.7,
  "max_tokens": 300,
  "conversation_id": "my-app-user-1",
  "caller_id": "my-app-user-1",
  "client_message_id": "u-20260623-0001"
}

响应示例:

json
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1779400000,
  "model": "gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "在呢,怎么啦"},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
  "trace_id": "...",
  "policy": {
    "should_reply": true,
    "reply_mode": "calm",
    "user_state": "normal",
    "risk_level": "low",
    "reason": "按真人历史风格自然短回。",
    "reply_delay_seconds": 0,
    "reply_delay_reason": ""
  }
}

流式请求使用 OpenAI 风格 SSE:

bash
curl -H "Accept-Language: zh-CN" -N http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $XUWEN_API_KEY" \
  -d '{
    "messages": [{"role": "user", "content": "今天有点累"}],
    "stream": true,
    "conversation_id": "demo"
  }'

图片输入遵循 OpenAI 多模态格式:

json
{
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "看看这张图"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
      ]
    }
  ]
}

图片相关配置:

  • VISION_ENABLED=true
  • 如果主聊天模型原生支持视觉:CHAT_MODEL_SUPPORTS_VISION=true
  • 否则配置 VISION_API_URL、VISION_API_KEY、VISION_MODEL

联网与 URL 读取:

  • WEB_ACCESS_ENABLED=true 后,用户明确要求“搜索/新闻/最新/天气/价格”等公开实时信息时,后端会调用 Tavily 或 SearXNG,并把摘要注入 prompt。
  • 用户消息里包含 http://、https:// 链接,且 WEB_ACCESS_ENABLED=true、WEB_FETCH_ENABLED=true 时,后端会直接尝试读取网页标题和正文摘录,并把结果注入 prompt。
  • 用户只写裸域名(如 example.com)时,不会无条件访问。后端会先用本地规则判断是否有“打开/看看/这个网站是什么”等访问意图;命中后再调用小模型确认要访问的候选 URL,确认后按 https://example.com 访问。这个小模型复用 LIFE_API_URL / LIFE_API_KEY / LIFE_MODEL 配置。
  • URL 读取只支持普通文本网页,不执行 JavaScript;后端会拒绝本机、内网、链路本地等地址,并限制跳转、响应大小和正文字符数。
  • 相关诊断在 /debug/stats 的 calls.web.search、calls.web.search.skipped、calls.web.intent、calls.web.fetch、calls.web.fetch.skipped。

POST /v1/responses ​

OpenAI Responses API 兼容端点(中等子集)。和 /v1/chat/completions 共用同一套 检索 / 决策 / 生活状态 / 关系记忆 / 沉默策略,只是协议不同。

支持字段:

  • model(占位,接受但忽略——实际用 .env 的 CHAT_MODEL)
  • input:字符串或消息数组;多模态 input_image 走与 chat 路由相同的视觉链路
  • instructions:内部转为一条 system message 注入到 prompt 顶部
  • stream:流式开关,事件按官方 Responses 协议
  • temperature / top_p / max_output_tokens
  • previous_response_id:进程内 LRU 缓存,找到后会沿用上一轮的 conversation_id; 显式传 conversation_id 时以显式值为准
  • conversation_id:Afterglow 扩展,用于关联回写
  • store:语义忽略(后端始终缓存到 LRU 容量上限,重启即丢失)

不支持: tools / function-calling / file inputs / image_generation / code_interpreter / MCP / background 模式。

请求示例:

bash
curl -H "Accept-Language: zh-CN" http://127.0.0.1:8000/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $XUWEN_API_KEY" \
  -d '{
    "instructions": "请用对方的语气陪我聊聊",
    "input": "今天有点累",
    "conversation_id": "demo-1"
  }'

非流式响应字段:id(resp_xxx)、object="response"、created_at、model、status="completed"、output[](含 message item,message 内 content[] 是 output_text)、output_text(便利字段:所有 output_text 拼接)、usage、trace_id、policy(同 chat 接口)、previous_response_id。

流式事件序列(按官方 Responses 协议):

event: response.created → event: response.in_progress
→ event: response.output_item.added → event: response.content_part.added
→ event: response.output_text.delta(多次) → event: response.output_text.done
→ event: response.content_part.done → event: response.output_item.done
→ event: response.completed → data: [DONE]

沉默响应: 走完整事件序列,output_text.delta 只发一次(值为 SILENCE_RESPONSE_SENTINEL, 默认 "[silent]"),status 为 completed;调用方应靠顶层 policy.should_reply == false 或 output_text == sentinel 识别。

多条消息分条: 与 /v1/chat/completions 同样的语义 —— 主模型用 \n\n 在同一段 output_text 内表示"想分多条发出去"。流式时 \n\n 可能跨多个 output_text.delta, 客户端应先拼接完整 output_text 再 split,按段渲染并加 1.5-3s 段间延迟以模拟分条节奏。 详见上面 /v1/chat/completions 的"多条消息分条"小节。

previous_response_id 串接: 第二次请求带上上次返回的 id,后端会沿用上次的 conversation_id 自动接上对话。重启进程后缓存丢失,此时建议改用显式 conversation_id。

POST /v1/companion/proactive(实验性,不推荐使用) ​

让 AI 主动开启一个话题。这个功能目前是实验性能力,主动开场质量、话题选择和防打扰策略仍不稳定, 不推荐产品接入或开启;保留接口主要用于本地实验和后续迭代验证。

请求示例:

json
{
  "conversation_id": "my-app-user-1",
  "caller_id": "my-im-caller-1",
  "reason": "morning",
  "private_context": "用户昨天说今天有考试",
  "topic_hint": "轻轻问候一下"
}

响应示例:

json
{
  "message": "醒了吗,今天是不是要考试来着",
  "life": {
    "date": "2026-05-22",
    "time_slot": "上午",
    "current_activity": "刚醒一会儿",
    "recent_meal": "喝了水",
    "mood": "普通",
    "availability": "available",
    "topic_seed": "问问今天安排",
    "next_update_at": "2026-05-22 11:30",
    "reply_delay_seconds": 0,
    "reply_delay_reason": "",
    "day_plan_summary": "...",
    "recent_timeline_summary": "..."
  },
  "relationship_memory": "...",
  "trace_id": "...",
  "policy": {
    "should_reply": true,
    "reply_mode": "calm",
    "user_state": "normal",
    "risk_level": "low",
    "reason": "按真人历史风格自然短回。",
    "reply_delay_seconds": 0,
    "reply_delay_reason": ""
  },
  "silenced": false
}

caller_id 可选,用来读取同一调用方最近上下文缓存;没有稳定 caller_id 时只传 conversation_id 也可以。private_context 是内部触发背景,不会作为用户消息写入历史。 生成的 AI 消息会在传入 conversation_id 时写入 live memory,并写入最近上下文缓存。

主动开场内容生成会按这个优先级找话题钩子:最近上下文缓存、关系记忆、RAG 片段。最近上下文缓存 保存在 .data/persona/proactive_context_cache.json,默认每个 caller/conversation 保留 40 条短消息, 生成时最多注入 12 条;它只用于短期连续性,不是长期记忆,也不参与风格学习。

POST /v1/companion/proactive/poll(实验性,不推荐使用) ​

轮询式主动聊天入口。当前不推荐作为产品路径使用;如需实验,客户端、外部调度器或 IM bot 可周期性请求本接口。后端会维护每个 conversation_id 或 caller_id 的 pending candidate, 并返回下一次应该请求的时间。

典型流程:

  1. 首次请求,后端返回 state="scheduled" 和 next_poll_at_ms。
  2. 客户端在 next_poll_at_ms 到达后再次请求。
  3. 如果 candidate 创建后用户没有先发消息,且当前画像评分和防打扰门控通过,返回 state="sent",proactive.message 即应发送给用户的主动开场。
  4. 如果期间用户已经通过 /v1/chat/completions 或 /v1/responses 发过消息,后端会自动记录 用户活动并取消本次 pending candidate,返回新的 next_poll_at_ms。只有外部系统绕过 Afterglow 聊天入口时,才需要额外传 last_user_message_at_ms。

请求示例:

json
{
  "conversation_id": "my-app-user-1",
  "caller_id": "my-im-caller-1",
  "reason": "learned_rhythm",
  "auto_send": true
}

响应示例:

json
{
  "state": "scheduled",
  "should_send": false,
  "score": 0.0,
  "threshold": 0.55,
  "reason": "scheduled",
  "skip_reasons": [],
  "features": {},
  "private_context": "",
  "topic_hint": "",
  "profile_summary": "",
  "next_poll_at_ms": 1783180800000,
  "scheduled_for_ms": 1783180800000,
  "candidate_created_at_ms": 1783177200000,
  "cancelled_by_user_activity": false,
  "proactive": null
}

到点且通过门控时,如果 auto_send=true,state 会是 sent 或 silenced,proactive 字段会包含与 /v1/companion/proactive 相同结构的生成结果。

POST /v1/companion/proactive/decide(实验性,不推荐使用) ​

基于历史主动开聊画像做“现在要不要主动找用户”的可解释门控。这个接口适合前端轮询、 外部调度器或 IM bot 调试;默认只返回即时决策,不维护 pending candidate。功能整体仍是实验性, 不建议产品路径使用。

运行时优先使用独立 proactive_analysis.json,再回退导入期 .data/persona/proactive_profile.json;缺少有效画像时, 后端会从 dialogue_windows 兜底重建。功能默认关闭,需设置 PROACTIVE_ENABLED=true。

请求示例:

json
{
  "conversation_id": "my-app-user-1",
  "caller_id": "my-im-caller-1",
  "reason": "learned_rhythm",
  "auto_send": false
}

响应示例:

json
{
  "should_send": true,
  "score": 0.76,
  "threshold": 0.55,
  "reason": "matched_learned_rhythm",
  "skip_reasons": [],
  "features": {
    "time_match": 0.9,
    "idle_gap_match": 0.8,
    "availability": 1.0
  },
  "private_context": "主动聊天候选原因:learned_rhythm...",
  "topic_hint": "按历史主动开聊习惯...",
  "profile_summary": "共识别 12 次 TA 主动开聊样本...",
  "next_check_seconds": 900,
  "proactive": null
}

如果 auto_send=true 且通过门控,proactive 字段会包含与 /v1/companion/proactive 相同结构的生成结果。

GET /v1/companion/proactive/profile(实验性调试) ​

查看主动聊天画像和最近审计记录。审计记录包含候选时间、发送/跳过状态、分数、跳过原因和 特征权重;完整日志写入 .data/persona/proactive_audit.jsonl。

记忆接口 ​

GET /memory/stats ​

查看向量库和回写队列状态。

响应字段:

字段类型说明
friend_messagesint单条对方消息 chunk 总数
dialogue_windowsint多轮对话窗口 chunk 总数
response_pairsint用户输入→对方回复样本对总数
history_imagesint历史图片 VLM 摘要 chunk 总数
live_messagesint运行时回写的消息数(含 user_new + ai_generated)
relationship_memoriesint关系记忆条数(用户近况蒸馏)
writeback_enabledbool.env 里 WRITEBACK_ENABLED 的值
writeback_pausedbool是否被 /memory/writeback/pause 暂停

POST /memory/search ​

调试用检索接口,只跑检索,不调用聊天模型。前端诊断面板可以用它看"到底召回了什么"。

请求体:

字段类型必填默认说明
querystring是—查询文本;空字符串返回空结果
conversation_idstring | null否null用于限定 recent_live 范围
top_kint否12融合结果上限

响应字段:

字段类型说明
fusedMemorySearchHit[]最终融合后的结果,最接近主 prompt 实际用的记忆
response_pairsMemorySearchHit[]用户输入→对方回复样本召回
friend_examplesMemorySearchHit[]单条对方消息召回
dialogue_windowsMemorySearchHit[]多轮上下文片段召回
history_imagesMemorySearchHit[]历史图片摘要召回
recent_liveMemorySearchHit[]当前会话最近 live 记忆
trace_idstring本次请求追踪 ID

每个 MemorySearchHit 字段:

字段类型说明
chunk_idstring唯一 ID,可用于后续 DELETE /memory/{table}/{id}
kindfriend / window / history_image / live / response_pairchunk 种类
textstringchunk 文本
scorefloat排序分数;越大越相关
rankint在对应 list 内的排名(1 起)
timestamp_msint原始消息时间,Unix 毫秒
session_idstring同一段连续对话的标识
sender_namestring发送方名字
sourceenum见下方说明
warmthfloat暖度得分(亲密关系词频)

source 取值与语义:

  • human_original / history:真人原始聊天,允许作为 persona / 风格证据
  • user_new:新会话用户输入,用于事实记忆和"用户最近发生了什么",不参与风格蒸馏
  • ai_generated:AI 分身回复,用于连续性检索;默认只在同一 conversation_id 内参与语义检索。开启 AI_GENERATED_LONG_TERM_ENABLED=true 后才会跨会话长期检索

POST /memory/writeback/pause ​

暂停 live memory 回写。响应: {"status": "paused"}

POST /memory/writeback/resume ​

恢复 live memory 回写。响应: {"status": "running"}

DELETE /memory/{table}/{memory_id} ​

软删除某条记忆(标记 deleted,检索和统计跳过)。

Path 参数:

字段说明
table允许 friend_messages / live_messages / response_pairs;其它返回 400
memory_idMemorySearchHit.chunk_id 拿到的 ID

响应: {"status": "deleted"}

错误: 400(表名非法)/ 404(id 找不到)

元信息接口 ​

GET /healthz ​

基础进程健康检查。唯一不需要鉴权的端点,可用于容器存活探针。

响应字段:

字段类型说明
status"ok"进程能响应 HTTP 即返回 ok
versionstring当前后端版本号

GET /readyz ​

检查必要配置、向量库连接和 persona 卡片是否就绪。用于反代/编排的就绪探针。 ready=false 时仍返回 200(不是 503),方便编排器读 issues 决定下一步。

响应字段:

字段类型说明
readybool全部检查通过才是 true
issuesstring[]不就绪的具体原因(每条人类可读,如 "缺少 persona_card.md")

GET /info 和 GET /v1/info ​

返回前端需要的应用名、模型名、关系类型和 persona 卡片状态。两个路径完全等价。

响应字段:

字段类型说明
app_namestring前端 title 显示用
app_sloganstring副标题文案
friend_namestring对方名字(被模仿者)
self_namestring用户名字
relationship_typefriend / lover / family / colleague / custom关系类型
relationship_descriptionstring人类可读的关系描述
persona_templatestring内置模板名或自定义模板路径
embedding_modelstring向量模型名
chat_modelstring主聊天模型名(请求里的 model 字段会被忽略,实际就用这个)
versionstring后端版本号
has_persona_cardboolpersona_card.md 是否已生成

文档提取接口 ​

把上传的文档转成纯文本,让前端拼到 user message 里发出去。LLM 完全不感知文件概念。

GET /v1/documents/formats ​

列出当前后端支持的扩展名。

字段类型说明
extensionsstring[]小写扩展名(无前导点)

POST /v1/documents/extract ​

上传文档并提取纯文本。请求格式是 multipart/form-data,不是 JSON。

Form 字段:

字段类型必填说明
filefile是待提取的文件,扩展名必须在 /formats 返回列表里

响应字段:

字段类型说明
filenamestring上传时的原文件名
extensionstring小写扩展名(不带点)
textstring提取出来的文本
char_countinttext 的字符数
estimated_tokensint粗略 token 估算(≈ 字符数 / 4),仅供前端预算用

错误: 400(缺文件名 / 文件为空 / 扩展名不支持 / 解析失败)

bash
curl -H "Accept-Language: zh-CN" -F "file=@notes.pdf" \
  -H "Authorization: Bearer $XUWEN_API_KEY" \
  http://127.0.0.1:8000/v1/documents/extract

表情包与图片接口 ​

表情包用于 AI 真正"发表情"的能力。后端把可用表情列表注入到 prompt,AI 决定要发时输出 [sticker:名字],前端识别并渲染对应图片。字节文件持久化在 STICKER_DATA_DIR,元数据存 LanceDB。

Owner 枚举:

  • ai:只让 AI 发,用户不可发
  • self:仅用户面板可见
  • shared:双方都能用,默认值

GET /v1/stickers ​

列出表情包。

Query 参数:

字段类型必填说明
ownerai / self / shared否留空返回全部,传值则按 owner 过滤

响应: {"items": [StickerResponse, ...]}

每条 StickerResponse 字段:

字段类型说明
namestring唯一标识,AI 用 [sticker:name] 指代
descriptionstring给 AI 看的语义描述(决定何时使用)
ownerOwner见上方枚举
tagsstring[]软分类标签
extensionstring实际文件扩展(png/jpg/jpeg/gif/webp/bmp)
shastring图片文件 sha256
created_at_msint入库时间,Unix 毫秒
image_urlstring相对路径,拼到 API 基址后可拉取图片

POST /v1/stickers ​

新建表情包。

请求体:

字段类型必填默认约束
namestring是—1-32 字符;必须唯一,重复返回 409
descriptionstring是—1-200 字符;给 AI 看的语义
data_urlstring是—data:image/<ext>;base64,...,受 STICKER_MAX_IMAGE_BYTES 限制(默认 2MB)
ownerOwner否shared见枚举
tagsstring[]否[]—

响应: 201 + StickerResponse

错误: 400(data URL 非法 / 超大 / 扩展不支持)/ 409(同名已存在)

PATCH /v1/stickers/{name} ​

更新已有表情包的元数据(不能改图片字节,要改图请删了重建)。

Path: name(要修改的表情名)

请求体(三个字段都可选):

字段类型说明
descriptionstring | null新描述
ownerOwner | null改归属
tagsstring[] | null整体替换标签数组(不是追加)

响应: 200 + 更新后的 StickerResponse

错误: 400 / 404(表情包不存在)

DELETE /v1/stickers/{name} ​

软删除表情包(图片文件保留,仅从可用列表移除)。

响应: {"status": "deleted", "name": "ok"} / 错误: 404

GET /v1/stickers/{name}/image ​

返回表情包的图片字节。响应 content-type 按扩展自动设置,带 Cache-Control: public, max-age=86400。

错误: 404

GET /images/{sha} ​

通过 sha256 读取已持久化的聊天图片(用户发来的或多轮里出现的)。

Path: sha(sha256 十六进制全长)

响应: 图片字节,content-type 按 magic bytes 推断。

错误: 404(sha 对不上文件)

关系分析接口 ​

生成报告语言由请求头选择,读取已保存报告时保持原语言,不自动翻译。缓存只复用相同输出语言,旧缓存重算。

关系分析从保留逐条时间戳的原始聊天文件重新解析,不从 LanceDB 反推。所有端点都使用主 API 鉴权。

POST /analysis/start ​

启动后台分析任务。files 只接受 CONFIG_UI_UPLOADS_DIR 内的文件名或绝对路径;留空时自动使用该目录下全部 .json / .jsonl 文件。CLI 需要分析其他位置的文件时使用 xuwen analyze <paths...>。

json
{
  "files": ["chat-export.json"],
  "timeline": true,
  "personality": true,
  "resume": true,
  "since": "2023-01"
}

响应包含 task_id、status、progress、stage 和最终 report。

网络、鉴权或模型接口等非输出错误会立即取消尚未完成的请求并进入 failed,不会执行 Reduce,也不会发布时间线、性格或实验报告。已经成功写入的块缓存会保留;使用默认 resume=true 重新运行时只补尚未完成的块。

块级模型返回政策拒绝或连续输出无效 JSON 时,系统不会调用最终大模型回退,也不会终止整次分析。该块会以空结果写入缓存并从终稿候选中跳过;块文本、每次解析错误和模型原始输出会写入 .data/analysis/failures/<block_id>.json,任务结果与 manifest.json 会记录跳过数量。CLI 可用 xuwen analyze <文件> --list-blocks 列出块,或用 xuwen analyze <文件> --inspect-block <block_id> 在不调用模型的情况下查看指定块。

任务状态 ​

方法与路径作用
GET /analysis/{task_id}查询任务状态。
GET /analysis/{task_id}/stream订阅 SSE 进度,任务结束后关闭。
POST /analysis/{task_id}/cancel取消仍在运行的任务。

只读结果 ​

方法与路径作用
GET /analysis/timeline读取 .data/analysis/timeline.json。
GET /analysis/personality读取结构化性格报告。
GET /analysis/proactive读取 proactive_analysis.json,包含双方开聊记录、AI 动机与时间归因、证据和置信度,以及对方主动的频率与时间分布。
GET /analysis/experimental独立读取实验结果;ANALYSIS_EXPERIMENTAL_ENABLED=false 时返回 404。

实验结果不会混入前两个响应。块级小模型只提取结构化候选;最终大模型负责重组普通人格报告、关系阶段、实验人格画像和生活画像。普通人格报告会生成去证据的 personality_prompt_context.md,供主聊天模型了解目标角色的人格与关系互动倾向。生活规律先写入结构化 life_profile.json,再渲染为 life_context.md 供生活时间线使用。只有开启 ANALYSIS_PERSONALITY_PROMPT_ENABLED 后,普通人格画像才会进入主聊天 Prompt;只有开启 ANALYSIS_LIFE_CONTEXT_ENABLED 后,生活画像才会进入生活时间线模型的状态决策 Prompt。

生活候选使用独立的块级 Map,不要求单个几天范围的块已经证明“长期规律”;它只提取带时间证据的 sleep/meal/activity/availability 候选,最终大模型再跨块判断稳定性。最终模型通过 source_ids 引用候选,代码按编号回填原始证据,避免模型改写证据文字后被误判为无来源。生活提取有独立缓存版本,因此升级提取规则后可以复用已有关系与实验缓存,只补跑生活候选。

生活时间线会把 life_context.md 作为长期语义先验,与按消息时间戳计算的 circadian_profile.json、当前真实时间、今日计划和当天已经发生的状态变化共同使用。它只能调整候选活动和日程的概率,不能把历史规律直接写成今天此刻的事实。当前对话和当天状态具有更高优先级。

所有观察都带有 subject:friend 表示目标角色,self 表示用户,both 表示双方,relationship 表示关系本身,unknown 表示主体不明。普通人格只接受目标角色观察,关系章节可接受 friend/both/relationship;生活画像只接受 friend。生活规律由模型直接输出 category、time_patterns、contexts、target_fields 和 sensitive_relationship_context,代码不再扫描中文关键词推断类别或敏感性。用户习惯、共同活动、主体不明和敏感关系内容不会进入生活时间线。

开启 ANALYSIS_EXPERIMENTAL_ENABLED 后,每个分析块会额外执行一次只收集实验人格候选的独立 Map,避免和普通时间线/人格输出争用 token;最终大模型再跨块生成 experimental/personality_profile.json 和带证据的 Markdown 报告。只有同时开启 ANALYSIS_EXPERIMENTAL_PROMPT_ENABLED,系统才会生成去证据的 experimental/prompt_context.md。它是包含真实性格、人际态度、依恋、可能隐瞒的情境、操控意图和精神健康假设的人格画像,不是回应策略清单;每项仍需保留把握度、条件、反例和替代解释。

关系分析不在配置向导中提供。使用主 API 的 /analysis/* 任务端点,或通过 xuwen analyze <paths...> 分析任意位置的本地文件。完整操作说明见 高级与实验功能:聊天记录分析。

最终发送给主聊天模型的内容 ​

每轮主聊天请求由一条 system 消息、近期多轮消息和本轮用户消息组成。system 消息包含 persona 卡片与语气统计、当前生活状态、关系状态、按本轮问题检索到的真人回复和对话片段、输出规则,以及按开关启用的普通人格画像和实验人格画像;涉及联网或网页读取时还会加入对应结果。结构化 personality_report.json、life_profile.json、时间线、实验完整报告、证据原文和 session ID 不会整份直接发送给主聊天模型。

其中 personality_prompt_context.md 在普通上下文开关开启时加入主聊天模型,life_context.md 在生活上下文开关开启时加入生活时间线模型,experimental/prompt_context.md 在实验双开关开启时加入主聊天模型;当前对话和检索到的真人原文具有更高事实优先级。分析阶段本身仍会把所选聊天块发送给 Analysis API,这是生成报告所必需的独立调用。

调试接口 ​

仅在 DEBUG_ENDPOINTS_ENABLED=true 时挂载。所有 /debug/* 端点都过鉴权中间件。

GET /debug/stats ​

详细运行诊断。

响应主要字段:

字段类型说明
versionstring后端版本号
memoryobject各表 chunk 数量,结构同 GET /memory/stats
databaseobjectLanceDB 操作耗时分布(by_operation + recent)
lifeobject当前 AI 生活状态快照 + 今日计划 + 最近时间线
writebackobject回写队列统计:enqueued / written / flushed_batches / dropped / failed / paused / pending_turns
calls.<stage>object按 stage 分组的延迟统计:count / error_count / error_rate / avg/p50/p95_latency_ms / 最近 N 条记录。常见 stage:retrieval、llm.complete、llm.stream、life.decide、response.policy、response.policy.refined、responses.complete、responses.stream、web.search、web.fetch、chat.silenced、companion.silenced
model_chainarray最近 80 次模型调用的完整链路:trace_id / stage / attempt / model / url / stream / latency_ms / status / status_code / upstream_request_id / request(messages 摘要) / response(content 预览) / error

GET /debug/config ​

脱敏配置快照。API key 只显示是否已配置,不返回具体值。

响应主要字段:

字段类型说明
app_name / app_slogan / app_timezonestring应用元数据
self_name / friend_name / relationship_type / persona_templatestring身份与关系
chat_model / embedding_model / embedding_dim / embedding_input_mode / embedding_batch_size / embedding_max_concurrency / embedding_max_requests_per_minutemixed模型配置
session_gap_minutes / window_size / window_overlap / final_context_k / rrf_k / recency_half_life_daysint/float切分与检索参数
writeback_enabled / writeback_batch_turns / writeback_vectorizebool/int回写策略
live_top_k / ai_generated_source_weight / ai_generated_long_term_enabledmixedlive 检索与来源权重
response_policyobjectmodel_enabled / model / endpoint_overridden / key_overridden / temperature / max_tokens —— 是否启用小模型复核及是否单独配置 endpoint
analysisobject分析模型、实验生成开关和实验摘要 Prompt 开关。
silence_response_sentinel / silence_finish_reasonstring沉默响应配置
responses_store_capacityint/v1/responses 服务端 LRU 缓存容量
vision_enabled / chat_model_supports_visionbool视觉链路开关
web_access_enabled / web_search_provider / web_search_base_url_configured / web_search_client_active / web_fetch_enabled / web_fetch_client_active / web_fetch_max_urls / web_fetch_max_bytes / web_fetch_max_charsmixed联网与 URL 读取
enable_pii_redactionboolPII 脱敏总开关
api_keys_configuredobjectopenai / embedding / vision / web_search / local_guard 是否各自已配置(值都是 bool,不含具体 key)
pathsobjectlance_db / persona / images 实际落盘路径
envobjectpython 解释器版本 + process_pid

POST /debug/proactive/force-due(实验性调试) ​

本地调试用:把指定 conversation_id 或 caller_id 的主动聊天 pending candidate 的 scheduled_for_ms 改成当前时间,使下一次 /v1/companion/proactive/poll 能立刻走到 到期检查。只在 /debug/* 已启用时可用;生产路径不应调用。

请求:

json
{
  "conversation_id": "proactive-full-123",
  "caller_id": "my-im-caller-1"
}

响应:

json
{
  "forced": true,
  "conversation_id": "proactive-full-123",
  "previous_scheduled_for_ms": 1783180800000,
  "scheduled_for_ms": 1783177200000,
  "candidate_created_at_ms": 1783177000000
}

POST /debug/metrics/reset ​

清空内存里的运行指标,不删除 LanceDB 数据。

响应: {"status": "ok"}

清空内存里的运行指标,不删除 LanceDB 数据。

错误格式 ​

业务错误通常返回:

json
{
  "error": {
    "code": "xuwen.some_code",
    "message": "可读错误信息",
    "request_id": "..."
  }
}

FastAPI 参数校验错误使用标准 422 响应。

AGPL-3.0-or-later