feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ChatOptions {
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
max_tokens?: number;
|
||||
}
|
||||
|
||||
interface AIProvider {
|
||||
name: string;
|
||||
chat(messages: ChatMessage[], options?: ChatOptions): Promise<string>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AIGatewayService {
|
||||
private readonly logger = new Logger(AIGatewayService.name);
|
||||
private providers: Map<string, AIProvider> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.registerProviders();
|
||||
}
|
||||
|
||||
private registerProviders() {
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
let apiUrl = process.env.OPENAI_API_URL || 'https://api.openai.com/v1/chat/completions';
|
||||
if (!apiUrl.endsWith('/chat/completions')) {
|
||||
apiUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions';
|
||||
}
|
||||
const defaultModel = process.env.OPENAI_MODEL || 'gpt-3.5-turbo';
|
||||
this.providers.set('openai', new OpenAICompatibleProvider(
|
||||
process.env.OPENAI_API_KEY!,
|
||||
apiUrl,
|
||||
defaultModel,
|
||||
'OpenAI 兼容接口',
|
||||
));
|
||||
this.logger.log(`OpenAI 兼容接口已注册: ${defaultModel}`);
|
||||
}
|
||||
|
||||
if (process.env.OPENCODE_API_KEY) {
|
||||
let apiUrl = process.env.OPENCODE_API_URL || 'https://opencode.ai/zen/go/v1';
|
||||
if (!apiUrl.endsWith('/chat/completions')) {
|
||||
apiUrl = apiUrl.replace(/\/+$/, '') + '/chat/completions';
|
||||
}
|
||||
const defaultModel = process.env.OPENCODE_MODEL || 'deepseek-v4-flash';
|
||||
this.providers.set('opencode', new OpenAICompatibleProvider(
|
||||
process.env.OPENCODE_API_KEY!,
|
||||
apiUrl,
|
||||
defaultModel,
|
||||
'OpenCode Go',
|
||||
));
|
||||
this.logger.log(`OpenCode Go 已注册: ${defaultModel}`);
|
||||
}
|
||||
}
|
||||
|
||||
async chat(model: string, messages: ChatMessage[], options?: ChatOptions): Promise<string> {
|
||||
const modelMap: Record<string, string> = {
|
||||
'general': 'openai',
|
||||
'openai': 'openai',
|
||||
'gpt-3.5': 'openai',
|
||||
'gpt-4': 'openai',
|
||||
'longcat': 'openai',
|
||||
'meituan/longcat-flash-lite': 'openai',
|
||||
'opencode-go': 'opencode',
|
||||
'opencode': 'opencode',
|
||||
'deepseek-v4-flash': 'opencode',
|
||||
};
|
||||
|
||||
const providerKey = modelMap[model.toLowerCase()] || (model.includes('/') ? 'openai' : model);
|
||||
const provider = this.providers.get(providerKey);
|
||||
|
||||
if (provider) {
|
||||
try {
|
||||
const reply = await provider.chat(messages, options);
|
||||
if (typeof reply !== 'string' || reply.length === 0) {
|
||||
throw new Error(`AI 返回内容为空: ${JSON.stringify(reply)}`);
|
||||
}
|
||||
return reply;
|
||||
} catch (err: any) {
|
||||
this.logger.error(`${provider.name} 调用失败: ${err.message}`);
|
||||
return this.fallback(messages);
|
||||
}
|
||||
}
|
||||
|
||||
return this.fallback(messages);
|
||||
}
|
||||
|
||||
private fallback(messages: ChatMessage[]): string {
|
||||
const lastMsg = messages[messages.length - 1]?.content || '';
|
||||
const mockReplies: Record<string, string> = {
|
||||
'你好': '你好!我是宇之然 AI 助手,很高兴为你服务!',
|
||||
'hello': 'Hello! I am YuZhiRan AI assistant, nice to meet you!',
|
||||
};
|
||||
|
||||
for (const [key, reply] of Object.entries(mockReplies)) {
|
||||
if (lastMsg.toLowerCase().includes(key)) {
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastMsg.includes('提示词') || lastMsg.includes('prompt')) {
|
||||
return '好的提示词需要明确角色、任务、输出格式和约束条件。例如:"你是一名专业的文案编辑,请帮我优化以下产品描述,要求语言简洁有力,突出产品核心卖点,控制在200字以内。"';
|
||||
}
|
||||
|
||||
if (lastMsg.includes('模型') || lastMsg.includes('大模型')) {
|
||||
return '目前主流的 AI 大模型包括:OpenAI 的 GPT 系列、Anthropic 的 Claude 系列、Google 的 Gemini 系列,以及国内的 DeepSeek、通义千问、文心一言、GLM 等。各模型在语言理解、代码生成、逻辑推理等方面各有优势。';
|
||||
}
|
||||
|
||||
const names = Array.from(this.providers.values()).map(p => p.name).join('、');
|
||||
return `我是宇之然 AI 助手。关于"${lastMsg.slice(0, 50)}..."的问题,我已收到。当前 AI 沙箱处于模拟模式,请配置 API Key 以获取真实回复。已配置的 API:${names}。`;
|
||||
}
|
||||
|
||||
getRegisteredProviders(): string[] {
|
||||
return Array.from(this.providers.keys());
|
||||
}
|
||||
}
|
||||
|
||||
class OpenAICompatibleProvider implements AIProvider {
|
||||
name: string;
|
||||
private apiKey: string;
|
||||
private apiUrl: string;
|
||||
private defaultModel: string;
|
||||
|
||||
constructor(apiKey: string, apiUrl: string, defaultModel: string, name?: string) {
|
||||
this.apiKey = apiKey;
|
||||
this.apiUrl = apiUrl;
|
||||
this.defaultModel = defaultModel;
|
||||
this.name = name || 'OpenAI 兼容接口';
|
||||
}
|
||||
|
||||
async chat(messages: ChatMessage[], options?: ChatOptions): Promise<string> {
|
||||
const res = await fetch(this.apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.defaultModel,
|
||||
messages,
|
||||
temperature: options?.temperature ?? 0.7,
|
||||
top_p: options?.top_p ?? 1,
|
||||
max_tokens: options?.max_tokens ?? 2000,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`${this.name} API error: ${res.status} ${await res.text()}`);
|
||||
}
|
||||
|
||||
const data = await res.json() as any;
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user