feat: multi-provider AI support with per-provider model selection
- Add llm-factory.ts: unified LLM provider abstraction (Google/Claude/OpenAI) - Install @langchain/anthropic and @langchain/openai packages - resolveAiProvider picks active provider from DB settings (Claude > OpenAI > Google) - All AI graphs (reminder, new-patient, reschedule, internal-chat) now accept provider+model params - Add claudeAiModel, openAiModel, googleAiModel columns to ai_settings table - New PUT /api/ai/provider-model route to save selected model per provider - UI model dropdowns for Claude (Haiku/Sonnet/Opus), OpenAI (GPT-5.x series), Google (Gemini 2.5/3.x) - Google AI section also gets model selector alongside existing API key field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { StateGraph, END, START, Annotation } from "@langchain/langgraph";
|
||||
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
||||
import { getLlm, type AiProvider } from "./llm-factory";
|
||||
|
||||
const GraphState = Annotation.Root({
|
||||
message: Annotation<string>(),
|
||||
@@ -95,7 +95,9 @@ async function confirmNode(state: GraphStateType, config: any) {
|
||||
if (!apiKey) return { reply: fallback };
|
||||
|
||||
try {
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey });
|
||||
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||
const model: string | undefined = config?.configurable?.model;
|
||||
const llm = getLlm(provider, apiKey, model);
|
||||
const apptClause = appt ? ` Their appointment is on ${appt}.` : "";
|
||||
const response = await llm.invoke([
|
||||
{
|
||||
@@ -134,7 +136,9 @@ async function rescheduleNode(state: GraphStateType, config: any) {
|
||||
if (!apiKey) return { reply: fallback };
|
||||
|
||||
try {
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey });
|
||||
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||
const model: string | undefined = config?.configurable?.model;
|
||||
const llm = getLlm(provider, apiKey, model);
|
||||
const response = await llm.invoke([
|
||||
{
|
||||
role: "system",
|
||||
@@ -160,7 +164,9 @@ async function otherNode(state: GraphStateType, config: any) {
|
||||
const fallback = NEW_APPT_FALLBACKS[lang] ?? NEW_APPT_FALLBACKS["English"]!;
|
||||
if (!apiKey) return { reply: fallback, intent: "wants_appointment" };
|
||||
try {
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey });
|
||||
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||
const model: string | undefined = config?.configurable?.model;
|
||||
const llm = getLlm(provider, apiKey, model);
|
||||
const response = await llm.invoke([
|
||||
{
|
||||
role: "system",
|
||||
@@ -177,7 +183,9 @@ async function otherNode(state: GraphStateType, config: any) {
|
||||
const fallback = state.generalFallback || (GENERAL_FALLBACKS[lang] ?? GENERAL_FALLBACKS["English"]!);
|
||||
if (!apiKey) return { reply: fallback };
|
||||
try {
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey });
|
||||
const provider: AiProvider = config?.configurable?.provider ?? "google";
|
||||
const model: string | undefined = config?.configurable?.model;
|
||||
const llm = getLlm(provider, apiKey, model);
|
||||
const response = await llm.invoke([
|
||||
{
|
||||
role: "system",
|
||||
@@ -215,11 +223,13 @@ export async function runReminderGraph(
|
||||
language = "English",
|
||||
appointmentDatetime = "",
|
||||
rescheduleGreeting = "",
|
||||
generalFallback = ""
|
||||
generalFallback = "",
|
||||
provider: AiProvider = "google",
|
||||
model?: string
|
||||
): Promise<{ reply: string | null; intent: string | null }> {
|
||||
const result = await graph.invoke(
|
||||
{ message: patientMessage, intent: "", reply: "", language, appointmentDatetime, rescheduleGreeting, generalFallback },
|
||||
{ configurable: { apiKey } }
|
||||
{ configurable: { apiKey, provider, model } }
|
||||
);
|
||||
return {
|
||||
reply: result.reply || null,
|
||||
|
||||
Reference in New Issue
Block a user