From ddc87418f43c9b95bdc52f2c16d57ff1b8c208ff Mon Sep 17 00:00:00 2001 From: Sarah Wooders Date: Mon, 10 Nov 2025 16:39:01 -0800 Subject: [PATCH] feat: revert model_settings (#6089) --- fern/openapi.json | 354 +++++++++--------- letta/orm/agent.py | 8 +- letta/schemas/agent.py | 37 +- letta/schemas/llm_config.py | 15 +- letta/schemas/model.py | 56 +-- letta/server/server.py | 7 - .../llm_model_configs/gemini-2.5-flash.json | 2 +- tests/managers/test_agent_manager.py | 7 +- tests/sdk_v1/agents_test.py | 12 +- 9 files changed, 235 insertions(+), 263 deletions(-) diff --git a/fern/openapi.json b/fern/openapi.json index ad30f247..617e19bf 100644 --- a/fern/openapi.json +++ b/fern/openapi.json @@ -18937,84 +18937,24 @@ "model": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/ModelSettings" }, { "type": "null" } ], - "title": "Model", - "description": "The model handle used by the agent (format: provider/model-name)." + "description": "The model used by the agent." }, "embedding": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/EmbeddingModelSettings" }, { "type": "null" } ], - "title": "Embedding", - "description": "The embedding model handle used by the agent (format: provider/model-name)." - }, - "model_settings": { - "anyOf": [ - { - "oneOf": [ - { - "$ref": "#/components/schemas/OpenAIModelSettings" - }, - { - "$ref": "#/components/schemas/AnthropicModelSettings" - }, - { - "$ref": "#/components/schemas/GoogleAIModelSettings" - }, - { - "$ref": "#/components/schemas/GoogleVertexModelSettings" - }, - { - "$ref": "#/components/schemas/AzureModelSettings" - }, - { - "$ref": "#/components/schemas/XAIModelSettings" - }, - { - "$ref": "#/components/schemas/GroqModelSettings" - }, - { - "$ref": "#/components/schemas/DeepseekModelSettings" - }, - { - "$ref": "#/components/schemas/TogetherModelSettings" - }, - { - "$ref": "#/components/schemas/BedrockModelSettings" - } - ], - "discriminator": { - "propertyName": "provider", - "mapping": { - "anthropic": "#/components/schemas/AnthropicModelSettings", - "azure": "#/components/schemas/AzureModelSettings", - "bedrock": "#/components/schemas/BedrockModelSettings", - "deepseek": "#/components/schemas/DeepseekModelSettings", - "google_ai": "#/components/schemas/GoogleAIModelSettings", - "google_vertex": "#/components/schemas/GoogleVertexModelSettings", - "groq": "#/components/schemas/GroqModelSettings", - "openai": "#/components/schemas/OpenAIModelSettings", - "together": "#/components/schemas/TogetherModelSettings", - "xai": "#/components/schemas/XAIModelSettings" - } - } - }, - { - "type": "null" - } - ], - "title": "Model Settings", - "description": "The model settings used by the agent." + "description": "The embedding model used by the agent." }, "response_format": { "anyOf": [ @@ -19406,6 +19346,11 @@ }, "AnthropicModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -19454,6 +19399,7 @@ } }, "type": "object", + "required": ["model"], "title": "AnthropicModelSettings" }, "AnthropicThinking": { @@ -19467,8 +19413,6 @@ }, "budget_tokens": { "type": "integer", - "maximum": 1024, - "minimum": 0, "title": "Budget Tokens", "description": "The maximum number of tokens the model can use for extended thinking.", "default": 1024 @@ -20277,6 +20221,11 @@ }, "AzureModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -20298,8 +20247,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -20336,6 +20283,7 @@ } }, "type": "object", + "required": ["model"], "title": "AzureModelSettings", "description": "Azure OpenAI model configuration (OpenAI-compatible)." }, @@ -20592,6 +20540,11 @@ }, "BedrockModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -20613,8 +20566,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -20651,6 +20602,7 @@ } }, "type": "object", + "required": ["model"], "title": "BedrockModelSettings", "description": "AWS Bedrock model configuration." }, @@ -23207,27 +23159,6 @@ { "type": "string" }, - { - "type": "null" - } - ], - "title": "Model", - "description": "The model handle for the agent to use (format: provider/model-name)." - }, - "embedding": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Embedding", - "description": "The embedding model handle used by the agent (format: provider/model-name)." - }, - "model_settings": { - "anyOf": [ { "oneOf": [ { @@ -23281,8 +23212,23 @@ "type": "null" } ], - "title": "Model Settings", - "description": "The model settings for the agent." + "title": "Model", + "description": "The model handle or model settings for the agent to use, specified either by a handle or an object. See the model schema for more information." + }, + "embedding": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/EmbeddingModelSettings" + }, + { + "type": "null" + } + ], + "title": "Embedding", + "description": "The embedding configuration handle used by the agent, specified in the format provider/model-name." }, "context_window_limit": { "anyOf": [ @@ -24041,6 +23987,11 @@ }, "DeepseekModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -24062,8 +24013,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -24100,6 +24049,7 @@ } }, "type": "object", + "required": ["model"], "title": "DeepseekModelSettings", "description": "Deepseek model configuration (OpenAI-compatible)." }, @@ -24628,6 +24578,25 @@ ], "title": "EmbeddingModel" }, + "EmbeddingModelSettings": { + "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, + "provider": { + "type": "string", + "enum": ["openai", "ollama"], + "title": "Provider", + "description": "The provider of the model." + } + }, + "type": "object", + "required": ["model", "provider"], + "title": "EmbeddingModelSettings", + "description": "Schema for defining settings for an embedding model" + }, "EventMessage": { "properties": { "id": { @@ -25696,8 +25665,6 @@ }, "thinking_budget": { "type": "integer", - "maximum": 1024, - "minimum": 0, "title": "Thinking Budget", "description": "The thinking budget for the model.", "default": 1024 @@ -25779,6 +25746,11 @@ }, "GoogleAIModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -25800,8 +25772,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -25846,10 +25816,16 @@ } }, "type": "object", + "required": ["model"], "title": "GoogleAIModelSettings" }, "GoogleVertexModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -25871,8 +25847,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -25917,10 +25891,16 @@ } }, "type": "object", + "required": ["model"], "title": "GoogleVertexModelSettings" }, "GroqModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -25942,8 +25922,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -25980,6 +25958,7 @@ } }, "type": "object", + "required": ["model"], "title": "GroqModelSettings", "description": "Groq model configuration (OpenAI-compatible)." }, @@ -27382,27 +27361,6 @@ { "type": "string" }, - { - "type": "null" - } - ], - "title": "Model", - "description": "The model handle for the agent to use (format: provider/model-name)." - }, - "embedding": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Embedding", - "description": "The embedding model handle used by the agent (format: provider/model-name)." - }, - "model_settings": { - "anyOf": [ { "oneOf": [ { @@ -27456,8 +27414,23 @@ "type": "null" } ], - "title": "Model Settings", - "description": "The model settings for the agent." + "title": "Model", + "description": "The model handle or model settings for the agent to use, specified either by a handle or an object. See the model schema for more information." + }, + "embedding": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/EmbeddingModelSettings" + }, + { + "type": "null" + } + ], + "title": "Embedding", + "description": "The embedding configuration handle used by the agent, specified in the format provider/model-name." }, "context_window_limit": { "anyOf": [ @@ -30655,6 +30628,31 @@ ], "title": "Model" }, + "ModelSettings": { + "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, + "max_output_tokens": { + "type": "integer", + "title": "Max Output Tokens", + "description": "The maximum number of tokens the model can generate.", + "default": 4096 + }, + "parallel_tool_calls": { + "type": "boolean", + "title": "Parallel Tool Calls", + "description": "Whether to enable parallel tool calling.", + "default": false + } + }, + "type": "object", + "required": ["model"], + "title": "ModelSettings", + "description": "Schema for defining settings for a model" + }, "ModifyApprovalRequest": { "properties": { "requires_approval": { @@ -30754,6 +30752,11 @@ }, "OpenAIModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -30775,8 +30778,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -30820,6 +30821,7 @@ } }, "type": "object", + "required": ["model"], "title": "OpenAIModelSettings" }, "OpenAIReasoning": { @@ -34395,6 +34397,11 @@ }, "TogetherModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -34416,8 +34423,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -34454,6 +34459,7 @@ } }, "type": "object", + "required": ["model"], "title": "TogetherModelSettings", "description": "Together AI model configuration (OpenAI-compatible)." }, @@ -36057,27 +36063,6 @@ { "type": "string" }, - { - "type": "null" - } - ], - "title": "Model", - "description": "The model handle used by the agent (format: provider/model-name)." - }, - "embedding": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Embedding", - "description": "The embedding model handle used by the agent (format: provider/model-name)." - }, - "model_settings": { - "anyOf": [ { "oneOf": [ { @@ -36131,8 +36116,23 @@ "type": "null" } ], - "title": "Model Settings", - "description": "The model settings for the agent." + "title": "Model", + "description": "The model used by the agent, specified either by a handle or an object. See the model schema for more information." + }, + "embedding": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/EmbeddingModelSettings" + }, + { + "type": "null" + } + ], + "title": "Embedding", + "description": "The embedding configuration handle used by the agent, specified in the format provider/model-name." }, "context_window_limit": { "anyOf": [ @@ -36849,6 +36849,11 @@ }, "XAIModelSettings": { "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "The name of the model." + }, "max_output_tokens": { "type": "integer", "title": "Max Output Tokens", @@ -36870,8 +36875,6 @@ }, "temperature": { "type": "number", - "maximum": 1, - "minimum": 0, "title": "Temperature", "description": "The temperature of the model.", "default": 0.7 @@ -36908,6 +36911,7 @@ } }, "type": "object", + "required": ["model"], "title": "XAIModelSettings", "description": "xAI model configuration (OpenAI-compatible)." }, @@ -37181,27 +37185,6 @@ { "type": "string" }, - { - "type": "null" - } - ], - "title": "Model", - "description": "The model handle for the agent to use (format: provider/model-name)." - }, - "embedding": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Embedding", - "description": "The embedding model handle used by the agent (format: provider/model-name)." - }, - "model_settings": { - "anyOf": [ { "oneOf": [ { @@ -37255,8 +37238,23 @@ "type": "null" } ], - "title": "Model Settings", - "description": "The model settings for the agent." + "title": "Model", + "description": "The model handle or model settings for the agent to use, specified either by a handle or an object. See the model schema for more information." + }, + "embedding": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/components/schemas/EmbeddingModelSettings" + }, + { + "type": "null" + } + ], + "title": "Embedding", + "description": "The embedding configuration handle used by the agent, specified in the format provider/model-name." }, "context_window_limit": { "anyOf": [ diff --git a/letta/orm/agent.py b/letta/orm/agent.py index 01b3cbca..22fd33de 100644 --- a/letta/orm/agent.py +++ b/letta/orm/agent.py @@ -285,9 +285,7 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin if resolver: state[field_name] = resolver() - state["model"] = self.llm_config.handle if self.llm_config else None - state["model_settings"] = self.llm_config._to_model_settings() if self.llm_config else None - state["embedding"] = self.embedding_config.handle if self.embedding_config else None + state["model"] = self.llm_config._to_model() if self.llm_config else None return self.__pydantic_model__(**state) @@ -427,8 +425,6 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin state["managed_group"] = multi_agent_group state["tool_exec_environment_variables"] = tool_exec_environment_variables state["secrets"] = tool_exec_environment_variables - state["model"] = self.llm_config.handle if self.llm_config else None - state["model_settings"] = self.llm_config._to_model_settings() if self.llm_config else None - state["embedding"] = self.embedding_config.handle if self.embedding_config else None + state["model"] = self.llm_config._to_model() if self.llm_config else None return self.__pydantic_model__(**state) diff --git a/letta/schemas/agent.py b/letta/schemas/agent.py index b3fa1cab..1f1aa7b3 100644 --- a/letta/schemas/agent.py +++ b/letta/schemas/agent.py @@ -5,7 +5,7 @@ from typing import Dict, List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from letta.constants import CORE_MEMORY_LINE_NUMBER_WARNING, DEFAULT_EMBEDDING_CHUNK_SIZE -from letta.errors import AgentExportProcessingError, LettaInvalidArgumentError +from letta.errors import AgentExportProcessingError from letta.schemas.block import Block, CreateBlock from letta.schemas.embedding_config import EmbeddingConfig from letta.schemas.enums import PrimitiveType @@ -18,7 +18,7 @@ from letta.schemas.letta_stop_reason import StopReasonType from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import Memory from letta.schemas.message import Message, MessageCreate -from letta.schemas.model import ModelSettingsUnion +from letta.schemas.model import EmbeddingModelSettings, ModelSettings, ModelSettingsUnion from letta.schemas.openai.chat_completion_response import UsageStatistics from letta.schemas.response_format import ResponseFormatUnion from letta.schemas.source import Source @@ -83,9 +83,8 @@ class AgentState(OrmMetadataBase, validate_assignment=True): embedding_config: EmbeddingConfig = Field( ..., description="Deprecated: Use `embedding` field instead. The embedding configuration used by the agent.", deprecated=True ) - model: Optional[str] = Field(None, description="The model handle used by the agent (format: provider/model-name).") - embedding: Optional[str] = Field(None, description="The embedding model handle used by the agent (format: provider/model-name).") - model_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings used by the agent.") + model: Optional[ModelSettings] = Field(None, description="The model used by the agent.") + embedding: Optional[EmbeddingModelSettings] = Field(None, description="The embedding model used by the agent.") response_format: Optional[ResponseFormatUnion] = Field( None, @@ -230,12 +229,13 @@ class CreateAgent(BaseModel, validate_assignment=True): # embedding_config: Optional[EmbeddingConfig] = Field( None, description="Deprecated: Use `embedding` field instead. The embedding configuration used by the agent.", deprecated=True ) - model: Optional[str] = Field( # TODO: make this required (breaking change) + model: Optional[str | ModelSettingsUnion] = Field( # TODO: make this required (breaking change) None, - description="The model handle for the agent to use (format: provider/model-name).", + description="The model handle or model settings for the agent to use, specified either by a handle or an object. See the model schema for more information.", + ) + embedding: Optional[str | EmbeddingModelSettings] = Field( + None, description="The embedding configuration handle used by the agent, specified in the format provider/model-name." ) - embedding: Optional[str] = Field(None, description="The embedding model handle used by the agent (format: provider/model-name).") - model_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings for the agent.") context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.") embedding_chunk_size: Optional[int] = Field( @@ -348,12 +348,9 @@ class CreateAgent(BaseModel, validate_assignment=True): # if not model: return model - if "/" not in model: - raise LettaInvalidArgumentError("The model handle should be in the format provider/model-name", argument_name="model") - provider_name, model_name = model.split("/", 1) if not provider_name or not model_name: - raise LettaInvalidArgumentError("The model handle should be in the format provider/model-name", argument_name="model") + raise ValueError("The llm config handle should be in the format provider/model-name") return model @@ -363,12 +360,9 @@ class CreateAgent(BaseModel, validate_assignment=True): # if not embedding: return embedding - if "/" not in embedding: - raise ValueError("The embedding handle should be in the format provider/model-name") - provider_name, embedding_name = embedding.split("/", 1) if not provider_name or not embedding_name: - raise ValueError("The embedding handle should be in the format provider/model-name") + raise ValueError("The embedding config handle should be in the format provider/model-name") return embedding @@ -416,12 +410,13 @@ class UpdateAgent(BaseModel): ) # model configuration - model: Optional[str] = Field( + model: Optional[str | ModelSettingsUnion ] = Field( None, - description="The model handle used by the agent (format: provider/model-name).", + description="The model used by the agent, specified either by a handle or an object. See the model schema for more information.", + ) + embedding: Optional[str | EmbeddingModelSettings] = Field( + None, description="The embedding configuration handle used by the agent, specified in the format provider/model-name." ) - embedding: Optional[str] = Field(None, description="The embedding model handle used by the agent (format: provider/model-name).") - model_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings for the agent.") context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.") reasoning: Optional[bool] = Field( None, diff --git a/letta/schemas/llm_config.py b/letta/schemas/llm_config.py index 3c1c95fe..d586a0df 100644 --- a/letta/schemas/llm_config.py +++ b/letta/schemas/llm_config.py @@ -255,7 +255,7 @@ class LLMConfig(BaseModel): + (f" [ip={self.model_endpoint}]" if self.model_endpoint else "") ) - def _to_model_settings(self) -> "ModelSettings": + def _to_model(self) -> "ModelSettings": """ Convert LLMConfig back into a Model schema (OpenAIModelSettings, AnthropicModelSettings, etc.). This is the inverse of the _to_legacy_config_params() methods in model.py. @@ -279,6 +279,7 @@ class LLMConfig(BaseModel): if self.model_endpoint_type == "openai": return OpenAIModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, reasoning=OpenAIReasoning(reasoning_effort=self.reasoning_effort or "minimal"), @@ -286,6 +287,7 @@ class LLMConfig(BaseModel): elif self.model_endpoint_type == "anthropic": thinking_type = "enabled" if self.enable_reasoner else "disabled" return AnthropicModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, thinking=AnthropicThinking(type=thinking_type, budget_tokens=self.max_reasoning_tokens or 1024), @@ -293,6 +295,7 @@ class LLMConfig(BaseModel): ) elif self.model_endpoint_type == "google_ai": return GoogleAIModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 65536, temperature=self.temperature, thinking_config=GeminiThinkingConfig( @@ -301,6 +304,7 @@ class LLMConfig(BaseModel): ) elif self.model_endpoint_type == "google_vertex": return GoogleVertexModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 65536, temperature=self.temperature, thinking_config=GeminiThinkingConfig( @@ -309,34 +313,39 @@ class LLMConfig(BaseModel): ) elif self.model_endpoint_type == "azure": return AzureModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, ) elif self.model_endpoint_type == "xai": return XAIModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, ) elif self.model_endpoint_type == "groq": return GroqModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, ) elif self.model_endpoint_type == "deepseek": return DeepseekModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, ) elif self.model_endpoint_type == "together": return TogetherModelSettings( + model=self.model, max_output_tokens=self.max_tokens or 4096, temperature=self.temperature, ) elif self.model_endpoint_type == "bedrock": - return Model(max_output_tokens=self.max_tokens or 4096) + return Model(model=self.model, max_output_tokens=self.max_tokens or 4096) else: # If we don't know the model type, use the default Model schema - return Model(max_output_tokens=self.max_tokens or 4096) + return Model(model=self.model, max_output_tokens=self.max_tokens or 4096) @classmethod def is_openai_reasoning_model(cls, config: "LLMConfig") -> bool: diff --git a/letta/schemas/model.py b/letta/schemas/model.py index 3c35af7d..5bcb8e5f 100644 --- a/letta/schemas/model.py +++ b/letta/schemas/model.py @@ -120,25 +120,6 @@ class Model(LLMConfig, ModelBase): provider_category=llm_config.provider_category, ) - @property - def model_settings_schema(self) -> Optional[dict]: - """Returns the JSON schema for the ModelSettings class corresponding to this model's provider.""" - PROVIDER_SETTINGS_MAP = { - ProviderType.openai: OpenAIModelSettings, - ProviderType.anthropic: AnthropicModelSettings, - ProviderType.google_ai: GoogleAIModelSettings, - ProviderType.google_vertex: GoogleVertexModelSettings, - ProviderType.azure: AzureModelSettings, - ProviderType.xai: XAIModelSettings, - ProviderType.groq: GroqModelSettings, - ProviderType.deepseek: DeepseekModelSettings, - ProviderType.together: TogetherModelSettings, - ProviderType.bedrock: BedrockModelSettings, - } - - settings_class = PROVIDER_SETTINGS_MAP.get(self.provider_type) - return settings_class.model_json_schema() if settings_class else None - class EmbeddingModel(EmbeddingConfig, ModelBase): model_type: Literal["embedding"] = Field("embedding", description="Type of model (llm or embedding)") @@ -203,11 +184,18 @@ class EmbeddingModel(EmbeddingConfig, ModelBase): class ModelSettings(BaseModel): """Schema for defining settings for a model""" - # model: str = Field(..., description="The name of the model.") + model: str = Field(..., description="The name of the model.") max_output_tokens: int = Field(4096, description="The maximum number of tokens the model can generate.") parallel_tool_calls: bool = Field(False, description="Whether to enable parallel tool calling.") +class EmbeddingModelSettings(BaseModel): + """Schema for defining settings for an embedding model""" + + model: str = Field(..., description="The name of the model.") + provider: Literal["openai", "ollama"] = Field(..., description="The provider of the model.") + + class OpenAIReasoning(BaseModel): reasoning_effort: Literal["minimal", "low", "medium", "high"] = Field( "minimal", description="The reasoning effort to use when generating text reasoning models" @@ -221,7 +209,7 @@ class OpenAIReasoning(BaseModel): class OpenAIModelSettings(ModelSettings): provider: Literal["openai"] = Field("openai", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") reasoning: OpenAIReasoning = Field(OpenAIReasoning(reasoning_effort="high"), description="The reasoning configuration for the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") @@ -240,7 +228,6 @@ class OpenAIModelSettings(ModelSettings): "max_tokens": self.max_output_tokens, "reasoning_effort": self.reasoning.reasoning_effort, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -252,7 +239,7 @@ class OpenAIModelSettings(ModelSettings): class AnthropicThinking(BaseModel): type: Literal["enabled", "disabled"] = Field("enabled", description="The type of thinking to use.") - budget_tokens: int = Field(1024, ge=0, le=1024, description="The maximum number of tokens the model can use for extended thinking.") + budget_tokens: int = Field(1024, description="The maximum number of tokens the model can use for extended thinking.") class AnthropicModelSettings(ModelSettings): @@ -279,18 +266,17 @@ class AnthropicModelSettings(ModelSettings): "extended_thinking": self.thinking.type == "enabled", "thinking_budget_tokens": self.thinking.budget_tokens, "verbosity": self.verbosity, - "parallel_tool_calls": self.parallel_tool_calls, } class GeminiThinkingConfig(BaseModel): include_thoughts: bool = Field(True, description="Whether to include thoughts in the model's response.") - thinking_budget: int = Field(1024, ge=0, le=1024, description="The thinking budget for the model.") + thinking_budget: int = Field(1024, description="The thinking budget for the model.") class GoogleAIModelSettings(ModelSettings): provider: Literal["google_ai"] = Field("google_ai", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") thinking_config: GeminiThinkingConfig = Field( GeminiThinkingConfig(include_thoughts=True, thinking_budget=1024), description="The thinking configuration for the model." ) @@ -302,7 +288,6 @@ class GoogleAIModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "max_reasoning_tokens": self.thinking_config.thinking_budget if self.thinking_config.include_thoughts else 0, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -314,7 +299,7 @@ class AzureModelSettings(ModelSettings): """Azure OpenAI model configuration (OpenAI-compatible).""" provider: Literal["azure"] = Field("azure", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -322,7 +307,6 @@ class AzureModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -330,7 +314,7 @@ class XAIModelSettings(ModelSettings): """xAI model configuration (OpenAI-compatible).""" provider: Literal["xai"] = Field("xai", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -338,7 +322,6 @@ class XAIModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -346,7 +329,7 @@ class GroqModelSettings(ModelSettings): """Groq model configuration (OpenAI-compatible).""" provider: Literal["groq"] = Field("groq", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -361,7 +344,7 @@ class DeepseekModelSettings(ModelSettings): """Deepseek model configuration (OpenAI-compatible).""" provider: Literal["deepseek"] = Field("deepseek", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -369,7 +352,6 @@ class DeepseekModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -377,7 +359,7 @@ class TogetherModelSettings(ModelSettings): """Together AI model configuration (OpenAI-compatible).""" provider: Literal["together"] = Field("together", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -385,7 +367,6 @@ class TogetherModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } @@ -393,7 +374,7 @@ class BedrockModelSettings(ModelSettings): """AWS Bedrock model configuration.""" provider: Literal["bedrock"] = Field("bedrock", description="The provider of the model.") - temperature: float = Field(0.7, ge=0.0, le=1.0, description="The temperature of the model.") + temperature: float = Field(0.7, description="The temperature of the model.") response_format: Optional[ResponseFormatUnion] = Field(None, description="The response format for the model.") def _to_legacy_config_params(self) -> dict: @@ -401,7 +382,6 @@ class BedrockModelSettings(ModelSettings): "temperature": self.temperature, "max_tokens": self.max_output_tokens, "response_format": self.response_format, - "parallel_tool_calls": self.parallel_tool_calls, } diff --git a/letta/server/server.py b/letta/server/server.py index be188dac..f15e5c91 100644 --- a/letta/server/server.py +++ b/letta/server/server.py @@ -436,8 +436,6 @@ class SyncServer(object): handle = f"{request.model.provider}/{request.model.model}" # TODO: figure out how to override various params additional_config_params = request.model._to_legacy_config_params() - additional_config_params["model"] = request.model.model - additional_config_params["provider_name"] = request.model.provider config_params = { "handle": handle, @@ -527,11 +525,6 @@ class SyncServer(object): request.llm_config = await self.get_cached_llm_config_async(actor=actor, **config_params) log_event(name="end get_cached_llm_config", attributes=config_params) - # update with model_settings - if request.model_settings is not None: - update_llm_config_params = request.model_settings._to_legacy_config_params() - request.llm_config.update(update_llm_config_params) - # Copy parallel_tool_calls from request to llm_config if provided if request.parallel_tool_calls is not None: if request.llm_config is None: diff --git a/tests/configs/llm_model_configs/gemini-2.5-flash.json b/tests/configs/llm_model_configs/gemini-2.5-flash.json index 51e58d2e..387f1eb7 100644 --- a/tests/configs/llm_model_configs/gemini-2.5-flash.json +++ b/tests/configs/llm_model_configs/gemini-2.5-flash.json @@ -6,5 +6,5 @@ "model_wrapper": null, "put_inner_thoughts_in_kwargs": true, "enable_reasoner": true, - "max_reasoning_tokens": 1000 + "max_reasoning_tokens": 20000 } diff --git a/tests/managers/test_agent_manager.py b/tests/managers/test_agent_manager.py index 4c54a0ca..96056cc3 100644 --- a/tests/managers/test_agent_manager.py +++ b/tests/managers/test_agent_manager.py @@ -1250,7 +1250,7 @@ async def test_agent_state_schema_unchanged(server: SyncServer): from letta.schemas.group import Group from letta.schemas.llm_config import LLMConfig from letta.schemas.memory import Memory - from letta.schemas.model import ModelSettings + from letta.schemas.model import EmbeddingModelSettings, ModelSettings from letta.schemas.response_format import ResponseFormatUnion from letta.schemas.source import Source from letta.schemas.tool import Tool @@ -1271,10 +1271,9 @@ async def test_agent_state_schema_unchanged(server: SyncServer): "agent_type": AgentType, # LLM information "llm_config": LLMConfig, - "model": str, - "embedding": str, + "model": ModelSettings, + "embedding": EmbeddingModelSettings, "embedding_config": EmbeddingConfig, - "model_settings": ModelSettings, "response_format": (ResponseFormatUnion, type(None)), # State fields "description": (str, type(None)), diff --git a/tests/sdk_v1/agents_test.py b/tests/sdk_v1/agents_test.py index 2eee1c12..6eb7c7df 100644 --- a/tests/sdk_v1/agents_test.py +++ b/tests/sdk_v1/agents_test.py @@ -5,9 +5,11 @@ AGENTS_CREATE_PARAMS = [ "caren_agent", {"name": "caren", "model": "openai/gpt-4o-mini", "embedding": "openai/text-embedding-3-small"}, { - # Verify model_settings is populated with config values - # Note: The 'model' field itself is separate from model_settings - "model_settings": {"max_output_tokens": 4096, "parallel_tool_calls": False} + # Verify model field contains the model name and settings + # Note: we override 'model' here since the input is a string but the output is a ModelSettings object + "model": {"model": "gpt-4o-mini", "max_output_tokens": 4096, "parallel_tool_calls": False}, + # Note: we override 'embedding' here since it's currently not populated in AgentState (remains None) + "embedding": None, }, None, ), @@ -18,8 +20,8 @@ AGENTS_MODIFY_PARAMS = [ "caren_agent", {"name": "caren_updated"}, { - # After modifying just the name, model_settings should still be present - "model_settings": {"max_output_tokens": 4096, "parallel_tool_calls": False} + # After modifying just the name, model field should still be present and unchanged + "model": {"model": "gpt-4o-mini", "max_output_tokens": 4096, "parallel_tool_calls": False} }, None, ),