Skip to content
12 changes: 11 additions & 1 deletion src/lib/llm/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-05-12T00:00:00Z'));
});
afterEach(() => vi.useRealTimers());
afterEach(() => {
__setLlmProvider(null);
vi.useRealTimers();
});

const schema = z.object({ ok: z.boolean(), answer: z.string() });

Expand Down Expand Up @@ -69,4 +72,11 @@ describe('llmCall', () => {
expect(r.ok).toBe(true);
if (r.ok) expect(r.data.answer).toBe('yo');
});

it('falls back to default provider or returns unavailable when override is cleared', async () => {
__setLlmProvider(null);
const r = await llmCall({ prompt: 'noop', schema });
expect(r.ok).toBe(false);
if (!r.ok) expect(r.error.code).toBe('llm_unavailable');
});
});
21 changes: 20 additions & 1 deletion src/lib/llm/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import Groq from 'groq-sdk';
import type { Result } from '../result';
import { ok, err } from '../result';

Expand All @@ -25,14 +26,32 @@ type LlmCallArgs<T> = {
schema: z.ZodType<T>;
};

const groqApiKey = process.env.GROQ_API_KEY;

const groqProvider: LlmProvider = {
name: 'groq',
complete: async (prompt: string) => {
if (!groqApiKey) {
throw new Error('GROQ_API_KEY is not set');
}
const groq = new Groq({ apiKey: groqApiKey });
const completion = await groq.chat.completions.create({
messages: [{ role: 'user', content: prompt }],
model: 'llama3-8b-8192',
});
return completion.choices[0]?.message?.content ?? '';
},
isHealthy: () => !!groqApiKey,
};

let providerOverride: LlmProvider | null = null;

export function __setLlmProvider(p: LlmProvider | null): void {
providerOverride = p;
}

function pickProvider(): LlmProvider | null {
return providerOverride;
return providerOverride || (groqApiKey ? groqProvider : null);
}

function extractJson(raw: string): string {
Expand Down
Loading