letta-nightly 0.7.15.dev20250515104317__py3-none-any.whl → 0.7.17.dev20250516090339__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 +12 -0
- letta/agents/helpers.py +48 -5
- letta/agents/letta_agent.py +64 -28
- letta/agents/letta_agent_batch.py +44 -26
- letta/agents/voice_sleeptime_agent.py +6 -4
- letta/client/client.py +16 -1
- letta/constants.py +3 -0
- letta/functions/async_composio_toolset.py +1 -1
- letta/interfaces/anthropic_streaming_interface.py +40 -6
- letta/interfaces/openai_streaming_interface.py +303 -0
- letta/jobs/llm_batch_job_polling.py +6 -2
- letta/orm/agent.py +102 -1
- letta/orm/block.py +3 -0
- letta/orm/sqlalchemy_base.py +459 -158
- letta/schemas/agent.py +10 -2
- letta/schemas/block.py +3 -0
- letta/schemas/memory.py +7 -2
- letta/server/rest_api/routers/v1/agents.py +29 -27
- letta/server/rest_api/routers/v1/blocks.py +1 -1
- letta/server/rest_api/routers/v1/groups.py +2 -2
- letta/server/rest_api/routers/v1/messages.py +11 -11
- letta/server/rest_api/routers/v1/runs.py +2 -2
- letta/server/rest_api/routers/v1/tools.py +4 -4
- letta/server/rest_api/routers/v1/users.py +9 -9
- letta/server/rest_api/routers/v1/voice.py +1 -1
- letta/server/server.py +74 -0
- letta/services/agent_manager.py +417 -7
- letta/services/block_manager.py +12 -8
- letta/services/helpers/agent_manager_helper.py +19 -0
- letta/services/job_manager.py +99 -0
- letta/services/llm_batch_manager.py +28 -27
- letta/services/message_manager.py +66 -19
- letta/services/passage_manager.py +14 -0
- letta/services/tool_executor/tool_executor.py +19 -1
- letta/services/tool_manager.py +13 -3
- letta/services/user_manager.py +70 -0
- letta/types/__init__.py +0 -0
- {letta_nightly-0.7.15.dev20250515104317.dist-info → letta_nightly-0.7.17.dev20250516090339.dist-info}/METADATA +3 -3
- {letta_nightly-0.7.15.dev20250515104317.dist-info → letta_nightly-0.7.17.dev20250516090339.dist-info}/RECORD +43 -41
- {letta_nightly-0.7.15.dev20250515104317.dist-info → letta_nightly-0.7.17.dev20250516090339.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.15.dev20250515104317.dist-info → letta_nightly-0.7.17.dev20250516090339.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.15.dev20250515104317.dist-info → letta_nightly-0.7.17.dev20250516090339.dist-info}/entry_points.txt +0 -0
letta/schemas/agent.py
CHANGED
@@ -312,9 +312,17 @@ def get_prompt_template_for_agent_type(agent_type: Optional[AgentType] = None):
|
|
312
312
|
)
|
313
313
|
return (
|
314
314
|
"{% for block in blocks %}"
|
315
|
-
|
315
|
+
"<{{ block.label }}>\n"
|
316
|
+
"<description>\n"
|
317
|
+
"{{ block.description }}\n"
|
318
|
+
"</description>\n"
|
319
|
+
"<metadata>\n"
|
320
|
+
'{% if block.read_only %}read_only="true" {% endif %}chars_current="{{ block.value|length }}" chars_limit="{{ block.limit }}"\n'
|
321
|
+
"</metadata>\n"
|
322
|
+
"<value>\n"
|
316
323
|
"{{ block.value }}\n"
|
317
|
-
"</
|
324
|
+
"</value>\n"
|
325
|
+
"</{{ block.label }}>\n"
|
318
326
|
"{% if not loop.last %}\n{% endif %}"
|
319
327
|
"{% endfor %}"
|
320
328
|
)
|
letta/schemas/block.py
CHANGED
@@ -25,6 +25,9 @@ class BaseBlock(LettaBase, validate_assignment=True):
|
|
25
25
|
# context window label
|
26
26
|
label: Optional[str] = Field(None, description="Label of the block (e.g. 'human', 'persona') in the context window.")
|
27
27
|
|
28
|
+
# permissions of the agent
|
29
|
+
read_only: bool = Field(False, description="Whether the agent has read-only access to the block.")
|
30
|
+
|
28
31
|
# metadata
|
29
32
|
description: Optional[str] = Field(None, description="Description of the block.")
|
30
33
|
metadata: Optional[dict] = Field({}, description="Metadata of the block.")
|
letta/schemas/memory.py
CHANGED
@@ -69,9 +69,14 @@ class Memory(BaseModel, validate_assignment=True):
|
|
69
69
|
# Memory.template is a Jinja2 template for compiling memory module into a prompt string.
|
70
70
|
prompt_template: str = Field(
|
71
71
|
default="{% for block in blocks %}"
|
72
|
-
|
72
|
+
"<{{ block.label }}>\n"
|
73
|
+
"<metadata>"
|
74
|
+
'read_only="{{ block.read_only}}" chars_current="{{ block.value|length }}" chars_limit="{{ block.limit }}"'
|
75
|
+
"</metadata>"
|
76
|
+
"<value>"
|
73
77
|
"{{ block.value }}\n"
|
74
|
-
"</
|
78
|
+
"</value>"
|
79
|
+
"</{{ block.label }}>\n"
|
75
80
|
"{% if not loop.last %}\n{% endif %}"
|
76
81
|
"{% endfor %}",
|
77
82
|
description="Jinja2 template for compiling memory blocks into a prompt string",
|
@@ -44,7 +44,7 @@ logger = get_logger(__name__)
|
|
44
44
|
|
45
45
|
|
46
46
|
@router.get("/", response_model=List[AgentState], operation_id="list_agents")
|
47
|
-
def list_agents(
|
47
|
+
async def list_agents(
|
48
48
|
name: Optional[str] = Query(None, description="Name of the agent"),
|
49
49
|
tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
|
50
50
|
match_all_tags: bool = Query(
|
@@ -83,10 +83,10 @@ def list_agents(
|
|
83
83
|
"""
|
84
84
|
|
85
85
|
# Retrieve the actor (user) details
|
86
|
-
actor = server.user_manager.
|
86
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
87
87
|
|
88
88
|
# Call list_agents directly without unnecessary dict handling
|
89
|
-
return server.agent_manager.
|
89
|
+
return await server.agent_manager.list_agents_async(
|
90
90
|
actor=actor,
|
91
91
|
name=name,
|
92
92
|
before=before,
|
@@ -163,7 +163,7 @@ async def import_agent_serialized(
|
|
163
163
|
"""
|
164
164
|
Import a serialized agent file and recreate the agent in the system.
|
165
165
|
"""
|
166
|
-
actor = server.user_manager.
|
166
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
167
167
|
|
168
168
|
try:
|
169
169
|
serialized_data = await file.read()
|
@@ -223,7 +223,7 @@ class CreateAgentRequest(CreateAgent):
|
|
223
223
|
|
224
224
|
|
225
225
|
@router.post("/", response_model=AgentState, operation_id="create_agent")
|
226
|
-
def create_agent(
|
226
|
+
async def create_agent(
|
227
227
|
agent: CreateAgentRequest = Body(...),
|
228
228
|
server: "SyncServer" = Depends(get_letta_server),
|
229
229
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -233,23 +233,23 @@ def create_agent(
|
|
233
233
|
Create a new agent with the specified configuration.
|
234
234
|
"""
|
235
235
|
try:
|
236
|
-
actor = server.user_manager.
|
237
|
-
return server.
|
236
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
237
|
+
return await server.create_agent_async(agent, actor=actor)
|
238
238
|
except Exception as e:
|
239
239
|
traceback.print_exc()
|
240
240
|
raise HTTPException(status_code=500, detail=str(e))
|
241
241
|
|
242
242
|
|
243
243
|
@router.patch("/{agent_id}", response_model=AgentState, operation_id="modify_agent")
|
244
|
-
def modify_agent(
|
244
|
+
async def modify_agent(
|
245
245
|
agent_id: str,
|
246
246
|
update_agent: UpdateAgent = Body(...),
|
247
247
|
server: "SyncServer" = Depends(get_letta_server),
|
248
248
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
249
249
|
):
|
250
250
|
"""Update an existing agent"""
|
251
|
-
actor = server.user_manager.
|
252
|
-
return server.
|
251
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
252
|
+
return await server.update_agent_async(agent_id=agent_id, request=update_agent, actor=actor)
|
253
253
|
|
254
254
|
|
255
255
|
@router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="list_agent_tools")
|
@@ -333,7 +333,7 @@ def detach_source(
|
|
333
333
|
|
334
334
|
|
335
335
|
@router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
|
336
|
-
def retrieve_agent(
|
336
|
+
async def retrieve_agent(
|
337
337
|
agent_id: str,
|
338
338
|
server: "SyncServer" = Depends(get_letta_server),
|
339
339
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -344,7 +344,7 @@ def retrieve_agent(
|
|
344
344
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
345
345
|
|
346
346
|
try:
|
347
|
-
return server.agent_manager.
|
347
|
+
return await server.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
348
348
|
except NoResultFound as e:
|
349
349
|
raise HTTPException(status_code=404, detail=str(e))
|
350
350
|
|
@@ -414,7 +414,7 @@ def retrieve_block(
|
|
414
414
|
|
415
415
|
|
416
416
|
@router.get("/{agent_id}/core-memory/blocks", response_model=List[Block], operation_id="list_core_memory_blocks")
|
417
|
-
def list_blocks(
|
417
|
+
async def list_blocks(
|
418
418
|
agent_id: str,
|
419
419
|
server: "SyncServer" = Depends(get_letta_server),
|
420
420
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -424,7 +424,7 @@ def list_blocks(
|
|
424
424
|
"""
|
425
425
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
426
426
|
try:
|
427
|
-
agent = server.agent_manager.
|
427
|
+
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
|
428
428
|
return agent.memory.blocks
|
429
429
|
except NoResultFound as e:
|
430
430
|
raise HTTPException(status_code=404, detail=str(e))
|
@@ -628,12 +628,12 @@ async def send_message(
|
|
628
628
|
Process a user message and return the agent's response.
|
629
629
|
This endpoint accepts a message from a user and processes it through the agent.
|
630
630
|
"""
|
631
|
-
actor = server.user_manager.
|
631
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
632
632
|
# TODO: This is redundant, remove soon
|
633
|
-
agent = server.agent_manager.
|
633
|
+
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
|
634
634
|
agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
|
635
|
-
experimental_header = request_obj.headers.get("
|
636
|
-
feature_enabled = settings.use_experimental or experimental_header
|
635
|
+
experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
|
636
|
+
feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
|
637
637
|
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai", "google_vertex", "google_ai"]
|
638
638
|
|
639
639
|
if agent_eligible and feature_enabled and model_compatible:
|
@@ -646,7 +646,7 @@ async def send_message(
|
|
646
646
|
actor=actor,
|
647
647
|
)
|
648
648
|
|
649
|
-
result = await experimental_agent.step(request.messages, max_steps=10)
|
649
|
+
result = await experimental_agent.step(request.messages, max_steps=10, use_assistant_message=request.use_assistant_message)
|
650
650
|
else:
|
651
651
|
result = await server.send_message_to_agent(
|
652
652
|
agent_id=agent_id,
|
@@ -686,15 +686,15 @@ async def send_message_streaming(
|
|
686
686
|
It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
|
687
687
|
"""
|
688
688
|
request_start_timestamp_ns = get_utc_timestamp_ns()
|
689
|
-
actor = server.user_manager.
|
689
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
690
690
|
# TODO: This is redundant, remove soon
|
691
|
-
agent = server.agent_manager.
|
691
|
+
agent = await server.agent_manager.get_agent_by_id_async(agent_id, actor)
|
692
692
|
agent_eligible = not agent.enable_sleeptime and not agent.multi_agent_group and agent.agent_type != AgentType.sleeptime_agent
|
693
|
-
experimental_header = request_obj.headers.get("
|
694
|
-
feature_enabled = settings.use_experimental or experimental_header
|
693
|
+
experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
|
694
|
+
feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
|
695
695
|
model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
|
696
696
|
|
697
|
-
if agent_eligible and feature_enabled and model_compatible:
|
697
|
+
if agent_eligible and feature_enabled and model_compatible and request.stream_tokens:
|
698
698
|
experimental_agent = LettaAgent(
|
699
699
|
agent_id=agent_id,
|
700
700
|
message_manager=server.message_manager,
|
@@ -705,7 +705,9 @@ async def send_message_streaming(
|
|
705
705
|
)
|
706
706
|
|
707
707
|
result = StreamingResponse(
|
708
|
-
experimental_agent.step_stream(
|
708
|
+
experimental_agent.step_stream(
|
709
|
+
request.messages, max_steps=10, use_assistant_message=request.use_assistant_message, stream_tokens=request.stream_tokens
|
710
|
+
),
|
709
711
|
media_type="text/event-stream",
|
710
712
|
)
|
711
713
|
else:
|
@@ -784,7 +786,7 @@ async def send_message_async(
|
|
784
786
|
Asynchronously process a user message and return a run object.
|
785
787
|
The actual processing happens in the background, and the status can be checked using the run ID.
|
786
788
|
"""
|
787
|
-
actor = server.user_manager.
|
789
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
788
790
|
|
789
791
|
# Create a new job
|
790
792
|
run = Run(
|
@@ -838,6 +840,6 @@ async def list_agent_groups(
|
|
838
840
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
839
841
|
):
|
840
842
|
"""Lists the groups for an agent"""
|
841
|
-
actor = server.user_manager.
|
843
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
842
844
|
print("in list agents with manager_type", manager_type)
|
843
845
|
return server.agent_manager.list_groups(agent_id=agent_id, manager_type=manager_type, actor=actor)
|
@@ -26,7 +26,7 @@ async def list_blocks(
|
|
26
26
|
server: SyncServer = Depends(get_letta_server),
|
27
27
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
28
28
|
):
|
29
|
-
actor = server.user_manager.
|
29
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
30
30
|
return await server.block_manager.get_blocks_async(
|
31
31
|
actor=actor,
|
32
32
|
label=label,
|
@@ -135,7 +135,7 @@ async def send_group_message(
|
|
135
135
|
Process a user message and return the group's response.
|
136
136
|
This endpoint accepts a message from a user and processes it through through agents in the group based on the specified pattern
|
137
137
|
"""
|
138
|
-
actor = server.user_manager.
|
138
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
139
139
|
result = await server.send_group_message_to_agent(
|
140
140
|
group_id=group_id,
|
141
141
|
actor=actor,
|
@@ -174,7 +174,7 @@ async def send_group_message_streaming(
|
|
174
174
|
This endpoint accepts a message from a user and processes it through agents in the group based on the specified pattern.
|
175
175
|
It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
|
176
176
|
"""
|
177
|
-
actor = server.user_manager.
|
177
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
178
178
|
result = await server.send_group_message_to_agent(
|
179
179
|
group_id=group_id,
|
180
180
|
actor=actor,
|
@@ -52,7 +52,7 @@ async def create_messages_batch(
|
|
52
52
|
detail=f"Server misconfiguration: LETTA_ENABLE_BATCH_JOB_POLLING is set to False.",
|
53
53
|
)
|
54
54
|
|
55
|
-
actor = server.user_manager.
|
55
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
56
56
|
batch_job = BatchJob(
|
57
57
|
user_id=actor.id,
|
58
58
|
status=JobStatus.running,
|
@@ -63,7 +63,7 @@ async def create_messages_batch(
|
|
63
63
|
)
|
64
64
|
|
65
65
|
try:
|
66
|
-
batch_job = server.job_manager.
|
66
|
+
batch_job = await server.job_manager.create_job_async(pydantic_job=batch_job, actor=actor)
|
67
67
|
|
68
68
|
# create the batch runner
|
69
69
|
batch_runner = LettaAgentBatch(
|
@@ -86,7 +86,7 @@ async def create_messages_batch(
|
|
86
86
|
traceback.print_exc()
|
87
87
|
|
88
88
|
# mark job as failed
|
89
|
-
server.job_manager.
|
89
|
+
await server.job_manager.update_job_by_id_async(job_id=batch_job.id, job_update=JobUpdate(status=JobStatus.failed), actor=actor)
|
90
90
|
raise
|
91
91
|
return batch_job
|
92
92
|
|
@@ -100,10 +100,10 @@ async def retrieve_batch_run(
|
|
100
100
|
"""
|
101
101
|
Get the status of a batch run.
|
102
102
|
"""
|
103
|
-
actor = server.user_manager.
|
103
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
104
104
|
|
105
105
|
try:
|
106
|
-
job = server.job_manager.
|
106
|
+
job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
|
107
107
|
return BatchJob.from_job(job)
|
108
108
|
except NoResultFound:
|
109
109
|
raise HTTPException(status_code=404, detail="Batch not found")
|
@@ -118,7 +118,7 @@ async def list_batch_runs(
|
|
118
118
|
List all batch runs.
|
119
119
|
"""
|
120
120
|
# TODO: filter
|
121
|
-
actor = server.user_manager.
|
121
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
122
122
|
|
123
123
|
jobs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.BATCH)
|
124
124
|
return [BatchJob.from_job(job) for job in jobs]
|
@@ -150,11 +150,11 @@ async def list_batch_messages(
|
|
150
150
|
- For subsequent pages, use the ID of the last message from the previous response as the cursor
|
151
151
|
- Results will include messages before/after the cursor based on sort_descending
|
152
152
|
"""
|
153
|
-
actor = server.user_manager.
|
153
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
154
154
|
|
155
155
|
# First, verify the batch job exists and the user has access to it
|
156
156
|
try:
|
157
|
-
job = server.job_manager.
|
157
|
+
job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
|
158
158
|
BatchJob.from_job(job)
|
159
159
|
except NoResultFound:
|
160
160
|
raise HTTPException(status_code=404, detail="Batch not found")
|
@@ -177,11 +177,11 @@ async def cancel_batch_run(
|
|
177
177
|
"""
|
178
178
|
Cancel a batch run.
|
179
179
|
"""
|
180
|
-
actor = server.user_manager.
|
180
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
181
181
|
|
182
182
|
try:
|
183
|
-
job = server.job_manager.
|
184
|
-
job = server.job_manager.
|
183
|
+
job = await server.job_manager.get_job_by_id_async(job_id=batch_id, actor=actor)
|
184
|
+
job = await server.job_manager.update_job_by_id_async(job_id=job.id, job_update=JobUpdate(status=JobStatus.cancelled), actor=actor)
|
185
185
|
|
186
186
|
# Get related llm batch jobs
|
187
187
|
llm_batch_jobs = server.batch_manager.list_llm_batch_jobs(letta_batch_id=job.id, actor=actor)
|
@@ -115,7 +115,7 @@ async def list_run_messages(
|
|
115
115
|
if order not in ["asc", "desc"]:
|
116
116
|
raise HTTPException(status_code=400, detail="Order must be 'asc' or 'desc'")
|
117
117
|
|
118
|
-
actor = server.user_manager.
|
118
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
119
119
|
|
120
120
|
try:
|
121
121
|
messages = server.job_manager.get_run_messages(
|
@@ -182,7 +182,7 @@ async def list_run_steps(
|
|
182
182
|
if order not in ["asc", "desc"]:
|
183
183
|
raise HTTPException(status_code=400, detail="Order must be 'asc' or 'desc'")
|
184
184
|
|
185
|
-
actor = server.user_manager.
|
185
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
186
186
|
|
187
187
|
try:
|
188
188
|
steps = server.job_manager.get_job_steps(
|
@@ -76,7 +76,7 @@ def retrieve_tool(
|
|
76
76
|
|
77
77
|
|
78
78
|
@router.get("/", response_model=List[Tool], operation_id="list_tools")
|
79
|
-
def list_tools(
|
79
|
+
async def list_tools(
|
80
80
|
after: Optional[str] = None,
|
81
81
|
limit: Optional[int] = 50,
|
82
82
|
name: Optional[str] = None,
|
@@ -87,11 +87,11 @@ def list_tools(
|
|
87
87
|
Get a list of all tools available to agents belonging to the org of the user
|
88
88
|
"""
|
89
89
|
try:
|
90
|
-
actor = server.user_manager.
|
90
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
91
91
|
if name is not None:
|
92
|
-
tool = server.tool_manager.
|
92
|
+
tool = await server.tool_manager.get_tool_by_name_async(tool_name=name, actor=actor)
|
93
93
|
return [tool] if tool else []
|
94
|
-
return server.tool_manager.
|
94
|
+
return await server.tool_manager.list_tools_async(actor=actor, after=after, limit=limit)
|
95
95
|
except Exception as e:
|
96
96
|
# Log or print the full exception here for debugging
|
97
97
|
print(f"Error occurred: {e}")
|
@@ -14,7 +14,7 @@ router = APIRouter(prefix="/users", tags=["users", "admin"])
|
|
14
14
|
|
15
15
|
|
16
16
|
@router.get("/", tags=["admin"], response_model=List[User], operation_id="list_users")
|
17
|
-
def list_users(
|
17
|
+
async def list_users(
|
18
18
|
after: Optional[str] = Query(None),
|
19
19
|
limit: Optional[int] = Query(50),
|
20
20
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -23,7 +23,7 @@ def list_users(
|
|
23
23
|
Get a list of all users in the database
|
24
24
|
"""
|
25
25
|
try:
|
26
|
-
users = server.user_manager.
|
26
|
+
users = await server.user_manager.list_actors_async(after=after, limit=limit)
|
27
27
|
except HTTPException:
|
28
28
|
raise
|
29
29
|
except Exception as e:
|
@@ -32,7 +32,7 @@ def list_users(
|
|
32
32
|
|
33
33
|
|
34
34
|
@router.post("/", tags=["admin"], response_model=User, operation_id="create_user")
|
35
|
-
def create_user(
|
35
|
+
async def create_user(
|
36
36
|
request: UserCreate = Body(...),
|
37
37
|
server: "SyncServer" = Depends(get_letta_server),
|
38
38
|
):
|
@@ -40,33 +40,33 @@ def create_user(
|
|
40
40
|
Create a new user in the database
|
41
41
|
"""
|
42
42
|
user = User(**request.model_dump())
|
43
|
-
user = server.user_manager.
|
43
|
+
user = await server.user_manager.create_actor_async(user)
|
44
44
|
return user
|
45
45
|
|
46
46
|
|
47
47
|
@router.put("/", tags=["admin"], response_model=User, operation_id="update_user")
|
48
|
-
def update_user(
|
48
|
+
async def update_user(
|
49
49
|
user: UserUpdate = Body(...),
|
50
50
|
server: "SyncServer" = Depends(get_letta_server),
|
51
51
|
):
|
52
52
|
"""
|
53
53
|
Update a user in the database
|
54
54
|
"""
|
55
|
-
user = server.user_manager.
|
55
|
+
user = await server.user_manager.update_actor_async(user)
|
56
56
|
return user
|
57
57
|
|
58
58
|
|
59
59
|
@router.delete("/", tags=["admin"], response_model=User, operation_id="delete_user")
|
60
|
-
def delete_user(
|
60
|
+
async def delete_user(
|
61
61
|
user_id: str = Query(..., description="The user_id key to be deleted."),
|
62
62
|
server: "SyncServer" = Depends(get_letta_server),
|
63
63
|
):
|
64
64
|
# TODO make a soft deletion, instead of a hard deletion
|
65
65
|
try:
|
66
|
-
user = server.user_manager.
|
66
|
+
user = await server.user_manager.get_actor_by_id_async(actor_id=user_id)
|
67
67
|
if user is None:
|
68
68
|
raise HTTPException(status_code=404, detail=f"User does not exist")
|
69
|
-
server.user_manager.
|
69
|
+
await server.user_manager.delete_actor_by_id_async(user_id=user_id)
|
70
70
|
except HTTPException:
|
71
71
|
raise
|
72
72
|
except Exception as e:
|
@@ -36,7 +36,7 @@ async def create_voice_chat_completions(
|
|
36
36
|
server: "SyncServer" = Depends(get_letta_server),
|
37
37
|
user_id: Optional[str] = Header(None, alias="user_id"),
|
38
38
|
):
|
39
|
-
actor = server.user_manager.
|
39
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=user_id)
|
40
40
|
|
41
41
|
# Create OpenAI async client
|
42
42
|
client = openai.AsyncClient(
|
letta/server/server.py
CHANGED
@@ -794,6 +794,54 @@ class SyncServer(Server):
|
|
794
794
|
|
795
795
|
return main_agent
|
796
796
|
|
797
|
+
@trace_method
|
798
|
+
async def create_agent_async(
|
799
|
+
self,
|
800
|
+
request: CreateAgent,
|
801
|
+
actor: User,
|
802
|
+
# interface
|
803
|
+
interface: Union[AgentInterface, None] = None,
|
804
|
+
) -> AgentState:
|
805
|
+
if request.llm_config is None:
|
806
|
+
if request.model is None:
|
807
|
+
raise ValueError("Must specify either model or llm_config in request")
|
808
|
+
config_params = {
|
809
|
+
"handle": request.model,
|
810
|
+
"context_window_limit": request.context_window_limit,
|
811
|
+
"max_tokens": request.max_tokens,
|
812
|
+
"max_reasoning_tokens": request.max_reasoning_tokens,
|
813
|
+
"enable_reasoner": request.enable_reasoner,
|
814
|
+
}
|
815
|
+
log_event(name="start get_cached_llm_config", attributes=config_params)
|
816
|
+
request.llm_config = self.get_cached_llm_config(actor=actor, **config_params)
|
817
|
+
log_event(name="end get_cached_llm_config", attributes=config_params)
|
818
|
+
|
819
|
+
if request.embedding_config is None:
|
820
|
+
if request.embedding is None:
|
821
|
+
raise ValueError("Must specify either embedding or embedding_config in request")
|
822
|
+
embedding_config_params = {
|
823
|
+
"handle": request.embedding,
|
824
|
+
"embedding_chunk_size": request.embedding_chunk_size or constants.DEFAULT_EMBEDDING_CHUNK_SIZE,
|
825
|
+
}
|
826
|
+
log_event(name="start get_cached_embedding_config", attributes=embedding_config_params)
|
827
|
+
request.embedding_config = self.get_cached_embedding_config(actor=actor, **embedding_config_params)
|
828
|
+
log_event(name="end get_cached_embedding_config", attributes=embedding_config_params)
|
829
|
+
|
830
|
+
log_event(name="start create_agent db")
|
831
|
+
main_agent = await self.agent_manager.create_agent_async(
|
832
|
+
agent_create=request,
|
833
|
+
actor=actor,
|
834
|
+
)
|
835
|
+
log_event(name="end create_agent db")
|
836
|
+
|
837
|
+
if request.enable_sleeptime:
|
838
|
+
if request.agent_type == AgentType.voice_convo_agent:
|
839
|
+
main_agent = self.create_voice_sleeptime_agent(main_agent=main_agent, actor=actor)
|
840
|
+
else:
|
841
|
+
main_agent = self.create_sleeptime_agent(main_agent=main_agent, actor=actor)
|
842
|
+
|
843
|
+
return main_agent
|
844
|
+
|
797
845
|
def update_agent(
|
798
846
|
self,
|
799
847
|
agent_id: str,
|
@@ -820,6 +868,32 @@ class SyncServer(Server):
|
|
820
868
|
actor=actor,
|
821
869
|
)
|
822
870
|
|
871
|
+
async def update_agent_async(
|
872
|
+
self,
|
873
|
+
agent_id: str,
|
874
|
+
request: UpdateAgent,
|
875
|
+
actor: User,
|
876
|
+
) -> AgentState:
|
877
|
+
if request.model is not None:
|
878
|
+
request.llm_config = self.get_llm_config_from_handle(handle=request.model, actor=actor)
|
879
|
+
|
880
|
+
if request.embedding is not None:
|
881
|
+
request.embedding_config = self.get_embedding_config_from_handle(handle=request.embedding, actor=actor)
|
882
|
+
|
883
|
+
if request.enable_sleeptime:
|
884
|
+
agent = self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
|
885
|
+
if agent.multi_agent_group is None:
|
886
|
+
if agent.agent_type == AgentType.voice_convo_agent:
|
887
|
+
self.create_voice_sleeptime_agent(main_agent=agent, actor=actor)
|
888
|
+
else:
|
889
|
+
self.create_sleeptime_agent(main_agent=agent, actor=actor)
|
890
|
+
|
891
|
+
return await self.agent_manager.update_agent_async(
|
892
|
+
agent_id=agent_id,
|
893
|
+
agent_update=request,
|
894
|
+
actor=actor,
|
895
|
+
)
|
896
|
+
|
823
897
|
def create_sleeptime_agent(self, main_agent: AgentState, actor: User) -> AgentState:
|
824
898
|
request = CreateAgent(
|
825
899
|
name=main_agent.name + "-sleeptime",
|