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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
### Core Concepts

- **Agents**: Agents are the main building block of kagent. They are a system prompt, a set of tools and agents, and an LLM configuration represented with a Kubernetes custom resource called "Agent".
- **LLM Providers**: Kagent supports multiple LLM providers, including [OpenAI](https://kagent.dev/docs/kagent/supported-providers/openai), [Azure OpenAI](https://kagent.dev/docs/kagent/supported-providers/azure-openai), [Anthropic](https://kagent.dev/docs/kagent/supported-providers/anthropic), [Google Vertex AI](https://kagent.dev/docs/kagent/supported-providers/google-vertexai), [Ollama](https://kagent.dev/docs/kagent/supported-providers/ollama) and any other custom providers and models accessible via AI gateways. Providers are represented by the ModelConfig resource.
- **LLM Providers**: Kagent supports multiple LLM providers, including [OpenAI](https://kagent.dev/docs/kagent/supported-providers/openai), [Azure OpenAI](https://kagent.dev/docs/kagent/supported-providers/azure-openai), [Anthropic](https://kagent.dev/docs/kagent/supported-providers/anthropic), [Google Vertex AI](https://kagent.dev/docs/kagent/supported-providers/google-vertexai), [Ollama](https://kagent.dev/docs/kagent/supported-providers/ollama), [OrcaRouter](https://www.orcarouter.ai) and any other custom providers and models accessible via AI gateways. Providers are represented by the ModelConfig resource.
- **MCP Tools**: Agents can connect to any MCP server that provides tools. Kagent comes with an MCP server with tools for Kubernetes, Istio, Helm, Argo, Prometheus, Grafana, Cilium, and others. All tools are Kubernetes custom resources (ToolServers) and can be used by multiple agents.
- **Observability**: Kagent supports [OpenTelemetry tracing](https://kagent.dev/docs/kagent/getting-started/tracing), which allows you to monitor what's happening with your agents and tools.

Expand Down
9 changes: 5 additions & 4 deletions docs/architecture/crds-and-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Configures LLM provider credentials and model parameters.
```
ModelConfigSpec
├── model: string (e.g. "gpt-4o", "claude-sonnet-4-5-20250514")
├── provider: Anthropic | OpenAI | AzureOpenAI | Ollama | Gemini | GeminiVertexAI | AnthropicVertexAI | Bedrock | SAPAICore | Foundry
├── provider: Anthropic | OpenAI | AzureOpenAI | Ollama | Gemini | GeminiVertexAI | AnthropicVertexAI | Bedrock | SAPAICore | Foundry | OrcaRouter
├── apiKeySecret: string (Secret name)
├── apiKeySecretKey: string (key within Secret)
├── apiKeyPassthrough: bool (use Bearer token from A2A request)
Expand Down Expand Up @@ -146,9 +146,10 @@ ModelConfigSpec
├── bedrock: BedrockConfig
├── sapAICore: SAPAICoreConfig
│ └── baseUrl, resourceGroup, authUrl
└── foundry: FoundryConfig # Go runtime only
└── endpoint, endpointFrom, deployment, apiVersion
└── region
├── foundry: FoundryConfig # Go runtime only
│ └── endpoint, endpointFrom, deployment, apiVersion
└── orcaRouter: OrcaRouterConfig # OpenAI-compatible gateway
└── baseUrl, temperature, maxTokens, maxCompletionTokens, topP, reasoningEffort, apiFormat, timeout
```

### Key Validation Rules
Expand Down
18 changes: 18 additions & 0 deletions go/adk/pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,24 @@ func CreateLLM(ctx context.Context, m adk.Model, log logr.Logger) (adkmodel.LLM,
}
return models.NewOpenAIModelWithLogger(cfg, log)

case *adk.OrcaRouter:
baseURL := m.BaseUrl
if baseURL == "" {
baseURL = "https://api.orcarouter.ai/v1"
}
cfg := &models.OpenAIConfig{
TransportConfig: transportConfigFromBase(m.BaseModel, m.Timeout),
Model: m.Model,
BaseUrl: baseURL,
MaxTokens: m.MaxTokens,
MaxCompletionTokens: m.MaxCompletionTokens,
Temperature: m.Temperature,
TopP: m.TopP,
ReasoningEffort: m.ReasoningEffort,
APIFormat: m.APIFormat,
}
return models.NewOrcaRouterModelWithLogger(cfg, log)

case *adk.AzureOpenAI:
cfg := &models.AzureOpenAIConfig{
TransportConfig: transportConfigFromBase(m.BaseModel, nil),
Expand Down
16 changes: 16 additions & 0 deletions go/adk/pkg/models/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ func NewOpenAIModelWithLogger(config *OpenAIConfig, logger logr.Logger) (*OpenAI
return newOpenAIModelFromConfig(config, apiKey, logger)
}

// NewOrcaRouterModelWithLogger creates an OpenAI-compatible model for the
// OrcaRouter gateway. The wire protocol matches OpenAI chat completions, so it
// reuses the OpenAI client pointed at the OrcaRouter base URL; the API key is
// read from ORCAROUTER_API_KEY (or forwarded per-request when APIKeyPassthrough
// is enabled).
func NewOrcaRouterModelWithLogger(config *OpenAIConfig, logger logr.Logger) (*OpenAIModel, error) {
apiKey := "passthrough" // placeholder; real auth set per-request by transport
if !config.APIKeyPassthrough {
apiKey = os.Getenv("ORCAROUTER_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("ORCAROUTER_API_KEY environment variable is not set")
}
}
return newOpenAIModelFromConfig(config, apiKey, logger)
}

// NewOpenAICompatibleModelWithLogger creates an OpenAI-compatible model (e.g. LiteLLM, Ollama).
// baseURL is the API base (e.g. http://localhost:11434/v1 for Ollama). apiKey is optional; if empty,
// OPENAI_API_KEY is used, then a placeholder for endpoints that do not require a key.
Expand Down
38 changes: 38 additions & 0 deletions go/api/adk/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const (
ModelTypeBedrock = "bedrock"
ModelTypeSAPAICore = "sap_ai_core"
ModelTypeFoundry = "foundry"
ModelTypeOrcaRouter = "orcarouter"
)

func (o *OpenAI) MarshalJSON() ([]byte, error) {
Expand Down Expand Up @@ -359,6 +360,37 @@ func (f *Foundry) GetType() string {
return ModelTypeFoundry
}

// OrcaRouter is the OrcaRouter OpenAI-compatible gateway model type. The wire
// format is identical to OpenAI chat completions, so the runtime builds an
// OpenAI-compatible client pointed at api.orcarouter.ai.
type OrcaRouter struct {
BaseModel
BaseUrl string `json:"base_url,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
ReasoningEffort *string `json:"reasoning_effort,omitempty"`
Timeout *int `json:"timeout,omitempty"`
// APIFormat selects chatCompletions (default) or responses.
APIFormat string `json:"api_format,omitempty"`
}

func (o *OrcaRouter) MarshalJSON() ([]byte, error) {
type Alias OrcaRouter
return json.Marshal(&struct {
Type string `json:"type"`
*Alias
}{
Type: ModelTypeOrcaRouter,
Alias: (*Alias)(o),
})
}

func (o *OrcaRouter) GetType() string {
return ModelTypeOrcaRouter
}

// GenericModel is a catch-all model type used by the Go ADK when the model
// type doesn't match any known constant.
type GenericModel struct {
Expand Down Expand Up @@ -433,6 +465,12 @@ func ParseModel(bytes []byte) (Model, error) {
return nil, err
}
return &foundry, nil
case ModelTypeOrcaRouter:
var orcaRouter OrcaRouter
if err := json.Unmarshal(bytes, &orcaRouter); err != nil {
return nil, err
}
return &orcaRouter, nil
}
return nil, fmt.Errorf("unknown model type: %s", model.Type)
}
Expand Down
103 changes: 103 additions & 0 deletions go/api/config/crd/bases/kagent.dev_modelconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,55 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
orcaRouter:
description: OrcaRouter-specific configuration
properties:
apiFormat:
default: chatCompletions
description: |-
APIFormat selects which OpenAI HTTP API the runtime uses for this model.
chatCompletions (default) posts to /v1/chat/completions.
responses posts to /v1/responses.
enum:
- chatCompletions
- responses
type: string
baseUrl:
description: Base URL for the OrcaRouter API (overrides default
https://api.orcarouter.ai/v1)
type: string
maxCompletionTokens:
description: Maximum completion tokens to generate
minimum: 1
type: integer
maxTokens:
description: Maximum tokens to generate
minimum: 1
type: integer
reasoningEffort:
description: Reasoning effort
enum:
- none
- minimal
- low
- medium
- high
- xhigh
- max
type: string
temperature:
description: Temperature for sampling
type: string
timeout:
description: Timeout
type: integer
topP:
description: Top-p sampling parameter
type: string
type: object
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
Expand All @@ -805,6 +854,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
- OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
Expand Down Expand Up @@ -902,6 +952,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
- message: provider.orcaRouter must be nil if the provider is not OrcaRouter
rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
Expand Down Expand Up @@ -1444,6 +1496,54 @@ spec:
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
orcaRouter:
description: OrcaRouter-specific configuration
properties:
apiFormat:
default: chatCompletions
description: |-
APIFormat selects which OpenAI HTTP API the runtime uses for this model.
chatCompletions (default) posts to /v1/chat/completions.
responses posts to /v1/responses.
enum:
- chatCompletions
- responses
type: string
baseUrl:
description: Base URL for the OrcaRouter API (overrides default
https://api.orcarouter.ai/v1)
type: string
maxCompletionTokens:
description: Maximum completion tokens to generate
minimum: 1
type: integer
maxTokens:
description: Maximum tokens to generate
minimum: 1
type: integer
reasoningEffort:
description: Reasoning effort
enum:
- none
- minimal
- low
- medium
- high
- xhigh
type: string
temperature:
description: Temperature for sampling
type: string
timeout:
description: Timeout
type: integer
topP:
description: Top-p sampling parameter
type: string
type: object
x-kubernetes-validations:
- message: maxTokens and maxCompletionTokens are mutually exclusive
rule: '!(has(self.maxTokens) && has(self.maxCompletionTokens))'
provider:
default: OpenAI
description: The provider of the model
Expand All @@ -1458,6 +1558,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
- OrcaRouter
type: string
sapAICore:
description: SAP AI Core-specific configuration
Expand Down Expand Up @@ -1555,6 +1656,8 @@ spec:
rule: '!(has(self.sapAICore) && self.provider != ''SAPAICore'')'
- message: provider.foundry must be nil if the provider is not Foundry
rule: '!(has(self.foundry) && self.provider != ''Foundry'')'
- message: provider.orcaRouter must be nil if the provider is not OrcaRouter
rule: '!(has(self.orcaRouter) && self.provider != ''OrcaRouter'')'
- message: apiKeySecret must be set if apiKeySecretKey is set
rule: '!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))'
- message: apiKeySecretKey must be set if apiKeySecret is set (except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
- OrcaRouter
type: string
required:
- type
Expand Down Expand Up @@ -272,6 +273,7 @@ spec:
- Bedrock
- SAPAICore
- Foundry
- OrcaRouter
type: string
required:
- type
Expand Down
55 changes: 54 additions & 1 deletion go/api/v1alpha2/modelconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
)

// ModelProvider represents the model provider type
// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry
// +kubebuilder:validation:Enum=Anthropic;OpenAI;AzureOpenAI;Ollama;Gemini;GeminiVertexAI;AnthropicVertexAI;Bedrock;SAPAICore;Foundry;OrcaRouter
type ModelProvider string

const (
Expand All @@ -42,6 +42,7 @@ const (
ModelProviderBedrock ModelProvider = "Bedrock"
ModelProviderSAPAICore ModelProvider = "SAPAICore"
ModelProviderFoundry ModelProvider = "Foundry"
ModelProviderOrcaRouter ModelProvider = "OrcaRouter"
)

type BaseVertexAIConfig struct {
Expand Down Expand Up @@ -432,6 +433,53 @@ type FoundryConfig struct {
APIVersion string `json:"apiVersion,omitempty"`
}

// OrcaRouterConfig contains OrcaRouter-specific configuration options.
//
// OrcaRouter is an OpenAI-compatible AI gateway (https://www.orcarouter.ai) that
// routes to many upstream models behind a single endpoint. Like the OpenAI
// provider, it speaks the chat completions wire format; the default base URL is
// the OrcaRouter gateway endpoint.
//
// +kubebuilder:validation:XValidation:message="maxTokens and maxCompletionTokens are mutually exclusive",rule="!(has(self.maxTokens) && has(self.maxCompletionTokens))"
type OrcaRouterConfig struct {
// Base URL for the OrcaRouter API (overrides default https://api.orcarouter.ai/v1)
// +optional
BaseURL string `json:"baseUrl,omitempty"`

// Temperature for sampling
// +optional
Temperature string `json:"temperature,omitempty"`

// Maximum tokens to generate
// +optional
// +kubebuilder:validation:Minimum=1
MaxTokens int `json:"maxTokens,omitempty"`

// Maximum completion tokens to generate
// +optional
// +kubebuilder:validation:Minimum=1
MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"`

// Top-p sampling parameter
// +optional
TopP string `json:"topP,omitempty"`

// Reasoning effort
// +optional
ReasoningEffort *OpenAIReasoningEffort `json:"reasoningEffort,omitempty"`

// APIFormat selects which OpenAI HTTP API the runtime uses for this model.
// chatCompletions (default) posts to /v1/chat/completions.
// responses posts to /v1/responses.
// +optional
// +kubebuilder:default=chatCompletions
APIFormat *OpenAIAPIFormat `json:"apiFormat,omitempty"`

// Timeout
// +optional
Timeout *int `json:"timeout,omitempty"`
}

// TLSConfig contains TLS/SSL configuration options for outbound HTTPS
// connections from the agent (model provider, RemoteMCPServer). The
// XValidation rules below apply at admission to every CRD field that
Expand Down Expand Up @@ -500,6 +548,7 @@ func (t *TLSConfig) IsEmpty() bool {
// +kubebuilder:validation:XValidation:message="provider.bedrock must be nil if the provider is not Bedrock",rule="!(has(self.bedrock) && self.provider != 'Bedrock')"
// +kubebuilder:validation:XValidation:message="provider.sapAICore must be nil if the provider is not SAPAICore",rule="!(has(self.sapAICore) && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="provider.foundry must be nil if the provider is not Foundry",rule="!(has(self.foundry) && self.provider != 'Foundry')"
// +kubebuilder:validation:XValidation:message="provider.orcaRouter must be nil if the provider is not OrcaRouter",rule="!(has(self.orcaRouter) && self.provider != 'OrcaRouter')"
// +kubebuilder:validation:XValidation:message="apiKeySecret must be set if apiKeySecretKey is set",rule="!(has(self.apiKeySecretKey) && !has(self.apiKeySecret))"
// +kubebuilder:validation:XValidation:message="apiKeySecretKey must be set if apiKeySecret is set (except for Bedrock and SAPAICore providers)",rule="!(has(self.apiKeySecret) && !has(self.apiKeySecretKey) && self.provider != 'Bedrock' && self.provider != 'SAPAICore')"
// +kubebuilder:validation:XValidation:message="apiKeyPassthrough and apiKeySecret are mutually exclusive",rule="!(has(self.apiKeyPassthrough) && self.apiKeyPassthrough && has(self.apiKeySecret) && size(self.apiKeySecret) > 0)"
Expand Down Expand Up @@ -577,6 +626,10 @@ type ModelConfigSpec struct {
// +optional
Foundry *FoundryConfig `json:"foundry,omitempty"`

// OrcaRouter-specific configuration
// +optional
OrcaRouter *OrcaRouterConfig `json:"orcaRouter,omitempty"`

// TLS configuration for provider connections.
// Enables agents to connect to internal LiteLLM gateways or other providers
// that use self-signed certificates or custom certificate authorities.
Expand Down
2 changes: 2 additions & 0 deletions go/api/v1alpha2/modelproviderconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func DefaultModelProviderEndpoint(providerType ModelProvider) string {
return "https://generativelanguage.googleapis.com"
case ModelProviderOllama:
return "http://localhost:11434"
case ModelProviderOrcaRouter:
return "https://api.orcarouter.ai/v1"
default:
// Azure, Bedrock, Vertex AI require user-specific endpoints
return ""
Expand Down
Loading
Loading