letta-nightly 0.7.6.dev20250430104233__py3-none-any.whl → 0.7.8.dev20250501064110__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.
Files changed (70) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +8 -12
  3. letta/agents/exceptions.py +6 -0
  4. letta/agents/helpers.py +1 -1
  5. letta/agents/letta_agent.py +48 -35
  6. letta/agents/letta_agent_batch.py +6 -2
  7. letta/agents/voice_agent.py +41 -59
  8. letta/agents/{ephemeral_memory_agent.py → voice_sleeptime_agent.py} +106 -129
  9. letta/client/client.py +3 -3
  10. letta/constants.py +18 -2
  11. letta/functions/composio_helpers.py +100 -0
  12. letta/functions/function_sets/base.py +0 -10
  13. letta/functions/function_sets/voice.py +92 -0
  14. letta/functions/functions.py +4 -2
  15. letta/functions/helpers.py +19 -101
  16. letta/groups/helpers.py +1 -0
  17. letta/groups/sleeptime_multi_agent.py +5 -1
  18. letta/helpers/message_helper.py +21 -4
  19. letta/helpers/tool_execution_helper.py +1 -1
  20. letta/interfaces/anthropic_streaming_interface.py +165 -158
  21. letta/interfaces/openai_chat_completions_streaming_interface.py +1 -1
  22. letta/llm_api/anthropic.py +15 -10
  23. letta/llm_api/anthropic_client.py +5 -1
  24. letta/llm_api/google_vertex_client.py +1 -1
  25. letta/llm_api/llm_api_tools.py +7 -0
  26. letta/llm_api/llm_client.py +12 -2
  27. letta/llm_api/llm_client_base.py +4 -0
  28. letta/llm_api/openai.py +9 -3
  29. letta/llm_api/openai_client.py +18 -4
  30. letta/memory.py +3 -1
  31. letta/orm/enums.py +1 -0
  32. letta/orm/group.py +2 -0
  33. letta/orm/provider.py +10 -0
  34. letta/personas/examples/voice_memory_persona.txt +5 -0
  35. letta/prompts/system/voice_chat.txt +29 -0
  36. letta/prompts/system/voice_sleeptime.txt +74 -0
  37. letta/schemas/agent.py +14 -2
  38. letta/schemas/enums.py +11 -0
  39. letta/schemas/group.py +37 -2
  40. letta/schemas/llm_config.py +1 -0
  41. letta/schemas/llm_config_overrides.py +2 -2
  42. letta/schemas/message.py +4 -3
  43. letta/schemas/providers.py +75 -213
  44. letta/schemas/tool.py +8 -12
  45. letta/server/rest_api/app.py +12 -0
  46. letta/server/rest_api/chat_completions_interface.py +1 -1
  47. letta/server/rest_api/interface.py +8 -10
  48. letta/server/rest_api/{optimistic_json_parser.py → json_parser.py} +62 -26
  49. letta/server/rest_api/routers/v1/agents.py +1 -1
  50. letta/server/rest_api/routers/v1/embeddings.py +4 -3
  51. letta/server/rest_api/routers/v1/llms.py +4 -3
  52. letta/server/rest_api/routers/v1/providers.py +4 -1
  53. letta/server/rest_api/routers/v1/voice.py +0 -2
  54. letta/server/rest_api/utils.py +22 -33
  55. letta/server/server.py +91 -37
  56. letta/services/agent_manager.py +14 -7
  57. letta/services/group_manager.py +61 -0
  58. letta/services/helpers/agent_manager_helper.py +69 -12
  59. letta/services/message_manager.py +2 -2
  60. letta/services/passage_manager.py +13 -4
  61. letta/services/provider_manager.py +25 -14
  62. letta/services/summarizer/summarizer.py +20 -15
  63. letta/services/tool_executor/tool_execution_manager.py +1 -1
  64. letta/services/tool_executor/tool_executor.py +3 -3
  65. letta/services/tool_manager.py +32 -7
  66. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/METADATA +4 -5
  67. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/RECORD +70 -64
  68. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/LICENSE +0 -0
  69. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/WHEEL +0 -0
  70. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/entry_points.txt +0 -0
