This commit is contained in:
2026-01-29 12:26:13 +01:00
parent ba0f116bc2
commit 6dbcadcaee
79 changed files with 2795 additions and 657 deletions

41
src/services/ai/index.ts Normal file
View File

@@ -0,0 +1,41 @@
/**
* AI Service - Main entry point for AI functionality
*/
import { createLogger } from "../../core/logger";
import { ReplicateProvider } from "./replicate";
import type { AiProvider, AiResponse } from "./types";
const logger = createLogger("AI:Service");
export class AiService {
private provider: AiProvider;
constructor(provider?: AiProvider) {
this.provider = provider ?? new ReplicateProvider();
}
async health(): Promise<boolean> {
return this.provider.health();
}
async generateResponse(
prompt: string,
systemPrompt: string
): Promise<AiResponse> {
logger.debug("Generating response", { promptLength: prompt.length });
return this.provider.ask({ prompt, systemPrompt });
}
}
// Singleton instance
let aiService: AiService | null = null;
export function getAiService(): AiService {
if (!aiService) {
aiService = new AiService();
}
return aiService;
}
export type { AiProvider, AiResponse } from "./types";

View File

@@ -0,0 +1,63 @@
/**
* Replicate AI provider implementation
*/
import Replicate from "replicate";
import { config } from "../../core/config";
import { createLogger } from "../../core/logger";
import type { AiProvider, AiResponse, AskOptions } from "./types";
const logger = createLogger("AI:Replicate");
export class ReplicateProvider implements AiProvider {
private client: Replicate;
constructor() {
this.client = new Replicate({
auth: config.ai.replicateApiToken,
});
}
async health(): Promise<boolean> {
try {
// Simple health check - just verify we can create a client
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 formattedPrompt = `<|im_start|>system
${systemPrompt}<|im_end|>
<|im_start|>user
${prompt}<|im_end|>
<|im_start|>assistant
`;
const input = {
prompt: formattedPrompt,
temperature: temperature ?? config.ai.temperature,
max_new_tokens: maxTokens ?? config.ai.maxTokens,
};
let output = "";
for await (const event of this.client.stream(config.ai.model as `${string}/${string}:${string}`, {
input,
})) {
output += event;
// Discord message limit safety
if (output.length >= 1900) break;
}
return { text: output.slice(0, 1900) };
} catch (error: unknown) {
logger.error("Failed to generate response", error);
throw error;
}
}
}

27
src/services/ai/types.ts Normal file
View File

@@ -0,0 +1,27 @@
/**
* AI Provider interface
* Allows swapping AI providers (Replicate, OpenAI, etc.) without changing business logic
*/
export interface AiResponse {
text: string;
}
export interface AiProvider {
/**
* Generate a response to a prompt
*/
ask(options: AskOptions): Promise<AiResponse>;
/**
* Check if the AI service is healthy
*/
health(): Promise<boolean>;
}
export interface AskOptions {
prompt: string;
systemPrompt: string;
maxTokens?: number;
temperature?: number;
}