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);