- Optimized Markov chain storage: Switched from full rewrites to incremental DB updates. - Improved AI creativity: Reduced repetitions using presence/frequency penalties and prompt tuning. - Increased Markov randomness: Lowered order to 1 and enabled learning of special characters/emojis. - Added @-ping protection: Automatically strips '@' symbols from AI and Markov responses. - Enhanced robustness: Added startup token checks, directory auto-creation, and admin/bot-info caching.
108 lines
2.8 KiB
TypeScript
108 lines
2.8 KiB
TypeScript
/**
|
|
* OpenAI-compatible AI client for multiple providers.
|
|
* Supports OpenAI, Ollama, OpenRouter, and any OpenAI-compatible API.
|
|
*/
|
|
|
|
export interface AIConfig {
|
|
apiKey: string;
|
|
baseUrl: string;
|
|
model: string;
|
|
systemPrompt: string;
|
|
groupPrompt?: string;
|
|
}
|
|
|
|
export interface AIMessage {
|
|
role: 'user' | 'assistant' | 'system';
|
|
content: string;
|
|
}
|
|
|
|
/**
|
|
* Mask an API key for safe display
|
|
* sk-proj-abc123xyz... → sk-***...***xyz
|
|
*/
|
|
export function maskApiKey(key: string): string {
|
|
if (!key || key.length < 8) return '***';
|
|
const start = key.substring(0, 3);
|
|
const end = key.substring(key.length - 3);
|
|
return `${start}***...***${end}`;
|
|
}
|
|
|
|
/**
|
|
* Generate AI response using OpenAI-compatible API
|
|
*/
|
|
export async function generateAIResponse(
|
|
config: AIConfig,
|
|
context: AIMessage[],
|
|
userMessage: string
|
|
): Promise<string> {
|
|
const messages: AIMessage[] = [];
|
|
|
|
// Build system prompt (global + group)
|
|
let systemPrompt = config.systemPrompt;
|
|
if (config.groupPrompt) {
|
|
systemPrompt += '\n\n' + config.groupPrompt;
|
|
}
|
|
messages.push({ role: 'system', content: systemPrompt });
|
|
|
|
// Add context messages (last 50)
|
|
messages.push(...context.slice(-50));
|
|
|
|
// Add user message
|
|
messages.push({ role: 'user', content: userMessage });
|
|
|
|
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${config.apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: config.model,
|
|
messages,
|
|
max_tokens: 500,
|
|
temperature: 0.8,
|
|
presence_penalty: 0.6,
|
|
frequency_penalty: 0.6,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(`AI API error: ${response.status} - ${error}`);
|
|
}
|
|
|
|
const data = await response.json() as { choices: Array<{ message?: { content?: string } }> };
|
|
return data.choices[0]?.message?.content || 'Keine Antwort erhalten.';
|
|
}
|
|
|
|
/**
|
|
* Get default base URL for a provider
|
|
*/
|
|
export function getDefaultBaseUrl(provider: string): string {
|
|
switch (provider) {
|
|
case 'openai':
|
|
return 'https://api.openai.com/v1';
|
|
case 'ollama':
|
|
return 'http://localhost:11434/v1';
|
|
case 'openrouter':
|
|
return 'https://openrouter.ai/api/v1';
|
|
default:
|
|
return 'https://api.openai.com/v1';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available models for a provider
|
|
*/
|
|
export function getProviderModels(provider: string): string[] {
|
|
switch (provider) {
|
|
case 'openai':
|
|
return ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'];
|
|
case 'ollama':
|
|
return ['llama3', 'llama3.1', 'mistral', 'codellama'];
|
|
case 'openrouter':
|
|
return ['openai/gpt-4o-mini', 'anthropic/claude-3.5-sonnet', 'meta-llama/llama-3.1-8b-instruct'];
|
|
default:
|
|
return ['gpt-4o-mini'];
|
|
}
|
|
} |