letta-nightly 0.6.3.dev20241213104231__py3-none-any.whl → 0.6.4.dev20241214104034__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.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (63) hide show
  1. letta/__init__.py +2 -2
  2. letta/agent.py +54 -45
  3. letta/chat_only_agent.py +6 -8
  4. letta/cli/cli.py +2 -10
  5. letta/client/client.py +121 -138
  6. letta/config.py +0 -161
  7. letta/main.py +3 -8
  8. letta/memory.py +3 -14
  9. letta/o1_agent.py +1 -5
  10. letta/offline_memory_agent.py +2 -6
  11. letta/orm/__init__.py +2 -0
  12. letta/orm/agent.py +109 -0
  13. letta/orm/agents_tags.py +10 -18
  14. letta/orm/block.py +29 -4
  15. letta/orm/blocks_agents.py +5 -11
  16. letta/orm/custom_columns.py +152 -0
  17. letta/orm/message.py +3 -38
  18. letta/orm/organization.py +2 -7
  19. letta/orm/passage.py +10 -32
  20. letta/orm/source.py +5 -25
  21. letta/orm/sources_agents.py +13 -0
  22. letta/orm/sqlalchemy_base.py +54 -30
  23. letta/orm/tool.py +1 -19
  24. letta/orm/tools_agents.py +7 -24
  25. letta/orm/user.py +3 -4
  26. letta/schemas/agent.py +48 -65
  27. letta/schemas/memory.py +2 -1
  28. letta/schemas/sandbox_config.py +12 -1
  29. letta/server/rest_api/app.py +0 -5
  30. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
  31. letta/server/rest_api/routers/v1/agents.py +99 -78
  32. letta/server/rest_api/routers/v1/blocks.py +22 -25
  33. letta/server/rest_api/routers/v1/jobs.py +4 -4
  34. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
  35. letta/server/rest_api/routers/v1/sources.py +12 -12
  36. letta/server/rest_api/routers/v1/tools.py +35 -15
  37. letta/server/rest_api/routers/v1/users.py +0 -46
  38. letta/server/server.py +172 -718
  39. letta/server/ws_api/server.py +0 -5
  40. letta/services/agent_manager.py +405 -0
  41. letta/services/block_manager.py +13 -21
  42. letta/services/helpers/agent_manager_helper.py +90 -0
  43. letta/services/organization_manager.py +0 -1
  44. letta/services/passage_manager.py +62 -62
  45. letta/services/sandbox_config_manager.py +3 -3
  46. letta/services/source_manager.py +22 -1
  47. letta/services/tool_execution_sandbox.py +4 -4
  48. letta/services/user_manager.py +11 -6
  49. letta/utils.py +2 -2
  50. {letta_nightly-0.6.3.dev20241213104231.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/METADATA +1 -1
  51. {letta_nightly-0.6.3.dev20241213104231.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/RECORD +54 -58
  52. letta/metadata.py +0 -407
  53. letta/schemas/agents_tags.py +0 -33
  54. letta/schemas/api_key.py +0 -21
  55. letta/schemas/blocks_agents.py +0 -32
  56. letta/schemas/tools_agents.py +0 -32
  57. letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
  58. letta/services/agents_tags_manager.py +0 -64
  59. letta/services/blocks_agents_manager.py +0 -106
  60. letta/services/tools_agents_manager.py +0 -94
  61. {letta_nightly-0.6.3.dev20241213104231.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/LICENSE +0 -0
  62. {letta_nightly-0.6.3.dev20241213104231.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/WHEEL +0 -0
  63. {letta_nightly-0.6.3.dev20241213104231.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,8 @@ from fastapi.responses import JSONResponse, StreamingResponse
17
17
  from pydantic import Field
18
18
 
19
19
  from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
20
- from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
20
+ from letta.orm.errors import NoResultFound
21
+ from letta.schemas.agent import AgentState, CreateAgent, UpdateAgent
21
22
  from letta.schemas.block import ( # , BlockLabelUpdate, BlockLimitUpdate
22
23
  Block,
23
24
  BlockUpdate,
@@ -54,23 +55,38 @@ from letta.server.server import SyncServer
54
55
  router = APIRouter(prefix="/agents", tags=["agents"])
55
56
 
56
57
 
58
+ # TODO: This should be paginated
57
59
  @router.get("/", response_model=List[AgentState], operation_id="list_agents")
58
60
  def list_agents(
59
61
  name: Optional[str] = Query(None, description="Name of the agent"),
60
62
  tags: Optional[List[str]] = Query(None, description="List of tags to filter agents by"),
63
+ match_all_tags: bool = Query(
64
+ False,
65
+ description="If True, only returns agents that match ALL given tags. Otherwise, return agents that have ANY of the passed in tags.",
66
+ ),
61
67
  server: "SyncServer" = Depends(get_letta_server),
62
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
68
+ user_id: Optional[str] = Header(None, alias="user_id"),
69
+ # Extract user_id from header, default to None if not present
63
70
  ):
64
71
  """
65
72
  List all agents associated with a given user.
66
73
  This endpoint retrieves a list of all agents and their configurations associated with the specified user ID.
67
74
  """
68
- actor = server.get_user_or_default(user_id=user_id)
69
-
70
- agents = server.list_agents(user_id=actor.id, tags=tags)
71
- # TODO: move this logic to the ORM
72
- if name:
73
- agents = [a for a in agents if a.name == name]
75
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
76
+
77
+ # Use dictionary comprehension to build kwargs dynamically
78
+ kwargs = {
79
+ key: value
80
+ for key, value in {
81
+ "tags": tags,
82
+ "match_all_tags": match_all_tags,
83
+ "name": name,
84
+ }.items()
85
+ if value is not None
86
+ }
87
+
88
+ # Call list_agents with the dynamic kwargs
89
+ agents = server.agent_manager.list_agents(actor=actor, **kwargs)
74
90
  return agents
75
91
 
76
92
 
@@ -83,7 +99,7 @@ def get_agent_context_window(
83
99
  """
84
100
  Retrieve the context window of a specific agent.
85
101
  """
86
- actor = server.get_user_or_default(user_id=user_id)
102
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
87
103
 
88
104
  return server.get_agent_context_window(user_id=actor.id, agent_id=agent_id)
89
105
 
@@ -106,20 +122,20 @@ def create_agent(
106
122
  """
107
123
  Create a new agent with the specified configuration.
108
124
  """
109
- actor = server.get_user_or_default(user_id=user_id)
125
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
110
126
  return server.create_agent(agent, actor=actor)
111
127
 
112
128
 
113
129
  @router.patch("/{agent_id}", response_model=AgentState, operation_id="update_agent")
114
130
  def update_agent(
115
131
  agent_id: str,
116
- update_agent: UpdateAgentState = Body(...),
132
+ update_agent: UpdateAgent = Body(...),
117
133
  server: "SyncServer" = Depends(get_letta_server),
118
134
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
119
135
  ):
120
136
  """Update an exsiting agent"""
121
- actor = server.get_user_or_default(user_id=user_id)
122
- return server.update_agent(update_agent, actor=actor)
137
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
138
+ return server.update_agent(agent_id, update_agent, actor=actor)
123
139
 
124
140
 
125
141
  @router.get("/{agent_id}/tools", response_model=List[Tool], operation_id="get_tools_from_agent")
@@ -129,7 +145,7 @@ def get_tools_from_agent(
129
145
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
130
146
  ):
131
147
  """Get tools from an existing agent"""
132
- actor = server.get_user_or_default(user_id=user_id)
148
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
133
149
  return server.get_tools_from_agent(agent_id=agent_id, user_id=actor.id)
134
150
 
135
151
 
@@ -141,7 +157,7 @@ def add_tool_to_agent(
141
157
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
142
158
  ):
143
159
  """Add tools to an existing agent"""
144
- actor = server.get_user_or_default(user_id=user_id)
160
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
145
161
  return server.add_tool_to_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
146
162
 
147
163
 
@@ -153,7 +169,7 @@ def remove_tool_from_agent(
153
169
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
154
170
  ):
155
171
  """Add tools to an existing agent"""
156
- actor = server.get_user_or_default(user_id=user_id)
172
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
157
173
  return server.remove_tool_from_agent(agent_id=agent_id, tool_id=tool_id, user_id=actor.id)
158
174
 
159
175
 
@@ -166,13 +182,12 @@ def get_agent_state(
166
182
  """
167
183
  Get the state of the agent.
168
184
  """
169
- actor = server.get_user_or_default(user_id=user_id)
185
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
170
186
 
171
- if not server.ms.get_agent(user_id=actor.id, agent_id=agent_id):
172
- # agent does not exist
173
- raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
174
-
175
- return server.get_agent_state(user_id=actor.id, agent_id=agent_id)
187
+ try:
188
+ return server.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor)
189
+ except NoResultFound as e:
190
+ raise HTTPException(status_code=404, detail=str(e))
176
191
 
177
192
 
178
193
  @router.delete("/{agent_id}", response_model=AgentState, operation_id="delete_agent")
@@ -184,38 +199,37 @@ def delete_agent(
184
199
  """
185
200
  Delete an agent.
186
201
  """
187
- actor = server.get_user_or_default(user_id=user_id)
188
-
189
- agent = server.get_agent(agent_id)
190
- if not agent:
191
- raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found.")
192
-
193
- server.delete_agent(user_id=actor.id, agent_id=agent_id)
194
- return agent
202
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
203
+ try:
204
+ return server.agent_manager.delete_agent(agent_id=agent_id, actor=actor)
205
+ except NoResultFound:
206
+ raise HTTPException(status_code=404, detail=f"Agent agent_id={agent_id} not found for user_id={actor.id}.")
195
207
 
196
208
 
197
209
  @router.get("/{agent_id}/sources", response_model=List[Source], operation_id="get_agent_sources")
198
210
  def get_agent_sources(
199
211
  agent_id: str,
200
212
  server: "SyncServer" = Depends(get_letta_server),
213
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
201
214
  ):
202
215
  """
203
216
  Get the sources associated with an agent.
204
217
  """
205
-
206
- return server.list_attached_sources(agent_id)
218
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
219
+ return server.agent_manager.list_attached_sources(agent_id=agent_id, actor=actor)
207
220
 
208
221
 
209
222
  @router.get("/{agent_id}/memory/messages", response_model=List[Message], operation_id="list_agent_in_context_messages")
210
223
  def get_agent_in_context_messages(
211
224
  agent_id: str,
212
225
  server: "SyncServer" = Depends(get_letta_server),
226
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
213
227
  ):
214
228
  """
215
229
  Retrieve the messages in the context of a specific agent.
216
230
  """
217
-
218
- return server.get_in_context_messages(agent_id=agent_id)
231
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
232
+ return server.get_in_context_messages(agent_id=agent_id, actor=actor)
219
233
 
220
234
 
221
235
  # TODO: remove? can also get with agent blocks
@@ -223,13 +237,15 @@ def get_agent_in_context_messages(
223
237
  def get_agent_memory(
224
238
  agent_id: str,
225
239
  server: "SyncServer" = Depends(get_letta_server),
240
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
226
241
  ):
227
242
  """
228
243
  Retrieve the memory state of a specific agent.
229
244
  This endpoint fetches the current memory state of the agent identified by the user ID and agent ID.
230
245
  """
246
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
231
247
 
232
- return server.get_agent_memory(agent_id=agent_id)
248
+ return server.get_agent_memory(agent_id=agent_id, actor=actor)
233
249
 
234
250
 
235
251
  @router.get("/{agent_id}/memory/block/{block_label}", response_model=Block, operation_id="get_agent_memory_block")
@@ -242,10 +258,12 @@ def get_agent_memory_block(
242
258
  """
243
259
  Retrieve a memory block from an agent.
244
260
  """
245
- actor = server.get_user_or_default(user_id=user_id)
261
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
246
262
 
247
- block_id = server.blocks_agents_manager.get_block_id_for_label(agent_id=agent_id, block_label=block_label)
248
- return server.block_manager.get_block_by_id(block_id, actor=actor)
263
+ try:
264
+ return server.agent_manager.get_block_with_label(agent_id=agent_id, block_label=block_label, actor=actor)
265
+ except NoResultFound as e:
266
+ raise HTTPException(status_code=404, detail=str(e))
249
267
 
250
268
 
251
269
  @router.get("/{agent_id}/memory/block", response_model=List[Block], operation_id="get_agent_memory_blocks")
@@ -257,9 +275,12 @@ def get_agent_memory_blocks(
257
275
  """
258
276
  Retrieve the memory blocks of a specific agent.
259
277
  """
260
- actor = server.get_user_or_default(user_id=user_id)
261
- block_ids = server.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
262
- return [server.block_manager.get_block_by_id(block_id, actor=actor) for block_id in block_ids]
278
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
279
+ try:
280
+ agent = server.agent_manager.get_agent_by_id(agent_id, actor=actor)
281
+ return agent.memory.blocks
282
+ except NoResultFound as e:
283
+ raise HTTPException(status_code=404, detail=str(e))
263
284
 
264
285
 
265
286
  @router.post("/{agent_id}/memory/block", response_model=Memory, operation_id="add_agent_memory_block")
@@ -272,16 +293,17 @@ def add_agent_memory_block(
272
293
  """
273
294
  Creates a memory block and links it to the agent.
274
295
  """
275
- actor = server.get_user_or_default(user_id=user_id)
296
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
276
297
 
277
298
  # Copied from POST /blocks
299
+ # TODO: Should have block_manager accept only CreateBlock
300
+ # TODO: This will be possible once we move ID creation to the ORM
278
301
  block_req = Block(**create_block.model_dump())
279
302
  block = server.block_manager.create_or_update_block(actor=actor, block=block_req)
280
303
 
281
304
  # Link the block to the agent
282
- updated_memory = server.link_block_to_agent_memory(user_id=actor.id, agent_id=agent_id, block_id=block.id)
283
-
284
- return updated_memory
305
+ agent = server.agent_manager.attach_block(agent_id=agent_id, block_id=block.id, actor=actor)
306
+ return agent.memory
285
307
 
286
308
 
287
309
  @router.delete("/{agent_id}/memory/block/{block_label}", response_model=Memory, operation_id="remove_agent_memory_block_by_label")
@@ -296,56 +318,56 @@ def remove_agent_memory_block(
296
318
  """
297
319
  Removes a memory block from an agent by unlnking it. If the block is not linked to any other agent, it is deleted.
298
320
  """
299
- actor = server.get_user_or_default(user_id=user_id)
321
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
300
322
 
301
323
  # Unlink the block from the agent
302
- updated_memory = server.unlink_block_from_agent_memory(user_id=actor.id, agent_id=agent_id, block_label=block_label)
324
+ agent = server.agent_manager.detach_block_with_label(agent_id=agent_id, block_label=block_label, actor=actor)
303
325
 
304
- return updated_memory
326
+ return agent.memory
305
327
 
306
328
 
307
329
  @router.patch("/{agent_id}/memory/block/{block_label}", response_model=Block, operation_id="update_agent_memory_block_by_label")
308
330
  def update_agent_memory_block(
309
331
  agent_id: str,
310
332
  block_label: str,
311
- update_block: BlockUpdate = Body(...),
333
+ block_update: BlockUpdate = Body(...),
312
334
  server: "SyncServer" = Depends(get_letta_server),
313
335
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
314
336
  ):
315
337
  """
316
338
  Removes a memory block from an agent by unlnking it. If the block is not linked to any other agent, it is deleted.
317
339
  """
318
- actor = server.get_user_or_default(user_id=user_id)
319
-
320
- # get the block_id from the label
321
- block_id = server.blocks_agents_manager.get_block_id_for_label(agent_id=agent_id, block_label=block_label)
340
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
322
341
 
323
- # update the block
324
- return server.block_manager.update_block(block_id=block_id, block_update=update_block, actor=actor)
342
+ block = server.agent_manager.get_block_with_label(agent_id=agent_id, block_label=block_label, actor=actor)
343
+ return server.block_manager.update_block(block.id, block_update=block_update, actor=actor)
325
344
 
326
345
 
327
346
  @router.get("/{agent_id}/memory/recall", response_model=RecallMemorySummary, operation_id="get_agent_recall_memory_summary")
328
347
  def get_agent_recall_memory_summary(
329
348
  agent_id: str,
330
349
  server: "SyncServer" = Depends(get_letta_server),
350
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
331
351
  ):
332
352
  """
333
353
  Retrieve the summary of the recall memory of a specific agent.
334
354
  """
355
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
335
356
 
336
- return server.get_recall_memory_summary(agent_id=agent_id)
357
+ return server.get_recall_memory_summary(agent_id=agent_id, actor=actor)
337
358
 
338
359
 
339
360
  @router.get("/{agent_id}/memory/archival", response_model=ArchivalMemorySummary, operation_id="get_agent_archival_memory_summary")
340
361
  def get_agent_archival_memory_summary(
341
362
  agent_id: str,
342
363
  server: "SyncServer" = Depends(get_letta_server),
364
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
343
365
  ):
344
366
  """
345
367
  Retrieve the summary of the archival memory of a specific agent.
346
368
  """
347
-
348
- return server.get_archival_memory_summary(agent_id=agent_id)
369
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
370
+ return server.get_archival_memory_summary(agent_id=agent_id, actor=actor)
349
371
 
350
372
 
351
373
  @router.get("/{agent_id}/archival", response_model=List[Passage], operation_id="list_agent_archival_memory")
@@ -360,7 +382,7 @@ def get_agent_archival_memory(
360
382
  """
361
383
  Retrieve the memories in an agent's archival memory store (paginated query).
362
384
  """
363
- actor = server.get_user_or_default(user_id=user_id)
385
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
364
386
 
365
387
  # TODO need to add support for non-postgres here
366
388
  # chroma will throw:
@@ -369,7 +391,7 @@ def get_agent_archival_memory(
369
391
  return server.get_agent_archival_cursor(
370
392
  user_id=actor.id,
371
393
  agent_id=agent_id,
372
- cursor=after, # TODO: deleting before, after. is this expected?
394
+ cursor=after, # TODO: deleting before, after. is this expected?
373
395
  limit=limit,
374
396
  )
375
397
 
@@ -384,9 +406,9 @@ def insert_agent_archival_memory(
384
406
  """
385
407
  Insert a memory into an agent's archival memory store.
386
408
  """
387
- actor = server.get_user_or_default(user_id=user_id)
409
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
388
410
 
389
- return server.insert_archival_memory(user_id=actor.id, agent_id=agent_id, memory_contents=request.text)
411
+ return server.insert_archival_memory(agent_id=agent_id, memory_contents=request.text, actor=actor)
390
412
 
391
413
 
392
414
  # TODO(ethan): query or path parameter for memory_id?
@@ -402,9 +424,9 @@ def delete_agent_archival_memory(
402
424
  """
403
425
  Delete a memory from an agent's archival memory store.
404
426
  """
405
- actor = server.get_user_or_default(user_id=user_id)
427
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
406
428
 
407
- server.delete_archival_memory(user_id=actor.id, agent_id=agent_id, memory_id=memory_id)
429
+ server.delete_archival_memory(agent_id=agent_id, memory_id=memory_id, actor=actor)
408
430
  return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"Memory id={memory_id} successfully deleted"})
409
431
 
410
432
 
@@ -429,7 +451,7 @@ def get_agent_messages(
429
451
  """
430
452
  Retrieve message history for an agent.
431
453
  """
432
- actor = server.get_user_or_default(user_id=user_id)
454
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
433
455
 
434
456
  return server.get_agent_recall_cursor(
435
457
  user_id=actor.id,
@@ -449,11 +471,13 @@ def update_message(
449
471
  message_id: str,
450
472
  request: MessageUpdate = Body(...),
451
473
  server: "SyncServer" = Depends(get_letta_server),
474
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
452
475
  ):
453
476
  """
454
477
  Update the details of a message associated with an agent.
455
478
  """
456
- return server.update_agent_message(agent_id=agent_id, message_id=message_id, request=request)
479
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
480
+ return server.update_agent_message(agent_id=agent_id, message_id=message_id, request=request, actor=actor)
457
481
 
458
482
 
459
483
  @router.post(
@@ -471,11 +495,11 @@ async def send_message(
471
495
  Process a user message and return the agent's response.
472
496
  This endpoint accepts a message from a user and processes it through the agent.
473
497
  """
474
- actor = server.get_user_or_default(user_id=user_id)
498
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
475
499
  result = await send_message_to_agent(
476
500
  server=server,
477
501
  agent_id=agent_id,
478
- user_id=actor.id,
502
+ actor=actor,
479
503
  messages=request.messages,
480
504
  stream_steps=False,
481
505
  stream_tokens=False,
@@ -511,11 +535,11 @@ async def send_message_streaming(
511
535
  It will stream the steps of the response always, and stream the tokens if 'stream_tokens' is set to True.
512
536
  """
513
537
 
514
- actor = server.get_user_or_default(user_id=user_id)
538
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
515
539
  result = await send_message_to_agent(
516
540
  server=server,
517
541
  agent_id=agent_id,
518
- user_id=actor.id,
542
+ actor=actor,
519
543
  messages=request.messages,
520
544
  stream_steps=True,
521
545
  stream_tokens=request.stream_tokens,
@@ -531,7 +555,6 @@ async def process_message_background(
531
555
  server: SyncServer,
532
556
  actor: User,
533
557
  agent_id: str,
534
- user_id: str,
535
558
  messages: list,
536
559
  assistant_message_tool_name: str,
537
560
  assistant_message_tool_kwarg: str,
@@ -542,7 +565,7 @@ async def process_message_background(
542
565
  result = await send_message_to_agent(
543
566
  server=server,
544
567
  agent_id=agent_id,
545
- user_id=user_id,
568
+ actor=actor,
546
569
  messages=messages,
547
570
  stream_steps=False, # NOTE(matt)
548
571
  stream_tokens=False,
@@ -585,7 +608,7 @@ async def send_message_async(
585
608
  Asynchronously process a user message and return a job ID.
586
609
  The actual processing happens in the background, and the status can be checked using the job ID.
587
610
  """
588
- actor = server.get_user_or_default(user_id=user_id)
611
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
589
612
 
590
613
  # Create a new job
591
614
  job = Job(
@@ -605,7 +628,6 @@ async def send_message_async(
605
628
  server=server,
606
629
  actor=actor,
607
630
  agent_id=agent_id,
608
- user_id=actor.id,
609
631
  messages=request.messages,
610
632
  assistant_message_tool_name=request.assistant_message_tool_name,
611
633
  assistant_message_tool_kwarg=request.assistant_message_tool_kwarg,
@@ -618,7 +640,7 @@ async def send_message_async(
618
640
  async def send_message_to_agent(
619
641
  server: SyncServer,
620
642
  agent_id: str,
621
- user_id: str,
643
+ actor: User,
622
644
  # role: MessageRole,
623
645
  messages: Union[List[Message], List[MessageCreate]],
624
646
  stream_steps: bool,
@@ -645,8 +667,7 @@ async def send_message_to_agent(
645
667
 
646
668
  # Get the generator object off of the agent's streaming interface
647
669
  # This will be attached to the POST SSE request used under-the-hood
648
- # letta_agent = server.load_agent(agent_id=agent_id)
649
- letta_agent = server.load_agent(agent_id=agent_id)
670
+ letta_agent = server.load_agent(agent_id=agent_id, actor=actor)
650
671
 
651
672
  # Disable token streaming if not OpenAI
652
673
  # TODO: cleanup this logic
@@ -685,7 +706,7 @@ async def send_message_to_agent(
685
706
  task = asyncio.create_task(
686
707
  asyncio.to_thread(
687
708
  server.send_messages,
688
- user_id=user_id,
709
+ actor=actor,
689
710
  agent_id=agent_id,
690
711
  messages=messages,
691
712
  interface=streaming_interface,
@@ -1,10 +1,9 @@
1
1
  from typing import TYPE_CHECKING, List, Optional
2
2
 
3
- from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query
3
+ from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Response
4
4
 
5
5
  from letta.orm.errors import NoResultFound
6
6
  from letta.schemas.block import Block, BlockUpdate, CreateBlock
7
- from letta.schemas.memory import Memory
8
7
  from letta.server.rest_api.utils import get_letta_server
9
8
  from letta.server.server import SyncServer
10
9
 
@@ -23,7 +22,7 @@ def list_blocks(
23
22
  server: SyncServer = Depends(get_letta_server),
24
23
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
25
24
  ):
26
- actor = server.get_user_or_default(user_id=user_id)
25
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
27
26
  return server.block_manager.get_blocks(actor=actor, label=label, is_template=templates_only, template_name=name)
28
27
 
29
28
 
@@ -33,7 +32,7 @@ def create_block(
33
32
  server: SyncServer = Depends(get_letta_server),
34
33
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
35
34
  ):
36
- actor = server.get_user_or_default(user_id=user_id)
35
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
37
36
  block = Block(**create_block.model_dump())
38
37
  return server.block_manager.create_or_update_block(actor=actor, block=block)
39
38
 
@@ -41,12 +40,12 @@ def create_block(
41
40
  @router.patch("/{block_id}", response_model=Block, operation_id="update_memory_block")
42
41
  def update_block(
43
42
  block_id: str,
44
- update_block: BlockUpdate = Body(...),
43
+ block_update: BlockUpdate = Body(...),
45
44
  server: SyncServer = Depends(get_letta_server),
46
45
  user_id: Optional[str] = Header(None, alias="user_id"),
47
46
  ):
48
- actor = server.get_user_or_default(user_id=user_id)
49
- return server.block_manager.update_block(block_id=block_id, block_update=update_block, actor=actor)
47
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
48
+ return server.block_manager.update_block(block_id=block_id, block_update=block_update, actor=actor)
50
49
 
51
50
 
52
51
  @router.delete("/{block_id}", response_model=Block, operation_id="delete_memory_block")
@@ -55,7 +54,7 @@ def delete_block(
55
54
  server: SyncServer = Depends(get_letta_server),
56
55
  user_id: Optional[str] = Header(None, alias="user_id"),
57
56
  ):
58
- actor = server.get_user_or_default(user_id=user_id)
57
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
59
58
  return server.block_manager.delete_block(block_id=block_id, actor=actor)
60
59
 
61
60
 
@@ -66,7 +65,7 @@ def get_block(
66
65
  user_id: Optional[str] = Header(None, alias="user_id"),
67
66
  ):
68
67
  print("call get block", block_id)
69
- actor = server.get_user_or_default(user_id=user_id)
68
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
70
69
  try:
71
70
  block = server.block_manager.get_block_by_id(block_id=block_id, actor=actor)
72
71
  if block is None:
@@ -76,7 +75,7 @@ def get_block(
76
75
  raise HTTPException(status_code=404, detail="Block not found")
77
76
 
78
77
 
79
- @router.patch("/{block_id}/attach", response_model=Block, operation_id="link_agent_memory_block")
78
+ @router.patch("/{block_id}/attach", response_model=None, status_code=204, operation_id="link_agent_memory_block")
80
79
  def link_agent_memory_block(
81
80
  block_id: str,
82
81
  agent_id: str = Query(..., description="The unique identifier of the agent to attach the source to."),
@@ -86,17 +85,16 @@ def link_agent_memory_block(
86
85
  """
87
86
  Link a memory block to an agent.
88
87
  """
89
- actor = server.get_user_or_default(user_id=user_id)
88
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
90
89
 
91
- block = server.block_manager.get_block_by_id(block_id=block_id, actor=actor)
92
- if block is None:
93
- raise HTTPException(status_code=404, detail="Block not found")
94
-
95
- server.blocks_agents_manager.add_block_to_agent(agent_id=agent_id, block_id=block_id, block_label=block.label)
96
- return block
90
+ try:
91
+ server.agent_manager.attach_block(agent_id=agent_id, block_id=block_id, actor=actor)
92
+ return Response(status_code=204)
93
+ except NoResultFound as e:
94
+ raise HTTPException(status_code=404, detail=str(e))
97
95
 
98
96
 
99
- @router.patch("/{block_id}/detach", response_model=Memory, operation_id="unlink_agent_memory_block")
97
+ @router.patch("/{block_id}/detach", response_model=None, status_code=204, operation_id="unlink_agent_memory_block")
100
98
  def unlink_agent_memory_block(
101
99
  block_id: str,
102
100
  agent_id: str = Query(..., description="The unique identifier of the agent to attach the source to."),
@@ -106,11 +104,10 @@ def unlink_agent_memory_block(
106
104
  """
107
105
  Unlink a memory block from an agent
108
106
  """
109
- actor = server.get_user_or_default(user_id=user_id)
107
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
110
108
 
111
- block = server.block_manager.get_block_by_id(block_id=block_id, actor=actor)
112
- if block is None:
113
- raise HTTPException(status_code=404, detail="Block not found")
114
- # Link the block to the agent
115
- server.blocks_agents_manager.remove_block_with_id_from_agent(agent_id=agent_id, block_id=block_id)
116
- return block
109
+ try:
110
+ server.agent_manager.detach_block(agent_id=agent_id, block_id=block_id, actor=actor)
111
+ return Response(status_code=204)
112
+ except NoResultFound as e:
113
+ raise HTTPException(status_code=404, detail=str(e))
@@ -20,7 +20,7 @@ def list_jobs(
20
20
  """
21
21
  List all jobs.
22
22
  """
23
- actor = server.get_user_or_default(user_id=user_id)
23
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
24
24
 
25
25
  # TODO: add filtering by status
26
26
  jobs = server.job_manager.list_jobs(actor=actor)
@@ -40,7 +40,7 @@ def list_active_jobs(
40
40
  """
41
41
  List all active jobs.
42
42
  """
43
- actor = server.get_user_or_default(user_id=user_id)
43
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
44
44
 
45
45
  return server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running])
46
46
 
@@ -54,7 +54,7 @@ def get_job(
54
54
  """
55
55
  Get the status of a job.
56
56
  """
57
- actor = server.get_user_or_default(user_id=user_id)
57
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
58
58
 
59
59
  try:
60
60
  return server.job_manager.get_job_by_id(job_id=job_id, actor=actor)
@@ -71,7 +71,7 @@ def delete_job(
71
71
  """
72
72
  Delete a job by its job_id.
73
73
  """
74
- actor = server.get_user_or_default(user_id=user_id)
74
+ actor = server.user_manager.get_user_or_default(user_id=user_id)
75
75
 
76
76
  try:
77
77
  job = server.job_manager.delete_job_by_id(job_id=job_id, actor=actor)