AI 模块 API 文档
基础信息
- Base URL:
/api/ai - 认证方式: JWT Bearer Token
数据库表
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 | 必填 | 请求时间 |
接口列表
POST/api/ai/chat
1. 文本对话(流式 SSE)
触发时机: 在 首页(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 所有者 |
[
{ "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]
重要问题:见 P07
POST/api/ai/chat-simple
2. 文本对话(非流式)
触发时机: HomePage 加载时(可能用于获取欢迎语),或ChatBar 的 useEffect 初始化
HomePage 加载 → 可能在首次打开 ChatBar 时调用 → POST /api/ai/chat-simple
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| billId | string | 必填 | 账单ID |
| provider | string | 选填 | AI 提供商(不传自动选择) |
| model | string | 选填 | 模型名 |
| messages | array | 必填 | 对话历史 |
/chat 类似,但不输出 SSE
2. 无可用 Key 时返回 { content: "", info: "no_ai_configured" }
POST/api/ai/transcribe
3. 语音转写(Whisper)
触发时机: 在 HomePage ChatBar 按住麦克风按钮说话,松开后自动触发HomePage ChatBar → 按住麦克风按钮 → 说话 → 松开 → POST /api/ai/transcribe
Content-Type: multipart/form-data
请求参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| billId | string | 必填 | 账单ID(FormData) |
| provider | string | 选填 | 提供商(默认 openai,Whisper) |
| audio | File | 必填 | 音频文件,最大 12MB |
zh,格式 json,温度 0)
依赖:见 P15
响应:
{ "text": "转写后的文字内容" }
错误:
- 无 OpenAI Key → 400:
语音转写需要可用的 OpenAI Key - 音频无法识别 → 422:
未识别到有效语音内容
POST/api/ai/voice-chat
4. 语音直发对话
触发时机: 在 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 |
resolveNativeVoiceCandidates)
2. 遍历候选,用每个 Provider 的 TTS/语音模型处理
3. 第一个成功立即返回
问题:见 P16
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
})
注意:见 P17
已知问题汇总
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