diff --git a/agentkit/sdk/model_gateway/__init__.py b/agentkit/sdk/model_gateway/__init__.py new file mode 100644 index 00000000..501ac15f --- /dev/null +++ b/agentkit/sdk/model_gateway/__init__.py @@ -0,0 +1,101 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .client import AgentkitModelGatewayClient +from .types import ( + ApiKeysForModelGateway, + AuthzConfigForModelGateway, + ConsumerForCreateModelGateway, + ConsumersForModelGateway, + CreateModelGatewayRequest, + CreateModelGatewayConsumerRequest, + CreateModelGatewayConsumerResponse, + CreateModelGatewayProviderRequest, + CreateModelGatewayProviderResponse, + CreateModelGatewayResponse, + CredentialsForModelGateway, + DeleteModelGatewayConsumerRequest, + DeleteModelGatewayConsumerResponse, + DeleteModelGatewayProviderRequest, + DeleteModelGatewayProviderResponse, + GetModelGatewayConsumerRequest, + GetModelGatewayConsumerResponse, + GetModelGatewayProviderRequest, + GetModelGatewayProviderResponse, + ListModelGatewayConsumersRequest, + ListModelGatewayConsumersResponse, + ListModelGatewaysRequest, + ListModelGatewaysResponse, + ListModelGatewayProvidersRequest, + ListModelGatewayProvidersResponse, + ModelGatewaysForModelGateway, + ProviderAuthzConfigsForModelGateway, + ProviderModelsForModelGateway, + ProvidersItemForCreateModelGateway, + ProvidersForModelGateway, + ProviderSpecForModelGateway, + RetryPolicyForModelGateway, + TagFiltersForModelGateway, + TagsForModelGateway, + TokenRateLimitConfigForModelGateway, + TokenRateLimitRulesForModelGateway, + UpdateModelGatewayConsumerRequest, + UpdateModelGatewayConsumerResponse, + UpdateModelGatewayProviderRequest, + UpdateModelGatewayProviderResponse, +) + +__all__ = [ + "AgentkitModelGatewayClient", + "ApiKeysForModelGateway", + "AuthzConfigForModelGateway", + "ConsumerForCreateModelGateway", + "ConsumersForModelGateway", + "CreateModelGatewayRequest", + "CreateModelGatewayConsumerRequest", + "CreateModelGatewayConsumerResponse", + "CreateModelGatewayProviderRequest", + "CreateModelGatewayProviderResponse", + "CreateModelGatewayResponse", + "CredentialsForModelGateway", + "DeleteModelGatewayConsumerRequest", + "DeleteModelGatewayConsumerResponse", + "DeleteModelGatewayProviderRequest", + "DeleteModelGatewayProviderResponse", + "GetModelGatewayConsumerRequest", + "GetModelGatewayConsumerResponse", + "GetModelGatewayProviderRequest", + "GetModelGatewayProviderResponse", + "ListModelGatewayConsumersRequest", + "ListModelGatewayConsumersResponse", + "ListModelGatewaysRequest", + "ListModelGatewaysResponse", + "ListModelGatewayProvidersRequest", + "ListModelGatewayProvidersResponse", + "ModelGatewaysForModelGateway", + "ProviderAuthzConfigsForModelGateway", + "ProviderModelsForModelGateway", + "ProvidersItemForCreateModelGateway", + "ProvidersForModelGateway", + "ProviderSpecForModelGateway", + "RetryPolicyForModelGateway", + "TagFiltersForModelGateway", + "TagsForModelGateway", + "TokenRateLimitConfigForModelGateway", + "TokenRateLimitRulesForModelGateway", + "UpdateModelGatewayConsumerRequest", + "UpdateModelGatewayConsumerResponse", + "UpdateModelGatewayProviderRequest", + "UpdateModelGatewayProviderResponse", +] diff --git a/agentkit/sdk/model_gateway/client.py b/agentkit/sdk/model_gateway/client.py new file mode 100644 index 00000000..dd8c496f --- /dev/null +++ b/agentkit/sdk/model_gateway/client.py @@ -0,0 +1,191 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Auto-generated from API JSON definition +# Do not edit manually + +from __future__ import annotations + +from typing import Dict + +from agentkit.client import BaseAgentkitClient + +from .types import ( + CreateModelGatewayRequest, + CreateModelGatewayConsumerRequest, + CreateModelGatewayConsumerResponse, + CreateModelGatewayProviderRequest, + CreateModelGatewayProviderResponse, + CreateModelGatewayResponse, + DeleteModelGatewayConsumerRequest, + DeleteModelGatewayConsumerResponse, + DeleteModelGatewayProviderRequest, + DeleteModelGatewayProviderResponse, + GetModelGatewayConsumerRequest, + GetModelGatewayConsumerResponse, + GetModelGatewayProviderRequest, + GetModelGatewayProviderResponse, + ListModelGatewayConsumersRequest, + ListModelGatewayConsumersResponse, + ListModelGatewaysRequest, + ListModelGatewaysResponse, + ListModelGatewayProvidersRequest, + ListModelGatewayProvidersResponse, + UpdateModelGatewayConsumerRequest, + UpdateModelGatewayConsumerResponse, + UpdateModelGatewayProviderRequest, + UpdateModelGatewayProviderResponse, +) + + +class AgentkitModelGatewayClient(BaseAgentkitClient): + """AgentKit Model Gateway Management Service""" + + API_ACTIONS: Dict[str, str] = { + "CreateModelGateway": "CreateModelGateway", + "CreateModelGatewayConsumer": "CreateModelGatewayConsumer", + "CreateModelGatewayProvider": "CreateModelGatewayProvider", + "DeleteModelGatewayConsumer": "DeleteModelGatewayConsumer", + "DeleteModelGatewayProvider": "DeleteModelGatewayProvider", + "GetModelGatewayConsumer": "GetModelGatewayConsumer", + "GetModelGatewayProvider": "GetModelGatewayProvider", + "ListModelGatewayConsumers": "ListModelGatewayConsumers", + "ListModelGateways": "ListModelGateways", + "ListModelGatewayProviders": "ListModelGatewayProviders", + "UpdateModelGatewayConsumer": "UpdateModelGatewayConsumer", + "UpdateModelGatewayProvider": "UpdateModelGatewayProvider", + } + + def __init__( + self, + access_key: str = "", + secret_key: str = "", + region: str = "", + session_token: str = "", + ) -> None: + super().__init__( + access_key=access_key, + secret_key=secret_key, + region=region, + session_token=session_token, + service_name="model_gateway", + ) + + def create_model_gateway_consumer( + self, request: CreateModelGatewayConsumerRequest + ) -> CreateModelGatewayConsumerResponse: + return self._invoke_api( + api_action="CreateModelGatewayConsumer", + request=request, + response_type=CreateModelGatewayConsumerResponse, + ) + + def create_model_gateway( + self, request: CreateModelGatewayRequest + ) -> CreateModelGatewayResponse: + return self._invoke_api( + api_action="CreateModelGateway", + request=request, + response_type=CreateModelGatewayResponse, + ) + + def create_model_gateway_provider( + self, request: CreateModelGatewayProviderRequest + ) -> CreateModelGatewayProviderResponse: + return self._invoke_api( + api_action="CreateModelGatewayProvider", + request=request, + response_type=CreateModelGatewayProviderResponse, + ) + + def delete_model_gateway_consumer( + self, request: DeleteModelGatewayConsumerRequest + ) -> DeleteModelGatewayConsumerResponse: + return self._invoke_api( + api_action="DeleteModelGatewayConsumer", + request=request, + response_type=DeleteModelGatewayConsumerResponse, + ) + + def delete_model_gateway_provider( + self, request: DeleteModelGatewayProviderRequest + ) -> DeleteModelGatewayProviderResponse: + return self._invoke_api( + api_action="DeleteModelGatewayProvider", + request=request, + response_type=DeleteModelGatewayProviderResponse, + ) + + def get_model_gateway_consumer( + self, request: GetModelGatewayConsumerRequest + ) -> GetModelGatewayConsumerResponse: + return self._invoke_api( + api_action="GetModelGatewayConsumer", + request=request, + response_type=GetModelGatewayConsumerResponse, + ) + + def get_model_gateway_provider( + self, request: GetModelGatewayProviderRequest + ) -> GetModelGatewayProviderResponse: + return self._invoke_api( + api_action="GetModelGatewayProvider", + request=request, + response_type=GetModelGatewayProviderResponse, + ) + + def list_model_gateway_consumers( + self, request: ListModelGatewayConsumersRequest + ) -> ListModelGatewayConsumersResponse: + return self._invoke_api( + api_action="ListModelGatewayConsumers", + request=request, + response_type=ListModelGatewayConsumersResponse, + ) + + def list_model_gateways( + self, request: ListModelGatewaysRequest + ) -> ListModelGatewaysResponse: + return self._invoke_api( + api_action="ListModelGateways", + request=request, + response_type=ListModelGatewaysResponse, + ) + + def list_model_gateway_providers( + self, request: ListModelGatewayProvidersRequest + ) -> ListModelGatewayProvidersResponse: + return self._invoke_api( + api_action="ListModelGatewayProviders", + request=request, + response_type=ListModelGatewayProvidersResponse, + ) + + def update_model_gateway_consumer( + self, request: UpdateModelGatewayConsumerRequest + ) -> UpdateModelGatewayConsumerResponse: + return self._invoke_api( + api_action="UpdateModelGatewayConsumer", + request=request, + response_type=UpdateModelGatewayConsumerResponse, + ) + + def update_model_gateway_provider( + self, request: UpdateModelGatewayProviderRequest + ) -> UpdateModelGatewayProviderResponse: + return self._invoke_api( + api_action="UpdateModelGatewayProvider", + request=request, + response_type=UpdateModelGatewayProviderResponse, + ) diff --git a/agentkit/sdk/model_gateway/types.py b/agentkit/sdk/model_gateway/types.py new file mode 100644 index 00000000..64c0d97d --- /dev/null +++ b/agentkit/sdk/model_gateway/types.py @@ -0,0 +1,382 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Auto-generated from API JSON definition +# Do not edit manually + +from __future__ import annotations + +from typing import Optional + +from pydantic import AliasChoices, BaseModel, Field + + +class ModelGatewayBaseModel(BaseModel): + """AgentKit auto-generated base model""" + + model_config = {"populate_by_name": True, "arbitrary_types_allowed": True} + + +# Data Types +class ApiKeysForModelGateway(ModelGatewayBaseModel): + name: Optional[str] = Field(default=None, alias="Name") + value: Optional[str] = Field(default=None, alias="Value") + + +class AuthzConfigForModelGateway(ModelGatewayBaseModel): + allow_all_providers: Optional[bool] = Field(default=None, alias="AllowAllProviders") + provider_authz_configs: Optional[list[ProviderAuthzConfigsForModelGateway]] = Field( + default=None, alias="ProviderAuthzConfigs" + ) + + +class ConsumerForCreateModelGateway(ModelGatewayBaseModel): + consumer_name: str = Field(..., alias="ConsumerName") + + +class ConsumersForModelGateway(ModelGatewayBaseModel): + consumer_id: Optional[str] = Field(default=None, alias="ConsumerId") + consumer_name: Optional[str] = Field(default=None, alias="ConsumerName") + model_gateway_id: Optional[str] = Field(default=None, alias="ModelGatewayId") + api_keys: Optional[list[str]] = Field(default=None, alias="ApiKeys") + authz_config: Optional[AuthzConfigForModelGateway] = Field( + default=None, alias="AuthzConfig" + ) + token_rate_limit_config: Optional[TokenRateLimitConfigForModelGateway] = Field( + default=None, alias="TokenRateLimitConfig" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + created_at: Optional[str] = Field(default=None, alias="CreatedAt") + updated_at: Optional[str] = Field(default=None, alias="UpdatedAt") + + +class CredentialsForModelGateway(ModelGatewayBaseModel): + type: Optional[str] = Field(default=None, alias="Type") + api_keys: Optional[list[ApiKeysForModelGateway]] = Field( + default=None, alias="ApiKeys" + ) + + +class ModelGatewaysForModelGateway(ModelGatewayBaseModel): + model_gateway_id: Optional[str] = Field(default=None, alias="ModelGatewayId") + retry_policy: Optional[RetryPolicyForModelGateway] = Field( + default=None, alias="RetryPolicy" + ) + project_name: Optional[str] = Field(default=None, alias="ProjectName") + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + status: Optional[str] = Field(default=None, alias="Status") + message: Optional[str] = Field(default=None, alias="Message") + created_at: Optional[str] = Field(default=None, alias="CreatedAt") + updated_at: Optional[str] = Field(default=None, alias="UpdatedAt") + + +class ProviderAuthzConfigsForModelGateway(ModelGatewayBaseModel): + provider_id: Optional[str] = Field(default=None, alias="ProviderId") + allow_all_provider_models: Optional[bool] = Field( + default=None, alias="AllowAllProviderModels" + ) + allowed_provider_model_ids: Optional[list[str]] = Field( + default=None, alias="AllowedProviderModelIds" + ) + + +class ProviderModelsForModelGateway(ModelGatewayBaseModel): + provider_model_id: Optional[str] = Field(default=None, alias="ProviderModelId") + model_name: Optional[str] = Field(default=None, alias="ModelName") + + +class ProviderSpecForModelGateway(ModelGatewayBaseModel): + base_url: Optional[str] = Field(default=None, alias="BaseUrl") + + +class ProvidersForModelGateway(ModelGatewayBaseModel): + provider_id: Optional[str] = Field(default=None, alias="ProviderId") + provider_name: Optional[str] = Field(default=None, alias="ProviderName") + model_gateway_id: Optional[str] = Field(default=None, alias="ModelGatewayId") + base_url: Optional[str] = Field(default=None, alias="BaseUrl") + provider_type: Optional[str] = Field(default=None, alias="ProviderType") + protocols: Optional[list[str]] = Field(default=None, alias="Protocols") + provider_source: Optional[str] = Field(default=None, alias="ProviderSource") + provider_spec: Optional[ProviderSpecForModelGateway] = Field( + default=None, alias="ProviderSpec" + ) + credentials: Optional[CredentialsForModelGateway] = Field( + default=None, alias="Credentials" + ) + provider_models: Optional[list[ProviderModelsForModelGateway]] = Field( + default=None, alias="ProviderModels" + ) + fallback_provider_model_names: Optional[list[str]] = Field( + default=None, alias="FallbackProviderModelNames" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + created_at: Optional[str] = Field(default=None, alias="CreatedAt") + updated_at: Optional[str] = Field(default=None, alias="UpdatedAt") + + +class RetryPolicyForModelGateway(ModelGatewayBaseModel): + enable: Optional[bool] = Field(default=None, alias="Enable") + max_retries: Optional[int] = Field(default=None, alias="MaxRetries") + + +class TagFiltersForModelGateway(ModelGatewayBaseModel): + key: Optional[str] = Field(default=None, alias="Key") + values: Optional[list[str]] = Field(default=None, alias="Values") + + +class TagsForModelGateway(ModelGatewayBaseModel): + key: str = Field(..., alias="Key") + value: Optional[str] = Field(default=None, alias="Value") + + +class TokenRateLimitConfigForModelGateway(ModelGatewayBaseModel): + enable: Optional[bool] = Field(default=None, alias="Enable") + rules: Optional[list[TokenRateLimitRulesForModelGateway]] = Field( + default=None, alias="Rules" + ) + + +class TokenRateLimitRulesForModelGateway(ModelGatewayBaseModel): + enable: Optional[bool] = Field(default=None, alias="Enable") + time_window: Optional[int] = Field(default=None, alias="TimeWindow") + value: Optional[int] = Field(default=None, alias="Value") + + +# CreateModelGateway - Request +class ProvidersItemForCreateModelGateway(ModelGatewayBaseModel): + provider_type: Optional[str] = Field(default=None, alias="ProviderType") + provider_name: str = Field(..., alias="ProviderName") + protocols: Optional[list[str]] = Field(default=None, alias="Protocols") + provider_source: Optional[str] = Field(default=None, alias="ProviderSource") + provider_spec: Optional[ProviderSpecForModelGateway] = Field( + default=None, alias="ProviderSpec" + ) + credentials: Optional[CredentialsForModelGateway] = Field( + default=None, alias="Credentials" + ) + provider_models: Optional[list[ProviderModelsForModelGateway]] = Field( + default=None, alias="ProviderModels" + ) + fallback_provider_model_names: Optional[list[str]] = Field( + default=None, alias="FallbackProviderModelNames" + ) + + +class CreateModelGatewayRequest(ModelGatewayBaseModel): + type: str = Field(..., alias="Type") + apig_gateway_id: Optional[str] = Field(default=None, alias="ApigGatewayId") + providers: Optional[list[ProvidersItemForCreateModelGateway]] = Field( + default=None, alias="Providers" + ) + consumer: ConsumerForCreateModelGateway = Field(..., alias="Consumer") + retry_policy: Optional[RetryPolicyForModelGateway] = Field( + default=None, alias="RetryPolicy" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + + +# CreateModelGateway - Response +class CreateModelGatewayResponse(ModelGatewayBaseModel): + model_gateway_id: Optional[str] = Field(default=None, alias="ModelGatewayId") + + +# ListModelGateways - Request +class ListModelGatewaysRequest(ModelGatewayBaseModel): + status: Optional[str] = Field(default=None, alias="Status") + page_number: Optional[int] = Field(default=None, alias="PageNumber") + page_size: Optional[int] = Field(default=None, alias="PageSize") + tag_filters: Optional[list[TagFiltersForModelGateway]] = Field( + default=None, alias="TagFilters" + ) + + +# ListModelGateways - Response +class ListModelGatewaysResponse(ModelGatewayBaseModel): + total: Optional[int] = Field(default=None, alias="Total") + model_gateways: Optional[list[ModelGatewaysForModelGateway]] = Field( + default=None, alias="ModelGateways" + ) + + +# CreateModelGatewayProvider - Request +class CreateModelGatewayProviderRequest(ModelGatewayBaseModel): + model_gateway_id: str = Field(..., alias="ModelGatewayId") + provider_type: str = Field(..., alias="ProviderType") + provider_name: str = Field(..., alias="ProviderName") + protocols: Optional[list[str]] = Field(default=None, alias="Protocols") + provider_source: Optional[str] = Field(default=None, alias="ProviderSource") + provider_spec: Optional[ProviderSpecForModelGateway] = Field( + default=None, alias="ProviderSpec" + ) + credentials: Optional[CredentialsForModelGateway] = Field( + default=None, alias="Credentials" + ) + provider_models: Optional[list[ProviderModelsForModelGateway]] = Field( + default=None, alias="ProviderModels" + ) + fallback_provider_model_names: Optional[list[str]] = Field( + default=None, alias="FallbackProviderModelNames" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + + +# CreateModelGatewayProvider - Response +class CreateModelGatewayProviderResponse(ModelGatewayBaseModel): + provider_id: Optional[str] = Field(default=None, alias="ProviderId") + + +# UpdateModelGatewayProvider - Request +class UpdateModelGatewayProviderRequest(ModelGatewayBaseModel): + provider_id: str = Field(..., alias="ProviderId") + provider_name: Optional[str] = Field(default=None, alias="ProviderName") + protocols: Optional[list[str]] = Field(default=None, alias="Protocols") + provider_source: Optional[str] = Field(default=None, alias="ProviderSource") + provider_spec: Optional[ProviderSpecForModelGateway] = Field( + default=None, alias="ProviderSpec" + ) + credentials: Optional[CredentialsForModelGateway] = Field( + default=None, alias="Credentials" + ) + provider_models: Optional[list[ProviderModelsForModelGateway]] = Field( + default=None, alias="ProviderModels" + ) + fallback_provider_model_names: Optional[list[str]] = Field( + default=None, alias="FallbackProviderModelNames" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + + +# UpdateModelGatewayProvider - Response +class UpdateModelGatewayProviderResponse(ModelGatewayBaseModel): + provider_id: Optional[str] = Field(default=None, alias="ProviderId") + + +# DeleteModelGatewayProvider - Request +class DeleteModelGatewayProviderRequest(ModelGatewayBaseModel): + provider_id: str = Field(..., alias="ProviderId") + + +# DeleteModelGatewayProvider - Response +class DeleteModelGatewayProviderResponse(ModelGatewayBaseModel): + pass + + +# GetModelGatewayProvider - Request +class GetModelGatewayProviderRequest(ModelGatewayBaseModel): + provider_id: str = Field(..., alias="ProviderId") + + +# GetModelGatewayProvider - Response +class GetModelGatewayProviderResponse(ModelGatewayBaseModel): + provider: Optional[ProvidersForModelGateway] = Field(default=None, alias="Provider") + + +# ListModelGatewayProviders - Request +class ListModelGatewayProvidersRequest(ModelGatewayBaseModel): + provider_name: Optional[str] = Field(default=None, alias="ProviderName") + model_gateway_id: str = Field(..., alias="ModelGatewayId") + page_number: Optional[int] = Field(default=None, alias="PageNumber") + page_size: Optional[int] = Field(default=None, alias="PageSize") + tag_filters: Optional[list[TagFiltersForModelGateway]] = Field( + default=None, alias="TagFilters" + ) + + +# ListModelGatewayProviders - Response +class ListModelGatewayProvidersResponse(ModelGatewayBaseModel): + total: Optional[int] = Field(default=None, alias="Total") + providers: Optional[list[ProvidersForModelGateway]] = Field( + default=None, alias="Providers" + ) + + +# CreateModelGatewayConsumer - Request +class CreateModelGatewayConsumerRequest(ModelGatewayBaseModel): + consumer_name: str = Field(..., alias="ConsumerName") + model_gateway_id: str = Field(..., alias="ModelGatewayId") + authz_config: Optional[AuthzConfigForModelGateway] = Field( + default=None, alias="AuthzConfig" + ) + token_rate_limit_config: Optional[TokenRateLimitConfigForModelGateway] = Field( + default=None, alias="TokenRateLimitConfig" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + tags: Optional[list[TagsForModelGateway]] = Field(default=None, alias="Tags") + + +# CreateModelGatewayConsumer - Response +class CreateModelGatewayConsumerResponse(ModelGatewayBaseModel): + consumer_id: Optional[str] = Field(default=None, alias="ConsumerId") + + +# UpdateModelGatewayConsumer - Request +class UpdateModelGatewayConsumerRequest(ModelGatewayBaseModel): + consumer_id: str = Field(..., alias="ConsumerId") + consumer_name: Optional[str] = Field(default=None, alias="ConsumerName") + authz_config: Optional[AuthzConfigForModelGateway] = Field( + default=None, alias="AuthzConfig" + ) + token_rate_limit_config: Optional[TokenRateLimitConfigForModelGateway] = Field( + default=None, alias="TokenRateLimitConfig" + ) + comments: Optional[str] = Field(default=None, alias="Comments") + + +# UpdateModelGatewayConsumer - Response +class UpdateModelGatewayConsumerResponse(ModelGatewayBaseModel): + consumer_id: Optional[str] = Field(default=None, alias="ConsumerId") + + +# DeleteModelGatewayConsumer - Request +class DeleteModelGatewayConsumerRequest(ModelGatewayBaseModel): + consumer_id: str = Field(..., alias="ConsumerId") + + +# DeleteModelGatewayConsumer - Response +class DeleteModelGatewayConsumerResponse(ModelGatewayBaseModel): + pass + + +# GetModelGatewayConsumer - Request +class GetModelGatewayConsumerRequest(ModelGatewayBaseModel): + consumer_id: str = Field(..., alias="ConsumerId") + + +# GetModelGatewayConsumer - Response +class GetModelGatewayConsumerResponse(ModelGatewayBaseModel): + consumer: Optional[ConsumersForModelGateway] = Field(default=None, alias="Consumer") + + +# ListModelGatewayConsumers - Request +class ListModelGatewayConsumersRequest(ModelGatewayBaseModel): + consumer_name: Optional[str] = Field(default=None, alias="ConsumerName") + model_gateway_id: str = Field(..., alias="ModelGatewayId") + page_number: Optional[int] = Field(default=None, alias="PageNumber") + page_size: Optional[int] = Field(default=None, alias="PageSize") + tag_filters: Optional[list[TagFiltersForModelGateway]] = Field( + default=None, alias="TagFilters" + ) + + +# ListModelGatewayConsumers - Response +class ListModelGatewayConsumersResponse(ModelGatewayBaseModel): + total: Optional[int] = Field(default=None, alias="Total") + consumers: Optional[list[ConsumersForModelGateway]] = Field( + default=None, alias="Consumers" + ) diff --git a/agentkit/toolkit/cli/cli.py b/agentkit/toolkit/cli/cli.py index 1d830863..93c7df15 100644 --- a/agentkit/toolkit/cli/cli.py +++ b/agentkit/toolkit/cli/cli.py @@ -33,6 +33,7 @@ from agentkit.toolkit.cli.cli_destroy import destroy_command from agentkit.toolkit.cli.cli_memory import memory_app from agentkit.toolkit.cli.cli_knowledge import knowledge_app +from agentkit.toolkit.cli.cli_model_gateway import model_gateway_app from agentkit.toolkit.cli.cli_tools import tools_app from agentkit.toolkit.cli.cli_runtime import runtime_app from agentkit.toolkit.cli.cli_skills import skills_app @@ -123,6 +124,7 @@ def main( app.add_typer(auth_app, name="auth") app.add_typer(memory_app, name="memory") app.add_typer(knowledge_app, name="knowledge") +app.add_typer(model_gateway_app, name="model-gateway") app.add_typer(tools_app, name="tools") app.add_typer(runtime_app, name="runtime") app.add_typer(skills_app, name="skills") diff --git a/agentkit/toolkit/cli/cli_model_gateway.py b/agentkit/toolkit/cli/cli_model_gateway.py new file mode 100644 index 00000000..713d03f0 --- /dev/null +++ b/agentkit/toolkit/cli/cli_model_gateway.py @@ -0,0 +1,1109 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AgentKit CLI - Model Gateway commands.""" + +from __future__ import annotations + +from enum import Enum +import json +import time +from typing import List, Optional + +import typer +from rich.console import Console +from rich.panel import Panel +from rich.table import Table + +from agentkit.sdk.model_gateway.client import AgentkitModelGatewayClient +from agentkit.sdk.model_gateway import types as mgw + +console = Console() + +MODEL_GATEWAY_RUNNING_STATUS = "Running" +MODEL_GATEWAY_FAILED_STATUSES = {"CreatedFailed", "UpdatedFailed", "DeletedFailed"} +MODEL_GATEWAY_ERROR_STATUSES = MODEL_GATEWAY_FAILED_STATUSES | {"Error"} + + +class ModelGatewayExampleType(str, Enum): + curl = "curl" + openai = "openai" + anthropic = "anthropic" + agentkit = "agentkit" + + +model_gateway_app = typer.Typer( + name="model-gateway", + help="Manage AgentKit Model Gateway.", + add_completion=False, +) +provider_app = typer.Typer( + name="provider", + help="Manage model gateway providers.", + add_completion=False, +) +consumer_app = typer.Typer( + name="consumer", + help="Manage model gateway consumers.", + add_completion=False, +) + + +def _print_api_error(action: str, exc: Exception) -> None: + msg = str(exc) + server_message = None + code = None + try: + start = msg.find("{") + end = msg.rfind("}") + if start != -1 and end > start: + payload = json.loads(msg[start : end + 1]) + err = payload.get("ResponseMetadata", {}).get("Error", {}) + code = err.get("Code") + server_message = err.get("Message") + except Exception: + pass + + lines = [] + if code: + lines.append(f"Code: [yellow]{code}[/yellow]") + lines.append(f"Message: [red]{server_message or msg}[/red]") + console.print( + Panel.fit( + "\n".join(lines), + title=f"{action} Error", + border_style="red", + ) + ) + + +def _client(region: Optional[str]) -> AgentkitModelGatewayClient: + return AgentkitModelGatewayClient(region=(region or "").strip()) + + +def _require_value(option_name: str, value: str) -> str: + normalized = value.strip() + if not normalized: + raise typer.BadParameter(f"{option_name} cannot be empty") + return normalized + + +def _require_values(option_name: str, values: List[str]) -> List[str]: + if not values: + raise typer.BadParameter(f"{option_name} requires at least one value") + return [_require_value(option_name, value) for value in values] + + +def _require_single_protocol(values: Optional[List[str]]) -> List[str]: + protocols = _require_values("--protocol", values or []) + if len(protocols) != 1: + raise typer.BadParameter("--protocol currently requires exactly one value") + return protocols + + +def _require_allow_models(values: Optional[List[str]]) -> Optional[List[str]]: + if not values: + return None + + normalized_values = _require_values("--allow-model", values) + for item in normalized_values: + if "/" not in item: + _require_value("--allow-model provider name", item) + continue + provider_name, model_name = item.split("/", 1) + _require_value("--allow-model provider name", provider_name) + _require_value("--allow-model model name", model_name) + return normalized_values + + +def _get_model_gateway( + client: AgentkitModelGatewayClient, + model_gateway_id: str, +) -> mgw.ModelGatewaysForModelGateway: + resp = client.list_model_gateways( + mgw.ListModelGatewaysRequest(page_number=1, page_size=100) + ) + for gateway in resp.model_gateways or []: + if gateway.model_gateway_id == model_gateway_id: + return gateway + raise RuntimeError(f"Model gateway not found: {model_gateway_id}") + + +def _wait_model_gateway_running( + client: AgentkitModelGatewayClient, + model_gateway_id: str, + action: str, + timeout_seconds: int = 120, + poll_interval_seconds: int = 2, +) -> mgw.ModelGatewaysForModelGateway: + time.sleep(1) + deadline = time.monotonic() + timeout_seconds + last_status = None + with console.status( + "[cyan]Waiting for ModelGateway status to become Running...[/cyan]", + spinner="dots", + ) as wait_status: + while True: + gateway = _get_model_gateway(client, model_gateway_id) + status = gateway.status or "" + if status != last_status: + last_status = status + wait_status.update( + f"[cyan]ModelGateway status: {status or 'Unknown'}[/cyan]" + ) + if status == MODEL_GATEWAY_RUNNING_STATUS: + return gateway + if status in MODEL_GATEWAY_ERROR_STATUSES: + message = f"ModelGateway status is {status}" + if gateway.message: + message = f"{message}: {gateway.message}" + raise RuntimeError(message) + if time.monotonic() >= deadline: + raise RuntimeError( + f"Timed out waiting for ModelGateway to become Running " + f"after {action}. Last status: {status or 'Unknown'}" + ) + time.sleep(poll_interval_seconds) + + +def _resolve_model_gateway_id( + client: AgentkitModelGatewayClient, +) -> str: + resp = client.list_model_gateways( + mgw.ListModelGatewaysRequest(page_number=1, page_size=100) + ) + gateways = resp.model_gateways or [] + if gateways and gateways[0].model_gateway_id: + return gateways[0].model_gateway_id + raise typer.BadParameter( + "No model gateway found. Run 'agentkit model-gateway activate' first." + ) + + +def _normalize_protocol(protocol: str) -> str: + value = protocol.strip() + aliases = { + "openai": "OpenAICompatible", + "openai-compatible": "OpenAICompatible", + "OpenAICompatible": "OpenAICompatible", + "anthropic": "AnthropicCompatible", + "anthropic-compatible": "AnthropicCompatible", + "AnthropicCompatible": "AnthropicCompatible", + } + if value not in aliases: + raise typer.BadParameter( + "--protocol must be one of: openai, anthropic, " + "OpenAICompatible, AnthropicCompatible" + ) + return aliases[value] + + +def _build_credentials(api_keys: List[str]) -> mgw.CredentialsForModelGateway: + return mgw.CredentialsForModelGateway( + type="APIKey", + api_keys=[ + mgw.ApiKeysForModelGateway(name=f"key_{idx}", value=value) + for idx, value in enumerate(api_keys, start=1) + ], + ) + + +def _provider_models(models: List[str]) -> List[mgw.ProviderModelsForModelGateway]: + return [mgw.ProviderModelsForModelGateway(model_name=model) for model in models] + + +def _provider_table(providers: List[mgw.ProvidersForModelGateway]) -> Table: + table = Table(title="Model Gateway Providers") + table.add_column("Provider ID", style="cyan") + table.add_column("Name", style="white") + table.add_column("Access URL", style="blue") + table.add_column("Models", style="green") + for item in providers: + models = ", ".join(m.model_name or "" for m in item.provider_models or []) + table.add_row( + item.provider_id or "", + item.provider_name or "", + item.base_url or "", + models, + ) + return table + + +def _consumer_table( + consumers: List[mgw.ConsumersForModelGateway], + provider_names_by_id: Optional[dict[str, str]] = None, + provider_model_names_by_id: Optional[dict[str, str]] = None, +) -> Table: + provider_names_by_id = provider_names_by_id or {} + provider_model_names_by_id = provider_model_names_by_id or {} + table = Table(title="Model Gateway Consumers") + table.add_column("Consumer ID", style="cyan") + table.add_column("Name", style="white") + table.add_column("Authorization", style="green") + table.add_column("TPM", style="magenta") + table.add_column("TPD", style="magenta") + for item in consumers: + tpm, tpd = _consumer_rate_limits(item) + table.add_row( + item.consumer_id or "", + item.consumer_name or "", + _consumer_authz(item, provider_names_by_id, provider_model_names_by_id), + tpm, + tpd, + ) + return table + + +def _print_fields(fields: List[tuple[str, str]]) -> None: + for key, value in fields: + typer.echo(f"{key}: {value}") + + +def _provider_show(provider: mgw.ProvidersForModelGateway) -> None: + models = ", ".join(m.model_name or "" for m in provider.provider_models or []) + base_url = provider.provider_spec.base_url if provider.provider_spec else "" + api_keys = ( + ", ".join(key.value or "" for key in provider.credentials.api_keys or []) + if provider.credentials + else "" + ) + _print_fields( + [ + ("Provider ID", provider.provider_id or ""), + ("Name", provider.provider_name or ""), + ("Access URL", provider.base_url or ""), + ("Base URL", base_url), + ("API Keys", api_keys), + ("Models", models), + ] + ) + + +def _consumer_authz( + consumer: mgw.ConsumersForModelGateway, + provider_names_by_id: dict[str, str], + provider_model_names_by_id: dict[str, str], +) -> str: + if not consumer.authz_config: + return "" + if consumer.authz_config.allow_all_providers: + return "All" + if not consumer.authz_config.provider_authz_configs: + return "" + + parts = [] + for config in consumer.authz_config.provider_authz_configs: + provider_id = config.provider_id or "" + provider_name = provider_names_by_id.get(provider_id, provider_id) + if config.allow_all_provider_models: + parts.append(f"{provider_name}:*") + else: + for model_id in config.allowed_provider_model_ids or []: + model_name = provider_model_names_by_id.get(model_id, model_id) + parts.append(f"{provider_name}:{model_name}") + return "; ".join(parts) + + +def _consumer_rate_limits(consumer: mgw.ConsumersForModelGateway) -> tuple[str, str]: + tpm = "" + tpd = "" + if consumer.token_rate_limit_config: + for rule in consumer.token_rate_limit_config.rules or []: + if rule.time_window == 60: + tpm = str(rule.value or "") + elif rule.time_window == 86400: + tpd = str(rule.value or "") + return tpm, tpd + + +def _consumer_show( + consumer: mgw.ConsumersForModelGateway, + provider_names_by_id: dict[str, str], + provider_model_names_by_id: dict[str, str], +) -> None: + tpm, tpd = _consumer_rate_limits(consumer) + _print_fields( + [ + ("Consumer ID", consumer.consumer_id or ""), + ("Name", consumer.consumer_name or ""), + ("API Keys", ", ".join(consumer.api_keys or [])), + ( + "Authorization", + _consumer_authz( + consumer, + provider_names_by_id, + provider_model_names_by_id, + ), + ), + ("TPM", tpm), + ("TPD", tpd), + ] + ) + + +def _provider_authz_display_maps( + client: AgentkitModelGatewayClient, + model_gateway_id: str, +) -> tuple[dict[str, str], dict[str, str]]: + resp = client.list_model_gateway_providers( + mgw.ListModelGatewayProvidersRequest( + model_gateway_id=model_gateway_id, + page_number=1, + page_size=100, + ) + ) + provider_names_by_id = {} + provider_model_names_by_id = {} + for item in resp.providers or []: + if item.provider_id: + provider_names_by_id[item.provider_id] = ( + item.provider_name or item.provider_id + ) + for model in item.provider_models or []: + if model.provider_model_id: + provider_model_names_by_id[model.provider_model_id] = ( + model.model_name or model.provider_model_id + ) + return provider_names_by_id, provider_model_names_by_id + + +def _find_provider_by_name( + client: AgentkitModelGatewayClient, + model_gateway_id: str, + name: str, +) -> mgw.ProvidersForModelGateway: + resp = client.list_model_gateway_providers( + mgw.ListModelGatewayProvidersRequest( + model_gateway_id=model_gateway_id, + provider_name=name, + page_number=1, + page_size=100, + ) + ) + matches = [p for p in resp.providers or [] if p.provider_name == name] + if len(matches) != 1 or not matches[0].provider_id: + raise typer.BadParameter(f"Provider not found or ambiguous: {name}") + return matches[0] + + +def _find_consumer_by_name( + client: AgentkitModelGatewayClient, + model_gateway_id: str, + name: str, +) -> mgw.ConsumersForModelGateway: + resp = client.list_model_gateway_consumers( + mgw.ListModelGatewayConsumersRequest( + model_gateway_id=model_gateway_id, + consumer_name=name, + page_number=1, + page_size=100, + ) + ) + matches = [c for c in resp.consumers or [] if c.consumer_name == name] + if len(matches) != 1 or not matches[0].consumer_id: + raise typer.BadParameter(f"Consumer not found or ambiguous: {name}") + return matches[0] + + +def _build_token_rate_limit_config( + tpm: Optional[int], + tpd: Optional[int], +) -> Optional[mgw.TokenRateLimitConfigForModelGateway]: + rules = [] + if tpm is not None: + rules.append(mgw.TokenRateLimitRulesForModelGateway(time_window=60, value=tpm)) + if tpd is not None: + rules.append( + mgw.TokenRateLimitRulesForModelGateway(time_window=86400, value=tpd) + ) + if not rules: + return None + return mgw.TokenRateLimitConfigForModelGateway(enable=True, rules=rules) + + +def _build_authz_config( + client: AgentkitModelGatewayClient, + model_gateway_id: str, + allow_models: Optional[List[str]], +) -> Optional[mgw.AuthzConfigForModelGateway]: + if not allow_models: + return None + + grouped: dict[str, list[str]] = {} + allow_all_provider_ids: set[str] = set() + for item in allow_models: + if "/" not in item: + provider_name = _require_value("--allow-model provider name", item) + provider = _find_provider_by_name(client, model_gateway_id, provider_name) + allow_all_provider_ids.add(provider.provider_id or "") + grouped.pop(provider.provider_id or "", None) + continue + provider_name, model_name = item.split("/", 1) + provider_name = _require_value("--allow-model provider name", provider_name) + model_name = _require_value("--allow-model model name", model_name) + provider_item = _find_provider_by_name(client, model_gateway_id, provider_name) + provider_resp = client.get_model_gateway_provider( + mgw.GetModelGatewayProviderRequest(provider_id=provider_item.provider_id) + ) + provider = provider_resp.provider or provider_item + provider_id = provider.provider_id or "" + provider_model_id = "" + for model in provider.provider_models or []: + if model.model_name == model_name: + provider_model_id = model.provider_model_id or "" + break + if not provider_model_id: + raise typer.BadParameter( + f"Model not found or has no ID for provider '{provider_name}': " + f"{model_name}" + ) + if provider_id not in allow_all_provider_ids: + grouped.setdefault(provider_id, []).append(provider_model_id) + + provider_authz_configs = [ + mgw.ProviderAuthzConfigsForModelGateway( + provider_id=provider_id, + allow_all_provider_models=True, + ) + for provider_id in allow_all_provider_ids + ] + provider_authz_configs.extend( + mgw.ProviderAuthzConfigsForModelGateway( + provider_id=provider_id, + allow_all_provider_models=False, + allowed_provider_model_ids=models, + ) + for provider_id, models in grouped.items() + ) + + return mgw.AuthzConfigForModelGateway( + allow_all_providers=False, + provider_authz_configs=provider_authz_configs, + ) + + +@model_gateway_app.command("show-example") +def show_example_command( + provider: str = typer.Option(..., "--provider", help="Provider name"), + consumer: str = typer.Option(..., "--consumer", help="Consumer name"), + example: ModelGatewayExampleType = typer.Option( + ModelGatewayExampleType.curl, + "--example", + help="Example type: curl, openai, anthropic, or agentkit", + ), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Show an example for calling the model gateway.""" + provider = _require_value("--provider", provider) + consumer = _require_value("--consumer", consumer) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + provider_item = _find_provider_by_name(client, gateway_id, provider) + provider_resp = client.get_model_gateway_provider( + mgw.GetModelGatewayProviderRequest(provider_id=provider_item.provider_id) + ) + consumer_item = _find_consumer_by_name(client, gateway_id, consumer) + consumer_resp = client.get_model_gateway_consumer( + mgw.GetModelGatewayConsumerRequest(consumer_id=consumer_item.consumer_id) + ) + + if not provider_resp.provider: + raise typer.BadParameter(f"Provider not found: {provider}") + if not consumer_resp.consumer: + raise typer.BadParameter(f"Consumer not found: {consumer}") + + access_url = provider_resp.provider.base_url or "" + if not access_url: + raise typer.BadParameter(f"Provider has no Access URL: {provider}") + + api_keys = consumer_resp.consumer.api_keys or [] + api_key = api_keys[0] if api_keys else "" + if not api_key: + raise typer.BadParameter(f"Consumer has no API key: {consumer}") + + protocols = provider_resp.provider.protocols or [] + protocol = protocols[0] if protocols else "OpenAICompatible" + if protocol == "OpenAICompatible": + path = "/chat/completions" + elif protocol == "AnthropicCompatible": + path = "/messages" + else: + raise typer.BadParameter(f"Unsupported provider protocol: {protocol}") + + provider_models = [ + (item.provider_model_id or "", item.model_name) + for item in provider_resp.provider.provider_models or [] + if item.model_name + ] + if not provider_models: + raise typer.BadParameter(f"Provider has no model: {provider}") + + provider_id = provider_resp.provider.provider_id or provider_item.provider_id + authz_config = consumer_resp.consumer.authz_config + accessible_models: List[str] = [] + if authz_config and authz_config.allow_all_providers: + accessible_models = [model_name for _, model_name in provider_models] + elif authz_config and authz_config.provider_authz_configs: + allow_all_models = False + allowed_model_ids: set[str] = set() + for config in authz_config.provider_authz_configs: + if config.provider_id != provider_id: + continue + if config.allow_all_provider_models: + allow_all_models = True + break + allowed_model_ids.update(config.allowed_provider_model_ids or []) + if allow_all_models: + accessible_models = [model_name for _, model_name in provider_models] + else: + accessible_models = [ + model_name + for model_id, model_name in provider_models + if model_id in allowed_model_ids + ] + if not accessible_models: + console.print( + Panel.fit( + f"No accessible model for consumer '{consumer}' " + f"on provider '{provider}'.", + title="ShowModelGatewayExample Warning", + border_style="yellow", + ) + ) + return + model = accessible_models[0] + + if example == ModelGatewayExampleType.curl: + typer.echo( + f'curl "{access_url}{path}" \\\n' + ' -H "Content-Type: application/json" \\\n' + f' -H "Authorization: Bearer {api_key}" \\\n' + " -d '{\n" + f' "model": "{model}",\n' + ' "messages": [\n' + " {\n" + ' "role": "user",\n' + ' "content": "Hello, world"\n' + " }\n" + " ]\n" + " }'" + ) + elif example == ModelGatewayExampleType.openai: + if protocol != "OpenAICompatible": + raise typer.BadParameter( + f"OpenAI example requires OpenAI-Compatible protocol: {protocol}" + ) + typer.echo( + "from openai import OpenAI\n\n" + "client = OpenAI(\n" + f' api_key="{api_key}",\n' + f' base_url="{access_url}",\n' + ")\n\n" + "completion = client.chat.completions.create(\n" + f' model="{model}",\n' + " messages=[\n" + " {\n" + ' "role": "user",\n' + ' "content": "你是谁?",\n' + " }\n" + " ],\n" + ")\n\n" + "print(completion.choices[0].message)" + ) + elif example == ModelGatewayExampleType.anthropic: + if protocol != "AnthropicCompatible": + raise typer.BadParameter( + f"Anthropic example requires Anthropic-Compatible protocol: {protocol}" + ) + typer.echo( + "from anthropic import Anthropic\n\n" + "client = Anthropic(\n" + f' api_key="{api_key}",\n' + f' base_url="{access_url}",\n' + ")\n\n" + "for message in client.messages.create(\n" + " max_tokens=1024,\n" + " messages=[\n" + " {\n" + ' "role": "user",\n' + ' "content": "Hello, world",\n' + " }\n" + " ],\n" + f' model="{model}",\n' + "):\n" + " print(message)" + ) + elif example == ModelGatewayExampleType.agentkit: + if protocol != "OpenAICompatible": + raise typer.BadParameter( + f"AgentKit example requires OpenAI-Compatible protocol: {protocol}" + ) + typer.echo( + "agentkit config \n" + f" -e MODEL_AGENT_NAME={model} \\\n" + f" -e MODEL_AGENT_API_BASE={access_url} \\\n" + f" -e MODEL_AGENT_API_KEY={api_key}" + ) + except Exception as e: + _print_api_error("ShowModelGatewayExample", e) + raise typer.Exit(1) + + +@model_gateway_app.command("activate") +def activate_command( + apig_gateway_id: Optional[str] = typer.Option( + None, "--apig-gateway-id", help="API Gateway instance ID" + ), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Activate the model gateway.""" + if apig_gateway_id is not None: + apig_gateway_id = _require_value("--apig-gateway-id", apig_gateway_id) + try: + client = _client(region) + existing = client.list_model_gateways( + mgw.ListModelGatewaysRequest(page_number=1, page_size=100) + ) + gateways = existing.model_gateways or [] + if gateways and gateways[0].model_gateway_id: + gateway = gateways[0] + status = gateway.status or "Unknown" + title = "ModelGateway Already Exists" + border_style = "green" + state = "[green]Already exists[/green]" + if status != MODEL_GATEWAY_RUNNING_STATUS: + border_style = ( + "red" if status in MODEL_GATEWAY_ERROR_STATUSES else "yellow" + ) + state = ( + "[red]Already exists with error status[/red]" + if status in MODEL_GATEWAY_ERROR_STATUSES + else "[yellow]Already exists but not Running[/yellow]" + ) + lines = [ + state, + f"ModelGatewayId: {gateway.model_gateway_id}", + f"Status: {status}", + ] + if gateway.message: + lines.append(f"Message: {gateway.message}") + console.print( + Panel.fit( + "\n".join(lines), + title=title, + border_style=border_style, + ) + ) + if status in MODEL_GATEWAY_ERROR_STATUSES: + raise typer.Exit(1) + return + + resp = client.create_model_gateway( + mgw.CreateModelGatewayRequest( + type="Standard" if apig_gateway_id else "Shared", + apig_gateway_id=apig_gateway_id, + consumer=mgw.ConsumerForCreateModelGateway(consumer_name="default"), + ) + ) + if resp.model_gateway_id: + _wait_model_gateway_running( + client, + resp.model_gateway_id, + "CreateModelGateway", + ) + console.print( + Panel.fit( + "[green]Activated[/green]\n" f"ModelGatewayId: {resp.model_gateway_id}", + title="CreateModelGateway", + border_style="green", + ) + ) + except typer.Exit: + raise + except Exception as e: + _print_api_error("CreateModelGateway", e) + raise typer.Exit(1) + + +@provider_app.command("add") +def provider_add_command( + name: str = typer.Option(..., "--name", help="Provider name"), + base_url: str = typer.Option(..., "--base-url", help="Provider base URL"), + api_keys: List[str] = typer.Option(..., "--api-key", help="API key, repeatable"), + models: List[str] = typer.Option(..., "--model", help="Model name, repeatable"), + protocol: List[str] = typer.Option( + ["openai"], "--protocol", help="openai or anthropic, exactly once" + ), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Add a provider.""" + name = _require_value("--name", name) + base_url = _require_value("--base-url", base_url) + api_keys = _require_values("--api-key", api_keys) + models = _require_values("--model", models) + protocol = _require_single_protocol(protocol) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + resp = client.create_model_gateway_provider( + mgw.CreateModelGatewayProviderRequest( + model_gateway_id=gateway_id, + provider_type="Custom", + provider_name=name, + protocols=[_normalize_protocol(p) for p in protocol], + provider_source="Domain", + provider_spec=mgw.ProviderSpecForModelGateway(base_url=base_url), + credentials=_build_credentials(api_keys), + provider_models=_provider_models(models), + ) + ) + _wait_model_gateway_running(client, gateway_id, "CreateModelGatewayProvider") + console.print( + Panel.fit( + f"[green]Added[/green]\nProviderId: {resp.provider_id}", + title="CreateModelGatewayProvider", + border_style="green", + ) + ) + except Exception as e: + _print_api_error("CreateModelGatewayProvider", e) + raise typer.Exit(1) + + +@provider_app.command("list") +def provider_list_command( + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """List providers.""" + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + resp = client.list_model_gateway_providers( + mgw.ListModelGatewayProvidersRequest( + model_gateway_id=gateway_id, + page_number=1, + page_size=100, + ) + ) + console.print(_provider_table(resp.providers or [])) + except Exception as e: + _print_api_error("ListModelGatewayProviders", e) + raise typer.Exit(1) + + +@provider_app.command("show") +def provider_show_command( + name: str = typer.Option(..., "--name", help="Provider name"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Show a provider by name.""" + name = _require_value("--name", name) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + provider = _find_provider_by_name(client, gateway_id, name) + resp = client.get_model_gateway_provider( + mgw.GetModelGatewayProviderRequest(provider_id=provider.provider_id) + ) + if resp.provider: + _provider_show(resp.provider) + except Exception as e: + _print_api_error("GetModelGatewayProvider", e) + raise typer.Exit(1) + + +@provider_app.command("update") +def provider_update_command( + name: str = typer.Option(..., "--name", help="Provider name"), + base_url: Optional[str] = typer.Option( + None, "--base-url", help="Provider base URL" + ), + api_keys: Optional[List[str]] = typer.Option( + None, "--api-key", help="API key, repeatable" + ), + models: Optional[List[str]] = typer.Option( + None, "--model", help="Model name, repeatable" + ), + protocol: Optional[List[str]] = typer.Option( + None, "--protocol", help="openai or anthropic, repeatable" + ), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Update a provider by name.""" + name = _require_value("--name", name) + if base_url is not None: + base_url = _require_value("--base-url", base_url) + if api_keys is not None: + api_keys = _require_values("--api-key", api_keys) + if models is not None: + models = _require_values("--model", models) + if protocol: + protocol = _require_single_protocol(protocol) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + provider = _find_provider_by_name(client, gateway_id, name) + request_kwargs = {} + if protocol: + request_kwargs["protocols"] = [_normalize_protocol(p) for p in protocol] + if base_url is not None: + request_kwargs["provider_source"] = "Domain" + request_kwargs["provider_spec"] = mgw.ProviderSpecForModelGateway( + base_url=base_url + ) + if api_keys is not None: + request_kwargs["credentials"] = _build_credentials(api_keys) + if models is not None: + provider_resp = client.get_model_gateway_provider( + mgw.GetModelGatewayProviderRequest(provider_id=provider.provider_id) + ) + current_provider = provider_resp.provider or provider + provider_models = [] + existing_models = { + item.model_name: item + for item in current_provider.provider_models or [] + if item.model_name + } + for model_name in models: + existing_model = existing_models.get(model_name) + if not existing_model: + provider_models.append( + mgw.ProviderModelsForModelGateway(model_name=model_name) + ) + continue + if not existing_model.provider_model_id: + raise typer.BadParameter( + f"Existing provider model has no ID: {model_name}" + ) + provider_models.append( + mgw.ProviderModelsForModelGateway( + provider_model_id=existing_model.provider_model_id, + model_name=model_name, + ) + ) + request_kwargs["provider_models"] = provider_models + resp = client.update_model_gateway_provider( + mgw.UpdateModelGatewayProviderRequest( + provider_id=provider.provider_id, + **request_kwargs, + ) + ) + _wait_model_gateway_running(client, gateway_id, "UpdateModelGatewayProvider") + console.print( + Panel.fit( + f"[green]Updated[/green]\nProviderId: {resp.provider_id}", + title="UpdateModelGatewayProvider", + border_style="green", + ) + ) + except Exception as e: + _print_api_error("UpdateModelGatewayProvider", e) + raise typer.Exit(1) + + +@provider_app.command("delete") +def provider_delete_command( + name: str = typer.Option(..., "--name", help="Provider name"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Delete a provider by name.""" + name = _require_value("--name", name) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + provider = _find_provider_by_name(client, gateway_id, name) + client.delete_model_gateway_provider( + mgw.DeleteModelGatewayProviderRequest(provider_id=provider.provider_id) + ) + _wait_model_gateway_running(client, gateway_id, "DeleteModelGatewayProvider") + console.print( + Panel.fit("[green]Deleted[/green]", title="DeleteModelGatewayProvider") + ) + except Exception as e: + _print_api_error("DeleteModelGatewayProvider", e) + raise typer.Exit(1) + + +@consumer_app.command("add") +def consumer_add_command( + name: str = typer.Option(..., "--name", help="Consumer name"), + allow_models: Optional[List[str]] = typer.Option( + None, + "--allow-model", + help="provider-name or provider-name/model-name, repeatable", + ), + tpm: Optional[int] = typer.Option(None, "--tpm", help="Tokens per minute"), + tpd: Optional[int] = typer.Option(None, "--tpd", help="Tokens per day"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Add a consumer.""" + name = _require_value("--name", name) + allow_models = _require_allow_models(allow_models) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + resp = client.create_model_gateway_consumer( + mgw.CreateModelGatewayConsumerRequest( + consumer_name=name, + model_gateway_id=gateway_id, + authz_config=_build_authz_config(client, gateway_id, allow_models), + token_rate_limit_config=_build_token_rate_limit_config(tpm, tpd), + ) + ) + _wait_model_gateway_running(client, gateway_id, "CreateModelGatewayConsumer") + console.print( + Panel.fit( + f"[green]Added[/green]\nConsumerId: {resp.consumer_id}", + title="CreateModelGatewayConsumer", + border_style="green", + ) + ) + except Exception as e: + _print_api_error("CreateModelGatewayConsumer", e) + raise typer.Exit(1) + + +@consumer_app.command("list") +def consumer_list_command( + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """List consumers.""" + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + resp = client.list_model_gateway_consumers( + mgw.ListModelGatewayConsumersRequest( + model_gateway_id=gateway_id, + page_number=1, + page_size=100, + ) + ) + provider_names_by_id, provider_model_names_by_id = _provider_authz_display_maps( + client, + gateway_id, + ) + console.print( + _consumer_table( + resp.consumers or [], + provider_names_by_id, + provider_model_names_by_id, + ) + ) + except Exception as e: + _print_api_error("ListModelGatewayConsumers", e) + raise typer.Exit(1) + + +@consumer_app.command("show") +def consumer_show_command( + name: str = typer.Option(..., "--name", help="Consumer name"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Show a consumer by name.""" + name = _require_value("--name", name) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + consumer = _find_consumer_by_name(client, gateway_id, name) + resp = client.get_model_gateway_consumer( + mgw.GetModelGatewayConsumerRequest(consumer_id=consumer.consumer_id) + ) + if resp.consumer: + ( + provider_names_by_id, + provider_model_names_by_id, + ) = _provider_authz_display_maps(client, gateway_id) + _consumer_show( + resp.consumer, + provider_names_by_id, + provider_model_names_by_id, + ) + except Exception as e: + _print_api_error("GetModelGatewayConsumer", e) + raise typer.Exit(1) + + +@consumer_app.command("update") +def consumer_update_command( + name: str = typer.Option(..., "--name", help="Consumer name"), + allow_models: Optional[List[str]] = typer.Option( + None, + "--allow-model", + help="provider-name or provider-name/model-name, repeatable", + ), + tpm: Optional[int] = typer.Option(None, "--tpm", help="Tokens per minute"), + tpd: Optional[int] = typer.Option(None, "--tpd", help="Tokens per day"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Update a consumer by name.""" + name = _require_value("--name", name) + allow_models = _require_allow_models(allow_models) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + consumer = _find_consumer_by_name(client, gateway_id, name) + request_kwargs = {} + if allow_models is not None: + request_kwargs["authz_config"] = _build_authz_config( + client, gateway_id, allow_models + ) + if tpm is not None or tpd is not None: + request_kwargs["token_rate_limit_config"] = _build_token_rate_limit_config( + tpm, tpd + ) + resp = client.update_model_gateway_consumer( + mgw.UpdateModelGatewayConsumerRequest( + consumer_id=consumer.consumer_id, + **request_kwargs, + ) + ) + _wait_model_gateway_running(client, gateway_id, "UpdateModelGatewayConsumer") + console.print( + Panel.fit( + f"[green]Updated[/green]\nConsumerId: {resp.consumer_id}", + title="UpdateModelGatewayConsumer", + border_style="green", + ) + ) + except Exception as e: + _print_api_error("UpdateModelGatewayConsumer", e) + raise typer.Exit(1) + + +@consumer_app.command("delete") +def consumer_delete_command( + name: str = typer.Option(..., "--name", help="Consumer name"), + region: Optional[str] = typer.Option(None, "--region", help="Region override"), +): + """Delete a consumer by name.""" + name = _require_value("--name", name) + try: + client = _client(region) + gateway_id = _resolve_model_gateway_id(client) + consumer = _find_consumer_by_name(client, gateway_id, name) + client.delete_model_gateway_consumer( + mgw.DeleteModelGatewayConsumerRequest(consumer_id=consumer.consumer_id) + ) + _wait_model_gateway_running(client, gateway_id, "DeleteModelGatewayConsumer") + console.print( + Panel.fit("[green]Deleted[/green]", title="DeleteModelGatewayConsumer") + ) + except Exception as e: + _print_api_error("DeleteModelGatewayConsumer", e) + raise typer.Exit(1) + + +model_gateway_app.add_typer(provider_app, name="provider") +model_gateway_app.add_typer(consumer_app, name="consumer") diff --git a/docs/content/9.model-gateway/1.model_gateway_quickstart.md b/docs/content/9.model-gateway/1.model_gateway_quickstart.md new file mode 100644 index 00000000..f91c7804 --- /dev/null +++ b/docs/content/9.model-gateway/1.model_gateway_quickstart.md @@ -0,0 +1,142 @@ +# AgentKit 模型网关快速开始指南 + +模型网关提供统一的模型访问调用入口。您可以将火山方舟等模型服务配置为供应商,再创建消费者获取访问密钥,最后通过模型网关的 `/chat/completions` 接口调用模型。 + +本文以火山方舟 `doubao-seed-evolving` 为例。 + +## 准备工作 + +请先完成 AgentKit CLI 安装,并配置火山引擎 AK/SK: + +```bash +export VOLCENGINE_ACCESS_KEY= +export VOLCENGINE_SECRET_KEY= +``` + +## 1. 激活模型网关 + +执行 `activate` 创建或复用当前账号下的模型网关。是否传入 `--apig-gateway-id` 决定模型网关类型: + +- 不传 `--apig-gateway-id`:创建共享型模型网关,适合快速启动和体验。 +- 传入 `--apig-gateway-id`:创建标准型模型网关,适合需要 VPC 内私网访问、自定义入站/出站网络规格,或对稳定性要求更高的生产场景。 + +快速启动可以直接创建共享型模型网关: + +```bash +agentkit model-gateway activate +``` + +如果需要标准型模型网关,请先准备 API 网关实例 ID,再执行: + +```bash +agentkit model-gateway activate --apig-gateway-id +``` + +如果已存在模型网关,CLI 会直接提示已存在,并展示 `ModelGatewayId` 和当前状态。只有状态为 `Running` 时,后续配置和调用才可正常进行。 + +## 2. 添加火山方舟供应商 + +使用火山方舟 OpenAI Compatible 接口作为供应商: + +```bash +agentkit model-gateway provider add \ + --name quickstart \ + --base-url https://ark.cn-beijing.volces.com/api/v3 \ + --api-key \ + --model doubao-seed-evolving +``` + +说明: + +- `--name quickstart` 是供应商名称,后续消费者授权会用到。 +- `--base-url` 是火山方舟 OpenAI 兼容协议的 Base URL。 +- `--api-key` 传入火山方舟 API Key。 +- `--model` 配置该供应商支持的模型名。 +- `--protocol` OpenAI 兼容协议使用 `openai`,Anthropic 兼容协议使用 `anthropic`,通常无需显式传入。 + +供应商创建是异步操作。命令执行后 CLI 会等待模型网关回到 `Running` 状态。 + +## 3. 查看供应商 + +通过 `provider show` 确认供应商配置,重点检查 `Access URL`、`Base URL` 和 `Models`: + +```bash +agentkit model-gateway provider show --name quickstart +``` + +输出会逐行展示。调用模型网关时使用 `Access URL`,它是模型网关代理后的访问地址;`Base URL` 是供应商真实地址。 + +```text +Provider ID: p-ab*******************uv +Name: quickstart +Access URL: https://sab****************uv.apigateway-cn-beijing.volceapi.com/p-ab*******************uv +Base URL: https://ark.cn-beijing.volces.com/api/v3 +API Keys: 01******-****-****-****-**********ef +Models: doubao-seed-evolving +``` + +## 4. 创建消费者 + +创建消费者,并授权它访问 `ark` 供应商下的所有模型: + +```bash +agentkit model-gateway consumer add \ + --name quickstart \ + --allow-model quickstart +``` + +如果只想授权单个模型,也可以写成: + +```bash +agentkit model-gateway consumer add \ + --name quickstart \ + --allow-model quickstart/doubao-seed-evolving +``` + +消费者创建同样是异步操作。命令执行后 CLI 会等待模型网关回到 `Running` 状态。 + +## 5. 查看消费者密钥 + +通过 `consumer show` 获取消费者的 API Key: + +```bash +agentkit model-gateway consumer show --name quickstart +``` + +输出会逐行展示。调用模型网关时使用 `API Keys` 中的密钥。 + +```text +Consumer ID: c-ab*******************uv +Name: quickstart +API Keys: sk-01****************************ef +Authorization: quickstart:* +TPM: +TPD: +``` + +## 6. 调用模型网关 + +使用 `show-example` 生成 curl 调用示例: + +```bash +agentkit model-gateway show-example --provider quickstart --consumer quickstart +``` + +OpenAI 兼容的供应商会使用 `/chat/completions`,Anthropic 兼容的供应商会使用 `/messages`。输出示例: + +```text +curl "/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "model": "doubao-seed-evolving", + "messages": [ + { + "role": "user", + "content": "你是谁?" + } + ] + }' +``` + +如果配置正确,模型网关会将请求转发到火山方舟,并返回响应。