letta-nightly 0.5.4.dev20241121104201__py3-none-any.whl → 0.5.4.dev20241123104112__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.
- letta/agent.py +48 -25
- letta/agent_store/db.py +1 -1
- letta/client/client.py +361 -7
- letta/constants.py +5 -14
- letta/functions/helpers.py +5 -42
- letta/functions/schema_generator.py +24 -4
- letta/local_llm/utils.py +6 -3
- letta/log.py +7 -9
- letta/metadata.py +17 -4
- letta/orm/__init__.py +2 -0
- letta/orm/block.py +5 -2
- letta/orm/blocks_agents.py +29 -0
- letta/orm/mixins.py +8 -0
- letta/orm/organization.py +8 -1
- letta/orm/sandbox_config.py +56 -0
- letta/orm/sqlalchemy_base.py +9 -3
- letta/schemas/block.py +15 -1
- letta/schemas/blocks_agents.py +32 -0
- letta/schemas/letta_base.py +9 -0
- letta/schemas/memory.py +42 -8
- letta/schemas/sandbox_config.py +114 -0
- letta/schemas/tool.py +2 -45
- letta/server/rest_api/routers/v1/__init__.py +4 -9
- letta/server/rest_api/routers/v1/agents.py +85 -1
- letta/server/rest_api/routers/v1/sandbox_configs.py +108 -0
- letta/server/rest_api/routers/v1/tools.py +3 -5
- letta/server/rest_api/utils.py +6 -0
- letta/server/server.py +159 -12
- letta/services/block_manager.py +3 -1
- letta/services/blocks_agents_manager.py +84 -0
- letta/services/sandbox_config_manager.py +256 -0
- letta/services/tool_execution_sandbox.py +326 -0
- letta/services/tool_manager.py +10 -10
- letta/services/tool_sandbox_env/.gitkeep +0 -0
- letta/settings.py +4 -0
- {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/METADATA +28 -27
- {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/RECORD +40 -31
- {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -9,6 +9,7 @@ from tqdm import tqdm
|
|
|
9
9
|
|
|
10
10
|
from letta.agent_store.storage import StorageConnector
|
|
11
11
|
from letta.constants import (
|
|
12
|
+
BASE_TOOLS,
|
|
12
13
|
CLI_WARNING_PREFIX,
|
|
13
14
|
FIRST_MESSAGE_ATTEMPTS,
|
|
14
15
|
FUNC_FAILED_HEARTBEAT_MESSAGE,
|
|
@@ -49,6 +50,7 @@ from letta.schemas.tool_rule import TerminalToolRule
|
|
|
49
50
|
from letta.schemas.usage import LettaUsageStatistics
|
|
50
51
|
from letta.services.block_manager import BlockManager
|
|
51
52
|
from letta.services.source_manager import SourceManager
|
|
53
|
+
from letta.services.tool_execution_sandbox import ToolExecutionSandbox
|
|
52
54
|
from letta.services.user_manager import UserManager
|
|
53
55
|
from letta.streaming_interface import StreamingRefreshCLIInterface
|
|
54
56
|
from letta.system import (
|
|
@@ -725,9 +727,27 @@ class Agent(BaseAgent):
|
|
|
725
727
|
if isinstance(function_args[name], dict):
|
|
726
728
|
function_args[name] = spec[name](**function_args[name])
|
|
727
729
|
|
|
728
|
-
|
|
730
|
+
# TODO: This needs to be rethought, how do we allow functions that modify agent state/db?
|
|
731
|
+
# TODO: There should probably be two types of tools: stateless/stateful
|
|
732
|
+
|
|
733
|
+
if function_name in BASE_TOOLS:
|
|
734
|
+
function_args["self"] = self # need to attach self to arg since it's dynamically linked
|
|
735
|
+
function_response = function_to_call(**function_args)
|
|
736
|
+
else:
|
|
737
|
+
# execute tool in a sandbox
|
|
738
|
+
# TODO: allow agent_state to specify which sandbox to execute tools in
|
|
739
|
+
sandbox_run_result = ToolExecutionSandbox(function_name, function_args, self.agent_state.user_id).run(
|
|
740
|
+
agent_state=self.agent_state
|
|
741
|
+
)
|
|
742
|
+
function_response, updated_agent_state = sandbox_run_result.func_return, sandbox_run_result.agent_state
|
|
743
|
+
# update agent state
|
|
744
|
+
if self.agent_state != updated_agent_state and updated_agent_state is not None:
|
|
745
|
+
self.agent_state = updated_agent_state
|
|
746
|
+
self.memory = self.agent_state.memory # TODO: don't duplicate
|
|
747
|
+
|
|
748
|
+
# rebuild memory
|
|
749
|
+
self.rebuild_memory()
|
|
729
750
|
|
|
730
|
-
function_response = function_to_call(**function_args)
|
|
731
751
|
if function_name in ["conversation_search", "conversation_search_date", "archival_memory_search"]:
|
|
732
752
|
# with certain functions we rely on the paging mechanism to handle overflow
|
|
733
753
|
truncate = False
|
|
@@ -747,6 +767,7 @@ class Agent(BaseAgent):
|
|
|
747
767
|
error_msg_user = f"{error_msg}\n{traceback.format_exc()}"
|
|
748
768
|
printd(error_msg_user)
|
|
749
769
|
function_response = package_function_response(False, error_msg)
|
|
770
|
+
# TODO: truncate error message somehow
|
|
750
771
|
messages.append(
|
|
751
772
|
Message.dict_to_message(
|
|
752
773
|
agent_id=self.agent_state.id,
|
|
@@ -1208,6 +1229,30 @@ class Agent(BaseAgent):
|
|
|
1208
1229
|
new_messages = [new_system_message_obj] + self._messages[1:] # swap index 0 (system)
|
|
1209
1230
|
self._messages = new_messages
|
|
1210
1231
|
|
|
1232
|
+
def update_memory_blocks_from_db(self):
|
|
1233
|
+
for block in self.memory.to_dict()["memory"].values():
|
|
1234
|
+
if block.get("templates", False):
|
|
1235
|
+
# we don't expect to update shared memory blocks that
|
|
1236
|
+
# are templates. this is something we could update in the
|
|
1237
|
+
# future if we expect templates to change often.
|
|
1238
|
+
continue
|
|
1239
|
+
block_id = block.get("id")
|
|
1240
|
+
|
|
1241
|
+
# TODO: This is really hacky and we should probably figure out how to
|
|
1242
|
+
db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
|
|
1243
|
+
if db_block is None:
|
|
1244
|
+
# this case covers if someone has deleted a shared block by interacting
|
|
1245
|
+
# with some other agent.
|
|
1246
|
+
# in that case we should remove this shared block from the agent currently being
|
|
1247
|
+
# evaluated.
|
|
1248
|
+
printd(f"removing block: {block_id=}")
|
|
1249
|
+
continue
|
|
1250
|
+
if not isinstance(db_block.value, str):
|
|
1251
|
+
printd(f"skipping block update, unexpected value: {block_id=}")
|
|
1252
|
+
continue
|
|
1253
|
+
# TODO: we may want to update which columns we're updating from shared memory e.g. the limit
|
|
1254
|
+
self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
|
|
1255
|
+
|
|
1211
1256
|
def rebuild_memory(self, force=False, update_timestamp=True, ms: Optional[MetadataStore] = None):
|
|
1212
1257
|
"""Rebuilds the system message with the latest memory object and any shared memory block updates"""
|
|
1213
1258
|
curr_system_message = self.messages[0] # this is the system + memory bank, not just the system prompt
|
|
@@ -1219,28 +1264,7 @@ class Agent(BaseAgent):
|
|
|
1219
1264
|
return
|
|
1220
1265
|
|
|
1221
1266
|
if ms:
|
|
1222
|
-
|
|
1223
|
-
if block.get("templates", False):
|
|
1224
|
-
# we don't expect to update shared memory blocks that
|
|
1225
|
-
# are templates. this is something we could update in the
|
|
1226
|
-
# future if we expect templates to change often.
|
|
1227
|
-
continue
|
|
1228
|
-
block_id = block.get("id")
|
|
1229
|
-
|
|
1230
|
-
# TODO: This is really hacky and we should probably figure out how to
|
|
1231
|
-
db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
|
|
1232
|
-
if db_block is None:
|
|
1233
|
-
# this case covers if someone has deleted a shared block by interacting
|
|
1234
|
-
# with some other agent.
|
|
1235
|
-
# in that case we should remove this shared block from the agent currently being
|
|
1236
|
-
# evaluated.
|
|
1237
|
-
printd(f"removing block: {block_id=}")
|
|
1238
|
-
continue
|
|
1239
|
-
if not isinstance(db_block.value, str):
|
|
1240
|
-
printd(f"skipping block update, unexpected value: {block_id=}")
|
|
1241
|
-
continue
|
|
1242
|
-
# TODO: we may want to update which columns we're updating from shared memory e.g. the limit
|
|
1243
|
-
self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
|
|
1267
|
+
self.update_memory_blocks_from_db()
|
|
1244
1268
|
|
|
1245
1269
|
# If the memory didn't update, we probably don't want to update the timestamp inside
|
|
1246
1270
|
# For example, if we're doing a system prompt swap, this should probably be False
|
|
@@ -1281,7 +1305,6 @@ class Agent(BaseAgent):
|
|
|
1281
1305
|
assert isinstance(new_system_prompt, str)
|
|
1282
1306
|
|
|
1283
1307
|
if new_system_prompt == self.system:
|
|
1284
|
-
input("same???")
|
|
1285
1308
|
return
|
|
1286
1309
|
|
|
1287
1310
|
self.system = new_system_prompt
|
letta/agent_store/db.py
CHANGED
|
@@ -433,7 +433,7 @@ class PostgresStorageConnector(SQLStorageConnector):
|
|
|
433
433
|
else:
|
|
434
434
|
db_record = self.db_model(**record.dict())
|
|
435
435
|
session.add(db_record)
|
|
436
|
-
print(f"Added record with id {record.id}")
|
|
436
|
+
# print(f"Added record with id {record.id}")
|
|
437
437
|
session.commit()
|
|
438
438
|
|
|
439
439
|
added_ids.append(record.id)
|
letta/client/client.py
CHANGED
|
@@ -39,6 +39,16 @@ from letta.schemas.message import Message, MessageCreate, UpdateMessage
|
|
|
39
39
|
from letta.schemas.openai.chat_completions import ToolCall
|
|
40
40
|
from letta.schemas.organization import Organization
|
|
41
41
|
from letta.schemas.passage import Passage
|
|
42
|
+
from letta.schemas.sandbox_config import (
|
|
43
|
+
E2BSandboxConfig,
|
|
44
|
+
LocalSandboxConfig,
|
|
45
|
+
SandboxConfig,
|
|
46
|
+
SandboxConfigCreate,
|
|
47
|
+
SandboxConfigUpdate,
|
|
48
|
+
SandboxEnvironmentVariable,
|
|
49
|
+
SandboxEnvironmentVariableCreate,
|
|
50
|
+
SandboxEnvironmentVariableUpdate,
|
|
51
|
+
)
|
|
42
52
|
from letta.schemas.source import Source, SourceCreate, SourceUpdate
|
|
43
53
|
from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
|
|
44
54
|
from letta.schemas.tool_rule import BaseToolRule
|
|
@@ -190,9 +200,6 @@ class AbstractClient(object):
|
|
|
190
200
|
def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
191
201
|
raise NotImplementedError
|
|
192
202
|
|
|
193
|
-
def load_crewai_tool(self, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
194
|
-
raise NotImplementedError
|
|
195
|
-
|
|
196
203
|
def load_composio_tool(self, action: "ActionType") -> Tool:
|
|
197
204
|
raise NotImplementedError
|
|
198
205
|
|
|
@@ -299,6 +306,112 @@ class AbstractClient(object):
|
|
|
299
306
|
def delete_org(self, org_id: str) -> Organization:
|
|
300
307
|
raise NotImplementedError
|
|
301
308
|
|
|
309
|
+
def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
310
|
+
"""
|
|
311
|
+
Create a new sandbox configuration.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
config (Union[LocalSandboxConfig, E2BSandboxConfig]): The sandbox settings.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
SandboxConfig: The created sandbox configuration.
|
|
318
|
+
"""
|
|
319
|
+
raise NotImplementedError
|
|
320
|
+
|
|
321
|
+
def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
322
|
+
"""
|
|
323
|
+
Update an existing sandbox configuration.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
sandbox_config_id (str): The ID of the sandbox configuration to update.
|
|
327
|
+
config (Union[LocalSandboxConfig, E2BSandboxConfig]): The updated sandbox settings.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
SandboxConfig: The updated sandbox configuration.
|
|
331
|
+
"""
|
|
332
|
+
raise NotImplementedError
|
|
333
|
+
|
|
334
|
+
def delete_sandbox_config(self, sandbox_config_id: str) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Delete a sandbox configuration.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
sandbox_config_id (str): The ID of the sandbox configuration to delete.
|
|
340
|
+
"""
|
|
341
|
+
raise NotImplementedError
|
|
342
|
+
|
|
343
|
+
def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
|
|
344
|
+
"""
|
|
345
|
+
List all sandbox configurations.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
limit (int, optional): The maximum number of sandbox configurations to return. Defaults to 50.
|
|
349
|
+
cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
List[SandboxConfig]: A list of sandbox configurations.
|
|
353
|
+
"""
|
|
354
|
+
raise NotImplementedError
|
|
355
|
+
|
|
356
|
+
def create_sandbox_env_var(
|
|
357
|
+
self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
|
|
358
|
+
) -> SandboxEnvironmentVariable:
|
|
359
|
+
"""
|
|
360
|
+
Create a new environment variable for a sandbox configuration.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
sandbox_config_id (str): The ID of the sandbox configuration to associate the environment variable with.
|
|
364
|
+
key (str): The name of the environment variable.
|
|
365
|
+
value (str): The value of the environment variable.
|
|
366
|
+
description (Optional[str], optional): A description of the environment variable. Defaults to None.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
SandboxEnvironmentVariable: The created environment variable.
|
|
370
|
+
"""
|
|
371
|
+
raise NotImplementedError
|
|
372
|
+
|
|
373
|
+
def update_sandbox_env_var(
|
|
374
|
+
self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
|
|
375
|
+
) -> SandboxEnvironmentVariable:
|
|
376
|
+
"""
|
|
377
|
+
Update an existing environment variable.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
env_var_id (str): The ID of the environment variable to update.
|
|
381
|
+
key (Optional[str], optional): The updated name of the environment variable. Defaults to None.
|
|
382
|
+
value (Optional[str], optional): The updated value of the environment variable. Defaults to None.
|
|
383
|
+
description (Optional[str], optional): The updated description of the environment variable. Defaults to None.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
SandboxEnvironmentVariable: The updated environment variable.
|
|
387
|
+
"""
|
|
388
|
+
raise NotImplementedError
|
|
389
|
+
|
|
390
|
+
def delete_sandbox_env_var(self, env_var_id: str) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Delete an environment variable by its ID.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
env_var_id (str): The ID of the environment variable to delete.
|
|
396
|
+
"""
|
|
397
|
+
raise NotImplementedError
|
|
398
|
+
|
|
399
|
+
def list_sandbox_env_vars(
|
|
400
|
+
self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
|
|
401
|
+
) -> List[SandboxEnvironmentVariable]:
|
|
402
|
+
"""
|
|
403
|
+
List all environment variables associated with a sandbox configuration.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
sandbox_config_id (str): The ID of the sandbox configuration to retrieve environment variables for.
|
|
407
|
+
limit (int, optional): The maximum number of environment variables to return. Defaults to 50.
|
|
408
|
+
cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
List[SandboxEnvironmentVariable]: A list of environment variables.
|
|
412
|
+
"""
|
|
413
|
+
raise NotImplementedError
|
|
414
|
+
|
|
302
415
|
|
|
303
416
|
class RESTClient(AbstractClient):
|
|
304
417
|
"""
|
|
@@ -899,8 +1012,8 @@ class RESTClient(AbstractClient):
|
|
|
899
1012
|
else:
|
|
900
1013
|
return Block(**response.json())
|
|
901
1014
|
|
|
902
|
-
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
|
|
903
|
-
request = BlockUpdate(id=block_id, template_name=name, value=text)
|
|
1015
|
+
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
|
|
1016
|
+
request = BlockUpdate(id=block_id, template_name=name, value=text, limit=limit if limit else self.get_block(block_id).limit)
|
|
904
1017
|
response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
|
|
905
1018
|
if response.status_code != 200:
|
|
906
1019
|
raise ValueError(f"Failed to update block: {response.text}")
|
|
@@ -1568,6 +1681,161 @@ class RESTClient(AbstractClient):
|
|
|
1568
1681
|
# Parse and return the deleted organization
|
|
1569
1682
|
return Organization(**response.json())
|
|
1570
1683
|
|
|
1684
|
+
def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
1685
|
+
"""
|
|
1686
|
+
Create a new sandbox configuration.
|
|
1687
|
+
"""
|
|
1688
|
+
payload = {
|
|
1689
|
+
"config": config.model_dump(),
|
|
1690
|
+
}
|
|
1691
|
+
response = requests.post(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, json=payload)
|
|
1692
|
+
if response.status_code != 200:
|
|
1693
|
+
raise ValueError(f"Failed to create sandbox config: {response.text}")
|
|
1694
|
+
return SandboxConfig(**response.json())
|
|
1695
|
+
|
|
1696
|
+
def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
1697
|
+
"""
|
|
1698
|
+
Update an existing sandbox configuration.
|
|
1699
|
+
"""
|
|
1700
|
+
payload = {
|
|
1701
|
+
"config": config.model_dump(),
|
|
1702
|
+
}
|
|
1703
|
+
response = requests.patch(
|
|
1704
|
+
f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}",
|
|
1705
|
+
headers=self.headers,
|
|
1706
|
+
json=payload,
|
|
1707
|
+
)
|
|
1708
|
+
if response.status_code != 200:
|
|
1709
|
+
raise ValueError(f"Failed to update sandbox config with ID '{sandbox_config_id}': {response.text}")
|
|
1710
|
+
return SandboxConfig(**response.json())
|
|
1711
|
+
|
|
1712
|
+
def delete_sandbox_config(self, sandbox_config_id: str) -> None:
|
|
1713
|
+
"""
|
|
1714
|
+
Delete a sandbox configuration.
|
|
1715
|
+
"""
|
|
1716
|
+
response = requests.delete(f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}", headers=self.headers)
|
|
1717
|
+
if response.status_code == 404:
|
|
1718
|
+
raise ValueError(f"Sandbox config with ID '{sandbox_config_id}' does not exist")
|
|
1719
|
+
elif response.status_code != 204:
|
|
1720
|
+
raise ValueError(f"Failed to delete sandbox config with ID '{sandbox_config_id}': {response.text}")
|
|
1721
|
+
|
|
1722
|
+
def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
|
|
1723
|
+
"""
|
|
1724
|
+
List all sandbox configurations.
|
|
1725
|
+
"""
|
|
1726
|
+
params = {"limit": limit, "cursor": cursor}
|
|
1727
|
+
response = requests.get(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, params=params)
|
|
1728
|
+
if response.status_code != 200:
|
|
1729
|
+
raise ValueError(f"Failed to list sandbox configs: {response.text}")
|
|
1730
|
+
return [SandboxConfig(**config_data) for config_data in response.json()]
|
|
1731
|
+
|
|
1732
|
+
def create_sandbox_env_var(
|
|
1733
|
+
self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
|
|
1734
|
+
) -> SandboxEnvironmentVariable:
|
|
1735
|
+
"""
|
|
1736
|
+
Create a new environment variable for a sandbox configuration.
|
|
1737
|
+
"""
|
|
1738
|
+
payload = {"key": key, "value": value, "description": description}
|
|
1739
|
+
response = requests.post(
|
|
1740
|
+
f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
|
|
1741
|
+
headers=self.headers,
|
|
1742
|
+
json=payload,
|
|
1743
|
+
)
|
|
1744
|
+
if response.status_code != 200:
|
|
1745
|
+
raise ValueError(f"Failed to create environment variable for sandbox config ID '{sandbox_config_id}': {response.text}")
|
|
1746
|
+
return SandboxEnvironmentVariable(**response.json())
|
|
1747
|
+
|
|
1748
|
+
def update_sandbox_env_var(
|
|
1749
|
+
self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
|
|
1750
|
+
) -> SandboxEnvironmentVariable:
|
|
1751
|
+
"""
|
|
1752
|
+
Update an existing environment variable.
|
|
1753
|
+
"""
|
|
1754
|
+
payload = {k: v for k, v in {"key": key, "value": value, "description": description}.items() if v is not None}
|
|
1755
|
+
response = requests.patch(
|
|
1756
|
+
f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}",
|
|
1757
|
+
headers=self.headers,
|
|
1758
|
+
json=payload,
|
|
1759
|
+
)
|
|
1760
|
+
if response.status_code != 200:
|
|
1761
|
+
raise ValueError(f"Failed to update environment variable with ID '{env_var_id}': {response.text}")
|
|
1762
|
+
return SandboxEnvironmentVariable(**response.json())
|
|
1763
|
+
|
|
1764
|
+
def delete_sandbox_env_var(self, env_var_id: str) -> None:
|
|
1765
|
+
"""
|
|
1766
|
+
Delete an environment variable by its ID.
|
|
1767
|
+
"""
|
|
1768
|
+
response = requests.delete(
|
|
1769
|
+
f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}", headers=self.headers
|
|
1770
|
+
)
|
|
1771
|
+
if response.status_code == 404:
|
|
1772
|
+
raise ValueError(f"Environment variable with ID '{env_var_id}' does not exist")
|
|
1773
|
+
elif response.status_code != 204:
|
|
1774
|
+
raise ValueError(f"Failed to delete environment variable with ID '{env_var_id}': {response.text}")
|
|
1775
|
+
|
|
1776
|
+
def list_sandbox_env_vars(
|
|
1777
|
+
self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
|
|
1778
|
+
) -> List[SandboxEnvironmentVariable]:
|
|
1779
|
+
"""
|
|
1780
|
+
List all environment variables associated with a sandbox configuration.
|
|
1781
|
+
"""
|
|
1782
|
+
params = {"limit": limit, "cursor": cursor}
|
|
1783
|
+
response = requests.get(
|
|
1784
|
+
f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
|
|
1785
|
+
headers=self.headers,
|
|
1786
|
+
params=params,
|
|
1787
|
+
)
|
|
1788
|
+
if response.status_code != 200:
|
|
1789
|
+
raise ValueError(f"Failed to list environment variables for sandbox config ID '{sandbox_config_id}': {response.text}")
|
|
1790
|
+
return [SandboxEnvironmentVariable(**var_data) for var_data in response.json()]
|
|
1791
|
+
|
|
1792
|
+
def update_agent_memory_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
|
|
1793
|
+
|
|
1794
|
+
# @router.patch("/{agent_id}/memory/label", response_model=Memory, operation_id="update_agent_memory_label")
|
|
1795
|
+
response = requests.patch(
|
|
1796
|
+
f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/label",
|
|
1797
|
+
headers=self.headers,
|
|
1798
|
+
json={"current_label": current_label, "new_label": new_label},
|
|
1799
|
+
)
|
|
1800
|
+
if response.status_code != 200:
|
|
1801
|
+
raise ValueError(f"Failed to update agent memory label: {response.text}")
|
|
1802
|
+
return Memory(**response.json())
|
|
1803
|
+
|
|
1804
|
+
def add_agent_memory_block(self, agent_id: str, create_block: BlockCreate) -> Memory:
|
|
1805
|
+
|
|
1806
|
+
# @router.post("/{agent_id}/memory/block", response_model=Memory, operation_id="add_agent_memory_block")
|
|
1807
|
+
response = requests.post(
|
|
1808
|
+
f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block",
|
|
1809
|
+
headers=self.headers,
|
|
1810
|
+
json=create_block.model_dump(),
|
|
1811
|
+
)
|
|
1812
|
+
if response.status_code != 200:
|
|
1813
|
+
raise ValueError(f"Failed to add agent memory block: {response.text}")
|
|
1814
|
+
return Memory(**response.json())
|
|
1815
|
+
|
|
1816
|
+
def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
|
|
1817
|
+
|
|
1818
|
+
# @router.delete("/{agent_id}/memory/block/{block_label}", response_model=Memory, operation_id="remove_agent_memory_block")
|
|
1819
|
+
response = requests.delete(
|
|
1820
|
+
f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block/{block_label}",
|
|
1821
|
+
headers=self.headers,
|
|
1822
|
+
)
|
|
1823
|
+
if response.status_code != 200:
|
|
1824
|
+
raise ValueError(f"Failed to remove agent memory block: {response.text}")
|
|
1825
|
+
return Memory(**response.json())
|
|
1826
|
+
|
|
1827
|
+
def update_agent_memory_limit(self, agent_id: str, block_label: str, limit: int) -> Memory:
|
|
1828
|
+
|
|
1829
|
+
# @router.patch("/{agent_id}/memory/limit", response_model=Memory, operation_id="update_agent_memory_limit")
|
|
1830
|
+
response = requests.patch(
|
|
1831
|
+
f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/limit",
|
|
1832
|
+
headers=self.headers,
|
|
1833
|
+
json={"label": block_label, "limit": limit},
|
|
1834
|
+
)
|
|
1835
|
+
if response.status_code != 200:
|
|
1836
|
+
raise ValueError(f"Failed to update agent memory limit: {response.text}")
|
|
1837
|
+
return Memory(**response.json())
|
|
1838
|
+
|
|
1571
1839
|
|
|
1572
1840
|
class LocalClient(AbstractClient):
|
|
1573
1841
|
"""
|
|
@@ -2690,7 +2958,7 @@ class LocalClient(AbstractClient):
|
|
|
2690
2958
|
Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
|
|
2691
2959
|
)
|
|
2692
2960
|
|
|
2693
|
-
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
|
|
2961
|
+
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
|
|
2694
2962
|
"""
|
|
2695
2963
|
Update a block
|
|
2696
2964
|
|
|
@@ -2703,7 +2971,9 @@ class LocalClient(AbstractClient):
|
|
|
2703
2971
|
block (Block): Updated block
|
|
2704
2972
|
"""
|
|
2705
2973
|
return self.server.block_manager.update_block(
|
|
2706
|
-
block_id=block_id,
|
|
2974
|
+
block_id=block_id,
|
|
2975
|
+
block_update=BlockUpdate(template_name=name, value=text, limit=limit if limit else self.get_block(block_id).limit),
|
|
2976
|
+
actor=self.user,
|
|
2707
2977
|
)
|
|
2708
2978
|
|
|
2709
2979
|
def get_block(self, block_id: str) -> Block:
|
|
@@ -2774,3 +3044,87 @@ class LocalClient(AbstractClient):
|
|
|
2774
3044
|
|
|
2775
3045
|
def delete_org(self, org_id: str) -> Organization:
|
|
2776
3046
|
return self.server.organization_manager.delete_organization_by_id(org_id=org_id)
|
|
3047
|
+
|
|
3048
|
+
def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
3049
|
+
"""
|
|
3050
|
+
Create a new sandbox configuration.
|
|
3051
|
+
"""
|
|
3052
|
+
config_create = SandboxConfigCreate(config=config)
|
|
3053
|
+
return self.server.sandbox_config_manager.create_or_update_sandbox_config(sandbox_config_create=config_create, actor=self.user)
|
|
3054
|
+
|
|
3055
|
+
def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
|
|
3056
|
+
"""
|
|
3057
|
+
Update an existing sandbox configuration.
|
|
3058
|
+
"""
|
|
3059
|
+
sandbox_update = SandboxConfigUpdate(config=config)
|
|
3060
|
+
return self.server.sandbox_config_manager.update_sandbox_config(
|
|
3061
|
+
sandbox_config_id=sandbox_config_id, sandbox_update=sandbox_update, actor=self.user
|
|
3062
|
+
)
|
|
3063
|
+
|
|
3064
|
+
def delete_sandbox_config(self, sandbox_config_id: str) -> None:
|
|
3065
|
+
"""
|
|
3066
|
+
Delete a sandbox configuration.
|
|
3067
|
+
"""
|
|
3068
|
+
return self.server.sandbox_config_manager.delete_sandbox_config(sandbox_config_id=sandbox_config_id, actor=self.user)
|
|
3069
|
+
|
|
3070
|
+
def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
|
|
3071
|
+
"""
|
|
3072
|
+
List all sandbox configurations.
|
|
3073
|
+
"""
|
|
3074
|
+
return self.server.sandbox_config_manager.list_sandbox_configs(actor=self.user, limit=limit, cursor=cursor)
|
|
3075
|
+
|
|
3076
|
+
def create_sandbox_env_var(
|
|
3077
|
+
self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
|
|
3078
|
+
) -> SandboxEnvironmentVariable:
|
|
3079
|
+
"""
|
|
3080
|
+
Create a new environment variable for a sandbox configuration.
|
|
3081
|
+
"""
|
|
3082
|
+
env_var_create = SandboxEnvironmentVariableCreate(key=key, value=value, description=description)
|
|
3083
|
+
return self.server.sandbox_config_manager.create_sandbox_env_var(
|
|
3084
|
+
env_var_create=env_var_create, sandbox_config_id=sandbox_config_id, actor=self.user
|
|
3085
|
+
)
|
|
3086
|
+
|
|
3087
|
+
def update_sandbox_env_var(
|
|
3088
|
+
self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
|
|
3089
|
+
) -> SandboxEnvironmentVariable:
|
|
3090
|
+
"""
|
|
3091
|
+
Update an existing environment variable.
|
|
3092
|
+
"""
|
|
3093
|
+
env_var_update = SandboxEnvironmentVariableUpdate(key=key, value=value, description=description)
|
|
3094
|
+
return self.server.sandbox_config_manager.update_sandbox_env_var(
|
|
3095
|
+
env_var_id=env_var_id, env_var_update=env_var_update, actor=self.user
|
|
3096
|
+
)
|
|
3097
|
+
|
|
3098
|
+
def delete_sandbox_env_var(self, env_var_id: str) -> None:
|
|
3099
|
+
"""
|
|
3100
|
+
Delete an environment variable by its ID.
|
|
3101
|
+
"""
|
|
3102
|
+
return self.server.sandbox_config_manager.delete_sandbox_env_var(env_var_id=env_var_id, actor=self.user)
|
|
3103
|
+
|
|
3104
|
+
def list_sandbox_env_vars(
|
|
3105
|
+
self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
|
|
3106
|
+
) -> List[SandboxEnvironmentVariable]:
|
|
3107
|
+
"""
|
|
3108
|
+
List all environment variables associated with a sandbox configuration.
|
|
3109
|
+
"""
|
|
3110
|
+
return self.server.sandbox_config_manager.list_sandbox_env_vars(
|
|
3111
|
+
sandbox_config_id=sandbox_config_id, actor=self.user, limit=limit, cursor=cursor
|
|
3112
|
+
)
|
|
3113
|
+
|
|
3114
|
+
def update_agent_memory_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
|
|
3115
|
+
return self.server.update_agent_memory_label(
|
|
3116
|
+
user_id=self.user_id, agent_id=agent_id, current_block_label=current_label, new_block_label=new_label
|
|
3117
|
+
)
|
|
3118
|
+
|
|
3119
|
+
def add_agent_memory_block(self, agent_id: str, create_block: BlockCreate) -> Memory:
|
|
3120
|
+
block_req = Block(**create_block.model_dump())
|
|
3121
|
+
block = self.server.block_manager.create_or_update_block(actor=self.user, block=block_req)
|
|
3122
|
+
# Link the block to the agent
|
|
3123
|
+
updated_memory = self.server.link_block_to_agent_memory(user_id=self.user_id, agent_id=agent_id, block_id=block.id)
|
|
3124
|
+
return updated_memory
|
|
3125
|
+
|
|
3126
|
+
def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
|
|
3127
|
+
return self.server.unlink_block_from_agent_memory(user_id=self.user_id, agent_id=agent_id, block_label=block_label)
|
|
3128
|
+
|
|
3129
|
+
def update_agent_memory_limit(self, agent_id: str, block_label: str, limit: int) -> Memory:
|
|
3130
|
+
return self.server.update_agent_memory_limit(user_id=self.user_id, agent_id=agent_id, block_label=block_label, limit=limit)
|
letta/constants.py
CHANGED
|
@@ -36,14 +36,8 @@ DEFAULT_PERSONA = "sam_pov"
|
|
|
36
36
|
DEFAULT_HUMAN = "basic"
|
|
37
37
|
DEFAULT_PRESET = "memgpt_chat"
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
BASE_TOOLS = [
|
|
41
|
-
"send_message",
|
|
42
|
-
"conversation_search",
|
|
43
|
-
"conversation_search_date",
|
|
44
|
-
"archival_memory_insert",
|
|
45
|
-
"archival_memory_search",
|
|
46
|
-
]
|
|
39
|
+
# Base tools that cannot be edited, as they access agent state directly
|
|
40
|
+
BASE_TOOLS = ["send_message", "conversation_search", "conversation_search_date", "archival_memory_insert", "archival_memory_search"]
|
|
47
41
|
|
|
48
42
|
# The name of the tool used to send message to the user
|
|
49
43
|
# May not be relevant in cases where the agent has multiple ways to message to user (send_imessage, send_discord_mesasge, ...)
|
|
@@ -134,8 +128,9 @@ MESSAGE_SUMMARY_REQUEST_ACK = "Understood, I will respond with a summary of the
|
|
|
134
128
|
MESSAGE_SUMMARY_TRUNC_KEEP_N_LAST = 3
|
|
135
129
|
|
|
136
130
|
# Default memory limits
|
|
137
|
-
CORE_MEMORY_PERSONA_CHAR_LIMIT =
|
|
138
|
-
CORE_MEMORY_HUMAN_CHAR_LIMIT =
|
|
131
|
+
CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 5000
|
|
132
|
+
CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 5000
|
|
133
|
+
CORE_MEMORY_BLOCK_CHAR_LIMIT: int = 5000
|
|
139
134
|
|
|
140
135
|
# Function return limits
|
|
141
136
|
FUNCTION_RETURN_CHAR_LIMIT = 6000 # ~300 words
|
|
@@ -155,9 +150,5 @@ FUNC_FAILED_HEARTBEAT_MESSAGE = f"{NON_USER_MSG_PREFIX}Function call failed, ret
|
|
|
155
150
|
|
|
156
151
|
RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5
|
|
157
152
|
|
|
158
|
-
# TODO Is this config or constant?
|
|
159
|
-
CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 2000
|
|
160
|
-
CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 2000
|
|
161
|
-
|
|
162
153
|
MAX_FILENAME_LENGTH = 255
|
|
163
154
|
RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"}
|