letta-nightly 0.7.21.dev20250522104246__py3-none-any.whl → 0.7.22.dev20250523104244__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.dev20250523104244.dist-info}/METADATA +1 -1
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.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.dev20250523104244.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,8 @@
|
|
1
1
|
import threading
|
2
2
|
from collections import defaultdict
|
3
3
|
|
4
|
+
from letta.tracing import trace_method
|
5
|
+
|
4
6
|
|
5
7
|
class PerAgentLockManager:
|
6
8
|
"""Manages per-agent locks."""
|
@@ -8,10 +10,12 @@ class PerAgentLockManager:
|
|
8
10
|
def __init__(self):
|
9
11
|
self.locks = defaultdict(threading.Lock)
|
10
12
|
|
13
|
+
@trace_method
|
11
14
|
def get_lock(self, agent_id: str) -> threading.Lock:
|
12
15
|
"""Retrieve the lock for a specific agent_id."""
|
13
16
|
return self.locks[agent_id]
|
14
17
|
|
18
|
+
@trace_method
|
15
19
|
def clear_lock(self, agent_id: str):
|
16
20
|
"""Optionally remove a lock if no longer needed (to prevent unbounded growth)."""
|
17
21
|
if agent_id in self.locks:
|
@@ -6,12 +6,14 @@ from letta.schemas.providers import Provider as PydanticProvider
|
|
6
6
|
from letta.schemas.providers import ProviderCheck, ProviderCreate, ProviderUpdate
|
7
7
|
from letta.schemas.user import User as PydanticUser
|
8
8
|
from letta.server.db import db_registry
|
9
|
+
from letta.tracing import trace_method
|
9
10
|
from letta.utils import enforce_types
|
10
11
|
|
11
12
|
|
12
13
|
class ProviderManager:
|
13
14
|
|
14
15
|
@enforce_types
|
16
|
+
@trace_method
|
15
17
|
def create_provider(self, request: ProviderCreate, actor: PydanticUser) -> PydanticProvider:
|
16
18
|
"""Create a new provider if it doesn't already exist."""
|
17
19
|
with db_registry.session() as session:
|
@@ -32,6 +34,7 @@ class ProviderManager:
|
|
32
34
|
return new_provider.to_pydantic()
|
33
35
|
|
34
36
|
@enforce_types
|
37
|
+
@trace_method
|
35
38
|
def update_provider(self, provider_id: str, provider_update: ProviderUpdate, actor: PydanticUser) -> PydanticProvider:
|
36
39
|
"""Update provider details."""
|
37
40
|
with db_registry.session() as session:
|
@@ -48,6 +51,7 @@ class ProviderManager:
|
|
48
51
|
return existing_provider.to_pydantic()
|
49
52
|
|
50
53
|
@enforce_types
|
54
|
+
@trace_method
|
51
55
|
def delete_provider_by_id(self, provider_id: str, actor: PydanticUser):
|
52
56
|
"""Delete a provider."""
|
53
57
|
with db_registry.session() as session:
|
@@ -62,6 +66,7 @@ class ProviderManager:
|
|
62
66
|
session.commit()
|
63
67
|
|
64
68
|
@enforce_types
|
69
|
+
@trace_method
|
65
70
|
def list_providers(
|
66
71
|
self,
|
67
72
|
actor: PydanticUser,
|
@@ -87,16 +92,45 @@ class ProviderManager:
|
|
87
92
|
return [provider.to_pydantic() for provider in providers]
|
88
93
|
|
89
94
|
@enforce_types
|
95
|
+
@trace_method
|
96
|
+
async def list_providers_async(
|
97
|
+
self,
|
98
|
+
actor: PydanticUser,
|
99
|
+
name: Optional[str] = None,
|
100
|
+
provider_type: Optional[ProviderType] = None,
|
101
|
+
after: Optional[str] = None,
|
102
|
+
limit: Optional[int] = 50,
|
103
|
+
) -> List[PydanticProvider]:
|
104
|
+
"""List all providers with optional pagination."""
|
105
|
+
filter_kwargs = {}
|
106
|
+
if name:
|
107
|
+
filter_kwargs["name"] = name
|
108
|
+
if provider_type:
|
109
|
+
filter_kwargs["provider_type"] = provider_type
|
110
|
+
async with db_registry.async_session() as session:
|
111
|
+
providers = await ProviderModel.list_async(
|
112
|
+
db_session=session,
|
113
|
+
after=after,
|
114
|
+
limit=limit,
|
115
|
+
actor=actor,
|
116
|
+
**filter_kwargs,
|
117
|
+
)
|
118
|
+
return [provider.to_pydantic() for provider in providers]
|
119
|
+
|
120
|
+
@enforce_types
|
121
|
+
@trace_method
|
90
122
|
def get_provider_id_from_name(self, provider_name: Union[str, None], actor: PydanticUser) -> Optional[str]:
|
91
123
|
providers = self.list_providers(name=provider_name, actor=actor)
|
92
124
|
return providers[0].id if providers else None
|
93
125
|
|
94
126
|
@enforce_types
|
127
|
+
@trace_method
|
95
128
|
def get_override_key(self, provider_name: Union[str, None], actor: PydanticUser) -> Optional[str]:
|
96
129
|
providers = self.list_providers(name=provider_name, actor=actor)
|
97
130
|
return providers[0].api_key if providers else None
|
98
131
|
|
99
132
|
@enforce_types
|
133
|
+
@trace_method
|
100
134
|
def check_provider_api_key(self, provider_check: ProviderCheck) -> None:
|
101
135
|
provider = PydanticProvider(
|
102
136
|
name=provider_check.provider_type.value,
|
@@ -12,6 +12,7 @@ from letta.schemas.sandbox_config import SandboxConfig as PydanticSandboxConfig
|
|
12
12
|
from letta.schemas.sandbox_config import SandboxConfigCreate, SandboxConfigUpdate, SandboxType
|
13
13
|
from letta.schemas.user import User as PydanticUser
|
14
14
|
from letta.server.db import db_registry
|
15
|
+
from letta.tracing import trace_method
|
15
16
|
from letta.utils import enforce_types, printd
|
16
17
|
|
17
18
|
logger = get_logger(__name__)
|
@@ -21,6 +22,7 @@ class SandboxConfigManager:
|
|
21
22
|
"""Manager class to handle business logic related to SandboxConfig and SandboxEnvironmentVariable."""
|
22
23
|
|
23
24
|
@enforce_types
|
25
|
+
@trace_method
|
24
26
|
def get_or_create_default_sandbox_config(self, sandbox_type: SandboxType, actor: PydanticUser) -> PydanticSandboxConfig:
|
25
27
|
sandbox_config = self.get_sandbox_config_by_type(sandbox_type, actor=actor)
|
26
28
|
if not sandbox_config:
|
@@ -38,6 +40,7 @@ class SandboxConfigManager:
|
|
38
40
|
return sandbox_config
|
39
41
|
|
40
42
|
@enforce_types
|
43
|
+
@trace_method
|
41
44
|
def create_or_update_sandbox_config(self, sandbox_config_create: SandboxConfigCreate, actor: PydanticUser) -> PydanticSandboxConfig:
|
42
45
|
"""Create or update a sandbox configuration based on the PydanticSandboxConfig schema."""
|
43
46
|
config = sandbox_config_create.config
|
@@ -71,6 +74,61 @@ class SandboxConfigManager:
|
|
71
74
|
return db_sandbox.to_pydantic()
|
72
75
|
|
73
76
|
@enforce_types
|
77
|
+
@trace_method
|
78
|
+
async def get_or_create_default_sandbox_config_async(self, sandbox_type: SandboxType, actor: PydanticUser) -> PydanticSandboxConfig:
|
79
|
+
sandbox_config = await self.get_sandbox_config_by_type_async(sandbox_type, actor=actor)
|
80
|
+
if not sandbox_config:
|
81
|
+
logger.debug(f"Creating new sandbox config of type {sandbox_type}, none found for organization {actor.organization_id}.")
|
82
|
+
|
83
|
+
# TODO: Add more sandbox types later
|
84
|
+
if sandbox_type == SandboxType.E2B:
|
85
|
+
default_config = {} # Empty
|
86
|
+
else:
|
87
|
+
# TODO: May want to move this to environment variables v.s. persisting in database
|
88
|
+
default_local_sandbox_path = LETTA_TOOL_EXECUTION_DIR
|
89
|
+
default_config = LocalSandboxConfig(sandbox_dir=default_local_sandbox_path).model_dump(exclude_none=True)
|
90
|
+
|
91
|
+
sandbox_config = await self.create_or_update_sandbox_config_async(SandboxConfigCreate(config=default_config), actor=actor)
|
92
|
+
return sandbox_config
|
93
|
+
|
94
|
+
@enforce_types
|
95
|
+
@trace_method
|
96
|
+
async def create_or_update_sandbox_config_async(
|
97
|
+
self, sandbox_config_create: SandboxConfigCreate, actor: PydanticUser
|
98
|
+
) -> PydanticSandboxConfig:
|
99
|
+
"""Create or update a sandbox configuration based on the PydanticSandboxConfig schema."""
|
100
|
+
config = sandbox_config_create.config
|
101
|
+
sandbox_type = config.type
|
102
|
+
sandbox_config = PydanticSandboxConfig(
|
103
|
+
type=sandbox_type, config=config.model_dump(exclude_none=True), organization_id=actor.organization_id
|
104
|
+
)
|
105
|
+
|
106
|
+
# Attempt to retrieve the existing sandbox configuration by type within the organization
|
107
|
+
db_sandbox = await self.get_sandbox_config_by_type_async(sandbox_config.type, actor=actor)
|
108
|
+
if db_sandbox:
|
109
|
+
# Prepare the update data, excluding fields that should not be reset
|
110
|
+
update_data = sandbox_config.model_dump(exclude_unset=True, exclude_none=True)
|
111
|
+
update_data = {key: value for key, value in update_data.items() if getattr(db_sandbox, key) != value}
|
112
|
+
|
113
|
+
# If there are changes, update the sandbox configuration
|
114
|
+
if update_data:
|
115
|
+
db_sandbox = await self.update_sandbox_config_async(db_sandbox.id, SandboxConfigUpdate(**update_data), actor)
|
116
|
+
else:
|
117
|
+
printd(
|
118
|
+
f"`create_or_update_sandbox_config` was called with user_id={actor.id}, organization_id={actor.organization_id}, "
|
119
|
+
f"type={sandbox_config.type}, but found existing configuration with nothing to update."
|
120
|
+
)
|
121
|
+
|
122
|
+
return db_sandbox
|
123
|
+
else:
|
124
|
+
# If the sandbox configuration doesn't exist, create a new one
|
125
|
+
async with db_registry.async_session() as session:
|
126
|
+
db_sandbox = SandboxConfigModel(**sandbox_config.model_dump(exclude_none=True))
|
127
|
+
await db_sandbox.create_async(session, actor=actor)
|
128
|
+
return db_sandbox.to_pydantic()
|
129
|
+
|
130
|
+
@enforce_types
|
131
|
+
@trace_method
|
74
132
|
def update_sandbox_config(
|
75
133
|
self, sandbox_config_id: str, sandbox_update: SandboxConfigUpdate, actor: PydanticUser
|
76
134
|
) -> PydanticSandboxConfig:
|
@@ -98,6 +156,35 @@ class SandboxConfigManager:
|
|
98
156
|
return sandbox.to_pydantic()
|
99
157
|
|
100
158
|
@enforce_types
|
159
|
+
@trace_method
|
160
|
+
async def update_sandbox_config_async(
|
161
|
+
self, sandbox_config_id: str, sandbox_update: SandboxConfigUpdate, actor: PydanticUser
|
162
|
+
) -> PydanticSandboxConfig:
|
163
|
+
"""Update an existing sandbox configuration."""
|
164
|
+
async with db_registry.async_session() as session:
|
165
|
+
sandbox = await SandboxConfigModel.read_async(db_session=session, identifier=sandbox_config_id, actor=actor)
|
166
|
+
# We need to check that the sandbox_update provided is the same type as the original sandbox
|
167
|
+
if sandbox.type != sandbox_update.config.type:
|
168
|
+
raise ValueError(
|
169
|
+
f"Mismatched type for sandbox config update: tried to update sandbox_config of type {sandbox.type} with config of type {sandbox_update.config.type}"
|
170
|
+
)
|
171
|
+
|
172
|
+
update_data = sandbox_update.model_dump(exclude_unset=True, exclude_none=True)
|
173
|
+
update_data = {key: value for key, value in update_data.items() if getattr(sandbox, key) != value}
|
174
|
+
|
175
|
+
if update_data:
|
176
|
+
for key, value in update_data.items():
|
177
|
+
setattr(sandbox, key, value)
|
178
|
+
await sandbox.update_async(db_session=session, actor=actor)
|
179
|
+
else:
|
180
|
+
printd(
|
181
|
+
f"`update_sandbox_config` called with user_id={actor.id}, organization_id={actor.organization_id}, "
|
182
|
+
f"name={sandbox.type}, but nothing to update."
|
183
|
+
)
|
184
|
+
return sandbox.to_pydantic()
|
185
|
+
|
186
|
+
@enforce_types
|
187
|
+
@trace_method
|
101
188
|
def delete_sandbox_config(self, sandbox_config_id: str, actor: PydanticUser) -> PydanticSandboxConfig:
|
102
189
|
"""Delete a sandbox configuration by its ID."""
|
103
190
|
with db_registry.session() as session:
|
@@ -106,6 +193,7 @@ class SandboxConfigManager:
|
|
106
193
|
return sandbox.to_pydantic()
|
107
194
|
|
108
195
|
@enforce_types
|
196
|
+
@trace_method
|
109
197
|
def list_sandbox_configs(
|
110
198
|
self,
|
111
199
|
actor: PydanticUser,
|
@@ -123,6 +211,7 @@ class SandboxConfigManager:
|
|
123
211
|
return [sandbox.to_pydantic() for sandbox in sandboxes]
|
124
212
|
|
125
213
|
@enforce_types
|
214
|
+
@trace_method
|
126
215
|
async def list_sandbox_configs_async(
|
127
216
|
self,
|
128
217
|
actor: PydanticUser,
|
@@ -140,6 +229,7 @@ class SandboxConfigManager:
|
|
140
229
|
return [sandbox.to_pydantic() for sandbox in sandboxes]
|
141
230
|
|
142
231
|
@enforce_types
|
232
|
+
@trace_method
|
143
233
|
def get_sandbox_config_by_id(self, sandbox_config_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
|
144
234
|
"""Retrieve a sandbox configuration by its ID."""
|
145
235
|
with db_registry.session() as session:
|
@@ -150,6 +240,7 @@ class SandboxConfigManager:
|
|
150
240
|
return None
|
151
241
|
|
152
242
|
@enforce_types
|
243
|
+
@trace_method
|
153
244
|
def get_sandbox_config_by_type(self, type: SandboxType, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
|
154
245
|
"""Retrieve a sandbox config by its type."""
|
155
246
|
with db_registry.session() as session:
|
@@ -167,6 +258,27 @@ class SandboxConfigManager:
|
|
167
258
|
return None
|
168
259
|
|
169
260
|
@enforce_types
|
261
|
+
@trace_method
|
262
|
+
async def get_sandbox_config_by_type_async(
|
263
|
+
self, type: SandboxType, actor: Optional[PydanticUser] = None
|
264
|
+
) -> Optional[PydanticSandboxConfig]:
|
265
|
+
"""Retrieve a sandbox config by its type."""
|
266
|
+
async with db_registry.async_session() as session:
|
267
|
+
try:
|
268
|
+
sandboxes = await SandboxConfigModel.list_async(
|
269
|
+
db_session=session,
|
270
|
+
type=type,
|
271
|
+
organization_id=actor.organization_id,
|
272
|
+
limit=1,
|
273
|
+
)
|
274
|
+
if sandboxes:
|
275
|
+
return sandboxes[0].to_pydantic()
|
276
|
+
return None
|
277
|
+
except NoResultFound:
|
278
|
+
return None
|
279
|
+
|
280
|
+
@enforce_types
|
281
|
+
@trace_method
|
170
282
|
def create_sandbox_env_var(
|
171
283
|
self, env_var_create: SandboxEnvironmentVariableCreate, sandbox_config_id: str, actor: PydanticUser
|
172
284
|
) -> PydanticEnvVar:
|
@@ -194,6 +306,7 @@ class SandboxConfigManager:
|
|
194
306
|
return env_var.to_pydantic()
|
195
307
|
|
196
308
|
@enforce_types
|
309
|
+
@trace_method
|
197
310
|
def update_sandbox_env_var(
|
198
311
|
self, env_var_id: str, env_var_update: SandboxEnvironmentVariableUpdate, actor: PydanticUser
|
199
312
|
) -> PydanticEnvVar:
|
@@ -215,6 +328,7 @@ class SandboxConfigManager:
|
|
215
328
|
return env_var.to_pydantic()
|
216
329
|
|
217
330
|
@enforce_types
|
331
|
+
@trace_method
|
218
332
|
def delete_sandbox_env_var(self, env_var_id: str, actor: PydanticUser) -> PydanticEnvVar:
|
219
333
|
"""Delete a sandbox environment variable by its ID."""
|
220
334
|
with db_registry.session() as session:
|
@@ -223,6 +337,7 @@ class SandboxConfigManager:
|
|
223
337
|
return env_var.to_pydantic()
|
224
338
|
|
225
339
|
@enforce_types
|
340
|
+
@trace_method
|
226
341
|
def list_sandbox_env_vars(
|
227
342
|
self,
|
228
343
|
sandbox_config_id: str,
|
@@ -242,6 +357,7 @@ class SandboxConfigManager:
|
|
242
357
|
return [env_var.to_pydantic() for env_var in env_vars]
|
243
358
|
|
244
359
|
@enforce_types
|
360
|
+
@trace_method
|
245
361
|
async def list_sandbox_env_vars_async(
|
246
362
|
self,
|
247
363
|
sandbox_config_id: str,
|
@@ -261,6 +377,7 @@ class SandboxConfigManager:
|
|
261
377
|
return [env_var.to_pydantic() for env_var in env_vars]
|
262
378
|
|
263
379
|
@enforce_types
|
380
|
+
@trace_method
|
264
381
|
def list_sandbox_env_vars_by_key(
|
265
382
|
self, key: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
|
266
383
|
) -> List[PydanticEnvVar]:
|
@@ -276,6 +393,7 @@ class SandboxConfigManager:
|
|
276
393
|
return [env_var.to_pydantic() for env_var in env_vars]
|
277
394
|
|
278
395
|
@enforce_types
|
396
|
+
@trace_method
|
279
397
|
def get_sandbox_env_vars_as_dict(
|
280
398
|
self, sandbox_config_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
|
281
399
|
) -> Dict[str, str]:
|
@@ -286,6 +404,18 @@ class SandboxConfigManager:
|
|
286
404
|
return result
|
287
405
|
|
288
406
|
@enforce_types
|
407
|
+
@trace_method
|
408
|
+
async def get_sandbox_env_vars_as_dict_async(
|
409
|
+
self, sandbox_config_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
|
410
|
+
) -> Dict[str, str]:
|
411
|
+
env_vars = await self.list_sandbox_env_vars_async(sandbox_config_id, actor, after, limit)
|
412
|
+
result = {}
|
413
|
+
for env_var in env_vars:
|
414
|
+
result[env_var.key] = env_var.value
|
415
|
+
return result
|
416
|
+
|
417
|
+
@enforce_types
|
418
|
+
@trace_method
|
289
419
|
def get_sandbox_env_var_by_key_and_sandbox_config_id(
|
290
420
|
self, key: str, sandbox_config_id: str, actor: Optional[PydanticUser] = None
|
291
421
|
) -> Optional[PydanticEnvVar]:
|
letta/services/source_manager.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import List, Optional
|
2
3
|
|
3
4
|
from letta.orm.errors import NoResultFound
|
@@ -9,6 +10,7 @@ from letta.schemas.source import Source as PydanticSource
|
|
9
10
|
from letta.schemas.source import SourceUpdate
|
10
11
|
from letta.schemas.user import User as PydanticUser
|
11
12
|
from letta.server.db import db_registry
|
13
|
+
from letta.tracing import trace_method
|
12
14
|
from letta.utils import enforce_types, printd
|
13
15
|
|
14
16
|
|
@@ -16,25 +18,27 @@ class SourceManager:
|
|
16
18
|
"""Manager class to handle business logic related to Sources."""
|
17
19
|
|
18
20
|
@enforce_types
|
19
|
-
|
21
|
+
@trace_method
|
22
|
+
async def create_source(self, source: PydanticSource, actor: PydanticUser) -> PydanticSource:
|
20
23
|
"""Create a new source based on the PydanticSource schema."""
|
21
24
|
# Try getting the source first by id
|
22
|
-
db_source = self.get_source_by_id(source.id, actor=actor)
|
25
|
+
db_source = await self.get_source_by_id(source.id, actor=actor)
|
23
26
|
if db_source:
|
24
27
|
return db_source
|
25
28
|
else:
|
26
|
-
with db_registry.
|
29
|
+
async with db_registry.async_session() as session:
|
27
30
|
# Provide default embedding config if not given
|
28
31
|
source.organization_id = actor.organization_id
|
29
32
|
source = SourceModel(**source.model_dump(to_orm=True, exclude_none=True))
|
30
|
-
source.
|
33
|
+
await source.create_async(session, actor=actor)
|
31
34
|
return source.to_pydantic()
|
32
35
|
|
33
36
|
@enforce_types
|
34
|
-
|
37
|
+
@trace_method
|
38
|
+
async def update_source(self, source_id: str, source_update: SourceUpdate, actor: PydanticUser) -> PydanticSource:
|
35
39
|
"""Update a source by its ID with the given SourceUpdate object."""
|
36
|
-
with db_registry.
|
37
|
-
source = SourceModel.
|
40
|
+
async with db_registry.async_session() as session:
|
41
|
+
source = await SourceModel.read_async(db_session=session, identifier=source_id, actor=actor)
|
38
42
|
|
39
43
|
# get update dictionary
|
40
44
|
update_data = source_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
|
@@ -53,18 +57,22 @@ class SourceManager:
|
|
53
57
|
return source.to_pydantic()
|
54
58
|
|
55
59
|
@enforce_types
|
56
|
-
|
60
|
+
@trace_method
|
61
|
+
async def delete_source(self, source_id: str, actor: PydanticUser) -> PydanticSource:
|
57
62
|
"""Delete a source by its ID."""
|
58
|
-
with db_registry.
|
59
|
-
source = SourceModel.
|
60
|
-
source.
|
63
|
+
async with db_registry.async_session() as session:
|
64
|
+
source = await SourceModel.read_async(db_session=session, identifier=source_id)
|
65
|
+
await source.hard_delete_async(db_session=session, actor=actor)
|
61
66
|
return source.to_pydantic()
|
62
67
|
|
63
68
|
@enforce_types
|
64
|
-
|
69
|
+
@trace_method
|
70
|
+
async def list_sources(
|
71
|
+
self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50, **kwargs
|
72
|
+
) -> List[PydanticSource]:
|
65
73
|
"""List all sources with optional pagination."""
|
66
|
-
with db_registry.
|
67
|
-
sources = SourceModel.
|
74
|
+
async with db_registry.async_session() as session:
|
75
|
+
sources = await SourceModel.list_async(
|
68
76
|
db_session=session,
|
69
77
|
after=after,
|
70
78
|
limit=limit,
|
@@ -74,18 +82,17 @@ class SourceManager:
|
|
74
82
|
return [source.to_pydantic() for source in sources]
|
75
83
|
|
76
84
|
@enforce_types
|
77
|
-
|
78
|
-
|
79
|
-
actor: PydanticUser,
|
80
|
-
) -> int:
|
85
|
+
@trace_method
|
86
|
+
async def size(self, actor: PydanticUser) -> int:
|
81
87
|
"""
|
82
88
|
Get the total count of sources for the given user.
|
83
89
|
"""
|
84
|
-
with db_registry.
|
85
|
-
return SourceModel.
|
90
|
+
async with db_registry.async_session() as session:
|
91
|
+
return await SourceModel.size_async(db_session=session, actor=actor)
|
86
92
|
|
87
93
|
@enforce_types
|
88
|
-
|
94
|
+
@trace_method
|
95
|
+
async def list_attached_agents(self, source_id: str, actor: Optional[PydanticUser] = None) -> List[PydanticAgentState]:
|
89
96
|
"""
|
90
97
|
Lists all agents that have the specified source attached.
|
91
98
|
|
@@ -96,30 +103,33 @@ class SourceManager:
|
|
96
103
|
Returns:
|
97
104
|
List[PydanticAgentState]: List of agents that have this source attached
|
98
105
|
"""
|
99
|
-
with db_registry.
|
106
|
+
async with db_registry.async_session() as session:
|
100
107
|
# Verify source exists and user has permission to access it
|
101
|
-
source = SourceModel.
|
108
|
+
source = await SourceModel.read_async(db_session=session, identifier=source_id, actor=actor)
|
102
109
|
|
103
110
|
# The agents relationship is already loaded due to lazy="selectin" in the Source model
|
104
111
|
# and will be properly filtered by organization_id due to the OrganizationMixin
|
105
|
-
|
112
|
+
agents_orm = source.agents
|
113
|
+
return await asyncio.gather(*[agent.to_pydantic_async() for agent in agents_orm])
|
106
114
|
|
107
115
|
# TODO: We make actor optional for now, but should most likely be enforced due to security reasons
|
108
116
|
@enforce_types
|
109
|
-
|
117
|
+
@trace_method
|
118
|
+
async def get_source_by_id(self, source_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSource]:
|
110
119
|
"""Retrieve a source by its ID."""
|
111
|
-
with db_registry.
|
120
|
+
async with db_registry.async_session() as session:
|
112
121
|
try:
|
113
|
-
source = SourceModel.
|
122
|
+
source = await SourceModel.read_async(db_session=session, identifier=source_id, actor=actor)
|
114
123
|
return source.to_pydantic()
|
115
124
|
except NoResultFound:
|
116
125
|
return None
|
117
126
|
|
118
127
|
@enforce_types
|
119
|
-
|
128
|
+
@trace_method
|
129
|
+
async def get_source_by_name(self, source_name: str, actor: PydanticUser) -> Optional[PydanticSource]:
|
120
130
|
"""Retrieve a source by its name."""
|
121
|
-
with db_registry.
|
122
|
-
sources = SourceModel.
|
131
|
+
async with db_registry.async_session() as session:
|
132
|
+
sources = await SourceModel.list_async(
|
123
133
|
db_session=session,
|
124
134
|
name=source_name,
|
125
135
|
organization_id=actor.organization_id,
|
@@ -131,44 +141,49 @@ class SourceManager:
|
|
131
141
|
return sources[0].to_pydantic()
|
132
142
|
|
133
143
|
@enforce_types
|
134
|
-
|
144
|
+
@trace_method
|
145
|
+
async def create_file(self, file_metadata: PydanticFileMetadata, actor: PydanticUser) -> PydanticFileMetadata:
|
135
146
|
"""Create a new file based on the PydanticFileMetadata schema."""
|
136
|
-
db_file = self.get_file_by_id(file_metadata.id, actor=actor)
|
147
|
+
db_file = await self.get_file_by_id(file_metadata.id, actor=actor)
|
137
148
|
if db_file:
|
138
149
|
return db_file
|
139
150
|
else:
|
140
|
-
with db_registry.
|
151
|
+
async with db_registry.async_session() as session:
|
141
152
|
file_metadata.organization_id = actor.organization_id
|
142
153
|
file_metadata = FileMetadataModel(**file_metadata.model_dump(to_orm=True, exclude_none=True))
|
143
|
-
file_metadata.
|
154
|
+
await file_metadata.create_async(session, actor=actor)
|
144
155
|
return file_metadata.to_pydantic()
|
145
156
|
|
146
157
|
# TODO: We make actor optional for now, but should most likely be enforced due to security reasons
|
147
158
|
@enforce_types
|
148
|
-
|
159
|
+
@trace_method
|
160
|
+
async def get_file_by_id(self, file_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticFileMetadata]:
|
149
161
|
"""Retrieve a file by its ID."""
|
150
|
-
with db_registry.
|
162
|
+
async with db_registry.async_session() as session:
|
151
163
|
try:
|
152
|
-
file = FileMetadataModel.
|
164
|
+
file = await FileMetadataModel.read_async(db_session=session, identifier=file_id, actor=actor)
|
153
165
|
return file.to_pydantic()
|
154
166
|
except NoResultFound:
|
155
167
|
return None
|
156
168
|
|
157
169
|
@enforce_types
|
158
|
-
|
170
|
+
@trace_method
|
171
|
+
async def list_files(
|
159
172
|
self, source_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
|
160
173
|
) -> List[PydanticFileMetadata]:
|
161
174
|
"""List all files with optional pagination."""
|
162
|
-
with db_registry.
|
163
|
-
|
175
|
+
async with db_registry.async_session() as session:
|
176
|
+
files_all = await FileMetadataModel.list_async(db_session=session, organization_id=actor.organization_id, source_id=source_id)
|
177
|
+
files = await FileMetadataModel.list_async(
|
164
178
|
db_session=session, after=after, limit=limit, organization_id=actor.organization_id, source_id=source_id
|
165
179
|
)
|
166
180
|
return [file.to_pydantic() for file in files]
|
167
181
|
|
168
182
|
@enforce_types
|
169
|
-
|
183
|
+
@trace_method
|
184
|
+
async def delete_file(self, file_id: str, actor: PydanticUser) -> PydanticFileMetadata:
|
170
185
|
"""Delete a file by its ID."""
|
171
|
-
with db_registry.
|
172
|
-
file = FileMetadataModel.
|
173
|
-
file.
|
186
|
+
async with db_registry.async_session() as session:
|
187
|
+
file = await FileMetadataModel.read_async(db_session=session, identifier=file_id)
|
188
|
+
await file.hard_delete_async(db_session=session, actor=actor)
|
174
189
|
return file.to_pydantic()
|
letta/services/step_manager.py
CHANGED
@@ -14,13 +14,14 @@ from letta.schemas.step import Step as PydanticStep
|
|
14
14
|
from letta.schemas.user import User as PydanticUser
|
15
15
|
from letta.server.db import db_registry
|
16
16
|
from letta.services.helpers.noop_helper import singleton
|
17
|
-
from letta.tracing import get_trace_id
|
17
|
+
from letta.tracing import get_trace_id, trace_method
|
18
18
|
from letta.utils import enforce_types
|
19
19
|
|
20
20
|
|
21
21
|
class StepManager:
|
22
22
|
|
23
23
|
@enforce_types
|
24
|
+
@trace_method
|
24
25
|
def list_steps(
|
25
26
|
self,
|
26
27
|
actor: PydanticUser,
|
@@ -54,6 +55,7 @@ class StepManager:
|
|
54
55
|
return [step.to_pydantic() for step in steps]
|
55
56
|
|
56
57
|
@enforce_types
|
58
|
+
@trace_method
|
57
59
|
def log_step(
|
58
60
|
self,
|
59
61
|
actor: PydanticUser,
|
@@ -96,6 +98,7 @@ class StepManager:
|
|
96
98
|
return new_step.to_pydantic()
|
97
99
|
|
98
100
|
@enforce_types
|
101
|
+
@trace_method
|
99
102
|
async def log_step_async(
|
100
103
|
self,
|
101
104
|
actor: PydanticUser,
|
@@ -138,12 +141,14 @@ class StepManager:
|
|
138
141
|
return new_step.to_pydantic()
|
139
142
|
|
140
143
|
@enforce_types
|
144
|
+
@trace_method
|
141
145
|
def get_step(self, step_id: str, actor: PydanticUser) -> PydanticStep:
|
142
146
|
with db_registry.session() as session:
|
143
147
|
step = StepModel.read(db_session=session, identifier=step_id, actor=actor)
|
144
148
|
return step.to_pydantic()
|
145
149
|
|
146
150
|
@enforce_types
|
151
|
+
@trace_method
|
147
152
|
def update_step_transaction_id(self, actor: PydanticUser, step_id: str, transaction_id: str) -> PydanticStep:
|
148
153
|
"""Update the transaction ID for a step.
|
149
154
|
|
@@ -236,6 +241,7 @@ class NoopStepManager(StepManager):
|
|
236
241
|
"""
|
237
242
|
|
238
243
|
@enforce_types
|
244
|
+
@trace_method
|
239
245
|
def log_step(
|
240
246
|
self,
|
241
247
|
actor: PydanticUser,
|
@@ -253,6 +259,7 @@ class NoopStepManager(StepManager):
|
|
253
259
|
return
|
254
260
|
|
255
261
|
@enforce_types
|
262
|
+
@trace_method
|
256
263
|
async def log_step_async(
|
257
264
|
self,
|
258
265
|
actor: PydanticUser,
|