AI 模块 API 文档

基础信息


数据库表

api_usage_logs 表(AI 调用记录)

字段 类型 必填 说明
id text (PK) 必填 10位随机ID
user_id text (FK→users) 选填 请求用户
bill_id text (FK→bills) 选填 账单
provider text 选填 AI 提供商
model text 选填 模型名
prompt_tokens integer 选填 输入 token 数
completion_tokens integer 选填 输出 token 数
total_tokens integer 选填 总 token 数
duration_ms integer 选填 请求耗时(毫秒)
created_at datetime 必填 请求时间

接口列表


1. 文本对话(流式 SSE)

POST /api/ai/chat

触发时机: 在 首页(HomePage) 的 ChatBar 输入文字消息后点击发送(或按回车)

HomePage ChatBar → 输入文字 → 点击发送/按回车 → POST /api/ai/chat

请求参数:

参数 类型 必填 说明
billId string 必填 账单ID
provider string 必填 AI 提供商(openai/anthropic/gemini
model string 选填 模型名(不传自动选择)
messages array 必填 对话历史 [{role, content}]
owner_type string 选填 指定 Key 类型
owner_id number 选填 指定 Key 所有者

messages 格式:

[
  { "role": "system", "content": "你是一个助手..." },
  { "role": "user", "content": "这个月花了多少钱?" }
]

后端处理:

  1. 校验用户是账单成员
  2. Key 解析(优先级:owner_type+owner_id > provider+model > provider 自动)
  3. 调用 proxyChatByProvider,流式输出 SSE

响应格式(SSE):

data: [DONE]

或错误时:
data: [ERROR] AI 请求失败,请稍后重试
data: [DONE]
重要问题: Anthropic 的 `proxyChatByProvider` 实现实际用的是 **non-streaming** `/v1/messages` 端点,而非流式。对 Anthropic Provider,`/chat` 实际上**不会返回真正的 SSE 流**。

2. 文本对话(非流式)

POST /api/ai/chat-simple

触发时机: HomePage 加载时(可能用于获取欢迎语),或 ChatBaruseEffect 初始化

HomePage 加载 → 可能在首次打开 ChatBar 时调用 → POST /api/ai/chat-simple

请求参数:

参数 类型 必填 说明
billId string 必填 账单ID
provider string 选填 AI 提供商(不传自动选择)
model string 选填 模型名
messages array 必填 对话历史

后端处理:

  1. /chat 类似,但不输出 SSE
  2. 无可用 Key 时返回 { content: "", info: "no_ai_configured" }

3. 语音转写(Whisper)

POST /api/ai/transcribe

触发时机: 在 HomePage ChatBar 按住麦克风按钮说话,松开后自动触发

HomePage ChatBar → 按住麦克风按钮 → 说话 → 松开 → POST /api/ai/transcribe

Content-Type: multipart/form-data

请求参数:

参数 类型 必填 说明
billId string 必填 账单ID(FormData)
provider string 选填 提供商(默认 openai,Whisper)
audio File 必填 音频文件,最大 12MB

后端处理:

  1. 校验用户是账单成员
  2. 获取 OpenAI Whisper Key
  3. POST 到 OpenAI Transcription API(语言 zh,格式 json,温度 0
依赖: 必须配置了 OpenAI Key(Whisper 仅支持 OpenAI)

响应:

{ "text": "转写后的文字内容" }

错误:


4. 语音直发对话

POST /api/ai/voice-chat

触发时机: 在 HomePage ChatBar 点击「语音直发」模式,说完一段话后自动发送

HomePage ChatBar → 切换到语音直发模式 → 按住说话 → 松开发送 → POST /api/ai/voice-chat

Content-Type: multipart/form-data

请求参数:

参数 类型 必填 说明
billId string 必填 账单ID
provider string 选填 偏好提供商(默认 openai)
systemPrompt string 选填 系统提示词
history string 选填 历史消息(JSON 字符串)
audio File 必填 音频文件,最大 12MB

后端处理:

  1. 获取语音候选 Key(resolveNativeVoiceCandidates
  2. 遍历候选,用每个 Provider 的 TTS/语音模型处理
  3. 第一个成功立即返回
问题: 所有 Provider 都失败时返回 502,无友好提示

Key 解析机制(后端内部逻辑)

优先级

owner_type + owner_id 精确匹配
    ↓ 不存在
provider + model 精确匹配(当前用户 Key)
    ↓ 不存在
provider 自动选择(priority 最小的)
    ↓ 都不存在
返回 400 错误

可用 Provider(chatProviders.js)

Provider 模型示例 API Endpoint 流式
openai gpt-4o, gpt-4o-mini api.openai.com/v1/chat/completions 必填
anthropic claude-3-5-sonnet api.anthropic.com/v1/messages 选填(实际不走流)
gemini gemini-1.5-pro generativelanguage.googleapis.com 必填
ollama llama3, qwen localhost:11434/api/chat 必填
azure gpt-4o (Azure URL) 必填
deepseek deepseek-chat api.deepseek.com/v1/chat/completions 必填

API 调用日志记录

成功调用后由 proxyChatByProvider 记录到 api_usage_logs 表:

// 在 chatProviders.js 中,成功响应后:
db.knex('api_usage_logs').insert({
  user_id, bill_id, provider, model,
  prompt_tokens, completion_tokens, total_tokens,
  duration_ms: elapsed
})
注意: 日志记录是在 `proxyChatByProvider` 中进行的,如果 Provider 返回错误,可能不会记录日志

已知问题汇总

  1. Anthropic 流式假象: /chat 对 Anthropic 实际不走 SSE(非流式 /v1/messages
  2. Key 明文存储: api_key_encrypted = Base64 编码,可直接解码
  3. Ollama 无认证: 通常本地运行,无 API Key 验证
  4. 语音直发全部失败: 返回 502,无友好提示
  5. API 日志记录不完整: Provider 请求失败时不记录 api_usage_logs