letta-nightly 0.6.21.dev20250205051348__py3-none-any.whl → 0.6.22.dev20250205223255__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 +4 -2
- letta/client/client.py +1 -1
- letta/constants.py +1 -0
- letta/functions/function_sets/multi_agent.py +9 -35
- letta/functions/helpers.py +114 -85
- letta/functions/interface.py +75 -0
- letta/orm/step.py +1 -0
- letta/schemas/step.py +1 -0
- letta/server/rest_api/interface.py +2 -2
- letta/services/step_manager.py +2 -0
- {letta_nightly-0.6.21.dev20250205051348.dist-info → letta_nightly-0.6.22.dev20250205223255.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.21.dev20250205051348.dist-info → letta_nightly-0.6.22.dev20250205223255.dist-info}/RECORD +16 -15
- {letta_nightly-0.6.21.dev20250205051348.dist-info → letta_nightly-0.6.22.dev20250205223255.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.21.dev20250205051348.dist-info → letta_nightly-0.6.22.dev20250205223255.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.21.dev20250205051348.dist-info → letta_nightly-0.6.22.dev20250205223255.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
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
|
-
|
|
509
|
-
|
|
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", "
|
|
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.
|
|
5
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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))
|
letta/functions/helpers.py
CHANGED
|
@@ -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.
|
|
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"
|
|
263
|
+
return f"{target_agent_id} said: '{messages_str}'"
|
|
272
264
|
else:
|
|
273
|
-
|
|
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
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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 =
|
|
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)
|
|
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
|
|
letta/services/step_manager.py
CHANGED
|
@@ -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
|
-
letta/__init__.py,sha256=
|
|
1
|
+
letta/__init__.py,sha256=HRAHARA2DybG2umdS9hdIE5OxZULncZxurOtMCJRHao,919
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
|
-
letta/agent.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
242
|
-
letta_nightly-0.6.
|
|
243
|
-
letta_nightly-0.6.
|
|
244
|
-
letta_nightly-0.6.
|
|
245
|
-
letta_nightly-0.6.
|
|
242
|
+
letta_nightly-0.6.22.dev20250205223255.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
243
|
+
letta_nightly-0.6.22.dev20250205223255.dist-info/METADATA,sha256=8CDzJTTMaujcYFqJ6p5_PUdmyaexnRuHsPs8keIaP-E,22156
|
|
244
|
+
letta_nightly-0.6.22.dev20250205223255.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
245
|
+
letta_nightly-0.6.22.dev20250205223255.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
246
|
+
letta_nightly-0.6.22.dev20250205223255.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|