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.
- letta/__init__.py +1 -1
- letta/agent.py +8 -12
- letta/agents/exceptions.py +6 -0
- letta/agents/helpers.py +1 -1
- letta/agents/letta_agent.py +48 -35
- letta/agents/letta_agent_batch.py +6 -2
- letta/agents/voice_agent.py +41 -59
- letta/agents/{ephemeral_memory_agent.py → voice_sleeptime_agent.py} +106 -129
- letta/client/client.py +3 -3
- letta/constants.py +18 -2
- letta/functions/composio_helpers.py +100 -0
- letta/functions/function_sets/base.py +0 -10
- letta/functions/function_sets/voice.py +92 -0
- letta/functions/functions.py +4 -2
- letta/functions/helpers.py +19 -101
- letta/groups/helpers.py +1 -0
- letta/groups/sleeptime_multi_agent.py +5 -1
- letta/helpers/message_helper.py +21 -4
- letta/helpers/tool_execution_helper.py +1 -1
- letta/interfaces/anthropic_streaming_interface.py +165 -158
- letta/interfaces/openai_chat_completions_streaming_interface.py +1 -1
- letta/llm_api/anthropic.py +15 -10
- letta/llm_api/anthropic_client.py +5 -1
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/llm_api_tools.py +7 -0
- letta/llm_api/llm_client.py +12 -2
- letta/llm_api/llm_client_base.py +4 -0
- letta/llm_api/openai.py +9 -3
- letta/llm_api/openai_client.py +18 -4
- letta/memory.py +3 -1
- letta/orm/enums.py +1 -0
- letta/orm/group.py +2 -0
- letta/orm/provider.py +10 -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 -2
- letta/schemas/enums.py +11 -0
- letta/schemas/group.py +37 -2
- letta/schemas/llm_config.py +1 -0
- letta/schemas/llm_config_overrides.py +2 -2
- letta/schemas/message.py +4 -3
- letta/schemas/providers.py +75 -213
- letta/schemas/tool.py +8 -12
- letta/server/rest_api/app.py +12 -0
- letta/server/rest_api/chat_completions_interface.py +1 -1
- letta/server/rest_api/interface.py +8 -10
- letta/server/rest_api/{optimistic_json_parser.py → json_parser.py} +62 -26
- letta/server/rest_api/routers/v1/agents.py +1 -1
- letta/server/rest_api/routers/v1/embeddings.py +4 -3
- letta/server/rest_api/routers/v1/llms.py +4 -3
- letta/server/rest_api/routers/v1/providers.py +4 -1
- letta/server/rest_api/routers/v1/voice.py +0 -2
- letta/server/rest_api/utils.py +22 -33
- letta/server/server.py +91 -37
- letta/services/agent_manager.py +14 -7
- letta/services/group_manager.py +61 -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/provider_manager.py +25 -14
- letta/services/summarizer/summarizer.py +20 -15
- letta/services/tool_executor/tool_execution_manager.py +1 -1
- letta/services/tool_executor/tool_executor.py +3 -3
- letta/services/tool_manager.py +32 -7
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/METADATA +4 -5
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/RECORD +70 -64
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.6.dev20250430104233.dist-info → letta_nightly-0.7.8.dev20250501064110.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
|
@@ -268,10 +268,11 @@ class SyncServer(Server):
|
|
268
268
|
)
|
269
269
|
|
270
270
|
# collect providers (always has Letta as a default)
|
271
|
-
self._enabled_providers: List[Provider] = [LettaProvider()]
|
271
|
+
self._enabled_providers: List[Provider] = [LettaProvider(name="letta")]
|
272
272
|
if model_settings.openai_api_key:
|
273
273
|
self._enabled_providers.append(
|
274
274
|
OpenAIProvider(
|
275
|
+
name="openai",
|
275
276
|
api_key=model_settings.openai_api_key,
|
276
277
|
base_url=model_settings.openai_api_base,
|
277
278
|
)
|
@@ -279,12 +280,14 @@ class SyncServer(Server):
|
|
279
280
|
if model_settings.anthropic_api_key:
|
280
281
|
self._enabled_providers.append(
|
281
282
|
AnthropicProvider(
|
283
|
+
name="anthropic",
|
282
284
|
api_key=model_settings.anthropic_api_key,
|
283
285
|
)
|
284
286
|
)
|
285
287
|
if model_settings.ollama_base_url:
|
286
288
|
self._enabled_providers.append(
|
287
289
|
OllamaProvider(
|
290
|
+
name="ollama",
|
288
291
|
base_url=model_settings.ollama_base_url,
|
289
292
|
api_key=None,
|
290
293
|
default_prompt_formatter=model_settings.default_prompt_formatter,
|
@@ -293,12 +296,14 @@ class SyncServer(Server):
|
|
293
296
|
if model_settings.gemini_api_key:
|
294
297
|
self._enabled_providers.append(
|
295
298
|
GoogleAIProvider(
|
299
|
+
name="google_ai",
|
296
300
|
api_key=model_settings.gemini_api_key,
|
297
301
|
)
|
298
302
|
)
|
299
303
|
if model_settings.google_cloud_location and model_settings.google_cloud_project:
|
300
304
|
self._enabled_providers.append(
|
301
305
|
GoogleVertexProvider(
|
306
|
+
name="google_vertex",
|
302
307
|
google_cloud_project=model_settings.google_cloud_project,
|
303
308
|
google_cloud_location=model_settings.google_cloud_location,
|
304
309
|
)
|
@@ -307,6 +312,7 @@ class SyncServer(Server):
|
|
307
312
|
assert model_settings.azure_api_version, "AZURE_API_VERSION is required"
|
308
313
|
self._enabled_providers.append(
|
309
314
|
AzureProvider(
|
315
|
+
name="azure",
|
310
316
|
api_key=model_settings.azure_api_key,
|
311
317
|
base_url=model_settings.azure_base_url,
|
312
318
|
api_version=model_settings.azure_api_version,
|
@@ -315,12 +321,14 @@ class SyncServer(Server):
|
|
315
321
|
if model_settings.groq_api_key:
|
316
322
|
self._enabled_providers.append(
|
317
323
|
GroqProvider(
|
324
|
+
name="groq",
|
318
325
|
api_key=model_settings.groq_api_key,
|
319
326
|
)
|
320
327
|
)
|
321
328
|
if model_settings.together_api_key:
|
322
329
|
self._enabled_providers.append(
|
323
330
|
TogetherProvider(
|
331
|
+
name="together",
|
324
332
|
api_key=model_settings.together_api_key,
|
325
333
|
default_prompt_formatter=model_settings.default_prompt_formatter,
|
326
334
|
)
|
@@ -329,6 +337,7 @@ class SyncServer(Server):
|
|
329
337
|
# vLLM exposes both a /chat/completions and a /completions endpoint
|
330
338
|
self._enabled_providers.append(
|
331
339
|
VLLMCompletionsProvider(
|
340
|
+
name="vllm",
|
332
341
|
base_url=model_settings.vllm_api_base,
|
333
342
|
default_prompt_formatter=model_settings.default_prompt_formatter,
|
334
343
|
)
|
@@ -338,12 +347,14 @@ class SyncServer(Server):
|
|
338
347
|
# e.g. "... --enable-auto-tool-choice --tool-call-parser hermes"
|
339
348
|
self._enabled_providers.append(
|
340
349
|
VLLMChatCompletionsProvider(
|
350
|
+
name="vllm",
|
341
351
|
base_url=model_settings.vllm_api_base,
|
342
352
|
)
|
343
353
|
)
|
344
354
|
if model_settings.aws_access_key and model_settings.aws_secret_access_key and model_settings.aws_region:
|
345
355
|
self._enabled_providers.append(
|
346
356
|
AnthropicBedrockProvider(
|
357
|
+
name="bedrock",
|
347
358
|
aws_region=model_settings.aws_region,
|
348
359
|
)
|
349
360
|
)
|
@@ -355,37 +366,37 @@ class SyncServer(Server):
|
|
355
366
|
if model_settings.lmstudio_base_url.endswith("/v1")
|
356
367
|
else model_settings.lmstudio_base_url + "/v1"
|
357
368
|
)
|
358
|
-
self._enabled_providers.append(LMStudioOpenAIProvider(base_url=lmstudio_url))
|
369
|
+
self._enabled_providers.append(LMStudioOpenAIProvider(name="lmstudio_openai", base_url=lmstudio_url))
|
359
370
|
if model_settings.deepseek_api_key:
|
360
|
-
self._enabled_providers.append(DeepSeekProvider(api_key=model_settings.deepseek_api_key))
|
371
|
+
self._enabled_providers.append(DeepSeekProvider(name="deepseek", api_key=model_settings.deepseek_api_key))
|
361
372
|
if model_settings.xai_api_key:
|
362
|
-
self._enabled_providers.append(XAIProvider(api_key=model_settings.xai_api_key))
|
373
|
+
self._enabled_providers.append(XAIProvider(name="xai", api_key=model_settings.xai_api_key))
|
363
374
|
|
364
375
|
# For MCP
|
365
376
|
"""Initialize the MCP clients (there may be multiple)"""
|
366
|
-
|
377
|
+
mcp_server_configs = self.get_mcp_servers()
|
367
378
|
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
|
-
|
379
|
+
|
380
|
+
for server_name, server_config in mcp_server_configs.items():
|
381
|
+
if server_config.type == MCPServerType.SSE:
|
382
|
+
self.mcp_clients[server_name] = SSEMCPClient(server_config)
|
383
|
+
elif server_config.type == MCPServerType.STDIO:
|
384
|
+
self.mcp_clients[server_name] = StdioMCPClient(server_config)
|
385
|
+
else:
|
386
|
+
raise ValueError(f"Invalid MCP server config: {server_config}")
|
387
|
+
|
388
|
+
try:
|
389
|
+
self.mcp_clients[server_name].connect_to_server()
|
390
|
+
except Exception as e:
|
391
|
+
logger.error(e)
|
392
|
+
self.mcp_clients.pop(server_name)
|
393
|
+
|
394
|
+
# Print out the tools that are connected
|
395
|
+
for server_name, client in self.mcp_clients.items():
|
396
|
+
logger.info(f"Attempting to fetch tools from MCP server: {server_name}")
|
397
|
+
mcp_tools = client.list_tools()
|
398
|
+
logger.info(f"MCP tools connected: {', '.join([t.name for t in mcp_tools])}")
|
399
|
+
logger.debug(f"MCP tools: {', '.join([str(t) for t in mcp_tools])}")
|
389
400
|
|
390
401
|
# TODO: Remove these in memory caches
|
391
402
|
self._llm_config_cache = {}
|
@@ -397,7 +408,9 @@ class SyncServer(Server):
|
|
397
408
|
def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
398
409
|
"""Updated method to load agents from persisted storage"""
|
399
410
|
agent_state = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
400
|
-
|
411
|
+
# TODO: Think about how to integrate voice sleeptime into sleeptime
|
412
|
+
# TODO: Voice sleeptime agents turn into normal agents when being messaged
|
413
|
+
if agent_state.multi_agent_group and agent_state.multi_agent_group.manager_type != ManagerType.voice_sleeptime:
|
401
414
|
return load_multi_agent(
|
402
415
|
group=agent_state.multi_agent_group, agent_state=agent_state, actor=actor, interface=interface, mcp_clients=self.mcp_clients
|
403
416
|
)
|
@@ -769,7 +782,10 @@ class SyncServer(Server):
|
|
769
782
|
log_event(name="end create_agent db")
|
770
783
|
|
771
784
|
if request.enable_sleeptime:
|
772
|
-
|
785
|
+
if request.agent_type == AgentType.voice_convo_agent:
|
786
|
+
main_agent = self.create_voice_sleeptime_agent(main_agent=main_agent, actor=actor)
|
787
|
+
else:
|
788
|
+
main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
|
773
789
|
|
774
790
|
return main_agent
|
775
791
|
|
@@ -788,7 +804,10 @@ class SyncServer(Server):
|
|
788
804
|
if request.enable_sleeptime:
|
789
805
|
agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
790
806
|
if agent.multi_agent_group is None:
|
791
|
-
|
807
|
+
if agent.agent_type == AgentType.voice_convo_agent:
|
808
|
+
self.create_voice_sleeptime_agent(main_agent=agent, actor=actor)
|
809
|
+
else:
|
810
|
+
self.create_sleeptime_agent(main_agent=agent, actor=actor)
|
792
811
|
|
793
812
|
return self.agent_manager.update_agent(
|
794
813
|
agent_id=agent_id,
|
@@ -828,6 +847,40 @@ class SyncServer(Server):
|
|
828
847
|
)
|
829
848
|
return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
|
830
849
|
|
850
|
+
def create_voice_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
|
851
|
+
# TODO: Inject system
|
852
|
+
request = CreateAgent(
|
853
|
+
name=main_agent.name + "-sleeptime",
|
854
|
+
agent_type=AgentType.voice_sleeptime_agent,
|
855
|
+
block_ids=[block.id for block in main_agent.memory.blocks],
|
856
|
+
memory_blocks=[
|
857
|
+
CreateBlock(
|
858
|
+
label="memory_persona",
|
859
|
+
value=get_persona_text("voice_memory_persona"),
|
860
|
+
),
|
861
|
+
],
|
862
|
+
llm_config=main_agent.llm_config,
|
863
|
+
embedding_config=main_agent.embedding_config,
|
864
|
+
project_id=main_agent.project_id,
|
865
|
+
)
|
866
|
+
voice_sleeptime_agent = self.agent_manager.create_agent(
|
867
|
+
agent_create=request,
|
868
|
+
actor=actor,
|
869
|
+
)
|
870
|
+
self.group_manager.create_group(
|
871
|
+
group=GroupCreate(
|
872
|
+
description="Low latency voice chat with async memory management.",
|
873
|
+
agent_ids=[voice_sleeptime_agent.id],
|
874
|
+
manager_config=VoiceSleeptimeManager(
|
875
|
+
manager_agent_id=main_agent.id,
|
876
|
+
max_message_buffer_length=constants.DEFAULT_MAX_MESSAGE_BUFFER_LENGTH,
|
877
|
+
min_message_buffer_length=constants.DEFAULT_MIN_MESSAGE_BUFFER_LENGTH,
|
878
|
+
),
|
879
|
+
),
|
880
|
+
actor=actor,
|
881
|
+
)
|
882
|
+
return self.agent_manager.get_agent_by_id(agent_id=main_agent.id, actor=actor)
|
883
|
+
|
831
884
|
# convert name->id
|
832
885
|
|
833
886
|
# TODO: These can be moved to agent_manager
|
@@ -1142,10 +1195,10 @@ class SyncServer(Server):
|
|
1142
1195
|
except NoResultFound:
|
1143
1196
|
raise HTTPException(status_code=404, detail=f"Organization with id {org_id} not found")
|
1144
1197
|
|
1145
|
-
def list_llm_models(self) -> List[LLMConfig]:
|
1198
|
+
def list_llm_models(self, byok_only: bool = False) -> List[LLMConfig]:
|
1146
1199
|
"""List available models"""
|
1147
1200
|
llm_models = []
|
1148
|
-
for provider in self.get_enabled_providers():
|
1201
|
+
for provider in self.get_enabled_providers(byok_only=byok_only):
|
1149
1202
|
try:
|
1150
1203
|
llm_models.extend(provider.list_llm_models())
|
1151
1204
|
except Exception as e:
|
@@ -1165,11 +1218,12 @@ class SyncServer(Server):
|
|
1165
1218
|
warnings.warn(f"An error occurred while listing embedding models for provider {provider}: {e}")
|
1166
1219
|
return embedding_models
|
1167
1220
|
|
1168
|
-
def get_enabled_providers(self):
|
1221
|
+
def get_enabled_providers(self, byok_only: bool = False):
|
1222
|
+
providers_from_db = {p.name: p.cast_to_subtype() for p in self.provider_manager.list_providers()}
|
1223
|
+
if byok_only:
|
1224
|
+
return list(providers_from_db.values())
|
1169
1225
|
providers_from_env = {p.name: p for p in self._enabled_providers}
|
1170
|
-
|
1171
|
-
# Merge the two dictionaries, keeping the values from providers_from_db where conflicts occur
|
1172
|
-
return {**providers_from_env, **providers_from_db}.values()
|
1226
|
+
return list(providers_from_env.values()) + list(providers_from_db.values())
|
1173
1227
|
|
1174
1228
|
@trace_method
|
1175
1229
|
def get_llm_config_from_handle(
|
@@ -1254,7 +1308,7 @@ class SyncServer(Server):
|
|
1254
1308
|
return embedding_config
|
1255
1309
|
|
1256
1310
|
def get_provider_from_name(self, provider_name: str) -> Provider:
|
1257
|
-
providers = [provider for provider in self.
|
1311
|
+
providers = [provider for provider in self.get_enabled_providers() if provider.name == provider_name]
|
1258
1312
|
if not providers:
|
1259
1313
|
raise ValueError(f"Provider {provider_name} is not supported")
|
1260
1314
|
elif len(providers) > 1:
|
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,15 @@ 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
|
83
|
+
max_message_buffer_length = group.manager_config.max_message_buffer_length
|
84
|
+
min_message_buffer_length = group.manager_config.min_message_buffer_length
|
85
|
+
# Safety check for buffer length range
|
86
|
+
self.ensure_buffer_length_range_valid(max_value=max_message_buffer_length, min_value=min_message_buffer_length)
|
87
|
+
new_group.max_message_buffer_length = max_message_buffer_length
|
88
|
+
new_group.min_message_buffer_length = min_message_buffer_length
|
80
89
|
case _:
|
81
90
|
raise ValueError(f"Unsupported manager type: {group.manager_config.manager_type}")
|
82
91
|
|
@@ -94,6 +103,8 @@ class GroupManager:
|
|
94
103
|
group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
|
95
104
|
|
96
105
|
sleeptime_agent_frequency = None
|
106
|
+
max_message_buffer_length = None
|
107
|
+
min_message_buffer_length = None
|
97
108
|
max_turns = None
|
98
109
|
termination_token = None
|
99
110
|
manager_agent_id = None
|
@@ -114,11 +125,24 @@ class GroupManager:
|
|
114
125
|
sleeptime_agent_frequency = group_update.manager_config.sleeptime_agent_frequency
|
115
126
|
if sleeptime_agent_frequency and group.turns_counter is None:
|
116
127
|
group.turns_counter = -1
|
128
|
+
case ManagerType.voice_sleeptime:
|
129
|
+
manager_agent_id = group_update.manager_config.manager_agent_id
|
130
|
+
max_message_buffer_length = group_update.manager_config.max_message_buffer_length or group.max_message_buffer_length
|
131
|
+
min_message_buffer_length = group_update.manager_config.min_message_buffer_length or group.min_message_buffer_length
|
132
|
+
if sleeptime_agent_frequency and group.turns_counter is None:
|
133
|
+
group.turns_counter = -1
|
117
134
|
case _:
|
118
135
|
raise ValueError(f"Unsupported manager type: {group_update.manager_config.manager_type}")
|
119
136
|
|
137
|
+
# Safety check for buffer length range
|
138
|
+
self.ensure_buffer_length_range_valid(max_value=max_message_buffer_length, min_value=min_message_buffer_length)
|
139
|
+
|
120
140
|
if sleeptime_agent_frequency:
|
121
141
|
group.sleeptime_agent_frequency = sleeptime_agent_frequency
|
142
|
+
if max_message_buffer_length:
|
143
|
+
group.max_message_buffer_length = max_message_buffer_length
|
144
|
+
if min_message_buffer_length:
|
145
|
+
group.min_message_buffer_length = min_message_buffer_length
|
122
146
|
if max_turns:
|
123
147
|
group.max_turns = max_turns
|
124
148
|
if termination_token:
|
@@ -271,3 +295,40 @@ class GroupManager:
|
|
271
295
|
if manager_agent:
|
272
296
|
for block in blocks:
|
273
297
|
session.add(BlocksAgents(agent_id=manager_agent.id, block_id=block.id, block_label=block.label))
|
298
|
+
|
299
|
+
@staticmethod
|
300
|
+
def ensure_buffer_length_range_valid(
|
301
|
+
max_value: Optional[int],
|
302
|
+
min_value: Optional[int],
|
303
|
+
max_name: str = "max_message_buffer_length",
|
304
|
+
min_name: str = "min_message_buffer_length",
|
305
|
+
) -> None:
|
306
|
+
"""
|
307
|
+
1) Both-or-none: if one is set, the other must be set.
|
308
|
+
2) Both must be ints > 4.
|
309
|
+
3) max_value must be strictly greater than min_value.
|
310
|
+
"""
|
311
|
+
# 1) require both-or-none
|
312
|
+
if (max_value is None) != (min_value is None):
|
313
|
+
raise ValueError(
|
314
|
+
f"Both '{max_name}' and '{min_name}' must be provided together " f"(got {max_name}={max_value}, {min_name}={min_value})"
|
315
|
+
)
|
316
|
+
|
317
|
+
# no further checks if neither is provided
|
318
|
+
if max_value is None:
|
319
|
+
return
|
320
|
+
|
321
|
+
# 2) type & lower‐bound checks
|
322
|
+
if not isinstance(max_value, int) or not isinstance(min_value, int):
|
323
|
+
raise ValueError(
|
324
|
+
f"Both '{max_name}' and '{min_name}' must be integers "
|
325
|
+
f"(got {max_name}={type(max_value).__name__}, {min_name}={type(min_value).__name__})"
|
326
|
+
)
|
327
|
+
if max_value <= 4 or min_value <= 4:
|
328
|
+
raise ValueError(
|
329
|
+
f"Both '{max_name}' and '{min_name}' must be greater than 4 " f"(got {max_name}={max_value}, {min_name}={min_value})"
|
330
|
+
)
|
331
|
+
|
332
|
+
# 3) ordering
|
333
|
+
if max_value <= min_value:
|
334
|
+
raise ValueError(f"'{max_name}' must be greater than '{min_name}' " f"(got {max_name}={max_value} <= {min_name}={min_value})")
|
@@ -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
|
@@ -1,6 +1,7 @@
|
|
1
|
-
from typing import List, Optional
|
1
|
+
from typing import List, Optional, Union
|
2
2
|
|
3
3
|
from letta.orm.provider import Provider as ProviderModel
|
4
|
+
from letta.schemas.enums import ProviderType
|
4
5
|
from letta.schemas.providers import Provider as PydanticProvider
|
5
6
|
from letta.schemas.providers import ProviderUpdate
|
6
7
|
from letta.schemas.user import User as PydanticUser
|
@@ -18,6 +19,9 @@ class ProviderManager:
|
|
18
19
|
def create_provider(self, provider: PydanticProvider, actor: PydanticUser) -> PydanticProvider:
|
19
20
|
"""Create a new provider if it doesn't already exist."""
|
20
21
|
with self.session_maker() as session:
|
22
|
+
if provider.name == provider.provider_type.value:
|
23
|
+
raise ValueError("Provider name must be unique and different from provider type")
|
24
|
+
|
21
25
|
# Assign the organization id based on the actor
|
22
26
|
provider.organization_id = actor.organization_id
|
23
27
|
|
@@ -59,29 +63,36 @@ class ProviderManager:
|
|
59
63
|
session.commit()
|
60
64
|
|
61
65
|
@enforce_types
|
62
|
-
def list_providers(
|
66
|
+
def list_providers(
|
67
|
+
self,
|
68
|
+
name: Optional[str] = None,
|
69
|
+
provider_type: Optional[ProviderType] = None,
|
70
|
+
after: Optional[str] = None,
|
71
|
+
limit: Optional[int] = 50,
|
72
|
+
actor: PydanticUser = None,
|
73
|
+
) -> List[PydanticProvider]:
|
63
74
|
"""List all providers with optional pagination."""
|
75
|
+
filter_kwargs = {}
|
76
|
+
if name:
|
77
|
+
filter_kwargs["name"] = name
|
78
|
+
if provider_type:
|
79
|
+
filter_kwargs["provider_type"] = provider_type
|
64
80
|
with self.session_maker() as session:
|
65
81
|
providers = ProviderModel.list(
|
66
82
|
db_session=session,
|
67
83
|
after=after,
|
68
84
|
limit=limit,
|
69
85
|
actor=actor,
|
86
|
+
**filter_kwargs,
|
70
87
|
)
|
71
88
|
return [provider.to_pydantic() for provider in providers]
|
72
89
|
|
73
90
|
@enforce_types
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
if len(anthropic_provider) != 0:
|
78
|
-
return anthropic_provider[0].id
|
79
|
-
return None
|
91
|
+
def get_provider_id_from_name(self, provider_name: Union[str, None]) -> Optional[str]:
|
92
|
+
providers = self.list_providers(name=provider_name)
|
93
|
+
return providers[0].id if providers else None
|
80
94
|
|
81
95
|
@enforce_types
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
if len(anthropic_provider) != 0:
|
86
|
-
return anthropic_provider[0].api_key
|
87
|
-
return None
|
96
|
+
def get_override_key(self, provider_name: Union[str, None]) -> Optional[str]:
|
97
|
+
providers = self.list_providers(name=provider_name)
|
98
|
+
return providers[0].api_key if providers else None
|