letta-nightly 0.12.1.dev20251024104217__py3-none-any.whl → 0.13.0.dev20251025104015__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.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/__init__.py +2 -3
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/simple_llm_request_adapter.py +8 -5
- letta/adapters/simple_llm_stream_adapter.py +22 -6
- letta/agents/agent_loop.py +10 -3
- letta/agents/base_agent.py +4 -1
- letta/agents/helpers.py +41 -9
- letta/agents/letta_agent.py +11 -10
- letta/agents/letta_agent_v2.py +47 -37
- letta/agents/letta_agent_v3.py +395 -300
- letta/agents/voice_agent.py +8 -6
- letta/agents/voice_sleeptime_agent.py +3 -3
- letta/constants.py +30 -7
- letta/errors.py +20 -0
- letta/functions/function_sets/base.py +55 -3
- letta/functions/mcp_client/types.py +33 -57
- letta/functions/schema_generator.py +135 -23
- letta/groups/sleeptime_multi_agent_v3.py +6 -11
- letta/groups/sleeptime_multi_agent_v4.py +227 -0
- letta/helpers/converters.py +78 -4
- letta/helpers/crypto_utils.py +6 -2
- letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
- letta/interfaces/anthropic_streaming_interface.py +3 -4
- letta/interfaces/gemini_streaming_interface.py +4 -6
- letta/interfaces/openai_streaming_interface.py +63 -28
- letta/llm_api/anthropic_client.py +7 -4
- letta/llm_api/deepseek_client.py +6 -4
- letta/llm_api/google_ai_client.py +3 -12
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/helpers.py +90 -61
- letta/llm_api/llm_api_tools.py +4 -1
- letta/llm_api/openai.py +12 -12
- letta/llm_api/openai_client.py +53 -16
- letta/local_llm/constants.py +4 -3
- letta/local_llm/json_parser.py +5 -2
- letta/local_llm/utils.py +2 -3
- letta/log.py +171 -7
- letta/orm/agent.py +43 -9
- letta/orm/archive.py +4 -0
- letta/orm/custom_columns.py +15 -0
- letta/orm/identity.py +11 -11
- letta/orm/mcp_server.py +9 -0
- letta/orm/message.py +6 -1
- letta/orm/run_metrics.py +7 -2
- letta/orm/sqlalchemy_base.py +2 -2
- letta/orm/tool.py +3 -0
- letta/otel/tracing.py +2 -0
- letta/prompts/prompt_generator.py +7 -2
- letta/schemas/agent.py +41 -10
- letta/schemas/agent_file.py +3 -0
- letta/schemas/archive.py +4 -2
- letta/schemas/block.py +2 -1
- letta/schemas/enums.py +36 -3
- letta/schemas/file.py +3 -3
- letta/schemas/folder.py +2 -1
- letta/schemas/group.py +2 -1
- letta/schemas/identity.py +18 -9
- letta/schemas/job.py +3 -1
- letta/schemas/letta_message.py +71 -12
- letta/schemas/letta_request.py +7 -3
- letta/schemas/letta_stop_reason.py +0 -25
- letta/schemas/llm_config.py +8 -2
- letta/schemas/mcp.py +80 -83
- letta/schemas/mcp_server.py +349 -0
- letta/schemas/memory.py +20 -8
- letta/schemas/message.py +212 -67
- letta/schemas/providers/anthropic.py +13 -6
- letta/schemas/providers/azure.py +6 -4
- letta/schemas/providers/base.py +8 -4
- letta/schemas/providers/bedrock.py +6 -2
- letta/schemas/providers/cerebras.py +7 -3
- letta/schemas/providers/deepseek.py +2 -1
- letta/schemas/providers/google_gemini.py +15 -6
- letta/schemas/providers/groq.py +2 -1
- letta/schemas/providers/lmstudio.py +9 -6
- letta/schemas/providers/mistral.py +2 -1
- letta/schemas/providers/openai.py +7 -2
- letta/schemas/providers/together.py +9 -3
- letta/schemas/providers/xai.py +7 -3
- letta/schemas/run.py +7 -2
- letta/schemas/run_metrics.py +2 -1
- letta/schemas/sandbox_config.py +2 -2
- letta/schemas/secret.py +3 -158
- letta/schemas/source.py +2 -2
- letta/schemas/step.py +2 -2
- letta/schemas/tool.py +24 -1
- letta/schemas/usage.py +0 -1
- letta/server/rest_api/app.py +123 -7
- letta/server/rest_api/dependencies.py +3 -0
- letta/server/rest_api/interface.py +7 -4
- letta/server/rest_api/redis_stream_manager.py +16 -1
- letta/server/rest_api/routers/v1/__init__.py +7 -0
- letta/server/rest_api/routers/v1/agents.py +332 -322
- letta/server/rest_api/routers/v1/archives.py +127 -40
- letta/server/rest_api/routers/v1/blocks.py +54 -6
- letta/server/rest_api/routers/v1/chat_completions.py +146 -0
- letta/server/rest_api/routers/v1/folders.py +27 -35
- letta/server/rest_api/routers/v1/groups.py +23 -35
- letta/server/rest_api/routers/v1/identities.py +24 -10
- letta/server/rest_api/routers/v1/internal_runs.py +107 -0
- letta/server/rest_api/routers/v1/internal_templates.py +162 -179
- letta/server/rest_api/routers/v1/jobs.py +15 -27
- letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
- letta/server/rest_api/routers/v1/messages.py +23 -34
- letta/server/rest_api/routers/v1/organizations.py +6 -27
- letta/server/rest_api/routers/v1/providers.py +35 -62
- letta/server/rest_api/routers/v1/runs.py +30 -43
- letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
- letta/server/rest_api/routers/v1/sources.py +26 -42
- letta/server/rest_api/routers/v1/steps.py +16 -29
- letta/server/rest_api/routers/v1/tools.py +17 -13
- letta/server/rest_api/routers/v1/users.py +5 -17
- letta/server/rest_api/routers/v1/voice.py +18 -27
- letta/server/rest_api/streaming_response.py +5 -2
- letta/server/rest_api/utils.py +187 -25
- letta/server/server.py +27 -22
- letta/server/ws_api/server.py +5 -4
- letta/services/agent_manager.py +148 -26
- letta/services/agent_serialization_manager.py +6 -1
- letta/services/archive_manager.py +168 -15
- letta/services/block_manager.py +14 -4
- letta/services/file_manager.py +33 -29
- letta/services/group_manager.py +10 -0
- letta/services/helpers/agent_manager_helper.py +65 -11
- letta/services/identity_manager.py +105 -4
- letta/services/job_manager.py +11 -1
- letta/services/mcp/base_client.py +2 -2
- letta/services/mcp/oauth_utils.py +33 -8
- letta/services/mcp_manager.py +174 -78
- letta/services/mcp_server_manager.py +1331 -0
- letta/services/message_manager.py +109 -4
- letta/services/organization_manager.py +4 -4
- letta/services/passage_manager.py +9 -25
- letta/services/provider_manager.py +91 -15
- letta/services/run_manager.py +72 -15
- letta/services/sandbox_config_manager.py +45 -3
- letta/services/source_manager.py +15 -8
- letta/services/step_manager.py +24 -1
- letta/services/streaming_service.py +581 -0
- letta/services/summarizer/summarizer.py +1 -1
- letta/services/tool_executor/core_tool_executor.py +111 -0
- letta/services/tool_executor/files_tool_executor.py +5 -3
- letta/services/tool_executor/sandbox_tool_executor.py +2 -2
- letta/services/tool_executor/tool_execution_manager.py +1 -1
- letta/services/tool_manager.py +10 -3
- letta/services/tool_sandbox/base.py +61 -1
- letta/services/tool_sandbox/local_sandbox.py +1 -3
- letta/services/user_manager.py +2 -2
- letta/settings.py +49 -5
- letta/system.py +14 -5
- letta/utils.py +73 -1
- letta/validators.py +105 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/METADATA +4 -2
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/RECORD +157 -151
- letta/schemas/letta_ping.py +0 -28
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/WHEEL +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,14 +8,16 @@ from fastapi import APIRouter, Body, Depends, File, Form, Header, HTTPException,
|
|
|
8
8
|
from fastapi.responses import JSONResponse
|
|
9
9
|
from marshmallow import ValidationError
|
|
10
10
|
from orjson import orjson
|
|
11
|
-
from pydantic import BaseModel, Field
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
12
|
from sqlalchemy.exc import IntegrityError, OperationalError
|
|
13
13
|
from starlette.responses import Response, StreamingResponse
|
|
14
14
|
|
|
15
15
|
from letta.agents.agent_loop import AgentLoop
|
|
16
|
+
from letta.agents.base_agent_v2 import BaseAgentV2
|
|
17
|
+
from letta.agents.letta_agent import LettaAgent
|
|
16
18
|
from letta.agents.letta_agent_v2 import LettaAgentV2
|
|
17
|
-
from letta.constants import
|
|
18
|
-
from letta.data_sources.redis_client import
|
|
19
|
+
from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, REDIS_RUN_ID_PREFIX
|
|
20
|
+
from letta.data_sources.redis_client import get_redis_client
|
|
19
21
|
from letta.errors import (
|
|
20
22
|
AgentExportIdMappingError,
|
|
21
23
|
AgentExportProcessingError,
|
|
@@ -28,11 +30,11 @@ from letta.log import get_logger
|
|
|
28
30
|
from letta.orm.errors import NoResultFound
|
|
29
31
|
from letta.otel.context import get_ctx_attributes
|
|
30
32
|
from letta.otel.metric_registry import MetricRegistry
|
|
31
|
-
from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent
|
|
33
|
+
from letta.schemas.agent import AgentRelationships, AgentState, CreateAgent, UpdateAgent
|
|
32
34
|
from letta.schemas.agent_file import AgentFileSchema
|
|
33
|
-
from letta.schemas.block import Block, BlockUpdate
|
|
35
|
+
from letta.schemas.block import BaseBlock, Block, BlockUpdate
|
|
34
36
|
from letta.schemas.enums import AgentType, RunStatus
|
|
35
|
-
from letta.schemas.file import AgentFileAttachment, PaginatedAgentFiles
|
|
37
|
+
from letta.schemas.file import AgentFileAttachment, FileMetadataBase, PaginatedAgentFiles
|
|
36
38
|
from letta.schemas.group import Group
|
|
37
39
|
from letta.schemas.job import LettaRequestConfig
|
|
38
40
|
from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion, MessageType
|
|
@@ -46,20 +48,20 @@ from letta.schemas.memory import (
|
|
|
46
48
|
CreateArchivalMemory,
|
|
47
49
|
Memory,
|
|
48
50
|
)
|
|
49
|
-
from letta.schemas.message import MessageCreate, MessageSearchRequest, MessageSearchResult
|
|
51
|
+
from letta.schemas.message import BaseMessage, MessageCreate, MessageCreateType, MessageSearchRequest, MessageSearchResult
|
|
50
52
|
from letta.schemas.passage import Passage
|
|
51
53
|
from letta.schemas.run import Run as PydanticRun, RunUpdate
|
|
52
|
-
from letta.schemas.source import Source
|
|
53
|
-
from letta.schemas.tool import Tool
|
|
54
|
+
from letta.schemas.source import BaseSource, Source
|
|
55
|
+
from letta.schemas.tool import BaseTool, Tool
|
|
54
56
|
from letta.schemas.user import User
|
|
55
57
|
from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
|
|
56
58
|
from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
|
|
57
|
-
from letta.server.rest_api.redis_stream_manager import create_background_stream_processor, redis_sse_stream_generator
|
|
58
59
|
from letta.server.server import SyncServer
|
|
59
60
|
from letta.services.lettuce import LettuceClient
|
|
60
61
|
from letta.services.run_manager import RunManager
|
|
61
62
|
from letta.settings import settings
|
|
62
|
-
from letta.utils import safe_create_shielded_task, safe_create_task, truncate_file_visible_content
|
|
63
|
+
from letta.utils import is_1_0_sdk_version, safe_create_shielded_task, safe_create_task, truncate_file_visible_content
|
|
64
|
+
from letta.validators import AgentId, BlockId, FileId, MessageId, SourceId, ToolId
|
|
63
65
|
|
|
64
66
|
# These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
|
|
65
67
|
|
|
@@ -94,8 +96,13 @@ async def list_agents(
|
|
|
94
96
|
"Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
|
|
95
97
|
"If not provided, all relationships are loaded by default. "
|
|
96
98
|
"Using this can optimize performance by reducing unnecessary joins."
|
|
99
|
+
"This is a legacy parameter, and no longer supported after 1.0.0 SDK versions."
|
|
97
100
|
),
|
|
98
101
|
),
|
|
102
|
+
include: List[AgentRelationships] = Query(
|
|
103
|
+
[],
|
|
104
|
+
description=("Specify which relational fields to include in the response. No relationships are included by default."),
|
|
105
|
+
),
|
|
99
106
|
order: Literal["asc", "desc"] = Query(
|
|
100
107
|
"desc", description="Sort order for agents by creation time. 'asc' for oldest first, 'desc' for newest first"
|
|
101
108
|
),
|
|
@@ -126,6 +133,8 @@ async def list_agents(
|
|
|
126
133
|
# Handle backwards compatibility - prefer new parameters over legacy ones
|
|
127
134
|
final_ascending = (order == "asc") if order else ascending
|
|
128
135
|
final_sort_by = order_by if order_by else sort_by
|
|
136
|
+
if include_relationships is None and is_1_0_sdk_version(headers):
|
|
137
|
+
include_relationships = [] # don't default include all if using new SDK version
|
|
129
138
|
|
|
130
139
|
# Call list_agents directly without unnecessary dict handling
|
|
131
140
|
return await server.agent_manager.list_agents_async(
|
|
@@ -143,6 +152,7 @@ async def list_agents(
|
|
|
143
152
|
identity_id=identity_id,
|
|
144
153
|
identifier_keys=identifier_keys,
|
|
145
154
|
include_relationships=include_relationships,
|
|
155
|
+
include=include,
|
|
146
156
|
ascending=final_ascending,
|
|
147
157
|
sort_by=final_sort_by,
|
|
148
158
|
show_hidden_agents=show_hidden_agents,
|
|
@@ -170,13 +180,14 @@ class IndentedORJSONResponse(Response):
|
|
|
170
180
|
|
|
171
181
|
@router.get("/{agent_id}/export", response_class=IndentedORJSONResponse, operation_id="export_agent")
|
|
172
182
|
async def export_agent(
|
|
173
|
-
agent_id: str,
|
|
174
|
-
max_steps: int = 100,
|
|
183
|
+
agent_id: str = AgentId,
|
|
184
|
+
max_steps: int = Query(100, deprecated=True),
|
|
175
185
|
server: "SyncServer" = Depends(get_letta_server),
|
|
176
186
|
headers: HeaderParams = Depends(get_headers),
|
|
177
187
|
use_legacy_format: bool = Query(
|
|
178
188
|
False,
|
|
179
|
-
description="If
|
|
189
|
+
description="If True, exports using the legacy single-agent 'v1' format with inline tools/blocks. If False, exports using the new multi-entity 'v2' format, with separate agents, tools, blocks, files, etc.",
|
|
190
|
+
deprecated=True,
|
|
180
191
|
),
|
|
181
192
|
# do not remove, used to autogeneration of spec
|
|
182
193
|
# TODO: Think of a better way to export AgentFileSchema
|
|
@@ -185,21 +196,12 @@ async def export_agent(
|
|
|
185
196
|
) -> JSONResponse:
|
|
186
197
|
"""
|
|
187
198
|
Export the serialized JSON representation of an agent, formatted with indentation.
|
|
188
|
-
|
|
189
|
-
Supports two export formats:
|
|
190
|
-
- Legacy format (use_legacy_format=true): Single agent with inline tools/blocks
|
|
191
|
-
- New format (default): Multi-entity format with separate agents, tools, blocks, files, etc.
|
|
192
199
|
"""
|
|
193
|
-
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
194
|
-
|
|
195
200
|
if use_legacy_format:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
# Use the new multi-entity export format
|
|
201
|
-
agent_file_schema = await server.agent_serialization_manager.export(agent_ids=[agent_id], actor=actor)
|
|
202
|
-
return agent_file_schema.model_dump()
|
|
201
|
+
raise HTTPException(status_code=400, detail="Legacy format is not supported")
|
|
202
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
203
|
+
agent_file_schema = await server.agent_serialization_manager.export(agent_ids=[agent_id], actor=actor)
|
|
204
|
+
return agent_file_schema.model_dump()
|
|
203
205
|
|
|
204
206
|
|
|
205
207
|
class ImportedAgentsResponse(BaseModel):
|
|
@@ -242,6 +244,7 @@ async def _import_agent(
|
|
|
242
244
|
actor: User,
|
|
243
245
|
# TODO: Support these fields for new agent file
|
|
244
246
|
append_copy_suffix: bool = True,
|
|
247
|
+
override_name: Optional[str] = None,
|
|
245
248
|
override_existing_tools: bool = True,
|
|
246
249
|
project_id: str | None = None,
|
|
247
250
|
strip_messages: bool = False,
|
|
@@ -262,6 +265,7 @@ async def _import_agent(
|
|
|
262
265
|
schema=agent_schema,
|
|
263
266
|
actor=actor,
|
|
264
267
|
append_copy_suffix=append_copy_suffix,
|
|
268
|
+
override_name=override_name,
|
|
265
269
|
override_existing_tools=override_existing_tools,
|
|
266
270
|
env_vars=env_vars,
|
|
267
271
|
override_embedding_config=embedding_config_override,
|
|
@@ -282,7 +286,15 @@ async def import_agent(
|
|
|
282
286
|
server: "SyncServer" = Depends(get_letta_server),
|
|
283
287
|
headers: HeaderParams = Depends(get_headers),
|
|
284
288
|
x_override_embedding_model: str | None = Header(None, alias="x-override-embedding-model"),
|
|
285
|
-
append_copy_suffix: bool = Form(
|
|
289
|
+
append_copy_suffix: bool = Form(
|
|
290
|
+
True,
|
|
291
|
+
description='If set to True, appends "_copy" to the end of the agent name.',
|
|
292
|
+
deprecated=True,
|
|
293
|
+
),
|
|
294
|
+
override_name: Optional[str] = Form(
|
|
295
|
+
None,
|
|
296
|
+
description="If provided, overrides the agent name with this value.",
|
|
297
|
+
),
|
|
286
298
|
override_existing_tools: bool = Form(
|
|
287
299
|
True,
|
|
288
300
|
description="If set to True, existing tools can get their source code overwritten by the uploaded tool definitions. Note that Letta core tools can never be updated externally.",
|
|
@@ -335,6 +347,7 @@ async def import_agent(
|
|
|
335
347
|
server=server,
|
|
336
348
|
actor=actor,
|
|
337
349
|
append_copy_suffix=append_copy_suffix,
|
|
350
|
+
override_name=override_name,
|
|
338
351
|
override_existing_tools=override_existing_tools,
|
|
339
352
|
project_id=project_id,
|
|
340
353
|
strip_messages=strip_messages,
|
|
@@ -351,9 +364,9 @@ async def import_agent(
|
|
|
351
364
|
return ImportedAgentsResponse(agent_ids=agent_ids)
|
|
352
365
|
|
|
353
366
|
|
|
354
|
-
@router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
|
|
367
|
+
@router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window", deprecated=True)
|
|
355
368
|
async def retrieve_agent_context_window(
|
|
356
|
-
agent_id:
|
|
369
|
+
agent_id: AgentId,
|
|
357
370
|
server: "SyncServer" = Depends(get_letta_server),
|
|
358
371
|
headers: HeaderParams = Depends(get_headers),
|
|
359
372
|
):
|
|
@@ -386,14 +399,14 @@ async def create_agent(
|
|
|
386
399
|
Create an agent.
|
|
387
400
|
"""
|
|
388
401
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
389
|
-
if headers.experimental_params.letta_v1_agent and agent.agent_type == AgentType.memgpt_v2_agent
|
|
402
|
+
if headers.experimental_params.letta_v1_agent and agent.agent_type == AgentType.memgpt_v2_agent:
|
|
390
403
|
agent.agent_type = AgentType.letta_v1_agent
|
|
391
404
|
return await server.create_agent_async(agent, actor=actor)
|
|
392
405
|
|
|
393
406
|
|
|
394
407
|
@router.patch("/{agent_id}", response_model=AgentState, operation_id="modify_agent")
|
|
395
408
|
async def modify_agent(
|
|
396
|
-
agent_id:
|
|
409
|
+
agent_id: AgentId,
|
|
397
410
|
update_agent: UpdateAgent = Body(...),
|
|
398
411
|
server: "SyncServer" = Depends(get_letta_server),
|
|
399
412
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -403,9 +416,9 @@ async def modify_agent(
|
|
|
403
416
|
return await server.update_agent_async(agent_id=agent_id, request=update_agent, actor=actor)
|
|
404
417
|
|
|
405
418
|
|
|
406
|
-
@router.get("/{agent_id}/tools", response_model=list[Tool], operation_id="
|
|
407
|
-
async def
|
|
408
|
-
agent_id:
|
|
419
|
+
@router.get("/{agent_id}/tools", response_model=list[Tool], operation_id="list_tools_for_agent")
|
|
420
|
+
async def list_tools_for_agent(
|
|
421
|
+
agent_id: AgentId,
|
|
409
422
|
server: "SyncServer" = Depends(get_letta_server),
|
|
410
423
|
headers: HeaderParams = Depends(get_headers),
|
|
411
424
|
before: Optional[str] = Query(
|
|
@@ -420,7 +433,7 @@ async def list_agent_tools(
|
|
|
420
433
|
),
|
|
421
434
|
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
|
422
435
|
):
|
|
423
|
-
"""Get tools from an existing agent"""
|
|
436
|
+
"""Get tools from an existing agent."""
|
|
424
437
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
425
438
|
return await server.agent_manager.list_attached_tools_async(
|
|
426
439
|
agent_id=agent_id,
|
|
@@ -432,10 +445,10 @@ async def list_agent_tools(
|
|
|
432
445
|
)
|
|
433
446
|
|
|
434
447
|
|
|
435
|
-
@router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=AgentState, operation_id="
|
|
436
|
-
async def
|
|
437
|
-
|
|
438
|
-
|
|
448
|
+
@router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=Optional[AgentState], operation_id="attach_tool_to_agent")
|
|
449
|
+
async def attach_tool_to_agent(
|
|
450
|
+
tool_id: ToolId,
|
|
451
|
+
agent_id: AgentId,
|
|
439
452
|
server: "SyncServer" = Depends(get_letta_server),
|
|
440
453
|
headers: HeaderParams = Depends(get_headers),
|
|
441
454
|
):
|
|
@@ -444,14 +457,16 @@ async def attach_tool(
|
|
|
444
457
|
"""
|
|
445
458
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
446
459
|
await server.agent_manager.attach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
|
|
460
|
+
if is_1_0_sdk_version(headers):
|
|
461
|
+
return None
|
|
447
462
|
# TODO: Unfortunately we need this to preserve our current API behavior
|
|
448
463
|
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
|
449
464
|
|
|
450
465
|
|
|
451
|
-
@router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=AgentState, operation_id="
|
|
452
|
-
async def
|
|
453
|
-
|
|
454
|
-
|
|
466
|
+
@router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=Optional[AgentState], operation_id="detach_tool_from_agent")
|
|
467
|
+
async def detach_tool_from_agent(
|
|
468
|
+
tool_id: ToolId,
|
|
469
|
+
agent_id: AgentId,
|
|
455
470
|
server: "SyncServer" = Depends(get_letta_server),
|
|
456
471
|
headers: HeaderParams = Depends(get_headers),
|
|
457
472
|
):
|
|
@@ -460,33 +475,57 @@ async def detach_tool(
|
|
|
460
475
|
"""
|
|
461
476
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
462
477
|
await server.agent_manager.detach_tool_async(agent_id=agent_id, tool_id=tool_id, actor=actor)
|
|
478
|
+
if is_1_0_sdk_version(headers):
|
|
479
|
+
return None
|
|
463
480
|
# TODO: Unfortunately we need this to preserve our current API behavior
|
|
464
481
|
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
|
465
482
|
|
|
466
483
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
484
|
+
class ModifyApprovalRequest(BaseModel):
|
|
485
|
+
"""Request body for modifying tool approval requirements."""
|
|
486
|
+
|
|
487
|
+
requires_approval: bool = Field(..., description="Whether the tool requires approval before execution")
|
|
488
|
+
|
|
489
|
+
model_config = ConfigDict(extra="forbid")
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@router.patch("/{agent_id}/tools/approval/{tool_name}", response_model=Optional[AgentState], operation_id="modify_approval_for_tool")
|
|
493
|
+
async def modify_approval_for_tool(
|
|
470
494
|
tool_name: str,
|
|
471
|
-
|
|
495
|
+
agent_id: AgentId,
|
|
496
|
+
requires_approval: bool | None = Query(None, description="Whether the tool requires approval before execution", deprecated=True),
|
|
497
|
+
request: ModifyApprovalRequest | None = Body(None),
|
|
472
498
|
server: "SyncServer" = Depends(get_letta_server),
|
|
473
499
|
headers: HeaderParams = Depends(get_headers),
|
|
474
500
|
):
|
|
475
501
|
"""
|
|
476
|
-
|
|
502
|
+
Modify the approval requirement for a tool attached to an agent.
|
|
503
|
+
|
|
504
|
+
Accepts requires_approval via request body (preferred) or query parameter (deprecated).
|
|
477
505
|
"""
|
|
506
|
+
# Prefer body over query param for backwards compatibility
|
|
507
|
+
if request is not None:
|
|
508
|
+
approval_value = request.requires_approval
|
|
509
|
+
elif requires_approval is not None:
|
|
510
|
+
approval_value = requires_approval
|
|
511
|
+
else:
|
|
512
|
+
raise HTTPException(
|
|
513
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
514
|
+
detail="requires_approval must be provided either in request body or as query parameter",
|
|
515
|
+
)
|
|
516
|
+
|
|
478
517
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
479
|
-
await server.agent_manager.modify_approvals_async(
|
|
480
|
-
|
|
481
|
-
|
|
518
|
+
await server.agent_manager.modify_approvals_async(agent_id=agent_id, tool_name=tool_name, requires_approval=approval_value, actor=actor)
|
|
519
|
+
if is_1_0_sdk_version(headers):
|
|
520
|
+
return None
|
|
482
521
|
# TODO: Unfortunately we need this to preserve our current API behavior
|
|
483
522
|
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
|
484
523
|
|
|
485
524
|
|
|
486
|
-
@router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent")
|
|
525
|
+
@router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent", deprecated=True)
|
|
487
526
|
async def attach_source(
|
|
488
|
-
|
|
489
|
-
|
|
527
|
+
source_id: SourceId,
|
|
528
|
+
agent_id: AgentId,
|
|
490
529
|
server: "SyncServer" = Depends(get_letta_server),
|
|
491
530
|
headers: HeaderParams = Depends(get_headers),
|
|
492
531
|
):
|
|
@@ -512,8 +551,8 @@ async def attach_source(
|
|
|
512
551
|
|
|
513
552
|
@router.patch("/{agent_id}/folders/attach/{folder_id}", response_model=AgentState, operation_id="attach_folder_to_agent")
|
|
514
553
|
async def attach_folder_to_agent(
|
|
515
|
-
|
|
516
|
-
|
|
554
|
+
folder_id: SourceId,
|
|
555
|
+
agent_id: AgentId,
|
|
517
556
|
server: "SyncServer" = Depends(get_letta_server),
|
|
518
557
|
headers: HeaderParams = Depends(get_headers),
|
|
519
558
|
):
|
|
@@ -537,10 +576,10 @@ async def attach_folder_to_agent(
|
|
|
537
576
|
return agent_state
|
|
538
577
|
|
|
539
578
|
|
|
540
|
-
@router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent")
|
|
579
|
+
@router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent", deprecated=True)
|
|
541
580
|
async def detach_source(
|
|
542
|
-
|
|
543
|
-
|
|
581
|
+
source_id: SourceId,
|
|
582
|
+
agent_id: AgentId,
|
|
544
583
|
server: "SyncServer" = Depends(get_letta_server),
|
|
545
584
|
headers: HeaderParams = Depends(get_headers),
|
|
546
585
|
):
|
|
@@ -569,8 +608,8 @@ async def detach_source(
|
|
|
569
608
|
|
|
570
609
|
@router.patch("/{agent_id}/folders/detach/{folder_id}", response_model=AgentState, operation_id="detach_folder_from_agent")
|
|
571
610
|
async def detach_folder_from_agent(
|
|
572
|
-
|
|
573
|
-
|
|
611
|
+
folder_id: SourceId,
|
|
612
|
+
agent_id: AgentId,
|
|
574
613
|
server: "SyncServer" = Depends(get_letta_server),
|
|
575
614
|
headers: HeaderParams = Depends(get_headers),
|
|
576
615
|
):
|
|
@@ -597,9 +636,9 @@ async def detach_folder_from_agent(
|
|
|
597
636
|
return agent_state
|
|
598
637
|
|
|
599
638
|
|
|
600
|
-
@router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="
|
|
601
|
-
async def
|
|
602
|
-
agent_id:
|
|
639
|
+
@router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_files_for_agent")
|
|
640
|
+
async def close_all_files_for_agent(
|
|
641
|
+
agent_id: AgentId,
|
|
603
642
|
server: "SyncServer" = Depends(get_letta_server),
|
|
604
643
|
headers: HeaderParams = Depends(get_headers),
|
|
605
644
|
):
|
|
@@ -614,10 +653,10 @@ async def close_all_open_files(
|
|
|
614
653
|
return await server.file_agent_manager.close_all_other_files(agent_id=agent_id, keep_file_names=[], actor=actor)
|
|
615
654
|
|
|
616
655
|
|
|
617
|
-
@router.patch("/{agent_id}/files/{file_id}/open", response_model=List[str], operation_id="
|
|
618
|
-
async def
|
|
619
|
-
|
|
620
|
-
|
|
656
|
+
@router.patch("/{agent_id}/files/{file_id}/open", response_model=List[str], operation_id="open_file_for_agent")
|
|
657
|
+
async def open_file_for_agent(
|
|
658
|
+
file_id: FileId,
|
|
659
|
+
agent_id: AgentId,
|
|
621
660
|
server: "SyncServer" = Depends(get_letta_server),
|
|
622
661
|
headers: HeaderParams = Depends(get_headers),
|
|
623
662
|
):
|
|
@@ -663,10 +702,10 @@ async def open_file(
|
|
|
663
702
|
return closed_files
|
|
664
703
|
|
|
665
704
|
|
|
666
|
-
@router.patch("/{agent_id}/files/{file_id}/close", response_model=None, operation_id="
|
|
667
|
-
async def
|
|
668
|
-
|
|
669
|
-
|
|
705
|
+
@router.patch("/{agent_id}/files/{file_id}/close", response_model=None, operation_id="close_file_for_agent")
|
|
706
|
+
async def close_file_for_agent(
|
|
707
|
+
file_id: FileId,
|
|
708
|
+
agent_id: AgentId,
|
|
670
709
|
server: "SyncServer" = Depends(get_letta_server),
|
|
671
710
|
headers: HeaderParams = Depends(get_headers),
|
|
672
711
|
):
|
|
@@ -690,33 +729,39 @@ async def close_file(
|
|
|
690
729
|
|
|
691
730
|
@router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
|
|
692
731
|
async def retrieve_agent(
|
|
693
|
-
agent_id:
|
|
732
|
+
agent_id: AgentId,
|
|
694
733
|
include_relationships: list[str] | None = Query(
|
|
695
734
|
None,
|
|
696
735
|
description=(
|
|
697
736
|
"Specify which relational fields (e.g., 'tools', 'sources', 'memory') to include in the response. "
|
|
698
737
|
"If not provided, all relationships are loaded by default. "
|
|
699
738
|
"Using this can optimize performance by reducing unnecessary joins."
|
|
739
|
+
"This is a legacy parameter, and no longer supported after 1.0.0 SDK versions."
|
|
700
740
|
),
|
|
701
741
|
),
|
|
742
|
+
include: List[AgentRelationships] = Query(
|
|
743
|
+
[],
|
|
744
|
+
description=("Specify which relational fields to include in the response. No relationships are included by default."),
|
|
745
|
+
),
|
|
702
746
|
server: "SyncServer" = Depends(get_letta_server),
|
|
703
747
|
headers: HeaderParams = Depends(get_headers),
|
|
704
748
|
):
|
|
705
749
|
"""
|
|
706
750
|
Get the state of the agent.
|
|
707
751
|
"""
|
|
708
|
-
# Check if agent_id matches uuid4 format
|
|
709
|
-
if not AGENT_ID_PATTERN.match(agent_id):
|
|
710
|
-
raise HTTPException(status_code=400, detail=f"agent_id {agent_id} is not in the valid format 'agent-<uuid4>'")
|
|
711
752
|
|
|
712
753
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
713
754
|
|
|
714
|
-
|
|
755
|
+
if include_relationships is None and is_1_0_sdk_version(headers):
|
|
756
|
+
include_relationships = [] # don't default include all if using new SDK version
|
|
757
|
+
return await server.agent_manager.get_agent_by_id_async(
|
|
758
|
+
agent_id=agent_id, include_relationships=include_relationships, include=include, actor=actor
|
|
759
|
+
)
|
|
715
760
|
|
|
716
761
|
|
|
717
762
|
@router.delete("/{agent_id}", response_model=None, operation_id="delete_agent")
|
|
718
763
|
async def delete_agent(
|
|
719
|
-
agent_id:
|
|
764
|
+
agent_id: AgentId,
|
|
720
765
|
server: "SyncServer" = Depends(get_letta_server),
|
|
721
766
|
headers: HeaderParams = Depends(get_headers),
|
|
722
767
|
):
|
|
@@ -728,9 +773,9 @@ async def delete_agent(
|
|
|
728
773
|
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Agent id={agent_id} successfully deleted"})
|
|
729
774
|
|
|
730
775
|
|
|
731
|
-
@router.get("/{agent_id}/sources", response_model=list[Source], operation_id="list_agent_sources")
|
|
776
|
+
@router.get("/{agent_id}/sources", response_model=list[Source], operation_id="list_agent_sources", deprecated=True)
|
|
732
777
|
async def list_agent_sources(
|
|
733
|
-
agent_id:
|
|
778
|
+
agent_id: AgentId,
|
|
734
779
|
server: "SyncServer" = Depends(get_letta_server),
|
|
735
780
|
headers: HeaderParams = Depends(get_headers),
|
|
736
781
|
before: Optional[str] = Query(
|
|
@@ -759,9 +804,9 @@ async def list_agent_sources(
|
|
|
759
804
|
)
|
|
760
805
|
|
|
761
806
|
|
|
762
|
-
@router.get("/{agent_id}/folders", response_model=list[Source], operation_id="
|
|
763
|
-
async def
|
|
764
|
-
agent_id:
|
|
807
|
+
@router.get("/{agent_id}/folders", response_model=list[Source], operation_id="list_folders_for_agent")
|
|
808
|
+
async def list_folders_for_agent(
|
|
809
|
+
agent_id: AgentId,
|
|
765
810
|
server: "SyncServer" = Depends(get_letta_server),
|
|
766
811
|
headers: HeaderParams = Depends(get_headers),
|
|
767
812
|
before: Optional[str] = Query(
|
|
@@ -790,9 +835,9 @@ async def list_agent_folders(
|
|
|
790
835
|
)
|
|
791
836
|
|
|
792
837
|
|
|
793
|
-
@router.get("/{agent_id}/files", response_model=PaginatedAgentFiles, operation_id="
|
|
794
|
-
async def
|
|
795
|
-
agent_id:
|
|
838
|
+
@router.get("/{agent_id}/files", response_model=PaginatedAgentFiles, operation_id="list_files_for_agent")
|
|
839
|
+
async def list_files_for_agent(
|
|
840
|
+
agent_id: AgentId,
|
|
796
841
|
before: Optional[str] = Query(
|
|
797
842
|
None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order"
|
|
798
843
|
),
|
|
@@ -812,7 +857,7 @@ async def list_agent_files(
|
|
|
812
857
|
headers: HeaderParams = Depends(get_headers),
|
|
813
858
|
):
|
|
814
859
|
"""
|
|
815
|
-
Get the files attached to an agent with their open/closed status
|
|
860
|
+
Get the files attached to an agent with their open/closed status.
|
|
816
861
|
"""
|
|
817
862
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
818
863
|
|
|
@@ -855,9 +900,9 @@ async def list_agent_files(
|
|
|
855
900
|
|
|
856
901
|
|
|
857
902
|
# TODO: remove? can also get with agent blocks
|
|
858
|
-
@router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
|
|
903
|
+
@router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory", deprecated=True)
|
|
859
904
|
async def retrieve_agent_memory(
|
|
860
|
-
agent_id:
|
|
905
|
+
agent_id: AgentId,
|
|
861
906
|
server: "SyncServer" = Depends(get_letta_server),
|
|
862
907
|
headers: HeaderParams = Depends(get_headers),
|
|
863
908
|
):
|
|
@@ -871,9 +916,9 @@ async def retrieve_agent_memory(
|
|
|
871
916
|
|
|
872
917
|
|
|
873
918
|
@router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
|
|
874
|
-
async def
|
|
875
|
-
agent_id: str,
|
|
919
|
+
async def retrieve_block_for_agent(
|
|
876
920
|
block_label: str,
|
|
921
|
+
agent_id: AgentId,
|
|
877
922
|
server: "SyncServer" = Depends(get_letta_server),
|
|
878
923
|
headers: HeaderParams = Depends(get_headers),
|
|
879
924
|
):
|
|
@@ -886,8 +931,8 @@ async def retrieve_block(
|
|
|
886
931
|
|
|
887
932
|
|
|
888
933
|
@router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
|
|
889
|
-
async def
|
|
890
|
-
agent_id:
|
|
934
|
+
async def list_blocks_for_agent(
|
|
935
|
+
agent_id: AgentId,
|
|
891
936
|
server: "SyncServer" = Depends(get_letta_server),
|
|
892
937
|
headers: HeaderParams = Depends(get_headers),
|
|
893
938
|
before: Optional[str] = Query(
|
|
@@ -918,9 +963,9 @@ async def list_blocks(
|
|
|
918
963
|
|
|
919
964
|
|
|
920
965
|
@router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
|
|
921
|
-
async def
|
|
922
|
-
agent_id: str,
|
|
966
|
+
async def modify_block_for_agent(
|
|
923
967
|
block_label: str,
|
|
968
|
+
agent_id: AgentId,
|
|
924
969
|
block_update: BlockUpdate = Body(...),
|
|
925
970
|
server: "SyncServer" = Depends(get_letta_server),
|
|
926
971
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -941,9 +986,9 @@ async def modify_block(
|
|
|
941
986
|
|
|
942
987
|
|
|
943
988
|
@router.patch("/{agent_id}/core-memory/blocks/attach/{block_id}", response_model=AgentState, operation_id="attach_core_memory_block")
|
|
944
|
-
async def
|
|
945
|
-
|
|
946
|
-
|
|
989
|
+
async def attach_block_to_agent(
|
|
990
|
+
block_id: BlockId,
|
|
991
|
+
agent_id: AgentId,
|
|
947
992
|
server: "SyncServer" = Depends(get_letta_server),
|
|
948
993
|
headers: HeaderParams = Depends(get_headers),
|
|
949
994
|
):
|
|
@@ -955,9 +1000,9 @@ async def attach_block(
|
|
|
955
1000
|
|
|
956
1001
|
|
|
957
1002
|
@router.patch("/{agent_id}/core-memory/blocks/detach/{block_id}", response_model=AgentState, operation_id="detach_core_memory_block")
|
|
958
|
-
async def
|
|
959
|
-
|
|
960
|
-
|
|
1003
|
+
async def detach_block_from_agent(
|
|
1004
|
+
block_id: BlockId,
|
|
1005
|
+
agent_id: AgentId,
|
|
961
1006
|
server: "SyncServer" = Depends(get_letta_server),
|
|
962
1007
|
headers: HeaderParams = Depends(get_headers),
|
|
963
1008
|
):
|
|
@@ -968,9 +1013,85 @@ async def detach_block(
|
|
|
968
1013
|
return await server.agent_manager.detach_block_async(agent_id=agent_id, block_id=block_id, actor=actor)
|
|
969
1014
|
|
|
970
1015
|
|
|
971
|
-
@router.
|
|
1016
|
+
@router.patch("/{agent_id}/archives/attach/{archive_id}", response_model=None, operation_id="attach_archive_to_agent")
|
|
1017
|
+
async def attach_archive_to_agent(
|
|
1018
|
+
archive_id: str,
|
|
1019
|
+
agent_id: AgentId,
|
|
1020
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
1021
|
+
headers: HeaderParams = Depends(get_headers),
|
|
1022
|
+
):
|
|
1023
|
+
"""
|
|
1024
|
+
Attach an archive to an agent.
|
|
1025
|
+
"""
|
|
1026
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1027
|
+
await server.archive_manager.attach_agent_to_archive_async(
|
|
1028
|
+
agent_id=agent_id,
|
|
1029
|
+
archive_id=archive_id,
|
|
1030
|
+
actor=actor,
|
|
1031
|
+
)
|
|
1032
|
+
return None
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
@router.patch("/{agent_id}/archives/detach/{archive_id}", response_model=None, operation_id="detach_archive_from_agent")
|
|
1036
|
+
async def detach_archive_from_agent(
|
|
1037
|
+
archive_id: str,
|
|
1038
|
+
agent_id: AgentId,
|
|
1039
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
1040
|
+
headers: HeaderParams = Depends(get_headers),
|
|
1041
|
+
):
|
|
1042
|
+
"""
|
|
1043
|
+
Detach an archive from an agent.
|
|
1044
|
+
"""
|
|
1045
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1046
|
+
await server.archive_manager.detach_agent_from_archive_async(
|
|
1047
|
+
agent_id=agent_id,
|
|
1048
|
+
archive_id=archive_id,
|
|
1049
|
+
actor=actor,
|
|
1050
|
+
)
|
|
1051
|
+
return None
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
@router.patch("/{agent_id}/identities/attach/{identity_id}", response_model=None, operation_id="attach_identity_to_agent")
|
|
1055
|
+
async def attach_identity_to_agent(
|
|
1056
|
+
identity_id: str,
|
|
1057
|
+
agent_id: AgentId,
|
|
1058
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
1059
|
+
headers: HeaderParams = Depends(get_headers),
|
|
1060
|
+
):
|
|
1061
|
+
"""
|
|
1062
|
+
Attach an identity to an agent.
|
|
1063
|
+
"""
|
|
1064
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1065
|
+
await server.identity_manager.attach_agent_async(
|
|
1066
|
+
identity_id=identity_id,
|
|
1067
|
+
agent_id=agent_id,
|
|
1068
|
+
actor=actor,
|
|
1069
|
+
)
|
|
1070
|
+
return None
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
@router.patch("/{agent_id}/identities/detach/{identity_id}", response_model=None, operation_id="detach_identity_from_agent")
|
|
1074
|
+
async def detach_identity_from_agent(
|
|
1075
|
+
identity_id: str,
|
|
1076
|
+
agent_id: AgentId,
|
|
1077
|
+
server: "SyncServer" = Depends(get_letta_server),
|
|
1078
|
+
headers: HeaderParams = Depends(get_headers),
|
|
1079
|
+
):
|
|
1080
|
+
"""
|
|
1081
|
+
Detach an identity from an agent.
|
|
1082
|
+
"""
|
|
1083
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1084
|
+
await server.identity_manager.detach_agent_async(
|
|
1085
|
+
identity_id=identity_id,
|
|
1086
|
+
agent_id=agent_id,
|
|
1087
|
+
actor=actor,
|
|
1088
|
+
)
|
|
1089
|
+
return None
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
@router.get("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="list_passages", deprecated=True)
|
|
972
1093
|
async def list_passages(
|
|
973
|
-
agent_id:
|
|
1094
|
+
agent_id: AgentId,
|
|
974
1095
|
server: "SyncServer" = Depends(get_letta_server),
|
|
975
1096
|
after: str | None = Query(None, description="Unique ID of the memory to start the query range at."),
|
|
976
1097
|
before: str | None = Query(None, description="Unique ID of the memory to end the query range at."),
|
|
@@ -997,9 +1118,9 @@ async def list_passages(
|
|
|
997
1118
|
)
|
|
998
1119
|
|
|
999
1120
|
|
|
1000
|
-
@router.post("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="create_passage")
|
|
1121
|
+
@router.post("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="create_passage", deprecated=True)
|
|
1001
1122
|
async def create_passage(
|
|
1002
|
-
agent_id:
|
|
1123
|
+
agent_id: AgentId,
|
|
1003
1124
|
request: CreateArchivalMemory = Body(...),
|
|
1004
1125
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1005
1126
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1014,9 +1135,14 @@ async def create_passage(
|
|
|
1014
1135
|
)
|
|
1015
1136
|
|
|
1016
1137
|
|
|
1017
|
-
@router.get(
|
|
1138
|
+
@router.get(
|
|
1139
|
+
"/{agent_id}/archival-memory/search",
|
|
1140
|
+
response_model=ArchivalMemorySearchResponse,
|
|
1141
|
+
operation_id="search_archival_memory",
|
|
1142
|
+
deprecated=True,
|
|
1143
|
+
)
|
|
1018
1144
|
async def search_archival_memory(
|
|
1019
|
-
agent_id:
|
|
1145
|
+
agent_id: AgentId,
|
|
1020
1146
|
query: str = Query(..., description="String to search for using semantic similarity"),
|
|
1021
1147
|
tags: Optional[List[str]] = Query(None, description="Optional list of tags to filter search results"),
|
|
1022
1148
|
tag_match_mode: Literal["any", "all"] = Query(
|
|
@@ -1061,10 +1187,10 @@ async def search_archival_memory(
|
|
|
1061
1187
|
|
|
1062
1188
|
# TODO(ethan): query or path parameter for memory_id?
|
|
1063
1189
|
# @router.delete("/{agent_id}/archival")
|
|
1064
|
-
@router.delete("/{agent_id}/archival-memory/{memory_id}", response_model=None, operation_id="delete_passage")
|
|
1190
|
+
@router.delete("/{agent_id}/archival-memory/{memory_id}", response_model=None, operation_id="delete_passage", deprecated=True)
|
|
1065
1191
|
async def delete_passage(
|
|
1066
|
-
agent_id: str,
|
|
1067
1192
|
memory_id: str,
|
|
1193
|
+
agent_id: AgentId,
|
|
1068
1194
|
# memory_id: str = Query(..., description="Unique ID of the memory to be deleted."),
|
|
1069
1195
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1070
1196
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1085,7 +1211,7 @@ AgentMessagesResponse = Annotated[
|
|
|
1085
1211
|
|
|
1086
1212
|
@router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_messages")
|
|
1087
1213
|
async def list_messages(
|
|
1088
|
-
agent_id:
|
|
1214
|
+
agent_id: AgentId,
|
|
1089
1215
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1090
1216
|
before: Optional[str] = Query(
|
|
1091
1217
|
None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order"
|
|
@@ -1099,9 +1225,9 @@ async def list_messages(
|
|
|
1099
1225
|
),
|
|
1100
1226
|
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
|
1101
1227
|
group_id: str | None = Query(None, description="Group ID to filter messages by."),
|
|
1102
|
-
use_assistant_message: bool = Query(True, description="Whether to use assistant messages"),
|
|
1103
|
-
assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool."),
|
|
1104
|
-
assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument."),
|
|
1228
|
+
use_assistant_message: bool = Query(True, description="Whether to use assistant messages", deprecated=True),
|
|
1229
|
+
assistant_message_tool_name: str = Query(DEFAULT_MESSAGE_TOOL, description="The name of the designated message tool.", deprecated=True),
|
|
1230
|
+
assistant_message_tool_kwarg: str = Query(DEFAULT_MESSAGE_TOOL_KWARG, description="The name of the message argument.", deprecated=True),
|
|
1105
1231
|
include_err: bool | None = Query(
|
|
1106
1232
|
None, description="Whether to include error messages and error statuses. For debugging purposes only."
|
|
1107
1233
|
),
|
|
@@ -1130,8 +1256,8 @@ async def list_messages(
|
|
|
1130
1256
|
|
|
1131
1257
|
@router.patch("/{agent_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_message")
|
|
1132
1258
|
async def modify_message(
|
|
1133
|
-
agent_id:
|
|
1134
|
-
message_id:
|
|
1259
|
+
agent_id: AgentId, # backwards compatible. Consider removing for v1
|
|
1260
|
+
message_id: MessageId,
|
|
1135
1261
|
request: LettaMessageUpdateUnion = Body(...),
|
|
1136
1262
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1137
1263
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1153,8 +1279,8 @@ async def modify_message(
|
|
|
1153
1279
|
operation_id="send_message",
|
|
1154
1280
|
)
|
|
1155
1281
|
async def send_message(
|
|
1156
|
-
agent_id: str,
|
|
1157
1282
|
request_obj: Request, # FastAPI Request
|
|
1283
|
+
agent_id: AgentId,
|
|
1158
1284
|
server: SyncServer = Depends(get_letta_server),
|
|
1159
1285
|
request: LettaRequest = Body(...),
|
|
1160
1286
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1280,8 +1406,8 @@ async def send_message(
|
|
|
1280
1406
|
},
|
|
1281
1407
|
)
|
|
1282
1408
|
async def send_message_streaming(
|
|
1283
|
-
agent_id: str,
|
|
1284
1409
|
request_obj: Request, # FastAPI Request
|
|
1410
|
+
agent_id: AgentId,
|
|
1285
1411
|
server: SyncServer = Depends(get_letta_server),
|
|
1286
1412
|
request: LettaStreamingRequest = Body(...),
|
|
1287
1413
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1291,199 +1417,30 @@ async def send_message_streaming(
|
|
|
1291
1417
|
This endpoint accepts a message from a user and processes it through the agent.
|
|
1292
1418
|
It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
|
|
1293
1419
|
"""
|
|
1294
|
-
|
|
1295
|
-
MetricRegistry().user_message_counter.add(1, get_ctx_attributes())
|
|
1296
|
-
|
|
1297
|
-
# TODO (cliandy): clean this up
|
|
1298
|
-
redis_client = await get_redis_client()
|
|
1420
|
+
from letta.services.streaming_service import StreamingService
|
|
1299
1421
|
|
|
1300
1422
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1301
|
-
# TODO: This is redundant, remove soon
|
|
1302
|
-
agent = await server.agent_manager.get_agent_by_id_async(
|
|
1303
|
-
agent_id, actor, include_relationships=["memory", "multi_agent_group", "sources", "tool_exec_environment_variables", "tools"]
|
|
1304
|
-
)
|
|
1305
|
-
agent_eligible = agent.multi_agent_group is None or agent.multi_agent_group.manager_type in ["sleeptime", "voice_sleeptime"]
|
|
1306
|
-
model_compatible = agent.llm_config.model_endpoint_type in [
|
|
1307
|
-
"anthropic",
|
|
1308
|
-
"openai",
|
|
1309
|
-
"together",
|
|
1310
|
-
"google_ai",
|
|
1311
|
-
"google_vertex",
|
|
1312
|
-
"bedrock",
|
|
1313
|
-
"ollama",
|
|
1314
|
-
"azure",
|
|
1315
|
-
"xai",
|
|
1316
|
-
"groq",
|
|
1317
|
-
"deepseek",
|
|
1318
|
-
]
|
|
1319
|
-
model_compatible_token_streaming = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "bedrock", "deepseek"]
|
|
1320
|
-
if agent.agent_type == AgentType.letta_v1_agent and agent.llm_config.model_endpoint_type in ["google_ai", "google_vertex"]:
|
|
1321
|
-
model_compatible_token_streaming = True
|
|
1322
1423
|
|
|
1323
|
-
#
|
|
1324
|
-
|
|
1325
|
-
runs_manager = RunManager()
|
|
1326
|
-
run = await runs_manager.create_run(
|
|
1327
|
-
pydantic_run=PydanticRun(
|
|
1328
|
-
agent_id=agent_id,
|
|
1329
|
-
background=request.background or False,
|
|
1330
|
-
metadata={
|
|
1331
|
-
"run_type": "send_message_streaming",
|
|
1332
|
-
},
|
|
1333
|
-
request_config=LettaRequestConfig.from_letta_request(request),
|
|
1334
|
-
),
|
|
1335
|
-
actor=actor,
|
|
1336
|
-
)
|
|
1337
|
-
run_update_metadata = None
|
|
1338
|
-
await redis_client.set(f"{REDIS_RUN_ID_PREFIX}:{agent_id}", run.id if run else None)
|
|
1339
|
-
else:
|
|
1340
|
-
run = None
|
|
1341
|
-
|
|
1342
|
-
try:
|
|
1343
|
-
if agent_eligible and model_compatible:
|
|
1344
|
-
agent_loop = AgentLoop.load(agent_state=agent, actor=actor)
|
|
1424
|
+
# use the streaming service for unified stream handling
|
|
1425
|
+
streaming_service = StreamingService(server)
|
|
1345
1426
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
input_messages=request.messages,
|
|
1353
|
-
max_steps=request.max_steps,
|
|
1354
|
-
stream_tokens=request.stream_tokens and model_compatible_token_streaming,
|
|
1355
|
-
run_id=run.id if run else None,
|
|
1356
|
-
use_assistant_message=request.use_assistant_message,
|
|
1357
|
-
request_start_timestamp_ns=request_start_timestamp_ns,
|
|
1358
|
-
include_return_message_types=request.include_return_message_types,
|
|
1359
|
-
)
|
|
1360
|
-
async for chunk in stream:
|
|
1361
|
-
yield chunk
|
|
1362
|
-
|
|
1363
|
-
if run:
|
|
1364
|
-
runs_manager = RunManager()
|
|
1365
|
-
from letta.schemas.enums import RunStatus
|
|
1366
|
-
|
|
1367
|
-
await runs_manager.update_run_by_id_async(
|
|
1368
|
-
run_id=run.id,
|
|
1369
|
-
update=RunUpdate(status=RunStatus.completed, stop_reason=agent_loop.stop_reason.stop_reason.value),
|
|
1370
|
-
actor=actor,
|
|
1371
|
-
)
|
|
1372
|
-
|
|
1373
|
-
except LLMTimeoutError as e:
|
|
1374
|
-
error_data = {
|
|
1375
|
-
"error": {"type": "llm_timeout", "message": "The LLM request timed out. Please try again.", "detail": str(e)}
|
|
1376
|
-
}
|
|
1377
|
-
yield (f"data: {json.dumps(error_data)}\n\n", 504)
|
|
1378
|
-
except LLMRateLimitError as e:
|
|
1379
|
-
error_data = {
|
|
1380
|
-
"error": {
|
|
1381
|
-
"type": "llm_rate_limit",
|
|
1382
|
-
"message": "Rate limit exceeded for LLM model provider. Please wait before making another request.",
|
|
1383
|
-
"detail": str(e),
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
yield (f"data: {json.dumps(error_data)}\n\n", 429)
|
|
1387
|
-
except LLMAuthenticationError as e:
|
|
1388
|
-
error_data = {
|
|
1389
|
-
"error": {
|
|
1390
|
-
"type": "llm_authentication",
|
|
1391
|
-
"message": "Authentication failed with the LLM model provider.",
|
|
1392
|
-
"detail": str(e),
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
yield (f"data: {json.dumps(error_data)}\n\n", 401)
|
|
1396
|
-
except LLMError as e:
|
|
1397
|
-
error_data = {"error": {"type": "llm_error", "message": "An error occurred with the LLM request.", "detail": str(e)}}
|
|
1398
|
-
yield (f"data: {json.dumps(error_data)}\n\n", 502)
|
|
1399
|
-
except Exception as e:
|
|
1400
|
-
error_data = {"error": {"type": "internal_error", "message": "An internal server error occurred.", "detail": str(e)}}
|
|
1401
|
-
yield (f"data: {json.dumps(error_data)}\n\n", 500)
|
|
1402
|
-
|
|
1403
|
-
raw_stream = error_aware_stream()
|
|
1404
|
-
|
|
1405
|
-
from letta.server.rest_api.streaming_response import StreamingResponseWithStatusCode, add_keepalive_to_stream
|
|
1406
|
-
|
|
1407
|
-
if request.background and settings.track_agent_run:
|
|
1408
|
-
if isinstance(redis_client, NoopAsyncRedisClient):
|
|
1409
|
-
raise HTTPException(
|
|
1410
|
-
status_code=503,
|
|
1411
|
-
detail=(
|
|
1412
|
-
"Background streaming requires Redis to be running. "
|
|
1413
|
-
"Please ensure Redis is properly configured. "
|
|
1414
|
-
f"LETTA_REDIS_HOST: {settings.redis_host}, LETTA_REDIS_PORT: {settings.redis_port}"
|
|
1415
|
-
),
|
|
1416
|
-
)
|
|
1417
|
-
|
|
1418
|
-
safe_create_task(
|
|
1419
|
-
create_background_stream_processor(
|
|
1420
|
-
stream_generator=raw_stream,
|
|
1421
|
-
redis_client=redis_client,
|
|
1422
|
-
run_id=run.id,
|
|
1423
|
-
run_manager=server.run_manager,
|
|
1424
|
-
actor=actor,
|
|
1425
|
-
),
|
|
1426
|
-
label=f"background_stream_processor_{run.id}",
|
|
1427
|
-
)
|
|
1427
|
+
run, result = await streaming_service.create_agent_stream(
|
|
1428
|
+
agent_id=agent_id,
|
|
1429
|
+
actor=actor,
|
|
1430
|
+
request=request,
|
|
1431
|
+
run_type="send_message_streaming",
|
|
1432
|
+
)
|
|
1428
1433
|
|
|
1429
|
-
|
|
1430
|
-
redis_client=redis_client,
|
|
1431
|
-
run_id=run.id,
|
|
1432
|
-
)
|
|
1433
|
-
|
|
1434
|
-
# Conditionally wrap with keepalive based on request parameter
|
|
1435
|
-
if request.include_pings and settings.enable_keepalive:
|
|
1436
|
-
stream = add_keepalive_to_stream(raw_stream, keepalive_interval=settings.keepalive_interval)
|
|
1437
|
-
else:
|
|
1438
|
-
stream = raw_stream
|
|
1439
|
-
|
|
1440
|
-
result = StreamingResponseWithStatusCode(
|
|
1441
|
-
stream,
|
|
1442
|
-
media_type="text/event-stream",
|
|
1443
|
-
)
|
|
1444
|
-
else:
|
|
1445
|
-
result = await server.send_message_to_agent(
|
|
1446
|
-
agent_id=agent_id,
|
|
1447
|
-
actor=actor,
|
|
1448
|
-
input_messages=request.messages,
|
|
1449
|
-
stream_steps=True,
|
|
1450
|
-
stream_tokens=request.stream_tokens,
|
|
1451
|
-
# Support for AssistantMessage
|
|
1452
|
-
use_assistant_message=request.use_assistant_message,
|
|
1453
|
-
assistant_message_tool_name=request.assistant_message_tool_name,
|
|
1454
|
-
assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
|
|
1455
|
-
request_start_timestamp_ns=request_start_timestamp_ns,
|
|
1456
|
-
include_return_message_types=request.include_return_message_types,
|
|
1457
|
-
)
|
|
1458
|
-
if settings.track_agent_run:
|
|
1459
|
-
run_status = RunStatus.running
|
|
1460
|
-
return result
|
|
1461
|
-
except PendingApprovalError as e:
|
|
1462
|
-
if settings.track_agent_run:
|
|
1463
|
-
run_update_metadata = {"error": str(e)}
|
|
1464
|
-
run_status = RunStatus.failed
|
|
1465
|
-
raise HTTPException(
|
|
1466
|
-
status_code=409, detail={"code": "PENDING_APPROVAL", "message": str(e), "pending_request_id": e.pending_request_id}
|
|
1467
|
-
)
|
|
1468
|
-
except Exception as e:
|
|
1469
|
-
if settings.track_agent_run:
|
|
1470
|
-
run_update_metadata = {"error": str(e)}
|
|
1471
|
-
run_status = RunStatus.failed
|
|
1472
|
-
raise
|
|
1473
|
-
finally:
|
|
1474
|
-
if settings.track_agent_run:
|
|
1475
|
-
await server.run_manager.update_run_by_id_async(
|
|
1476
|
-
run_id=run.id, update=RunUpdate(status=run_status, metadata=run_update_metadata), actor=actor
|
|
1477
|
-
)
|
|
1434
|
+
return result
|
|
1478
1435
|
|
|
1479
1436
|
|
|
1480
1437
|
class CancelAgentRunRequest(BaseModel):
|
|
1481
1438
|
run_ids: list[str] | None = Field(None, description="Optional list of run IDs to cancel")
|
|
1482
1439
|
|
|
1483
1440
|
|
|
1484
|
-
@router.post("/{agent_id}/messages/cancel", operation_id="
|
|
1485
|
-
async def
|
|
1486
|
-
agent_id:
|
|
1441
|
+
@router.post("/{agent_id}/messages/cancel", operation_id="cancel_message")
|
|
1442
|
+
async def cancel_message(
|
|
1443
|
+
agent_id: AgentId,
|
|
1487
1444
|
request: CancelAgentRunRequest = Body(None),
|
|
1488
1445
|
server: SyncServer = Depends(get_letta_server),
|
|
1489
1446
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1574,6 +1531,9 @@ async def _process_message_background(
|
|
|
1574
1531
|
) -> None:
|
|
1575
1532
|
"""Background task to process the message and update run status."""
|
|
1576
1533
|
request_start_timestamp_ns = get_utc_timestamp_ns()
|
|
1534
|
+
agent_loop = None
|
|
1535
|
+
result = None
|
|
1536
|
+
|
|
1577
1537
|
try:
|
|
1578
1538
|
agent = await server.agent_manager.get_agent_by_id_async(
|
|
1579
1539
|
agent_id, actor, include_relationships=["memory", "multi_agent_group", "sources", "tool_exec_environment_variables", "tools"]
|
|
@@ -1620,9 +1580,14 @@ async def _process_message_background(
|
|
|
1620
1580
|
runs_manager = RunManager()
|
|
1621
1581
|
from letta.schemas.enums import RunStatus
|
|
1622
1582
|
|
|
1583
|
+
if result.stop_reason.stop_reason == "cancelled":
|
|
1584
|
+
run_status = RunStatus.cancelled
|
|
1585
|
+
else:
|
|
1586
|
+
run_status = RunStatus.completed
|
|
1587
|
+
|
|
1623
1588
|
await runs_manager.update_run_by_id_async(
|
|
1624
1589
|
run_id=run_id,
|
|
1625
|
-
update=RunUpdate(status=
|
|
1590
|
+
update=RunUpdate(status=run_status, stop_reason=result.stop_reason.stop_reason),
|
|
1626
1591
|
actor=actor,
|
|
1627
1592
|
)
|
|
1628
1593
|
|
|
@@ -1646,6 +1611,41 @@ async def _process_message_background(
|
|
|
1646
1611
|
update=RunUpdate(status=RunStatus.failed),
|
|
1647
1612
|
actor=actor,
|
|
1648
1613
|
)
|
|
1614
|
+
finally:
|
|
1615
|
+
# Critical: Explicit resource cleanup to prevent accumulation
|
|
1616
|
+
if agent_loop and result:
|
|
1617
|
+
await _cleanup_background_task_resources(agent_loop, result)
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
async def _cleanup_background_task_resources(agent_loop: BaseAgentV2 | LettaAgent, result: StreamingResponse | LettaResponse) -> None:
|
|
1621
|
+
"""
|
|
1622
|
+
Explicit cleanup of resources created during background message processing.
|
|
1623
|
+
|
|
1624
|
+
Proper cleanup of:
|
|
1625
|
+
- Agent instances and their internal state
|
|
1626
|
+
- Message buffers and response accumulation
|
|
1627
|
+
- Any database connections or sessions
|
|
1628
|
+
- LLM client resources
|
|
1629
|
+
"""
|
|
1630
|
+
import gc
|
|
1631
|
+
|
|
1632
|
+
try:
|
|
1633
|
+
if agent_loop is not None:
|
|
1634
|
+
if agent_loop.response_messages:
|
|
1635
|
+
# Clear response message buffer to prevent accumulation
|
|
1636
|
+
agent_loop.response_messages.clear()
|
|
1637
|
+
# Clean up agent loop resources
|
|
1638
|
+
del agent_loop
|
|
1639
|
+
|
|
1640
|
+
if result is not None:
|
|
1641
|
+
del result # Clear result data to free memory
|
|
1642
|
+
|
|
1643
|
+
# Force garbage collection to clean up references and release memory
|
|
1644
|
+
gc.collect()
|
|
1645
|
+
except Exception as e:
|
|
1646
|
+
# Handle errors for logging but don't fail the background task
|
|
1647
|
+
logger.warning(f"Error during background task resource cleanup: {e}")
|
|
1648
|
+
pass
|
|
1649
1649
|
|
|
1650
1650
|
|
|
1651
1651
|
@router.post(
|
|
@@ -1654,7 +1654,7 @@ async def _process_message_background(
|
|
|
1654
1654
|
operation_id="create_agent_message_async",
|
|
1655
1655
|
)
|
|
1656
1656
|
async def send_message_async(
|
|
1657
|
-
agent_id:
|
|
1657
|
+
agent_id: AgentId,
|
|
1658
1658
|
server: SyncServer = Depends(get_letta_server),
|
|
1659
1659
|
request: LettaAsyncRequest = Body(...),
|
|
1660
1660
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1667,8 +1667,14 @@ async def send_message_async(
|
|
|
1667
1667
|
"""
|
|
1668
1668
|
MetricRegistry().user_message_counter.add(1, get_ctx_attributes())
|
|
1669
1669
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1670
|
+
|
|
1671
|
+
try:
|
|
1672
|
+
is_message_input = request.messages[0].type == MessageCreateType.message
|
|
1673
|
+
except:
|
|
1674
|
+
is_message_input = True
|
|
1675
|
+
use_lettuce = headers.experimental_params.message_async and is_message_input
|
|
1676
|
+
|
|
1670
1677
|
# Create a new run
|
|
1671
|
-
use_lettuce = headers.experimental_params.message_async
|
|
1672
1678
|
run = PydanticRun(
|
|
1673
1679
|
callback_url=request.callback_url,
|
|
1674
1680
|
agent_id=agent_id,
|
|
@@ -1750,23 +1756,32 @@ async def send_message_async(
|
|
|
1750
1756
|
return run
|
|
1751
1757
|
|
|
1752
1758
|
|
|
1759
|
+
class ResetMessagesRequest(BaseModel):
|
|
1760
|
+
"""Request body for resetting messages on an agent."""
|
|
1761
|
+
|
|
1762
|
+
add_default_initial_messages: bool = Field(
|
|
1763
|
+
False,
|
|
1764
|
+
description="If true, adds the default initial messages after resetting.",
|
|
1765
|
+
)
|
|
1766
|
+
|
|
1767
|
+
|
|
1753
1768
|
@router.patch("/{agent_id}/reset-messages", response_model=AgentState, operation_id="reset_messages")
|
|
1754
1769
|
async def reset_messages(
|
|
1755
|
-
agent_id:
|
|
1756
|
-
|
|
1770
|
+
agent_id: AgentId,
|
|
1771
|
+
request: ResetMessagesRequest = Body(...),
|
|
1757
1772
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1758
1773
|
headers: HeaderParams = Depends(get_headers),
|
|
1759
1774
|
):
|
|
1760
1775
|
"""Resets the messages for an agent"""
|
|
1761
1776
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1762
1777
|
return await server.agent_manager.reset_messages_async(
|
|
1763
|
-
agent_id=agent_id, actor=actor, add_default_initial_messages=add_default_initial_messages
|
|
1778
|
+
agent_id=agent_id, actor=actor, add_default_initial_messages=request.add_default_initial_messages
|
|
1764
1779
|
)
|
|
1765
1780
|
|
|
1766
1781
|
|
|
1767
|
-
@router.get("/{agent_id}/groups", response_model=list[Group], operation_id="
|
|
1768
|
-
async def
|
|
1769
|
-
agent_id:
|
|
1782
|
+
@router.get("/{agent_id}/groups", response_model=list[Group], operation_id="list_groups_for_agent")
|
|
1783
|
+
async def list_groups_for_agent(
|
|
1784
|
+
agent_id: AgentId,
|
|
1770
1785
|
manager_type: str | None = Query(None, description="Manager type to filter groups by"),
|
|
1771
1786
|
server: "SyncServer" = Depends(get_letta_server),
|
|
1772
1787
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1782,7 +1797,7 @@ async def list_agent_groups(
|
|
|
1782
1797
|
),
|
|
1783
1798
|
order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
|
|
1784
1799
|
):
|
|
1785
|
-
"""Lists the groups for an agent"""
|
|
1800
|
+
"""Lists the groups for an agent."""
|
|
1786
1801
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
|
1787
1802
|
logger.info("in list agents with manager_type", manager_type)
|
|
1788
1803
|
return await server.agent_manager.list_groups_async(
|
|
@@ -1799,10 +1814,10 @@ async def list_agent_groups(
|
|
|
1799
1814
|
@router.post(
|
|
1800
1815
|
"/{agent_id}/messages/preview-raw-payload",
|
|
1801
1816
|
response_model=Dict[str, Any],
|
|
1802
|
-
operation_id="
|
|
1817
|
+
operation_id="preview_model_request",
|
|
1803
1818
|
)
|
|
1804
|
-
async def
|
|
1805
|
-
agent_id:
|
|
1819
|
+
async def preview_model_request(
|
|
1820
|
+
agent_id: AgentId,
|
|
1806
1821
|
request: Union[LettaRequest, LettaStreamingRequest] = Body(...),
|
|
1807
1822
|
server: SyncServer = Depends(get_letta_server),
|
|
1808
1823
|
headers: HeaderParams = Depends(get_headers),
|
|
@@ -1845,19 +1860,14 @@ async def preview_raw_payload(
|
|
|
1845
1860
|
)
|
|
1846
1861
|
|
|
1847
1862
|
|
|
1848
|
-
@router.post("/{agent_id}/summarize", status_code=204, operation_id="
|
|
1849
|
-
async def
|
|
1850
|
-
agent_id:
|
|
1851
|
-
request_obj: Request, # FastAPI Request
|
|
1852
|
-
max_message_length: int = Query(..., description="Maximum number of messages to retain after summarization."),
|
|
1863
|
+
@router.post("/{agent_id}/summarize", status_code=204, operation_id="summarize_messages")
|
|
1864
|
+
async def summarize_messages(
|
|
1865
|
+
agent_id: AgentId,
|
|
1853
1866
|
server: SyncServer = Depends(get_letta_server),
|
|
1854
1867
|
headers: HeaderParams = Depends(get_headers),
|
|
1855
1868
|
):
|
|
1856
1869
|
"""
|
|
1857
|
-
Summarize an agent's conversation history
|
|
1858
|
-
|
|
1859
|
-
This endpoint summarizes the current message history for a given agent,
|
|
1860
|
-
truncating and compressing it down to the specified `max_message_length`.
|
|
1870
|
+
Summarize an agent's conversation history.
|
|
1861
1871
|
"""
|
|
1862
1872
|
|
|
1863
1873
|
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|