letta-nightly 0.6.49.dev20250408030511__py3-none-any.whl → 0.6.50.dev20250409043626__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 +1 -1
- letta/agent.py +8 -1
- letta/functions/function_sets/base.py +4 -1
- letta/functions/helpers.py +16 -2
- letta/jobs/__init__.py +0 -0
- letta/jobs/helpers.py +25 -0
- letta/jobs/llm_batch_job_polling.py +204 -0
- letta/jobs/scheduler.py +28 -0
- letta/jobs/types.py +10 -0
- letta/llm_api/anthropic.py +8 -3
- letta/llm_api/anthropic_client.py +5 -4
- letta/llm_api/llm_api_tools.py +2 -0
- letta/llm_api/openai_client.py +3 -1
- letta/memory.py +20 -4
- letta/orm/message.py +21 -5
- letta/schemas/enums.py +1 -0
- letta/schemas/llm_config.py +8 -4
- letta/schemas/message.py +8 -7
- letta/server/rest_api/app.py +11 -0
- letta/server/rest_api/chat_completions_interface.py +1 -0
- letta/server/rest_api/routers/v1/agents.py +16 -3
- letta/server/server.py +5 -1
- letta/services/agent_manager.py +34 -28
- letta/services/helpers/agent_manager_helper.py +3 -1
- letta/services/llm_batch_manager.py +97 -6
- letta/services/tool_sandbox/local_sandbox.py +2 -1
- letta/settings.py +4 -0
- letta/streaming_interface.py +2 -0
- {letta_nightly-0.6.49.dev20250408030511.dist-info → letta_nightly-0.6.50.dev20250409043626.dist-info}/METADATA +5 -4
- {letta_nightly-0.6.49.dev20250408030511.dist-info → letta_nightly-0.6.50.dev20250409043626.dist-info}/RECORD +33 -28
- {letta_nightly-0.6.49.dev20250408030511.dist-info → letta_nightly-0.6.50.dev20250409043626.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.49.dev20250408030511.dist-info → letta_nightly-0.6.50.dev20250409043626.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.49.dev20250408030511.dist-info → letta_nightly-0.6.50.dev20250409043626.dist-info}/entry_points.txt +0 -0
letta/server/rest_api/app.py
CHANGED
|
@@ -16,6 +16,7 @@ from starlette.middleware.cors import CORSMiddleware
|
|
|
16
16
|
from letta.__init__ import __version__
|
|
17
17
|
from letta.constants import ADMIN_PREFIX, API_PREFIX, OPENAI_API_PREFIX
|
|
18
18
|
from letta.errors import BedrockPermissionError, LettaAgentNotFoundError, LettaUserNotFoundError
|
|
19
|
+
from letta.jobs.scheduler import shutdown_cron_scheduler, start_cron_jobs
|
|
19
20
|
from letta.log import get_logger
|
|
20
21
|
from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
|
|
21
22
|
from letta.schemas.letta_message import create_letta_message_union_schema
|
|
@@ -144,6 +145,12 @@ def create_application() -> "FastAPI":
|
|
|
144
145
|
executor = concurrent.futures.ThreadPoolExecutor(max_workers=settings.event_loop_threadpool_max_workers)
|
|
145
146
|
loop.set_default_executor(executor)
|
|
146
147
|
|
|
148
|
+
@app.on_event("startup")
|
|
149
|
+
def on_startup():
|
|
150
|
+
global server
|
|
151
|
+
|
|
152
|
+
start_cron_jobs(server)
|
|
153
|
+
|
|
147
154
|
@app.on_event("shutdown")
|
|
148
155
|
def shutdown_mcp_clients():
|
|
149
156
|
global server
|
|
@@ -159,6 +166,10 @@ def create_application() -> "FastAPI":
|
|
|
159
166
|
t.start()
|
|
160
167
|
t.join()
|
|
161
168
|
|
|
169
|
+
@app.on_event("shutdown")
|
|
170
|
+
def shutdown_scheduler():
|
|
171
|
+
shutdown_cron_scheduler()
|
|
172
|
+
|
|
162
173
|
@app.exception_handler(Exception)
|
|
163
174
|
async def generic_error_handler(request: Request, exc: Exception):
|
|
164
175
|
# Log the actual error for debugging
|
|
@@ -14,7 +14,7 @@ from letta.agents.letta_agent import LettaAgent
|
|
|
14
14
|
from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
|
|
15
15
|
from letta.log import get_logger
|
|
16
16
|
from letta.orm.errors import NoResultFound
|
|
17
|
-
from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent
|
|
17
|
+
from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgent
|
|
18
18
|
from letta.schemas.block import Block, BlockUpdate
|
|
19
19
|
from letta.schemas.job import JobStatus, JobUpdate, LettaRequestConfig
|
|
20
20
|
from letta.schemas.letta_message import LettaMessageUnion, LettaMessageUpdateUnion
|
|
@@ -31,6 +31,7 @@ from letta.schemas.user import User
|
|
|
31
31
|
from letta.serialize_schemas.pydantic_agent_schema import AgentSchema
|
|
32
32
|
from letta.server.rest_api.utils import get_letta_server
|
|
33
33
|
from letta.server.server import SyncServer
|
|
34
|
+
from letta.settings import settings
|
|
34
35
|
|
|
35
36
|
# These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
|
|
36
37
|
|
|
@@ -593,7 +594,13 @@ async def send_message(
|
|
|
593
594
|
# TODO: This is redundant, remove soon
|
|
594
595
|
agent = server.agent_manager.get_agent_by_id(agent_id, actor)
|
|
595
596
|
|
|
596
|
-
if
|
|
597
|
+
if (
|
|
598
|
+
agent.llm_config.model_endpoint_type == "anthropic"
|
|
599
|
+
and not agent.enable_sleeptime
|
|
600
|
+
and not agent.multi_agent_group
|
|
601
|
+
and not agent.agent_type == AgentType.sleeptime_agent
|
|
602
|
+
and settings.use_experimental
|
|
603
|
+
):
|
|
597
604
|
experimental_agent = LettaAgent(
|
|
598
605
|
agent_id=agent_id,
|
|
599
606
|
message_manager=server.message_manager,
|
|
@@ -649,7 +656,13 @@ async def send_message_streaming(
|
|
|
649
656
|
# TODO: This is redundant, remove soon
|
|
650
657
|
agent = server.agent_manager.get_agent_by_id(agent_id, actor)
|
|
651
658
|
|
|
652
|
-
if
|
|
659
|
+
if (
|
|
660
|
+
agent.llm_config.model_endpoint_type == "anthropic"
|
|
661
|
+
and not agent.enable_sleeptime
|
|
662
|
+
and not agent.multi_agent_group
|
|
663
|
+
and not agent.agent_type == AgentType.sleeptime_agent
|
|
664
|
+
and settings.use_experimental
|
|
665
|
+
):
|
|
653
666
|
experimental_agent = LettaAgent(
|
|
654
667
|
agent_id=agent_id,
|
|
655
668
|
message_manager=server.message_manager,
|
letta/server/server.py
CHANGED
|
@@ -8,6 +8,7 @@ from abc import abstractmethod
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
10
10
|
|
|
11
|
+
from anthropic import AsyncAnthropic
|
|
11
12
|
from composio.client import Composio
|
|
12
13
|
from composio.client.collections import ActionModel, AppModel
|
|
13
14
|
from fastapi import HTTPException
|
|
@@ -352,6 +353,9 @@ class SyncServer(Server):
|
|
|
352
353
|
self._llm_config_cache = {}
|
|
353
354
|
self._embedding_config_cache = {}
|
|
354
355
|
|
|
356
|
+
# TODO: Replace this with the Anthropic client we have in house
|
|
357
|
+
self.anthropic_async_client = AsyncAnthropic()
|
|
358
|
+
|
|
355
359
|
def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
356
360
|
"""Updated method to load agents from persisted storage"""
|
|
357
361
|
agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
|
@@ -775,7 +779,7 @@ class SyncServer(Server):
|
|
|
775
779
|
|
|
776
780
|
def create_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
|
|
777
781
|
request = CreateAgent(
|
|
778
|
-
name=main_agent.name,
|
|
782
|
+
name=main_agent.name + "-sleeptime",
|
|
779
783
|
agent_type=AgentType.sleeptime_agent,
|
|
780
784
|
block_ids=[block.id for block in main_agent.memory.blocks],
|
|
781
785
|
memory_blocks=[
|
letta/services/agent_manager.py
CHANGED
|
@@ -38,7 +38,7 @@ from letta.schemas.group import ManagerType
|
|
|
38
38
|
from letta.schemas.llm_config import LLMConfig
|
|
39
39
|
from letta.schemas.memory import Memory
|
|
40
40
|
from letta.schemas.message import Message as PydanticMessage
|
|
41
|
-
from letta.schemas.message import MessageCreate
|
|
41
|
+
from letta.schemas.message import MessageCreate, MessageUpdate
|
|
42
42
|
from letta.schemas.passage import Passage as PydanticPassage
|
|
43
43
|
from letta.schemas.source import Source as PydanticSource
|
|
44
44
|
from letta.schemas.tool import Tool as PydanticTool
|
|
@@ -115,9 +115,10 @@ class AgentManager:
|
|
|
115
115
|
block = self.block_manager.create_or_update_block(PydanticBlock(**create_block.model_dump(to_orm=True)), actor=actor)
|
|
116
116
|
block_ids.append(block.id)
|
|
117
117
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
# add passed in `tools`
|
|
119
|
+
tool_names = agent_create.tools or []
|
|
120
|
+
|
|
121
|
+
# add base tools
|
|
121
122
|
if agent_create.include_base_tools:
|
|
122
123
|
if agent_create.agent_type == AgentType.sleeptime_agent:
|
|
123
124
|
tool_names.extend(BASE_SLEEPTIME_TOOLS)
|
|
@@ -128,42 +129,45 @@ class AgentManager:
|
|
|
128
129
|
tool_names.extend(BASE_TOOLS + BASE_MEMORY_TOOLS)
|
|
129
130
|
if agent_create.include_multi_agent_tools:
|
|
130
131
|
tool_names.extend(MULTI_AGENT_TOOLS)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Remove duplicates
|
|
132
|
+
|
|
133
|
+
# remove duplicates
|
|
134
134
|
tool_names = list(set(tool_names))
|
|
135
135
|
|
|
136
|
+
# convert tool names to ids
|
|
137
|
+
tool_ids = []
|
|
138
|
+
for tool_name in tool_names:
|
|
139
|
+
tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
140
|
+
if not tool:
|
|
141
|
+
raise ValueError(f"Tool {tool_name} not found")
|
|
142
|
+
tool_ids.append(tool.id)
|
|
143
|
+
|
|
144
|
+
# add passed in `tool_ids`
|
|
145
|
+
for tool_id in agent_create.tool_ids or []:
|
|
146
|
+
if tool_id not in tool_ids:
|
|
147
|
+
tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=actor)
|
|
148
|
+
if tool:
|
|
149
|
+
tool_ids.append(tool.id)
|
|
150
|
+
tool_names.append(tool.name)
|
|
151
|
+
else:
|
|
152
|
+
raise ValueError(f"Tool {tool_id} not found")
|
|
153
|
+
|
|
136
154
|
# add default tool rules
|
|
155
|
+
tool_rules = agent_create.tool_rules or []
|
|
137
156
|
if agent_create.include_base_tool_rules:
|
|
138
|
-
if not agent_create.tool_rules:
|
|
139
|
-
tool_rules = []
|
|
140
|
-
else:
|
|
141
|
-
tool_rules = agent_create.tool_rules
|
|
142
|
-
|
|
143
157
|
# apply default tool rules
|
|
144
158
|
for tool_name in tool_names:
|
|
145
159
|
if tool_name == "send_message" or tool_name == "send_message_to_agent_async" or tool_name == "finish_rethinking_memory":
|
|
146
160
|
tool_rules.append(PydanticTerminalToolRule(tool_name=tool_name))
|
|
147
|
-
elif tool_name in BASE_TOOLS:
|
|
161
|
+
elif tool_name in BASE_TOOLS + BASE_MEMORY_TOOLS + BASE_SLEEPTIME_TOOLS:
|
|
148
162
|
tool_rules.append(PydanticContinueToolRule(tool_name=tool_name))
|
|
149
163
|
|
|
150
164
|
if agent_create.agent_type == AgentType.sleeptime_agent:
|
|
151
165
|
tool_rules.append(PydanticChildToolRule(tool_name="view_core_memory_with_line_numbers", children=["core_memory_insert"]))
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
tool_rules = agent_create.tool_rules
|
|
155
|
-
# Check tool rules are valid
|
|
167
|
+
# if custom rules, check tool rules are valid
|
|
156
168
|
if agent_create.tool_rules:
|
|
157
169
|
check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=agent_create.tool_rules)
|
|
158
170
|
|
|
159
|
-
tool_ids = agent_create.tool_ids or []
|
|
160
|
-
for tool_name in tool_names:
|
|
161
|
-
tool = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
162
|
-
if tool:
|
|
163
|
-
tool_ids.append(tool.id)
|
|
164
|
-
# Remove duplicates
|
|
165
|
-
tool_ids = list(set(tool_ids))
|
|
166
|
-
|
|
167
171
|
# Create the agent
|
|
168
172
|
agent_state = self._create_agent(
|
|
169
173
|
name=agent_create.name,
|
|
@@ -714,10 +718,12 @@ class AgentManager:
|
|
|
714
718
|
model=agent_state.llm_config.model,
|
|
715
719
|
openai_message_dict={"role": "system", "content": new_system_message_str},
|
|
716
720
|
)
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
+
message = self.message_manager.update_message_by_id(
|
|
722
|
+
message_id=curr_system_message.id,
|
|
723
|
+
message_update=MessageUpdate(**message.model_dump()),
|
|
724
|
+
actor=actor,
|
|
725
|
+
)
|
|
726
|
+
return self.set_in_context_messages(agent_id=agent_id, message_ids=agent_state.message_ids, actor=actor)
|
|
721
727
|
else:
|
|
722
728
|
return agent_state
|
|
723
729
|
|
|
@@ -238,7 +238,9 @@ def initialize_message_sequence(
|
|
|
238
238
|
first_user_message = get_login_event() # event letting Letta know the user just logged in
|
|
239
239
|
|
|
240
240
|
if include_initial_boot_message:
|
|
241
|
-
if agent_state.
|
|
241
|
+
if agent_state.agent_type == AgentType.sleeptime_agent:
|
|
242
|
+
initial_boot_messages = []
|
|
243
|
+
elif agent_state.llm_config.model is not None and "gpt-3.5" in agent_state.llm_config.model:
|
|
242
244
|
initial_boot_messages = get_initial_boot_messages("startup_with_send_message_gpt35")
|
|
243
245
|
else:
|
|
244
246
|
initial_boot_messages = get_initial_boot_messages("startup_with_send_message")
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import List, Optional
|
|
3
3
|
|
|
4
4
|
from anthropic.types.beta.messages import BetaMessageBatch, BetaMessageBatchIndividualResponse
|
|
5
|
+
from sqlalchemy import tuple_
|
|
5
6
|
|
|
7
|
+
from letta.jobs.types import BatchPollingResult, ItemUpdateInfo
|
|
6
8
|
from letta.log import get_logger
|
|
7
9
|
from letta.orm.llm_batch_items import LLMBatchItem
|
|
8
10
|
from letta.orm.llm_batch_job import LLMBatchJob
|
|
9
11
|
from letta.schemas.agent import AgentStepState
|
|
10
|
-
from letta.schemas.enums import AgentStepStatus, JobStatus
|
|
12
|
+
from letta.schemas.enums import AgentStepStatus, JobStatus, ProviderType
|
|
11
13
|
from letta.schemas.llm_batch_job import LLMBatchItem as PydanticLLMBatchItem
|
|
12
14
|
from letta.schemas.llm_batch_job import LLMBatchJob as PydanticLLMBatchJob
|
|
13
15
|
from letta.schemas.llm_config import LLMConfig
|
|
@@ -28,7 +30,7 @@ class LLMBatchManager:
|
|
|
28
30
|
@enforce_types
|
|
29
31
|
def create_batch_request(
|
|
30
32
|
self,
|
|
31
|
-
llm_provider:
|
|
33
|
+
llm_provider: ProviderType,
|
|
32
34
|
create_batch_response: BetaMessageBatch,
|
|
33
35
|
actor: PydanticUser,
|
|
34
36
|
status: JobStatus = JobStatus.created,
|
|
@@ -45,7 +47,7 @@ class LLMBatchManager:
|
|
|
45
47
|
return batch.to_pydantic()
|
|
46
48
|
|
|
47
49
|
@enforce_types
|
|
48
|
-
def
|
|
50
|
+
def get_batch_job_by_id(self, batch_id: str, actor: Optional[PydanticUser] = None) -> PydanticLLMBatchJob:
|
|
49
51
|
"""Retrieve a single batch job by ID."""
|
|
50
52
|
with self.session_maker() as session:
|
|
51
53
|
batch = LLMBatchJob.read(db_session=session, identifier=batch_id, actor=actor)
|
|
@@ -56,7 +58,7 @@ class LLMBatchManager:
|
|
|
56
58
|
self,
|
|
57
59
|
batch_id: str,
|
|
58
60
|
status: JobStatus,
|
|
59
|
-
actor: PydanticUser,
|
|
61
|
+
actor: Optional[PydanticUser] = None,
|
|
60
62
|
latest_polling_response: Optional[BetaMessageBatch] = None,
|
|
61
63
|
) -> PydanticLLMBatchJob:
|
|
62
64
|
"""Update a batch job’s status and optionally its polling response."""
|
|
@@ -65,7 +67,34 @@ class LLMBatchManager:
|
|
|
65
67
|
batch.status = status
|
|
66
68
|
batch.latest_polling_response = latest_polling_response
|
|
67
69
|
batch.last_polled_at = datetime.datetime.now(datetime.timezone.utc)
|
|
68
|
-
|
|
70
|
+
batch = batch.update(db_session=session, actor=actor)
|
|
71
|
+
return batch.to_pydantic()
|
|
72
|
+
|
|
73
|
+
def bulk_update_batch_statuses(
|
|
74
|
+
self,
|
|
75
|
+
updates: List[BatchPollingResult],
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Efficiently update many LLMBatchJob rows. This is used by the cron jobs.
|
|
79
|
+
|
|
80
|
+
`updates` = [(batch_id, new_status, polling_response_or_None), …]
|
|
81
|
+
"""
|
|
82
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
83
|
+
|
|
84
|
+
with self.session_maker() as session:
|
|
85
|
+
mappings = []
|
|
86
|
+
for batch_id, status, response in updates:
|
|
87
|
+
mappings.append(
|
|
88
|
+
{
|
|
89
|
+
"id": batch_id,
|
|
90
|
+
"status": status,
|
|
91
|
+
"latest_polling_response": response,
|
|
92
|
+
"last_polled_at": now,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
session.bulk_update_mappings(LLMBatchJob, mappings)
|
|
97
|
+
session.commit()
|
|
69
98
|
|
|
70
99
|
@enforce_types
|
|
71
100
|
def delete_batch_request(self, batch_id: str, actor: PydanticUser) -> None:
|
|
@@ -74,6 +103,18 @@ class LLMBatchManager:
|
|
|
74
103
|
batch = LLMBatchJob.read(db_session=session, identifier=batch_id, actor=actor)
|
|
75
104
|
batch.hard_delete(db_session=session, actor=actor)
|
|
76
105
|
|
|
106
|
+
@enforce_types
|
|
107
|
+
def list_running_batches(self, actor: Optional[PydanticUser] = None) -> List[PydanticLLMBatchJob]:
|
|
108
|
+
"""Return all running LLM batch jobs, optionally filtered by actor's organization."""
|
|
109
|
+
with self.session_maker() as session:
|
|
110
|
+
query = session.query(LLMBatchJob).filter(LLMBatchJob.status == JobStatus.running)
|
|
111
|
+
|
|
112
|
+
if actor is not None:
|
|
113
|
+
query = query.filter(LLMBatchJob.organization_id == actor.organization_id)
|
|
114
|
+
|
|
115
|
+
results = query.all()
|
|
116
|
+
return [batch.to_pydantic() for batch in results]
|
|
117
|
+
|
|
77
118
|
@enforce_types
|
|
78
119
|
def create_batch_item(
|
|
79
120
|
self,
|
|
@@ -131,6 +172,56 @@ class LLMBatchManager:
|
|
|
131
172
|
|
|
132
173
|
return item.update(db_session=session, actor=actor).to_pydantic()
|
|
133
174
|
|
|
175
|
+
def bulk_update_batch_items_by_agent(
|
|
176
|
+
self,
|
|
177
|
+
updates: List[ItemUpdateInfo],
|
|
178
|
+
) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Efficiently update LLMBatchItem rows by (batch_id, agent_id).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
updates: List of tuples:
|
|
184
|
+
(batch_id, agent_id, new_request_status, batch_request_result)
|
|
185
|
+
"""
|
|
186
|
+
with self.session_maker() as session:
|
|
187
|
+
# For bulk_update_mappings, we need the primary key of each row
|
|
188
|
+
# So we must map (batch_id, agent_id) → actual PK (id)
|
|
189
|
+
# We'll do it in one DB query using the (batch_id, agent_id) sets
|
|
190
|
+
|
|
191
|
+
# 1. Gather the pairs
|
|
192
|
+
key_pairs = [(b_id, a_id) for (b_id, a_id, *_rest) in updates]
|
|
193
|
+
|
|
194
|
+
# 2. Query items in a single step
|
|
195
|
+
items = (
|
|
196
|
+
session.query(LLMBatchItem.id, LLMBatchItem.batch_id, LLMBatchItem.agent_id)
|
|
197
|
+
.filter(tuple_(LLMBatchItem.batch_id, LLMBatchItem.agent_id).in_(key_pairs))
|
|
198
|
+
.all()
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Build a map from (batch_id, agent_id) → PK id
|
|
202
|
+
pair_to_pk = {}
|
|
203
|
+
for row_id, row_batch_id, row_agent_id in items:
|
|
204
|
+
pair_to_pk[(row_batch_id, row_agent_id)] = row_id
|
|
205
|
+
|
|
206
|
+
# 3. Construct mappings for the PK-based bulk update
|
|
207
|
+
mappings = []
|
|
208
|
+
for batch_id, agent_id, new_status, new_result in updates:
|
|
209
|
+
pk_id = pair_to_pk.get((batch_id, agent_id))
|
|
210
|
+
if not pk_id:
|
|
211
|
+
# Nonexistent or mismatch → skip
|
|
212
|
+
continue
|
|
213
|
+
mappings.append(
|
|
214
|
+
{
|
|
215
|
+
"id": pk_id,
|
|
216
|
+
"request_status": new_status,
|
|
217
|
+
"batch_request_result": new_result,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if mappings:
|
|
222
|
+
session.bulk_update_mappings(LLMBatchItem, mappings)
|
|
223
|
+
session.commit()
|
|
224
|
+
|
|
134
225
|
@enforce_types
|
|
135
226
|
def delete_batch_item(self, item_id: str, actor: PydanticUser) -> None:
|
|
136
227
|
"""Hard delete a batch item by ID."""
|
|
@@ -12,6 +12,7 @@ from letta.services.helpers.tool_execution_helper import (
|
|
|
12
12
|
install_pip_requirements_for_sandbox,
|
|
13
13
|
)
|
|
14
14
|
from letta.services.tool_sandbox.base import AsyncToolSandboxBase
|
|
15
|
+
from letta.settings import tool_settings
|
|
15
16
|
from letta.tracing import log_event, trace_method
|
|
16
17
|
from letta.utils import get_friendly_error_msg
|
|
17
18
|
|
|
@@ -152,7 +153,7 @@ class AsyncToolSandboxLocal(AsyncToolSandboxBase):
|
|
|
152
153
|
)
|
|
153
154
|
|
|
154
155
|
try:
|
|
155
|
-
stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=
|
|
156
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=tool_settings.local_sandbox_timeout)
|
|
156
157
|
except asyncio.TimeoutError:
|
|
157
158
|
# Terminate the process on timeout
|
|
158
159
|
if process.returncode is None:
|
letta/settings.py
CHANGED
|
@@ -17,6 +17,7 @@ class ToolSettings(BaseSettings):
|
|
|
17
17
|
|
|
18
18
|
# Local Sandbox configurations
|
|
19
19
|
local_sandbox_dir: Optional[str] = None
|
|
20
|
+
local_sandbox_timeout: float = 180
|
|
20
21
|
|
|
21
22
|
# MCP settings
|
|
22
23
|
mcp_connect_to_server_timeout: float = 30.0
|
|
@@ -203,6 +204,9 @@ class Settings(BaseSettings):
|
|
|
203
204
|
httpx_max_keepalive_connections: int = 500
|
|
204
205
|
httpx_keepalive_expiry: float = 120.0
|
|
205
206
|
|
|
207
|
+
# cron job parameters
|
|
208
|
+
poll_running_llm_batches_interval_seconds: int = 5 * 60
|
|
209
|
+
|
|
206
210
|
@property
|
|
207
211
|
def letta_pg_uri(self) -> str:
|
|
208
212
|
if self.pg_uri:
|
letta/streaming_interface.py
CHANGED
|
@@ -54,6 +54,7 @@ class AgentChunkStreamingInterface(ABC):
|
|
|
54
54
|
message_id: str,
|
|
55
55
|
message_date: datetime,
|
|
56
56
|
expect_reasoning_content: bool = False,
|
|
57
|
+
name: Optional[str] = None,
|
|
57
58
|
message_index: int = 0,
|
|
58
59
|
):
|
|
59
60
|
"""Process a streaming chunk from an OpenAI-compatible server"""
|
|
@@ -105,6 +106,7 @@ class StreamingCLIInterface(AgentChunkStreamingInterface):
|
|
|
105
106
|
message_id: str,
|
|
106
107
|
message_date: datetime,
|
|
107
108
|
expect_reasoning_content: bool = False,
|
|
109
|
+
name: Optional[str] = None,
|
|
108
110
|
message_index: int = 0,
|
|
109
111
|
):
|
|
110
112
|
assert len(chunk.choices) == 1, chunk
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: letta-nightly
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.50.dev20250409043626
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -24,6 +24,7 @@ Provides-Extra: server
|
|
|
24
24
|
Provides-Extra: tests
|
|
25
25
|
Requires-Dist: alembic (>=1.13.3,<2.0.0)
|
|
26
26
|
Requires-Dist: anthropic (>=0.49.0,<0.50.0)
|
|
27
|
+
Requires-Dist: apscheduler (>=3.11.0,<4.0.0)
|
|
27
28
|
Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev" or extra == "all"
|
|
28
29
|
Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev" or extra == "all"
|
|
29
30
|
Requires-Dist: boto3 (>=1.36.24,<2.0.0) ; extra == "bedrock"
|
|
@@ -31,7 +32,7 @@ Requires-Dist: brotli (>=1.1.0,<2.0.0)
|
|
|
31
32
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
32
33
|
Requires-Dist: composio-core (>=0.7.7,<0.8.0)
|
|
33
34
|
Requires-Dist: composio-langchain (>=0.7.7,<0.8.0)
|
|
34
|
-
Requires-Dist: datamodel-code-generator[http] (>=0.25.0,<0.26.0)
|
|
35
|
+
Requires-Dist: datamodel-code-generator[http] (>=0.25.0,<0.26.0)
|
|
35
36
|
Requires-Dist: demjson3 (>=3.0.6,<4.0.0)
|
|
36
37
|
Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
|
|
37
38
|
Requires-Dist: docstring-parser (>=0.16,<0.17)
|
|
@@ -49,12 +50,12 @@ Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
|
|
|
49
50
|
Requires-Dist: jinja2 (>=3.1.5,<4.0.0)
|
|
50
51
|
Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
|
|
51
52
|
Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "desktop" or extra == "all"
|
|
52
|
-
Requires-Dist: letta_client (>=0.1.97,<0.2.0)
|
|
53
|
+
Requires-Dist: letta_client (>=0.1.97,<0.2.0)
|
|
53
54
|
Requires-Dist: llama-index (>=0.12.2,<0.13.0)
|
|
54
55
|
Requires-Dist: llama-index-embeddings-openai (>=0.3.1,<0.4.0)
|
|
55
56
|
Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "desktop" or extra == "all"
|
|
56
57
|
Requires-Dist: marshmallow-sqlalchemy (>=1.4.1,<2.0.0)
|
|
57
|
-
Requires-Dist: mcp (>=1.3.0,<2.0.0)
|
|
58
|
+
Requires-Dist: mcp (>=1.3.0,<2.0.0)
|
|
58
59
|
Requires-Dist: nltk (>=3.8.1,<4.0.0)
|
|
59
60
|
Requires-Dist: numpy (>=1.26.2,<2.0.0)
|
|
60
61
|
Requires-Dist: openai (>=1.60.0,<2.0.0)
|