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.
- letta/__init__.py +1 -1
- letta/agent.py +12 -0
- letta/agents/helpers.py +48 -5
- letta/agents/letta_agent.py +46 -18
- 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/groups/sleeptime_multi_agent.py +1 -0
- letta/interfaces/anthropic_streaming_interface.py +40 -6
- 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 +365 -133
- 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 +13 -13
- letta/server/rest_api/routers/v1/messages.py +6 -6
- letta/server/rest_api/routers/v1/tools.py +3 -3
- letta/server/server.py +74 -0
- letta/services/agent_manager.py +421 -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 +51 -19
- letta/services/tool_executor/tool_executor.py +19 -1
- letta/services/tool_manager.py +13 -3
- letta/types/__init__.py +0 -0
- {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/METADATA +3 -3
- {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/RECORD +36 -35
- {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.15.dev20250514104255.dist-info → letta_nightly-0.7.16.dev20250515205957.dist-info}/WHEEL +0 -0
- {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.
|
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.
|
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.
|
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("
|
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("
|
694
|
-
feature_enabled = settings.use_experimental or experimental_header
|
695
|
-
model_compatible = agent.llm_config.model_endpoint_type
|
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.
|
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
|
|
@@ -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.
|
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.
|
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.
|
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)
|
@@ -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.
|
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}")
|
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",
|