From 176b39145bca2c0e6eb9048f3265b85716e85d06 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 3 Mar 2026 22:39:38 +0800 Subject: [PATCH] docs: update LLM model configs API documentation for group endpoints - Add note clarifying that GET endpoints for groups return the same structure as user endpoints - Document LLMModelConfigListResponse schema with default configuration selection logic - Add comprehensive example for GET group configurations endpoint - Update endpoint numbering to accommodate new group endpoints - Ensure consistency between user and group API documentation --- docs/llm-model-configs-api.md | 94 ++++++++++++++++++- .../routes/controller/llm_model_configs.py | 23 ++++- gns3server/schemas/__init__.py | 3 +- .../schemas/controller/llm_model_configs.py | 8 ++ 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/docs/llm-model-configs-api.md b/docs/llm-model-configs-api.md index b1ad9f1b8..01f97c6f8 100644 --- a/docs/llm-model-configs-api.md +++ b/docs/llm-model-configs-api.md @@ -97,6 +97,8 @@ The `model_type` field accepts the following values: | DELETE | `/v3/access/groups/{group_id}/llm-model-configs/{config_id}` | Delete a configuration | Group.Modify | | PUT | `/v3/access/groups/{group_id}/llm-model-configs/default/{config_id}` | Set default configuration | Group.Modify | +**Note:** The GET endpoints for groups return the same structure as user endpoints: `configs`, `default_config`, and `total`. + --- ## Request/Response Schemas @@ -169,6 +171,20 @@ The `model_type` field accepts the following values: 2. Group's config marked with `is_default: true` (if user has no default) 3. First config in the list (fallback if no default is marked) +### LLMModelConfigListResponse + +| Field | Type | Description | +|-------|------|-------------| +| `configs` | list[LLMModelConfigResponse] | Configuration list | +| `default_config` | LLMModelConfigResponse (nullable) | Default configuration (never null if configs list is not empty) | +| `total` | integer | Total count | + +**Default Configuration Selection Logic:** +1. Config marked with `is_default: true` +2. First config in the list (fallback if no default is marked) + +**Usage:** This schema is used for group configuration endpoints (e.g., `GET /groups/{group_id}/llm-model-configs`). + ### LLMModelConfigWithSource | Field | Type | Description | @@ -306,7 +322,75 @@ curl -X GET http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \ - `source: "user"` indicates the config belongs to the user - `source: "group"` indicates the config is inherited from a group -### 4. Update a configuration (without optimistic locking) +### 4. Get group configurations + +```bash +curl -X GET http://localhost:3080/v3/access/groups/{group_id}/llm-model-configs \ + -H "Authorization: Bearer " +``` + +**Response:** +```json +{ + "configs": [ + { + "config_id": "uuid-1", + "name": "Claude-3", + "model_type": "text", + "config": { + "provider": "anthropic", + "base_url": "https://api.anthropic.com", + "model": "claude-3-opus-20240229", + "temperature": 0.7, + "api_key": "sk-ant-xxx" + }, + "user_id": null, + "group_id": "uuid-group", + "is_default": true, + "version": 0, + "created_at": "2026-03-03T12:00:00Z", + "updated_at": "2026-03-03T12:00:00Z" + }, + { + "config_id": "uuid-2", + "name": "GPT-4", + "model_type": "text", + "config": { + "provider": "openai", + "base_url": "https://api.openai.com/v1", + "model": "gpt-4", + "temperature": 0.7, + "api_key": "sk-xxx" + }, + "user_id": null, + "group_id": "uuid-group", + "is_default": false, + "version": 0, + "created_at": "2026-03-03T12:00:00Z", + "updated_at": "2026-03-03T12:00:00Z" + } + ], + "default_config": { + "config_id": "uuid-1", + "name": "Claude-3", + "model_type": "text", + "config": { + "provider": "anthropic", + ... + }, + "user_id": null, + "group_id": "uuid-group", + "is_default": true, + "version": 0, + ... + }, + "total": 2 +} +``` + +**Note:** The response structure is the same as user endpoints, with `configs`, `default_config`, and `total` fields. + +### 5. Update a configuration (without optimistic locking) ```bash curl -X PUT http://localhost:3080/v3/access/users/{user_id}/llm-model-configs/{config_id} \ @@ -318,7 +402,7 @@ curl -X PUT http://localhost:3080/v3/access/users/{user_id}/llm-model-configs/{c }' ``` -### 5. Update a configuration (WITH optimistic locking) +### 6. Update a configuration (WITH optimistic locking) **Best practice for avoiding concurrent modification conflicts:** @@ -357,14 +441,14 @@ HTTP 409 Conflict 3. Apply your changes on top of the latest data 4. Retry the update with the new `expected_version` -### 6. Set default configuration +### 7. Set default configuration ```bash curl -X PUT http://localhost:3080/v3/access/users/{user_id}/llm-model-configs/default/{config_id} \ -H "Authorization: Bearer " ``` -### 7. Get default configuration +### 8. Get default configuration Get the user's default configuration: @@ -421,7 +505,7 @@ The response format is the same as for users. - `/default` endpoint: Requires explicit `is_default: true` flag, returns 404 if not found - `default_config` field in list: Falls back to first config if no explicit default is marked -### 8. Delete a configuration +### 9. Delete a configuration ```bash curl -X DELETE http://localhost:3080/v3/access/users/{user_id}/llm-model-configs/{config_id} \ diff --git a/gns3server/api/routes/controller/llm_model_configs.py b/gns3server/api/routes/controller/llm_model_configs.py index c7acde0d6..0e4dbcbd4 100644 --- a/gns3server/api/routes/controller/llm_model_configs.py +++ b/gns3server/api/routes/controller/llm_model_configs.py @@ -377,13 +377,13 @@ async def set_user_default_llm_model_config( @router.get( "/groups/{group_id}/llm-model-configs", - response_model=List[schemas.LLMModelConfigResponse], + response_model=schemas.LLMModelConfigListResponse, dependencies=[Depends(has_privilege("Group.Audit"))] ) async def get_group_llm_model_configs( group_id: UUID, llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)) -) -> List[schemas.LLMModelConfigResponse]: +) -> schemas.LLMModelConfigListResponse: """ Get all LLM model configurations for a user group. @@ -392,7 +392,7 @@ async def get_group_llm_model_configs( try: configs = await llm_repo.get_group_configs(group_id) - return [ + config_responses = [ schemas.LLMModelConfigResponse( config_id=config.config_id, name=config.name, @@ -407,6 +407,23 @@ async def get_group_llm_model_configs( ) for config in configs ] + + # Find default config (same logic as user configs) + default_config = None + for config in config_responses: + if config.is_default: + default_config = config + break + + # Fallback to first config if no default is marked + if default_config is None and config_responses: + default_config = config_responses[0] + + return schemas.LLMModelConfigListResponse( + configs=config_responses, + default_config=default_config, + total=len(config_responses) + ) except Exception as e: log.error(f"Failed to retrieve group LLM model configs: {e}") raise HTTPException( diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index 9d1d54058..33f4c7337 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -36,7 +36,8 @@ from .controller.llm_model_configs import ( LLMModelConfigUpdate, LLMModelConfigResponse, LLMModelConfigWithSource, - LLMModelConfigInheritedResponse + LLMModelConfigInheritedResponse, + LLMModelConfigListResponse ) from .controller.rbac import RoleCreate, RoleUpdate, Role, Privilege, ACECreate, ACEUpdate, ACE from .controller.pools import Resource, ResourceCreate, ResourcePoolCreate, ResourcePoolUpdate, ResourcePool diff --git a/gns3server/schemas/controller/llm_model_configs.py b/gns3server/schemas/controller/llm_model_configs.py index 87fe8d24d..662cbbb25 100644 --- a/gns3server/schemas/controller/llm_model_configs.py +++ b/gns3server/schemas/controller/llm_model_configs.py @@ -126,3 +126,11 @@ class LLMModelConfigInheritedResponse(BaseModel): configs: list[LLMModelConfigWithSource] default_config: Optional[LLMModelConfigWithSource] = None total: int + + +class LLMModelConfigListResponse(BaseModel): + """Response containing a list of model configurations with default.""" + + configs: list[LLMModelConfigResponse] + default_config: Optional[LLMModelConfigResponse] = None + total: int