letta-nightly 0.6.21.dev20250205104040__py3-none-any.whl → 0.6.22.dev20250206104042__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.6.21"
1
+ __version__ = "0.6.22"
2
2
 
3
3
 
4
4
  # import clients
letta/agent.py CHANGED
@@ -505,8 +505,9 @@ class Agent(BaseAgent):
505
505
  function_response, sandbox_run_result = self.execute_tool_and_persist_state(function_name, function_args, target_letta_tool)
506
506
 
507
507
  if sandbox_run_result and sandbox_run_result.status == "error":
508
- error_msg = f"Error calling function {function_name} with args {function_args}: {sandbox_run_result.stderr}"
509
- messages = self._handle_function_error_response(error_msg, tool_call_id, function_name, function_response, messages)
508
+ messages = self._handle_function_error_response(
509
+ function_response, tool_call_id, function_name, function_response, messages
510
+ )
510
511
  return messages, False, True # force a heartbeat to allow agent to handle error
511
512
 
512
513
  # handle trunction
@@ -790,6 +791,7 @@ class Agent(BaseAgent):
790
791
  actor=self.user,
791
792
  provider_name=self.agent_state.llm_config.model_endpoint_type,
792
793
  model=self.agent_state.llm_config.model,
794
+ model_endpoint=self.agent_state.llm_config.model_endpoint,
793
795
  context_window_limit=self.agent_state.llm_config.context_window,
794
796
  usage=response.usage,
795
797
  # TODO(@caren): Add full provider support - this line is a workaround for v0 BYOK feature
letta/client/client.py CHANGED
@@ -463,7 +463,7 @@ class RESTClient(AbstractClient):
463
463
  if token:
464
464
  self.headers = {"accept": "application/json", "Authorization": f"Bearer {token}"}
465
465
  elif password:
466
- self.headers = {"accept": "application/json", "X-BARE-PASSWORD": f"password {password}"}
466
+ self.headers = {"accept": "application/json", "Authorization": f"Bearer {password}"}
467
467
  else:
468
468
  self.headers = {"accept": "application/json"}
469
469
  if headers:
letta/constants.py CHANGED
@@ -53,6 +53,7 @@ BASE_MEMORY_TOOLS = ["core_memory_append", "core_memory_replace"]
53
53
  MULTI_AGENT_TOOLS = ["send_message_to_agent_and_wait_for_reply", "send_message_to_agents_matching_all_tags", "send_message_to_agent_async"]
54
54
  MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES = 3
55
55
  MULTI_AGENT_SEND_MESSAGE_TIMEOUT = 20 * 60
56
+ MULTI_AGENT_CONCURRENT_SENDS = 15
56
57
 
57
58
  # The name of the tool used to send message to the user
58
59
  # May not be relevant in cases where the agent has multiple ways to message to user (send_imessage, send_discord_mesasge, ...)
@@ -1,11 +1,13 @@
1
1
  import asyncio
2
2
  from typing import TYPE_CHECKING, List
3
3
 
4
- from letta.constants import MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES, MULTI_AGENT_SEND_MESSAGE_TIMEOUT
5
- from letta.functions.helpers import async_send_message_with_retries, execute_send_message_to_agent, fire_and_forget_send_to_agent
4
+ from letta.functions.helpers import (
5
+ _send_message_to_agents_matching_all_tags_async,
6
+ execute_send_message_to_agent,
7
+ fire_and_forget_send_to_agent,
8
+ )
6
9
  from letta.schemas.enums import MessageRole
7
10
  from letta.schemas.message import MessageCreate
