This commit is contained in:
Raghotham Murthy 2025-10-27 10:52:20 -07:00
parent e21db79d6c
commit 13b6f3df65
12 changed files with 1238 additions and 605 deletions

View file

@ -15,7 +15,7 @@ info:
servers: servers:
- url: http://any-hosted-llama-stack.com - url: http://any-hosted-llama-stack.com
paths: paths:
/v1/admin/providers: /v1/admin/providers/{api}:
post: post:
responses: responses:
'200': '200':
@ -44,7 +44,14 @@ paths:
Register a new provider instance at runtime. The provider will be validated, Register a new provider instance at runtime. The provider will be validated,
instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions. instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.
parameters: [] parameters:
- name: api
in: path
description: >-
API namespace this provider implements (e.g., 'inference', 'vector_io').
required: true
schema:
type: string
requestBody: requestBody:
content: content:
application/json: application/json:
@ -52,7 +59,7 @@ paths:
$ref: '#/components/schemas/RegisterProviderRequest' $ref: '#/components/schemas/RegisterProviderRequest'
required: true required: true
deprecated: false deprecated: false
/v1/admin/providers/{provider_id}: /v1/admin/providers/{api}/{provider_id}:
post: post:
responses: responses:
'200': '200':
@ -81,10 +88,14 @@ paths:
Update the configuration and/or attributes of a dynamic provider. The provider Update the configuration and/or attributes of a dynamic provider. The provider
will be re-instantiated with the new configuration (hot-reload). Static providers will be re-instantiated with the new configuration (hot-reload).
from run.yaml cannot be updated.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to update description: ID of the provider to update
@ -120,8 +131,14 @@ paths:
Remove a dynamic provider, shutting down its instance and removing it from Remove a dynamic provider, shutting down its instance and removing it from
the kvstore. Static providers from run.yaml cannot be unregistered. the kvstore.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to unregister. description: ID of the provider to unregister.
@ -129,6 +146,47 @@ paths:
schema: schema:
type: string type: string
deprecated: false deprecated: false
/v1/admin/providers/{api}/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
parameters:
- name: api
in: path
description: API namespace the provider implements.
required: true
schema:
type: string
- name: provider_id
in: path
description: ID of the provider to test.
required: true
schema:
type: string
deprecated: false
/v1/chat/completions: /v1/chat/completions:
get: get:
responses: responses:
@ -1368,7 +1426,43 @@ paths:
List all available providers. List all available providers.
parameters: [] parameters: []
deprecated: false deprecated: false
/v1/providers/{provider_id}: /v1/providers/{api}:
get:
responses:
'200':
description: >-
A ListProvidersResponse containing providers for the specified API.
content:
application/json:
schema:
$ref: '#/components/schemas/ListProvidersResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: List providers for a specific API.
description: >-
List providers for a specific API.
List all providers that implement a specific API.
parameters:
- name: api
in: path
description: >-
The API namespace to filter by (e.g., 'inference', 'vector_io')
required: true
schema:
type: string
deprecated: false
/v1/providers/{api}/{provider_id}:
get: get:
responses: responses:
'200': '200':
@ -1390,52 +1484,21 @@ paths:
$ref: '#/components/responses/DefaultError' $ref: '#/components/responses/DefaultError'
tags: tags:
- Providers - Providers
summary: Get provider. summary: Get provider for specific API.
description: >- description: >-
Get provider. Get provider for specific API.
Get detailed information about a specific provider. Get detailed information about a specific provider for a specific API.
parameters: parameters:
- name: provider_id - name: api
in: path in: path
description: The ID of the provider to inspect. description: The API namespace.
required: true required: true
schema: schema:
type: string type: string
deprecated: false
/v1/providers/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
Works for both static and dynamic providers.
parameters:
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to test. description: The ID of the provider to inspect.
required: true required: true
schema: schema:
type: string type: string
@ -4370,9 +4433,6 @@ components:
type: string type: string
description: >- description: >-
Unique identifier for this provider instance. Unique identifier for this provider instance.
api:
type: string
description: API namespace this provider implements.
provider_type: provider_type:
type: string type: string
description: Provider type (e.g., 'remote::openai'). description: Provider type (e.g., 'remote::openai').
@ -4399,7 +4459,6 @@ components:
additionalProperties: false additionalProperties: false
required: required:
- provider_id - provider_id
- api
- provider_type - provider_type
- config - config
title: RegisterProviderRequest title: RegisterProviderRequest
@ -4608,6 +4667,32 @@ components:
- provider - provider
title: UpdateProviderResponse title: UpdateProviderResponse
description: Response after updating a provider. description: Response after updating a provider.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
Order: Order:
type: string type: string
enum: enum:
@ -7076,32 +7161,6 @@ components:
title: ListProvidersResponse title: ListProvidersResponse
description: >- description: >-
Response containing a list of all available providers. Response containing a list of all available providers.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
ListOpenAIResponseObject: ListOpenAIResponseObject:
type: object type: object
properties: properties:

View file

@ -3526,6 +3526,51 @@
}, },
"deprecated": true "deprecated": true
} }
},
"/v1/providers/{provider_id}": {
"get": {
"responses": {
"200": {
"description": "A ListProvidersResponse containing all providers with matching provider_id.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListProvidersResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "Get providers by ID (deprecated - use /providers/{api}/{provider_id} instead).",
"description": "Get providers by ID (deprecated - use /providers/{api}/{provider_id} instead).\nDEPRECATED: Returns all providers with the given provider_id across all APIs.\nThis can return multiple providers if the same ID is used for different APIs.\nUse /providers/{api}/{provider_id} for unambiguous access.",
"parameters": [
{
"name": "provider_id",
"in": "path",
"description": "The ID of the provider(s) to inspect.",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": true
}
} }
}, },
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
@ -13350,6 +13395,103 @@
"logger_config" "logger_config"
], ],
"title": "SupervisedFineTuneRequest" "title": "SupervisedFineTuneRequest"
},
"ProviderInfo": {
"type": "object",
"properties": {
"api": {
"type": "string",
"description": "The API name this provider implements"
},
"provider_id": {
"type": "string",
"description": "Unique identifier for the provider"
},
"provider_type": {
"type": "string",
"description": "The type of provider implementation"
},
"config": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Configuration parameters for the provider"
},
"health": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Current health status of the provider"
}
},
"additionalProperties": false,
"required": [
"api",
"provider_id",
"provider_type",
"config",
"health"
],
"title": "ProviderInfo",
"description": "Information about a registered provider including its configuration and health status."
},
"ListProvidersResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProviderInfo"
},
"description": "List of provider information objects"
}
},
"additionalProperties": false,
"required": [
"data"
],
"title": "ListProvidersResponse",
"description": "Response containing a list of all available providers."
} }
}, },
"responses": { "responses": {
@ -13461,6 +13603,11 @@
"name": "PostTraining (Coming Soon)", "name": "PostTraining (Coming Soon)",
"description": "" "description": ""
}, },
{
"name": "Providers",
"description": "Providers API for inspecting, listing, and modifying providers and their configurations.",
"x-displayName": "Providers"
},
{ {
"name": "Safety", "name": "Safety",
"description": "OpenAI-compatible Moderations API.", "description": "OpenAI-compatible Moderations API.",
@ -13484,6 +13631,7 @@
"Inference", "Inference",
"Models", "Models",
"PostTraining (Coming Soon)", "PostTraining (Coming Soon)",
"Providers",
"Safety", "Safety",
"VectorIO" "VectorIO"
] ]

View file

@ -2600,6 +2600,46 @@ paths:
$ref: '#/components/schemas/SupervisedFineTuneRequest' $ref: '#/components/schemas/SupervisedFineTuneRequest'
required: true required: true
deprecated: true deprecated: true
/v1/providers/{provider_id}:
get:
responses:
'200':
description: >-
A ListProvidersResponse containing all providers with matching provider_id.
content:
application/json:
schema:
$ref: '#/components/schemas/ListProvidersResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: >-
Get providers by ID (deprecated - use /providers/{api}/{provider_id} instead).
description: >-
Get providers by ID (deprecated - use /providers/{api}/{provider_id} instead).
DEPRECATED: Returns all providers with the given provider_id across all APIs.
This can return multiple providers if the same ID is used for different APIs.
Use /providers/{api}/{provider_id} for unambiguous access.
parameters:
- name: provider_id
in: path
description: The ID of the provider(s) to inspect.
required: true
schema:
type: string
deprecated: true
jsonSchemaDialect: >- jsonSchemaDialect: >-
https://json-schema.org/draft/2020-12/schema https://json-schema.org/draft/2020-12/schema
components: components:
@ -10121,6 +10161,66 @@ components:
- hyperparam_search_config - hyperparam_search_config
- logger_config - logger_config
title: SupervisedFineTuneRequest title: SupervisedFineTuneRequest
ProviderInfo:
type: object
properties:
api:
type: string
description: The API name this provider implements
provider_id:
type: string
description: Unique identifier for the provider
provider_type:
type: string
description: The type of provider implementation
config:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: >-
Configuration parameters for the provider
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Current health status of the provider
additionalProperties: false
required:
- api
- provider_id
- provider_type
- config
- health
title: ProviderInfo
description: >-
Information about a registered provider including its configuration and health
status.
ListProvidersResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/ProviderInfo'
description: List of provider information objects
additionalProperties: false
required:
- data
title: ListProvidersResponse
description: >-
Response containing a list of all available providers.
responses: responses:
BadRequest400: BadRequest400:
description: The request was invalid or malformed description: The request was invalid or malformed
@ -10226,6 +10326,10 @@ tags:
description: '' description: ''
- name: PostTraining (Coming Soon) - name: PostTraining (Coming Soon)
description: '' description: ''
- name: Providers
description: >-
Providers API for inspecting, listing, and modifying providers and their configurations.
x-displayName: Providers
- name: Safety - name: Safety
description: OpenAI-compatible Moderations API. description: OpenAI-compatible Moderations API.
x-displayName: Safety x-displayName: Safety
@ -10243,5 +10347,6 @@ x-tagGroups:
- Inference - Inference
- Models - Models
- PostTraining (Coming Soon) - PostTraining (Coming Soon)
- Providers
- Safety - Safety
- VectorIO - VectorIO

