letta-nightly 0.7.21.dev20250522104246__py3-none-any.whl → 0.7.22.dev20250523081403__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 +2 -2
- letta/agents/base_agent.py +4 -2
- letta/agents/letta_agent.py +3 -10
- letta/agents/letta_agent_batch.py +6 -6
- letta/cli/cli.py +0 -316
- letta/cli/cli_load.py +0 -52
- letta/client/client.py +2 -1554
- letta/data_sources/connectors.py +4 -2
- letta/functions/ast_parsers.py +33 -43
- letta/groups/sleeptime_multi_agent_v2.py +49 -13
- letta/jobs/llm_batch_job_polling.py +3 -3
- letta/jobs/scheduler.py +20 -19
- letta/llm_api/anthropic_client.py +3 -0
- letta/llm_api/google_vertex_client.py +5 -0
- letta/llm_api/openai_client.py +5 -0
- letta/main.py +2 -362
- letta/server/db.py +5 -0
- letta/server/rest_api/routers/v1/agents.py +72 -43
- letta/server/rest_api/routers/v1/llms.py +2 -2
- letta/server/rest_api/routers/v1/messages.py +5 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +18 -18
- letta/server/rest_api/routers/v1/sources.py +49 -36
- letta/server/server.py +53 -22
- letta/services/agent_manager.py +797 -124
- letta/services/block_manager.py +14 -62
- letta/services/group_manager.py +37 -0
- letta/services/identity_manager.py +9 -0
- letta/services/job_manager.py +17 -0
- letta/services/llm_batch_manager.py +88 -64
- letta/services/message_manager.py +19 -0
- letta/services/organization_manager.py +10 -0
- letta/services/passage_manager.py +13 -0
- letta/services/per_agent_lock_manager.py +4 -0
- letta/services/provider_manager.py +34 -0
- letta/services/sandbox_config_manager.py +130 -0
- letta/services/source_manager.py +59 -44
- letta/services/step_manager.py +8 -1
- letta/services/tool_manager.py +21 -0
- letta/services/tool_sandbox/e2b_sandbox.py +4 -2
- letta/services/tool_sandbox/local_sandbox.py +7 -3
- letta/services/user_manager.py +16 -0
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/METADATA +1 -1
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/RECORD +46 -50
- letta/__main__.py +0 -3
- letta/benchmark/benchmark.py +0 -98
- letta/benchmark/constants.py +0 -14
- letta/cli/cli_config.py +0 -227
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/entry_points.txt +0 -0
@@ -161,7 +161,7 @@ async def list_batch_messages(
|
|
161
161
|
|
162
162
|
# Get messages directly using our efficient method
|
163
163
|
# We'll need to update the underlying implementation to use message_id as cursor
|
164
|
-
messages = server.batch_manager.
|
164
|
+
messages = await server.batch_manager.get_messages_for_letta_batch_async(
|
165
165
|
letta_batch_job_id=batch_id, limit=limit, actor=actor, agent_id=agent_id, sort_descending=sort_descending, cursor=cursor
|
166
166
|
)
|
167
167
|
|
@@ -184,7 +184,7 @@ async def cancel_batch_run(
|
|
184
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
|
-
llm_batch_jobs = server.batch_manager.
|
187
|
+
llm_batch_jobs = await server.batch_manager.list_llm_batch_jobs_async(letta_batch_id=job.id, actor=actor)
|
188
188
|
for llm_batch_job in llm_batch_jobs:
|
189
189
|
if llm_batch_job.status in {JobStatus.running, JobStatus.created}:
|
190
190
|
# TODO: Extend to providers beyond anthropic
|
@@ -194,6 +194,8 @@ async def cancel_batch_run(
|
|
194
194
|
await server.anthropic_async_client.messages.batches.cancel(anthropic_batch_id)
|
195
195
|
|
196
196
|
# Update all the batch_job statuses
|
197
|
-
server.batch_manager.
|
197
|
+
await server.batch_manager.update_llm_batch_status_async(
|
198
|
+
llm_batch_id=llm_batch_job.id, status=JobStatus.cancelled, actor=actor
|
199
|
+
)
|
198
200
|
except NoResultFound:
|
199
201
|
raise HTTPException(status_code=404, detail="Run not found")
|
@@ -22,36 +22,36 @@ logger = get_logger(__name__)
|
|
22
22
|
|
23
23
|
|
24
24
|
@router.post("/", response_model=PydanticSandboxConfig)
|
25
|
-
def create_sandbox_config(
|
25
|
+
async def create_sandbox_config(
|
26
26
|
config_create: SandboxConfigCreate,
|
27
27
|
server: SyncServer = Depends(get_letta_server),
|
28
28
|
actor_id: str = Depends(get_user_id),
|
29
29
|
):
|
30
|
-
actor = server.user_manager.
|
30
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
31
31
|
|
32
|
-
return server.sandbox_config_manager.
|
32
|
+
return await server.sandbox_config_manager.create_or_update_sandbox_config_async(config_create, actor)
|
33
33
|
|
34
34
|
|
35
35
|
@router.post("/e2b/default", response_model=PydanticSandboxConfig)
|
36
|
-
def create_default_e2b_sandbox_config(
|
36
|
+
async def create_default_e2b_sandbox_config(
|
37
37
|
server: SyncServer = Depends(get_letta_server),
|
38
38
|
actor_id: str = Depends(get_user_id),
|
39
39
|
):
|
40
|
-
actor = server.user_manager.
|
41
|
-
return server.sandbox_config_manager.
|
40
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
41
|
+
return await server.sandbox_config_manager.get_or_create_default_sandbox_config_async(sandbox_type=SandboxType.E2B, actor=actor)
|
42
42
|
|
43
43
|
|
44
44
|
@router.post("/local/default", response_model=PydanticSandboxConfig)
|
45
|
-
def create_default_local_sandbox_config(
|
45
|
+
async def create_default_local_sandbox_config(
|
46
46
|
server: SyncServer = Depends(get_letta_server),
|
47
47
|
actor_id: str = Depends(get_user_id),
|
48
48
|
):
|
49
|
-
actor = server.user_manager.
|
50
|
-
return server.sandbox_config_manager.
|
49
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
50
|
+
return await server.sandbox_config_manager.get_or_create_default_sandbox_config_async(sandbox_type=SandboxType.LOCAL, actor=actor)
|
51
51
|
|
52
52
|
|
53
53
|
@router.post("/local", response_model=PydanticSandboxConfig)
|
54
|
-
def create_custom_local_sandbox_config(
|
54
|
+
async def create_custom_local_sandbox_config(
|
55
55
|
local_sandbox_config: LocalSandboxConfig,
|
56
56
|
server: SyncServer = Depends(get_letta_server),
|
57
57
|
actor_id: str = Depends(get_user_id),
|
@@ -67,26 +67,26 @@ def create_custom_local_sandbox_config(
|
|
67
67
|
)
|
68
68
|
|
69
69
|
# Retrieve the user (actor)
|
70
|
-
actor = server.user_manager.
|
70
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
71
71
|
|
72
72
|
# Wrap the LocalSandboxConfig into a SandboxConfigCreate
|
73
73
|
sandbox_config_create = SandboxConfigCreate(config=local_sandbox_config)
|
74
74
|
|
75
75
|
# Use the manager to create or update the sandbox config
|
76
|
-
sandbox_config = server.sandbox_config_manager.
|
76
|
+
sandbox_config = await server.sandbox_config_manager.create_or_update_sandbox_config_async(sandbox_config_create, actor=actor)
|
77
77
|
|
78
78
|
return sandbox_config
|
79
79
|
|
80
80
|
|
81
81
|
@router.patch("/{sandbox_config_id}", response_model=PydanticSandboxConfig)
|
82
|
-
def update_sandbox_config(
|
82
|
+
async def update_sandbox_config(
|
83
83
|
sandbox_config_id: str,
|
84
84
|
config_update: SandboxConfigUpdate,
|
85
85
|
server: SyncServer = Depends(get_letta_server),
|
86
86
|
actor_id: str = Depends(get_user_id),
|
87
87
|
):
|
88
|
-
actor = server.user_manager.
|
89
|
-
return server.sandbox_config_manager.
|
88
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
89
|
+
return await server.sandbox_config_manager.update_sandbox_config_async(sandbox_config_id, config_update, actor)
|
90
90
|
|
91
91
|
|
92
92
|
@router.delete("/{sandbox_config_id}", status_code=204)
|
@@ -112,7 +112,7 @@ async def list_sandbox_configs(
|
|
112
112
|
|
113
113
|
|
114
114
|
@router.post("/local/recreate-venv", response_model=PydanticSandboxConfig)
|
115
|
-
def force_recreate_local_sandbox_venv(
|
115
|
+
async def force_recreate_local_sandbox_venv(
|
116
116
|
server: SyncServer = Depends(get_letta_server),
|
117
117
|
actor_id: str = Depends(get_user_id),
|
118
118
|
):
|
@@ -120,10 +120,10 @@ def force_recreate_local_sandbox_venv(
|
|
120
120
|
Forcefully recreate the virtual environment for the local sandbox.
|
121
121
|
Deletes and recreates the venv, then reinstalls required dependencies.
|
122
122
|
"""
|
123
|
-
actor = server.user_manager.
|
123
|
+
actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
|
124
124
|
|
125
125
|
# Retrieve the local sandbox config
|
126
|
-
sbx_config = server.sandbox_config_manager.
|
126
|
+
sbx_config = await server.sandbox_config_manager.get_or_create_default_sandbox_config_async(sandbox_type=SandboxType.LOCAL, actor=actor)
|
127
127
|
|
128
128
|
local_configs = sbx_config.get_local_config()
|
129
129
|
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import asyncio
|
1
2
|
import os
|
2
3
|
import tempfile
|
3
4
|
from typing import List, Optional
|
@@ -21,18 +22,18 @@ router = APIRouter(prefix="/sources", tags=["sources"])
|
|
21
22
|
|
22
23
|
|
23
24
|
@router.get("/count", response_model=int, operation_id="count_sources")
|
24
|
-
def count_sources(
|
25
|
+
async def count_sources(
|
25
26
|
server: "SyncServer" = Depends(get_letta_server),
|
26
27
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
27
28
|
):
|
28
29
|
"""
|
29
30
|
Count all data sources created by a user.
|
30
31
|
"""
|
31
|
-
return server.source_manager.size(actor=server.user_manager.get_user_or_default(user_id=actor_id))
|
32
|
+
return await server.source_manager.size(actor=server.user_manager.get_user_or_default(user_id=actor_id))
|
32
33
|
|
33
34
|
|
34
35
|
@router.get("/{source_id}", response_model=Source, operation_id="retrieve_source")
|
35
|
-
def retrieve_source(
|
36
|
+
async def retrieve_source(
|
36
37
|
source_id: str,
|
37
38
|
server: "SyncServer" = Depends(get_letta_server),
|
38
39
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -42,14 +43,14 @@ def retrieve_source(
|
|
42
43
|
"""
|
43
44
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
44
45
|
|
45
|
-
source = server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
46
|
+
source = await server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
46
47
|
if not source:
|
47
48
|
raise HTTPException(status_code=404, detail=f"Source with id={source_id} not found.")
|
48
49
|
return source
|
49
50
|
|
50
51
|
|
51
52
|
@router.get("/name/{source_name}", response_model=str, operation_id="get_source_id_by_name")
|
52
|
-
def get_source_id_by_name(
|
53
|
+
async def get_source_id_by_name(
|
53
54
|
source_name: str,
|
54
55
|
server: "SyncServer" = Depends(get_letta_server),
|
55
56
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -59,14 +60,14 @@ def get_source_id_by_name(
|
|
59
60
|
"""
|
60
61
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
61
62
|
|
62
|
-
source = server.source_manager.get_source_by_name(source_name=source_name, actor=actor)
|
63
|
+
source = await server.source_manager.get_source_by_name(source_name=source_name, actor=actor)
|
63
64
|
if not source:
|
64
65
|
raise HTTPException(status_code=404, detail=f"Source with name={source_name} not found.")
|
65
66
|
return source.id
|
66
67
|
|
67
68
|
|
68
69
|
@router.get("/", response_model=List[Source], operation_id="list_sources")
|
69
|
-
def list_sources(
|
70
|
+
async def list_sources(
|
70
71
|
server: "SyncServer" = Depends(get_letta_server),
|
71
72
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
72
73
|
):
|
@@ -74,8 +75,7 @@ def list_sources(
|
|
74
75
|
List all data sources created by a user.
|
75
76
|
"""
|
76
77
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
77
|
-
|
78
|
-
return server.list_all_sources(actor=actor)
|
78
|
+
return await server.source_manager.list_sources(actor=actor)
|
79
79
|
|
80
80
|
|
81
81
|
@router.get("/count", response_model=int, operation_id="count_sources")
|
@@ -90,7 +90,7 @@ def count_sources(
|
|
90
90
|
|
91
91
|
|
92
92
|
@router.post("/", response_model=Source, operation_id="create_source")
|
93
|
-
def create_source(
|
93
|
+
async def create_source(
|
94
94
|
source_create: SourceCreate,
|
95
95
|
server: "SyncServer" = Depends(get_letta_server),
|
96
96
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -99,6 +99,8 @@ def create_source(
|
|
99
99
|
Create a new data source.
|
100
100
|
"""
|
101
101
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
102
|
+
|
103
|
+
# TODO: need to asyncify this
|
102
104
|
if not source_create.embedding_config:
|
103
105
|
if not source_create.embedding:
|
104
106
|
# TODO: modify error type
|
@@ -115,11 +117,11 @@ def create_source(
|
|
115
117
|
instructions=source_create.instructions,
|
116
118
|
metadata=source_create.metadata,
|
117
119
|
)
|
118
|
-
return server.source_manager.create_source(source=source, actor=actor)
|
120
|
+
return await server.source_manager.create_source(source=source, actor=actor)
|
119
121
|
|
120
122
|
|
121
123
|
@router.patch("/{source_id}", response_model=Source, operation_id="modify_source")
|
122
|
-
def modify_source(
|
124
|
+
async def modify_source(
|
123
125
|
source_id: str,
|
124
126
|
source: SourceUpdate,
|
125
127
|
server: "SyncServer" = Depends(get_letta_server),
|
@@ -130,13 +132,13 @@ def modify_source(
|
|
130
132
|
"""
|
131
133
|
# TODO: allow updating the handle/embedding config
|
132
134
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
133
|
-
if not server.source_manager.get_source_by_id(source_id=source_id, actor=actor):
|
135
|
+
if not await server.source_manager.get_source_by_id(source_id=source_id, actor=actor):
|
134
136
|
raise HTTPException(status_code=404, detail=f"Source with id={source_id} does not exist.")
|
135
|
-
return server.source_manager.update_source(source_id=source_id, source_update=source, actor=actor)
|
137
|
+
return await server.source_manager.update_source(source_id=source_id, source_update=source, actor=actor)
|
136
138
|
|
137
139
|
|
138
140
|
@router.delete("/{source_id}", response_model=None, operation_id="delete_source")
|
139
|
-
def delete_source(
|
141
|
+
async def delete_source(
|
140
142
|
source_id: str,
|
141
143
|
server: "SyncServer" = Depends(get_letta_server),
|
142
144
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
@@ -145,20 +147,21 @@ def delete_source(
|
|
145
147
|
Delete a data source.
|
146
148
|
"""
|
147
149
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
148
|
-
source = server.source_manager.get_source_by_id(source_id=source_id)
|
149
|
-
agents = server.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
150
|
+
source = await server.source_manager.get_source_by_id(source_id=source_id)
|
151
|
+
agents = await server.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
150
152
|
for agent in agents:
|
151
153
|
if agent.enable_sleeptime:
|
152
154
|
try:
|
155
|
+
# TODO: make async
|
153
156
|
block = server.agent_manager.get_block_with_label(agent_id=agent.id, block_label=source.name, actor=actor)
|
154
157
|
server.block_manager.delete_block(block.id, actor)
|
155
158
|
except:
|
156
159
|
pass
|
157
|
-
server.delete_source(source_id=source_id, actor=actor)
|
160
|
+
await server.delete_source(source_id=source_id, actor=actor)
|
158
161
|
|
159
162
|
|
160
163
|
@router.post("/{source_id}/upload", response_model=Job, operation_id="upload_file_to_source")
|
161
|
-
def upload_file_to_source(
|
164
|
+
async def upload_file_to_source(
|
162
165
|
file: UploadFile,
|
163
166
|
source_id: str,
|
164
167
|
background_tasks: BackgroundTasks,
|
@@ -170,7 +173,7 @@ def upload_file_to_source(
|
|
170
173
|
"""
|
171
174
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
172
175
|
|
173
|
-
source = server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
176
|
+
source = await server.source_manager.get_source_by_id(source_id=source_id, actor=actor)
|
174
177
|
assert source is not None, f"Source with id={source_id} not found."
|
175
178
|
bytes = file.file.read()
|
176
179
|
|
@@ -184,8 +187,8 @@ def upload_file_to_source(
|
|
184
187
|
server.job_manager.create_job(job, actor=actor)
|
185
188
|
|
186
189
|
# create background tasks
|
187
|
-
|
188
|
-
|
190
|
+
asyncio.create_task(load_file_to_source_async(server, source_id=source.id, file=file, job_id=job.id, bytes=bytes, actor=actor))
|
191
|
+
asyncio.create_task(sleeptime_document_ingest_async(server, source_id, actor))
|
189
192
|
|
190
193
|
# return job information
|
191
194
|
# Is this necessary? Can we just return the job from create_job?
|
@@ -195,8 +198,11 @@ def upload_file_to_source(
|
|
195
198
|
|
196
199
|
|
197
200
|
@router.get("/{source_id}/passages", response_model=List[Passage], operation_id="list_source_passages")
|
198
|
-
def list_source_passages(
|
201
|
+
async def list_source_passages(
|
199
202
|
source_id: str,
|
203
|
+
after: Optional[str] = Query(None, description="Message after which to retrieve the returned messages."),
|
204
|
+
before: Optional[str] = Query(None, description="Message before which to retrieve the returned messages."),
|
205
|
+
limit: int = Query(100, description="Maximum number of messages to retrieve."),
|
200
206
|
server: SyncServer = Depends(get_letta_server),
|
201
207
|
actor_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
202
208
|
):
|
@@ -204,12 +210,17 @@ def list_source_passages(
|
|
204
210
|
List all passages associated with a data source.
|
205
211
|
"""
|
206
212
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
207
|
-
|
208
|
-
|
213
|
+
return await server.agent_manager.list_passages_async(
|
214
|
+
actor=actor,
|
215
|
+
source_id=source_id,
|
216
|
+
after=after,
|
217
|
+
before=before,
|
218
|
+
limit=limit,
|
219
|
+
)
|
209
220
|
|
210
221
|
|
211
222
|
@router.get("/{source_id}/files", response_model=List[FileMetadata], operation_id="list_source_files")
|
212
|
-
def list_source_files(
|
223
|
+
async def list_source_files(
|
213
224
|
source_id: str,
|
214
225
|
limit: int = Query(1000, description="Number of files to return"),
|
215
226
|
after: Optional[str] = Query(None, description="Pagination cursor to fetch the next set of results"),
|
@@ -220,13 +231,13 @@ def list_source_files(
|
|
220
231
|
List paginated files associated with a data source.
|
221
232
|
"""
|
222
233
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
223
|
-
return server.source_manager.list_files(source_id=source_id, limit=limit, after=after, actor=actor)
|
234
|
+
return await server.source_manager.list_files(source_id=source_id, limit=limit, after=after, actor=actor)
|
224
235
|
|
225
236
|
|
226
237
|
# it's redundant to include /delete in the URL path. The HTTP verb DELETE already implies that action.
|
227
238
|
# it's still good practice to return a status indicating the success or failure of the deletion
|
228
239
|
@router.delete("/{source_id}/{file_id}", status_code=204, operation_id="delete_file_from_source")
|
229
|
-
def delete_file_from_source(
|
240
|
+
async def delete_file_from_source(
|
230
241
|
source_id: str,
|
231
242
|
file_id: str,
|
232
243
|
background_tasks: BackgroundTasks,
|
@@ -238,13 +249,15 @@ def delete_file_from_source(
|
|
238
249
|
"""
|
239
250
|
actor = server.user_manager.get_user_or_default(user_id=actor_id)
|
240
251
|
|
241
|
-
deleted_file = server.source_manager.delete_file(file_id=file_id, actor=actor)
|
242
|
-
|
252
|
+
deleted_file = await server.source_manager.delete_file(file_id=file_id, actor=actor)
|
253
|
+
|
254
|
+
# TODO: make async
|
255
|
+
asyncio.create_task(sleeptime_document_ingest_async(server, source_id, actor, clear_history=True))
|
243
256
|
if deleted_file is None:
|
244
257
|
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
|
245
258
|
|
246
259
|
|
247
|
-
def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, file: UploadFile, bytes: bytes, actor: User):
|
260
|
+
async def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, file: UploadFile, bytes: bytes, actor: User):
|
248
261
|
# Create a temporary directory (deleted after the context manager exits)
|
249
262
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
250
263
|
# Sanitize the filename
|
@@ -256,12 +269,12 @@ def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, f
|
|
256
269
|
buffer.write(bytes)
|
257
270
|
|
258
271
|
# Pass the file to load_file_to_source
|
259
|
-
server.load_file_to_source(source_id, file_path, job_id, actor)
|
272
|
+
await server.load_file_to_source(source_id, file_path, job_id, actor)
|
260
273
|
|
261
274
|
|
262
|
-
def sleeptime_document_ingest_async(server: SyncServer, source_id: str, actor: User, clear_history: bool = False):
|
263
|
-
source = server.source_manager.get_source_by_id(source_id=source_id)
|
264
|
-
agents = server.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
275
|
+
async def sleeptime_document_ingest_async(server: SyncServer, source_id: str, actor: User, clear_history: bool = False):
|
276
|
+
source = await server.source_manager.get_source_by_id(source_id=source_id)
|
277
|
+
agents = await server.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
265
278
|
for agent in agents:
|
266
279
|
if agent.enable_sleeptime:
|
267
|
-
server.sleeptime_document_ingest(agent, source, actor, clear_history)
|
280
|
+
server.sleeptime_document_ingest(agent, source, actor, clear_history) # TODO: make async
|
letta/server/server.py
CHANGED
@@ -50,7 +50,7 @@ from letta.schemas.letta_message import LegacyLettaMessage, LettaMessage, ToolRe
|
|
50
50
|
from letta.schemas.letta_message_content import TextContent
|
51
51
|
from letta.schemas.letta_response import LettaResponse
|
52
52
|
from letta.schemas.llm_config import LLMConfig
|
53
|
-
from letta.schemas.memory import ArchivalMemorySummary,
|
53
|
+
from letta.schemas.memory import ArchivalMemorySummary, Memory, RecallMemorySummary
|
54
54
|
from letta.schemas.message import Message, MessageCreate, MessageUpdate
|
55
55
|
from letta.schemas.organization import Organization
|
56
56
|
from letta.schemas.passage import Passage, PassageUpdate
|
@@ -969,6 +969,11 @@ class SyncServer(Server):
|
|
969
969
|
"""Return the memory of an agent (core memory)"""
|
970
970
|
return self.agent_manager.get_agent_by_id(agent_id=agent_id, actor=actor).memory
|
971
971
|
|
972
|
+
async def get_agent_memory_async(self, agent_id: str, actor: User) -> Memory:
|
973
|
+
"""Return the memory of an agent (core memory)"""
|
974
|
+
agent = await self.agent_manager.get_agent_by_id_async(agent_id=agent_id, actor=actor)
|
975
|
+
return agent.memory
|
976
|
+
|
972
977
|
def get_archival_memory_summary(self, agent_id: str, actor: User) -> ArchivalMemorySummary:
|
973
978
|
return ArchivalMemorySummary(size=self.agent_manager.passage_size(actor=actor, agent_id=agent_id))
|
974
979
|
|
@@ -1169,17 +1174,20 @@ class SyncServer(Server):
|
|
1169
1174
|
# rebuild system prompt for agent, potentially changed
|
1170
1175
|
return self.agent_manager.rebuild_system_prompt(agent_id=agent_id, actor=actor).memory
|
1171
1176
|
|
1172
|
-
def delete_source(self, source_id: str, actor: User):
|
1177
|
+
async def delete_source(self, source_id: str, actor: User):
|
1173
1178
|
"""Delete a data source"""
|
1174
|
-
self.source_manager.delete_source(source_id=source_id, actor=actor)
|
1179
|
+
await self.source_manager.delete_source(source_id=source_id, actor=actor)
|
1175
1180
|
|
1176
1181
|
# delete data from passage store
|
1182
|
+
# TODO: make async
|
1177
1183
|
passages_to_be_deleted = self.agent_manager.list_passages(actor=actor, source_id=source_id, limit=None)
|
1184
|
+
|
1185
|
+
# TODO: make this async
|
1178
1186
|
self.passage_manager.delete_passages(actor=actor, passages=passages_to_be_deleted)
|
1179
1187
|
|
1180
1188
|
# TODO: delete data from agent passage stores (?)
|
1181
1189
|
|
1182
|
-
def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
1190
|
+
async def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
1183
1191
|
|
1184
1192
|
# update job
|
1185
1193
|
job = self.job_manager.get_job_by_id(job_id, actor=actor)
|
@@ -1189,21 +1197,22 @@ class SyncServer(Server):
|
|
1189
1197
|
# try:
|
1190
1198
|
from letta.data_sources.connectors import DirectoryConnector
|
1191
1199
|
|
1192
|
-
|
1200
|
+
# TODO: move this into a thread
|
1201
|
+
source = await self.source_manager.get_source_by_id(source_id=source_id)
|
1193
1202
|
if source is None:
|
1194
1203
|
raise ValueError(f"Source {source_id} does not exist")
|
1195
1204
|
connector = DirectoryConnector(input_files=[file_path])
|
1196
|
-
num_passages, num_documents = self.load_data(user_id=source.created_by_id, source_name=source.name, connector=connector)
|
1205
|
+
num_passages, num_documents = await self.load_data(user_id=source.created_by_id, source_name=source.name, connector=connector)
|
1197
1206
|
|
1198
1207
|
# update all agents who have this source attached
|
1199
|
-
agent_states = self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
1208
|
+
agent_states = await self.source_manager.list_attached_agents(source_id=source_id, actor=actor)
|
1200
1209
|
for agent_state in agent_states:
|
1201
1210
|
agent_id = agent_state.id
|
1202
1211
|
|
1203
1212
|
# Attach source to agent
|
1204
|
-
curr_passage_size = self.agent_manager.
|
1213
|
+
curr_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
|
1205
1214
|
agent_state = self.agent_manager.attach_source(agent_id=agent_state.id, source_id=source_id, actor=actor)
|
1206
|
-
new_passage_size = self.agent_manager.
|
1215
|
+
new_passage_size = await self.agent_manager.passage_size_async(actor=actor, agent_id=agent_id)
|
1207
1216
|
assert new_passage_size >= curr_passage_size # in case empty files are added
|
1208
1217
|
|
1209
1218
|
# rebuild system prompt and force
|
@@ -1266,7 +1275,7 @@ class SyncServer(Server):
|
|
1266
1275
|
actor=actor,
|
1267
1276
|
)
|
1268
1277
|
|
1269
|
-
def load_data(
|
1278
|
+
async def load_data(
|
1270
1279
|
self,
|
1271
1280
|
user_id: str,
|
1272
1281
|
connector: DataConnector,
|
@@ -1277,12 +1286,12 @@ class SyncServer(Server):
|
|
1277
1286
|
|
1278
1287
|
# load data from a data source into the document store
|
1279
1288
|
user = self.user_manager.get_user_by_id(user_id=user_id)
|
1280
|
-
source = self.source_manager.get_source_by_name(source_name=source_name, actor=user)
|
1289
|
+
source = await self.source_manager.get_source_by_name(source_name=source_name, actor=user)
|
1281
1290
|
if source is None:
|
1282
1291
|
raise ValueError(f"Data source {source_name} does not exist for user {user_id}")
|
1283
1292
|
|
1284
1293
|
# load data into the document store
|
1285
|
-
passage_count, document_count = load_data(connector, source, self.passage_manager, self.source_manager, actor=user)
|
1294
|
+
passage_count, document_count = await load_data(connector, source, self.passage_manager, self.source_manager, actor=user)
|
1286
1295
|
return passage_count, document_count
|
1287
1296
|
|
1288
1297
|
def list_data_source_passages(self, user_id: str, source_id: str) -> List[Passage]:
|
@@ -1290,6 +1299,7 @@ class SyncServer(Server):
|
|
1290
1299
|
return self.agent_manager.list_passages(actor=self.user_manager.get_user_or_default(user_id=user_id), source_id=source_id)
|
1291
1300
|
|
1292
1301
|
def list_all_sources(self, actor: User) -> List[Source]:
|
1302
|
+
# TODO: legacy: remove
|
1293
1303
|
"""List all sources (w/ extra metadata) belonging to a user"""
|
1294
1304
|
|
1295
1305
|
sources = self.source_manager.list_sources(actor=actor)
|
@@ -1376,7 +1386,7 @@ class SyncServer(Server):
|
|
1376
1386
|
"""Asynchronously list available models with maximum concurrency"""
|
1377
1387
|
import asyncio
|
1378
1388
|
|
1379
|
-
providers = self.
|
1389
|
+
providers = await self.get_enabled_providers_async(
|
1380
1390
|
provider_category=provider_category,
|
1381
1391
|
provider_name=provider_name,
|
1382
1392
|
provider_type=provider_type,
|
@@ -1422,7 +1432,7 @@ class SyncServer(Server):
|
|
1422
1432
|
import asyncio
|
1423
1433
|
|
1424
1434
|
# Get all eligible providers first
|
1425
|
-
providers = self.
|
1435
|
+
providers = await self.get_enabled_providers_async(actor=actor)
|
1426
1436
|
|
1427
1437
|
# Fetch embedding models from each provider concurrently
|
1428
1438
|
async def get_provider_embedding_models(provider):
|
@@ -1475,6 +1485,35 @@ class SyncServer(Server):
|
|
1475
1485
|
|
1476
1486
|
return providers
|
1477
1487
|
|
1488
|
+
async def get_enabled_providers_async(
|
1489
|
+
self,
|
1490
|
+
actor: User,
|
1491
|
+
provider_category: Optional[List[ProviderCategory]] = None,
|
1492
|
+
provider_name: Optional[str] = None,
|
1493
|
+
provider_type: Optional[ProviderType] = None,
|
1494
|
+
) -> List[Provider]:
|
1495
|
+
providers = []
|
1496
|
+
if not provider_category or ProviderCategory.base in provider_category:
|
1497
|
+
providers_from_env = [p for p in self._enabled_providers]
|
1498
|
+
providers.extend(providers_from_env)
|
1499
|
+
|
1500
|
+
if not provider_category or ProviderCategory.byok in provider_category:
|
1501
|
+
providers_from_db = await self.provider_manager.list_providers_async(
|
1502
|
+
name=provider_name,
|
1503
|
+
provider_type=provider_type,
|
1504
|
+
actor=actor,
|
1505
|
+
)
|
1506
|
+
providers_from_db = [p.cast_to_subtype() for p in providers_from_db]
|
1507
|
+
providers.extend(providers_from_db)
|
1508
|
+
|
1509
|
+
if provider_name is not None:
|
1510
|
+
providers = [p for p in providers if p.name == provider_name]
|
1511
|
+
|
1512
|
+
if provider_type is not None:
|
1513
|
+
providers = [p for p in providers if p.provider_type == provider_type]
|
1514
|
+
|
1515
|
+
return providers
|
1516
|
+
|
1478
1517
|
@trace_method
|
1479
1518
|
def get_llm_config_from_handle(
|
1480
1519
|
self,
|
@@ -1613,14 +1652,6 @@ class SyncServer(Server):
|
|
1613
1652
|
def add_embedding_model(self, request: EmbeddingConfig) -> EmbeddingConfig:
|
1614
1653
|
"""Add a new embedding model"""
|
1615
1654
|
|
1616
|
-
def get_agent_context_window(self, agent_id: str, actor: User) -> ContextWindowOverview:
|
1617
|
-
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
1618
|
-
return letta_agent.get_context_window()
|
1619
|
-
|
1620
|
-
async def get_agent_context_window_async(self, agent_id: str, actor: User) -> ContextWindowOverview:
|
1621
|
-
letta_agent = self.load_agent(agent_id=agent_id, actor=actor)
|
1622
|
-
return await letta_agent.get_context_window_async()
|
1623
|
-
|
1624
1655
|
def run_tool_from_source(
|
1625
1656
|
self,
|
1626
1657
|
actor: User,
|