feat: add compaction_settings to agents (#6625)
* initial commit * Add database migration for compaction_settings field This migration adds the compaction_settings column to the agents table to support customized summarization configuration for each agent. 🐾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * fix * rename * update apis * fix tests * update web test --------- Co-authored-by: Letta <noreply@letta.com> Co-authored-by: Kian Jones <kian@letta.com>
This commit is contained in:
committed by
Caren Thomas
parent
4309ecf606
commit
7ea297231a
@@ -0,0 +1,28 @@
|
|||||||
|
"""add compaction_settings to agents table
|
||||||
|
|
||||||
|
Revision ID: d0880aae6cee
|
||||||
|
Revises: 2e5e90d3cdf8
|
||||||
|
Create Date: 2025-12-10 16:17:23.595775
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from letta.orm.custom_columns import CompactionSettingsColumn
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "d0880aae6cee"
|
||||||
|
down_revision: Union[str, None] = "2e5e90d3cdf8"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.add_column("agents", sa.Column("compaction_settings", CompactionSettingsColumn(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_column("agents", "compaction_settings")
|
||||||
@@ -20669,6 +20669,17 @@
|
|||||||
"title": "Model Settings",
|
"title": "Model Settings",
|
||||||
"description": "The model settings used by the agent."
|
"description": "The model settings used by the agent."
|
||||||
},
|
},
|
||||||
|
"compaction_settings": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/CompactionSettings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The compaction settings configuration used for compaction."
|
||||||
|
},
|
||||||
"response_format": {
|
"response_format": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
@@ -24254,6 +24265,53 @@
|
|||||||
"required": ["code"],
|
"required": ["code"],
|
||||||
"title": "CodeInput"
|
"title": "CodeInput"
|
||||||
},
|
},
|
||||||
|
"CompactionSettings": {
|
||||||
|
"properties": {
|
||||||
|
"model_settings": {
|
||||||
|
"$ref": "#/components/schemas/ModelSettings",
|
||||||
|
"description": "The model settings to use for summarization."
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Prompt",
|
||||||
|
"description": "The prompt to use for summarization."
|
||||||
|
},
|
||||||
|
"prompt_acknowledgement": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Prompt Acknowledgement",
|
||||||
|
"description": "Whether to include an acknowledgement post-prompt (helps prevent non-summary outputs)."
|
||||||
|
},
|
||||||
|
"clip_chars": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Clip Chars",
|
||||||
|
"description": "The maximum length of the summary in characters. If none, no clipping is performed.",
|
||||||
|
"default": 2000
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["all", "sliding_window"],
|
||||||
|
"title": "Mode",
|
||||||
|
"description": "The type of summarization technique use.",
|
||||||
|
"default": "sliding_window"
|
||||||
|
},
|
||||||
|
"sliding_window_percentage": {
|
||||||
|
"type": "number",
|
||||||
|
"title": "Sliding Window Percentage",
|
||||||
|
"description": "The percentage of the context window to keep post-summarization (only used in sliding window mode).",
|
||||||
|
"default": 0.3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["model_settings", "prompt", "prompt_acknowledgement"],
|
||||||
|
"title": "CompactionSettings"
|
||||||
|
},
|
||||||
"ComparisonOperator": {
|
"ComparisonOperator": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["eq", "gte", "lte"],
|
"enum": ["eq", "gte", "lte"],
|
||||||
@@ -25051,6 +25109,17 @@
|
|||||||
"title": "Model Settings",
|
"title": "Model Settings",
|
||||||
"description": "The model settings for the agent."
|
"description": "The model settings for the agent."
|
||||||
},
|
},
|
||||||
|
"compaction_settings": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/CompactionSettings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The compaction settings configuration used for compaction."
|
||||||
|
},
|
||||||
"context_window_limit": {
|
"context_window_limit": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
@@ -29267,6 +29336,17 @@
|
|||||||
"title": "Model Settings",
|
"title": "Model Settings",
|
||||||
"description": "The model settings for the agent."
|
"description": "The model settings for the agent."
|
||||||
},
|
},
|
||||||
|
"compaction_settings": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/CompactionSettings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The compaction settings configuration used for compaction."
|
||||||
|
},
|
||||||
"context_window_limit": {
|
"context_window_limit": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
@@ -32637,6 +32717,25 @@
|
|||||||
],
|
],
|
||||||
"title": "Model"
|
"title": "Model"
|
||||||
},
|
},
|
||||||
|
"ModelSettings": {
|
||||||
|
"properties": {
|
||||||
|
"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",
|
||||||
|
"title": "ModelSettings",
|
||||||
|
"description": "Schema for defining settings for a model"
|
||||||
|
},
|
||||||
"ModifyApprovalRequest": {
|
"ModifyApprovalRequest": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"requires_approval": {
|
"requires_approval": {
|
||||||
@@ -38498,6 +38597,17 @@
|
|||||||
"title": "Model Settings",
|
"title": "Model Settings",
|
||||||
"description": "The model settings for the agent."
|
"description": "The model settings for the agent."
|
||||||
},
|
},
|
||||||
|
"compaction_settings": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/CompactionSettings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The compaction settings configuration used for compaction."
|
||||||
|
},
|
||||||
"context_window_limit": {
|
"context_window_limit": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
@@ -39713,6 +39823,17 @@
|
|||||||
"title": "Model Settings",
|
"title": "Model Settings",
|
||||||
"description": "The model settings for the agent."
|
"description": "The model settings for the agent."
|
||||||
},
|
},
|
||||||
|
"compaction_settings": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/CompactionSettings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The compaction settings configuration used for compaction."
|
||||||
|
},
|
||||||
"context_window_limit": {
|
"context_window_limit": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ from letta.server.rest_api.utils import (
|
|||||||
)
|
)
|
||||||
from letta.services.helpers.tool_parser_helper import runtime_override_tool_json_schema
|
from letta.services.helpers.tool_parser_helper import runtime_override_tool_json_schema
|
||||||
from letta.services.summarizer.summarizer_all import summarize_all
|
from letta.services.summarizer.summarizer_all import summarize_all
|
||||||
from letta.services.summarizer.summarizer_config import SummarizerConfig, get_default_summarizer_config
|
from letta.services.summarizer.summarizer_config import CompactionSettings, get_default_compaction_settings
|
||||||
from letta.services.summarizer.summarizer_sliding_window import (
|
from letta.services.summarizer.summarizer_sliding_window import (
|
||||||
count_tokens,
|
count_tokens,
|
||||||
summarize_via_sliding_window,
|
summarize_via_sliding_window,
|
||||||
@@ -1318,10 +1318,10 @@ class LettaAgentV3(LettaAgentV2):
|
|||||||
Simplified compaction method. Does NOT do any persistence (handled in the loop)
|
Simplified compaction method. Does NOT do any persistence (handled in the loop)
|
||||||
"""
|
"""
|
||||||
# compact the current in-context messages (self.in_context_messages)
|
# compact the current in-context messages (self.in_context_messages)
|
||||||
# Use agent's summarizer_config if set, otherwise fall back to defaults
|
# Use agent's compaction_settings if set, otherwise fall back to defaults
|
||||||
# TODO: add this back
|
summarizer_config = self.agent_state.compaction_settings or get_default_compaction_settings(
|
||||||
# summarizer_config = self.agent_state.summarizer_config or get_default_summarizer_config(self.agent_state.llm_config)
|
self.agent_state.llm_config._to_model_settings()
|
||||||
summarizer_config = get_default_summarizer_config(self.agent_state.llm_config._to_model_settings())
|
)
|
||||||
summarization_mode_used = summarizer_config.mode
|
summarization_mode_used = summarizer_config.mode
|
||||||
if summarizer_config.mode == "all":
|
if summarizer_config.mode == "all":
|
||||||
summary, compacted_messages = await summarize_all(
|
summary, compacted_messages = await summarize_all(
|
||||||
|
|||||||
@@ -88,6 +88,32 @@ def deserialize_embedding_config(data: Optional[Dict]) -> Optional[EmbeddingConf
|
|||||||
return EmbeddingConfig(**data) if data else None
|
return EmbeddingConfig(**data) if data else None
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------
|
||||||
|
# CompactionSettings Serialization
|
||||||
|
# --------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_compaction_settings(config: Union[Optional["CompactionSettings"], Dict]) -> Optional[Dict]:
|
||||||
|
"""Convert a CompactionSettings object into a JSON-serializable dictionary."""
|
||||||
|
if config:
|
||||||
|
# Import here to avoid circular dependency
|
||||||
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
|
|
||||||
|
if isinstance(config, CompactionSettings):
|
||||||
|
return config.model_dump(mode="json")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_compaction_settings(data: Optional[Dict]) -> Optional["CompactionSettings"]:
|
||||||
|
"""Convert a dictionary back into a CompactionSettings object."""
|
||||||
|
if data:
|
||||||
|
# Import here to avoid circular dependency
|
||||||
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
|
|
||||||
|
return CompactionSettings(**data)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
# ToolRule Serialization
|
# ToolRule Serialization
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncAttrs
|
|||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from letta.orm.block import Block
|
from letta.orm.block import Block
|
||||||
from letta.orm.custom_columns import EmbeddingConfigColumn, LLMConfigColumn, ResponseFormatColumn, ToolRulesColumn
|
from letta.orm.custom_columns import CompactionSettingsColumn, EmbeddingConfigColumn, LLMConfigColumn, ResponseFormatColumn, ToolRulesColumn
|
||||||
from letta.orm.identity import Identity
|
from letta.orm.identity import Identity
|
||||||
from letta.orm.mixins import OrganizationMixin, ProjectMixin, TemplateEntityMixin, TemplateMixin
|
from letta.orm.mixins import OrganizationMixin, ProjectMixin, TemplateEntityMixin, TemplateMixin
|
||||||
from letta.orm.organization import Organization
|
from letta.orm.organization import Organization
|
||||||
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
|
|||||||
from letta.orm.run import Run
|
from letta.orm.run import Run
|
||||||
from letta.orm.source import Source
|
from letta.orm.source import Source
|
||||||
from letta.orm.tool import Tool
|
from letta.orm.tool import Tool
|
||||||
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
|
|
||||||
|
|
||||||
class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin, TemplateMixin, AsyncAttrs):
|
class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin, TemplateMixin, AsyncAttrs):
|
||||||
@@ -74,6 +75,9 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin
|
|||||||
embedding_config: Mapped[Optional[EmbeddingConfig]] = mapped_column(
|
embedding_config: Mapped[Optional[EmbeddingConfig]] = mapped_column(
|
||||||
EmbeddingConfigColumn, doc="the embedding configuration object for this agent."
|
EmbeddingConfigColumn, doc="the embedding configuration object for this agent."
|
||||||
)
|
)
|
||||||
|
compaction_settings: Mapped[Optional[dict]] = mapped_column(
|
||||||
|
CompactionSettingsColumn, nullable=True, doc="the compaction settings configuration object for compaction."
|
||||||
|
)
|
||||||
|
|
||||||
# Tool rules
|
# Tool rules
|
||||||
tool_rules: Mapped[Optional[List[ToolRule]]] = mapped_column(ToolRulesColumn, doc="the tool rules for this agent.")
|
tool_rules: Mapped[Optional[List[ToolRule]]] = mapped_column(ToolRulesColumn, doc="the tool rules for this agent.")
|
||||||
@@ -222,6 +226,7 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin
|
|||||||
"metadata": self.metadata_, # Exposed as 'metadata' to Pydantic
|
"metadata": self.metadata_, # Exposed as 'metadata' to Pydantic
|
||||||
"llm_config": self.llm_config,
|
"llm_config": self.llm_config,
|
||||||
"embedding_config": self.embedding_config,
|
"embedding_config": self.embedding_config,
|
||||||
|
"compaction_settings": self.compaction_settings,
|
||||||
"project_id": self.project_id,
|
"project_id": self.project_id,
|
||||||
"template_id": self.template_id,
|
"template_id": self.template_id,
|
||||||
"base_template_id": self.base_template_id,
|
"base_template_id": self.base_template_id,
|
||||||
@@ -328,6 +333,7 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin
|
|||||||
"metadata": self.metadata_, # Exposed as 'metadata' to Pydantic
|
"metadata": self.metadata_, # Exposed as 'metadata' to Pydantic
|
||||||
"llm_config": self.llm_config,
|
"llm_config": self.llm_config,
|
||||||
"embedding_config": self.embedding_config,
|
"embedding_config": self.embedding_config,
|
||||||
|
"compaction_settings": self.compaction_settings,
|
||||||
"project_id": self.project_id,
|
"project_id": self.project_id,
|
||||||
"template_id": self.template_id,
|
"template_id": self.template_id,
|
||||||
"base_template_id": self.base_template_id,
|
"base_template_id": self.base_template_id,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from letta.helpers.converters import (
|
|||||||
deserialize_agent_step_state,
|
deserialize_agent_step_state,
|
||||||
deserialize_approvals,
|
deserialize_approvals,
|
||||||
deserialize_batch_request_result,
|
deserialize_batch_request_result,
|
||||||
|
deserialize_compaction_settings,
|
||||||
deserialize_create_batch_response,
|
deserialize_create_batch_response,
|
||||||
deserialize_embedding_config,
|
deserialize_embedding_config,
|
||||||
deserialize_llm_config,
|
deserialize_llm_config,
|
||||||
@@ -19,6 +20,7 @@ from letta.helpers.converters import (
|
|||||||
serialize_agent_step_state,
|
serialize_agent_step_state,
|
||||||
serialize_approvals,
|
serialize_approvals,
|
||||||
serialize_batch_request_result,
|
serialize_batch_request_result,
|
||||||
|
serialize_compaction_settings,
|
||||||
serialize_create_batch_response,
|
serialize_create_batch_response,
|
||||||
serialize_embedding_config,
|
serialize_embedding_config,
|
||||||
serialize_llm_config,
|
serialize_llm_config,
|
||||||
@@ -59,6 +61,19 @@ class EmbeddingConfigColumn(TypeDecorator):
|
|||||||
return deserialize_embedding_config(value)
|
return deserialize_embedding_config(value)
|
||||||
|
|
||||||
|
|
||||||
|
class CompactionSettingsColumn(TypeDecorator):
|
||||||
|
"""Custom SQLAlchemy column type for storing CompactionSettings as JSON."""
|
||||||
|
|
||||||
|
impl = JSON
|
||||||
|
cache_ok = True
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
return serialize_compaction_settings(value)
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
return deserialize_compaction_settings(value)
|
||||||
|
|
||||||
|
|
||||||
class ToolRulesColumn(TypeDecorator):
|
class ToolRulesColumn(TypeDecorator):
|
||||||
"""Custom SQLAlchemy column type for storing a list of ToolRules as JSON."""
|
"""Custom SQLAlchemy column type for storing a list of ToolRules as JSON."""
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from letta.schemas.response_format import ResponseFormatUnion
|
|||||||
from letta.schemas.source import Source
|
from letta.schemas.source import Source
|
||||||
from letta.schemas.tool import Tool
|
from letta.schemas.tool import Tool
|
||||||
from letta.schemas.tool_rule import ToolRule
|
from letta.schemas.tool_rule import ToolRule
|
||||||
from letta.services.summarizer.summarizer_config import SummarizerConfig
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
from letta.utils import calculate_file_defaults_based_on_context_window, create_random_username
|
from letta.utils import calculate_file_defaults_based_on_context_window, create_random_username
|
||||||
|
|
||||||
|
|
||||||
@@ -87,9 +87,9 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
|
|||||||
model: Optional[str] = Field(None, description="The model handle used by the agent (format: provider/model-name).")
|
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).")
|
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_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings used by the agent.")
|
||||||
|
compaction_settings: Optional[CompactionSettings] = Field(
|
||||||
# TODO: add this back
|
None, description="The compaction settings configuration used for compaction."
|
||||||
# summarizer_config: Optional[SummarizerConfig] = Field(None, description="The summarizer configuration used by the agent.")
|
)
|
||||||
|
|
||||||
response_format: Optional[ResponseFormatUnion] = Field(
|
response_format: Optional[ResponseFormatUnion] = Field(
|
||||||
None,
|
None,
|
||||||
@@ -245,9 +245,9 @@ class CreateAgent(BaseModel, validate_assignment=True): #
|
|||||||
)
|
)
|
||||||
embedding: Optional[str] = Field(None, description="The embedding 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 for the agent.")
|
model_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings for the agent.")
|
||||||
|
compaction_settings: Optional[CompactionSettings] = Field(
|
||||||
# TODO: add this back
|
None, description="The compaction settings configuration used for compaction."
|
||||||
# summarizer_config: Optional[SummarizerConfig] = Field(None, description="The summarizer configuration used by the agent.")
|
)
|
||||||
|
|
||||||
context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.")
|
context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.")
|
||||||
embedding_chunk_size: Optional[int] = Field(
|
embedding_chunk_size: Optional[int] = Field(
|
||||||
@@ -441,9 +441,9 @@ class UpdateAgent(BaseModel):
|
|||||||
)
|
)
|
||||||
embedding: Optional[str] = Field(None, description="The embedding 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 for the agent.")
|
model_settings: Optional[ModelSettingsUnion] = Field(None, description="The model settings for the agent.")
|
||||||
|
compaction_settings: Optional[CompactionSettings] = Field(
|
||||||
# TODO: add this back
|
None, description="The compaction settings configuration used for compaction."
|
||||||
# summarizer_config: Optional[SummarizerConfig] = Field(None, description="The summarizer configuration used by the agent.")
|
)
|
||||||
|
|
||||||
context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.")
|
context_window_limit: Optional[int] = Field(None, description="The context window limit used by the agent.")
|
||||||
reasoning: Optional[bool] = Field(
|
reasoning: Optional[bool] = Field(
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ class AgentManager:
|
|||||||
agent_type=agent_create.agent_type,
|
agent_type=agent_create.agent_type,
|
||||||
llm_config=agent_create.llm_config,
|
llm_config=agent_create.llm_config,
|
||||||
embedding_config=agent_create.embedding_config,
|
embedding_config=agent_create.embedding_config,
|
||||||
|
compaction_settings=agent_create.compaction_settings,
|
||||||
organization_id=actor.organization_id,
|
organization_id=actor.organization_id,
|
||||||
description=agent_create.description,
|
description=agent_create.description,
|
||||||
metadata_=agent_create.metadata,
|
metadata_=agent_create.metadata,
|
||||||
@@ -750,6 +751,7 @@ class AgentManager:
|
|||||||
"system": agent_update.system,
|
"system": agent_update.system,
|
||||||
"llm_config": agent_update.llm_config,
|
"llm_config": agent_update.llm_config,
|
||||||
"embedding_config": agent_update.embedding_config,
|
"embedding_config": agent_update.embedding_config,
|
||||||
|
"compaction_settings": agent_update.compaction_settings,
|
||||||
"message_ids": agent_update.message_ids,
|
"message_ids": agent_update.message_ids,
|
||||||
"tool_rules": agent_update.tool_rules,
|
"tool_rules": agent_update.tool_rules,
|
||||||
"description": agent_update.description,
|
"description": agent_update.description,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from letta.schemas.llm_config import LLMConfig
|
|||||||
from letta.schemas.message import Message, MessageRole
|
from letta.schemas.message import Message, MessageRole
|
||||||
from letta.schemas.user import User
|
from letta.schemas.user import User
|
||||||
from letta.services.summarizer.summarizer import simple_summary
|
from letta.services.summarizer.summarizer import simple_summary
|
||||||
from letta.services.summarizer.summarizer_config import SummarizerConfig
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ async def summarize_all(
|
|||||||
# LLM config for the summarizer model
|
# LLM config for the summarizer model
|
||||||
llm_config: LLMConfig,
|
llm_config: LLMConfig,
|
||||||
# Actual summarization configuration
|
# Actual summarization configuration
|
||||||
summarizer_config: SummarizerConfig,
|
summarizer_config: CompactionSettings,
|
||||||
in_context_messages: List[Message],
|
in_context_messages: List[Message],
|
||||||
# new_messages: List[Message],
|
# new_messages: List[Message],
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from letta.schemas.llm_config import LLMConfig
|
|||||||
from letta.schemas.model import ModelSettings
|
from letta.schemas.model import ModelSettings
|
||||||
|
|
||||||
|
|
||||||
class SummarizerConfig(BaseModel):
|
class CompactionSettings(BaseModel):
|
||||||
# summarizer_model: LLMConfig = Field(default=..., description="The model to use for summarization.")
|
# summarizer_model: LLMConfig = Field(default=..., description="The model to use for summarization.")
|
||||||
model_settings: ModelSettings = Field(default=..., description="The model settings to use for summarization.")
|
model_settings: ModelSettings = Field(default=..., description="The model settings to use for summarization.")
|
||||||
prompt: str = Field(default=..., description="The prompt to use for summarization.")
|
prompt: str = Field(default=..., description="The prompt to use for summarization.")
|
||||||
@@ -23,20 +23,20 @@ class SummarizerConfig(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_default_summarizer_config(model_settings: ModelSettings) -> SummarizerConfig:
|
def get_default_compaction_settings(model_settings: ModelSettings) -> CompactionSettings:
|
||||||
"""Build a default SummarizerConfig from global settings for backward compatibility.
|
"""Build a default CompactionSettings from global settings for backward compatibility.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
llm_config: The LLMConfig to use for the summarizer model (typically the agent's llm_config).
|
llm_config: The LLMConfig to use for the summarizer model (typically the agent's llm_config).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A SummarizerConfig with default values from global settings.
|
A CompactionSettings with default values from global settings.
|
||||||
"""
|
"""
|
||||||
from letta.constants import MESSAGE_SUMMARY_REQUEST_ACK
|
from letta.constants import MESSAGE_SUMMARY_REQUEST_ACK
|
||||||
from letta.prompts import gpt_summarize
|
from letta.prompts import gpt_summarize
|
||||||
from letta.settings import summarizer_settings
|
from letta.settings import summarizer_settings
|
||||||
|
|
||||||
return SummarizerConfig(
|
return CompactionSettings(
|
||||||
mode="sliding_window",
|
mode="sliding_window",
|
||||||
model_settings=model_settings,
|
model_settings=model_settings,
|
||||||
prompt=gpt_summarize.SYSTEM,
|
prompt=gpt_summarize.SYSTEM,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from letta.schemas.user import User
|
|||||||
from letta.services.context_window_calculator.token_counter import create_token_counter
|
from letta.services.context_window_calculator.token_counter import create_token_counter
|
||||||
from letta.services.message_manager import MessageManager
|
from letta.services.message_manager import MessageManager
|
||||||
from letta.services.summarizer.summarizer import simple_summary
|
from letta.services.summarizer.summarizer import simple_summary
|
||||||
from letta.services.summarizer.summarizer_config import SummarizerConfig
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
from letta.system import package_summarize_message_no_counts
|
from letta.system import package_summarize_message_no_counts
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -48,7 +48,7 @@ async def summarize_via_sliding_window(
|
|||||||
actor: User,
|
actor: User,
|
||||||
# Actual summarization configuration
|
# Actual summarization configuration
|
||||||
llm_config: LLMConfig,
|
llm_config: LLMConfig,
|
||||||
summarizer_config: SummarizerConfig,
|
summarizer_config: CompactionSettings,
|
||||||
in_context_messages: List[Message],
|
in_context_messages: List[Message],
|
||||||
# new_messages: List[Message],
|
# new_messages: List[Message],
|
||||||
) -> Tuple[str, List[Message]]:
|
) -> Tuple[str, List[Message]]:
|
||||||
|
|||||||
@@ -618,12 +618,12 @@ async def test_summarize_multiple_large_tool_calls(server: SyncServer, actor, ll
|
|||||||
#
|
#
|
||||||
|
|
||||||
# ======================================================================================================================
|
# ======================================================================================================================
|
||||||
# SummarizerConfig Mode Tests (with pytest.patch) - Using LettaAgentV3
|
# CompactionSettings Mode Tests (with pytest.patch) - Using LettaAgentV3
|
||||||
# ======================================================================================================================
|
# ======================================================================================================================
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from letta.services.summarizer.summarizer_config import SummarizerConfig, get_default_summarizer_config
|
from letta.services.summarizer.summarizer_config import CompactionSettings, get_default_compaction_settings
|
||||||
|
|
||||||
# Test both summarizer modes: "all" summarizes entire history, "sliding_window" keeps recent messages
|
# Test both summarizer modes: "all" summarizes entire history, "sliding_window" keeps recent messages
|
||||||
SUMMARIZER_CONFIG_MODES: list[Literal["all", "sliding_window"]] = ["all", "sliding_window"]
|
SUMMARIZER_CONFIG_MODES: list[Literal["all", "sliding_window"]] = ["all", "sliding_window"]
|
||||||
@@ -634,7 +634,7 @@ SUMMARIZER_CONFIG_MODES: list[Literal["all", "sliding_window"]] = ["all", "slidi
|
|||||||
@pytest.mark.parametrize("llm_config", TESTED_LLM_CONFIGS, ids=[c.model for c in TESTED_LLM_CONFIGS])
|
@pytest.mark.parametrize("llm_config", TESTED_LLM_CONFIGS, ids=[c.model for c in TESTED_LLM_CONFIGS])
|
||||||
async def test_summarize_with_mode(server: SyncServer, actor, llm_config: LLMConfig, mode: Literal["all", "sliding_window"]):
|
async def test_summarize_with_mode(server: SyncServer, actor, llm_config: LLMConfig, mode: Literal["all", "sliding_window"]):
|
||||||
"""
|
"""
|
||||||
Test summarization with different SummarizerConfig modes using LettaAgentV3.
|
Test summarization with different CompactionSettings modes using LettaAgentV3.
|
||||||
|
|
||||||
This test verifies that both summarization modes work correctly:
|
This test verifies that both summarization modes work correctly:
|
||||||
- "all": Summarizes the entire conversation history into a single summary
|
- "all": Summarizes the entire conversation history into a single summary
|
||||||
@@ -674,11 +674,11 @@ async def test_summarize_with_mode(server: SyncServer, actor, llm_config: LLMCon
|
|||||||
# Persist the new messages
|
# Persist the new messages
|
||||||
new_letta_messages = await server.message_manager.create_many_messages_async(new_letta_messages, actor=actor)
|
new_letta_messages = await server.message_manager.create_many_messages_async(new_letta_messages, actor=actor)
|
||||||
|
|
||||||
# Create a custom SummarizerConfig with the desired mode
|
# Create a custom CompactionSettings with the desired mode
|
||||||
def mock_get_default_summarizer_config(model_settings):
|
def mock_get_default_compaction_settings(model_settings):
|
||||||
config = get_default_summarizer_config(model_settings)
|
config = get_default_compaction_settings(model_settings)
|
||||||
# Override the mode
|
# Override the mode
|
||||||
return SummarizerConfig(
|
return CompactionSettings(
|
||||||
model_settings=config.model_settings,
|
model_settings=config.model_settings,
|
||||||
prompt=config.prompt,
|
prompt=config.prompt,
|
||||||
prompt_acknowledgement=config.prompt_acknowledgement,
|
prompt_acknowledgement=config.prompt_acknowledgement,
|
||||||
@@ -687,7 +687,7 @@ async def test_summarize_with_mode(server: SyncServer, actor, llm_config: LLMCon
|
|||||||
sliding_window_percentage=config.sliding_window_percentage,
|
sliding_window_percentage=config.sliding_window_percentage,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("letta.agents.letta_agent_v3.get_default_summarizer_config", mock_get_default_summarizer_config):
|
with patch("letta.agents.letta_agent_v3.get_default_compaction_settings", mock_get_default_compaction_settings):
|
||||||
agent_loop = LettaAgentV3(agent_state=agent_state, actor=actor)
|
agent_loop = LettaAgentV3(agent_state=agent_state, actor=actor)
|
||||||
|
|
||||||
summary, result = await agent_loop.compact(messages=in_context_messages)
|
summary, result = await agent_loop.compact(messages=in_context_messages)
|
||||||
@@ -848,13 +848,13 @@ async def test_sliding_window_cutoff_index_does_not_exceed_message_count(server:
|
|||||||
the sliding window logic works with actual token counting.
|
the sliding window logic works with actual token counting.
|
||||||
"""
|
"""
|
||||||
from letta.schemas.model import ModelSettings
|
from letta.schemas.model import ModelSettings
|
||||||
from letta.services.summarizer.summarizer_config import get_default_summarizer_config
|
from letta.services.summarizer.summarizer_config import get_default_compaction_settings
|
||||||
from letta.services.summarizer.summarizer_sliding_window import summarize_via_sliding_window
|
from letta.services.summarizer.summarizer_sliding_window import summarize_via_sliding_window
|
||||||
|
|
||||||
# Create a real summarizer config using the default factory
|
# Create a real summarizer config using the default factory
|
||||||
# Override sliding_window_percentage to 0.3 for this test
|
# Override sliding_window_percentage to 0.3 for this test
|
||||||
model_settings = ModelSettings() # Use defaults
|
model_settings = ModelSettings() # Use defaults
|
||||||
summarizer_config = get_default_summarizer_config(model_settings)
|
summarizer_config = get_default_compaction_settings(model_settings)
|
||||||
summarizer_config.sliding_window_percentage = 0.3
|
summarizer_config.sliding_window_percentage = 0.3
|
||||||
|
|
||||||
# Create 65 messages (similar to the failing case in the bug report)
|
# Create 65 messages (similar to the failing case in the bug report)
|
||||||
@@ -1401,11 +1401,11 @@ async def test_summarize_all(server: SyncServer, actor, llm_config: LLMConfig):
|
|||||||
"""
|
"""
|
||||||
from letta.schemas.model import ModelSettings
|
from letta.schemas.model import ModelSettings
|
||||||
from letta.services.summarizer.summarizer_all import summarize_all
|
from letta.services.summarizer.summarizer_all import summarize_all
|
||||||
from letta.services.summarizer.summarizer_config import get_default_summarizer_config
|
from letta.services.summarizer.summarizer_config import get_default_compaction_settings
|
||||||
|
|
||||||
# Create a summarizer config with "all" mode
|
# Create a summarizer config with "all" mode
|
||||||
model_settings = ModelSettings()
|
model_settings = ModelSettings()
|
||||||
summarizer_config = get_default_summarizer_config(model_settings)
|
summarizer_config = get_default_compaction_settings(model_settings)
|
||||||
summarizer_config.mode = "all"
|
summarizer_config.mode = "all"
|
||||||
|
|
||||||
# Create test messages - a simple conversation
|
# Create test messages - a simple conversation
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|||||||
from letta.schemas.llm_batch_job import AgentStepState, LLMBatchItem
|
from letta.schemas.llm_batch_job import AgentStepState, LLMBatchItem
|
||||||
from letta.schemas.llm_config import LLMConfig
|
from letta.schemas.llm_config import LLMConfig
|
||||||
from letta.schemas.message import Message as PydanticMessage, MessageCreate, MessageUpdate
|
from letta.schemas.message import Message as PydanticMessage, MessageCreate, MessageUpdate
|
||||||
|
from letta.schemas.model import ModelSettings
|
||||||
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
||||||
from letta.schemas.organization import Organization, Organization as PydanticOrganization, OrganizationUpdate
|
from letta.schemas.organization import Organization, Organization as PydanticOrganization, OrganizationUpdate
|
||||||
from letta.schemas.passage import Passage as PydanticPassage
|
from letta.schemas.passage import Passage as PydanticPassage
|
||||||
@@ -96,6 +97,7 @@ from letta.server.server import SyncServer
|
|||||||
from letta.services.block_manager import BlockManager
|
from letta.services.block_manager import BlockManager
|
||||||
from letta.services.helpers.agent_manager_helper import calculate_base_tools, calculate_multi_agent_tools, validate_agent_exists_async
|
from letta.services.helpers.agent_manager_helper import calculate_base_tools, calculate_multi_agent_tools, validate_agent_exists_async
|
||||||
from letta.services.step_manager import FeedbackType
|
from letta.services.step_manager import FeedbackType
|
||||||
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
from letta.settings import settings, tool_settings
|
from letta.settings import settings, tool_settings
|
||||||
from letta.utils import calculate_file_defaults_based_on_context_window
|
from letta.utils import calculate_file_defaults_based_on_context_window
|
||||||
from tests.helpers.utils import comprehensive_agent_checks, validate_context_window_overview
|
from tests.helpers.utils import comprehensive_agent_checks, validate_context_window_overview
|
||||||
@@ -526,6 +528,91 @@ async def test_update_agent(server: SyncServer, comprehensive_test_agent_fixture
|
|||||||
assert updated_agent.updated_at > last_updated_timestamp
|
assert updated_agent.updated_at > last_updated_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_create_agent_with_compaction_settings(server: SyncServer, default_user, default_block):
|
||||||
|
"""Test that agents can be created with custom compaction_settings"""
|
||||||
|
# Upsert base tools
|
||||||
|
await server.tool_manager.upsert_base_tools_async(actor=default_user)
|
||||||
|
|
||||||
|
# Create custom compaction settings
|
||||||
|
llm_config = LLMConfig.default_config("gpt-4o-mini")
|
||||||
|
model_settings = llm_config._to_model_settings()
|
||||||
|
|
||||||
|
compaction_settings = CompactionSettings(
|
||||||
|
model_settings=model_settings,
|
||||||
|
prompt="Custom summarization prompt",
|
||||||
|
prompt_acknowledgement="Acknowledged",
|
||||||
|
clip_chars=1500,
|
||||||
|
mode="all",
|
||||||
|
sliding_window_percentage=0.5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create agent with compaction settings
|
||||||
|
create_agent_request = CreateAgent(
|
||||||
|
name="test_compaction_agent",
|
||||||
|
agent_type="memgpt_v2_agent",
|
||||||
|
system="test system",
|
||||||
|
llm_config=llm_config,
|
||||||
|
embedding_config=EmbeddingConfig.default_config(provider="openai"),
|
||||||
|
block_ids=[default_block.id],
|
||||||
|
include_base_tools=True,
|
||||||
|
compaction_settings=compaction_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
created_agent = await server.agent_manager.create_agent_async(
|
||||||
|
create_agent_request,
|
||||||
|
actor=default_user,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify compaction settings were stored correctly
|
||||||
|
assert created_agent.compaction_settings is not None
|
||||||
|
assert created_agent.compaction_settings.mode == "all"
|
||||||
|
assert created_agent.compaction_settings.clip_chars == 1500
|
||||||
|
assert created_agent.compaction_settings.sliding_window_percentage == 0.5
|
||||||
|
assert created_agent.compaction_settings.prompt == "Custom summarization prompt"
|
||||||
|
assert created_agent.compaction_settings.prompt_acknowledgement == "Acknowledged"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
await server.agent_manager.delete_agent_async(agent_id=created_agent.id, actor=default_user)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_update_agent_compaction_settings(server: SyncServer, comprehensive_test_agent_fixture, default_user):
|
||||||
|
"""Test that an agent's compaction_settings can be updated"""
|
||||||
|
agent, _ = comprehensive_test_agent_fixture
|
||||||
|
|
||||||
|
# Verify initial state (should be None or default)
|
||||||
|
assert agent.compaction_settings is None
|
||||||
|
|
||||||
|
# Create new compaction settings
|
||||||
|
llm_config = LLMConfig.default_config("gpt-4o-mini")
|
||||||
|
model_settings = llm_config._to_model_settings()
|
||||||
|
|
||||||
|
new_compaction_settings = CompactionSettings(
|
||||||
|
model_settings=model_settings,
|
||||||
|
prompt="Updated summarization prompt",
|
||||||
|
prompt_acknowledgement="Updated acknowledgement",
|
||||||
|
clip_chars=3000,
|
||||||
|
mode="sliding_window",
|
||||||
|
sliding_window_percentage=0.4,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update agent with compaction settings
|
||||||
|
update_agent_request = UpdateAgent(
|
||||||
|
compaction_settings=new_compaction_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_agent = await server.agent_manager.update_agent_async(agent.id, update_agent_request, actor=default_user)
|
||||||
|
|
||||||
|
# Verify compaction settings were updated correctly
|
||||||
|
assert updated_agent.compaction_settings is not None
|
||||||
|
assert updated_agent.compaction_settings.mode == "sliding_window"
|
||||||
|
assert updated_agent.compaction_settings.clip_chars == 3000
|
||||||
|
assert updated_agent.compaction_settings.sliding_window_percentage == 0.4
|
||||||
|
assert updated_agent.compaction_settings.prompt == "Updated summarization prompt"
|
||||||
|
assert updated_agent.compaction_settings.prompt_acknowledgement == "Updated acknowledgement"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_agent_file_defaults_based_on_context_window(server: SyncServer, default_user, default_block):
|
async def test_agent_file_defaults_based_on_context_window(server: SyncServer, default_user, default_block):
|
||||||
"""Test that file-related defaults are set based on the model's context window size"""
|
"""Test that file-related defaults are set based on the model's context window size"""
|
||||||
@@ -1282,6 +1369,7 @@ async def test_agent_state_schema_unchanged(server: SyncServer):
|
|||||||
from letta.schemas.source import Source
|
from letta.schemas.source import Source
|
||||||
from letta.schemas.tool import Tool
|
from letta.schemas.tool import Tool
|
||||||
from letta.schemas.tool_rule import ToolRule
|
from letta.schemas.tool_rule import ToolRule
|
||||||
|
from letta.services.summarizer.summarizer_config import CompactionSettings
|
||||||
|
|
||||||
# Define the expected schema structure
|
# Define the expected schema structure
|
||||||
expected_schema = {
|
expected_schema = {
|
||||||
@@ -1298,6 +1386,7 @@ async def test_agent_state_schema_unchanged(server: SyncServer):
|
|||||||
"agent_type": AgentType,
|
"agent_type": AgentType,
|
||||||
# LLM information
|
# LLM information
|
||||||
"llm_config": LLMConfig,
|
"llm_config": LLMConfig,
|
||||||
|
"compaction_settings": CompactionSettings,
|
||||||
"model": str,
|
"model": str,
|
||||||
"embedding": str,
|
"embedding": str,
|
||||||
"embedding_config": EmbeddingConfig,
|
"embedding_config": EmbeddingConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user