feat(llm): Added Groq and Deepseek providers

This commit is contained in:
2026-07-16 18:25:07 +05:30
parent 13155cba23
commit 2b56c01e4c
13 changed files with 829 additions and 0 deletions

View File

@@ -5,6 +5,8 @@ const LLMConfigSchema = z.object({
OPENROUTER_API_KEY: z.string().optional(),
ANTHROPIC_API_KEY: z.string().optional(),
OPENAI_API_KEY: z.string().optional(),
GROQ_API_KEY: z.string().optional(),
DEEPSEEK_API_KEY: z.string().optional(),
});
export const llmConfig = LLMConfigSchema.parse(process.env);

View File

@@ -6,4 +6,6 @@ export * from "./providers/ollama.js";
export * from "./providers/openrouter.js";
export * from "./providers/anthropic.js";
export * from "./providers/openai.js";
export * from "./providers/groq.js";
export * from "./providers/deepseek.js";
export * from "./provider-manager.js";

View File

@@ -90,6 +90,20 @@ export const AVAILABLE_PROVIDERS: ModelProviderMeta[] = [
defaultModel: "claude-3-5-sonnet-latest",
defaultEmbeddingModel: "",
},
{
id: "groq",
displayName: "Groq",
description: "Official Groq integration using @langchain/groq SDK",
defaultModel: "llama-3.3-70b-versatile",
defaultEmbeddingModel: "",
},
{
id: "deepseek",
displayName: "DeepSeek",
description: "Official DeepSeek integration using @langchain/deepseek SDK",
defaultModel: "deepseek-chat",
defaultEmbeddingModel: "",
},
{
id: "openrouter",
displayName: "OpenRouter",

View File

@@ -101,6 +101,8 @@ function getSettingsDb() {
const openRouterKey = process.env.OPENROUTER_API_KEY;
const anthropicKey = process.env.ANTHROPIC_API_KEY;
const openaiKey = process.env.OPENAI_API_KEY;
const groqKey = process.env.GROQ_API_KEY;
const deepseekKey = process.env.DEEPSEEK_API_KEY;
let hasInsertedGenerative = false;
let hasInsertedEmbedding = false;
@@ -209,6 +211,52 @@ function getSettingsDb() {
}
}
if (groqKey && groqKey.trim()) {
const id = "provider-default-groq";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(
`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type, maxContext)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`,
).run(
id,
"Groq (Env)",
"groq",
groqKey.trim(),
isActive,
"llama-3.3-70b-versatile",
"generative",
8192,
);
if (isActive === 1) {
hasInsertedGenerative = true;
}
}
if (deepseekKey && deepseekKey.trim()) {
const id = "provider-default-deepseek";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(
`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type, maxContext)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`,
).run(
id,
"DeepSeek (Env)",
"deepseek",
deepseekKey.trim(),
isActive,
"deepseek-chat",
"generative",
64000,
);
if (isActive === 1) {
hasInsertedGenerative = true;
}
}
if (openRouterKey && openRouterKey.trim()) {
const id = "provider-default-openrouter";
const isActive = hasInsertedGenerative ? 0 : 1;
@@ -464,6 +512,8 @@ export class ProviderManager {
const openRouterKey = process.env.OPENROUTER_API_KEY;
const anthropicKey = process.env.ANTHROPIC_API_KEY;
const openaiKey = process.env.OPENAI_API_KEY;
const groqKey = process.env.GROQ_API_KEY;
const deepseekKey = process.env.DEEPSEEK_API_KEY;
let hasInsertedGenerative = false;
let hasInsertedEmbedding = false;
@@ -572,6 +622,52 @@ export class ProviderManager {
}
}
if (groqKey && groqKey.trim()) {
const id = "provider-default-groq";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(
`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type, maxContext)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`,
).run(
id,
"Groq (Env)",
"groq",
groqKey.trim(),
isActive,
"llama-3.3-70b-versatile",
"generative",
8192,
);
if (isActive === 1) {
hasInsertedGenerative = true;
}
}
if (deepseekKey && deepseekKey.trim()) {
const id = "provider-default-deepseek";
const isActive = hasInsertedGenerative ? 0 : 1;
db.prepare(
`
INSERT INTO provider_instances (id, name, providerName, apiKey, isActive, modelName, type, maxContext)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`,
).run(
id,
"DeepSeek (Env)",
"deepseek",
deepseekKey.trim(),
isActive,
"deepseek-chat",
"generative",
64000,
);
if (isActive === 1) {
hasInsertedGenerative = true;
}
}
if (openRouterKey && openRouterKey.trim()) {
const id = "provider-default-openrouter";
const isActive = hasInsertedGenerative ? 0 : 1;
@@ -757,6 +853,32 @@ export class ProviderManager {
maxContext: 200000,
};
}
const groqKey = process.env.GROQ_API_KEY;
if (groqKey && groqKey.trim()) {
return {
id: "provider-default-env-fallback",
name: "Groq (Env Fallback)",
providerName: "groq",
apiKey: groqKey.trim(),
isActive: true,
modelName: "llama-3.3-70b-versatile",
type: "generative",
maxContext: 8192,
};
}
const deepseekKey = process.env.DEEPSEEK_API_KEY;
if (deepseekKey && deepseekKey.trim()) {
return {
id: "provider-default-env-fallback",
name: "DeepSeek (Env Fallback)",
providerName: "deepseek",
apiKey: deepseekKey.trim(),
isActive: true,
modelName: "deepseek-chat",
type: "generative",
maxContext: 64000,
};
}
const openRouterKey = process.env.OPENROUTER_API_KEY;
if (openRouterKey && openRouterKey.trim()) {
return {

View File

@@ -0,0 +1,114 @@
import { z } from "zod";
import { ChatDeepSeek } from "@langchain/deepseek";
import {
ILLMProvider,
LLMRequest,
LLMResponse,
LLMCallRecord,
} from "../llm.js";
import { llmConfig } from "../config.js";
import { ProviderManager } from "../provider-manager.js";
export class DeepSeekProvider implements ILLMProvider {
static readonly providerId = "deepseek";
static readonly displayName = "DeepSeek";
static readonly description =
"Official DeepSeek integration using @langchain/deepseek SDK";
static readonly defaultModel = "deepseek-chat";
providerName = "DeepSeek";
private model: ChatDeepSeek;
private modelNameUsed: string;
private providerInstanceName?: string;
private maxContextUsed?: number;
lastCalls: LLMCallRecord[] = [];
constructor(
apiKey?: string,
modelName?: string,
providerInstanceName?: string,
maxContext?: number,
) {
let key = apiKey;
let model = modelName;
this.providerInstanceName = providerInstanceName;
this.maxContextUsed = maxContext;
if (!key) {
const active = ProviderManager.getActive("generative");
if (active && active.providerName === DeepSeekProvider.providerId) {
key = active.apiKey;
if (!model) {
model = active.modelName;
}
if (!this.providerInstanceName) {
this.providerInstanceName = active.name;
}
if (this.maxContextUsed === undefined) {
this.maxContextUsed = active.maxContext;
}
}
}
if (!key) {
key = llmConfig.DEEPSEEK_API_KEY;
if (!this.providerInstanceName && key) {
this.providerInstanceName = "Environment Variable";
}
}
if (!key) {
throw new Error(
"DEEPSEEK_API_KEY is required to initialize DeepSeekProvider",
);
}
this.modelNameUsed = model || DeepSeekProvider.defaultModel;
this.model = new ChatDeepSeek({
apiKey: key,
model: this.modelNameUsed,
});
}
async generateStructuredResponse<T extends z.ZodTypeAny>(
request: LLMRequest<T>,
): Promise<LLMResponse<z.infer<T>>> {
const structuredModel = this.model.withStructuredOutput(request.schema, {
includeRaw: true,
});
const result = (await structuredModel.invoke([
{ role: "system", content: request.systemPrompt },
{ role: "user", content: request.userContext },
])) as unknown as {
parsed?: z.infer<T>;
raw?: {
usage_metadata?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
};
};
};
const parsed = result?.parsed;
const raw = result?.raw;
const usage = {
inputTokens: raw?.usage_metadata?.input_tokens || 0,
outputTokens: raw?.usage_metadata?.output_tokens || 0,
totalTokens: raw?.usage_metadata?.total_tokens || 0,
modelName: this.modelNameUsed,
providerInstanceName: this.providerInstanceName || "Default",
maxContext:
this.maxContextUsed !== undefined ? this.maxContextUsed : 64000,
};
this.lastCalls.push({
systemPrompt: request.systemPrompt,
userContext: request.userContext,
usage,
});
return { success: true, data: parsed, usage };
}
}

View File

@@ -0,0 +1,112 @@
import { z } from "zod";
import { ChatGroq } from "@langchain/groq";
import {
ILLMProvider,
LLMRequest,
LLMResponse,
LLMCallRecord,
} from "../llm.js";
import { llmConfig } from "../config.js";
import { ProviderManager } from "../provider-manager.js";
export class GroqProvider implements ILLMProvider {
static readonly providerId = "groq";
static readonly displayName = "Groq";
static readonly description =
"Official Groq integration using @langchain/groq SDK";
static readonly defaultModel = "llama-3.3-70b-versatile";
providerName = "Groq";
private model: ChatGroq;
private modelNameUsed: string;
private providerInstanceName?: string;
private maxContextUsed?: number;
lastCalls: LLMCallRecord[] = [];
constructor(
apiKey?: string,
modelName?: string,
providerInstanceName?: string,
maxContext?: number,
) {
let key = apiKey;
let model = modelName;
this.providerInstanceName = providerInstanceName;
this.maxContextUsed = maxContext;
if (!key) {
const active = ProviderManager.getActive("generative");
if (active && active.providerName === GroqProvider.providerId) {
key = active.apiKey;
if (!model) {
model = active.modelName;
}
if (!this.providerInstanceName) {
this.providerInstanceName = active.name;
}
if (this.maxContextUsed === undefined) {
this.maxContextUsed = active.maxContext;
}
}
}
if (!key) {
key = llmConfig.GROQ_API_KEY;
if (!this.providerInstanceName && key) {
this.providerInstanceName = "Environment Variable";
}
}
if (!key) {
throw new Error("GROQ_API_KEY is required to initialize GroqProvider");
}
this.modelNameUsed = model || GroqProvider.defaultModel;
this.model = new ChatGroq({
apiKey: key,
model: this.modelNameUsed,
});
}
async generateStructuredResponse<T extends z.ZodTypeAny>(
request: LLMRequest<T>,
): Promise<LLMResponse<z.infer<T>>> {
const structuredModel = this.model.withStructuredOutput(request.schema, {
includeRaw: true,
});
const result = (await structuredModel.invoke([
{ role: "system", content: request.systemPrompt },
{ role: "user", content: request.userContext },
])) as unknown as {
parsed?: z.infer<T>;
raw?: {
usage_metadata?: {
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
};
};
};
const parsed = result?.parsed;
const raw = result?.raw;
const usage = {
inputTokens: raw?.usage_metadata?.input_tokens || 0,
outputTokens: raw?.usage_metadata?.output_tokens || 0,
totalTokens: raw?.usage_metadata?.total_tokens || 0,
modelName: this.modelNameUsed,
providerInstanceName: this.providerInstanceName || "Default",
maxContext:
this.maxContextUsed !== undefined ? this.maxContextUsed : 8192,
};
this.lastCalls.push({
systemPrompt: request.systemPrompt,
userContext: request.userContext,
usage,
});
return { success: true, data: parsed, usage };
}
}