update openrouter
This commit is contained in:
107
src/services/ai/openrouter.ts
Normal file
107
src/services/ai/openrouter.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* OpenRouter AI provider implementation
|
||||
*/
|
||||
|
||||
import OpenAI from "openai";
|
||||
import { config } from "../../core/config";
|
||||
import { createLogger } from "../../core/logger";
|
||||
import type { AiProvider, AiResponse, AskOptions, MessageStyle } from "./types";
|
||||
|
||||
const logger = createLogger("AI:OpenRouter");
|
||||
|
||||
// Style classification options
|
||||
const STYLE_OPTIONS: MessageStyle[] = ["story", "snarky", "insult", "explicit", "helpful"];
|
||||
|
||||
export class OpenRouterProvider implements AiProvider {
|
||||
private client: OpenAI;
|
||||
|
||||
constructor() {
|
||||
this.client = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
apiKey: config.ai.openRouterApiKey,
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": "https://github.com/crunk-bun",
|
||||
"X-Title": "Joel Discord Bot",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async health(): Promise<boolean> {
|
||||
try {
|
||||
// Simple health check - verify we can list models
|
||||
await this.client.models.list();
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Health check failed", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async ask(options: AskOptions): Promise<AiResponse> {
|
||||
const { prompt, systemPrompt, maxTokens, temperature } = options;
|
||||
|
||||
try {
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: config.ai.model,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
max_tokens: maxTokens ?? config.ai.maxTokens,
|
||||
temperature: temperature ?? config.ai.temperature,
|
||||
});
|
||||
|
||||
const text = completion.choices[0]?.message?.content ?? "";
|
||||
|
||||
// Discord message limit safety
|
||||
return { text: text.slice(0, 1900) };
|
||||
} catch (error: unknown) {
|
||||
logger.error("Failed to generate response", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a message to determine the appropriate response style
|
||||
*/
|
||||
async classifyMessage(message: string): Promise<MessageStyle> {
|
||||
try {
|
||||
const classification = await this.client.chat.completions.create({
|
||||
model: config.ai.classificationModel,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `Classify this message into exactly one category. Only respond with the category name, nothing else.
|
||||
|
||||
Message: "${message}"
|
||||
|
||||
Categories:
|
||||
- story: User wants a story, narrative, or creative writing
|
||||
- snarky: User is being sarcastic or deserves a witty comeback
|
||||
- insult: User is being rude or hostile, respond with brutal insults
|
||||
- explicit: User wants adult/NSFW content
|
||||
- helpful: User has a genuine question or needs actual help
|
||||
|
||||
Category:`,
|
||||
},
|
||||
],
|
||||
max_tokens: 10,
|
||||
temperature: 0.1,
|
||||
});
|
||||
|
||||
const result = classification.choices[0]?.message?.content?.toLowerCase().trim() as MessageStyle;
|
||||
|
||||
// Validate the result is a valid style
|
||||
if (STYLE_OPTIONS.includes(result)) {
|
||||
logger.debug("Message classified", { style: result });
|
||||
return result;
|
||||
}
|
||||
|
||||
logger.debug("Classification returned invalid style, defaulting to snarky", { result });
|
||||
return "snarky";
|
||||
} catch (error) {
|
||||
logger.error("Failed to classify message", error);
|
||||
return "snarky"; // Default to snarky on error
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user