View file

@ -40,7 +40,7 @@
} }
], ],
"paths": { "paths": {
"/v1/admin/providers": { "/v1/admin/providers/{api}": {
"post": { "post": {
"responses": { "responses": {
"200": { "200": {
@ -71,7 +71,17 @@
], ],
"summary": "Register a new dynamic provider.", "summary": "Register a new dynamic provider.",
"description": "Register a new dynamic provider.\nRegister a new provider instance at runtime. The provider will be validated,\ninstantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.", "description": "Register a new dynamic provider.\nRegister a new provider instance at runtime. The provider will be validated,\ninstantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.",
"parameters": [], "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace this provider implements (e.g., 'inference', 'vector_io').",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": { "requestBody": {
"content": { "content": {
"application/json": { "application/json": {
@ -85,7 +95,7 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/admin/providers/{provider_id}": { "/v1/admin/providers/{api}/{provider_id}": {
"post": { "post": {
"responses": { "responses": {
"200": { "200": {
@ -115,8 +125,17 @@
"Providers" "Providers"
], ],
"summary": "Update an existing provider's configuration.", "summary": "Update an existing provider's configuration.",
"description": "Update an existing provider's configuration.\nUpdate the configuration and/or attributes of a dynamic provider. The provider\nwill be re-instantiated with the new configuration (hot-reload). Static providers\nfrom run.yaml cannot be updated.", "description": "Update an existing provider's configuration.\nUpdate the configuration and/or attributes of a dynamic provider. The provider\nwill be re-instantiated with the new configuration (hot-reload).",
"parameters": [ "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements",
"required": true,
"schema": {
"type": "string"
}
},
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
@ -161,8 +180,17 @@
"Providers" "Providers"
], ],
"summary": "Unregister a dynamic provider.", "summary": "Unregister a dynamic provider.",
"description": "Unregister a dynamic provider.\nRemove a dynamic provider, shutting down its instance and removing it from\nthe kvstore. Static providers from run.yaml cannot be unregistered.", "description": "Unregister a dynamic provider.\nRemove a dynamic provider, shutting down its instance and removing it from\nthe kvstore.",
"parameters": [ "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements",
"required": true,
"schema": {
"type": "string"
}
},
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
@ -176,6 +204,60 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/admin/providers/{api}/{provider_id}/test": {
"post": {
"responses": {
"200": {
"description": "TestProviderConnectionResponse with health status.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestProviderConnectionResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "Test a provider connection.",
"description": "Test a provider connection.\nExecute a health check on a provider to verify it is reachable and functioning.",
"parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "provider_id",
"in": "path",
"description": "ID of the provider to test.",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": false
}
},
"/v1/chat/completions": { "/v1/chat/completions": {
"get": { "get": {
"responses": { "responses": {
@ -1771,7 +1853,52 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/providers/{provider_id}": { "/v1/providers/{api}": {
"get": {
"responses": {
"200": {
"description": "A ListProvidersResponse containing providers for the specified API.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListProvidersResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "List providers for a specific API.",
"description": "List providers for a specific API.\nList all providers that implement a specific API.",
"parameters": [
{
"name": "api",
"in": "path",
"description": "The API namespace to filter by (e.g., 'inference', 'vector_io')",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": false
}
},
"/v1/providers/{api}/{provider_id}": {
"get": { "get": {
"responses": { "responses": {
"200": { "200": {
@ -1800,58 +1927,22 @@
"tags": [ "tags": [
"Providers" "Providers"
], ],
"summary": "Get provider.", "summary": "Get provider for specific API.",
"description": "Get provider.\nGet detailed information about a specific provider.", "description": "Get provider for specific API.\nGet detailed information about a specific provider for a specific API.",
"parameters": [ "parameters": [
{ {
"name": "provider_id", "name": "api",
"in": "path", "in": "path",
"description": "The ID of the provider to inspect.", "description": "The API namespace.",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} }
}
],
"deprecated": false
}
}, },
"/v1/providers/{provider_id}/test": {
"post": {
"responses": {
"200": {
"description": "TestProviderConnectionResponse with health status.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestProviderConnectionResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "Test a provider connection.",
"description": "Test a provider connection.\nExecute a health check on a provider to verify it is reachable and functioning.\nWorks for both static and dynamic providers.",
"parameters": [
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
"description": "ID of the provider to test.", "description": "The ID of the provider to inspect.",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
@ -4193,10 +4284,6 @@
"type": "string", "type": "string",
"description": "Unique identifier for this provider instance." "description": "Unique identifier for this provider instance."
}, },
"api": {
"type": "string",
"description": "API namespace this provider implements."
},
"provider_type": { "provider_type": {
"type": "string", "type": "string",
"description": "Provider type (e.g., 'remote::openai')." "description": "Provider type (e.g., 'remote::openai')."
@ -4241,7 +4328,6 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"provider_id", "provider_id",
"api",
"provider_type", "provider_type",
"config" "config"
], ],
@ -4531,6 +4617,51 @@
"title": "UpdateProviderResponse", "title": "UpdateProviderResponse",
"description": "Response after updating a provider." "description": "Response after updating a provider."
}, },
"TestProviderConnectionResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the connection test succeeded"
},
"health": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Health status from the provider"
},
"error_message": {
"type": "string",
"description": "Error message if test failed"
}
},
"additionalProperties": false,
"required": [
"success"
],
"title": "TestProviderConnectionResponse",
"description": "Response from testing a provider connection."
},
"Order": { "Order": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -7768,51 +7899,6 @@
"title": "ListProvidersResponse", "title": "ListProvidersResponse",
"description": "Response containing a list of all available providers." "description": "Response containing a list of all available providers."
}, },
"TestProviderConnectionResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the connection test succeeded"
},
"health": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Health status from the provider"
},
"error_message": {
"type": "string",
"description": "Error message if test failed"
}
},
"additionalProperties": false,
"required": [
"success"
],
"title": "TestProviderConnectionResponse",
"description": "Response from testing a provider connection."
},
"ListOpenAIResponseObject": { "ListOpenAIResponseObject": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -12,7 +12,7 @@ info:
servers: servers:
- url: http://any-hosted-llama-stack.com - url: http://any-hosted-llama-stack.com
paths: paths:
/v1/admin/providers: /v1/admin/providers/{api}:
post: post:
responses: responses:
'200': '200':
@ -41,7 +41,14 @@ paths:
Register a new provider instance at runtime. The provider will be validated, Register a new provider instance at runtime. The provider will be validated,
instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions. instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.
parameters: [] parameters:
- name: api
in: path
description: >-
API namespace this provider implements (e.g., 'inference', 'vector_io').
required: true
schema:
type: string
requestBody: requestBody:
content: content:
application/json: application/json:
@ -49,7 +56,7 @@ paths:
$ref: '#/components/schemas/RegisterProviderRequest' $ref: '#/components/schemas/RegisterProviderRequest'
required: true required: true
deprecated: false deprecated: false
/v1/admin/providers/{provider_id}: /v1/admin/providers/{api}/{provider_id}:
post: post:
responses: responses:
'200': '200':
@ -78,10 +85,14 @@ paths:
Update the configuration and/or attributes of a dynamic provider. The provider Update the configuration and/or attributes of a dynamic provider. The provider
will be re-instantiated with the new configuration (hot-reload). Static providers will be re-instantiated with the new configuration (hot-reload).
from run.yaml cannot be updated.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to update description: ID of the provider to update
@ -117,8 +128,14 @@ paths:
Remove a dynamic provider, shutting down its instance and removing it from Remove a dynamic provider, shutting down its instance and removing it from
the kvstore. Static providers from run.yaml cannot be unregistered. the kvstore.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to unregister. description: ID of the provider to unregister.
@ -126,6 +143,47 @@ paths:
schema: schema:
type: string type: string
deprecated: false deprecated: false
/v1/admin/providers/{api}/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
parameters:
- name: api
in: path
description: API namespace the provider implements.
required: true
schema:
type: string
- name: provider_id
in: path
description: ID of the provider to test.
required: true
schema:
type: string
deprecated: false
/v1/chat/completions: /v1/chat/completions:
get: get:
responses: responses:
@ -1365,7 +1423,43 @@ paths:
List all available providers. List all available providers.
parameters: [] parameters: []
deprecated: false deprecated: false
/v1/providers/{provider_id}: /v1/providers/{api}:
get:
responses:
'200':
description: >-
A ListProvidersResponse containing providers for the specified API.
content:
application/json:
schema:
$ref: '#/components/schemas/ListProvidersResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: List providers for a specific API.
description: >-
List providers for a specific API.
List all providers that implement a specific API.
parameters:
- name: api
in: path
description: >-
The API namespace to filter by (e.g., 'inference', 'vector_io')
required: true
schema:
type: string
deprecated: false
/v1/providers/{api}/{provider_id}:
get: get:
responses: responses:
'200': '200':
@ -1387,52 +1481,21 @@ paths:
$ref: '#/components/responses/DefaultError' $ref: '#/components/responses/DefaultError'
tags: tags:
- Providers - Providers
summary: Get provider. summary: Get provider for specific API.
description: >- description: >-
Get provider. Get provider for specific API.
Get detailed information about a specific provider. Get detailed information about a specific provider for a specific API.
parameters: parameters:
- name: provider_id - name: api
in: path in: path
description: The ID of the provider to inspect. description: The API namespace.
required: true required: true
schema: schema:
type: string type: string
deprecated: false
/v1/providers/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
Works for both static and dynamic providers.
parameters:
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to test. description: The ID of the provider to inspect.
required: true required: true
schema: schema:
type: string type: string
@ -3157,9 +3220,6 @@ components:
type: string type: string
description: >- description: >-
Unique identifier for this provider instance. Unique identifier for this provider instance.
api:
type: string
description: API namespace this provider implements.
provider_type: provider_type:
type: string type: string
description: Provider type (e.g., 'remote::openai'). description: Provider type (e.g., 'remote::openai').
@ -3186,7 +3246,6 @@ components:
additionalProperties: false additionalProperties: false
required: required:
- provider_id - provider_id
- api
- provider_type - provider_type
- config - config
title: RegisterProviderRequest title: RegisterProviderRequest
@ -3395,6 +3454,32 @@ components:
- provider - provider
title: UpdateProviderResponse title: UpdateProviderResponse
description: Response after updating a provider. description: Response after updating a provider.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
Order: Order:
type: string type: string
enum: enum:
@ -5863,32 +5948,6 @@ components:
title: ListProvidersResponse title: ListProvidersResponse
description: >- description: >-
Response containing a list of all available providers. Response containing a list of all available providers.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
ListOpenAIResponseObject: ListOpenAIResponseObject:
type: object type: object
properties: properties:

View file

@ -40,7 +40,7 @@
} }
], ],
"paths": { "paths": {
"/v1/admin/providers": { "/v1/admin/providers/{api}": {
"post": { "post": {
"responses": { "responses": {
"200": { "200": {
@ -71,7 +71,17 @@
], ],
"summary": "Register a new dynamic provider.", "summary": "Register a new dynamic provider.",
"description": "Register a new dynamic provider.\nRegister a new provider instance at runtime. The provider will be validated,\ninstantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.", "description": "Register a new dynamic provider.\nRegister a new provider instance at runtime. The provider will be validated,\ninstantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.",
"parameters": [], "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace this provider implements (e.g., 'inference', 'vector_io').",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": { "requestBody": {
"content": { "content": {
"application/json": { "application/json": {
@ -85,7 +95,7 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/admin/providers/{provider_id}": { "/v1/admin/providers/{api}/{provider_id}": {
"post": { "post": {
"responses": { "responses": {
"200": { "200": {
@ -115,8 +125,17 @@
"Providers" "Providers"
], ],
"summary": "Update an existing provider's configuration.", "summary": "Update an existing provider's configuration.",
"description": "Update an existing provider's configuration.\nUpdate the configuration and/or attributes of a dynamic provider. The provider\nwill be re-instantiated with the new configuration (hot-reload). Static providers\nfrom run.yaml cannot be updated.", "description": "Update an existing provider's configuration.\nUpdate the configuration and/or attributes of a dynamic provider. The provider\nwill be re-instantiated with the new configuration (hot-reload).",
"parameters": [ "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements",
"required": true,
"schema": {
"type": "string"
}
},
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
@ -161,8 +180,17 @@
"Providers" "Providers"
], ],
"summary": "Unregister a dynamic provider.", "summary": "Unregister a dynamic provider.",
"description": "Unregister a dynamic provider.\nRemove a dynamic provider, shutting down its instance and removing it from\nthe kvstore. Static providers from run.yaml cannot be unregistered.", "description": "Unregister a dynamic provider.\nRemove a dynamic provider, shutting down its instance and removing it from\nthe kvstore.",
"parameters": [ "parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements",
"required": true,
"schema": {
"type": "string"
}
},
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
@ -176,6 +204,60 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/admin/providers/{api}/{provider_id}/test": {
"post": {
"responses": {
"200": {
"description": "TestProviderConnectionResponse with health status.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestProviderConnectionResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "Test a provider connection.",
"description": "Test a provider connection.\nExecute a health check on a provider to verify it is reachable and functioning.",
"parameters": [
{
"name": "api",
"in": "path",
"description": "API namespace the provider implements.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "provider_id",
"in": "path",
"description": "ID of the provider to test.",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": false
}
},
"/v1/chat/completions": { "/v1/chat/completions": {
"get": { "get": {
"responses": { "responses": {
@ -1771,7 +1853,52 @@
"deprecated": false "deprecated": false
} }
}, },
"/v1/providers/{provider_id}": { "/v1/providers/{api}": {
"get": {
"responses": {
"200": {
"description": "A ListProvidersResponse containing providers for the specified API.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ListProvidersResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "List providers for a specific API.",
"description": "List providers for a specific API.\nList all providers that implement a specific API.",
"parameters": [
{
"name": "api",
"in": "path",
"description": "The API namespace to filter by (e.g., 'inference', 'vector_io')",
"required": true,
"schema": {
"type": "string"
}
}
],
"deprecated": false
}
},
"/v1/providers/{api}/{provider_id}": {
"get": { "get": {
"responses": { "responses": {
"200": { "200": {
@ -1800,58 +1927,22 @@
"tags": [ "tags": [
"Providers" "Providers"
], ],
"summary": "Get provider.", "summary": "Get provider for specific API.",
"description": "Get provider.\nGet detailed information about a specific provider.", "description": "Get provider for specific API.\nGet detailed information about a specific provider for a specific API.",
"parameters": [ "parameters": [
{ {
"name": "provider_id", "name": "api",
"in": "path", "in": "path",
"description": "The ID of the provider to inspect.", "description": "The API namespace.",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} }
}
],
"deprecated": false
}
}, },
"/v1/providers/{provider_id}/test": {
"post": {
"responses": {
"200": {
"description": "TestProviderConnectionResponse with health status.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestProviderConnectionResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest400"
},
"429": {
"$ref": "#/components/responses/TooManyRequests429"
},
"500": {
"$ref": "#/components/responses/InternalServerError500"
},
"default": {
"$ref": "#/components/responses/DefaultError"
}
},
"tags": [
"Providers"
],
"summary": "Test a provider connection.",
"description": "Test a provider connection.\nExecute a health check on a provider to verify it is reachable and functioning.\nWorks for both static and dynamic providers.",
"parameters": [
{ {
"name": "provider_id", "name": "provider_id",
"in": "path", "in": "path",
"description": "ID of the provider to test.", "description": "The ID of the provider to inspect.",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
@ -5865,10 +5956,6 @@
"type": "string", "type": "string",
"description": "Unique identifier for this provider instance." "description": "Unique identifier for this provider instance."
}, },
"api": {
"type": "string",
"description": "API namespace this provider implements."
},
"provider_type": { "provider_type": {
"type": "string", "type": "string",
"description": "Provider type (e.g., 'remote::openai')." "description": "Provider type (e.g., 'remote::openai')."
@ -5913,7 +6000,6 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"provider_id", "provider_id",
"api",
"provider_type", "provider_type",
"config" "config"
], ],
@ -6203,6 +6289,51 @@
"title": "UpdateProviderResponse", "title": "UpdateProviderResponse",
"description": "Response after updating a provider." "description": "Response after updating a provider."
}, },
"TestProviderConnectionResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the connection test succeeded"
},
"health": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Health status from the provider"
},
"error_message": {
"type": "string",
"description": "Error message if test failed"
}
},
"additionalProperties": false,
"required": [
"success"
],
"title": "TestProviderConnectionResponse",
"description": "Response from testing a provider connection."
},
"Order": { "Order": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -9440,51 +9571,6 @@
"title": "ListProvidersResponse", "title": "ListProvidersResponse",
"description": "Response containing a list of all available providers." "description": "Response containing a list of all available providers."
}, },
"TestProviderConnectionResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"description": "Whether the connection test succeeded"
},
"health": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "null"
},
{
"type": "boolean"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "array"
},
{
"type": "object"
}
]
},
"description": "Health status from the provider"
},
"error_message": {
"type": "string",
"description": "Error message if test failed"
}
},
"additionalProperties": false,
"required": [
"success"
],
"title": "TestProviderConnectionResponse",
"description": "Response from testing a provider connection."
},
"ListOpenAIResponseObject": { "ListOpenAIResponseObject": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -15,7 +15,7 @@ info:
servers: servers:
- url: http://any-hosted-llama-stack.com - url: http://any-hosted-llama-stack.com
paths: paths:
/v1/admin/providers: /v1/admin/providers/{api}:
post: post:
responses: responses:
'200': '200':
@ -44,7 +44,14 @@ paths:
Register a new provider instance at runtime. The provider will be validated, Register a new provider instance at runtime. The provider will be validated,
instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions. instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.
parameters: [] parameters:
- name: api
in: path
description: >-
API namespace this provider implements (e.g., 'inference', 'vector_io').
required: true
schema:
type: string
requestBody: requestBody:
content: content:
application/json: application/json:
@ -52,7 +59,7 @@ paths:
$ref: '#/components/schemas/RegisterProviderRequest' $ref: '#/components/schemas/RegisterProviderRequest'
required: true required: true
deprecated: false deprecated: false
/v1/admin/providers/{provider_id}: /v1/admin/providers/{api}/{provider_id}:
post: post:
responses: responses:
'200': '200':
@ -81,10 +88,14 @@ paths:
Update the configuration and/or attributes of a dynamic provider. The provider Update the configuration and/or attributes of a dynamic provider. The provider
will be re-instantiated with the new configuration (hot-reload). Static providers will be re-instantiated with the new configuration (hot-reload).
from run.yaml cannot be updated.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to update description: ID of the provider to update
@ -120,8 +131,14 @@ paths:
Remove a dynamic provider, shutting down its instance and removing it from Remove a dynamic provider, shutting down its instance and removing it from
the kvstore. Static providers from run.yaml cannot be unregistered. the kvstore.
parameters: parameters:
- name: api
in: path
description: API namespace the provider implements
required: true
schema:
type: string
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to unregister. description: ID of the provider to unregister.
@ -129,6 +146,47 @@ paths:
schema: schema:
type: string type: string
deprecated: false deprecated: false
/v1/admin/providers/{api}/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
parameters:
- name: api
in: path
description: API namespace the provider implements.
required: true
schema:
type: string
- name: provider_id
in: path
description: ID of the provider to test.
required: true
schema:
type: string
deprecated: false
/v1/chat/completions: /v1/chat/completions:
get: get:
responses: responses:
@ -1368,7 +1426,43 @@ paths:
List all available providers. List all available providers.
parameters: [] parameters: []
deprecated: false deprecated: false
/v1/providers/{provider_id}: /v1/providers/{api}:
get:
responses:
'200':
description: >-
A ListProvidersResponse containing providers for the specified API.
content:
application/json:
schema:
$ref: '#/components/schemas/ListProvidersResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: List providers for a specific API.
description: >-
List providers for a specific API.
List all providers that implement a specific API.
parameters:
- name: api
in: path
description: >-
The API namespace to filter by (e.g., 'inference', 'vector_io')
required: true
schema:
type: string
deprecated: false
/v1/providers/{api}/{provider_id}:
get: get:
responses: responses:
'200': '200':
@ -1390,52 +1484,21 @@ paths:
$ref: '#/components/responses/DefaultError' $ref: '#/components/responses/DefaultError'
tags: tags:
- Providers - Providers
summary: Get provider. summary: Get provider for specific API.
description: >- description: >-
Get provider. Get provider for specific API.
Get detailed information about a specific provider. Get detailed information about a specific provider for a specific API.
parameters: parameters:
- name: provider_id - name: api
in: path in: path
description: The ID of the provider to inspect. description: The API namespace.
required: true required: true
schema: schema:
type: string type: string
deprecated: false
/v1/providers/{provider_id}/test:
post:
responses:
'200':
description: >-
TestProviderConnectionResponse with health status.
content:
application/json:
schema:
$ref: '#/components/schemas/TestProviderConnectionResponse'
'400':
$ref: '#/components/responses/BadRequest400'
'429':
$ref: >-
#/components/responses/TooManyRequests429
'500':
$ref: >-
#/components/responses/InternalServerError500
default:
$ref: '#/components/responses/DefaultError'
tags:
- Providers
summary: Test a provider connection.
description: >-
Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning.
Works for both static and dynamic providers.
parameters:
- name: provider_id - name: provider_id
in: path in: path
description: ID of the provider to test. description: The ID of the provider to inspect.
required: true required: true
schema: schema:
type: string type: string
@ -4370,9 +4433,6 @@ components:
type: string type: string
description: >- description: >-
Unique identifier for this provider instance. Unique identifier for this provider instance.
api:
type: string
description: API namespace this provider implements.
provider_type: provider_type:
type: string type: string
description: Provider type (e.g., 'remote::openai'). description: Provider type (e.g., 'remote::openai').
@ -4399,7 +4459,6 @@ components:
additionalProperties: false additionalProperties: false
required: required:
- provider_id - provider_id
- api
- provider_type - provider_type
- config - config
title: RegisterProviderRequest title: RegisterProviderRequest
@ -4608,6 +4667,32 @@ components:
- provider - provider
title: UpdateProviderResponse title: UpdateProviderResponse
description: Response after updating a provider. description: Response after updating a provider.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
Order: Order:
type: string type: string
enum: enum:
@ -7076,32 +7161,6 @@ components:
title: ListProvidersResponse title: ListProvidersResponse
description: >- description: >-
Response containing a list of all available providers. Response containing a list of all available providers.
TestProviderConnectionResponse:
type: object
properties:
success:
type: boolean
description: Whether the connection test succeeded
health:
type: object
additionalProperties:
oneOf:
- type: 'null'
- type: boolean
- type: number
- type: string
- type: array
- type: object
description: Health status from the provider
error_message:
type: string
description: Error message if test failed
additionalProperties: false
required:
- success
title: TestProviderConnectionResponse
description: >-
Response from testing a provider connection.
ListOpenAIResponseObject: ListOpenAIResponseObject:
type: object type: object
properties: properties:

View file

@ -137,24 +137,26 @@ class Providers(Protocol):
""" """
... ...
@webmethod(route="/providers/{provider_id}", method="GET", level=LLAMA_STACK_API_V1) @webmethod(route="/providers/{provider_id}", method="GET", level=LLAMA_STACK_API_V1, deprecated=True)
async def inspect_provider(self, provider_id: str) -> ProviderInfo: async def inspect_provider(self, provider_id: str) -> ListProvidersResponse:
"""Get provider. """Get providers by ID (deprecated - use /providers/{api}/{provider_id} instead).
Get detailed information about a specific provider. DEPRECATED: Returns all providers with the given provider_id across all APIs.
This can return multiple providers if the same ID is used for different APIs.
Use /providers/{api}/{provider_id} for unambiguous access.
:param provider_id: The ID of the provider to inspect. :param provider_id: The ID of the provider(s) to inspect.
:returns: A ProviderInfo object containing the provider's details. :returns: A ListProvidersResponse containing all providers with matching provider_id.
""" """
... ...
# ===== Dynamic Provider Management Methods ===== # ===== Dynamic Provider Management Methods =====
@webmethod(route="/admin/providers", method="POST", level=LLAMA_STACK_API_V1) @webmethod(route="/admin/providers/{api}", method="POST", level=LLAMA_STACK_API_V1)
async def register_provider( async def register_provider(
self, self,
provider_id: str,
api: str, api: str,
provider_id: str,
provider_type: str, provider_type: str,
config: dict[str, Any], config: dict[str, Any],
attributes: dict[str, list[str]] | None = None, attributes: dict[str, list[str]] | None = None,
@ -164,8 +166,8 @@ class Providers(Protocol):
Register a new provider instance at runtime. The provider will be validated, Register a new provider instance at runtime. The provider will be validated,
instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions. instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.
:param api: API namespace this provider implements (e.g., 'inference', 'vector_io').
:param provider_id: Unique identifier for this provider instance. :param provider_id: Unique identifier for this provider instance.
:param api: API namespace this provider implements.
:param provider_type: Provider type (e.g., 'remote::openai'). :param provider_type: Provider type (e.g., 'remote::openai').
:param config: Provider configuration (API keys, endpoints, etc.). :param config: Provider configuration (API keys, endpoints, etc.).
:param attributes: Optional attributes for ABAC access control. :param attributes: Optional attributes for ABAC access control.
@ -173,9 +175,10 @@ class Providers(Protocol):
""" """
... ...
@webmethod(route="/admin/providers/{provider_id}", method="PUT", level=LLAMA_STACK_API_V1) @webmethod(route="/admin/providers/{api}/{provider_id}", method="PUT", level=LLAMA_STACK_API_V1)
async def update_provider( async def update_provider(
self, self,
api: str,
provider_id: str, provider_id: str,
config: dict[str, Any] | None = None, config: dict[str, Any] | None = None,
attributes: dict[str, list[str]] | None = None, attributes: dict[str, list[str]] | None = None,
@ -183,9 +186,9 @@ class Providers(Protocol):
"""Update an existing provider's configuration. """Update an existing provider's configuration.
Update the configuration and/or attributes of a dynamic provider. The provider Update the configuration and/or attributes of a dynamic provider. The provider
will be re-instantiated with the new configuration (hot-reload). Static providers will be re-instantiated with the new configuration (hot-reload).
from run.yaml cannot be updated.
:param api: API namespace the provider implements
:param provider_id: ID of the provider to update :param provider_id: ID of the provider to update
:param config: New configuration parameters (merged with existing) :param config: New configuration parameters (merged with existing)
:param attributes: New attributes for access control :param attributes: New attributes for access control
@ -193,25 +196,49 @@ class Providers(Protocol):
""" """
... ...
@webmethod(route="/admin/providers/{provider_id}", method="DELETE", level=LLAMA_STACK_API_V1) @webmethod(route="/admin/providers/{api}/{provider_id}", method="DELETE", level=LLAMA_STACK_API_V1)
async def unregister_provider(self, provider_id: str) -> None: async def unregister_provider(self, api: str, provider_id: str) -> None:
"""Unregister a dynamic provider. """Unregister a dynamic provider.
Remove a dynamic provider, shutting down its instance and removing it from Remove a dynamic provider, shutting down its instance and removing it from
the kvstore. Static providers from run.yaml cannot be unregistered. the kvstore.
:param api: API namespace the provider implements
:param provider_id: ID of the provider to unregister. :param provider_id: ID of the provider to unregister.
""" """
... ...
@webmethod(route="/providers/{provider_id}/test", method="POST", level=LLAMA_STACK_API_V1) @webmethod(route="/admin/providers/{api}/{provider_id}/test", method="POST", level=LLAMA_STACK_API_V1)
async def test_provider_connection(self, provider_id: str) -> TestProviderConnectionResponse: async def test_provider_connection(self, api: str, provider_id: str) -> TestProviderConnectionResponse:
"""Test a provider connection. """Test a provider connection.
Execute a health check on a provider to verify it is reachable and functioning. Execute a health check on a provider to verify it is reachable and functioning.
Works for both static and dynamic providers.
:param api: API namespace the provider implements.
:param provider_id: ID of the provider to test. :param provider_id: ID of the provider to test.
:returns: TestProviderConnectionResponse with health status. :returns: TestProviderConnectionResponse with health status.
""" """
... ...
@webmethod(route="/providers/{api}", method="GET", level=LLAMA_STACK_API_V1)
async def list_providers_for_api(self, api: str) -> ListProvidersResponse:
"""List providers for a specific API.
List all providers that implement a specific API.
:param api: The API namespace to filter by (e.g., 'inference', 'vector_io')
:returns: A ListProvidersResponse containing providers for the specified API.
"""
...
@webmethod(route="/providers/{api}/{provider_id}", method="GET", level=LLAMA_STACK_API_V1)
async def inspect_provider_for_api(self, api: str, provider_id: str) -> ProviderInfo:
"""Get provider for specific API.
Get detailed information about a specific provider for a specific API.
:param api: The API namespace.
:param provider_id: The ID of the provider to inspect.
:returns: A ProviderInfo object containing the provider's details.
"""
...

View file

@ -79,29 +79,24 @@ class ProviderImpl(Providers):
from llama_stack.providers.utils.kvstore import kvstore_impl from llama_stack.providers.utils.kvstore import kvstore_impl
self.kvstore = await kvstore_impl(self.config.run_config.storage.stores.metadata) self.kvstore = await kvstore_impl(self.config.run_config.storage.stores.metadata)
logger.info("Initialized kvstore for dynamic provider management") logger.info("Initialized kvstore for dynamic provider management")
# Load existing dynamic providers from kvstore # Load existing dynamic providers from kvstore
await self._load_dynamic_providers() await self._load_dynamic_providers()
logger.info(f"📦 Loaded {len(self.dynamic_providers)} existing dynamic providers from kvstore") logger.info(f"Loaded {len(self.dynamic_providers)} existing dynamic providers from kvstore")
# Auto-instantiate connected providers on startup
if self.provider_registry:
for provider_id, conn_info in self.dynamic_providers.items(): for provider_id, conn_info in self.dynamic_providers.items():
if conn_info.status == ProviderConnectionStatus.connected: if conn_info.status == ProviderConnectionStatus.connected:
try: try:
impl = await self._instantiate_provider(conn_info) impl = await self._instantiate_provider(conn_info)
self.dynamic_provider_impls[provider_id] = impl self.dynamic_provider_impls[provider_id] = impl
logger.info(f"♻️ Auto-instantiated provider {provider_id} from kvstore")
except Exception as e: except Exception as e:
logger.error(f"Failed to auto-instantiate provider {provider_id}: {e}") logger.error(f"Failed to instantiate provider {provider_id}: {e}")
# Update status to failed # Update status to failed
conn_info.status = ProviderConnectionStatus.failed conn_info.status = ProviderConnectionStatus.failed
conn_info.error_message = str(e) conn_info.error_message = str(e)
conn_info.updated_at = datetime.now(UTC) conn_info.updated_at = datetime.now(UTC)
await self._store_connection(conn_info) await self._store_connection(conn_info)
else:
logger.warning("Provider registry not available, skipping auto-instantiation")
async def shutdown(self) -> None: async def shutdown(self) -> None:
logger.debug("ProviderImpl.shutdown") logger.debug("ProviderImpl.shutdown")
@ -174,13 +169,34 @@ class ProviderImpl(Providers):
return ListProvidersResponse(data=ret) return ListProvidersResponse(data=ret)
async def inspect_provider(self, provider_id: str) -> ProviderInfo: async def inspect_provider(self, provider_id: str) -> ListProvidersResponse:
"""Get all providers with the given provider_id (deprecated).
Returns all providers across all APIs that have this provider_id.
This is deprecated - use inspect_provider_for_api() for unambiguous access.
"""
all_providers = await self.list_providers()
matching = [p for p in all_providers.data if p.provider_id == provider_id]
if not matching:
raise ValueError(f"Provider {provider_id} not found")
return ListProvidersResponse(data=matching)
async def list_providers_for_api(self, api: str) -> ListProvidersResponse:
"""List providers for a specific API."""
all_providers = await self.list_providers()
filtered = [p for p in all_providers.data if p.api == api]
return ListProvidersResponse(data=filtered)
async def inspect_provider_for_api(self, api: str, provider_id: str) -> ProviderInfo:
"""Get a specific provider for a specific API."""
all_providers = await self.list_providers() all_providers = await self.list_providers()
for p in all_providers.data: for p in all_providers.data:
if p.provider_id == provider_id: if p.api == api and p.provider_id == provider_id:
return p return p
raise ValueError(f"Provider {provider_id} not found") raise ValueError(f"Provider {provider_id} not found for API {api}")
async def get_providers_health(self) -> dict[str, dict[str, HealthResponse]]: async def get_providers_health(self) -> dict[str, dict[str, HealthResponse]]:
"""Get health status for all providers. """Get health status for all providers.
@ -272,17 +288,19 @@ class ProviderImpl(Providers):
return ProviderConnectionInfo.model_validate_json(value) return ProviderConnectionInfo.model_validate_json(value)
return None return None
async def _delete_connection(self, provider_id: str) -> None: async def _delete_connection(self, api: str, provider_id: str) -> None:
"""Delete provider connection from kvstore. """Delete provider connection from kvstore.
:param api: API namespace
:param provider_id: Provider ID to delete :param provider_id: Provider ID to delete
""" """
if not self.kvstore: if not self.kvstore:
raise RuntimeError("KVStore not initialized") raise RuntimeError("KVStore not initialized")
key = f"{PROVIDER_CONNECTIONS_PREFIX}{provider_id}" # Use composite key: provider_connections:v1::{api}::{provider_id}
key = f"{PROVIDER_CONNECTIONS_PREFIX}{api}::{provider_id}"
await self.kvstore.delete(key) await self.kvstore.delete(key)
logger.debug(f"Deleted provider connection: {provider_id}") logger.debug(f"Deleted provider connection: {api}::{provider_id}")
async def _list_connections(self) -> list[ProviderConnectionInfo]: async def _list_connections(self) -> list[ProviderConnectionInfo]:
"""List all dynamic provider connections from kvstore. """List all dynamic provider connections from kvstore.
@ -306,6 +324,17 @@ class ProviderImpl(Providers):
self.dynamic_providers[cache_key] = conn self.dynamic_providers[cache_key] = conn
logger.debug(f"Loaded dynamic provider: {cache_key} (status: {conn.status})") logger.debug(f"Loaded dynamic provider: {cache_key} (status: {conn.status})")
def _find_provider_cache_key(self, provider_id: str) -> str | None:
"""Find the cache key for a provider by its provider_id.
Since we use composite keys ({api}::{provider_id}), this searches for the matching key.
Returns None if not found.
"""
for key in self.dynamic_providers.keys():
if key.endswith(f"::{provider_id}"):
return key
return None
# Helper methods for dynamic provider management # Helper methods for dynamic provider management
def _redact_sensitive_config(self, config: dict[str, Any]) -> dict[str, Any]: def _redact_sensitive_config(self, config: dict[str, Any]) -> dict[str, Any]:
@ -380,8 +409,8 @@ class ProviderImpl(Providers):
async def register_provider( async def register_provider(
self, self,
provider_id: str,
api: str, api: str,
provider_id: str,
provider_type: str, provider_type: str,
config: dict[str, Any], config: dict[str, Any],
attributes: dict[str, list[str]] | None = None, attributes: dict[str, list[str]] | None = None,
@ -394,7 +423,6 @@ class ProviderImpl(Providers):
All providers are stored in kvstore and treated equally. All providers are stored in kvstore and treated equally.
""" """
logger.info(f"📝 REGISTER_PROVIDER called: provider_id={provider_id}, api={api}, type={provider_type}")
if not self.kvstore: if not self.kvstore:
raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)") raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)")
@ -427,8 +455,6 @@ class ProviderImpl(Providers):
# Store in kvstore # Store in kvstore
await self._store_connection(conn_info) await self._store_connection(conn_info)
# Instantiate provider if we have a provider registry
if self.provider_registry:
impl = await self._instantiate_provider(conn_info) impl = await self._instantiate_provider(conn_info)
# Use composite key for impl cache too # Use composite key for impl cache too
self.dynamic_provider_impls[cache_key] = impl self.dynamic_provider_impls[cache_key] = impl
@ -437,15 +463,7 @@ class ProviderImpl(Providers):
conn_info.status = ProviderConnectionStatus.connected conn_info.status = ProviderConnectionStatus.connected
conn_info.updated_at = datetime.now(UTC) conn_info.updated_at = datetime.now(UTC)
logger.info( logger.info(f"Registered and instantiated dynamic provider {provider_id} (api={api}, type={provider_type})")
f"Registered and instantiated dynamic provider {provider_id} (api={api}, type={provider_type})"
)
else:
# No registry available - just mark as connected without instantiation
# This can happen during testing or if provider management is disabled
conn_info.status = ProviderConnectionStatus.connected
conn_info.updated_at = datetime.now(UTC)
logger.warning(f"Registered provider {provider_id} without instantiation (no registry)")
# Store updated status # Store updated status
await self._store_connection(conn_info) await self._store_connection(conn_info)
@ -468,6 +486,7 @@ class ProviderImpl(Providers):
async def update_provider( async def update_provider(
self, self,
api: str,
provider_id: str, provider_id: str,
config: dict[str, Any] | None = None, config: dict[str, Any] | None = None,
attributes: dict[str, list[str]] | None = None, attributes: dict[str, list[str]] | None = None,
@ -477,16 +496,16 @@ class ProviderImpl(Providers):
Updates persist to kvstore and survive server restarts. Updates persist to kvstore and survive server restarts.
This works for all providers (whether originally from run.yaml or API). This works for all providers (whether originally from run.yaml or API).
""" """
logger.info(f"🔄 UPDATE_PROVIDER called: provider_id={provider_id}, has_config={config is not None}, has_attributes={attributes is not None}")
if not self.kvstore: if not self.kvstore:
raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)") raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)")
# Check if provider exists # Use composite key
if provider_id not in self.dynamic_providers: cache_key = f"{api}::{provider_id}"
raise ValueError(f"Provider {provider_id} not found") if cache_key not in self.dynamic_providers:
raise ValueError(f"Provider {provider_id} not found for API {api}")
conn_info = self.dynamic_providers[provider_id] conn_info = self.dynamic_providers[cache_key]
# Update config if provided # Update config if provided
if config is not None: if config is not None:
@ -504,10 +523,9 @@ class ProviderImpl(Providers):
await self._store_connection(conn_info) await self._store_connection(conn_info)
# Hot-reload: Shutdown old instance and reinstantiate with new config # Hot-reload: Shutdown old instance and reinstantiate with new config
if self.provider_registry:
# Shutdown old instance if it exists # Shutdown old instance if it exists
if provider_id in self.dynamic_provider_impls: if cache_key in self.dynamic_provider_impls:
old_impl = self.dynamic_provider_impls[provider_id] old_impl = self.dynamic_provider_impls[cache_key]
if hasattr(old_impl, "shutdown"): if hasattr(old_impl, "shutdown"):
try: try:
await old_impl.shutdown() await old_impl.shutdown()
@ -517,7 +535,7 @@ class ProviderImpl(Providers):
# Reinstantiate with new config # Reinstantiate with new config
impl = await self._instantiate_provider(conn_info) impl = await self._instantiate_provider(conn_info)
self.dynamic_provider_impls[provider_id] = impl self.dynamic_provider_impls[cache_key] = impl
# Update status to connected after successful reinstantiation # Update status to connected after successful reinstantiation
conn_info.status = ProviderConnectionStatus.connected conn_info.status = ProviderConnectionStatus.connected
@ -525,12 +543,6 @@ class ProviderImpl(Providers):
await self._store_connection(conn_info) await self._store_connection(conn_info)
logger.info(f"Hot-reloaded dynamic provider {provider_id}") logger.info(f"Hot-reloaded dynamic provider {provider_id}")
else:
# No registry - just update config without reinstantiation
conn_info.status = ProviderConnectionStatus.connected
conn_info.updated_at = datetime.now(UTC)
await self._store_connection(conn_info)
logger.warning(f"Updated provider {provider_id} config without hot-reload (no registry)")
return UpdateProviderResponse(provider=conn_info) return UpdateProviderResponse(provider=conn_info)
@ -543,34 +555,36 @@ class ProviderImpl(Providers):
logger.error(f"Failed to update provider {provider_id}: {e}") logger.error(f"Failed to update provider {provider_id}: {e}")
raise RuntimeError(f"Failed to update provider: {e}") from e raise RuntimeError(f"Failed to update provider: {e}") from e
async def unregister_provider(self, provider_id: str) -> None: async def unregister_provider(self, api: str, provider_id: str) -> None:
"""Unregister a provider. """Unregister a provider.
Removes the provider from kvstore and shuts down its instance. Removes the provider from kvstore and shuts down its instance.
This works for all providers (whether originally from run.yaml or API). This works for all providers (whether originally from run.yaml or API).
""" """
logger.info(f"🗑️ UNREGISTER_PROVIDER called: provider_id={provider_id}")
if not self.kvstore: if not self.kvstore:
raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)") raise RuntimeError("Dynamic provider management is not enabled (no kvstore configured)")
# Check if provider exists # Use composite key
if provider_id not in self.dynamic_providers: cache_key = f"{api}::{provider_id}"
raise ValueError(f"Provider {provider_id} not found") if cache_key not in self.dynamic_providers:
raise ValueError(f"Provider {provider_id} not found for API {api}")
conn_info = self.dynamic_providers[cache_key]
try: try:
# Shutdown provider instance if it exists # Shutdown provider instance if it exists
if provider_id in self.dynamic_provider_impls: if cache_key in self.dynamic_provider_impls:
impl = self.dynamic_provider_impls[provider_id] impl = self.dynamic_provider_impls[cache_key]
if hasattr(impl, "shutdown"): if hasattr(impl, "shutdown"):
await impl.shutdown() await impl.shutdown()
del self.dynamic_provider_impls[provider_id] del self.dynamic_provider_impls[cache_key]
# Remove from kvstore # Remove from kvstore (using the api and provider_id from conn_info)
await self._delete_connection(provider_id) await self._delete_connection(conn_info.api, provider_id)
# Remove from runtime cache # Remove from runtime cache
del self.dynamic_providers[provider_id] del self.dynamic_providers[cache_key]
logger.info(f"Unregistered dynamic provider {provider_id}") logger.info(f"Unregistered dynamic provider {provider_id}")
@ -578,23 +592,24 @@ class ProviderImpl(Providers):
logger.error(f"Failed to unregister provider {provider_id}: {e}") logger.error(f"Failed to unregister provider {provider_id}: {e}")
raise RuntimeError(f"Failed to unregister provider: {e}") from e raise RuntimeError(f"Failed to unregister provider: {e}") from e
async def test_provider_connection(self, provider_id: str) -> TestProviderConnectionResponse: async def test_provider_connection(self, api: str, provider_id: str) -> TestProviderConnectionResponse:
"""Test a provider connection.""" """Test a provider connection."""
logger.info(f"🔍 TEST_PROVIDER_CONNECTION called: provider_id={provider_id}")
# Check if provider exists (static or dynamic) # Check if provider exists (static or dynamic)
provider_impl = None provider_impl = None
cache_key = f"{api}::{provider_id}"
# Check dynamic providers first (using composite keys)
if cache_key in self.dynamic_provider_impls:
provider_impl = self.dynamic_provider_impls[cache_key]
# Check dynamic providers first
if provider_id in self.dynamic_provider_impls:
provider_impl = self.dynamic_provider_impls[provider_id]
# Check static providers # Check static providers
elif provider_id in self.deps: if not provider_impl and provider_id in self.deps:
provider_impl = self.deps[provider_id] provider_impl = self.deps[provider_id]
if not provider_impl: if not provider_impl:
return TestProviderConnectionResponse( return TestProviderConnectionResponse(
success=False, error_message=f"Provider {provider_id} not found or not initialized" success=False, error_message=f"Provider {provider_id} not found for API {api}"
) )
# Check if provider has health method # Check if provider has health method
@ -611,8 +626,8 @@ class ProviderImpl(Providers):
health_result = await asyncio.wait_for(provider_impl.health(), timeout=5.0) health_result = await asyncio.wait_for(provider_impl.health(), timeout=5.0)
# Update health in dynamic provider cache if applicable # Update health in dynamic provider cache if applicable
if provider_id in self.dynamic_providers: if cache_key and cache_key in self.dynamic_providers:
conn_info = self.dynamic_providers[provider_id] conn_info = self.dynamic_providers[cache_key]
conn_info.health = ProviderHealth.from_health_response(health_result) conn_info.health = ProviderHealth.from_health_response(health_result)
conn_info.last_health_check = datetime.now(UTC) conn_info.last_health_check = datetime.now(UTC)
await self._store_connection(conn_info) await self._store_connection(conn_info)

View file

@ -34,16 +34,21 @@ from llama_stack.apis.synthetic_data_generation import SyntheticDataGeneration
from llama_stack.apis.telemetry import Telemetry from llama_stack.apis.telemetry import Telemetry
from llama_stack.apis.tools import RAGToolRuntime, ToolGroups, ToolRuntime from llama_stack.apis.tools import RAGToolRuntime, ToolGroups, ToolRuntime
from llama_stack.apis.vector_io import VectorIO from llama_stack.apis.vector_io import VectorIO
from llama_stack.core.access_control.datatypes import AccessRule
from llama_stack.core.conversations.conversations import ConversationServiceConfig, ConversationServiceImpl from llama_stack.core.conversations.conversations import ConversationServiceConfig, ConversationServiceImpl
from llama_stack.core.datatypes import Provider, SafetyConfig, StackRunConfig, VectorStoresConfig from llama_stack.core.datatypes import Provider, SafetyConfig, StackRunConfig, VectorStoresConfig
from llama_stack.core.distribution import get_provider_registry from llama_stack.core.distribution import builtin_automatically_routed_apis, get_provider_registry
from llama_stack.core.inspect import DistributionInspectConfig, DistributionInspectImpl from llama_stack.core.inspect import DistributionInspectConfig, DistributionInspectImpl
from llama_stack.core.prompts.prompts import PromptServiceConfig, PromptServiceImpl from llama_stack.core.prompts.prompts import PromptServiceConfig, PromptServiceImpl
from llama_stack.core.providers import ProviderImpl, ProviderImplConfig from llama_stack.core.providers import ProviderImpl, ProviderImplConfig
from llama_stack.core.resolver import ProviderRegistry, resolve_impls from llama_stack.core.resolver import (
ProviderRegistry,
instantiate_provider,
sort_providers_by_deps,
specs_for_autorouted_apis,
validate_and_prepare_providers,
)
from llama_stack.core.routing_tables.common import CommonRoutingTableImpl from llama_stack.core.routing_tables.common import CommonRoutingTableImpl
from llama_stack.core.access_control.datatypes import AccessRule
from llama_stack.core.store.registry import DistributionRegistry
from llama_stack.core.storage.datatypes import ( from llama_stack.core.storage.datatypes import (
InferenceStoreReference, InferenceStoreReference,
KVStoreReference, KVStoreReference,
@ -54,10 +59,12 @@ from llama_stack.core.storage.datatypes import (
StorageBackendConfig, StorageBackendConfig,
StorageConfig, StorageConfig,
) )
from llama_stack.core.store.registry import create_dist_registry from llama_stack.core.store.registry import DistributionRegistry, create_dist_registry
from llama_stack.core.utils.dynamic import instantiate_class_type from llama_stack.core.utils.dynamic import instantiate_class_type
from llama_stack.log import get_logger from llama_stack.log import get_logger
from llama_stack.providers.datatypes import Api from llama_stack.providers.datatypes import Api
from llama_stack.providers.utils.kvstore.kvstore import register_kvstore_backends
from llama_stack.providers.utils.sqlstore.sqlstore import register_sqlstore_backends
logger = get_logger(name=__name__, category="core") logger = get_logger(name=__name__, category="core")
@ -401,9 +408,6 @@ def _initialize_storage(run_config: StackRunConfig):
else: else:
raise ValueError(f"Unknown storage backend type: {type}") raise ValueError(f"Unknown storage backend type: {type}")
from llama_stack.providers.utils.kvstore.kvstore import register_kvstore_backends
from llama_stack.providers.utils.sqlstore.sqlstore import register_sqlstore_backends
register_kvstore_backends(kv_backends) register_kvstore_backends(kv_backends)
register_sqlstore_backends(sql_backends) register_sqlstore_backends(sql_backends)
@ -429,9 +433,6 @@ async def resolve_impls_via_provider_registration(
Returns: Returns:
Dictionary mapping API to implementation instances Dictionary mapping API to implementation instances
""" """
from llama_stack.core.distribution import builtin_automatically_routed_apis
from llama_stack.core.resolver import sort_providers_by_deps, specs_for_autorouted_apis, validate_and_prepare_providers
routing_table_apis = {x.routing_table_api for x in builtin_automatically_routed_apis()} routing_table_apis = {x.routing_table_api for x in builtin_automatically_routed_apis()}
router_apis = {x.router_api for x in builtin_automatically_routed_apis()} router_apis = {x.router_api for x in builtin_automatically_routed_apis()}
@ -455,50 +456,29 @@ async def resolve_impls_via_provider_registration(
# Register each provider through ProviderImpl # Register each provider through ProviderImpl
impls = internal_impls.copy() impls = internal_impls.copy()
logger.info(f"🚀 Starting provider registration for {len(sorted_providers)} providers from run.yaml") logger.info(f"Provider registration for {len(sorted_providers)} providers from run.yaml")
for api_str, provider in sorted_providers: for api_str, provider in sorted_providers:
# Skip providers that are not enabled # Skip providers that are not enabled
if provider.provider_id is None: if provider.provider_id is None:
continue continue
# Skip internal APIs that need special handling # Skip internal APIs (already initialized)
# - providers: already initialized as internal_impls
# - inspect: already initialized as internal_impls
# - telemetry: internal observability, directly instantiated below
if api_str in ["providers", "inspect"]: if api_str in ["providers", "inspect"]:
continue continue
# Telemetry is an internal API that should be directly instantiated
if api_str == "telemetry":
logger.info(f"Instantiating {provider.provider_id} for {api_str}")
from llama_stack.core.resolver import instantiate_provider
deps = {a: impls[a] for a in provider.spec.api_dependencies if a in impls}
for a in provider.spec.optional_api_dependencies:
if a in impls:
deps[a] = impls[a]
impl = await instantiate_provider(provider, deps, {}, dist_registry, run_config, policy)
api = Api(api_str)
impls[api] = impl
providers_impl.deps[api] = impl
continue
# Handle different provider types # Handle different provider types
try: try:
# Check if this is a routing table or router (system infrastructure) # Check if this is a router (system infrastructure)
is_routing_table = api_str.startswith("inner-") or provider.spec.provider_type in ["routing_table", "router"] is_router = not api_str.startswith("inner-") and (
is_router = not api_str.startswith("inner-") and (Api(api_str) in router_apis or provider.spec.provider_type == "router") Api(api_str) in router_apis or provider.spec.provider_type == "router"
)
if api_str.startswith("inner-") or provider.spec.provider_type == "routing_table": if api_str.startswith("inner-") or provider.spec.provider_type == "routing_table":
# Inner providers or routing tables cannot be registered through the API # Inner providers or routing tables cannot be registered through the API
# They need to be instantiated directly # They need to be instantiated directly
logger.info(f"Instantiating {provider.provider_id} for {api_str}") logger.info(f"Instantiating {provider.provider_id} for {api_str}")
from llama_stack.core.resolver import instantiate_provider
deps = {a: impls[a] for a in provider.spec.api_dependencies if a in impls} deps = {a: impls[a] for a in provider.spec.api_dependencies if a in impls}
for a in provider.spec.optional_api_dependencies: for a in provider.spec.optional_api_dependencies:
if a in impls: if a in impls:
@ -510,8 +490,9 @@ async def resolve_impls_via_provider_registration(
# For routing tables of autorouted APIs, get inner impls from the router API # For routing tables of autorouted APIs, get inner impls from the router API
# E.g., tool_groups routing table needs inner-tool_runtime providers # E.g., tool_groups routing table needs inner-tool_runtime providers
if provider.spec.provider_type == "routing_table": if provider.spec.provider_type == "routing_table":
from llama_stack.core.distribution import builtin_automatically_routed_apis autorouted_map = {
autorouted_map = {info.routing_table_api: info.router_api for info in builtin_automatically_routed_apis()} info.routing_table_api: info.router_api for info in builtin_automatically_routed_apis()
}
if Api(api_str) in autorouted_map: if Api(api_str) in autorouted_map:
router_api_str = autorouted_map[Api(api_str)].value router_api_str = autorouted_map[Api(api_str)].value
inner_key = f"inner-{router_api_str}" inner_key = f"inner-{router_api_str}"
@ -540,8 +521,6 @@ async def resolve_impls_via_provider_registration(
# Router providers also need special handling # Router providers also need special handling
logger.info(f"Instantiating router {provider.provider_id} for {api_str}") logger.info(f"Instantiating router {provider.provider_id} for {api_str}")
from llama_stack.core.resolver import instantiate_provider
deps = {a: impls[a] for a in provider.spec.api_dependencies if a in impls} deps = {a: impls[a] for a in provider.spec.api_dependencies if a in impls}
for a in provider.spec.optional_api_dependencies: for a in provider.spec.optional_api_dependencies:
if a in impls: if a in impls:
@ -564,9 +543,9 @@ async def resolve_impls_via_provider_registration(
api = Api(api_str) api = Api(api_str)
logger.info(f"Registering {provider.provider_id} for {api.value}") logger.info(f"Registering {provider.provider_id} for {api.value}")
response = await providers_impl.register_provider( await providers_impl.register_provider(
provider_id=provider.provider_id,
api=api.value, api=api.value,
provider_id=provider.provider_id,
provider_type=provider.spec.provider_type, provider_type=provider.spec.provider_type,
config=provider.config, config=provider.config,
attributes=getattr(provider, "attributes", None), attributes=getattr(provider, "attributes", None),
@ -580,10 +559,10 @@ async def resolve_impls_via_provider_registration(
# IMPORTANT: Update providers_impl.deps so subsequent providers can depend on this one # IMPORTANT: Update providers_impl.deps so subsequent providers can depend on this one
providers_impl.deps[api] = impl providers_impl.deps[api] = impl
logger.info(f"Successfully registered startup provider: {provider.provider_id}") logger.info(f"Successfully registered startup provider: {provider.provider_id}")
except Exception as e: except Exception as e:
logger.error(f"Failed to handle provider {provider.provider_id}: {e}") logger.error(f"Failed to handle provider {provider.provider_id}: {e}")
raise raise
return impls return impls

View file

@ -231,7 +231,7 @@ storage:
backends: backends:
kv_default: kv_default:
type: kv_sqlite type: kv_sqlite
db_path: ":memory:" db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/kvstore.db
sql_default: sql_default:
type: sql_sqlite type: sql_sqlite
db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/sql_store.db db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/sql_store.db

View file

@ -83,8 +83,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register a mock inference provider # Register a mock inference provider
response = await provider_impl.register_provider( response = await provider_impl.register_provider(
provider_id="test-inference-1",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-inference-1",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "test-key", "url": "https://api.openai.com/v1"}, config={"api_key": "test-key", "url": "https://api.openai.com/v1"},
attributes={"team": ["test-team"]}, attributes={"team": ["test-team"]},
@ -98,9 +98,9 @@ class TestDynamicProviderManagement:
assert response.provider.config["api_key"] == "test-key" assert response.provider.config["api_key"] == "test-key"
assert response.provider.attributes == {"team": ["test-team"]} assert response.provider.attributes == {"team": ["test-team"]}
# Verify provider is stored # Verify provider is stored (using composite key)
assert "test-inference-1" in provider_impl.dynamic_providers assert "inference::test-inference-1" in provider_impl.dynamic_providers
assert "test-inference-1" in provider_impl.dynamic_provider_impls assert "inference::test-inference-1" in provider_impl.dynamic_provider_impls
async def test_register_vector_store_provider(self, provider_impl): async def test_register_vector_store_provider(self, provider_impl):
"""Test registering a new vector store provider.""" """Test registering a new vector store provider."""
@ -111,8 +111,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register a mock vector_io provider # Register a mock vector_io provider
response = await provider_impl.register_provider( response = await provider_impl.register_provider(
provider_id="test-vector-store-1",
api=Api.vector_io.value, api=Api.vector_io.value,
provider_id="test-vector-store-1",
provider_type="inline::faiss", provider_type="inline::faiss",
config={"dimension": 768, "index_path": "/tmp/faiss_index"}, config={"dimension": 768, "index_path": "/tmp/faiss_index"},
) )
@ -132,8 +132,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register first provider # Register first provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-duplicate",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-duplicate",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "key1"}, config={"api_key": "key1"},
) )
@ -141,8 +141,8 @@ class TestDynamicProviderManagement:
# Try to register with same ID # Try to register with same ID
with pytest.raises(ValueError, match="already exists"): with pytest.raises(ValueError, match="already exists"):
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-duplicate",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-duplicate",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "key2"}, config={"api_key": "key2"},
) )
@ -155,14 +155,15 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-update",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-update",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "old-key", "timeout": 30}, config={"api_key": "old-key", "timeout": 30},
) )
# Update configuration # Update configuration
response = await provider_impl.update_provider( response = await provider_impl.update_provider(
api=Api.inference.value,
provider_id="test-update", provider_id="test-update",
config={"api_key": "new-key", "timeout": 60}, config={"api_key": "new-key", "timeout": 60},
) )
@ -181,8 +182,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider with initial attributes # Register provider with initial attributes
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-attributes",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-attributes",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "test-key"}, config={"api_key": "test-key"},
attributes={"team": ["team-a"]}, attributes={"team": ["team-a"]},
@ -190,6 +191,7 @@ class TestDynamicProviderManagement:
# Update attributes # Update attributes
response = await provider_impl.update_provider( response = await provider_impl.update_provider(
api=Api.inference.value,
provider_id="test-attributes", provider_id="test-attributes",
attributes={"team": ["team-a", "team-b"], "environment": ["prod"]}, attributes={"team": ["team-a", "team-b"], "environment": ["prod"]},
) )
@ -201,6 +203,7 @@ class TestDynamicProviderManagement:
"""Test that updating a non-existent provider fails.""" """Test that updating a non-existent provider fails."""
with pytest.raises(ValueError, match="not found"): with pytest.raises(ValueError, match="not found"):
await provider_impl.update_provider( await provider_impl.update_provider(
api=Api.inference.value,
provider_id="nonexistent", provider_id="nonexistent",
config={"api_key": "new-key"}, config={"api_key": "new-key"},
) )
@ -214,21 +217,22 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-unregister",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-unregister",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "test-key"}, config={"api_key": "test-key"},
) )
# Verify it exists # Verify it exists
assert "test-unregister" in provider_impl.dynamic_providers cache_key = f"{Api.inference.value}::test-unregister"
assert cache_key in provider_impl.dynamic_providers
# Unregister provider # Unregister provider
await provider_impl.unregister_provider(provider_id="test-unregister") await provider_impl.unregister_provider(api=Api.inference.value, provider_id="test-unregister")
# Verify it's removed # Verify it's removed
assert "test-unregister" not in provider_impl.dynamic_providers assert cache_key not in provider_impl.dynamic_providers
assert "test-unregister" not in provider_impl.dynamic_provider_impls assert cache_key not in provider_impl.dynamic_provider_impls
# Verify shutdown was called # Verify shutdown was called
mock_provider_instance.shutdown.assert_called_once() mock_provider_instance.shutdown.assert_called_once()
@ -236,7 +240,7 @@ class TestDynamicProviderManagement:
async def test_unregister_nonexistent_provider_fails(self, provider_impl): async def test_unregister_nonexistent_provider_fails(self, provider_impl):
"""Test that unregistering a non-existent provider fails.""" """Test that unregistering a non-existent provider fails."""
with pytest.raises(ValueError, match="not found"): with pytest.raises(ValueError, match="not found"):
await provider_impl.unregister_provider(provider_id="nonexistent") await provider_impl.unregister_provider(api=Api.inference.value, provider_id="nonexistent")
async def test_test_provider_connection_healthy(self, provider_impl): async def test_test_provider_connection_healthy(self, provider_impl):
"""Test testing a healthy provider connection.""" """Test testing a healthy provider connection."""
@ -246,14 +250,14 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-health",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-health",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "test-key"}, config={"api_key": "test-key"},
) )
# Test connection # Test connection
response = await provider_impl.test_provider_connection(provider_id="test-health") response = await provider_impl.test_provider_connection(api=Api.inference.value, provider_id="test-health")
# Verify response # Verify response
assert response.success is True assert response.success is True
@ -271,14 +275,16 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-unhealthy",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-unhealthy",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "invalid-key"}, config={"api_key": "invalid-key"},
) )
# Test connection # Test connection
response = await provider_impl.test_provider_connection(provider_id="test-unhealthy") response = await provider_impl.test_provider_connection(
api=Api.inference.value, provider_id="test-unhealthy"
)
# Verify response shows unhealthy status # Verify response shows unhealthy status
assert response.success is False assert response.success is False
@ -292,15 +298,15 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register multiple providers # Register multiple providers
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="dynamic-1",
api=Api.inference.value, api=Api.inference.value,
provider_id="dynamic-1",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "key1"}, config={"api_key": "key1"},
) )
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="dynamic-2",
api=Api.vector_io.value, api=Api.vector_io.value,
provider_id="dynamic-2",
provider_type="inline::faiss", provider_type="inline::faiss",
config={"dimension": 768}, config={"dimension": 768},
) )
@ -321,8 +327,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-inspect",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-inspect",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "test-key", "model": "gpt-4"}, config={"api_key": "test-key", "model": "gpt-4"},
) )
@ -330,14 +336,17 @@ class TestDynamicProviderManagement:
# Update the stored health info to reflect OK status # Update the stored health info to reflect OK status
# (In reality, the health check happens during registration, # (In reality, the health check happens during registration,
# but our mock may not have been properly called) # but our mock may not have been properly called)
conn_info = provider_impl.dynamic_providers["test-inspect"] cache_key = f"{Api.inference.value}::test-inspect"
conn_info = provider_impl.dynamic_providers[cache_key]
conn_info.health = ProviderHealth.from_health_response({"status": HealthStatus.OK}) conn_info.health = ProviderHealth.from_health_response({"status": HealthStatus.OK})
# Inspect provider # Inspect provider
provider_info = await provider_impl.inspect_provider(provider_id="test-inspect") response = await provider_impl.inspect_provider(provider_id="test-inspect")
# Verify provider info # Verify response
assert len(response.data) == 1
provider_info = response.data[0]
assert provider_info.provider_id == "test-inspect" assert provider_info.provider_id == "test-inspect"
assert provider_info.api == Api.inference.value assert provider_info.api == Api.inference.value
assert provider_info.provider_type == "remote::openai" assert provider_info.provider_type == "remote::openai"
@ -352,8 +361,8 @@ class TestDynamicProviderManagement:
with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance): with patch.object(provider_impl, "_instantiate_provider", return_value=mock_provider_instance):
# Register provider # Register provider
await provider_impl.register_provider( await provider_impl.register_provider(
provider_id="test-persist",
api=Api.inference.value, api=Api.inference.value,
provider_id="test-persist",
provider_type="remote::openai", provider_type="remote::openai",
config={"api_key": "persist-key"}, config={"api_key": "persist-key"},
) )
@ -397,5 +406,6 @@ class TestDynamicProviderManagement:
await new_impl._load_dynamic_providers() await new_impl._load_dynamic_providers()
# Verify the provider was loaded from kvstore # Verify the provider was loaded from kvstore
assert "test-persist" in new_impl.dynamic_providers cache_key = f"{Api.inference.value}::test-persist"
assert new_impl.dynamic_providers["test-persist"].config["api_key"] == "persist-key" assert cache_key in new_impl.dynamic_providers
assert new_impl.dynamic_providers[cache_key].config["api_key"] == "persist-key"