feat(admin): LLM config, knowledge base, and WeCom AI binding
CI / verify (pull_request) Has been cancelled

Add owner-scoped model API settings, uploadable knowledge bases, and WeCom bot AI/KB wiring with OpenAI-compatible URL normalization.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-26 09:31:22 +08:00
parent 745c192693
commit 2b7ef65cce
30 changed files with 2507 additions and 56 deletions
@@ -0,0 +1,66 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../common/prisma/prisma.module';
import { LlmChatClient } from '../llm/llm-chat.client';
import { KnowledgeRetrievalService } from '../llm/knowledge-retrieval.service';
import type { WecomBotRuntimeConfig } from './wecom-bot.types';
const DEFAULT_SYSTEM = [
'你是杜康好客企业内部助手。',
'优先依据提供的知识库内容回答;知识库未覆盖时如实说明不确定。',
'不要编造订单号、金额、权限;涉及写操作请引导用户使用指令(如「帮助」)。',
'回答简洁,使用中文。',
].join('');
@Injectable()
export class WecomBotAiService {
private readonly logger = new Logger(WecomBotAiService.name);
constructor(
private readonly prisma: PrismaService,
private readonly llm: LlmChatClient,
private readonly kb: KnowledgeRetrievalService,
) {}
async replyIfConfigured(bot: WecomBotRuntimeConfig, userText: string): Promise<string | null> {
if (!bot.aiEnabled || !bot.llmConfigId) return null;
const cfg = await this.prisma.llmApiConfig.findUnique({
where: { id: BigInt(bot.llmConfigId) },
});
if (!cfg?.enabled) {
return '已开启 AI,但绑定的语言模型未启用或已删除。请在 HQ「企微机器人」检查配置。';
}
let kbBlock = '';
if (bot.knowledgeBaseId) {
try {
kbBlock = await this.kb.buildContext(BigInt(bot.knowledgeBaseId), userText);
} catch (e) {
this.logger.warn(`kb retrieve failed: ${String(e)}`);
}
}
const systemParts = [
cfg.systemPrompt?.trim() || DEFAULT_SYSTEM,
kbBlock ? `\n\n以下为知识库检索片段:\n${kbBlock}` : '',
];
try {
return await this.llm.chat({
baseUrl: cfg.baseUrl,
apiKey: cfg.apiKey,
model: cfg.modelName,
temperature: cfg.temperature != null ? Number(cfg.temperature) : 0.3,
maxTokens: cfg.maxTokens ?? 1024,
messages: [
{ role: 'system', content: systemParts.join('') },
{ role: 'user', content: userText },
],
});
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
this.logger.error(`wecom ai reply failed: ${msg}`);
return `AI 回复失败:${msg}`;
}
}
}