diff --git a/server/src/game/GameLoop.ts b/server/src/game/GameLoop.ts index c7b9e57..0c90e1c 100644 --- a/server/src/game/GameLoop.ts +++ b/server/src/game/GameLoop.ts @@ -65,7 +65,7 @@ export class GameLoop { this.runtimeConstants = createRuntimeConstants(); this.world = new World(); this.map = new GameMap(); - this.llmService = createLlmService(); + this.llmService = createLlmService(this.logService); this.narrationService = createNarrationService(this.llmService); this.eventMemoryService = createEventMemoryService(); this.thoughtSystem = createThoughtSystem(this.llmService, this.narrationService, this.eventMemoryService); @@ -228,7 +228,7 @@ export class GameLoop { } generateNpcBackstory(entityId: number): void { - generateBackstoryAndDesires(this.world, entityId, this.llmService, this.tick); + generateBackstoryAndDesires(this.world, entityId, this.llmService, this.tick, this.logService); } private spawnInitialNPCs(count: number): void { diff --git a/server/src/llm/backstoryGenerator.ts b/server/src/llm/backstoryGenerator.ts index 6e1de0e..0066558 100644 --- a/server/src/llm/backstoryGenerator.ts +++ b/server/src/llm/backstoryGenerator.ts @@ -4,6 +4,7 @@ import type { LlmService } from './llmService.js'; import type { ItemRegistry } from '../industry/itemRegistry.js'; import type { RecipeRegistry } from '../industry/recipeRegistry.js'; import type { StructureData } from '../systems/buildingSystem.js'; +import type { LogService } from '../services/logService.js'; const STAT_KEYS: (keyof Stats)[] = [ 'strength', 'dexterity', 'constitution', 'intelligence', 'perception', @@ -87,6 +88,7 @@ export async function generateBackstoryAndDesires( entityId: number, llmService: LlmService, tick: number, + logService?: LogService, ): Promise { const name = world.getComponent(entityId, 'name'); const stats = world.getComponent(entityId, 'stats'); @@ -109,6 +111,7 @@ export async function generateBackstoryAndDesires( parsed = JSON.parse(result); } catch { // JSON parse failed — use raw string as backstory fallback + logService?.log('warning', 'LLM', `JSON parse failed for backstory (entity ${entityId}), using raw string`); world.addComponent(entityId, 'backstory', result); return; } diff --git a/server/src/llm/llmService.ts b/server/src/llm/llmService.ts index c7c1224..72b0dd3 100644 --- a/server/src/llm/llmService.ts +++ b/server/src/llm/llmService.ts @@ -5,6 +5,7 @@ import { createUsageCounters } from './usageCounters.js'; import { renderTemplate } from './promptTemplate.js'; import { templates } from './templates.js'; import { createTokenTracker, type TokenTracker } from './tokenTracker.js'; +import type { LogService } from '../services/logService.js'; export interface LlmService { generate(templateName: string, variables: Record): Promise; @@ -17,7 +18,7 @@ export interface LlmService { tokenUsage(): TokenTracker; } -export function createLlmService(): LlmService { +export function createLlmService(logService?: LogService): LlmService { const config = getLlmConfig(); if (!config.enabled || !config.model) { @@ -33,7 +34,7 @@ export function createLlmService(): LlmService { }; } - const client = createOpenRouterClient(config); + const client = createOpenRouterClient(config, logService); const queue = createGenerationQueue(client, { requestsPerMinute: config.requestsPerMinute, }); @@ -56,6 +57,7 @@ export function createLlmService(): LlmService { currentModel = config.fallbackModel; console.log(`[LLM] Switched to fallback model: ${currentModel}`); + logService?.log('warning', 'LLM', `Switched to fallback model: ${currentModel}`); const resetTime = resetAt ?? getNextMidnightUTC(); const delay = Math.max(0, resetTime - Date.now()); @@ -65,6 +67,7 @@ export function createLlmService(): LlmService { currentModel = config.model; switchBackTimer = null; console.log(`[LLM] Switched back to primary model: ${currentModel}`); + logService?.log('info', 'LLM', `Switched back to primary model: ${currentModel}`); }, delay); } @@ -82,6 +85,7 @@ export function createLlmService(): LlmService { const result = await queue.enqueue({ ...rendered, model: currentModel }); if (isRateLimited(result)) { + logService?.log('warning', 'LLM', 'Rate limited, switching to fallback'); switchToFallback(result.resetAt); return null; } @@ -95,6 +99,7 @@ export function createLlmService(): LlmService { const totalRequests = Object.values(counters.getStats()).reduce((sum, s) => sum + s.total, 0); if (totalRequests % 100 === 0) { console.log(`[LLM] ${counters.getSummary()}`); + logService?.log('info', 'LLM', counters.getSummary()); } return result.content; } diff --git a/server/src/llm/openRouterClient.ts b/server/src/llm/openRouterClient.ts index 12bebfd..c1289b2 100644 --- a/server/src/llm/openRouterClient.ts +++ b/server/src/llm/openRouterClient.ts @@ -1,5 +1,6 @@ import type { LlmConfig } from '../config/llmConfig.js'; import type { RenderedPrompt } from './promptTemplate.js'; +import type { LogService } from '../services/logService.js'; const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions'; @@ -38,7 +39,7 @@ export interface OpenRouterClient { complete(request: CompletionRequest): Promise; } -export function createOpenRouterClient(config: LlmConfig): OpenRouterClient { +export function createOpenRouterClient(config: LlmConfig, logService?: LogService): OpenRouterClient { return { async complete(request: CompletionRequest): Promise { try { @@ -72,6 +73,7 @@ export function createOpenRouterClient(config: LlmConfig): OpenRouterClient { } } console.warn(`OpenRouter API error: ${response.status} ${response.statusText}`); + logService?.log('error', 'LLM', `OpenRouter API error: ${response.status} ${response.statusText}`); return null; } @@ -88,6 +90,7 @@ export function createOpenRouterClient(config: LlmConfig): OpenRouterClient { }; } catch (error) { console.warn('OpenRouter request failed:', (error as Error).message); + logService?.log('error', 'LLM', `OpenRouter request failed: ${(error as Error).message}`); return null; } },