mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 12:02:49 +05:30
feat: Added openrouter provider
This commit is contained in:
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
const LLMConfigSchema = z.object({
|
||||
GOOGLE_API_KEY: z.string().optional(),
|
||||
OPENROUTER_API_KEY: z.string().optional(),
|
||||
});
|
||||
|
||||
export const llmConfig = LLMConfigSchema.parse(process.env);
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./llm.js";
|
||||
export * from "./config.js";
|
||||
export * from "./providers/google-genai.js";
|
||||
export * from "./providers/mock.js";
|
||||
export * from "./providers/openrouter.js";
|
||||
|
||||
31
packages/llm/src/providers/openrouter.ts
Normal file
31
packages/llm/src/providers/openrouter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
import { ChatOpenRouter } from "@langchain/openrouter";
|
||||
import { ILLMProvider, LLMRequest, LLMResponse } from "../llm.js";
|
||||
import { llmConfig } from "../config.js";
|
||||
|
||||
export class OpenRouterProvider implements ILLMProvider {
|
||||
providerName = "OpenRouter";
|
||||
private model: ChatOpenRouter;
|
||||
|
||||
constructor(apiKey?: string, modelName: string = "google/gemini-2.5-flash") {
|
||||
const key = apiKey || llmConfig.OPENROUTER_API_KEY;
|
||||
if (!key) {
|
||||
throw new Error("OPENROUTER_API_KEY is required to initialize OpenRouterProvider");
|
||||
}
|
||||
this.model = new ChatOpenRouter({
|
||||
apiKey: key,
|
||||
model: modelName,
|
||||
});
|
||||
}
|
||||
|
||||
async generateStructuredResponse<T extends z.ZodTypeAny>(
|
||||
request: LLMRequest<T>,
|
||||
): Promise<LLMResponse<z.infer<T>>> {
|
||||
const structuredModel = this.model.withStructuredOutput(request.schema);
|
||||
const result = await structuredModel.invoke([
|
||||
{ role: "system", content: request.systemPrompt },
|
||||
{ role: "user", content: request.userContext },
|
||||
]);
|
||||
return { success: true, data: result as z.infer<T> };
|
||||
}
|
||||
}
|
||||
81
packages/llm/tests/openrouter.test.ts
Normal file
81
packages/llm/tests/openrouter.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { describe, test, expect, vi } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { OpenRouterProvider } from "../src/providers/openrouter.js";
|
||||
import { llmConfig } from "../src/config.js";
|
||||
|
||||
// Mock the ChatOpenRouter class
|
||||
vi.mock("@langchain/openrouter", () => {
|
||||
return {
|
||||
ChatOpenRouter: class {
|
||||
config: unknown;
|
||||
constructor(config: unknown) {
|
||||
this.config = config;
|
||||
}
|
||||
withStructuredOutput = vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
invoke: vi.fn().mockImplementation(async () => {
|
||||
// Return a mock output that matches a sample schema
|
||||
return {
|
||||
name: "mocked response",
|
||||
success: true,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("OpenRouterProvider Unit Tests (Tier 1)", () => {
|
||||
test("initializes successfully with a provided apiKey", () => {
|
||||
const provider = new OpenRouterProvider("dummy-key");
|
||||
expect(provider.providerName).toBe("OpenRouter");
|
||||
});
|
||||
|
||||
test("initializes successfully with apiKey from config", () => {
|
||||
// Save current config
|
||||
const originalKey = llmConfig.OPENROUTER_API_KEY;
|
||||
llmConfig.OPENROUTER_API_KEY = "env-dummy-key";
|
||||
|
||||
try {
|
||||
const provider = new OpenRouterProvider();
|
||||
expect(provider.providerName).toBe("OpenRouter");
|
||||
} finally {
|
||||
llmConfig.OPENROUTER_API_KEY = originalKey;
|
||||
}
|
||||
});
|
||||
|
||||
test("throws error if no API key is provided or in config", () => {
|
||||
// Save current config
|
||||
const originalKey = llmConfig.OPENROUTER_API_KEY;
|
||||
llmConfig.OPENROUTER_API_KEY = undefined;
|
||||
|
||||
try {
|
||||
expect(() => new OpenRouterProvider()).toThrow(
|
||||
"OPENROUTER_API_KEY is required to initialize OpenRouterProvider"
|
||||
);
|
||||
} finally {
|
||||
llmConfig.OPENROUTER_API_KEY = originalKey;
|
||||
}
|
||||
});
|
||||
|
||||
test("generateStructuredResponse invokes the model with structured output", async () => {
|
||||
const provider = new OpenRouterProvider("dummy-key");
|
||||
const TestSchema = z.object({
|
||||
name: z.string(),
|
||||
success: z.boolean(),
|
||||
});
|
||||
|
||||
const response = await provider.generateStructuredResponse({
|
||||
systemPrompt: "system prompt",
|
||||
userContext: "user context",
|
||||
schema: TestSchema,
|
||||
});
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.data).toEqual({
|
||||
name: "mocked response",
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user