@@ -6,10 +6,9 @@ from random import uniform
6
6
  from typing import Any, Dict, List, Optional, Type, Union
7
7
 
8
8
  import humps
9
- from composio.constants import DEFAULT_ENTITY_ID
10
9
  from pydantic import BaseModel, Field, create_model
11
10
 
12
- from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
11
+ from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
13
12
  from letta.functions.interface import MultiAgentMessagingInterface
14
13
  from letta.orm.errors import NoResultFound
15
14
  from letta.schemas.enums import MessageRole
@@ -21,34 +20,6 @@ from letta.server.rest_api.utils import get_letta_server
21
20
  from letta.settings import settings
22
21
 
23
22
 
24
- # TODO: This is kind of hacky, as this is used to search up the action later on composio's side
25
- # TODO: So be very careful changing/removing these pair of functions
26
- def generate_func_name_from_composio_action(action_name: str) -> str:
27
- """
28
- Generates the composio function name from the composio action.
29
-
30
- Args:
31
- action_name: The composio action name
32
-
33
- Returns:
34
- function name
35
- """
36
- return action_name.lower()
37
-
38
-
39
- def generate_composio_action_from_func_name(func_name: str) -> str:
40
- """
41
- Generates the composio action from the composio function name.
42
-
43
- Args:
44
- func_name: The composio function name
45
-
46
- Returns:
47
- composio action name
48
- """
49
- return func_name.upper()
50
-
51
-
52
23
  # TODO needed?
53
24
  def generate_mcp_tool_wrapper(mcp_tool_name: str) -> tuple[str, str]:
54
25
 
@@ -58,73 +29,20 @@ def {mcp_tool_name}(**kwargs):
58
29
  """
59
30
 
60
31
  # Compile safety check
61
- assert_code_gen_compilable(wrapper_function_str.strip())
32
+ _assert_code_gen_compilable(wrapper_function_str.strip())
62
33
 
63
34
  return mcp_tool_name, wrapper_function_str.strip()
64
35
 
65
36
 
66
- def generate_composio_tool_wrapper(action_name: str) -> tuple[str, str]:
67
- # Generate func name
68
- func_name = generate_func_name_from_composio_action(action_name)
69
-
70
- wrapper_function_str = f"""\
71
- def {func_name}(**kwargs):
72
- raise RuntimeError("Something went wrong - we should never be using the persisted source code for Composio. Please reach out to Letta team")
73
- """
74
-
75
- # Compile safety check
76
- assert_code_gen_compilable(wrapper_function_str.strip())
77
-
78
- return func_name, wrapper_function_str.strip()
79
-
80
-
81
- def execute_composio_action(
82
- action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None
83
- ) -> tuple[str, str]:
84
- import os
85
-
86
- from composio.exceptions import (
87
- ApiKeyNotProvidedError,
88
- ComposioSDKError,
89
- ConnectedAccountNotFoundError,
90
- EnumMetadataNotFound,
91
- EnumStringNotFound,
92
- )
93
- from composio_langchain import ComposioToolSet
94
-
95
- entity_id = entity_id or os.getenv(COMPOSIO_ENTITY_ENV_VAR_KEY, DEFAULT_ENTITY_ID)
96
- try:
97
- composio_toolset = ComposioToolSet(api_key=api_key, entity_id=entity_id, lock=False)
98
- response = composio_toolset.execute_action(action=action_name, params=args)
99
- except ApiKeyNotProvidedError:
100
- raise RuntimeError(
101
- f"Composio API key is missing for action '{action_name}'. "
102
- "Please set the sandbox environment variables either through the ADE or the API."
103
- )
104
- except ConnectedAccountNotFoundError:
105
- raise RuntimeError(f"No connected account was found for action '{action_name}'. " "Please link an account and try again.")
106
- except EnumStringNotFound as e:
107
- raise RuntimeError(f"Invalid value provided for action '{action_name}': " + str(e) + ". Please check the action parameters.")
108
- except EnumMetadataNotFound as e:
109
- raise RuntimeError(f"Invalid value provided for action '{action_name}': " + str(e) + ". Please check the action parameters.")
110
- except ComposioSDKError as e:
111
- raise RuntimeError(f"An unexpected error occurred in Composio SDK while executing action '{action_name}': " + str(e))
112
-
113
- if response["error"]:
114
- raise RuntimeError(f"Error while executing action '{action_name}': " + str(response["error"]))
115
-
116
- return response["data"]
117
-
118
-
119
37
  def generate_langchain_tool_wrapper(
120
38
  tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None
121
39
  ) -> tuple[str, str]:
122
40
  tool_name = tool.__class__.__name__
123
41
  import_statement = f"from langchain_community.tools import {tool_name}"
124
- extra_module_imports = generate_import_code(additional_imports_module_attr_map)
42
+ extra_module_imports = _generate_import_code(additional_imports_module_attr_map)
125
43
 
126
44
  # Safety check that user has passed in all required imports:
127
- assert_all_classes_are_imported(tool, additional_imports_module_attr_map)
45
+ _assert_all_classes_are_imported(tool, additional_imports_module_attr_map)
128
46
 
129
47
  tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
130
48
  run_call = f"return tool._run(**kwargs)"
@@ -141,25 +59,25 @@ def {func_name}(**kwargs):
141
59
  """
