letta-nightly 0.7.15.dev20250514104255__py3-none-any.whl → 0.7.16.dev20250515205957__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +12 -0
  3. letta/agents/helpers.py +48 -5
  4. letta/agents/letta_agent.py +46 -18
  5. letta/agents/letta_agent_batch.py +44 -26
  6. letta/agents/voice_sleeptime_agent.py +6 -4
  7. letta/client/client.py +16 -1
  8. letta/constants.py +3 -0
  9. letta/functions/async_composio_toolset.py +1 -1
  10. letta/groups/sleeptime_multi_agent.py +1 -0
  11. letta/interfaces/anthropic_streaming_interface.py +40 -6
  12. letta/jobs/llm_batch_job_polling.py +6 -2
  13. letta/orm/agent.py +102 -1
  14. letta/orm/block.py +3 -0
  15. letta/orm/sqlalchemy_base.py +365 -133
  16. letta/schemas/agent.py +10 -2
  17. letta/schemas/block.py +3 -0
  18. letta/schemas/memory.py +7 -2
  19. letta/server/rest_api/routers/v1/agents.py +13 -13
  20. letta/server/rest_api/routers/v1/messages.py +6 -6
  21. letta/server/rest_api/routers/v1/tools.py +3 -3
  22. letta/server/server.py +74 -0
  23. letta/services/agent_manager.py +421 -7
  24. letta/services/block_manager.py +12 -8
  25. letta/services/helpers/agent_manager_helper.py +19 -0
  26. letta/services/job_manager.py +99 -0
  27. letta/services/llm_batch_manager.py +28 -27
  28. letta/services/message_manager.py +51 -19
  29. letta/services/tool_executor/tool_executor.py +19 -1
  30. letta/services/tool_manager.py +13 -3
  31. letta/types/__init__.py +0 -0
  32. {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/METADATA +3 -3
  33. {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/RECORD +36 -35
  34. {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/LICENSE +0 -0
  35. {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/WHEEL +0 -0
  36. {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/entry_points.txt +0 -0
@@ -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(
@@ -86,7 +86,7 @@ def list_agents(
86
86
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
87
87
 
88
88
  # Call list_agents directly without unnecessary dict handling
89
- return server.agent_manager.list_agents(
89
+ return await server.agent_manager.list_agents_async(
90
90
  actor=actor,
91
91
  name=name,
92
92
  before=before,
@@ -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
@@ -234,14 +234,14 @@ def create_agent(
234
234
  """
235
235
  try:
236
236
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
237
- return server.create_agent(agent, actor=actor)
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),
@@ -249,7 +249,7 @@ def modify_agent(
249
249
  ):
250
250
  """Update an existing agent"""
251
251
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
252
- return server.update_agent(agent_id=agent_id, request=update_agent, actor=actor)
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")
@@ -632,8 +632,8 @@ async def send_message(
632
632
  # TODO: This is redundant, remove soon
633
633
  agent = server.agent_manager.get_agent_by_id(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("x-experimental")
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,
@@ -690,11 +690,11 @@ async def send_message_streaming(
690
690
  # TODO: This is redundant, remove soon
691
691
  agent = server.agent_manager.get_agent_by_id(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("x-experimental")
694
- feature_enabled = settings.use_experimental or experimental_header
695
- model_compatible = agent.llm_config.model_endpoint_type in ["anthropic", "openai"]
693
+ experimental_header = request_obj.headers.get("X-EXPERIMENTAL") or "false"
694
+ feature_enabled = settings.use_experimental or experimental_header.lower() == "true"
695
+ model_compatible = agent.llm_config.model_endpoint_type == "anthropic"
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,
@@ -63,7 +63,7 @@ async def create_messages_batch(
63
63
  )
64
64
 
65
65
  try:
66
- batch_job = server.job_manager.create_job(pydantic_job=batch_job, actor=actor)
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.update_job_by_id(job_id=batch_job.id, job=BatchJob(status=JobStatus.failed), actor=actor)
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
 
@@ -103,7 +103,7 @@ async def retrieve_batch_run(
103
103
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
104
104
 
105
105
  try:
106
- job = server.job_manager.get_job_by_id(job_id=batch_id, actor=actor)
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")
@@ -154,7 +154,7 @@ async def list_batch_messages(
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.get_job_by_id(job_id=batch_id, actor=actor)
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")
@@ -180,8 +180,8 @@ async def cancel_batch_run(
180
180
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
181
181
 
182
182
  try:
183
- job = server.job_manager.get_job_by_id(job_id=batch_id, actor=actor)
184
- job = server.job_manager.update_job_by_id(job_id=job.id, job_update=JobUpdate(status=JobStatus.cancelled), actor=actor)
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)
@@ -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,
@@ -89,9 +89,9 @@ def list_tools(
89
89
  try:
90
90
  actor = server.user_manager.get_user_or_default(user_id=actor_id)
91
91
  if name is not None:
92
- tool = server.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
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.list_tools(actor=actor, after=after, limit=limit)
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}")
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",