Feature: UI dynamic provider model discovery#1274
Feature: UI dynamic provider model discovery#1274nujragan93 wants to merge 9 commits intokagent-dev:mainfrom
Conversation
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
EItanya
left a comment
There was a problem hiding this comment.
These changes are a super exciting first step for this feature! Really excited to see it all coming together. I have some structural changes as well as some nits. I think there will probably be multiple rounds of reviews, but this is a good start.
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
…/nujragan93/kagent into feature/provider-model-discovery Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces dynamic model discovery for LLM providers through a new Provider Custom Resource Definition (CRD). It enables users to configure custom provider endpoints (e.g., AI gateways, self-hosted LLMs) and automatically discover available models from those providers.
Changes:
- Added Provider CRD with controller for automatic model discovery from OpenAI, Anthropic, Ollama, Azure, Gemini, and Bedrock APIs
- Implemented backend API endpoints to list configured providers and their models
- Created new React components (ProviderCombobox, ModelCombobox) to support provider and model selection in the UI
- Added auto-selection of stock OpenAI provider on page load and fixed provider selection persistence bug
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| go/api/v1alpha2/provider_types.go | Defines Provider CRD structure with spec and status fields |
| go/internal/controller/provider_controller.go | Controller that watches Provider CRs and triggers reconciliation |
| go/internal/controller/reconciler/reconciler.go | Reconciliation logic for model discovery and status updates |
| go/internal/controller/provider/discoverer.go | Model discovery implementation for various LLM providers |
| go/internal/httpserver/handlers/providers.go | API handlers for listing providers and fetching models |
| go/config/crd/bases/kagent.dev_providers.yaml | Base CRD manifest with validation rules |
| helm/kagent-crds/templates/kagent.dev_providers.yaml | Helm template for Provider CRD |
| ui/src/types/index.ts | TypeScript type definitions for providers and models |
| ui/src/components/ProviderCombobox.tsx | New component for provider selection with grouping |
| ui/src/components/ModelCombobox.tsx | New component for model selection |
| ui/src/components/models/new/BasicInfoSection.tsx | Updated to use separate provider and model selection |
| ui/src/app/models/new/page.tsx | Main page logic with auto-fetch and provider management |
| ui/src/app/actions/providers.ts | Server actions for fetching configured providers and models |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - jsonPath: .spec.endpoint | ||
| name: Endpoint | ||
| type: string | ||
| - jsonPath: .status.discoveredModels[?(@)] |
There was a problem hiding this comment.
The JSONPath for the "Models" additionalPrinterColumn is inconsistent with the base CRD. In the base CRD (go/config/crd/bases/kagent.dev_providers.yaml), this uses .status.modelCount, but here it uses .status.discoveredModels[?(@)].
The JSONPath .status.discoveredModels[?(@)] is invalid and will not work correctly. It should be .status.modelCount to match the base CRD definition and to display the count of models as an integer.
| - jsonPath: .status.discoveredModels[?(@)] | |
| - jsonPath: .status.modelCount |
| - endpoint | ||
| - secretRef | ||
| - type | ||
| type: object | ||
| x-kubernetes-validations: | ||
| - message: endpoint must be a valid URL starting with http:// or https:// | ||
| rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://') |
There was a problem hiding this comment.
The CRD defines endpoint as a required field (line 96-97), but in the Go API definition (provider_types.go line 77), the Endpoint field is marked as omitempty and has validation rules that explicitly allow it to be empty (line 65: !has(self.endpoint) || self.endpoint == ''). Additionally, the GetEndpoint() method provides default values when the endpoint is not specified.
This creates an inconsistency where the CRD will reject Provider resources without an endpoint, even though the code is designed to support optional endpoints with defaults. The endpoint field should not be in the required list in the CRD.
| - endpoint | |
| - secretRef | |
| - type | |
| type: object | |
| x-kubernetes-validations: | |
| - message: endpoint must be a valid URL starting with http:// or https:// | |
| rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://') | |
| - secretRef | |
| - type | |
| type: object | |
| x-kubernetes-validations: | |
| - message: endpoint must be a valid URL starting with http:// or https:// | |
| rule: "!has(self.endpoint) || self.endpoint == '' || self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')" |
| - endpoint | ||
| - secretRef | ||
| - type | ||
| type: object | ||
| x-kubernetes-validations: | ||
| - message: endpoint must be a valid URL starting with http:// or https:// | ||
| rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://') | ||
| - message: secretRef.name and secretRef.key are required | ||
| rule: has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name) | ||
| > 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0 |
There was a problem hiding this comment.
Same issue as in go/config/crd/bases/kagent.dev_providers.yaml: the endpoint and secretRef fields are marked as required, but the Go API definition treats them as optional with defaults. This will prevent users from creating Provider resources without explicitly specifying an endpoint, even though the code supports defaults via the GetEndpoint() method.
Additionally, secretRef should only be required for providers that need authentication (not Ollama), as indicated by the XValidation rule in the Go type definition.
| - endpoint | |
| - secretRef | |
| - type | |
| type: object | |
| x-kubernetes-validations: | |
| - message: endpoint must be a valid URL starting with http:// or https:// | |
| rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://') | |
| - message: secretRef.name and secretRef.key are required | |
| rule: has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name) | |
| > 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0 | |
| - type | |
| type: object | |
| x-kubernetes-validations: | |
| - message: endpoint must be a valid URL starting with http:// or https:// | |
| rule: !has(self.endpoint) || self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://') | |
| - message: secretRef.name and secretRef.key are required for providers that need authentication (not Ollama) | |
| rule: self.type == 'Ollama' || (has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name) > 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0) |
| func providerReferencesSecret(provider *v1alpha2.Provider, secretObj types.NamespacedName) bool { | ||
| // Secrets must be in the same namespace as the provider | ||
| return provider.Namespace == secretObj.Namespace && | ||
| provider.Spec.SecretRef.Name == secretObj.Name | ||
| } |
There was a problem hiding this comment.
The function providerReferencesSecret directly accesses provider.Spec.SecretRef.Name without checking if SecretRef is nil. Since SecretRef is an optional pointer field (e.g., for Ollama providers that don't require authentication), this will cause a nil pointer dereference panic when processing providers without a SecretRef.
Add a nil check before accessing SecretRef fields.
| export interface TypedLocalReference { | ||
| kind?: string; | ||
| apiGroup?: string; | ||
| name: string; | ||
| namespace?: string; | ||
| } |
There was a problem hiding this comment.
The namespace field was removed from the TypedLocalReference interface, but it is still being accessed in ui/src/app/actions/agents.ts (line 72) and ui/src/lib/toolUtils.ts (line 157). This will cause TypeScript compilation errors or runtime issues.
The code attempts to access agent.namespace and tool.agent.namespace, which no longer exist on the type definition. This breaking change needs to be addressed by either:
- Keeping the
namespacefield inTypedLocalReference, or - Updating all code that references it to use an alternative approach
|
|
||
| fetchProviderModels(); | ||
| return () => { isMounted = false; }; | ||
| }, [selectedProvider, isEditMode, providerModelsData]); |
There was a problem hiding this comment.
The auto-fetch models effect (lines 279-332) includes providerModelsData in its dependency array (line 332). This creates a problematic cycle:
- When a provider is selected, it checks if models exist in
providerModelsData - If not, it fetches models and updates
providerModelsData(lines 308-311, 323) - The update to
providerModelsDatatriggers the effect to run again
This could lead to unnecessary re-renders and potential infinite loops if the hasModels check (line 288) doesn't work as expected. The dependency array should only include selectedProvider and isEditMode, not providerModelsData.
| }, [selectedProvider, isEditMode, providerModelsData]); | |
| }, [selectedProvider, isEditMode]); |
| github.com/jedib0t/go-pretty/v6 v6.7.8 | ||
| github.com/kagent-dev/kmcp v0.2.6 | ||
| github.com/kagent-dev/mockllm v0.0.3 | ||
| github.com/mark3labs/mcp-go v0.33.0 |
There was a problem hiding this comment.
The dependency github.com/mark3labs/mcp-go v0.33.0 was added to go.mod but appears to be unused in the codebase. This adds unnecessary bloat to the dependency tree. If this was added by mistake or for future use, it should be removed. If it's actually needed, there should be import statements using it.
| github.com/mark3labs/mcp-go v0.33.0 |
| * Fix: Remove `providers` from dependency array so effect only runs on mount. | ||
| */ | ||
|
|
||
| import { describe, it, expect, jest } from '@jest/globals'; |
There was a problem hiding this comment.
Unused import jest.
| import { describe, it, expect, jest } from '@jest/globals'; | |
| import { describe, it, expect } from '@jest/globals'; |
| it('demonstrates the bug: user selection gets overwritten', () => { | ||
| const providers = [stockOpenAI, configuredOpenAI]; | ||
|
|
||
| const { result, rerender } = renderHook( |
There was a problem hiding this comment.
Unused variable rerender.
| const { result, rerender } = renderHook( | |
| const { result } = renderHook( |
EItanya
left a comment
There was a problem hiding this comment.
These changes are looking great! I would love to test it all locally but it looks like there are some build failures
|
|
||
| // Provider is the Schema for the providers API. | ||
| // It represents a model provider configuration with automatic model discovery. | ||
| type Provider struct { |
There was a problem hiding this comment.
I think we should rename this to model provider so it's more clear what the purpose is, what do youthink?
There was a problem hiding this comment.
Agree, provider seems too vague, I will change this to modelprovider similar to modelconfig
|
|
||
| // Key is the key within the secret that contains the API key or credential | ||
| // +required | ||
| Key string `json:"key"` |
There was a problem hiding this comment.
Another API nit, why is this required? If there's only one key in the secret we can use that. Just small usability things.
| return ctrl.Result{}, fmt.Errorf("failed to update provider status: %w", err) | ||
| } | ||
|
|
||
| // No periodic requeue - discovery only on-demand via HTTP API |
There was a problem hiding this comment.
llms behind a provider would not change very frequently, this is why I opted to have a force refresh
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com> Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com>
…/nujragan93/kagent into feature/provider-model-discovery Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>


Summary
Introduces a new Provider Custom Resource Definition (CRD) that enables automatic model discovery from LLM providers. Users can configure provider endpoints (e.g., AI gateways, self-hosted LLMs) and kagent will dynamically discover available models.
Changes
Backend (Go)
Frontend (React/Next.js)
Infrastructure
Testing