letta-nightly 0.11.7.dev20251006104136__py3-none-any.whl → 0.11.7.dev20251008104128__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/letta_llm_request_adapter.py +0 -1
- letta/adapters/letta_llm_stream_adapter.py +7 -2
- letta/adapters/simple_llm_request_adapter.py +88 -0
- letta/adapters/simple_llm_stream_adapter.py +192 -0
- letta/agents/agent_loop.py +6 -0
- letta/agents/ephemeral_summary_agent.py +2 -1
- letta/agents/helpers.py +142 -6
- letta/agents/letta_agent.py +13 -33
- letta/agents/letta_agent_batch.py +2 -4
- letta/agents/letta_agent_v2.py +87 -77
- letta/agents/letta_agent_v3.py +899 -0
- letta/agents/voice_agent.py +2 -6
- letta/constants.py +8 -4
- letta/errors.py +40 -0
- letta/functions/function_sets/base.py +84 -4
- letta/functions/function_sets/multi_agent.py +0 -3
- letta/functions/schema_generator.py +113 -71
- letta/groups/dynamic_multi_agent.py +3 -2
- letta/groups/helpers.py +1 -2
- letta/groups/round_robin_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent.py +3 -2
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/groups/sleeptime_multi_agent_v3.py +17 -17
- letta/groups/supervisor_multi_agent.py +84 -80
- letta/helpers/converters.py +3 -0
- letta/helpers/message_helper.py +4 -0
- letta/helpers/tool_rule_solver.py +92 -5
- letta/interfaces/anthropic_streaming_interface.py +409 -0
- letta/interfaces/gemini_streaming_interface.py +296 -0
- letta/interfaces/openai_streaming_interface.py +752 -1
- letta/llm_api/anthropic_client.py +126 -16
- letta/llm_api/bedrock_client.py +4 -2
- letta/llm_api/deepseek_client.py +4 -1
- letta/llm_api/google_vertex_client.py +123 -42
- letta/llm_api/groq_client.py +4 -1
- letta/llm_api/llm_api_tools.py +11 -4
- letta/llm_api/llm_client_base.py +6 -2
- letta/llm_api/openai.py +32 -2
- letta/llm_api/openai_client.py +423 -18
- letta/llm_api/xai_client.py +4 -1
- letta/main.py +9 -5
- letta/memory.py +1 -0
- letta/orm/__init__.py +1 -1
- letta/orm/agent.py +10 -0
- letta/orm/block.py +7 -16
- letta/orm/blocks_agents.py +8 -2
- letta/orm/files_agents.py +2 -0
- letta/orm/job.py +7 -5
- letta/orm/mcp_oauth.py +1 -0
- letta/orm/message.py +21 -6
- letta/orm/organization.py +2 -0
- letta/orm/provider.py +6 -2
- letta/orm/run.py +71 -0
- letta/orm/sandbox_config.py +7 -1
- letta/orm/sqlalchemy_base.py +0 -306
- letta/orm/step.py +6 -5
- letta/orm/step_metrics.py +5 -5
- letta/otel/tracing.py +28 -3
- letta/plugins/defaults.py +4 -4
- letta/prompts/system_prompts/__init__.py +2 -0
- letta/prompts/system_prompts/letta_v1.py +25 -0
- letta/schemas/agent.py +3 -2
- letta/schemas/agent_file.py +9 -3
- letta/schemas/block.py +23 -10
- letta/schemas/enums.py +21 -2
- letta/schemas/job.py +17 -4
- letta/schemas/letta_message_content.py +71 -2
- letta/schemas/letta_stop_reason.py +5 -5
- letta/schemas/llm_config.py +53 -3
- letta/schemas/memory.py +1 -1
- letta/schemas/message.py +504 -117
- letta/schemas/openai/responses_request.py +64 -0
- letta/schemas/providers/__init__.py +2 -0
- letta/schemas/providers/anthropic.py +16 -0
- letta/schemas/providers/ollama.py +115 -33
- letta/schemas/providers/openrouter.py +52 -0
- letta/schemas/providers/vllm.py +2 -1
- letta/schemas/run.py +48 -42
- letta/schemas/step.py +2 -2
- letta/schemas/step_metrics.py +1 -1
- letta/schemas/tool.py +15 -107
- letta/schemas/tool_rule.py +88 -5
- letta/serialize_schemas/marshmallow_agent.py +1 -0
- letta/server/db.py +86 -408
- letta/server/rest_api/app.py +61 -10
- letta/server/rest_api/dependencies.py +14 -0
- letta/server/rest_api/redis_stream_manager.py +19 -8
- letta/server/rest_api/routers/v1/agents.py +364 -292
- letta/server/rest_api/routers/v1/blocks.py +14 -20
- letta/server/rest_api/routers/v1/identities.py +45 -110
- letta/server/rest_api/routers/v1/internal_templates.py +21 -0
- letta/server/rest_api/routers/v1/jobs.py +23 -6
- letta/server/rest_api/routers/v1/messages.py +1 -1
- letta/server/rest_api/routers/v1/runs.py +126 -85
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
- letta/server/rest_api/routers/v1/tools.py +281 -594
- letta/server/rest_api/routers/v1/voice.py +1 -1
- letta/server/rest_api/streaming_response.py +29 -29
- letta/server/rest_api/utils.py +122 -64
- letta/server/server.py +160 -887
- letta/services/agent_manager.py +236 -919
- letta/services/agent_serialization_manager.py +16 -0
- letta/services/archive_manager.py +0 -100
- letta/services/block_manager.py +211 -168
- letta/services/file_manager.py +1 -1
- letta/services/files_agents_manager.py +24 -33
- letta/services/group_manager.py +0 -142
- letta/services/helpers/agent_manager_helper.py +7 -2
- letta/services/helpers/run_manager_helper.py +85 -0
- letta/services/job_manager.py +96 -411
- letta/services/lettuce/__init__.py +6 -0
- letta/services/lettuce/lettuce_client_base.py +86 -0
- letta/services/mcp_manager.py +38 -6
- letta/services/message_manager.py +165 -362
- letta/services/organization_manager.py +0 -36
- letta/services/passage_manager.py +0 -345
- letta/services/provider_manager.py +0 -80
- letta/services/run_manager.py +301 -0
- letta/services/sandbox_config_manager.py +0 -234
- letta/services/step_manager.py +62 -39
- letta/services/summarizer/summarizer.py +9 -7
- letta/services/telemetry_manager.py +0 -16
- letta/services/tool_executor/builtin_tool_executor.py +35 -0
- letta/services/tool_executor/core_tool_executor.py +397 -2
- letta/services/tool_executor/files_tool_executor.py +3 -3
- letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
- letta/services/tool_executor/tool_execution_manager.py +6 -8
- letta/services/tool_executor/tool_executor_base.py +3 -3
- letta/services/tool_manager.py +85 -339
- letta/services/tool_sandbox/base.py +24 -13
- letta/services/tool_sandbox/e2b_sandbox.py +16 -1
- letta/services/tool_schema_generator.py +123 -0
- letta/services/user_manager.py +0 -99
- letta/settings.py +20 -4
- {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
- {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
- letta/agents/temporal/activities/__init__.py +0 -4
- letta/agents/temporal/activities/example_activity.py +0 -7
- letta/agents/temporal/activities/prepare_messages.py +0 -10
- letta/agents/temporal/temporal_agent_workflow.py +0 -56
- letta/agents/temporal/types.py +0 -25
- {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
letta/llm_api/xai_client.py
CHANGED
@@ -8,6 +8,7 @@ from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
|
8
8
|
from letta.llm_api.openai_client import OpenAIClient
|
9
9
|
from letta.otel.tracing import trace_method
|
10
10
|
from letta.schemas.embedding_config import EmbeddingConfig
|
11
|
+
from letta.schemas.enums import AgentType
|
11
12
|
from letta.schemas.llm_config import LLMConfig
|
12
13
|
from letta.schemas.message import Message as PydanticMessage
|
13
14
|
from letta.settings import model_settings
|
@@ -23,12 +24,14 @@ class XAIClient(OpenAIClient):
|
|
23
24
|
@trace_method
|
24
25
|
def build_request_data(
|
25
26
|
self,
|
27
|
+
agent_type: AgentType,
|
26
28
|
messages: List[PydanticMessage],
|
27
29
|
llm_config: LLMConfig,
|
28
30
|
tools: Optional[List[dict]] = None,
|
29
31
|
force_tool_call: Optional[str] = None,
|
32
|
+
requires_subsequent_tool_call: bool = False,
|
30
33
|
) -> dict:
|
31
|
-
data = super().build_request_data(messages, llm_config, tools, force_tool_call)
|
34
|
+
data = super().build_request_data(agent_type, messages, llm_config, tools, force_tool_call, requires_subsequent_tool_call)
|
32
35
|
|
33
36
|
# Specific bug for the mini models (as of Apr 14, 2025)
|
34
37
|
# 400 - {'code': 'Client specified an invalid argument', 'error': 'Argument not supported on this model: presencePenalty'}
|
letta/main.py
CHANGED
@@ -3,12 +3,16 @@ import os
|
|
3
3
|
import typer
|
4
4
|
|
5
5
|
from letta.cli.cli import server
|
6
|
-
from letta.cli.cli_load import app as load_app
|
7
|
-
|
8
|
-
# disable composio print on exit
|
9
|
-
os.environ["COMPOSIO_DISABLE_VERSION_CHECK"] = "true"
|
10
6
|
|
11
7
|
app = typer.Typer(pretty_exceptions_enable=False)
|
8
|
+
|
9
|
+
# Register server as both the default command and as a subcommand
|
12
10
|
app.command(name="server")(server)
|
13
11
|
|
14
|
-
|
12
|
+
|
13
|
+
# Also make server the default when no command is specified
|
14
|
+
@app.callback(invoke_without_command=True)
|
15
|
+
def main(ctx: typer.Context):
|
16
|
+
if ctx.invoked_subcommand is None:
|
17
|
+
# If no subcommand is specified, run the server
|
18
|
+
server()
|
letta/memory.py
CHANGED
letta/orm/__init__.py
CHANGED
@@ -15,7 +15,6 @@ from letta.orm.identities_agents import IdentitiesAgents
|
|
15
15
|
from letta.orm.identities_blocks import IdentitiesBlocks
|
16
16
|
from letta.orm.identity import Identity
|
17
17
|
from letta.orm.job import Job
|
18
|
-
from letta.orm.job_messages import JobMessage
|
19
18
|
from letta.orm.llm_batch_items import LLMBatchItem
|
20
19
|
from letta.orm.llm_batch_job import LLMBatchJob
|
21
20
|
from letta.orm.mcp_oauth import MCPOAuth
|
@@ -27,6 +26,7 @@ from letta.orm.passage_tag import PassageTag
|
|
27
26
|
from letta.orm.prompt import Prompt
|
28
27
|
from letta.orm.provider import Provider
|
29
28
|
from letta.orm.provider_trace import ProviderTrace
|
29
|
+
from letta.orm.run import Run
|
30
30
|
from letta.orm.sandbox_config import AgentEnvironmentVariable, SandboxConfig, SandboxEnvironmentVariable
|
31
31
|
from letta.orm.source import Source
|
32
32
|
from letta.orm.sources_agents import SourcesAgents
|
letta/orm/agent.py
CHANGED
@@ -28,6 +28,7 @@ if TYPE_CHECKING:
|
|
28
28
|
from letta.orm.files_agents import FileAgent
|
29
29
|
from letta.orm.identity import Identity
|
30
30
|
from letta.orm.organization import Organization
|
31
|
+
from letta.orm.run import Run
|
31
32
|
from letta.orm.source import Source
|
32
33
|
from letta.orm.tool import Tool
|
33
34
|
|
@@ -38,6 +39,8 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin
|
|
38
39
|
__table_args__ = (
|
39
40
|
Index("ix_agents_created_at", "created_at", "id"),
|
40
41
|
Index("ix_agents_organization_id", "organization_id"),
|
42
|
+
Index("ix_agents_organization_id_deployment_id", "organization_id", "deployment_id"),
|
43
|
+
Index("ix_agents_project_id", "project_id"),
|
41
44
|
)
|
42
45
|
|
43
46
|
# agent generates its own id
|
@@ -132,6 +135,13 @@ class Agent(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateEntityMixin
|
|
132
135
|
lazy="selectin",
|
133
136
|
doc="Tags associated with the agent.",
|
134
137
|
)
|
138
|
+
runs: Mapped[List["Run"]] = relationship(
|
139
|
+
"Run",
|
140
|
+
back_populates="agent",
|
141
|
+
cascade="all, delete-orphan",
|
142
|
+
lazy="selectin",
|
143
|
+
doc="Runs associated with the agent.",
|
144
|
+
)
|
135
145
|
identities: Mapped[List["Identity"]] = relationship(
|
136
146
|
"Identity",
|
137
147
|
secondary="identities_agents",
|
letta/orm/block.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import TYPE_CHECKING, List, Optional, Type
|
2
2
|
|
3
3
|
from sqlalchemy import JSON, BigInteger, ForeignKey, Index, Integer, String, UniqueConstraint, event
|
4
|
-
from sqlalchemy.orm import Mapped,
|
4
|
+
from sqlalchemy.orm import Mapped, declared_attr, mapped_column, relationship
|
5
5
|
|
6
6
|
from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT
|
7
7
|
from letta.orm.block_history import BlockHistory
|
@@ -25,6 +25,12 @@ class Block(OrganizationMixin, SqlalchemyBase, ProjectMixin, TemplateEntityMixin
|
|
25
25
|
UniqueConstraint("id", "label", name="unique_block_id_label"),
|
26
26
|
Index("created_at_label_idx", "created_at", "label"),
|
27
27
|
Index("ix_block_label", "label"),
|
28
|
+
Index("ix_block_organization_id", "organization_id"),
|
29
|
+
Index("ix_block_project_id", "project_id"),
|
30
|
+
Index("ix_block_is_template", "is_template"),
|
31
|
+
Index("ix_block_hidden", "hidden"),
|
32
|
+
Index("ix_block_org_project_template", "organization_id", "project_id", "is_template"),
|
33
|
+
Index("ix_block_organization_id_deployment_id", "organization_id", "deployment_id"),
|
28
34
|
)
|
29
35
|
|
30
36
|
template_name: Mapped[Optional[str]] = mapped_column(
|
@@ -104,21 +110,6 @@ class Block(OrganizationMixin, SqlalchemyBase, ProjectMixin, TemplateEntityMixin
|
|
104
110
|
) # Helps manage potential FK cycles
|
105
111
|
|
106
112
|
|
107
|
-
@event.listens_for(Block, "after_update") # Changed from 'before_update'
|
108
|
-
def block_before_update(mapper, connection, target):
|
109
|
-
"""Handle updating BlocksAgents when a block's label changes."""
|
110
|
-
label_history = attributes.get_history(target, "label")
|
111
|
-
if not label_history.has_changes():
|
112
|
-
return
|
113
|
-
|
114
|
-
blocks_agents = BlocksAgents.__table__
|
115
|
-
connection.execute(
|
116
|
-
blocks_agents.update()
|
117
|
-
.where(blocks_agents.c.block_id == target.id, blocks_agents.c.block_label == label_history.deleted[0])
|
118
|
-
.values(block_label=label_history.added[0])
|
119
|
-
)
|
120
|
-
|
121
|
-
|
122
113
|
@event.listens_for(Block, "before_insert")
|
123
114
|
@event.listens_for(Block, "before_update")
|
124
115
|
def validate_value_length(mapper, connection, target):
|
letta/orm/blocks_agents.py
CHANGED
@@ -15,7 +15,13 @@ class BlocksAgents(Base):
|
|
15
15
|
name="unique_label_per_agent",
|
16
16
|
),
|
17
17
|
ForeignKeyConstraint(
|
18
|
-
["block_id", "block_label"],
|
18
|
+
["block_id", "block_label"],
|
19
|
+
["block.id", "block.label"],
|
20
|
+
name="fk_block_id_label",
|
21
|
+
onupdate="CASCADE",
|
22
|
+
ondelete="CASCADE",
|
23
|
+
deferrable=True,
|
24
|
+
initially="IMMEDIATE",
|
19
25
|
),
|
20
26
|
UniqueConstraint("agent_id", "block_id", name="unique_agent_block"),
|
21
27
|
Index("ix_blocks_agents_block_label_agent_id", "block_label", "agent_id"),
|
@@ -24,6 +30,6 @@ class BlocksAgents(Base):
|
|
24
30
|
)
|
25
31
|
|
26
32
|
# unique agent + block label
|
27
|
-
agent_id: Mapped[str] = mapped_column(String, ForeignKey("agents.id"), primary_key=True)
|
33
|
+
agent_id: Mapped[str] = mapped_column(String, ForeignKey("agents.id", ondelete="CASCADE"), primary_key=True)
|
28
34
|
block_id: Mapped[str] = mapped_column(String, primary_key=True)
|
29
35
|
block_label: Mapped[str] = mapped_column(String, primary_key=True)
|
letta/orm/files_agents.py
CHANGED
@@ -32,6 +32,8 @@ class FileAgent(SqlalchemyBase, OrganizationMixin):
|
|
32
32
|
# helpful indexes for look-ups
|
33
33
|
Index("ix_file_agent", "file_id", "agent_id"),
|
34
34
|
Index("ix_agent_filename", "agent_id", "file_name"),
|
35
|
+
# improve lookups by agent alone (e.g., WHERE agent_id IN (...))
|
36
|
+
Index("ix_files_agents_agent_id", "agent_id"),
|
35
37
|
)
|
36
38
|
__pydantic_model__ = PydanticFileAgent
|
37
39
|
|
letta/orm/job.py
CHANGED
@@ -11,10 +11,8 @@ from letta.schemas.job import Job as PydanticJob, LettaRequestConfig
|
|
11
11
|
from letta.schemas.letta_stop_reason import StopReasonType
|
12
12
|
|
13
13
|
if TYPE_CHECKING:
|
14
|
-
from letta.orm.job_messages import JobMessage
|
15
14
|
from letta.orm.message import Message
|
16
15
|
from letta.orm.organization import Organization
|
17
|
-
from letta.orm.step import Step
|
18
16
|
from letta.orm.user import User
|
19
17
|
|
20
18
|
|
@@ -25,11 +23,17 @@ class Job(SqlalchemyBase, UserMixin):
|
|
25
23
|
|
26
24
|
__tablename__ = "jobs"
|
27
25
|
__pydantic_model__ = PydanticJob
|
28
|
-
__table_args__ = (
|
26
|
+
__table_args__ = (
|
27
|
+
Index("ix_jobs_created_at", "created_at", "id"),
|
28
|
+
Index("ix_jobs_user_id", "user_id"),
|
29
|
+
)
|
29
30
|
|
30
31
|
status: Mapped[JobStatus] = mapped_column(String, default=JobStatus.created, doc="The current status of the job.")
|
31
32
|
completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True, doc="The unix timestamp of when the job was completed.")
|
32
33
|
stop_reason: Mapped[Optional[StopReasonType]] = mapped_column(String, nullable=True, doc="The reason why the job was stopped.")
|
34
|
+
background: Mapped[Optional[bool]] = mapped_column(
|
35
|
+
Boolean, nullable=True, default=False, doc="Whether the job was created in background mode."
|
36
|
+
)
|
33
37
|
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, doc="The metadata of the job.")
|
34
38
|
job_type: Mapped[JobType] = mapped_column(
|
35
39
|
String,
|
@@ -55,8 +59,6 @@ class Job(SqlalchemyBase, UserMixin):
|
|
55
59
|
|
56
60
|
# relationships
|
57
61
|
user: Mapped["User"] = relationship("User", back_populates="jobs")
|
58
|
-
job_messages: Mapped[List["JobMessage"]] = relationship("JobMessage", back_populates="job", cascade="all, delete-orphan")
|
59
|
-
steps: Mapped[List["Step"]] = relationship("Step", back_populates="job", cascade="save-update")
|
60
62
|
# organization relationship (nullable for backward compatibility)
|
61
63
|
organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="jobs")
|
62
64
|
|
letta/orm/mcp_oauth.py
CHANGED
@@ -35,6 +35,7 @@ class MCPOAuth(SqlalchemyBase, OrganizationMixin, UserMixin):
|
|
35
35
|
# OAuth flow data
|
36
36
|
authorization_url: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="OAuth authorization URL")
|
37
37
|
authorization_code: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="OAuth authorization code")
|
38
|
+
authorization_code_enc: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="Encrypted OAuth authorization code")
|
38
39
|
|
39
40
|
# Token data
|
40
41
|
access_token: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="OAuth access token")
|
letta/orm/message.py
CHANGED
@@ -7,7 +7,8 @@ from sqlalchemy.orm import Mapped, Session, mapped_column, relationship
|
|
7
7
|
from letta.orm.custom_columns import MessageContentColumn, ToolCallColumn, ToolReturnColumn
|
8
8
|
from letta.orm.mixins import AgentMixin, OrganizationMixin
|
9
9
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
10
|
-
from letta.schemas.
|
10
|
+
from letta.schemas.enums import MessageRole
|
11
|
+
from letta.schemas.letta_message_content import MessageContent, TextContent, TextContent as PydanticTextContent
|
11
12
|
from letta.schemas.message import Message as PydanticMessage, ToolReturn
|
12
13
|
from letta.settings import DatabaseChoice, settings
|
13
14
|
|
@@ -21,6 +22,9 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
21
22
|
Index("ix_messages_created_at", "created_at", "id"),
|
22
23
|
Index("ix_messages_agent_sequence", "agent_id", "sequence_id"),
|
23
24
|
Index("ix_messages_org_agent", "organization_id", "agent_id"),
|
25
|
+
Index("ix_messages_run_id", "run_id"),
|
26
|
+
# Composite index for optimizing the frequently-run query:
|
27
|
+
Index("ix_messages_run_sequence", "run_id", "sequence_id"),
|
24
28
|
)
|
25
29
|
__pydantic_model__ = PydanticMessage
|
26
30
|
|
@@ -35,6 +39,9 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
35
39
|
step_id: Mapped[Optional[str]] = mapped_column(
|
36
40
|
ForeignKey("steps.id", ondelete="SET NULL"), nullable=True, doc="ID of the step that this message belongs to"
|
37
41
|
)
|
42
|
+
run_id: Mapped[Optional[str]] = mapped_column(
|
43
|
+
ForeignKey("runs.id", ondelete="SET NULL"), nullable=True, doc="ID of the run that this message belongs to"
|
44
|
+
)
|
38
45
|
otid: Mapped[Optional[str]] = mapped_column(nullable=True, doc="The offline threading ID associated with this message")
|
39
46
|
tool_returns: Mapped[List[ToolReturn]] = mapped_column(
|
40
47
|
ToolReturnColumn, nullable=True, doc="Tool execution return information for prior tool calls"
|
@@ -68,11 +75,7 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
68
75
|
# Relationships
|
69
76
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="messages", lazy="raise")
|
70
77
|
step: Mapped["Step"] = relationship("Step", back_populates="messages", lazy="selectin")
|
71
|
-
|
72
|
-
# Job relationship
|
73
|
-
job_message: Mapped[Optional["JobMessage"]] = relationship(
|
74
|
-
"JobMessage", back_populates="message", uselist=False, cascade="all, delete-orphan", single_parent=True
|
75
|
-
)
|
78
|
+
run: Mapped["Run"] = relationship("Run", back_populates="messages", lazy="selectin")
|
76
79
|
|
77
80
|
@property
|
78
81
|
def job(self) -> Optional["Job"]:
|
@@ -87,6 +90,18 @@ class Message(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
87
90
|
# If there are no tool calls, set tool_calls to None
|
88
91
|
if self.tool_calls is None or len(self.tool_calls) == 0:
|
89
92
|
model.tool_calls = None
|
93
|
+
|
94
|
+
# Handle legacy case of tool message with single tool return + single text content
|
95
|
+
if (
|
96
|
+
self.role == MessageRole.tool
|
97
|
+
and self.tool_returns
|
98
|
+
and len(self.tool_returns) == 1
|
99
|
+
and self.content
|
100
|
+
and len(self.content) == 1
|
101
|
+
and isinstance(self.content[0], TextContent)
|
102
|
+
):
|
103
|
+
self.tool_returns[0].func_response = self.content[0].text
|
104
|
+
|
90
105
|
return model
|
91
106
|
|
92
107
|
|
letta/orm/organization.py
CHANGED
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
|
19
19
|
from letta.orm.passage import ArchivalPassage, SourcePassage
|
20
20
|
from letta.orm.passage_tag import PassageTag
|
21
21
|
from letta.orm.provider import Provider
|
22
|
+
from letta.orm.run import Run
|
22
23
|
from letta.orm.sandbox_config import AgentEnvironmentVariable, SandboxConfig, SandboxEnvironmentVariable
|
23
24
|
from letta.orm.tool import Tool
|
24
25
|
from letta.orm.user import User
|
@@ -68,3 +69,4 @@ class Organization(SqlalchemyBase):
|
|
68
69
|
"LLMBatchItem", back_populates="organization", cascade="all, delete-orphan"
|
69
70
|
)
|
70
71
|
jobs: Mapped[List["Job"]] = relationship("Job", back_populates="organization", cascade="all, delete-orphan")
|
72
|
+
runs: Mapped[List["Run"]] = relationship("Run", back_populates="organization", cascade="all, delete-orphan")
|
letta/orm/provider.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from typing import TYPE_CHECKING
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
2
2
|
|
3
|
-
from sqlalchemy import UniqueConstraint
|
3
|
+
from sqlalchemy import Text, UniqueConstraint
|
4
4
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
5
5
|
|
6
6
|
from letta.orm.mixins import OrganizationMixin
|
@@ -33,5 +33,9 @@ class Provider(SqlalchemyBase, OrganizationMixin):
|
|
33
33
|
region: Mapped[str] = mapped_column(nullable=True, doc="Region used for requests to the provider.")
|
34
34
|
api_version: Mapped[str] = mapped_column(nullable=True, doc="API version used for requests to the provider.")
|
35
35
|
|
36
|
+
# encrypted columns
|
37
|
+
api_key_enc: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="Encrypted API key or secret key for the provider.")
|
38
|
+
access_key_enc: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="Encrypted access key for the provider.")
|
39
|
+
|
36
40
|
# relationships
|
37
41
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="providers")
|
letta/orm/run.py
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
import uuid
|
2
|
+
from datetime import datetime
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
4
|
+
|
5
|
+
from sqlalchemy import JSON, BigInteger, Boolean, DateTime, ForeignKey, Index, String
|
6
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
7
|
+
|
8
|
+
from letta.orm.mixins import OrganizationMixin, ProjectMixin, TemplateMixin
|
9
|
+
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
10
|
+
from letta.schemas.enums import RunStatus
|
11
|
+
from letta.schemas.job import LettaRequestConfig
|
12
|
+
from letta.schemas.letta_stop_reason import StopReasonType
|
13
|
+
from letta.schemas.run import Run as PydanticRun
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from letta.orm.agent import Agent
|
17
|
+
from letta.orm.message import Message
|
18
|
+
from letta.orm.organization import Organization
|
19
|
+
from letta.orm.step import Step
|
20
|
+
|
21
|
+
|
22
|
+
class Run(SqlalchemyBase, OrganizationMixin, ProjectMixin, TemplateMixin):
|
23
|
+
"""Runs are created when agents process messages and represent a conversation or processing session.
|
24
|
+
Unlike Jobs, Runs are specifically tied to agent interactions and message processing.
|
25
|
+
"""
|
26
|
+
|
27
|
+
__tablename__ = "runs"
|
28
|
+
__pydantic_model__ = PydanticRun
|
29
|
+
__table_args__ = (
|
30
|
+
Index("ix_runs_created_at", "created_at", "id"),
|
31
|
+
Index("ix_runs_agent_id", "agent_id"),
|
32
|
+
Index("ix_runs_organization_id", "organization_id"),
|
33
|
+
)
|
34
|
+
|
35
|
+
# Generate run ID with run- prefix
|
36
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: f"run-{uuid.uuid4()}")
|
37
|
+
|
38
|
+
# Core run fields
|
39
|
+
status: Mapped[RunStatus] = mapped_column(String, default=RunStatus.created, doc="The current status of the run.")
|
40
|
+
completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True, doc="The unix timestamp of when the run was completed.")
|
41
|
+
stop_reason: Mapped[Optional[StopReasonType]] = mapped_column(String, nullable=True, doc="The reason why the run was stopped.")
|
42
|
+
background: Mapped[Optional[bool]] = mapped_column(
|
43
|
+
Boolean, nullable=True, default=False, doc="Whether the run was created in background mode."
|
44
|
+
)
|
45
|
+
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, doc="The metadata of the run.")
|
46
|
+
request_config: Mapped[Optional[LettaRequestConfig]] = mapped_column(
|
47
|
+
JSON, nullable=True, doc="The request configuration for the run, stored as JSON."
|
48
|
+
)
|
49
|
+
|
50
|
+
# Agent relationship - A run belongs to one agent
|
51
|
+
agent_id: Mapped[str] = mapped_column(String, ForeignKey("agents.id"), nullable=False, doc="The agent that owns this run.")
|
52
|
+
|
53
|
+
# Callback related columns
|
54
|
+
callback_url: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="When set, POST to this URL after run completion.")
|
55
|
+
callback_sent_at: Mapped[Optional[datetime]] = mapped_column(nullable=True, doc="Timestamp when the callback was last attempted.")
|
56
|
+
callback_status_code: Mapped[Optional[int]] = mapped_column(nullable=True, doc="HTTP status code returned by the callback endpoint.")
|
57
|
+
callback_error: Mapped[Optional[str]] = mapped_column(
|
58
|
+
nullable=True, doc="Optional error message from attempting to POST the callback endpoint."
|
59
|
+
)
|
60
|
+
|
61
|
+
# Timing metrics (in nanoseconds for precision)
|
62
|
+
ttft_ns: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True, doc="Time to first token in nanoseconds")
|
63
|
+
total_duration_ns: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True, doc="Total run duration in nanoseconds")
|
64
|
+
|
65
|
+
# Relationships
|
66
|
+
agent: Mapped["Agent"] = relationship("Agent", back_populates="runs")
|
67
|
+
organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="runs")
|
68
|
+
|
69
|
+
# Steps that are part of this run
|
70
|
+
steps: Mapped[List["Step"]] = relationship("Step", back_populates="run", cascade="all, delete-orphan")
|
71
|
+
messages: Mapped[List["Message"]] = relationship("Message", back_populates="run", cascade="all, delete-orphan")
|
letta/orm/sandbox_config.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import uuid
|
2
2
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
3
3
|
|
4
|
-
from sqlalchemy import JSON, Enum as SqlEnum, Index, String, UniqueConstraint
|
4
|
+
from sqlalchemy import JSON, Enum as SqlEnum, Index, String, Text, UniqueConstraint
|
5
5
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
6
6
|
|
7
7
|
from letta.orm.mixins import AgentMixin, OrganizationMixin, SandboxConfigMixin
|
@@ -49,6 +49,9 @@ class SandboxEnvironmentVariable(SqlalchemyBase, OrganizationMixin, SandboxConfi
|
|
49
49
|
value: Mapped[str] = mapped_column(String, nullable=False, doc="The value of the environment variable.")
|
50
50
|
description: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="An optional description of the environment variable.")
|
51
51
|
|
52
|
+
# encrypted columns
|
53
|
+
value_enc: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="Encrypted value of the environment variable.")
|
54
|
+
|
52
55
|
# relationships
|
53
56
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="sandbox_environment_variables")
|
54
57
|
sandbox_config: Mapped["SandboxConfig"] = relationship("SandboxConfig", back_populates="sandbox_environment_variables")
|
@@ -71,5 +74,8 @@ class AgentEnvironmentVariable(SqlalchemyBase, OrganizationMixin, AgentMixin):
|
|
71
74
|
value: Mapped[str] = mapped_column(String, nullable=False, doc="The value of the environment variable.")
|
72
75
|
description: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="An optional description of the environment variable.")
|
73
76
|
|
77
|
+
# encrypted columns
|
78
|
+
value_enc: Mapped[Optional[str]] = mapped_column(Text, nullable=True, doc="Encrypted value of the environment variable.")
|
79
|
+
|
74
80
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="agent_environment_variables")
|
75
81
|
agent: Mapped[List["Agent"]] = relationship("Agent", back_populates="tool_exec_environment_variables")
|