142
60
 
143
61
  # Compile safety check
144
- assert_code_gen_compilable(wrapper_function_str)
62
+ _assert_code_gen_compilable(wrapper_function_str)
145
63
 
146
64
  return func_name, wrapper_function_str
147
65
 
148
66
 
149
- def assert_code_gen_compilable(code_str):
67
+ def _assert_code_gen_compilable(code_str):
150
68
  try:
151
69
  compile(code_str, "<string>", "exec")
152
70
  except SyntaxError as e:
153
71
  print(f"Syntax error in code: {e}")
154
72
 
155
73
 
156
- def assert_all_classes_are_imported(tool: Union["LangChainBaseTool"], additional_imports_module_attr_map: dict[str, str]) -> None:
74
+ def _assert_all_classes_are_imported(tool: Union["LangChainBaseTool"], additional_imports_module_attr_map: dict[str, str]) -> None:
157
75
  # Safety check that user has passed in all required imports:
158
76
  tool_name = tool.__class__.__name__
159
77
  current_class_imports = {tool_name}
160
78
  if additional_imports_module_attr_map:
161
79
  current_class_imports.update(set(additional_imports_module_attr_map.values()))
162
- required_class_imports = set(find_required_class_names_for_import(tool))
80
+ required_class_imports = set(_find_required_class_names_for_import(tool))
163
81
 
164
82
  if not current_class_imports.issuperset(required_class_imports):
165
83
  err_msg = f"[ERROR] You are missing module_attr pairs in `additional_imports_module_attr_map`. Currently, you have imports for {current_class_imports}, but the required classes for import are {required_class_imports}"
