letta-nightly 0.7.6.dev20250430104233__py3-none-any.whl → 0.7.7.dev20250430205840__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 (34) hide show
  1. letta/__init__.py +1 -1
  2. letta/agents/helpers.py +1 -1
  3. letta/agents/voice_agent.py +34 -55
  4. letta/agents/{ephemeral_memory_agent.py → voice_sleeptime_agent.py} +106 -129
  5. letta/client/client.py +3 -3
  6. letta/constants.py +13 -1
  7. letta/functions/function_sets/base.py +0 -10
  8. letta/functions/function_sets/voice.py +92 -0
  9. letta/functions/helpers.py +3 -5
  10. letta/orm/enums.py +1 -0
  11. letta/personas/examples/voice_memory_persona.txt +5 -0
  12. letta/prompts/system/voice_chat.txt +29 -0
  13. letta/prompts/system/voice_sleeptime.txt +74 -0
  14. letta/schemas/agent.py +14 -1
  15. letta/schemas/group.py +13 -2
  16. letta/schemas/message.py +4 -3
  17. letta/schemas/providers.py +0 -193
  18. letta/schemas/tool.py +5 -4
  19. letta/server/rest_api/routers/v1/embeddings.py +4 -3
  20. letta/server/rest_api/routers/v1/voice.py +2 -2
  21. letta/server/rest_api/utils.py +14 -14
  22. letta/server/server.py +66 -26
  23. letta/services/agent_manager.py +14 -7
  24. letta/services/group_manager.py +3 -0
  25. letta/services/helpers/agent_manager_helper.py +69 -12
  26. letta/services/message_manager.py +2 -2
  27. letta/services/passage_manager.py +13 -4
  28. letta/services/summarizer/summarizer.py +5 -8
  29. letta/services/tool_manager.py +32 -7
  30. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/METADATA +1 -1
  31. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/RECORD +34 -30
  32. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/LICENSE +0 -0
  33. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/WHEEL +0 -0
  34. {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/entry_points.txt +0 -0
letta/server/server.py CHANGED
@@ -44,7 +44,7 @@ from letta.schemas.embedding_config import EmbeddingConfig
44
44
  # openai schemas
45
45
  from letta.schemas.enums import JobStatus, MessageStreamStatus
46
46
  from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate
47
- from letta.schemas.group import GroupCreate, SleeptimeManager
47
+ from letta.schemas.group import GroupCreate, ManagerType, SleeptimeManager, VoiceSleeptimeManager
48
48
  from letta.schemas.job import Job, JobUpdate
49
49
  from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, ToolReturnMessage
50
50
  from letta.schemas.letta_message_content import TextContent
@@ -363,29 +363,29 @@ class SyncServer(Server):
363
363
 
364
364
  # For MCP
365
365
  """Initialize the MCP clients (there may be multiple)"""
366
- # mcp_server_configs = self.get_mcp_servers()
366
+ mcp_server_configs = self.get_mcp_servers()
367
367
  self.mcp_clients: Dict[str, BaseMCPClient] = {}
368
- #
369
- # for server_name, server_config in mcp_server_configs.items():
370
- # if server_config.type == MCPServerType.SSE:
371
- # self.mcp_clients[server_name] = SSEMCPClient(server_config)
372
- # elif server_config.type == MCPServerType.STDIO:
373
- # self.mcp_clients[server_name] = StdioMCPClient(server_config)
374
- # else:
375
- # raise ValueError(f"Invalid MCP server config: {server_config}")
376
- #
377
- # try:
378
- # self.mcp_clients[server_name].connect_to_server()
379
- # except Exception as e:
380
- # logger.error(e)
381
- # self.mcp_clients.pop(server_name)
382
- #
383
- # # Print out the tools that are connected
384
- # for server_name, client in self.mcp_clients.items():
385
- # logger.info(f"Attempting to fetch tools from MCP server: {server_name}")
386
- # mcp_tools = client.list_tools()
387
- # logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
388
- # logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
368
+
369
+ for server_name, server_config in mcp_server_configs.items():
370
+ if server_config.type == MCPServerType.SSE:
371
+ self.mcp_clients[server_name] = SSEMCPClient(server_config)
372
+ elif server_config.type == MCPServerType.STDIO:
373
+ self.mcp_clients[server_name] = StdioMCPClient(server_config)
374
+ else:
375
+ raise ValueError(f"Invalid MCP server config: {server_config}")
376
+
377
+ try:
378
+ self.mcp_clients[server_name].connect_to_server()
379
+ except Exception as e:
380
+ logger.error(e)
381
+ self.mcp_clients.pop(server_name)
382
+
383
+ # Print out the tools that are connected
384
+ for server_name, client in self.mcp_clients.items():
385
+ logger.info(f"Attempting to fetch tools from MCP server: {server_name}")
386
+ mcp_tools = client.list_tools()
387
+ logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
388
+ logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
389
389
 
390
390
  # TODO: Remove these in memory caches
391
391
  self._llm_config_cache = {}
@@ -397,7 +397,9 @@ class SyncServer(Server):
397
397
  def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
398
398
  """Updated method to load agents from persisted storage"""
399
399
  agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
400
- if agent_state.multi_agent_group:
400
+ # TODO: Think about how to integrate voice sleeptime into sleeptime
401
+ # TODO: Voice sleeptime agents turn into normal agents when being messaged
402
+ if agent_state.multi_agent_group and agent_state.multi_agent_group.manager_type != ManagerType.voice_sleeptime:
401
403
  return load_multi_agent(
402
404
  group=agent_state.multi_agent_group, agent_state=agent_state, actor=actor, interface=interface, mcp_clients=self.mcp_clients
403
405
  )
@@ -769,7 +771,10 @@ class SyncServer(Server):
769
771
  log_event(name="end create_agent db")
770
772
 
771
773
  if request.enable_sleeptime:
772
- main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
774
+ if request.agent_type == AgentType.voice_convo_agent:
775
+ main_agent = self.create_voice_sleeptime_agent(main_agent=main_agent, actor=actor)
776
+ else:
777
+ main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
773
778
 
774
779
  return main_agent
775
780
 
@@ -788,7 +793,10 @@ class SyncServer(Server):
788
793
  if request.enable_sleeptime:
789
794
  agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
790
795
  if agent.multi_agent_group is None:
791
- self.create_sleeptime_agent(main_agent=agent, actor=actor)
796
+ if agent.agent_type == AgentType.voice_convo_agent:
797
+ self.create_voice_sleeptime_agent(main_agent=agent, actor=actor)
798
+ else:
799
+ self.create_sleeptime_agent(main_agent=agent, actor=actor)
792
800
 
793
801
  return self.agent_manager.update_agent(
794
802
  agent_id=agent_id,
@@ -828,6 +836,38 @@ class SyncServer(Server):
828
836
  )
829
837
  return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
830
838
 
839
+ def create_voice_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
840
+ # TODO: Inject system
841
+ request = CreateAgent(
842
+ name=main_agent.name + "-sleeptime",
843
+ agent_type=AgentType.voice_sleeptime_agent,
844
+ block_ids=[block.id for block in main_agent.memory.blocks],
845
+ memory_blocks=[
846
+ CreateBlock(
847
+ label="memory_persona",
848
+ value=get_persona_text("voice_memory_persona"),
849
+ ),
850
+ ],
851
+ llm_config=main_agent.llm_config,
852
+ embedding_config=main_agent.embedding_config,
853
+ project_id=main_agent.project_id,
854
+ )
855
+ voice_sleeptime_agent = self.agent_manager.create_agent(
856
+ agent_create=request,
857
+ actor=actor,
858
+ )
859
+ self.group_manager.create_group(
860
+ group=GroupCreate(
861
+ description="Low latency voice chat with async memory management.",
862
+ agent_ids=[voice_sleeptime_agent.id],
863
+ manager_config=VoiceSleeptimeManager(
864
+ manager_agent_id=main_agent.id,
865
+ ),
866
+ ),
867
+ actor=actor,
868
+ )
869
+ return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
870
+
831
871
  # convert name->id
832
872
 
833
873
  # TODO: These can be moved to agent_manager
@@ -11,6 +11,8 @@ from letta.constants import (
11
11
  BASE_SLEEPTIME_CHAT_TOOLS,
12
12
  BASE_SLEEPTIME_TOOLS,
13
13
  BASE_TOOLS,
14
+ BASE_VOICE_SLEEPTIME_CHAT_TOOLS,
15
+ BASE_VOICE_SLEEPTIME_TOOLS,
14
16
  DATA_SOURCE_ATTACH_ALERT,
15
17
  MAX_EMBEDDING_DIM,
16
18
  MULTI_AGENT_TOOLS,
@@ -179,7 +181,11 @@ class AgentManager:
179
181
  # tools
180
182
  tool_names = set(agent_create.tools or [])
181
183
  if agent_create.include_base_tools:
182
- if agent_create.agent_type == AgentType.sleeptime_agent:
184
+ if agent_create.agent_type == AgentType.voice_sleeptime_agent:
185
+ tool_names |= set(BASE_VOICE_SLEEPTIME_TOOLS)
186
+ elif agent_create.agent_type == AgentType.voice_convo_agent:
187
+ tool_names |= set(BASE_VOICE_SLEEPTIME_CHAT_TOOLS)
188
+ elif agent_create.agent_type == AgentType.sleeptime_agent:
183
189
  tool_names |= set(BASE_SLEEPTIME_TOOLS)
184
190
  elif agent_create.enable_sleeptime:
185
191
  tool_names |= set(BASE_SLEEPTIME_CHAT_TOOLS)
@@ -603,12 +609,13 @@ class AgentManager:
603
609
  # Delete sleeptime agent and group (TODO this is flimsy pls fix)
604
610
  if agent.multi_agent_group:
605
611
  participant_agent_ids = agent.multi_agent_group.agent_ids
606
- if agent.multi_agent_group.manager_type == ManagerType.sleeptime and len(participant_agent_ids) == 1:
607
- try:
608
- sleeptime_agent = AgentModel.read(db_session=session, identifier=participant_agent_ids[0], actor=actor)
609
- agents_to_delete.append(sleeptime_agent)
610
- except NoResultFound:
611
- pass # agent already deleted
612
+ if agent.multi_agent_group.manager_type in {ManagerType.sleeptime, ManagerType.voice_sleeptime} and participant_agent_ids:
613
+ for participant_agent_id in participant_agent_ids:
614
+ try:
615
+ sleeptime_agent = AgentModel.read(db_session=session, identifier=participant_agent_id, actor=actor)
616
+ agents_to_delete.append(sleeptime_agent)
617
+ except NoResultFound:
618
+ pass # agent already deleted
612
619
  sleeptime_agent_group = GroupModel.read(db_session=session, identifier=agent.multi_agent_group.id, actor=actor)
613
620
  sleeptime_group_to_delete = sleeptime_agent_group
614
621
 
@@ -77,6 +77,9 @@ class GroupManager:
77
77
  new_group.sleeptime_agent_frequency = group.manager_config.sleeptime_agent_frequency
78
78
  if new_group.sleeptime_agent_frequency:
79
79
  new_group.turns_counter = -1
80
+ case ManagerType.voice_sleeptime:
81
+ new_group.manager_type = ManagerType.voice_sleeptime
82
+ new_group.manager_agent_id = group.manager_config.manager_agent_id
80
83
  case _:
81
84
  raise ValueError(f"Unsupported manager type: {group.manager_config.manager_type}")
82
85
 
@@ -20,7 +20,7 @@ from letta.schemas.message import Message, MessageCreate
20
20
  from letta.schemas.passage import Passage as PydanticPassage
21
21
  from letta.schemas.tool_rule import ToolRule
22
22
  from letta.schemas.user import User
23
- from letta.system import get_initial_boot_messages, get_login_event
23
+ from letta.system import get_initial_boot_messages, get_login_event, package_function_response
24
24
  from letta.tracing import trace_method
25
25
 
26
26
 
@@ -94,7 +94,11 @@ def _process_tags(agent: AgentModel, tags: List[str], replace=True):
94
94
  def derive_system_message(agent_type: AgentType, enable_sleeptime: Optional[bool] = None, system: Optional[str] = None):
95
95
  if system is None:
96
96
  # TODO: don't hardcode
97
- if agent_type == AgentType.memgpt_agent and not enable_sleeptime:
97
+ if agent_type == AgentType.voice_convo_agent:
98
+ system = gpt_system.get_system_text("voice_chat")
99
+ elif agent_type == AgentType.voice_sleeptime_agent:
100
+ system = gpt_system.get_system_text("voice_sleeptime")
101
+ elif agent_type == AgentType.memgpt_agent and not enable_sleeptime:
98
102
  system = gpt_system.get_system_text("memgpt_chat")
99
103
  elif agent_type == AgentType.memgpt_agent and enable_sleeptime:
100
104
  system = gpt_system.get_system_text("memgpt_sleeptime_chat")
@@ -278,23 +282,76 @@ def package_initial_message_sequence(
278
282
  packed_message = system.package_user_message(
279
283
  user_message=message_create.content,
280
284
  )
285
+ init_messages.append(
286
+ Message(
287
+ role=message_create.role,
288
+ content=[TextContent(text=packed_message)],
289
+ name=message_create.name,
290
+ organization_id=actor.organization_id,
291
+ agent_id=agent_id,
292
+ model=model,
293
+ )
294
+ )
281
295
  elif message_create.role == MessageRole.system:
282
296
  packed_message = system.package_system_message(
283
297
  system_message=message_create.content,
284
298
  )
299
+ init_messages.append(
300
+ Message(
301
+ role=message_create.role,
302
+ content=[TextContent(text=packed_message)],
303
+ name=message_create.name,
304
+ organization_id=actor.organization_id,
305
+ agent_id=agent_id,
306
+ model=model,
307
+ )
308
+ )
309
+ elif message_create.role == MessageRole.assistant:
310
+ # append tool call to send_message
311
+ import json
312
+ import uuid
313
+
314
+ from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall as OpenAIToolCall
315
+ from openai.types.chat.chat_completion_message_tool_call import Function as OpenAIFunction
316
+
317
+ from letta.constants import DEFAULT_MESSAGE_TOOL
318
+
319
+ tool_call_id = str(uuid.uuid4())
320
+ init_messages.append(
321
+ Message(
322
+ role=MessageRole.assistant,
323
+ content=None,
324
+ name=message_create.name,
325
+ organization_id=actor.organization_id,
326
+ agent_id=agent_id,
327
+ model=model,
328
+ tool_calls=[
329
+ OpenAIToolCall(
330
+ id=tool_call_id,
331
+ type="function",
332
+ function=OpenAIFunction(name=DEFAULT_MESSAGE_TOOL, arguments=json.dumps({"message": message_create.content})),
333
+ )
334
+ ],
335
+ )
336
+ )
337
+
338
+ # add tool return
339
+ function_response = package_function_response(True, "None")
340
+ init_messages.append(
341
+ Message(
342
+ role=MessageRole.tool,
343
+ content=[TextContent(text=function_response)],
344
+ name=message_create.name,
345
+ organization_id=actor.organization_id,
346
+ agent_id=agent_id,
347
+ model=model,
348
+ tool_call_id=tool_call_id,
349
+ )
350
+ )
285
351
  else:
352
+ # TODO: add tool call and tool return
286
353
  raise ValueError(f"Invalid message role: {message_create.role}")
287
354
 
288
- init_messages.append(
289
- Message(
290
- role=message_create.role,
291
- content=[TextContent(text=packed_message)],
292
- name=message_create.name,
293
- organization_id=actor.organization_id,
294
- agent_id=agent_id,
295
- model=model,
296
- )
297
- )
298
355
  return init_messages
299
356
 
300
357
 
@@ -122,7 +122,7 @@ class MessageManager:
122
122
  message = self.update_message_by_id(message_id=message_id, message_update=update_message, actor=actor)
123
123
 
124
124
  # convert back to LettaMessage
125
- for letta_msg in message.to_letta_message(use_assistant_message=True):
125
+ for letta_msg in message.to_letta_messages(use_assistant_message=True):
126
126
  if letta_msg.message_type == letta_message_update.message_type:
127
127
  return letta_msg
128
128
 
@@ -160,7 +160,7 @@ class MessageManager:
160
160
  message = self.update_message_by_id(message_id=message_id, message_update=update_message, actor=actor)
161
161
 
162
162
  # convert back to LettaMessage
163
- for letta_msg in message.to_letta_message(use_assistant_message=True):
163
+ for letta_msg in message.to_letta_messages(use_assistant_message=True):
164
164
  if letta_msg.message_type == letta_message_update.message_type:
165
165
  return letta_msg
166
166
 
@@ -220,15 +220,24 @@ class PassageManager:
220
220
  with self.session_maker() as session:
221
221
  return AgentPassage.size(db_session=session, actor=actor, agent_id=agent_id)
222
222
 
223
- def estimate_embeddings_size_GB(
223
+ def estimate_embeddings_size(
224
224
  self,
225
225
  actor: PydanticUser,
226
226
  agent_id: Optional[str] = None,
227
+ storage_unit: str = "GB",
227
228
  ) -> float:
228
229
  """
229
- Estimate the size of the embeddings in GB.
230
+ Estimate the size of the embeddings. Defaults to GB.
230
231
  """
232
+ BYTES_PER_STORAGE_UNIT = {
233
+ "B": 1,
234
+ "KB": 1024,
235
+ "MB": 1024**2,
236
+ "GB": 1024**3,
237
+ "TB": 1024**4,
238
+ }
239
+ if storage_unit not in BYTES_PER_STORAGE_UNIT:
240
+ raise ValueError(f"Invalid storage unit: {storage_unit}. Must be one of {list(BYTES_PER_STORAGE_UNIT.keys())}.")
231
241
  BYTES_PER_EMBEDDING_DIM = 4
232
- BYTES_PER_GB = 1024 * 1024 * 1024
233
- GB_PER_EMBEDDING = BYTES_PER_EMBEDDING_DIM / BYTES_PER_GB * MAX_EMBEDDING_DIM
242
+ GB_PER_EMBEDDING = BYTES_PER_EMBEDDING_DIM / BYTES_PER_STORAGE_UNIT[storage_unit] * MAX_EMBEDDING_DIM
234
243
  return self.size(actor=actor, agent_id=agent_id) * GB_PER_EMBEDDING
@@ -3,7 +3,7 @@ import json
3
3
  import traceback
4
4
  from typing import List, Tuple
5
5
 
6
- from letta.agents.ephemeral_memory_agent import EphemeralMemoryAgent
6
+ from letta.agents.voice_sleeptime_agent import VoiceSleeptimeAgent
7
7
  from letta.log import get_logger
8
8
  from letta.schemas.enums import MessageRole
9
9
  from letta.schemas.letta_message_content import TextContent
@@ -21,7 +21,7 @@ class Summarizer:
21
21
  """
22
22
 
23
23
  def __init__(
24
- self, mode: SummarizationMode, summarizer_agent: EphemeralMemoryAgent, message_buffer_limit: int = 10, message_buffer_min: int = 3
24
+ self, mode: SummarizationMode, summarizer_agent: VoiceSleeptimeAgent, message_buffer_limit: int = 10, message_buffer_min: int = 3
25
25
  ):
26
26
  self.mode = mode
27
27
 
@@ -109,17 +109,14 @@ class Summarizer:
109
109
 
110
110
  evicted_messages_str = "\n".join(formatted_evicted_messages)
111
111
  in_context_messages_str = "\n".join(formatted_in_context_messages)
112
- summary_request_text = f"""You are a specialized memory recall agent assisting another AI agent by asynchronously reorganizing its memory storage. The LLM agent you are helping maintains a limited context window that retains only the most recent {self.message_buffer_min} messages from its conversations. The provided conversation history includes messages that are about to be evicted from its context window, as well as some additional recent messages for extra clarity and context.
112
+ summary_request_text = f"""You’re a memory-recall helper for an AI that can only keep the last {self.message_buffer_min} messages. Scan the conversation history, focusing on messages about to drop out of that window, and write crisp notes that capture any important facts or insights about the human so they aren’t lost.
113
113
 
114
- Your task is to carefully review the provided conversation history and proactively generate detailed, relevant memories about the human participant, specifically targeting information contained in messages that are about to be evicted from the context window. Your notes will help preserve critical insights, events, or facts that would otherwise be forgotten.
115
-
116
- (Older) Evicted Messages:
114
+ (Older) Evicted Messages:\n
117
115
  {evicted_messages_str}
118
116
 
119
- (Newer) In-Context Messages:
117
+ (Newer) In-Context Messages:\n
120
118
  {in_context_messages_str}
121
119
  """
122
-
123
120
  # Fire-and-forget the summarization task
124
121
  self.fire_and_forget(
125
122
  self.summarizer_agent.step([MessageCreate(role=MessageRole.user, content=[TextContent(text=summary_request_text)])])
@@ -7,6 +7,8 @@ from letta.constants import (
7
7
  BASE_MEMORY_TOOLS,
8
8
  BASE_SLEEPTIME_TOOLS,
9
9
  BASE_TOOLS,
10
+ BASE_VOICE_SLEEPTIME_CHAT_TOOLS,
11
+ BASE_VOICE_SLEEPTIME_TOOLS,
10
12
  LETTA_TOOL_SET,
11
13
  MCP_TOOL_TAG_NAME_PREFIX,
12
14
  MULTI_AGENT_TOOLS,
@@ -39,18 +41,24 @@ class ToolManager:
39
41
  @enforce_types
40
42
  def create_or_update_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
41
43
  """Create a new tool based on the ToolCreate schema."""
42
- tool = self.get_tool_by_name(tool_name=pydantic_tool.name, actor=actor)
43
- if tool:
44
+ tool_id = self.get_tool_id_by_name(tool_name=pydantic_tool.name, actor=actor)
45
+ if tool_id:
44
46
  # Put to dict and remove fields that should not be reset
45
47
  update_data = pydantic_tool.model_dump(exclude_unset=True, exclude_none=True)
46
48
 
47
49
  # If there's anything to update
48
50
  if update_data:
49
- tool = self.update_tool_by_id(tool.id, ToolUpdate(**update_data), actor)
51
+ # In case we want to update the tool type
52
+ # Useful if we are shuffling around base tools
53
+ updated_tool_type = None
54
+ if "tool_type" in update_data:
55
+ updated_tool_type = update_data.get("tool_type")
56
+ tool = self.update_tool_by_id(tool_id, ToolUpdate(**update_data), actor, updated_tool_type=updated_tool_type)
50
57
  else:
51
58
  printd(
52
59
  f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={pydantic_tool.name}, but found existing tool with nothing to update."
53
60
  )
61
+ tool = self.get_tool_by_id(tool_id, actor=actor)
54
62
  else:
55
63
  tool = self.create_tool(pydantic_tool, actor=actor)
56
64
 
@@ -112,6 +120,16 @@ class ToolManager:
112
120
  except NoResultFound:
113
121
  return None
114
122
 
123
+ @enforce_types
124
+ def get_tool_id_by_name(self, tool_name: str, actor: PydanticUser) -> Optional[str]:
125
+ """Retrieve a tool by its name and a user. We derive the organization from the user, and retrieve that tool."""
126
+ try:
127
+ with self.session_maker() as session:
128
+ tool = ToolModel.read(db_session=session, name=tool_name, actor=actor)
129
+ return tool.id
130
+ except NoResultFound:
131
+ return None
132
+
115
133
  @enforce_types
116
134
  def list_tools(self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticTool]:
117
135
  """List all tools with optional pagination."""
@@ -154,7 +172,9 @@ class ToolManager:
154
172
  return ToolModel.size(db_session=session, actor=actor, name=LETTA_TOOL_SET)
155
173
 
156
174
  @enforce_types
157
- def update_tool_by_id(self, tool_id: str, tool_update: ToolUpdate, actor: PydanticUser) -> PydanticTool:
175
+ def update_tool_by_id(
176
+ self, tool_id: str, tool_update: ToolUpdate, actor: PydanticUser, updated_tool_type: Optional[ToolType] = None
177
+ ) -> PydanticTool:
158
178
  """Update a tool by its ID with the given ToolUpdate object."""
159
179
  with self.session_maker() as session:
160
180
  # Fetch the tool by ID
@@ -173,6 +193,9 @@ class ToolManager:
173
193
  tool.json_schema = new_schema
174
194
  tool.name = new_schema["name"]
175
195
 
196
+ if updated_tool_type:
197
+ tool.tool_type = updated_tool_type
198
+
176
199
  # Save the updated tool to the database
177
200
  return tool.update(db_session=session, actor=actor).to_pydantic()
178
201
 
@@ -190,7 +213,7 @@ class ToolManager:
190
213
  def upsert_base_tools(self, actor: PydanticUser) -> List[PydanticTool]:
191
214
  """Add default tools in base.py and multi_agent.py"""
192
215
  functions_to_schema = {}
193
- module_names = ["base", "multi_agent"]
216
+ module_names = ["base", "multi_agent", "voice"]
194
217
 
195
218
  for module_name in module_names:
196
219
  full_module_name = f"letta.functions.function_sets.{module_name}"
@@ -223,9 +246,12 @@ class ToolManager:
223
246
  elif name in BASE_SLEEPTIME_TOOLS:
224
247
  tool_type = ToolType.LETTA_SLEEPTIME_CORE
225
248
  tags = [tool_type.value]
249
+ elif name in BASE_VOICE_SLEEPTIME_TOOLS or name in BASE_VOICE_SLEEPTIME_CHAT_TOOLS:
250
+ tool_type = ToolType.LETTA_VOICE_SLEEPTIME_CORE
251
+ tags = [tool_type.value]
226
252
  else:
227
253
  raise ValueError(
228
- f"Tool name {name} is not in the list of base tool names: {BASE_TOOLS + BASE_MEMORY_TOOLS + MULTI_AGENT_TOOLS + BASE_SLEEPTIME_TOOLS}"
254
+ f"Tool name {name} is not in the list of base tool names: {BASE_TOOLS + BASE_MEMORY_TOOLS + MULTI_AGENT_TOOLS + BASE_SLEEPTIME_TOOLS + BASE_VOICE_SLEEPTIME_TOOLS + BASE_VOICE_SLEEPTIME_CHAT_TOOLS}"
229
255
  )
230
256
 
231
257
  # create to tool
@@ -243,5 +269,4 @@ class ToolManager:
243
269
  )
244
270
 
245
271
  # TODO: Delete any base tools that are stale
246
-
247
272
  return tools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.7.6.dev20250430104233
3
+ Version: 0.7.7.dev20250430205840
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team