Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export const CONFIG = {
BATCH_SIZE: int("INTENT_BATCH_SIZE", { default: 20, min: 1 }),
TINY_BATCH_FRACTION: number("INTENT_TINY_BATCH_FRACTION", { default: 0.2, min: 0, max: 1 }),
},
TEST: {
SCOPE: string("TEST_SCOPE", { default: "all" }),
},
} as const;
3 changes: 2 additions & 1 deletion src/llm_client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CONFIG } from "./config";
import { createDefaultGroqClient } from "./providers/groq";

import type { LlmClient, IntentContext } from "./types";
Expand All @@ -17,7 +18,7 @@ export function selectLlmClient(ctx: IntentContext): LlmClient | undefined {
if (ctx.llm) {
return ctx.llm;
}
const groqKey = process.env.GROQ_API_KEY;
const groqKey = CONFIG.GROQ.API_KEY;
if (groqKey && groqKey !== "") {
return createDefaultGroqClient(groqKey);
}
Expand Down
7 changes: 4 additions & 3 deletions src/providers/groq.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { describe, expect, test } from "vitest";

import { CONFIG } from "../config";
import { buildMessages } from "../messages";
import { buildRelevancySchema } from "../schema";

import { createDefaultGroqClient } from "./groq";

const hasKey = Boolean(process.env.GROQ_API_KEY);
const hasKey = Boolean(CONFIG.GROQ.API_KEY);

describe.skipIf(!hasKey)("groq provider integration", () => {
test.concurrent("provider returns scores for all schema keys", async () => {
const client = createDefaultGroqClient(process.env.GROQ_API_KEY!);
const client = createDefaultGroqClient(CONFIG.GROQ.API_KEY);
const candidates = [
{ key: "A", summary: "first" },
{ key: "B", summary: "second" },
Expand All @@ -28,7 +29,7 @@ describe.skipIf(!hasKey)("groq provider integration", () => {
});

test.concurrent("assigns 0 to unrelated and >0 to related", async () => {
const client = createDefaultGroqClient(process.env.GROQ_API_KEY!);
const client = createDefaultGroqClient(CONFIG.GROQ.API_KEY);
const candidates = [
{ key: "JS Arrays", summary: "Guide to sorting arrays in JavaScript" },
{ key: "Banana Bread Recipe", summary: "How to bake banana bread" },
Expand Down
3 changes: 2 additions & 1 deletion src/providers/groq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ function shouldRetry(err: any, remaining: number): boolean {
*
* @example
* ```typescript
* const client = createDefaultGroqClient(process.env.GROQ_API_KEY!);
* import { CONFIG } from "../config";
* const client = createDefaultGroqClient(CONFIG.GROQ.API_KEY);
* const result = await client.call(messages, schema, { model: "llama-3.3-70b" });
* ```
*/
Expand Down
45 changes: 27 additions & 18 deletions src/reranker.groq-default.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,36 @@ vi.mock("groq-sdk", () => ({
},
}));

// Mock CONFIG to have a test key
vi.mock("./config", async (importOriginal) => {
const original = await importOriginal<typeof import("./config")>();
return {
...original,
CONFIG: {
...original.CONFIG,
GROQ: {
...original.CONFIG.GROQ,
API_KEY: "test-key",
},
},
};
});

const { Reranker } = await import("./reranker");

describe("Reranker (default Groq) ", () => {
test("uses groq-sdk when GROQ_API_KEY is set", async () => {
const oldKey = process.env.GROQ_API_KEY;
process.env.GROQ_API_KEY = "test-key";
try {
const reranker = new Reranker<{ key: string; summary: string }>(
{
/* no llm */
},
{ key: (x) => x.key, summary: (x) => x.summary },
);
const out = await reranker.rerank("q", [
{ key: "A", summary: "" },
{ key: "B", summary: "" },
]);
expect(out.map((c) => c.key)).toEqual(["A"]);
expect(callMock.mock.calls.length).toBe(1);
} finally {
process.env.GROQ_API_KEY = oldKey;
}
const reranker = new Reranker<{ key: string; summary: string }>(
{
/* no llm */
},
{ key: (x) => x.key, summary: (x) => x.summary },
);
const out = await reranker.rerank("q", [
{ key: "A", summary: "" },
{ key: "B", summary: "" },
]);
expect(out.map((c) => c.key)).toEqual(["A"]);
expect(callMock.mock.calls.length).toBe(1);
});
});
3 changes: 2 additions & 1 deletion src/reranker.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { describe, expect, test } from "vitest";

import { CONFIG } from "./config";
import { Reranker } from "./reranker";

const hasKey = Boolean(process.env.GROQ_API_KEY);
const hasKey = Boolean(CONFIG.GROQ.API_KEY);

describe.skipIf(!hasKey)("reranker integration", () => {
test.concurrent(
Expand Down
28 changes: 22 additions & 6 deletions src/reranker.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,30 @@ function makeCtx(overrides: Partial<IntentContext> = {}): IntentContext & {

describe("Reranker.rerank", () => {
test("throws when no llm and no GROQ_API_KEY", async () => {
const oldKey = process.env.GROQ_API_KEY;
delete (process.env as any).GROQ_API_KEY;
const { CONFIG } = await import("./config");

try {
expect(() => new Reranker<RerankerCandidate>({} as any, { key: (c) => c.key })).toThrow(
/No LLM client provided/,
);
// Mock CONFIG to return empty API key
vi.doMock("./config", () => ({
CONFIG: {
...CONFIG,
GROQ: {
...CONFIG.GROQ,
API_KEY: "",
},
},
}));

// Re-import modules to get mocked config
vi.resetModules();
const { Reranker: RerankerWithMock } = await import("./reranker");

expect(
() => new RerankerWithMock<RerankerCandidate>({} as any, { key: (c) => c.key }),
).toThrow(/No LLM client provided/);
} finally {
process.env.GROQ_API_KEY = oldKey;
vi.doUnmock("./config");
vi.resetModules();
}
});

Expand Down
2 changes: 0 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ export type RerankerCandidate = {
summary: string;
};

// RerankerConfig moved to reranker_config.ts to centralize defaults/env/validation

export type RerankerExtractors<T> = {
key: (item: T) => string;
summary?: (item: T) => string;
Expand Down
5 changes: 3 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import "dotenv/config";
import { defineConfig } from "vitest/config";

const scope = process.env.TEST_SCOPE ?? "all"; // unit | int | all
import { CONFIG } from "./src/config";

const scope = CONFIG.TEST.SCOPE;
const includePatterns =
scope === "unit"
? ["src/**/*.unit.test.ts"]
Expand Down