@@ -167,7 +85,7 @@ def assert_all_classes_are_imported(tool: Union["LangChainBaseTool"], additional
167
85
  raise RuntimeError(err_msg)
168
86
 
169
87
 
170
- def find_required_class_names_for_import(obj: Union["LangChainBaseTool", BaseModel]) -> list[str]:
88
+ def _find_required_class_names_for_import(obj: Union["LangChainBaseTool", BaseModel]) -> list[str]:
171
89
  """
172
90
  Finds all the class names for required imports when instantiating the `obj`.
173
91
  NOTE: This does not return the full import path, only the class name.
@@ -183,7 +101,7 @@ def find_required_class_names_for_import(obj: Union["LangChainBaseTool", BaseMod
183
101
 
184
102
  # Collect all possible candidates for BaseModel objects
185
103
  candidates = []
186
- if is_base_model(curr_obj):
104
+ if _is_base_model(curr_obj):
187
105
  # If it is a base model, we get all the values of the object parameters
188
106
  # i.e., if obj('b' = <class A>), we would want to inspect <class A>
189
107
  fields = dict(curr_obj)
@@ -200,7 +118,7 @@ def find_required_class_names_for_import(obj: Union["LangChainBaseTool", BaseMod
200
118
 
201
119
  # Filter out all candidates that are not BaseModels
202
120
  # In the list example above, ['a', 3, None, <class A>], we want to filter out 'a', 3, and None
203
- candidates = filter(lambda x: is_base_model(x), candidates)
121
+ candidates = filter(lambda x: _is_base_model(x), candidates)
204
122
 
205
123
  # Classic BFS here
206
124
  for c in candidates:
@@ -218,7 +136,7 @@ def generate_imported_tool_instantiation_call_str(obj: Any) -> Optional[str]:
218
136
  # If it is a basic Python type, we trivially return the string version of that value
219
137
  # Handle basic types
220
138
  return repr(obj)
221
- elif is_base_model(obj):
139
+ elif _is_base_model(obj):
222
140
  # Otherwise, if it is a BaseModel
223
141
  # We want to pull out all the parameters, and reformat them into strings
224
142
  # e.g. {arg}={value}
@@ -271,11 +189,11 @@ def generate_imported_tool_instantiation_call_str(obj: Any) -> Optional[str]:
271
189
  return None
272
190
 
273
191
 
274
- def is_base_model(obj: Any):
192
+ def _is_base_model(obj: Any):
275
193
  return isinstance(obj, BaseModel)
276
194
 
277
195
 
278
- def generate_import_code(module_attr_map: Optional[dict]):
196
+ def _generate_import_code(module_attr_map: Optional[dict]):
279
197
  if not module_attr_map:
280
198
  return ""
281
199
 
@@ -288,7 +206,7 @@ def generate_import_code(module_attr_map: Optional[dict]):
288
206
  return "\n".join(code_lines)
289
207
 
290
208
 
291
- def parse_letta_response_for_assistant_message(
209
+ def _parse_letta_response_for_assistant_message(
292
210
  target_agent_id: str,
293
211
  letta_response: LettaResponse,
294
212
  ) -> Optional[str]:
@@ -348,7 +266,7 @@ def execute_send_message_to_agent(
348
266
  return asyncio.run(async_execute_send_message_to_agent(sender_agent, messages, other_agent_id, log_prefix))
349
267
 
350
268
 
351
- async def send_message_to_agent_no_stream(
269
+ async def _send_message_to_agent_no_stream(
352
270
  server: "SyncServer",
353
271
  agent_id: str,
354
272
  actor: User,
@@ -377,7 +295,7 @@ async def send_message_to_agent_no_stream(
377
295
  return LettaResponse(messages=final_messages, usage=usage_stats)
378
296
 
379
297
 
380
- async def async_send_message_with_retries(
298
+ async def _async_send_message_with_retries(
381
299
  server: "SyncServer",
382
300
  sender_agent: "Agent",
383
301
  target_agent_id: str,
@@ -391,7 +309,7 @@ async def async_send_message_with_retries(
391
309
  for attempt in range(1, max_retries + 1):
392
310
  try:
393
311
  response = await asyncio.wait_for(
394
- send_message_to_agent_no_stream(
312
+ _send_message_to_agent_no_stream(
395
313
  server=server,
396
314
  agent_id=target_agent_id,
397
315
  actor=sender_agent.user,
@@ -401,7 +319,7 @@ async def async_send_message_with_retries(
401
319
  )
402
320
 
403
321
  # Then parse out the assistant message
404
- assistant_message = parse_letta_response_for_assistant_message(target_agent_id, response)
322
+ assistant_message = _parse_letta_response_for_assistant_message(target_agent_id, response)
405
323
  if assistant_message:
406
324
  sender_agent.logger.info(f"{logging_prefix} - {assistant_message}")
407
325
  return assistant_message
letta/groups/helpers.py CHANGED
@@ -76,6 +76,7 @@ def load_multi_agent(
76
76
  agent_state=agent_state,
77
77
  interface=interface,
78
78
  user=actor,
79
+ mcp_clients=mcp_clients,
79
80
  group_id=group.id,
80
81
  agent_ids=group.agent_ids,
81
82
  description=group.description,
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
2
  import threading
3
3
  from datetime import datetime, timezone
4
- from typing import List, Optional
4
+ from typing import Dict, List, Optional
5
5
 
6
6
  from letta.agent import Agent, AgentState
7
+ from letta.functions.mcp_client.base_client import BaseMCPClient
7
8
  from letta.groups.helpers import stringify_message
8
9
  from letta.interface import AgentInterface
9
10
  from letta.orm import User
@@ -26,6 +27,7 @@ class SleeptimeMultiAgent(Agent):
26
27
  interface: AgentInterface,
27
28
  agent_state: AgentState,
28
29
  user: User,
30
+ mcp_clients: Optional[Dict[str, BaseMCPClient]] = None,
29
31
  # custom
30
32
  group_id: str = "",
31
33
  agent_ids: List[str] = [],
@@ -115,6 +117,7 @@ class SleeptimeMultiAgent(Agent):
115
117
  agent_state=participant_agent_state,
116
118
  interface=StreamingServerInterface(),
117
119
  user=self.user,
120
+ mcp_clients=self.mcp_clients,
118
121
  )
119
122
 
120
123
  prior_messages = []
@@ -212,6 +215,7 @@ class SleeptimeMultiAgent(Agent):
212
215
  agent_state=self.agent_state,
213
216
  interface=self.interface,
214
217
  user=self.user,
218
+ mcp_clients=self.mcp_clients,
215
219
  )
216
220
  # Perform main agent step
217
221
  usage_stats = main_agent.step(
@@ -4,7 +4,24 @@ from letta.schemas.letta_message_content import TextContent
4
4
  from letta.schemas.message import Message, MessageCreate
5
5
 
6
6
 
7
- def prepare_input_message_create(
7
+ def convert_message_creates_to_messages(
8
+ messages: list[MessageCreate],
9
+ agent_id: str,
10
+ wrap_user_message: bool = True,
11
+ wrap_system_message: bool = True,
12
+ ) -> list[Message]:
13
+ return [
14
+ _convert_message_create_to_message(
15
+ message=message,
16
+ agent_id=agent_id,
17
+ wrap_user_message=wrap_user_message,
18
+ wrap_system_message=wrap_system_message,
19
+ )
20
+ for message in messages
21
+ ]
22
+
23
+
24
+ def _convert_message_create_to_message(
8
25
  message: MessageCreate,
9
26
  agent_id: str,
10
27
  wrap_user_message: bool = True,
@@ -23,12 +40,12 @@ def prepare_input_message_create(
23
40
  raise ValueError("Message content is empty or invalid")
24
41
 
25
42
  # Apply wrapping if needed
26
- if message.role == MessageRole.user and wrap_user_message:
43
+ if message.role not in {MessageRole.user, MessageRole.system}:
44
+ raise ValueError(f"Invalid message role: {message.role}")
45
+ elif message.role == MessageRole.user and wrap_user_message:
27
46
  message_content = system.package_user_message(user_message=message_content)
28
47
  elif message.role == MessageRole.system and wrap_system_message:
29
48
  message_content = system.package_system_message(system_message=message_content)
30
- elif message.role not in {MessageRole.user, MessageRole.system}:
31
- raise ValueError(f"Invalid message role: {message.role}")
32
49
 
33
50
  return Message(
34
51
  agent_id=agent_id,
@@ -3,7 +3,7 @@ from typing import Any, Dict, Optional
3
3
 
4
4
  from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY, PRE_EXECUTION_MESSAGE_ARG
5
5
  from letta.functions.ast_parsers import coerce_dict_args_by_annotations, get_function_annotations_from_source
6
- from letta.functions.helpers import execute_composio_action, generate_composio_action_from_func_name
6
+ from letta.functions.composio_helpers import execute_composio_action, generate_composio_action_from_func_name
7
7
  from letta.helpers.composio_helpers import get_composio_api_key
8
8
  from letta.orm.enums import ToolType
9
9
  from letta.schemas.agent import AgentState