Files
ulfbot/src/ai.ts
sarah cb70812739 Refactor: Optimize DB performance, reduce repetitions, and add @-ping protection
- 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.
2026-05-14 13:35:45 +02:00

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'];
}
}