letta-nightly 0.11.6.dev20250903104037__py3-none-any.whl → 0.11.7.dev20250904104046__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/__init__.py +1 -1
- letta/agent.py +10 -14
- letta/agents/base_agent.py +18 -0
- letta/agents/helpers.py +32 -7
- letta/agents/letta_agent.py +953 -762
- letta/agents/voice_agent.py +1 -1
- letta/client/streaming.py +0 -1
- letta/constants.py +11 -8
- letta/errors.py +9 -0
- letta/functions/function_sets/base.py +77 -69
- letta/functions/function_sets/builtin.py +41 -22
- letta/functions/function_sets/multi_agent.py +1 -2
- letta/functions/schema_generator.py +0 -1
- letta/helpers/converters.py +8 -3
- letta/helpers/datetime_helpers.py +5 -4
- letta/helpers/message_helper.py +1 -2
- letta/helpers/pinecone_utils.py +0 -1
- letta/helpers/tool_rule_solver.py +10 -0
- letta/helpers/tpuf_client.py +848 -0
- letta/interface.py +8 -8
- letta/interfaces/anthropic_streaming_interface.py +7 -0
- letta/interfaces/openai_streaming_interface.py +29 -6
- letta/llm_api/anthropic_client.py +188 -18
- letta/llm_api/azure_client.py +0 -1
- letta/llm_api/bedrock_client.py +1 -2
- letta/llm_api/deepseek_client.py +319 -5
- letta/llm_api/google_vertex_client.py +75 -17
- letta/llm_api/groq_client.py +0 -1
- letta/llm_api/helpers.py +2 -2
- letta/llm_api/llm_api_tools.py +1 -50
- letta/llm_api/llm_client.py +6 -8
- letta/llm_api/mistral.py +1 -1
- letta/llm_api/openai.py +16 -13
- letta/llm_api/openai_client.py +31 -16
- letta/llm_api/together_client.py +0 -1
- letta/llm_api/xai_client.py +0 -1
- letta/local_llm/chat_completion_proxy.py +7 -6
- letta/local_llm/settings/settings.py +1 -1
- letta/orm/__init__.py +1 -0
- letta/orm/agent.py +8 -6
- letta/orm/archive.py +9 -1
- letta/orm/block.py +3 -4
- letta/orm/block_history.py +3 -1
- letta/orm/group.py +2 -3
- letta/orm/identity.py +1 -2
- letta/orm/job.py +1 -2
- letta/orm/llm_batch_items.py +1 -2
- letta/orm/message.py +8 -4
- letta/orm/mixins.py +18 -0
- letta/orm/organization.py +2 -0
- letta/orm/passage.py +8 -1
- letta/orm/passage_tag.py +55 -0
- letta/orm/sandbox_config.py +1 -3
- letta/orm/step.py +1 -2
- letta/orm/tool.py +1 -0
- letta/otel/resource.py +2 -2
- letta/plugins/plugins.py +1 -1
- letta/prompts/prompt_generator.py +10 -2
- letta/schemas/agent.py +11 -0
- letta/schemas/archive.py +4 -0
- letta/schemas/block.py +13 -0
- letta/schemas/embedding_config.py +0 -1
- letta/schemas/enums.py +24 -7
- letta/schemas/group.py +12 -0
- letta/schemas/letta_message.py +55 -1
- letta/schemas/letta_message_content.py +28 -0
- letta/schemas/letta_request.py +21 -4
- letta/schemas/letta_stop_reason.py +9 -1
- letta/schemas/llm_config.py +24 -8
- letta/schemas/mcp.py +0 -3
- letta/schemas/memory.py +14 -0
- letta/schemas/message.py +245 -141
- letta/schemas/openai/chat_completion_request.py +2 -1
- letta/schemas/passage.py +1 -0
- letta/schemas/providers/bedrock.py +1 -1
- letta/schemas/providers/openai.py +2 -2
- letta/schemas/tool.py +11 -5
- letta/schemas/tool_execution_result.py +0 -1
- letta/schemas/tool_rule.py +71 -0
- letta/serialize_schemas/marshmallow_agent.py +1 -2
- letta/server/rest_api/app.py +3 -3
- letta/server/rest_api/auth/index.py +0 -1
- letta/server/rest_api/interface.py +3 -11
- letta/server/rest_api/redis_stream_manager.py +3 -4
- letta/server/rest_api/routers/v1/agents.py +143 -84
- letta/server/rest_api/routers/v1/blocks.py +1 -1
- letta/server/rest_api/routers/v1/folders.py +1 -1
- letta/server/rest_api/routers/v1/groups.py +23 -22
- letta/server/rest_api/routers/v1/internal_templates.py +68 -0
- letta/server/rest_api/routers/v1/sandbox_configs.py +11 -5
- letta/server/rest_api/routers/v1/sources.py +1 -1
- letta/server/rest_api/routers/v1/tools.py +167 -15
- letta/server/rest_api/streaming_response.py +4 -3
- letta/server/rest_api/utils.py +75 -18
- letta/server/server.py +24 -35
- letta/services/agent_manager.py +359 -45
- letta/services/agent_serialization_manager.py +23 -3
- letta/services/archive_manager.py +72 -3
- letta/services/block_manager.py +1 -2
- letta/services/context_window_calculator/token_counter.py +11 -6
- letta/services/file_manager.py +1 -3
- letta/services/files_agents_manager.py +2 -4
- letta/services/group_manager.py +73 -12
- letta/services/helpers/agent_manager_helper.py +5 -5
- letta/services/identity_manager.py +8 -3
- letta/services/job_manager.py +2 -14
- letta/services/llm_batch_manager.py +1 -3
- letta/services/mcp/base_client.py +1 -2
- letta/services/mcp_manager.py +5 -6
- letta/services/message_manager.py +536 -15
- letta/services/organization_manager.py +1 -2
- letta/services/passage_manager.py +287 -12
- letta/services/provider_manager.py +1 -3
- letta/services/sandbox_config_manager.py +12 -7
- letta/services/source_manager.py +1 -2
- letta/services/step_manager.py +0 -1
- letta/services/summarizer/summarizer.py +4 -2
- letta/services/telemetry_manager.py +1 -3
- letta/services/tool_executor/builtin_tool_executor.py +136 -316
- letta/services/tool_executor/core_tool_executor.py +231 -74
- letta/services/tool_executor/files_tool_executor.py +2 -2
- letta/services/tool_executor/mcp_tool_executor.py +0 -1
- letta/services/tool_executor/multi_agent_tool_executor.py +2 -2
- letta/services/tool_executor/sandbox_tool_executor.py +0 -1
- letta/services/tool_executor/tool_execution_sandbox.py +2 -3
- letta/services/tool_manager.py +181 -64
- letta/services/tool_sandbox/modal_deployment_manager.py +2 -2
- letta/services/user_manager.py +1 -2
- letta/settings.py +5 -3
- letta/streaming_interface.py +3 -3
- letta/system.py +1 -1
- letta/utils.py +0 -1
- {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904104046.dist-info}/METADATA +11 -7
- {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904104046.dist-info}/RECORD +137 -135
- letta/llm_api/deepseek.py +0 -303
- {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904104046.dist-info}/WHEEL +0 -0
- {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904104046.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.11.6.dev20250903104037.dist-info → letta_nightly-0.11.7.dev20250904104046.dist-info}/licenses/LICENSE +0 -0
letta/server/rest_api/utils.py
CHANGED
@@ -7,8 +7,7 @@ from typing import TYPE_CHECKING, AsyncGenerator, Dict, Iterable, List, Optional
|
|
7
7
|
|
8
8
|
from fastapi import Header, HTTPException
|
9
9
|
from openai.types.chat import ChatCompletionMessageParam
|
10
|
-
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
|
11
|
-
from openai.types.chat.chat_completion_message_tool_call import Function as OpenAIFunction
|
10
|
+
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall, Function as OpenAIFunction
|
12
11
|
from openai.types.chat.completion_create_params import CompletionCreateParams
|
13
12
|
from pydantic import BaseModel
|
14
13
|
|
@@ -26,10 +25,11 @@ from letta.log import get_logger
|
|
26
25
|
from letta.otel.context import get_ctx_attributes
|
27
26
|
from letta.otel.metric_registry import MetricRegistry
|
28
27
|
from letta.otel.tracing import tracer
|
28
|
+
from letta.schemas.agent import AgentState
|
29
29
|
from letta.schemas.enums import MessageRole
|
30
30
|
from letta.schemas.letta_message_content import OmittedReasoningContent, ReasoningContent, RedactedReasoningContent, TextContent
|
31
31
|
from letta.schemas.llm_config import LLMConfig
|
32
|
-
from letta.schemas.message import Message, MessageCreate, ToolReturn
|
32
|
+
from letta.schemas.message import ApprovalCreate, Message, MessageCreate, ToolReturn
|
33
33
|
from letta.schemas.tool_execution_result import ToolExecutionResult
|
34
34
|
from letta.schemas.usage import LettaUsageStatistics
|
35
35
|
from letta.schemas.user import User
|
@@ -177,25 +177,31 @@ def create_input_messages(input_messages: List[MessageCreate], agent_id: str, ti
|
|
177
177
|
return messages
|
178
178
|
|
179
179
|
|
180
|
-
def
|
180
|
+
def create_approval_response_message_from_input(agent_state: AgentState, input_message: ApprovalCreate) -> List[Message]:
|
181
|
+
return [
|
182
|
+
Message(
|
183
|
+
role=MessageRole.approval,
|
184
|
+
agent_id=agent_state.id,
|
185
|
+
model=agent_state.llm_config.model,
|
186
|
+
approval_request_id=input_message.approval_request_id,
|
187
|
+
approve=input_message.approve,
|
188
|
+
denial_reason=input_message.reason,
|
189
|
+
)
|
190
|
+
]
|
191
|
+
|
192
|
+
|
193
|
+
def create_approval_request_message_from_llm_response(
|
181
194
|
agent_id: str,
|
182
195
|
model: str,
|
183
196
|
function_name: str,
|
184
197
|
function_arguments: Dict,
|
185
|
-
tool_execution_result: ToolExecutionResult,
|
186
198
|
tool_call_id: str,
|
187
|
-
function_call_success: bool,
|
188
|
-
function_response: Optional[str],
|
189
|
-
timezone: str,
|
190
199
|
actor: User,
|
191
200
|
continue_stepping: bool = False,
|
192
|
-
heartbeat_reason: Optional[str] = None,
|
193
201
|
reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
|
194
202
|
pre_computed_assistant_message_id: Optional[str] = None,
|
195
|
-
llm_batch_item_id: Optional[str] = None,
|
196
203
|
step_id: str | None = None,
|
197
|
-
) ->
|
198
|
-
messages = []
|
204
|
+
) -> Message:
|
199
205
|
# Construct the tool call with the assistant's message
|
200
206
|
# Force set request_heartbeat in tool_args to calculated continue_stepping
|
201
207
|
function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
|
@@ -209,19 +215,68 @@ def create_letta_messages_from_llm_response(
|
|
209
215
|
)
|
210
216
|
# TODO: Use ToolCallContent instead of tool_calls
|
211
217
|
# TODO: This helps preserve ordering
|
212
|
-
|
213
|
-
role=MessageRole.
|
218
|
+
approval_message = Message(
|
219
|
+
role=MessageRole.approval,
|
214
220
|
content=reasoning_content if reasoning_content else [],
|
215
221
|
agent_id=agent_id,
|
216
222
|
model=model,
|
217
223
|
tool_calls=[tool_call],
|
218
224
|
tool_call_id=tool_call_id,
|
219
225
|
created_at=get_utc_time(),
|
220
|
-
|
226
|
+
step_id=step_id,
|
221
227
|
)
|
222
228
|
if pre_computed_assistant_message_id:
|
223
|
-
|
224
|
-
|
229
|
+
approval_message.id = pre_computed_assistant_message_id
|
230
|
+
return approval_message
|
231
|
+
|
232
|
+
|
233
|
+
def create_letta_messages_from_llm_response(
|
234
|
+
agent_id: str,
|
235
|
+
model: str,
|
236
|
+
function_name: str,
|
237
|
+
function_arguments: Dict,
|
238
|
+
tool_execution_result: ToolExecutionResult,
|
239
|
+
tool_call_id: str,
|
240
|
+
function_call_success: bool,
|
241
|
+
function_response: Optional[str],
|
242
|
+
timezone: str,
|
243
|
+
actor: User,
|
244
|
+
continue_stepping: bool = False,
|
245
|
+
heartbeat_reason: Optional[str] = None,
|
246
|
+
reasoning_content: Optional[List[Union[TextContent, ReasoningContent, RedactedReasoningContent, OmittedReasoningContent]]] = None,
|
247
|
+
pre_computed_assistant_message_id: Optional[str] = None,
|
248
|
+
llm_batch_item_id: Optional[str] = None,
|
249
|
+
step_id: str | None = None,
|
250
|
+
is_approval_response: bool | None = None,
|
251
|
+
) -> List[Message]:
|
252
|
+
messages = []
|
253
|
+
if not is_approval_response:
|
254
|
+
# Construct the tool call with the assistant's message
|
255
|
+
# Force set request_heartbeat in tool_args to calculated continue_stepping
|
256
|
+
function_arguments[REQUEST_HEARTBEAT_PARAM] = continue_stepping
|
257
|
+
tool_call = OpenAIToolCall(
|
258
|
+
id=tool_call_id,
|
259
|
+
function=OpenAIFunction(
|
260
|
+
name=function_name,
|
261
|
+
arguments=json.dumps(function_arguments),
|
262
|
+
),
|
263
|
+
type="function",
|
264
|
+
)
|
265
|
+
# TODO: Use ToolCallContent instead of tool_calls
|
266
|
+
# TODO: This helps preserve ordering
|
267
|
+
assistant_message = Message(
|
268
|
+
role=MessageRole.assistant,
|
269
|
+
content=reasoning_content if reasoning_content else [],
|
270
|
+
agent_id=agent_id,
|
271
|
+
model=model,
|
272
|
+
tool_calls=[tool_call],
|
273
|
+
tool_call_id=tool_call_id,
|
274
|
+
created_at=get_utc_time(),
|
275
|
+
batch_item_id=llm_batch_item_id,
|
276
|
+
)
|
277
|
+
if pre_computed_assistant_message_id:
|
278
|
+
assistant_message.id = pre_computed_assistant_message_id
|
279
|
+
messages.append(assistant_message)
|
225
280
|
|
226
281
|
# TODO: Use ToolReturnContent instead of TextContent
|
227
282
|
# TODO: This helps preserve ordering
|
@@ -394,7 +449,9 @@ def convert_in_context_letta_messages_to_openai(in_context_messages: List[Messag
|
|
394
449
|
pass # It's not JSON, leave as-is
|
395
450
|
|
396
451
|
# Finally, convert to dict using your existing method
|
397
|
-
|
452
|
+
m = msg.to_openai_dict()
|
453
|
+
assert m is not None
|
454
|
+
openai_messages.append(m)
|
398
455
|
|
399
456
|
return openai_messages
|
400
457
|
|
letta/server/server.py
CHANGED
@@ -30,8 +30,10 @@ from letta.helpers.datetime_helpers import get_utc_time
|
|
30
30
|
from letta.helpers.json_helpers import json_dumps, json_loads
|
31
31
|
|
32
32
|
# TODO use custom interface
|
33
|
-
from letta.interface import
|
34
|
-
|
33
|
+
from letta.interface import (
|
34
|
+
AgentInterface, # abstract
|
35
|
+
CLIInterface, # for printing to terminal
|
36
|
+
)
|
35
37
|
from letta.log import get_logger
|
36
38
|
from letta.orm.errors import NoResultFound
|
37
39
|
from letta.otel.tracing import log_event, trace_method
|
@@ -52,7 +54,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
|
|
52
54
|
from letta.schemas.llm_config import LLMConfig
|
53
55
|
from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
|
54
56
|
from letta.schemas.message import Message, MessageCreate, MessageUpdate
|
55
|
-
from letta.schemas.passage import Passage
|
57
|
+
from letta.schemas.passage import Passage
|
56
58
|
from letta.schemas.pip_requirement import PipRequirement
|
57
59
|
from letta.schemas.providers import (
|
58
60
|
AnthropicProvider,
|
@@ -1114,7 +1116,7 @@ class SyncServer(Server):
|
|
1114
1116
|
ascending: Optional[bool] = True,
|
1115
1117
|
) -> List[Passage]:
|
1116
1118
|
# iterate over records
|
1117
|
-
records = await self.agent_manager.
|
1119
|
+
records = await self.agent_manager.query_agent_passages_async(
|
1118
1120
|
actor=actor,
|
1119
1121
|
agent_id=agent_id,
|
1120
1122
|
after=after,
|
@@ -1125,18 +1127,17 @@ class SyncServer(Server):
|
|
1125
1127
|
)
|
1126
1128
|
return records
|
1127
1129
|
|
1128
|
-
async def insert_archival_memory_async(
|
1130
|
+
async def insert_archival_memory_async(
|
1131
|
+
self, agent_id: str, memory_contents: str, actor: User, tags: Optional[List[str]], created_at: Optional[datetime]
|
1132
|
+
) -> List[Passage]:
|
1129
1133
|
# Get the agent object (loaded in memory)
|
1130
1134
|
agent_state = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
1131
1135
|
|
1132
|
-
#
|
1133
|
-
passages = await self.passage_manager.insert_passage(
|
1134
|
-
|
1135
|
-
|
1136
|
+
# Use passage manager which handles dual-write to Turbopuffer if enabled
|
1137
|
+
passages = await self.passage_manager.insert_passage(
|
1138
|
+
agent_state=agent_state, text=memory_contents, tags=tags, actor=actor, created_at=created_at
|
1139
|
+
)
|
1136
1140
|
|
1137
|
-
def modify_archival_memory(self, agent_id: str, memory_id: str, passage: PassageUpdate, actor: User) -> List[Passage]:
|
1138
|
-
passage = Passage(**passage.model_dump(exclude_unset=True, exclude_none=True))
|
1139
|
-
passages = self.passage_manager.update_passage_by_id(passage_id=memory_id, passage=passage, actor=actor)
|
1140
1141
|
return passages
|
1141
1142
|
|
1142
1143
|
async def delete_archival_memory_async(self, memory_id: str, actor: User):
|
@@ -1270,7 +1271,7 @@ class SyncServer(Server):
|
|
1270
1271
|
await self.source_manager.delete_source(source_id=source_id, actor=actor)
|
1271
1272
|
|
1272
1273
|
# delete data from passage store
|
1273
|
-
passages_to_be_deleted = await self.agent_manager.
|
1274
|
+
passages_to_be_deleted = await self.agent_manager.query_source_passages_async(actor=actor, source_id=source_id, limit=None)
|
1274
1275
|
await self.passage_manager.delete_source_passages_async(actor=actor, passages=passages_to_be_deleted)
|
1275
1276
|
|
1276
1277
|
# TODO: delete data from agent passage stores (?)
|
@@ -1316,27 +1317,6 @@ class SyncServer(Server):
|
|
1316
1317
|
async def sleeptime_document_ingest_async(
|
1317
1318
|
self, main_agent: AgentState, source: Source, actor: User, clear_history: bool = False
|
1318
1319
|
) -> None:
|
1319
|
-
# TEMPORARILY DISABLE UNTIL V2
|
1320
|
-
# sleeptime_agent_state = await self.create_document_sleeptime_agent_async(main_agent, source, actor, clear_history)
|
1321
|
-
# sleeptime_agent = LettaAgent(
|
1322
|
-
# agent_id=sleeptime_agent_state.id,
|
1323
|
-
# message_manager=self.message_manager,
|
1324
|
-
# agent_manager=self.agent_manager,
|
1325
|
-
# block_manager=self.block_manager,
|
1326
|
-
# job_manager=self.job_manager,
|
1327
|
-
# passage_manager=self.passage_manager,
|
1328
|
-
# actor=actor,
|
1329
|
-
# step_manager=self.step_manager,
|
1330
|
-
# telemetry_manager=self.telemetry_manager if settings.llm_api_logging else NoopTelemetryManager(),
|
1331
|
-
# )
|
1332
|
-
# passages = await self.agent_manager.list_passages_async(actor=actor, source_id=source.id)
|
1333
|
-
# for passage in passages:
|
1334
|
-
# await sleeptime_agent.step(
|
1335
|
-
# input_messages=[
|
1336
|
-
# MessageCreate(role="user", content=passage.text),
|
1337
|
-
# ]
|
1338
|
-
# )
|
1339
|
-
# await self.agent_manager.delete_agent_async(agent_id=sleeptime_agent_state.id, actor=actor)
|
1340
1320
|
pass
|
1341
1321
|
|
1342
1322
|
async def _remove_file_from_agent(self, agent_id: str, file_id: str, actor: User) -> None:
|
@@ -1556,7 +1536,16 @@ class SyncServer(Server):
|
|
1556
1536
|
local_configs = self.get_local_llm_configs()
|
1557
1537
|
llm_models.extend(local_configs)
|
1558
1538
|
|
1559
|
-
|
1539
|
+
# dedupe by handle for uniqueness
|
1540
|
+
# Seems like this is required from the tests?
|
1541
|
+
seen_handles = set()
|
1542
|
+
unique_models = []
|
1543
|
+
for model in llm_models:
|
1544
|
+
if model.handle not in seen_handles:
|
1545
|
+
seen_handles.add(model.handle)
|
1546
|
+
unique_models.append(model)
|
1547
|
+
|
1548
|
+
return unique_models
|
1560
1549
|
|
1561
1550
|
def list_embedding_models(self, actor: User) -> List[EmbeddingConfig]:
|
1562
1551
|
"""List available embedding models"""
|