Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/add-litellm-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@inkeep/agents-core": patch
"@inkeep/agents-manage-ui": patch
---

Add LiteLLM as a built-in OpenAI-compatible model provider. Use the `litellm/` prefix (e.g. `litellm/anthropic/claude-sonnet-4-5`) to route through a LiteLLM proxy; the base URL resolves from `providerOptions.baseURL`, then `LITELLM_API_BASE`, then `http://localhost:4000/v1`, and `LITELLM_API_KEY` is sent as a bearer token when set.
38 changes: 38 additions & 0 deletions agents-api/src/__tests__/run/agents/ModelFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,14 @@ describe('ModelFactory', () => {
});
});

test('should support litellm provider', () => {
const result = ModelFactory.parseModelString('litellm/anthropic/claude-sonnet-4-5');
expect(result).toEqual({
provider: 'litellm',
modelName: 'anthropic/claude-sonnet-4-5',
});
});

test('should support custom provider', () => {
const result = ModelFactory.parseModelString('custom/my-custom-model');
expect(result).toEqual({
Expand Down Expand Up @@ -900,6 +908,36 @@ describe('ModelFactory', () => {
}
});

test('should create LiteLLM models without provider options', () => {
// LiteLLM proxy exposes any provider model via an OpenAI-compatible API
const litellmModels = [
'litellm/anthropic/claude-sonnet-4-5',
'litellm/gpt-4o',
'litellm/bedrock/anthropic.claude-3-5-sonnet',
'litellm/my-team-alias',
];

for (const modelString of litellmModels) {
const config: ModelSettings = { model: modelString };
const model = ModelFactory.createModel(config);
expect(model).toBeDefined();
expect(model).toHaveProperty('modelId');
}
});

test('should create LiteLLM model with a custom base URL override', () => {
const config: ModelSettings = {
model: 'litellm/my-team-alias',
providerOptions: {
baseURL: 'https://litellm.internal.example.com/v1',
},
};

const model = ModelFactory.createModel(config);
expect(model).toBeDefined();
expect(model).toHaveProperty('modelId', 'my-team-alias');
});

test('should create Custom models with provider options', () => {
const config: ModelSettings = {
model: 'custom/my-custom-model',
Expand Down
34 changes: 34 additions & 0 deletions agents-docs/content/typescript-sdk/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Each model type inherits independently through the project → agent → sub age
| **OpenRouter** | `openrouter/anthropic/claude-sonnet-4-0`<br/>`openrouter/meta-llama/llama-3.1-405b` | `OPENROUTER_API_KEY` |
| **Gateway** | `gateway/openai/gpt-4.1-mini` | `AI_GATEWAY_API_KEY` |
| **NVIDIA NIM** | `nim/nvidia/llama-3.3-nemotron-super-49b-v1.5`<br/>`nim/nvidia/nemotron-4-340b-instruct` | `NIM_API_KEY` |
| **LiteLLM** | `litellm/anthropic/claude-sonnet-4-5`<br/>`litellm/gpt-4o` | `LITELLM_API_KEY` |
| **Custom OpenAI-compatible** | `custom/my-custom-model`<br/>`custom/llama-3-custom` | `CUSTOM_LLM_API_KEY` |
| **Mock** | `mock/default` | None required |

Expand Down Expand Up @@ -309,6 +310,39 @@ models: {
Azure OpenAI **requires** either `resourceName` (for standard Azure OpenAI deployments) or `baseURL` (for custom endpoints) in `providerOptions`. The `AZURE_API_KEY` environment variable must be set for authentication. Note that only one Azure OpenAI resource can be used at a time since authentication is handled via a single environment variable.
</Note>

**LiteLLM provider:**

[LiteLLM](https://docs.litellm.ai/docs/simple_proxy) exposes 100+ providers (OpenAI, Anthropic, Bedrock, Vertex, Azure, and more) behind a single OpenAI-compatible proxy. Use the `litellm/` prefix with any model name or alias configured on your proxy.

<Tabs>
<Tab title="TypeScript">
```typescript
models: {
base: {
model: "litellm/anthropic/claude-sonnet-4-5",
providerOptions: {
// Optional: overrides LITELLM_API_BASE (defaults to http://localhost:4000/v1)
baseURL: "https://my-litellm-proxy.example.com/v1",
temperature: 0.7
}
}
}
```
</Tab>
<Tab title="JSON">
```json
{
"baseUrl": "https://my-litellm-proxy.example.com/v1",
"temperature": 0.7
}
```
</Tab>
</Tabs>

<Note>
The LiteLLM provider points at your LiteLLM proxy. The base URL resolves from `providerOptions.baseURL`, then the `LITELLM_API_BASE` environment variable, then `http://localhost:4000/v1`. Set `LITELLM_API_KEY` to your proxy's virtual/master key; it is sent as a bearer token when present. Model names after the `litellm/` prefix are passed through to the proxy, so any provider model string or configured alias works.
</Note>

**Custom OpenAI-compatible provider:**

<Tabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
const [open, setOpen] = useState(defaultOpen);

const [showCustomInput, setShowCustomInput] = useState<
'openrouter' | 'gateway' | 'nim' | 'custom' | 'azure' | null
'openrouter' | 'gateway' | 'nim' | 'litellm' | 'custom' | 'azure' | null
>(null);
const [azureDeploymentName, setAzureDeploymentName] = useState('');
const [azureResourceName, setAzureResourceName] = useState('');
Expand Down Expand Up @@ -90,6 +90,10 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
const modelName = value.replace('nim/', '');
return { value, label: modelName, prefix: 'nim/' };
}
if (value.startsWith('litellm/')) {
const modelName = value.replace('litellm/', '');
return { value, label: modelName, prefix: 'litellm/' };
}
if (value.startsWith('custom/')) {
const modelName = value.replace('custom/', '');
return { value, label: modelName, prefix: 'custom/' };
Expand Down Expand Up @@ -203,6 +207,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
!modelValue.startsWith('openrouter/') &&
!modelValue.startsWith('gateway/') &&
!modelValue.startsWith('nim/') &&
!modelValue.startsWith('litellm/') &&
!modelValue.startsWith('custom/')
) {
// Could be openrouter format, let user decide or add logic here
Expand Down Expand Up @@ -310,6 +315,18 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
>
NVIDIA NIM ...
</CommandItem>
<CommandItem
className="flex items-center justify-between cursor-pointer text-foreground"
value="__litellm__"
onSelect={() => {
setShowCustomInput('litellm');
setOpen(false);
setCustomModelInput('');
onValueChange('litellm/...');
}}
>
LiteLLM ...
</CommandItem>
<CommandItem
className="flex items-center justify-between cursor-pointer text-foreground"
value="__azure__"
Expand Down Expand Up @@ -338,6 +355,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
openrouter: 'OpenRouter Model ID',
gateway: 'Vercel AI Gateway Model ID',
nim: 'NVIDIA NIM Model ID',
litellm: 'LiteLLM Model ID',
custom: '',
}[showCustomInput] || 'Custom Model ID'}
</div>
Expand All @@ -347,6 +365,8 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
'Examples: anthropic/claude-3-5-sonnet, meta-llama/llama-3.1-405b-instruct',
gateway: 'Examples: openai/gpt-4o, anthropic/claude-3-5-sonnet',
nim: 'Examples: nvidia/llama-3.3-nemotron-super-49b-v1.5, nvidia/nemotron-4-340b-instruct',
litellm:
'Examples: anthropic/claude-sonnet-4-5, gpt-4o, or a model alias configured on your LiteLLM proxy',
custom: '',
}[showCustomInput] || 'Examples: my-custom-model, llama-3-custom, custom-finetuned'}
</div>
Expand All @@ -357,6 +377,7 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
openrouter: 'anthropic/claude-3-5-sonnet',
gateway: 'openai/gpt-4o',
nim: 'nvidia/llama-3.3-nemotron-super-49b-v1.5',
litellm: 'anthropic/claude-sonnet-4-5',
custom: '',
}[showCustomInput] || 'my-custom-model'
}
Expand All @@ -371,7 +392,9 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
? 'gateway/'
: showCustomInput === 'nim'
? 'nim/'
: 'custom/';
: showCustomInput === 'litellm'
? 'litellm/'
: 'custom/';
onValueChange(`${prefix}${customModelInput.trim()}`);
setShowCustomInput(null);
setCustomModelInput('');
Expand All @@ -394,7 +417,9 @@ export const ModelSelector: FC<ModelSelectorProps> = ({
? 'gateway/'
: showCustomInput === 'nim'
? 'nim/'
: 'custom/';
: showCustomInput === 'litellm'
? 'litellm/'
: 'custom/';
onValueChange(`${prefix}${customModelInput.trim()}`);
setShowCustomInput(null);
setCustomModelInput('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface AgentStateData {
isSidebarSessionOpen: boolean;
variableSuggestions: string[];
/**
* Tracks if any model configuration modal is currently open (azure, openrouter, gateway, nim).
* Tracks if any model configuration modal is currently open (azure, openrouter, gateway, nim, litellm).
* Used to disable save button while configuration is in progress.
*/
hasOpenModelConfig: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ describe('ModelFactory', () => {
});
});

test('should parse litellm model string', () => {
const result = ModelFactory.parseModelString('litellm/anthropic/claude-sonnet-4-5');
expect(result).toEqual({
provider: 'litellm',
modelName: 'anthropic/claude-sonnet-4-5',
});
});

test('should parse custom model string', () => {
const result = ModelFactory.parseModelString('custom/my-custom-model');
expect(result).toEqual({
Expand Down
33 changes: 32 additions & 1 deletion packages/agents-core/src/utils/model-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ const nimDefault = createOpenAICompatible({
},
});

// LiteLLM proxy default endpoint (LiteLLM's documented default proxy port)
const LITELLM_DEFAULT_BASE_URL = 'http://localhost:4000/v1';

// LiteLLM default provider instance
const litellmDefault = createOpenAICompatible({
name: 'litellm',
baseURL: process.env.LITELLM_API_BASE || LITELLM_DEFAULT_BASE_URL,
headers: {
...(process.env.LITELLM_API_KEY && {
Authorization: `Bearer ${process.env.LITELLM_API_KEY}`,
}),
},
});

/**
* Factory for creating AI SDK language models from configuration
* Supports multiple providers and AI Gateway integration
Expand Down Expand Up @@ -78,6 +92,19 @@ export class ModelFactory {
};
return createOpenAICompatible(nimConfig);
}
case 'litellm': {
const litellmConfig = {
name: 'litellm',
baseURL: process.env.LITELLM_API_BASE || LITELLM_DEFAULT_BASE_URL,
headers: {
...(process.env.LITELLM_API_KEY && {
Authorization: `Bearer ${process.env.LITELLM_API_KEY}`,
}),
},
...config,
};
return createOpenAICompatible(litellmConfig);
}
case 'custom': {
if (!config.baseURL && !config.baseUrl) {
throw new Error(
Expand Down Expand Up @@ -261,6 +288,9 @@ export class ModelFactory {
case 'nim':
model = nimDefault(modelName);
break;
case 'litellm':
model = litellmDefault(modelName);
break;
case 'mock':
return createMockModel(modelName) as unknown as LanguageModel;
case 'custom':
Expand All @@ -271,7 +301,7 @@ export class ModelFactory {
throw new Error(
`Unsupported provider: ${provider}. ` +
`Supported providers are: ${ModelFactory.BUILT_IN_PROVIDERS.join(', ')}. ` +
`To access other models, use OpenRouter (openrouter/model-id), Vercel AI Gateway (gateway/model-id), NVIDIA NIM (nim/model-id), or Custom OpenAI-compatible (custom/model-id).`
`To access other models, use OpenRouter (openrouter/model-id), Vercel AI Gateway (gateway/model-id), NVIDIA NIM (nim/model-id), LiteLLM (litellm/model-id), or Custom OpenAI-compatible (custom/model-id).`
);
}
}
Expand All @@ -293,6 +323,7 @@ export class ModelFactory {
'openrouter',
'gateway',
'nim',
'litellm',
'custom',
'mock',
] as const;
Expand Down
Loading