按协议而不是按厂商做适配,与现有短信、对象存储的做法一致:openai 兼容格式 一个适配器即可覆盖 DeepSeek、通义、智谱、火山方舟、Ollama 等,新增厂商通常 只需在后台加一条模型记录;anthropic、gemini、webhook、debug 各一个适配器。 密钥沿用 integration.go 的 AES-GCM 加密存储。 测试账号托管的回复走 persistMessage 同一条落库和推送路径,因此未读数、 WebSocket 推送和会话排序全部复用现有逻辑,客户端无需改动。为此把 persistMessage 抽出 persistMessageContext,因为 worker 没有 *http.Request。 任务队列用数据库表而非内存:重启不丢回复,多实例不重复消费。领取用 UPDATE 打 claim_token 再回读,没有用 SELECT ... FOR UPDATE SKIP LOCKED——生产存在 MySQL 5.7 环境,那里该语法无法解析。同一会话同时只允许一个待处理任务 (pending_key 生成列 + 唯一键),所以用户连发多条消息只会得到一条回复。 闸门:仅 is_test=1 且在允许批次内的账号生效,托管账号之间不互相触发,三层 配额,调用失败默认静默。会话内是否标注 AI 身份由 ai.disclose_in_chat 控制 并默认开启,关闭前需确认所在地区的监管要求。 app.go、integration.go、im.go 三个文件同时包含本次改动之前工作区里就已存在 的未提交修改,一并带入。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
3.3 KiB
SQL
59 lines
3.3 KiB
SQL
SET NAMES utf8mb4;
|
|
|
|
-- Model records rather than a single provider block: several protocols have to
|
|
-- coexist so operations can switch models without a release.
|
|
CREATE TABLE IF NOT EXISTS ai_models (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(60) NOT NULL,
|
|
protocol VARCHAR(20) NOT NULL DEFAULT 'openai',
|
|
base_url VARCHAR(300) NOT NULL DEFAULT '',
|
|
model_name VARCHAR(120) NOT NULL DEFAULT '',
|
|
api_key_cipher TEXT NOT NULL,
|
|
temperature DECIMAL(3,2) NOT NULL DEFAULT 0.80,
|
|
max_tokens INT UNSIGNED NOT NULL DEFAULT 256,
|
|
timeout_ms INT UNSIGNED NOT NULL DEFAULT 15000,
|
|
status TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
|
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
|
extra_json JSON NULL,
|
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_ai_models_name (name),
|
|
KEY idx_ai_models_status (status, is_default)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- Every call is recorded for cost accounting and after-the-fact review. The
|
|
-- prompt is kept as a digest so conversation content does not accumulate here.
|
|
CREATE TABLE IF NOT EXISTS ai_call_logs (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
model_id BIGINT UNSIGNED NOT NULL,
|
|
scene VARCHAR(30) NOT NULL DEFAULT 'chat',
|
|
agent_user_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
conversation_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
latency_ms INT UNSIGNED NOT NULL DEFAULT 0,
|
|
prompt_tokens INT UNSIGNED NOT NULL DEFAULT 0,
|
|
completion_tokens INT UNSIGNED NOT NULL DEFAULT 0,
|
|
status TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
|
error VARCHAR(500) NOT NULL DEFAULT '',
|
|
prompt_digest VARCHAR(64) NOT NULL DEFAULT '',
|
|
reply_text VARCHAR(1000) NOT NULL DEFAULT '',
|
|
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
PRIMARY KEY (id),
|
|
KEY idx_ai_call_logs_model_created (model_id, created_at),
|
|
KEY idx_ai_call_logs_created (created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
INSERT INTO system_configs (config_key, config_value, value_type, description) VALUES
|
|
('ai.enabled', 'false', 'boolean', '总开关:关闭后不调用任何模型,测试账号不再自动回复'),
|
|
('ai.default_model_id', '', 'string', '默认模型 ID,留空则使用模型列表中标记为默认的一条'),
|
|
('ai.max_concurrency', '4', 'string', '同时进行的模型调用上限'),
|
|
('ai.daily_call_limit', '2000', 'string', '全局每日调用次数上限,0 表示不限制'),
|
|
('ai.agent_daily_reply_limit', '50', 'string', '单个托管账号每日回复条数上限,0 表示不限制'),
|
|
('ai.reply_delay_min_ms', '2000', 'string', '回复前的最小延迟毫秒数,避免秒回失真'),
|
|
('ai.reply_delay_max_ms', '6000', 'string', '回复前的最大延迟毫秒数'),
|
|
('ai.allow_batches', '', 'string', '允许托管的测试批次,逗号分隔;留空表示全部测试账号'),
|
|
('ai.disclose_in_chat', 'true', 'boolean', '会话内标注 AI 身份;关闭前需确认所在地区的监管要求'),
|
|
('ai.fallback_text', '', 'string', '模型调用失败时发送的兜底文案,留空则静默不回复'),
|
|
('ai.log_retention_days', '30', 'string', '调用日志保留天数')
|
|
ON DUPLICATE KEY UPDATE description = VALUES(description), value_type = VALUES(value_type);
|