Skip to content

Latest commit

 

History

History
412 lines (318 loc) · 15.1 KB

File metadata and controls

412 lines (318 loc) · 15.1 KB

API 參考

English · 中文

任何運行中的實例 /docs 路徑下都有實時、可瀏覽的參考文檔(utoipa 註解生成的 Scalar UI)。

這個頁面是手寫的端點摘要。Scalar UI 是權威 spec。

鑒權

每個 /comp/*/bff/v1/* 端點都需要 Authorization: Bearer <Supabase JWT>。JWT 必須是 HS256 簽名、密鑰為 SUPABASE_JWT_SECRETsub claim 必須是個 UUID;該 UUID 即該請求的 user_id。

/healthz/docs 是公開的。

公開端點

GET /healthz

存活探針。無需鑒權。

curl http://localhost:8080/healthz
{
  "status": "ok",
  "service": "eros-engine",
  "version": "0.3.1",
  "timestamp": "2026-05-05T19:06:05.309302232+00:00"
}

人格

GET /comp/personas

列出所有處於 active 狀態的人格基因。需鑒權。

curl -H "Authorization: Bearer $JWT" \
  http://localhost:8080/comp/personas
{
  "personas": [
    {
      "id": "11d6a45a-1fd9-4fe6-a943-3f049035eb68",
      "name": "Aria",
      "system_prompt": "",
      "tip_personality": "warm-but-reserved",
      "avatar_url": "https://avatars.etherfun.xyz/aria.png",
      "art_metadata": { "age": 27, "mbti": "INFJ", "model": "x-ai/grok-4-fast", },
      "is_active": true
    }
  ]
}

對話生命周期

POST /comp/chat/start

對指定人格基因開新 chat session。如果 (genome_id, jwt_user_id) 對應的 persona_instance 還不存在,服務器先建一個,然後建一個引用該 instance 的 chat_session

curl -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"genome_id":"11d6a45a-1fd9-4fe6-a943-3f049035eb68"}' \
  http://localhost:8080/comp/chat/start
{
  "session_id": "5f7e…",
  "persona_name": "Aria",
  "is_new": true
}

is_new=false 表示同一用戶用同一個 genome_id 再調 /start——引擎恢復已有 session 而不是建重複的。

POST /comp/chat/{session_id}/message/stream

流式聊天,返回 text/event-stream,使用 meta → delta* → done → final 状态机(详见 SSE streaming chat 0.2 设计文档)。

请求体必须包含 client_msg_id(26..36 个 ASCII 可打印字符,任意 UUID 或 ULID)。24 小时内同一对 (session_id, client_msg_id) 的重复请求将从 数据库重放历史帧,不会再次调用 OpenRouter。

curl -N -X POST \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"content":"hi","client_msg_id":"01J3333333333333333333333A"}' \
  http://localhost:8080/comp/chat/<session_id>/message/stream

示例帧(每行 data: 后跟一个 JSON 对象):

data: {"type":"meta","message_id":"01J...","action_type":"reply","model":"x-ai/grok-4-fast"}

data: {"type":"delta","message_id":"01J...","content":"你好"}

data: {"type":"done","message_id":"01J...","truncated":false,"usage":{"prompt_tokens":12,"completion_tokens":4,"total_tokens":16},"generation_id":"gen-abc"}

data: {"type":"final","lead_score":0.42,"should_show_cta":false,"agent_training_level":0.18}

每个用户最多 3 条并发活跃流。保活心跳(: ping)每 15 秒发一次, 防止反向代理因空闲超时断开连接。

流前错误(第一个 SSE 字节写出之前的 HTTP 4xx/5xx)携带含 codemessageuser_message 字段的 JSON 响应体;409 duplicate_in_progress 时还会带 original_user_message_id。完整错误码表见 设计文档

一旦第一个 SSE 字节写出,终端错误以带内 error 帧的形式到达并关闭流; 此时 HTTP 响应已提交 200 OK

可选:tier 选择。 请求体可附加 tier 字符串 —— 类型 String,正则 ^[a-z0-9_]{1,32}$(格式错返回 400)。 从 model_config.toml 中选择对应 tier 的模型和 allow_traits[tasks.chat_companion.tiers.<tier>])。tier 未知或缺省时 回退到任务默认块(会记录一条 warn 日志)。示例:

curl -N -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
        "content": "hi",
        "client_msg_id": "01J3333333333333333333333A",
        "tier": "gold"
      }' \
  http://localhost:8080/comp/chat/<session_id>/message/stream

可选:单轮 prompt traits。 请求体可附加 prompt_traits 数组 —— 详见 prompt-traits.zh.md。示例:

curl -N -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
        "content": "hi",
        "client_msg_id": "01J3333333333333333333333A",
        "prompt_traits": [
          {"tag": "nsfw_boost", "text": "<your injection text>"}
        ]
      }' \
  http://localhost:8080/comp/chat/<session_id>/message/stream

限制:最多 8 条,tag 满足 [a-z0-9_]{1,32}text ≤ 2000 字符 (trim 后非空)。违反作为 pre-stream 错误返回 400 BadRequest

可选:记忆注入范围。 请求体可附加 memory_scope 字符串,控制哪些 记忆层会被注入到 prompt 中。接受值:

注入内容
full 完整用户画像(含亲密字段)+ 关系记忆
neutral_and_relationship 中性画像(仅城市/职业/MBTI)+ 关系记忆 (默认)
relationship_only 仅关系记忆;不含画像
neutral_only 仅中性画像;不含关系记忆
insights_only 仅完整用户画像(含亲密字段);不含关系记忆
none 不注入任何记忆

重要(#40 缓解措施): 默认的 neutral_and_relationship 有意比 #40 之前的行为更窄(旧行为注入全部内容)。省略 memory_scope 并不 等同于旧行为——会应用收窄后的默认值。如需完整注入,请显式指定 full

可选:好感度注入范围。 请求体可附加 affinity_scope 值,控制六个 好感度轴中哪些会被注入到 prompt 中。接受值:

  • 具名预设:"bond" (默认) — warmth + intimacy + tension; "chemistry" — trust + intrigue + patience;"bond_and_chemistry" / "full" — 全部六轴;"none" — 不注入好感度。
  • 轴数组:["warmth", "trust", "intrigue", "intimacy", "patience", "tension"] 的任意子集。

重要(#40 缓解措施): 默认的 bond(3 轴)有意比 #40 之前的行为 更窄(旧行为注入全部六轴)。省略 affinity_scope 并不等同于旧行为。 如需全轴注入,请显式指定 "bond_and_chemistry""full"

同时使用两个字段的示例:

curl -N -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
        "content": "hi",
        "client_msg_id": "01J3333333333333333333333A",
        "memory_scope": "full",
        "affinity_scope": "bond_and_chemistry"
      }' \
  http://localhost:8080/comp/chat/<session_id>/message/stream

可选:OpenRouter audit 透传。 请求体可附加 audit 对象, 原样作为 wire 级别的 user / session_id / metadata 发送给 OpenRouter —— 详见 llm-audit.zh.md。示例:

curl -N -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
        "content": "hi",
        "client_msg_id": "01J3333333333333333333333A",
        "audit": {
          "user": "u_<hash>",
          "session_id": "conv_xyz",
          "metadata": { "feature": "chat", "plan": "pro" }
        }
      }' \
  http://localhost:8080/comp/chat/<session_id>/message/stream

限制:audit.useraudit.session_id ≤ 256 字符;audit.metadata ≤ 16 个 key,key 满足 [A-Za-z0-9_.-]{1,64},value 必须是 string 且 ≤ 512 字符。违反作为 pre-stream 错误返回 400 BadRequest

GET /comp/chat/{session_id}/history?limit=50&offset=0

分頁讀消息歷史,最新在前。

{
  "messages": [
    { "id": "", "role": "assistant", "content": "Bishop。", "sent_at": "" },
    { "id": "", "role": "user",      "content": "嗨…",     "sent_at": "" }
  ]
}

roleuser | assistant | gift_user | system_error

用戶畫像

GET /comp/chat/{user_id}/sessions

user_id 名下的所有 chat sessions。路徑裡的 user_id 必須 等於 JWT 裡的 user_id;否則 403。

GET /comp/user/{user_id}/profile

當前的 companion_insights JSONB 加上加權的 training_level。同樣的 user_id 等值檢查。

{
  "insights": {
    "city": "Hong Kong",
    "occupation": "graphic designer",
    "interests": ["jazz", "long walks"],
    "mbti_guess": "INFP"
  },
  "training_level": 0.42
}

training_level 是九個字段加權後的分數(city 0.05、occupation 0.05、interests 0.10、mbti_guess 0.15、love_values 0.15、emotional_needs 0.15、life_rhythm 0.10、personality_traits 0.15、matching_preferences 0.10)。權重總和為 1.0。

禮物事件

POST /comp/chat/{session_id}/event/gift

把外部事件帶來的好感度 deltas 應用上去(虛擬禮物、表情反應、任何你想建模成「用戶剛做了件好事」的事)。路由會寫一條 chat_messagesrole='gift_user')並通過好感度持久化路徑應用 deltas。

curl -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{
        "deltas": {"warmth": 0.05, "intimacy": 0.03, "tension": -0.02},
        "label": "玫瑰",
        "metadata": {"source": "frontend-shop", "amount": 100}
      }' \
  http://localhost:8080/comp/chat/<session_id>/event/gift

v0.1 的禮物路由 不會 觸發 LLM 反應(replynull)。人格在用戶下一輪消息裡承認這份禮物,那時新的好感度狀態塑造回覆。同步反應變體是後續增強。

GET /comp/chat/{session_id}/gifts

列出該 session 的所有禮物事件,分頁。

Debug

GET /comp/affinity/{session_id}

實時 6 維向量 + ghost 統計 + 關係標籤。受 EXPOSE_AFFINITY_DEBUG=true 環境變量控制;關閉時返 404。

{
  "warmth": 0.42,
  "trust": 0.28,
  "intrigue": 0.61,
  "intimacy": 0.15,
  "patience": 0.55,
  "tension": 0.18,
  "ghost_streak": 0,
  "total_ghosts": 0,
  "relationship_label": "stranger",
  "updated_at": "2026-05-05T19:42:00.000000Z"
}

生產部署通常關著(好感度向量是魔法的一部份——把它暴露出來會破壞錯覺)。如果你的前端想實時畫好感度雷達圖,再把它打開。

BFF(/bff/v1/*

面向第一方前端、把部分 /comp/* 路由重塑成前端形狀的鏡像層。鑒權與 canonical 路由完全相同(同樣的 Supabase JWT、同樣的 per-user ownership 檢查),只有 響應形狀 不同(更精簡的 DTO、打包好的 payload)。 canonical /comp/* 路由永遠不會為了遷就前端而被改形狀——而是在旁邊 新增一條 BFF 路由。目前有三條。

POST /bff/v1/comp/chat/start

冷啟動打包:一個 round-trip 內既解析(或創建)session,又返回它最近的 歷史,把前端原本分開的 start + history 兩個調用合成一個。同一用戶 + 同一輸入,會解析到與 canonical POST /comp/chat/start 完全相同的 session。

請求體 = canonical start 請求體,外加一個 BFF-only 字段:

  • genome_id / instance_id —— 標識人格(同 canonical)。
  • is_demo —— 可選,同 canonical。
  • history_limit —— 可選,打包歷史的頁大小;默認 50,上限 50。
{
  "session_id": "5f7e…",
  "instance_id": "",
  "persona_name": "Aria",
  "is_new": false,
  "history": [
    { "id": "3cc06c53-…", "client_msg_id": "c_abc", "role": "user",      "content": "hello",   "sent_at": "" },
    { "id": "9f2e7a10-…", "client_msg_id": null,    "role": "assistant", "content": "hi back", "sent_at": "" }
  ]
}

這裡 不會 打包 affinity——前端單獨讀取(見下面的 affinity event 路由),這樣 bootstrap 就與 EXPOSE_AFFINITY_DEBUG 解耦。

GET /bff/v1/comp/chat/{session_id}/history?limit=50&offset=0

給聊天屏用的精簡歷史投影:id / client_msg_id / role / content / sent_at(不含 extracted_facts)。idchat_messages 行的主鍵(UUID); client_msg_id 是前端串流時帶上的 id(沒帶的行為 null,例如 assistant 回合)。 鑒權、ownership 檢查、limit ∈ [1, 50] 夾取 都與 canonical history 路由相同。刻意差異: 默認 limit 是 50 (canonical 默認 20),因為 BFF 是為「冷啟動一次拉一整屏 backscroll」設計的。

{
  "session_id": "",
  "messages": [
    { "id": "3cc06c53-…", "client_msg_id": "c_abc", "role": "user",      "content": "alpha", "sent_at": "" },
    { "id": "9f2e7a10-…", "client_msg_id": null,    "role": "assistant", "content": "beta",  "sent_at": "" }
  ],
  "total": 2
}

total本次 響應裡 messages 的條數(== messages.len()), 不是該 session 的總行數。

GET /bff/v1/comp/affinity/{session_id}/event

最近一次用戶輪次的好感度 delta(post-EMA),供前端做逐輪觀測。與 canonical 的 /comp/affinity/{session_id} debug 路由不同,它 不受 EXPOSE_AFFINITY_DEBUG 控制(這塊歸前端所有)——但仍做 JWT + ownership 檢查。

{
  "session_id": "",
  "event": {
    "event_id": "",
    "event_type": "message",
    "effective_deltas": {
      "warmth": 0.03, "trust": 0.01, "intrigue": 0.0,
      "intimacy": 0.0, "patience": 0.0, "tension": -0.01
    },
    "created_at": ""
  }
}

eventnull 的情況:還沒有任何用戶輪次事件(全新 session,或只有 time-decay),或最近一次事件早於 affinity migration 0014event_typemessage | gift | proactive | ghost;ghost 輪次的 effective_deltas 全為零。

錯誤響應

所有錯誤都是 JSON 形狀 {"error": "<code>", "message": "<人類可讀>"}

狀態碼 code 何時
400 bad_request 請求體格式錯、UUID 無效、缺必填字段
401 unauthorized JWT 缺失 / 格式錯 / 過期 / 密鑰不符
403 forbidden 路徑 user 跟 JWT user 不匹配,或想讀別人的 session
404 not_found session / 人格 / 消息 id 不存在
500 internal 其餘一切(DB 錯、LLM API 錯等)

源碼

  • crates/eros-engine-server/src/routes/companion.rs——handler 實現
  • crates/eros-engine-server/src/routes/bff/companion.rs——BFF /bff/v1/comp/chat/*
  • crates/eros-engine-server/src/routes/bff/affinity.rs——BFF /bff/v1/comp/affinity/*
  • crates/eros-engine-server/src/routes/debug.rs——好感度 debug 路由
  • crates/eros-engine-server/src/routes/health.rs——/healthz
  • crates/eros-engine-server/src/openapi.rs——Scalar UI spec 元數據