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.
- letta/__init__.py +1 -1
- letta/agents/helpers.py +1 -1
- letta/agents/voice_agent.py +34 -55
- letta/agents/{ephemeral_memory_agent.py → voice_sleeptime_agent.py} +106 -129
- letta/client/client.py +3 -3
- letta/constants.py +13 -1
- letta/functions/function_sets/base.py +0 -10
- letta/functions/function_sets/voice.py +92 -0
- letta/functions/helpers.py +3 -5
- letta/orm/enums.py +1 -0
- letta/personas/examples/voice_memory_persona.txt +5 -0
- letta/prompts/system/voice_chat.txt +29 -0
- letta/prompts/system/voice_sleeptime.txt +74 -0
- letta/schemas/agent.py +14 -1
- letta/schemas/group.py +13 -2
- letta/schemas/message.py +4 -3
- letta/schemas/providers.py +0 -193
- letta/schemas/tool.py +5 -4
- letta/server/rest_api/routers/v1/embeddings.py +4 -3
- letta/server/rest_api/routers/v1/voice.py +2 -2
- letta/server/rest_api/utils.py +14 -14
- letta/server/server.py +66 -26
- letta/services/agent_manager.py +14 -7
- letta/services/group_manager.py +3 -0
- letta/services/helpers/agent_manager_helper.py +69 -12
- letta/services/message_manager.py +2 -2
- letta/services/passage_manager.py +13 -4
- letta/services/summarizer/summarizer.py +5 -8
- letta/services/tool_manager.py +32 -7
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/METADATA +1 -1
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/RECORD +34 -30
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.7.dev20250430205840.dist-info}/WHEEL +0 -0
- {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
|
-
|
366
|
+
mcp_server_configs = self.get_mcp_servers()
|
367
367
|
self.mcp_clients: Dict[str, BaseMCPClient] = {}
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
#
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
letta/services/agent_manager.py
CHANGED
@@ -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.
|
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
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
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
|
|
letta/services/group_manager.py
CHANGED
@@ -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.
|
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.
|
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.
|
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
|
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
|
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
|
-
|
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.
|
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:
|
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
|
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
|
-
|
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)])])
|
letta/services/tool_manager.py
CHANGED
@@ -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
|
-
|
43
|
-
if
|
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
|
-
|
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(
|
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
|