Skip to main content

服务定位

kira-agent 是 Kira 的独立 AI agent 服务,持有 chat agent run + SSE 流。它从 kira-be 拆出,目标是让 agent 长任务跟 kira-be 的 CRUD 流量解耦 —— deploy kira-be 不再卡住 LLM 长任务
  • 公网入口 https://agentapi.kira.art,浏览器直连(不经 kira-be 透传)
  • 核心 6 个 agent endpoint 都在这里(/task/task/:id/stream/thread/:id/current-task/messages 等),外加根信息 GET /(返 {service:"kira-agent",version})、GET /health,以及从 kira-be 搬来的 POST /video/submit/:taskId(重试失败视频生成)
  • kira-be 的 src/ai/* 已是 stale/dead code,agent 逻辑全在本服务
kira-be 不再持有 agent。任何指向 POST /agent/streaming 的旧文档/客户端都已失效 —— 新客户端直连 agentapi.kira.art

技术栈

类别技术说明
运行时Bun 1.3+Bun.serve,idleTimeout=0(SSE 长连接)
Web 框架HonoeitherAuth 中间件 + streamSSE
LLMAlibaba Qwen3.7-Plus(DashScope 直连)所有 tier(lite/nova/ultra)同一模型,详见 Models
AI SDKVercel AI SDK(ToolLoopAgent)tool loop,stepCountIs(10)
Broker / CacheAiven Dragonfly(rediss://,TLS)独立实例 kira-agent-dragonfly,run↔stream 解耦
DBshared Supabase(PostgreSQL)messages / threads / thread_version / user_profiles
存储Cloudflare R2(经 kira-cdn)生成图/视频资产,cdn-client.ts
异步任务Trigger.dev Cloud触发视频 task(tasks.trigger(taskId, payload, { tags, priority }),SDK @trigger.dev/sdk/v3),由 kira-video-worker 消费
实时Centrifugo视频结果回推(由 worker 触发)
观测OpenTelemetry → kira-otel-collector → Dash0traces / metrics / logs
分析PostHogchat_started / chat_completion / tool_usage

项目结构

src
bootstrap.ts
index.ts
env.ts
auth
telemetry

核心设计:run ↔ stream 解耦

agent run 跟 SSE handler 通过 Redis broker 解耦。浏览器断开 ≠ agent 断开
  • POST /task 起一个 detached agent run promise,登记到 in-memory registry,然后立刻返回 201 {taskId}。run 持续把 chunk 批量 XADDagent:stream:{taskId}
  • GET /stream/:taskId 是独立 reader,用一条独占 blocking 连接 XREAD BLOCK STREAMS agent:stream:{taskId} <lastId>。浏览器断了 reader 退出,run 不停。重连(可能落到任意 instance)从 Last-Event-ID 续读。
  • run 跑完 onFinish 写 DB(messages + thread_version)、触发视频 Trigger.dev task(tasks.trigger,经 TRIGGER_SECRET_KEY 鉴权),再给 stream/task 设短 TTL 自然蒸发。
Redis key 细节见 Redis Broker

多 instance 协作 —— 不用 PUB/SUB

服务多 instance(fra / nrt / sjc 三区),协调全靠 Redis key,没有 pub/sub:
  • dedup lock:SET NX agent:lock:{threadId} {instanceId}:{taskId} EX 60。同一 thread 不能并发跑两个 task。run 每 20s heartbeat 续 TTL(check-and-set,只有 owner 能续);锁过期 = 原 owner 死了。
  • abort 信号:SET agent:abort:{taskId} 1 EX 600,run loop 每 2s EXISTS 轮询命中就 controller.abort()。简单 poll,无 pub/sub
  • registry:in-memory Map 只为 SIGTERM graceful drain 用(本机要知道等谁跑完)。
POST /task 抢锁失败(409)时,从 lock value 解析出现役 taskId(instanceId:taskId 的第二段)返回给客户端,让它直接去 GET /stream/:taskId 续连。

Task 生命周期

running ──completeTask──► completed ──TTL 120s──► (gone)

   ├──failTask──────────► failed    ──TTL 120s──► (gone)

   └──requestAbort──► run loop 见 abort key → controller.abort()
                      → onFinish(isAborted) → completed(不写 DB)
aborted 不是独立 state,而是 completed 的子情况 —— 由”DB 写不写”区分。详见 Task Status

SSE 帧约定

reader 把 stream entry 翻译成标准 SSE 帧。每帧带 id(= Redis stream entry id),供客户端做 Last-Event-ID 续读。
id: <streamId>
data: [<chunk>, <chunk>, ...]   # 普通 chunk batch(JSON array)

id: <streamId>
event: done                      # run 完成,后续无新帧
data:

id: <streamId>
event: error                     # run 失败或 aborted
data: <error message>
客户端见 event: doneevent: error 就关闭连接。详见 Stream API

Graceful drain(SIGTERM)

不依赖 Bun.servecloseActiveConnections(行为不确定),自己实现:
1

draining = true

全局 flag 置位。/health 返 503,Fly LB 把本机摘下。
2

拒新 run

POST /task 直接返 503 instance draining(在 hono/agent 内 check isDraining())。
3

保留在途

现有 SSE handler + agent run 继续不动(heartbeat 仍续锁)。
4

等 registry 空或 240s

waitForRegistryEmpty(240s) 轮询 in-memory Map。
5

超时 abortAll

仍有在途 → abortAll() 让 SDK 清 LLM 连接,再给 60s cleanup window(onFinish 写 DB / XADD error frame)。
6

收尾退出

server.stop() → flush OTel(shutdownTelemetry)→ redis.disconnect()process.exit(0)
fly.tomlkill_signal=SIGTERM + kill_timeout=300s 兜底。Bun.serve({ idleTimeout: 0 }) 禁掉默认 10s idle 关 socket(否则 LLM 思考期间 SSE 会被误关触发 fly-proxy PU02)。

部署

Fly appkira-agent
主区域sjc(primary_region);运行时多区 fra / nrt / sjc
VMperformance,4 cpu / 8 GB
internal_port8090
进程管理auto_stop_machines=off,auto_start_machines=on,min_machines_running=1
公网入口agentapi.kira.art(经 Cloudflare 路由)
secretsfly secrets set ...,清单见 .env.example,真值在 Notion Key 页 ## kira-agent

观测

OTel HTTP instrumentation(@hono/otel)自动产生 server span + http.server.request.duration。run / tool 维度的 metric、log、span 全部走 kira-otel-collector → Dash0:
  • ai.chat.count{model, kira_model} —— run 启动计数
  • ai.tool.duration{tool, success, error_type} —— 每个 tool 的耗时直方图
  • run-finish 计数(completed / aborted / failed)
  • PostHog 业务事件 chat_started / chat_completion / tool_usage 用同一 task_id 串联
详见 ModelsTools