Skip to content

Commit e145879

Browse files
committed
feat: Add support for dynamically managing provider connections
1 parent 7b90e0e commit e145879

File tree

9 files changed

+3176
-8
lines changed

9 files changed

+3176
-8
lines changed

client-sdks/stainless/openapi.yml

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.

docs/static/llama-stack-spec.html

Lines changed: 571 additions & 0 deletions
Large diffs are not rendered by default.

docs/static/llama-stack-spec.yaml

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.

docs/static/stainless-llama-stack-spec.html

Lines changed: 571 additions & 0 deletions
Large diffs are not rendered by default.

docs/static/stainless-llama-stack-spec.yaml

Lines changed: 422 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the terms described in the LICENSE file in
5+
# the root directory of this source tree.
6+
7+
from datetime import UTC, datetime
8+
from enum import StrEnum
9+
from typing import Any
10+
11+
from pydantic import BaseModel, Field
12+
13+
from llama_stack.core.datatypes import User
14+
from llama_stack.providers.datatypes import HealthStatus
15+
from llama_stack.schema_utils import json_schema_type
16+
17+
18+
@json_schema_type
19+
class ProviderConnectionStatus(StrEnum):
20+
"""Status of a dynamic provider connection.
21+
22+
:cvar pending: Configuration stored, not yet initialized
23+
:cvar initializing: In the process of connecting
24+
:cvar connected: Successfully connected and healthy
25+
:cvar failed: Connection attempt failed
26+
:cvar disconnected: Previously connected, now disconnected
27+
:cvar testing: Health check in progress
28+
"""
29+
30+
pending = "pending"
31+
initializing = "initializing"
32+
connected = "connected"
33+
failed = "failed"
34+
disconnected = "disconnected"
35+
testing = "testing"
36+
37+
38+
@json_schema_type
39+
class ProviderHealth(BaseModel):
40+
"""Structured wrapper around provider health status.
41+
42+
This wraps the existing dict-based HealthResponse for API responses
43+
while maintaining backward compatibility with existing provider implementations.
44+
45+
:param status: Health status (OK, ERROR, NOT_IMPLEMENTED)
46+
:param message: Optional error or status message
47+
:param metrics: Provider-specific health metrics
48+
:param last_checked: Timestamp of last health check
49+
"""
50+
51+
status: HealthStatus
52+
message: str | None = None
53+
metrics: dict[str, Any] = Field(default_factory=dict)
54+
last_checked: datetime
55+
56+
@classmethod
57+
def from_health_response(cls, response: dict[str, Any]) -> "ProviderHealth":
58+
"""Convert dict-based HealthResponse to ProviderHealth.
59+
60+
This allows us to maintain the existing dict[str, Any] return type
61+
for provider.health() methods while providing a structured model
62+
for API responses.
63+
64+
:param response: Dict with 'status' and optional 'message', 'metrics'
65+
:returns: ProviderHealth instance
66+
"""
67+
return cls(
68+
status=HealthStatus(response.get("status", HealthStatus.NOT_IMPLEMENTED)),
69+
message=response.get("message"),
70+
metrics=response.get("metrics", {}),
71+
last_checked=datetime.now(UTC),
72+
)
73+
74+
75+
@json_schema_type
76+
class ProviderConnectionInfo(BaseModel):
77+
"""Information about a dynamically managed provider connection.
78+
79+
This model represents a provider that has been registered at runtime
80+
via the /providers API, as opposed to static providers configured in run.yaml.
81+
82+
Dynamic providers support full lifecycle management including registration,
83+
configuration updates, health monitoring, and removal.
84+
85+
:param provider_id: Unique identifier for this provider instance
86+
:param api: API namespace (e.g., "inference", "vector_io", "safety")
87+
:param provider_type: Provider type identifier (e.g., "remote::openai", "inline::faiss")
88+
:param config: Provider-specific configuration (API keys, endpoints, etc.)
89+
:param status: Current connection status
90+
:param health: Most recent health check result
91+
:param created_at: Timestamp when provider was registered
92+
:param updated_at: Timestamp of last update
93+
:param last_health_check: Timestamp of last health check
94+
:param error_message: Error message if status is failed
95+
:param metadata: User-defined metadata (deprecated, use attributes)
96+
:param owner: User who created this provider connection
97+
:param attributes: Key-value attributes for ABAC access control
98+
"""
99+
100+
provider_id: str
101+
api: str
102+
provider_type: str
103+
config: dict[str, Any]
104+
status: ProviderConnectionStatus
105+
health: ProviderHealth | None = None
106+
created_at: datetime
107+
updated_at: datetime
108+
last_health_check: datetime | None = None
109+
error_message: str | None = None
110+
metadata: dict[str, Any] = Field(
111+
default_factory=dict,
112+
description="Deprecated: use attributes for access control",
113+
)
114+
115+
# ABAC fields (same as ResourceWithOwner)
116+
owner: User | None = None
117+
attributes: dict[str, list[str]] | None = None

llama_stack/apis/providers/providers.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from pydantic import BaseModel
1010

11+
from llama_stack.apis.providers.connection import ProviderConnectionInfo
1112
from llama_stack.apis.version import LLAMA_STACK_API_V1
1213
from llama_stack.providers.datatypes import HealthResponse
1314
from llama_stack.schema_utils import json_schema_type, webmethod
@@ -40,6 +41,85 @@ class ListProvidersResponse(BaseModel):
4041
data: list[ProviderInfo]
4142

4243

