-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add OpenRouter + direct OpenAI as alternative LLM providers #442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: test
Are you sure you want to change the base?
Changes from all commits
0408e9d
5a3cac6
8f6b5b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import type { LanguageModel } from "ai"; | ||
| import { gateway } from "@ai-sdk/gateway"; | ||
| import { createOpenRouter } from "@openrouter/ai-sdk-provider"; | ||
| import { openai } from "@ai-sdk/openai"; | ||
|
|
||
| /** | ||
| * Resolves a model string (e.g. "openai/gpt-5-mini") to a LanguageModel, | ||
| * routing through whichever provider is configured. | ||
| * | ||
| * Priority: Vercel AI Gateway > OpenRouter > direct OpenAI. | ||
| */ | ||
| export function createModel(modelId: string): LanguageModel { | ||
| // Vercel AI Gateway — existing production behavior | ||
| if (process.env.VERCEL_AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN) { | ||
| return gateway(modelId); | ||
| } | ||
|
|
||
| // OpenRouter — supports "provider/model" format natively | ||
| if (process.env.OPENROUTER_API_KEY) { | ||
| const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); | ||
| return openrouter(modelId); | ||
| } | ||
|
|
||
| // Direct OpenAI — only supports openai/* models | ||
| if (process.env.OPENAI_API_KEY) { | ||
| if (!modelId.startsWith("openai/") && modelId.includes("/")) { | ||
| throw new Error( | ||
| `Model "${modelId}" is not an OpenAI model. Direct OpenAI mode only supports openai/* models. ` + | ||
| `Use OPENROUTER_API_KEY or VERCEL_AI_GATEWAY_API_KEY for multi-provider support.`, | ||
| ); | ||
| } | ||
| const bareModel = modelId.startsWith("openai/") ? modelId.slice(7) : modelId; | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| return openai(bareModel); | ||
| } | ||
|
|
||
| throw new Error( | ||
| "No LLM provider configured. Set VERCEL_AI_GATEWAY_API_KEY, OPENROUTER_API_KEY, or OPENAI_API_KEY.", | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,45 @@ | ||
| import { gateway, GatewayLanguageModelEntry } from "@ai-sdk/gateway"; | ||
| import isEmbedModel from "./isEmbedModel"; | ||
| import { DEFAULT_MODEL, LIGHTWEIGHT_MODEL } from "@/lib/const"; | ||
|
|
||
| /** | ||
| * Returns the list of available LLMs from the Vercel AI Gateway. | ||
| * Filters out embed models that are not suitable for chat. | ||
| * Default model list for non-gateway providers (OpenRouter, direct OpenAI). | ||
| * Returned only when the Vercel AI Gateway is not configured. | ||
| */ | ||
| const DEFAULT_MODELS: GatewayLanguageModelEntry[] = [ | ||
| { | ||
| id: DEFAULT_MODEL, | ||
| name: "GPT-5 Mini", | ||
| description: "Default model for chat and generation", | ||
| pricing: { input: "0.0001", output: "0.0004" }, | ||
| specification: { specificationVersion: "v2", provider: "openai", modelId: DEFAULT_MODEL }, | ||
| }, | ||
| { | ||
| id: LIGHTWEIGHT_MODEL, | ||
| name: "GPT-4o Mini", | ||
| description: "Lightweight model for simple tasks", | ||
| pricing: { input: "0.00015", output: "0.0006" }, | ||
| specification: { specificationVersion: "v2", provider: "openai", modelId: LIGHTWEIGHT_MODEL }, | ||
| }, | ||
| ]; | ||
|
|
||
| /** | ||
| * Returns the list of available LLMs. | ||
| * Uses Vercel AI Gateway when configured, otherwise returns a default list. | ||
| */ | ||
| export const getAvailableModels = async (): Promise<GatewayLanguageModelEntry[]> => { | ||
| try { | ||
| const apiResponse = await gateway.getAvailableModels(); | ||
| const gatewayModels = apiResponse.models.filter(m => !isEmbedModel(m)); | ||
| return gatewayModels; | ||
| } catch { | ||
| return []; | ||
| // Use Vercel AI Gateway when configured | ||
| if (process.env.VERCEL_AI_GATEWAY_API_KEY) { | ||
| try { | ||
| const apiResponse = await gateway.getAvailableModels(); | ||
| const gatewayModels = apiResponse.models.filter(m => !isEmbedModel(m)); | ||
| return gatewayModels; | ||
| } catch (error) { | ||
| console.error("[getAvailableModels] Gateway fetch failed:", error); | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| // Fallback for OpenRouter or direct OpenAI | ||
| return DEFAULT_MODELS; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { LanguageModel, ModelMessage } from "ai"; | ||
| import { createReleaseReportToolChain } from "./create_release_report/createReleaseReportToolChain"; | ||
| import { createNewArtistToolChain } from "./createNewArtistToolChain"; | ||
| import { createModel } from "@/lib/ai/createModel"; | ||
|
|
||
| export type ToolChainItem = { | ||
| toolName: string; | ||
|
|
@@ -18,22 +19,22 @@ export type PrepareStepResult = { | |
| // Forced toolChoice is incompatible with Anthropic extended thinking. | ||
| // Every tool used in a chain must have a model here to avoid the conflict. | ||
| export const TOOL_MODEL_MAP: Record<string, LanguageModel> = { | ||
| update_account_info: "gemini-2.5-pro", | ||
| get_spotify_search: "openai/gpt-5.4-mini", | ||
| update_artist_socials: "openai/gpt-5.4-mini", | ||
| artist_deep_research: "openai/gpt-5.4-mini", | ||
| spotify_deep_research: "openai/gpt-5.4-mini", | ||
| get_artist_socials: "openai/gpt-5.4-mini", | ||
| get_spotify_artist_top_tracks: "openai/gpt-5.4-mini", | ||
| get_spotify_artist_albums: "openai/gpt-5.4-mini", | ||
| get_spotify_album: "openai/gpt-5.4-mini", | ||
| search_web: "openai/gpt-5.4-mini", | ||
| generate_txt_file: "openai/gpt-5.4-mini", | ||
| create_segments: "openai/gpt-5.4-mini", | ||
| youtube_login: "openai/gpt-5.4-mini", | ||
| web_deep_research: "openai/gpt-5.4-mini", | ||
| create_knowledge_base: "openai/gpt-5.4-mini", | ||
| send_email: "openai/gpt-5.4-mini", | ||
| update_account_info: createModel("google/gemini-2.5-pro"), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: When only Prompt for AI agents |
||
| get_spotify_search: createModel("openai/gpt-5.4-mini"), | ||
| update_artist_socials: createModel("openai/gpt-5.4-mini"), | ||
| artist_deep_research: createModel("openai/gpt-5.4-mini"), | ||
| spotify_deep_research: createModel("openai/gpt-5.4-mini"), | ||
| get_artist_socials: createModel("openai/gpt-5.4-mini"), | ||
| get_spotify_artist_top_tracks: createModel("openai/gpt-5.4-mini"), | ||
| get_spotify_artist_albums: createModel("openai/gpt-5.4-mini"), | ||
| get_spotify_album: createModel("openai/gpt-5.4-mini"), | ||
| search_web: createModel("openai/gpt-5.4-mini"), | ||
| generate_txt_file: createModel("openai/gpt-5.4-mini"), | ||
| create_segments: createModel("openai/gpt-5.4-mini"), | ||
| youtube_login: createModel("openai/gpt-5.4-mini"), | ||
| web_deep_research: createModel("openai/gpt-5.4-mini"), | ||
| create_knowledge_base: createModel("openai/gpt-5.4-mini"), | ||
| send_email: createModel("openai/gpt-5.4-mini"), | ||
| }; | ||
|
|
||
| // Map trigger tool -> sequence AFTER trigger | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KISS principle