8
- from letta.server.rest_api.utils import get_letta_server
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  from letta.agent import Agent
@@ -22,12 +24,13 @@ def send_message_to_agent_and_wait_for_reply(self: "Agent", message: str, other_
22
24
  Returns:
23
25
  str: The response from the target agent.
24
26
  """
25
- message = (
27
+ augmented_message = (
26
28
  f"[Incoming message from agent with ID '{self.agent_state.id}' - to reply to this message, "
27
29
  f"make sure to use the 'send_message' at the end, and the system will notify the sender of your response] "
28
30
  f"{message}"
29
31
  )
30
- messages = [MessageCreate(role=MessageRole.system, content=message, name=self.agent_state.name)]
32
+ messages = [MessageCreate(role=MessageRole.system, content=augmented_message, name=self.agent_state.name)]
33
+
31
34
  return execute_send_message_to_agent(
32
35
  sender_agent=self,
33
36
  messages=messages,
@@ -81,33 +84,4 @@ def send_message_to_agents_matching_all_tags(self: "Agent", message: str, tags:
81
84
  have an entry in the returned list.
82
85
  """
83
86
 
84
- server = get_letta_server()
85
-
86
- message = (
87
- f"[Incoming message from agent with ID '{self.agent_state.id}' - to reply to this message, "
88
- f"make sure to use the 'send_message' at the end, and the system will notify the sender of your response] "
89
- f"{message}"
90
- )
91
-
92
- # Retrieve agents that match ALL specified tags
93
- matching_agents = server.agent_manager.list_agents(actor=self.user, tags=tags, match_all_tags=True, limit=100)
94
- messages = [MessageCreate(role=MessageRole.system, content=message, name=self.agent_state.name)]
95
-
96
- async def send_messages_to_all_agents():
97
- tasks = [
98
- async_send_message_with_retries(
99
- server=server,
100
- sender_agent=self,
101
- target_agent_id=agent_state.id,
102
- messages=messages,
103
- max_retries=MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES,
104
- timeout=MULTI_AGENT_SEND_MESSAGE_TIMEOUT,
105
- logging_prefix="[send_message_to_agents_matching_all_tags]",
106
- )
107
- for agent_state in matching_agents
108
- ]
109
- # Run all tasks in parallel
110
- return await asyncio.gather(*tasks)
111
-
112
- # Run the async function and return results
113
- return asyncio.run(send_messages_to_all_agents())
87
+ return asyncio.run(_send_message_to_agents_matching_all_tags_async(self, message, tags))
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import json
3
2
  import threading
4
3
  from random import uniform
5
4
  from typing import Any, List, Optional, Union
@@ -12,13 +11,17 @@ from letta.constants import (
12
11
  COMPOSIO_ENTITY_ENV_VAR_KEY,
13
12
  DEFAULT_MESSAGE_TOOL,
14
13
  DEFAULT_MESSAGE_TOOL_KWARG,
14
+ MULTI_AGENT_CONCURRENT_SENDS,
15
15
  MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES,
16
16
  MULTI_AGENT_SEND_MESSAGE_TIMEOUT,
17
17
  )
18
+ from letta.functions.interface import MultiAgentMessagingInterface
18
19
  from letta.orm.errors import NoResultFound
19
- from letta.schemas.letta_message import AssistantMessage, ReasoningMessage, ToolCallMessage
20
+ from letta.schemas.enums import MessageRole
21
+ from letta.schemas.letta_message import AssistantMessage
20
22
  from letta.schemas.letta_response import LettaResponse
21
- from letta.schemas.message import MessageCreate
23
+ from letta.schemas.message import Message, MessageCreate
24
+ from letta.schemas.user import User
22
25
  from letta.server.rest_api.utils import get_letta_server
23
26
 
24
27
 
@@ -249,85 +252,94 @@ def generate_import_code(module_attr_map: Optional[dict]):
249
252
  def parse_letta_response_for_assistant_message(
250
253
  target_agent_id: str,
251
254
  letta_response: LettaResponse,
252
- assistant_message_tool_name: str = DEFAULT_MESSAGE_TOOL,
253
- assistant_message_tool_kwarg: str = DEFAULT_MESSAGE_TOOL_KWARG,
254
255
  ) -> Optional[str]:
255
256
  messages = []
256
- # This is not ideal, but we would like to return something rather than nothing
257
- fallback_reasoning = []
258
257
  for m in letta_response.messages:
259
258
  if isinstance(m, AssistantMessage):
260
259
  messages.append(m.content)
261
- elif isinstance(m, ToolCallMessage) and m.tool_call.name == assistant_message_tool_name:
262
- try:
263
- messages.append(json.loads(m.tool_call.arguments)[assistant_message_tool_kwarg])
264
- except Exception: # TODO: Make this more specific
265
- continue
266
- elif isinstance(m, ReasoningMessage):
267
- fallback_reasoning.append(m.reasoning)
268
260
 
269
261
  if messages:
270
262
  messages_str = "\n".join(messages)
271
- return f"Agent {target_agent_id} said: '{messages_str}'"
263
+ return f"{target_agent_id} said: '{messages_str}'"
272
264
  else:
273
- messages_str = "\n".join(fallback_reasoning)
274
- return f"Agent {target_agent_id}'s inner thoughts: '{messages_str}'"
265
+ return f"No response from {target_agent_id}"
275
266
 
276
267
 
277
- def execute_send_message_to_agent(
268
+ async def async_execute_send_message_to_agent(
278
269
  sender_agent: "Agent",
279
270
  messages: List[MessageCreate],
280
271
  other_agent_id: str,
281
272
  log_prefix: str,
282
273
  ) -> Optional[str]:
283
274
  """
284
- Helper function to send a message to a specific Letta agent.
285
-
286
- Args:
287
- sender_agent ("Agent"): The sender agent object.
288
- message (str): The message to send.
289
- other_agent_id (str): The identifier of the target Letta agent.
290
- log_prefix (str): Logging prefix for retries.
291
-
292
- Returns:
293
- Optional[str]: The response from the Letta agent if required by the caller.
275
+ Async helper to:
276
+ 1) validate the target agent exists & is in the same org,
277
+ 2) send a message via async_send_message_with_retries.
294
278
  """
295
279
  server = get_letta_server()
296
280
 
297
- # Ensure the target agent is in the same org
281
+ # 1. Validate target agent
298
282
  try:
299
283
  server.agent_manager.get_agent_by_id(agent_id=other_agent_id, actor=sender_agent.user)
300
284
  except NoResultFound:
301
- raise ValueError(
302
- f"The passed-in agent_id {other_agent_id} either does not exist, "
303
- f"or does not belong to the same org ({sender_agent.user.organization_id})."
304
- )
285
+ raise ValueError(f"Target agent {other_agent_id} either does not exist or is not in org " f"({sender_agent.user.organization_id}).")
305
286
 
306
- # Async logic to send a message with retries and timeout
307
- async def async_send():
308
- return await async_send_message_with_retries(
309
- server=server,
310
- sender_agent=sender_agent,
311
- target_agent_id=other_agent_id,
312
- messages=messages,
313
- max_retries=MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES,
314
- timeout=MULTI_AGENT_SEND_MESSAGE_TIMEOUT,
315
- logging_prefix=log_prefix,
316
- )
287
+ # 2. Use your async retry logic
288
+ return await async_send_message_with_retries(
289
+ server=server,
290
+ sender_agent=sender_agent,
291
+ target_agent_id=other_agent_id,
292
+ messages=messages,
293
+ max_retries=MULTI_AGENT_SEND_MESSAGE_MAX_RETRIES,
294
+ timeout=MULTI_AGENT_SEND_MESSAGE_TIMEOUT,
295
+ logging_prefix=log_prefix,
296
+ )
317
297
 
318
- # Run in the current event loop or create one if needed
319
- try:
320
- return asyncio.run(async_send())
321
- except RuntimeError:
322
- loop = asyncio.get_event_loop()
323
- if loop.is_running():
324
- return loop.run_until_complete(async_send())
325
- else:
326
- raise
298
+
299
+ def execute_send_message_to_agent(
300
+ sender_agent: "Agent",
301
+ messages: List[MessageCreate],
302
+ other_agent_id: str,
303
+ log_prefix: str,
304
+ ) -> Optional[str]:
305
+ """
306
+ Synchronous wrapper that calls `async_execute_send_message_to_agent` using asyncio.run.
307
+ This function must be called from a synchronous context (i.e., no running event loop).
308
+ """
309
+ return asyncio.run(async_execute_send_message_to_agent(sender_agent, messages, other_agent_id, log_prefix))
310
+
311
+
312
+ async def send_message_to_agent_no_stream(
313
+ server: "SyncServer",
314
+ agent_id: str,
315
+ actor: User,
316
+ messages: Union[List[Message], List[MessageCreate]],
317
+ metadata: Optional[dict] = None,
318
+ ) -> LettaResponse:
319
+ """
320
+ A simpler helper to send messages to a single agent WITHOUT streaming.
321
+ Returns a LettaResponse containing the final messages.
322
+ """
323
+ interface = MultiAgentMessagingInterface()
324
+ if metadata:
325
+ interface.metadata = metadata
326
+
327
+ # Offload the synchronous `send_messages` call
328
+ usage_stats = await asyncio.to_thread(
329
+ server.send_messages,
330
+ actor=actor,
331
+ agent_id=agent_id,
332
+ messages=messages,
333
+ interface=interface,
334
+ metadata=metadata,
335
+ )
336
+
337
+ final_messages = interface.get_captured_send_messages()
338
+ return LettaResponse(messages=final_messages, usage=usage_stats)
327
339
 
328
340
 
329
341
  async def async_send_message_with_retries(
330
- server,
342
+ server: "SyncServer",
331
343
  sender_agent: "Agent",
332
344
  target_agent_id: str,
333
345
  messages: List[MessageCreate],
@@ -335,57 +347,34 @@ async def async_send_message_with_retries(
335
347
  timeout: int,
336
348
  logging_prefix: Optional[str] = None,
337
349
  ) -> str:
338
- """
339
- Shared helper coroutine to send a message to an agent with retries and a timeout.
340
350
 
341
- Args:
342
- server: The Letta server instance (from get_letta_server()).
343
- sender_agent (Agent): The agent initiating the send action.
344
- target_agent_id (str): The ID of the agent to send the message to.
345
- message_text (str): The text to send as the user message.
346
- max_retries (int): Maximum number of retries for the request.
347
- timeout (int): Maximum time to wait for a response (in seconds).
348
- logging_prefix (str): A prefix to append to logging
349
- Returns:
350
- str: The response or an error message.
351
- """
352
351
  logging_prefix = logging_prefix or "[async_send_message_with_retries]"
353
352
  for attempt in range(1, max_retries + 1):
354
353
  try:
355
- # Wrap in a timeout
356
354
  response = await asyncio.wait_for(
357
- server.send_message_to_agent(
355
+ send_message_to_agent_no_stream(
356
+ server=server,
358
357
  agent_id=target_agent_id,
359
358
  actor=sender_agent.user,
360
359
  messages=messages,
361
- stream_steps=False,
362
- stream_tokens=False,
363
- use_assistant_message=True,
364
- assistant_message_tool_name=DEFAULT_MESSAGE_TOOL,
365
- assistant_message_tool_kwarg=DEFAULT_MESSAGE_TOOL_KWARG,
366
360
  ),
367
361
  timeout=timeout,
368
362
  )
369
363
 
370
- # Extract assistant message
371
- assistant_message = parse_letta_response_for_assistant_message(
372
- target_agent_id,
373
- response,
374
- assistant_message_tool_name=DEFAULT_MESSAGE_TOOL,
375
- assistant_message_tool_kwarg=DEFAULT_MESSAGE_TOOL_KWARG,
376
- )
364
+ # Then parse out the assistant message
365
+ assistant_message = parse_letta_response_for_assistant_message(target_agent_id, response)
377
366
  if assistant_message:
378
367
  sender_agent.logger.info(f"{logging_prefix} - {assistant_message}")
379
368
  return assistant_message
380
369
  else:
381
370
  msg = f"(No response from agent {target_agent_id})"
382
371
  sender_agent.logger.info(f"{logging_prefix} - {msg}")
383
- sender_agent.logger.info(f"{logging_prefix} - raw response: {response.model_dump_json(indent=4)}")
384
- sender_agent.logger.info(f"{logging_prefix} - parsed assistant message: {assistant_message}")
385
372
  return msg
373
+
386
374
  except asyncio.TimeoutError:
387
375
  error_msg = f"(Timeout on attempt {attempt}/{max_retries} for agent {target_agent_id})"
388
376
  sender_agent.logger.warning(f"{logging_prefix} - {error_msg}")
377
+
389
378
  except Exception as e:
390
379
  error_msg = f"(Error on attempt {attempt}/{max_retries} for agent {target_agent_id}: {e})"
391
380
  sender_agent.logger.warning(f"{logging_prefix} - {error_msg}")
@@ -393,10 +382,10 @@ async def async_send_message_with_retries(
393
382
  # Exponential backoff before retrying
394
383
  if attempt < max_retries:
395
384
  backoff = uniform(0.5, 2) * (2**attempt)
396
- sender_agent.logger.warning(f"{logging_prefix} - Retrying the agent to agent send_message...sleeping for {backoff}")
385
+ sender_agent.logger.warning(f"{logging_prefix} - Retrying the agent-to-agent send_message...sleeping for {backoff}")
397
386
  await asyncio.sleep(backoff)
398
387
  else:
399
- sender_agent.logger.error(f"{logging_prefix} - Fatal error during agent to agent send_message: {error_msg}")
388
+ sender_agent.logger.error(f"{logging_prefix} - Fatal error: {error_msg}")
400
389
  raise Exception(error_msg)
401
390
 
402
391
 
@@ -482,3 +471,43 @@ def fire_and_forget_send_to_agent(
482
471
  except RuntimeError:
483
472
  # Means no event loop is running in this thread
484
473
  run_in_background_thread(background_task())
474
+
475
+
476
+ async def _send_message_to_agents_matching_all_tags_async(sender_agent: "Agent", message: str, tags: List[str]) -> List[str]:
477
+ server = get_letta_server()
478
+
479
+ augmented_message = (
480
+ f"[Incoming message from agent with ID '{sender_agent.agent_state.id}' - to reply to this message, "
481
+ f"make sure to use the 'send_message' at the end, and the system will notify the sender of your response] "
482
+ f"{message}"
483
+ )
484
+
485
+ # Retrieve up to 100 matching agents
486
+ matching_agents = server.agent_manager.list_agents(actor=sender_agent.user, tags=tags, match_all_tags=True, limit=100)
487
+
488
+ # Create a system message
489
+ messages = [MessageCreate(role=MessageRole.system, content=augmented_message, name=sender_agent.agent_state.name)]
490
+
491
+ # Possibly limit concurrency to avoid meltdown:
492
+ sem = asyncio.Semaphore(MULTI_AGENT_CONCURRENT_SENDS)
493
+
494
+ async def _send_single(agent_state):
495
+ async with sem:
496
+ return await async_send_message_with_retries(
497
+ server=server,
498
+ sender_agent=sender_agent,
499
+ target_agent_id=agent_state.id,
500
+ messages=messages,
501
+ max_retries=3,
502
+ timeout=30,
503
+ )
504
+
505
+ tasks = [asyncio.create_task(_send_single(agent_state)) for agent_state in matching_agents]
506
+ results = await asyncio.gather(*tasks, return_exceptions=True)
507
+ final = []
508
+ for r in results:
509
+ if isinstance(r, Exception):
510
+ final.append(str(r))
511
+ else:
512
+ final.append(r)
513
+ return final
@@ -0,0 +1,75 @@
1
+ import json
2
+ from typing import List, Optional
3
+
4
+ from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
5
+ from letta.interface import AgentInterface
6
+ from letta.schemas.letta_message import AssistantMessage, LettaMessage
7
+ from letta.schemas.message import Message
8
+
9
+
10
+ class MultiAgentMessagingInterface(AgentInterface):
11
+ """
12
+ A minimal interface that captures *only* calls to the 'send_message' function
13
+ by inspecting msg_obj.tool_calls. We parse out the 'message' field from the
14
+ JSON function arguments and store it as an AssistantMessage.
15
+ """
16
+
17
+ def __init__(self):
18
+ self._captured_messages: List[AssistantMessage] = []
19
+ self.metadata = {}
20
+
21
+ def internal_monologue(self, msg: str, msg_obj: Optional[Message] = None):
22
+ """Ignore internal monologue."""
23
+
24
+ def assistant_message(self, msg: str, msg_obj: Optional[Message] = None):
25
+ """Ignore normal assistant messages (only capturing send_message calls)."""
26
+
27
+ def function_message(self, msg: str, msg_obj: Optional[Message] = None):
28
+ """
29
+ Called whenever the agent logs a function call. We'll inspect msg_obj.tool_calls:
30
+ - If tool_calls include a function named 'send_message', parse its arguments
31
+ - Extract the 'message' field
32
+ - Save it as an AssistantMessage in self._captured_messages
33
+ """
34
+ if not msg_obj or not msg_obj.tool_calls:
35
+ return
36
+
37
+ for tool_call in msg_obj.tool_calls:
38
+ if not tool_call.function:
39
+ continue
40
+ if tool_call.function.name != DEFAULT_MESSAGE_TOOL:
41
+ # Skip any other function calls
42
+ continue
43
+
44
+ # Now parse the JSON in tool_call.function.arguments
45
+ func_args_str = tool_call.function.arguments or ""
46
+ try:
47
+ data = json.loads(func_args_str)
48
+ # Extract the 'message' key if present
49
+ content = data.get(DEFAULT_MESSAGE_TOOL_KWARG, str(data))
50
+ except json.JSONDecodeError:
51
+ # If we can't parse, store the raw string
52
+ content = func_args_str
53
+
54
+ # Store as an AssistantMessage
55
+ new_msg = AssistantMessage(
56
+ id=msg_obj.id,
57
+ date=msg_obj.created_at,
58
+ content=content,
59
+ )
60
+ self._captured_messages.append(new_msg)
61
+
62
+ def user_message(self, msg: str, msg_obj: Optional[Message] = None):
63
+ """Ignore user messages."""
64
+
65
+ def step_complete(self):
66
+ """No streaming => no step boundaries."""
67
+
68
+ def step_yield(self):
69
+ """No streaming => no final yield needed."""
70
+
71
+ def get_captured_send_messages(self) -> List[LettaMessage]:
72
+ """
73
+ Returns only the messages extracted from 'send_message' calls.
74
+ """
75
+ return self._captured_messages
letta/orm/step.py CHANGED
@@ -35,6 +35,7 @@ class Step(SqlalchemyBase):
35
35
  )
36
36
  provider_name: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The name of the provider used for this step.")
37
37
  model: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The name of the model used for this step.")
38
+ model_endpoint: Mapped[Optional[str]] = mapped_column(None, nullable=True, doc="The model endpoint url used for this step.")
38
39
  context_window_limit: Mapped[Optional[int]] = mapped_column(
39
40
  None, nullable=True, doc="The context window limit configured for this step."
40
41
  )
letta/schemas/step.py CHANGED
@@ -20,6 +20,7 @@ class Step(StepBase):
20
20
  )
21
21
  provider_name: Optional[str] = Field(None, description="The name of the provider used for this step.")
22
22
  model: Optional[str] = Field(None, description="The name of the model used for this step.")
23
+ model_endpoint: Optional[str] = Field(None, description="The model endpoint url used for this step.")
23
24
  context_window_limit: Optional[int] = Field(None, description="The context window limit configured for this step.")
24
25
  completion_tokens: Optional[int] = Field(None, description="The number of tokens generated by the agent during this step.")
25
26
  prompt_tokens: Optional[int] = Field(None, description="The number of tokens in the prompt during this step.")
@@ -315,7 +315,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
315
315
 
316
316
  # extra prints
317
317
  self.debug = False
318
- self.timeout = 30
318
+ self.timeout = 10 * 60 # 10 minute timeout
319
319
 
320
320
  def _reset_inner_thoughts_json_reader(self):
321
321
  # A buffer for accumulating function arguments (we want to buffer keys and run checks on each one)
@@ -330,7 +330,7 @@ class StreamingServerInterface(AgentChunkStreamingInterface):
330
330
  while self._active:
331
331
  try:
332
332
  # Wait until there is an item in the deque or the stream is deactivated
333
- await asyncio.wait_for(self._event.wait(), timeout=self.timeout) # 30 second timeout
333
+ await asyncio.wait_for(self._event.wait(), timeout=self.timeout)
334
334
  except asyncio.TimeoutError:
335
335
  break # Exit the loop if we timeout
336
336
 
@@ -55,6 +55,7 @@ class StepManager:
55
55
  actor: PydanticUser,
56
56
  provider_name: str,
57
57
  model: str,
58
+ model_endpoint: Optional[str],
58
59
  context_window_limit: int,
59
60
  usage: UsageStatistics,
60
61
  provider_id: Optional[str] = None,
@@ -66,6 +67,7 @@ class StepManager:
66
67
  "provider_id": provider_id,
67
68
  "provider_name": provider_name,
68
69
  "model": model,
70
+ "model_endpoint": model_endpoint,
69
71
  "context_window_limit": context_window_limit,
70
72
  "completion_tokens": usage.completion_tokens,
71
73
  "prompt_tokens": usage.prompt_tokens,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.6.21.dev20250205104040
3
+ Version: 0.6.22.dev20250206104042
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -1,6 +1,6 @@
1
- letta/__init__.py,sha256=bjhntPdDAWCs4iINZfoEzpfEiEU32NSYtCmSvdcL3gk,919
1
+ letta/__init__.py,sha256=HRAHARA2DybG2umdS9hdIE5OxZULncZxurOtMCJRHao,919
2
2
  letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
- letta/agent.py,sha256=Ds5-t_Ge1ZJf8wbQ2LbpK9LHEs6XNu0xLow3iXoZpd8,56980
3
+ letta/agent.py,sha256=xOhzNF-DMxBzCkADyw1-OILsxfy2gMBkV0CoQ3XfW_I,56980
4
4
  letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
5
5
  letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
6
6
  letta/chat_only_agent.py,sha256=71Lf-df8y3nsE9IFKpEigaZaWHoWnXnhVChkp1L-83I,4760
@@ -8,11 +8,11 @@ letta/cli/cli.py,sha256=_uGKM-RvGLGf7y8iWjkLgLTxIw7uWrdCdL5ETUOCkUs,16472
8
8
  letta/cli/cli_config.py,sha256=2oo4vui1GXQarAD6Ru4SRzPvcW4eX2mCXOBusfYGvJw,8533
9
9
  letta/cli/cli_load.py,sha256=xFw-CuzjChcIptaqQ1XpDROENt0JSjyPeiQ0nmEeO1k,2706
10
10
  letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- letta/client/client.py,sha256=Vzjq3a5dx9W5R96FvKkohHf2Yf0NV4XG0VWTH2h0624,138315
11
+ letta/client/client.py,sha256=ZgJEt5F1yB6Q_z9Qi0FJ7Vmlb-YK41tymSKFB7NWy38,138311
12
12
  letta/client/streaming.py,sha256=DzE86XJTg_0j9eC45Hrpy9vPt-Wfo1F-sIv_B7iNV6I,5509
13
13
  letta/client/utils.py,sha256=VCGV-op5ZSmurd4yw7Vhf93XDQ0BkyBT8qsuV7EqfiU,2859
14
14
  letta/config.py,sha256=JFGY4TWW0Wm5fTbZamOwWqk5G8Nn-TXyhgByGoAqy2c,12375
15
- letta/constants.py,sha256=SafL4BPMDzzVhp-Y6Uu51NaZAzOuUAoJhbKYpzbQZoQ,7242
15
+ letta/constants.py,sha256=ZyPGoe68NfBCteTQI6hX9aFhszuBvy10xakb2FFKV9M,7276
16
16
  letta/data_sources/connectors.py,sha256=R2AssXpqS7wN6VI8AfxvqaZs5S1ZACc4E_FewmR9iZI,7022
17
17
  letta/data_sources/connectors_helper.py,sha256=2TQjCt74fCgT5sw1AP8PalDEk06jPBbhrPG4HVr-WLs,3371
18
18
  letta/embeddings.py,sha256=VgqbUqYL6oTuLOKGOd_8swTRMYIpRTIWJbBthjT8eR8,8838
@@ -21,9 +21,10 @@ letta/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  letta/functions/ast_parsers.py,sha256=MEFfGxpflUsw34JiY9zdunkpbczAYxte8t4rDPOmXfQ,3620
22
22
  letta/functions/function_sets/base.py,sha256=bOiitkhzqYKwZBiRYrx29AlordiA5IrXw25eVSRK8BY,5984
23
23
  letta/functions/function_sets/extras.py,sha256=Z9yEdBpQFtTjpxkgbtkWMA8GtDWC6ai2bdsRpnv0H_w,4837
24
- letta/functions/function_sets/multi_agent.py,sha256=2tu7A_eMwkfXldZWpfwwnq0OXPFTjROo-NjXvuhxOhc,5357
24
+ letta/functions/function_sets/multi_agent.py,sha256=BcMeod0-lMhnKeaVWJp2T1PzRV0qvCuTWAPMJxAbgHI,4073
25
25
  letta/functions/functions.py,sha256=NyWLh7a-f4mXti5vM1oWDwXzfA58VmVVqL03O9vosKY,5672
26
- letta/functions/helpers.py,sha256=5f4ua21fffqU-M24ha2xNGrqIhqLWh0-sTNFOHbu18Q,19700
26
+ letta/functions/helpers.py,sha256=-DpUikW56Asps1XaRx4CEiTNc-DuLGgBgFYbNBO_37U,19964
27
+ letta/functions/interface.py,sha256=s_PPp5WDvGH_y9KUpMlORkdC141ITczFk3wsevrrUD8,2866
27
28
  letta/functions/schema_generator.py,sha256=qosgp3p27QRTqOCPLrSkCGVdyQsyTTZunXQ_g-YaTkw,20138
28
29
  letta/helpers/__init__.py,sha256=p0luQ1Oe3Skc6sH4O58aHHA3Qbkyjifpuq0DZ1GAY0U,59
29
30
  letta/helpers/tool_rule_solver.py,sha256=VnJfqb5L1Lcipc_tBVGj0om60GKQkMkNLgg6X9VZl2c,6210
@@ -108,7 +109,7 @@ letta/orm/source.py,sha256=cpaleNiP-DomM2oopwgL2DGn38ITQwLMs5mKODf_c_4,2167
108
109
  letta/orm/sources_agents.py,sha256=Ik_PokCBrXRd9wXWomeNeb8EtLUwjb9VMZ8LWXqpK5A,473
109
110
  letta/orm/sqlalchemy_base.py,sha256=jhhF3A_k5j3wxyZ-C_n6kwvQIZ1iU3-vvcke3QarLBA,21901
110
111
  letta/orm/sqlite_functions.py,sha256=JCScKiRlYCKxy9hChQ8wsk4GMKknZE24MunnG3fM1Gw,4255
111
- letta/orm/step.py,sha256=6ygEnkQSIMxNH08Om0a-UK-f2E66QSpp9GdGm7mGjFg,2853
112
+ letta/orm/step.py,sha256=6t_PlVd8pW1Rd6JeECImBG2n9P-yif0Sl9Uzhb-m77w,2982
112
113
  letta/orm/tool.py,sha256=JEPHlM4ePaLaGtHpHhYdKCteHTRJnOFgQmfR5wL8TpA,2379
113
114
  letta/orm/tools_agents.py,sha256=r6t-V21w2_mG8n38zuUb5jOi_3hRxsjgezsLA4sg0m4,626
114
115
  letta/orm/user.py,sha256=rK5N5ViDxmesZMqVVHB7FcQNpcSoM-hB42MyI6q3MnI,1004
@@ -167,7 +168,7 @@ letta/schemas/providers.py,sha256=1Sc7gWI6n9RkR4kOY4g3xGLVo6VCSwpiJySp3Pm3MQw,34
167
168
  letta/schemas/run.py,sha256=SRqPRziINIiPunjOhE_NlbnQYgxTvqmbauni_yfBQRA,2085
168
169
  letta/schemas/sandbox_config.py,sha256=Nz8K5brqe6jpf66KnTJ0-E7ZeFdPoBFGN-XOI35OeaY,5926
169
170
  letta/schemas/source.py,sha256=-BQVolcXA2ziCu2ztR6cbTdGUc8G7vGJy7rvpdf1hpg,2880
170
- letta/schemas/step.py,sha256=cCmDChQMndy7aMJGH0Z19VbzJkAeFTYuA0cJpzjW2g0,1928
171
+ letta/schemas/step.py,sha256=wQ-AzhMjH7tLFgZbJfcPkW9a9V81JYWzJCQtTfcb11E,2034
171
172
  letta/schemas/tool.py,sha256=dDyzvLZ_gw9DdcFplGTHoH9aFQnF2ez-ApYLRtxAfys,11051
172
173
  letta/schemas/tool_rule.py,sha256=tS7ily6NJD8E4n7Hla38jMUe6OIdhdc1ckq0AiRpu5Y,1893
173
174
  letta/schemas/usage.py,sha256=8oYRH-JX0PfjIu2zkT5Uu3UWQ7Unnz_uHiO8hRGI4m0,912
@@ -181,7 +182,7 @@ letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
181
182
  letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
182
183
  letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
183
184
  letta/server/rest_api/chat_completions_interface.py,sha256=i9tfb9oSh14QNY-1ghWYtdgP7_RiyPHD5NcA2FKF3Dw,10195
184
- letta/server/rest_api/interface.py,sha256=5lf5k5GJgUaRb8NCKotSK5PZDAcpYFmKjyPlMqtc964,51881
185
+ letta/server/rest_api/interface.py,sha256=ZGTJ5WIRNsWgKO0yoCD-yUESxq838qNrK3N1SzRZl40,51886
185
186
  letta/server/rest_api/optimistic_json_parser.py,sha256=1z4d9unmxMb0ou7owJ62uUQoNjNYf21FmaNdg0ZcqUU,6567
186
187
  letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
188
  letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -229,7 +230,7 @@ letta/services/per_agent_lock_manager.py,sha256=porM0cKKANQ1FvcGXOO_qM7ARk5Fgi1H
229
230
  letta/services/provider_manager.py,sha256=jEal0A0XWobWH5CVfmzPtcFhsflI-sanqyg26FqpDKk,3575
230
231
  letta/services/sandbox_config_manager.py,sha256=eWDNTscRG9Gt_Ixho3-daOOno_9KcebxeA9v_CbzYu0,13371
231
232
  letta/services/source_manager.py,sha256=0JLKIv405oS5wc6bY5k2bxxZpS9O-VwUGHVdGPbJ3e4,7676
232
- letta/services/step_manager.py,sha256=dvGFeobB7TjXBENG6pGrEr6tEil3Am7GyTzv2IjQifA,4885
233
+ letta/services/step_manager.py,sha256=_PJUgaXyUHKCdlwt9CAmKhdeCNzKE_0_8-SRdUzpZaA,4970
233
234
  letta/services/tool_execution_sandbox.py,sha256=4XBYkCEBLG6GqijxgqeLIQQJ9zRbsJa8vZ4dZG04Pq8,22080
234
235
  letta/services/tool_manager.py,sha256=9Y15q0GqnADk-tnUeWDFFsDOt_ZjwsPU2oteDVtHAF4,9572
235
236
  letta/services/user_manager.py,sha256=1U8BQ_-MBkEW2wnSFV_OsTwBmRAZLN8uHLFjnDjK3hA,4308
@@ -238,8 +239,8 @@ letta/streaming_interface.py,sha256=lo2VAQRUJOdWTijwnXuKOC9uejqr2siUAEmZiQUXkj8,
238
239
  letta/streaming_utils.py,sha256=jLqFTVhUL76FeOuYk8TaRQHmPTf3HSRc2EoJwxJNK6U,11946
239
240
  letta/system.py,sha256=S_0cod77iEttkFd1bSh2wenLCKA8YL487AuVenIDUng,8425
240
241
  letta/utils.py,sha256=lgBDWKmrQrmJGPxcgamFC2aJyi6I0dX7bzLBt3YC6j0,34051
241
- letta_nightly-0.6.21.dev20250205104040.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
242
- letta_nightly-0.6.21.dev20250205104040.dist-info/METADATA,sha256=9rpeMVxKdfJXbURcCQGwwSN4GpfbrhegKoWkn-axrJw,22156
243
- letta_nightly-0.6.21.dev20250205104040.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
244
- letta_nightly-0.6.21.dev20250205104040.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
245
- letta_nightly-0.6.21.dev20250205104040.dist-info/RECORD,,
242
+ letta_nightly-0.6.22.dev20250206104042.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
243
+ letta_nightly-0.6.22.dev20250206104042.dist-info/METADATA,sha256=K80Klxfva98oQxOTPEFh4tJ4-ucJqBjjW1LJxS2_O74,22156
244
+ letta_nightly-0.6.22.dev20250206104042.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
245
+ letta_nightly-0.6.22.dev20250206104042.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
246
+ letta_nightly-0.6.22.dev20250206104042.dist-info/RECORD,,