44+
# ===== Dynamic Provider Management API Models =====
45+
46+
47+
@json_schema_type
48+
class RegisterProviderRequest(BaseModel):
49+
"""Request to register a new dynamic provider.
50+
51+
:param provider_id: Unique identifier for the provider instance
52+
:param api: API namespace (e.g., 'inference', 'vector_io', 'safety')
53+
:param provider_type: Provider type identifier (e.g., 'remote::openai', 'inline::faiss')
54+
:param config: Provider-specific configuration (API keys, endpoints, etc.)
55+
:param attributes: Optional key-value attributes for ABAC access control
56+
"""
57+
58+
provider_id: str
59+
api: str
60+
provider_type: str
61+
config: dict[str, Any]
62+
attributes: dict[str, list[str]] | None = None
63+
64+
65+
@json_schema_type
66+
class RegisterProviderResponse(BaseModel):
67+
"""Response after registering a provider.
68+
69+
:param provider: Information about the registered provider
70+
"""
71+
72+
provider: ProviderConnectionInfo
73+
74+
75+
@json_schema_type
76+
class UpdateProviderRequest(BaseModel):
77+
"""Request to update an existing provider's configuration.
78+
79+
:param config: New configuration parameters (will be merged with existing)
80+
:param attributes: Optional updated attributes for access control
81+
"""
82+
83+
config: dict[str, Any] | None = None
84+
attributes: dict[str, list[str]] | None = None
85+
86+
87+
@json_schema_type
88+
class UpdateProviderResponse(BaseModel):
89+
"""Response after updating a provider.
90+
91+
:param provider: Updated provider information
92+
"""
93+
94+
provider: ProviderConnectionInfo
95+
96+
97+
@json_schema_type
98+
class UnregisterProviderResponse(BaseModel):
99+
"""Response after unregistering a provider.
100+
101+
:param success: Whether the operation succeeded
102+
:param message: Optional status message
103+
"""
104+
105+
success: bool
106+
message: str | None = None
107+
108+
109+
@json_schema_type
110+
class TestProviderConnectionResponse(BaseModel):
111+
"""Response from testing a provider connection.
112+
113+
:param success: Whether the connection test succeeded
114+
:param health: Health status from the provider
115+
:param error_message: Error message if test failed
116+
"""
117+
118+
success: bool
119+
health: HealthResponse | None = None
120+
error_message: str | None = None
121+
122+
43123
@runtime_checkable
44124
class Providers(Protocol):
45125
"""Providers
@@ -67,3 +147,71 @@ async def inspect_provider(self, provider_id: str) -> ProviderInfo:
67147
:returns: A ProviderInfo object containing the provider's details.
68148
"""
69149
...
150+
151+
# ===== Dynamic Provider Management Methods =====
152+
153+
@webmethod(route="/admin/providers", method="POST", level=LLAMA_STACK_API_V1)
154+
async def register_provider(
155+
self,
156+
provider_id: str,
157+
api: str,
158+
provider_type: str,
159+
config: dict[str, Any],
160+
attributes: dict[str, list[str]] | None = None,
161+
) -> RegisterProviderResponse:
162+
"""Register a new dynamic provider.
163+
164+
Register a new provider instance at runtime. The provider will be validated,
165+
instantiated, and persisted to the kvstore. Requires appropriate ABAC permissions.
166+
167+
:param provider_id: Unique identifier for this provider instance.
168+
:param api: API namespace this provider implements.
169+
:param provider_type: Provider type (e.g., 'remote::openai').
170+
:param config: Provider configuration (API keys, endpoints, etc.).
171+
:param attributes: Optional attributes for ABAC access control.
172+
:returns: RegisterProviderResponse with the registered provider info.
173+
"""
174+
...
175+
176+
@webmethod(route="/admin/providers/{provider_id}", method="PUT", level=LLAMA_STACK_API_V1)
177+
async def update_provider(
178+
self,
179+
provider_id: str,
180+
config: dict[str, Any] | None = None,
181+
attributes: dict[str, list[str]] | None = None,
182+
) -> UpdateProviderResponse:
183+
"""Update an existing provider's configuration.
184+
185+
Update the configuration and/or attributes of a dynamic provider. The provider
186+
will be re-instantiated with the new configuration (hot-reload). Static providers
187+
from run.yaml cannot be updated.
188+
189+
:param provider_id: ID of the provider to update
190+
:param config: New configuration parameters (merged with existing)
191+
:param attributes: New attributes for access control
192+
:returns: UpdateProviderResponse with updated provider info
193+
"""
194+
...
195+
196+
@webmethod(route="/admin/providers/{provider_id}", method="DELETE", level=LLAMA_STACK_API_V1)
197+
async def unregister_provider(self, provider_id: str) -> None:
198+
"""Unregister a dynamic provider.
199+
200+
Remove a dynamic provider, shutting down its instance and removing it from
201+
the kvstore. Static providers from run.yaml cannot be unregistered.
202+
203+
:param provider_id: ID of the provider to unregister.
204+
"""
205+
...
206+
207+
@webmethod(route="/providers/{provider_id}/test", method="POST", level=LLAMA_STACK_API_V1)
208+
async def test_provider_connection(self, provider_id: str) -> TestProviderConnectionResponse:
209+
"""Test a provider connection.
210+
211+
Execute a health check on a provider to verify it is reachable and functioning.
212+
Works for both static and dynamic providers.
213+
214+
:param provider_id: ID of the provider to test.
215+
:returns: TestProviderConnectionResponse with health status.
216+
"""
217+
...

0 commit comments

Comments
 (0)