From 10a827f0c593e8a01ab6a56c3f581587995c5e46 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 11 Mar 2026 10:14:06 +0800 Subject: [PATCH] feat(api): enhance max_tokens field with robust null handling - Update LLMModelConfigUpdate schema to accept Union[int, str] for max_tokens - Add field validator to gracefully handle various null representations: - Convert string "null" to null - Convert empty strings to null - Convert numeric strings to integers - Accept proper JSON null values - Update documentation to reflect new behavior and explain robust null handling - Prevents validation errors from incorrect null serialization by clients --- .../implemented/llm-model-configs.md | 10 +++++++++- .../schemas/controller/llm_model_configs.py | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/gns3-copilot/implemented/llm-model-configs.md b/docs/gns3-copilot/implemented/llm-model-configs.md index be7a27b04..57639909d 100644 --- a/docs/gns3-copilot/implemented/llm-model-configs.md +++ b/docs/gns3-copilot/implemented/llm-model-configs.md @@ -148,7 +148,7 @@ The `model_type` field accepts the following values: | `model` | string (optional) | Model name | | `temperature` | float (optional) | Temperature | | `api_key` | string (optional) | API key | -| `max_tokens` | integer (optional) | Max tokens | +| `max_tokens` | integer or string (optional) | Max tokens for generation. Accepts integers, null, or the string "null" (which will be converted to null) | | `context_limit` | integer (optional) | Model context window limit in K tokens | | `context_strategy` | string (optional) | Context trimming strategy | | `is_default` | boolean (optional) | Default flag | @@ -156,6 +156,14 @@ The `model_type` field accepts the following values: **Note:** When using `expected_version`, the API will verify the version hasn't changed since you read the data. If it has, you'll receive a 409 Conflict error. +**Robust Null Handling:** The `max_tokens` field includes a validator that gracefully handles various null representations: +- Accepts proper JSON `null` values +- Accepts the string `"null"` (common serialization issue) and converts it to `null` +- Accepts empty strings `""` and converts them to `null` +- Accepts numeric strings (e.g., `"4096"`) and converts them to integers + +This prevents validation errors when clients incorrectly serialize `null` as the string `"null"`. + ### LLMModelConfigResponse | Field | Type | Description | diff --git a/gns3server/schemas/controller/llm_model_configs.py b/gns3server/schemas/controller/llm_model_configs.py index 810bdfe9e..78cc92d93 100644 --- a/gns3server/schemas/controller/llm_model_configs.py +++ b/gns3server/schemas/controller/llm_model_configs.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import Optional, Literal -from pydantic import BaseModel, Field, ConfigDict +from typing import Optional, Literal, Union +from pydantic import BaseModel, Field, ConfigDict, field_validator from uuid import UUID from .base import DateTimeModelMixin @@ -100,7 +100,7 @@ class LLMModelConfigUpdate(BaseModel): model: Optional[str] = None temperature: Optional[float] = Field(None, ge=0.0, le=2.0) api_key: Optional[str] = None - max_tokens: Optional[int] = Field(None, gt=0) + max_tokens: Optional[Union[int, str]] = Field(None, description="Max tokens for generation (can be null)") context_limit: Optional[int] = Field(None, gt=0, description="Model context window limit in K tokens (e.g., 128 = 128K tokens)") context_strategy: Optional[Literal["conservative", "balanced", "aggressive"]] = Field( None, description="Context trimming strategy" @@ -113,6 +113,19 @@ class LLMModelConfigUpdate(BaseModel): # Allow extra config fields model_config = ConfigDict(extra="allow") + @field_validator('max_tokens', mode='before') + @classmethod + def validate_max_tokens(cls, v): + """Handle string 'null' values for max_tokens.""" + if v == "null" or v == "": + return None + if v is None: + return None + # Convert to int if it's a valid integer string + if isinstance(v, str) and v.isdigit(): + return int(v) + return v + # Response schemas class LLMModelConfigResponse(DateTimeModelMixin):