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
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import base64
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
import runpy
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from letta.log import get_logger
|
|
12
|
+
from letta.schemas.agent import AgentState
|
|
13
|
+
from letta.schemas.sandbox_config import SandboxConfig, SandboxRunResult, SandboxType
|
|
14
|
+
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
15
|
+
from letta.services.tool_manager import ToolManager
|
|
16
|
+
from letta.services.user_manager import UserManager
|
|
17
|
+
from letta.settings import tool_settings
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ToolExecutionSandbox:
|
|
23
|
+
METADATA_CONFIG_STATE_KEY = "config_state"
|
|
24
|
+
REQUIREMENT_TXT_NAME = "requirements.txt"
|
|
25
|
+
|
|
26
|
+
# This is the variable name in the auto-generated code that contains the function results
|
|
27
|
+
# We make this a long random string to avoid collisions with any variables in the user's code
|
|
28
|
+
LOCAL_SANDBOX_RESULT_VAR_NAME = "result_ZQqiequkcFwRwwGQMqkt"
|
|
29
|
+
|
|
30
|
+
def __init__(self, tool_name: str, args: dict, user_id: str, force_recreate=False):
|
|
31
|
+
self.tool_name = tool_name
|
|
32
|
+
self.args = args
|
|
33
|
+
|
|
34
|
+
# Get the user
|
|
35
|
+
# This user corresponds to the agent_state's user_id field
|
|
36
|
+
# agent_state is the state of the agent that invoked this run
|
|
37
|
+
self.user = UserManager().get_user_by_id(user_id=user_id)
|
|
38
|
+
|
|
39
|
+
# Get the tool
|
|
40
|
+
# TODO: So in theory, it's possible this retrieves a tool not provisioned to the agent
|
|
41
|
+
# TODO: That would probably imply that agent_state is incorrectly configured
|
|
42
|
+
self.tool = ToolManager().get_tool_by_name(tool_name=tool_name, actor=self.user)
|
|
43
|
+
if not self.tool:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Agent attempted to invoke tool {self.tool_name} that does not exist for organization {self.user.organization_id}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
self.sandbox_config_manager = SandboxConfigManager(tool_settings)
|
|
49
|
+
self.force_recreate = force_recreate
|
|
50
|
+
|
|
51
|
+
def run(self, agent_state: Optional[AgentState] = None) -> Optional[SandboxRunResult]:
|
|
52
|
+
"""
|
|
53
|
+
Run the tool in a sandbox environment.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
agent_state (Optional[AgentState]): The state of the agent invoking the tool
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple[Any, Optional[AgentState]]: Tuple containing (tool_result, agent_state)
|
|
60
|
+
"""
|
|
61
|
+
if tool_settings.e2b_api_key:
|
|
62
|
+
logger.info(f"Using e2b sandbox to execute {self.tool_name}")
|
|
63
|
+
code = self.generate_execution_script(agent_state=agent_state)
|
|
64
|
+
result = self.run_e2b_sandbox(code=code)
|
|
65
|
+
else:
|
|
66
|
+
logger.info(f"Using local sandbox to execute {self.tool_name}")
|
|
67
|
+
code = self.generate_execution_script(agent_state=agent_state)
|
|
68
|
+
result = self.run_local_dir_sandbox(code=code)
|
|
69
|
+
|
|
70
|
+
# Log out any stdout from the tool run
|
|
71
|
+
logger.info(f"Executed tool '{self.tool_name}', logging stdout from tool run: \n")
|
|
72
|
+
for log_line in result.stdout:
|
|
73
|
+
logger.info(f"{log_line}")
|
|
74
|
+
logger.info(f"Ending stdout log from tool run.")
|
|
75
|
+
|
|
76
|
+
# Return result
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
# local sandbox specific functions
|
|
80
|
+
from contextlib import contextmanager
|
|
81
|
+
|
|
82
|
+
@contextmanager
|
|
83
|
+
def temporary_env_vars(self, env_vars: dict):
|
|
84
|
+
original_env = os.environ.copy() # Backup original environment variables
|
|
85
|
+
os.environ.update(env_vars) # Update with the new variables
|
|
86
|
+
try:
|
|
87
|
+
yield
|
|
88
|
+
finally:
|
|
89
|
+
os.environ.clear()
|
|
90
|
+
os.environ.update(original_env) # Restore original environment variables
|
|
91
|
+
|
|
92
|
+
def run_local_dir_sandbox(self, code: str) -> Optional[SandboxRunResult]:
|
|
93
|
+
sbx_config = self.sandbox_config_manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.LOCAL, actor=self.user)
|
|
94
|
+
local_configs = sbx_config.get_local_config()
|
|
95
|
+
|
|
96
|
+
# Get environment variables for the sandbox
|
|
97
|
+
env_vars = self.sandbox_config_manager.get_sandbox_env_vars_as_dict(sandbox_config_id=sbx_config.id, actor=self.user, limit=100)
|
|
98
|
+
|
|
99
|
+
# Safety checks
|
|
100
|
+
if not os.path.isdir(local_configs.sandbox_dir):
|
|
101
|
+
raise FileNotFoundError(f"Sandbox directory does not exist: {local_configs.sandbox_dir}")
|
|
102
|
+
|
|
103
|
+
# Write the code to a temp file in the sandbox_dir
|
|
104
|
+
with tempfile.NamedTemporaryFile(mode="w", dir=local_configs.sandbox_dir, suffix=".py", delete=False) as temp_file:
|
|
105
|
+
temp_file.write(code)
|
|
106
|
+
temp_file.flush()
|
|
107
|
+
temp_file_path = temp_file.name
|
|
108
|
+
|
|
109
|
+
# Save the old stdout
|
|
110
|
+
old_stdout = sys.stdout
|
|
111
|
+
try:
|
|
112
|
+
# Redirect stdout to capture script output
|
|
113
|
+
captured_stdout = io.StringIO()
|
|
114
|
+
sys.stdout = captured_stdout
|
|
115
|
+
|
|
116
|
+
# Execute the temp file
|
|
117
|
+
with self.temporary_env_vars(env_vars):
|
|
118
|
+
result = runpy.run_path(temp_file_path, init_globals=env_vars)
|
|
119
|
+
|
|
120
|
+
# Fetch the result
|
|
121
|
+
func_result = result.get(self.LOCAL_SANDBOX_RESULT_VAR_NAME)
|
|
122
|
+
func_return, agent_state = self.parse_best_effort(func_result)
|
|
123
|
+
|
|
124
|
+
# Restore stdout and collect captured output
|
|
125
|
+
sys.stdout = old_stdout
|
|
126
|
+
stdout_output = captured_stdout.getvalue()
|
|
127
|
+
|
|
128
|
+
return SandboxRunResult(
|
|
129
|
+
func_return=func_return,
|
|
130
|
+
agent_state=agent_state,
|
|
131
|
+
stdout=[stdout_output],
|
|
132
|
+
sandbox_config_fingerprint=sbx_config.fingerprint(),
|
|
133
|
+
)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
raise RuntimeError(f"Executing tool {self.tool_name} has an unexpected error: {e}")
|
|
136
|
+
finally:
|
|
137
|
+
# Clean up the temp file and restore stdout
|
|
138
|
+
sys.stdout = old_stdout
|
|
139
|
+
os.remove(temp_file_path)
|
|
140
|
+
|
|
141
|
+
# e2b sandbox specific functions
|
|
142
|
+
|
|
143
|
+
def run_e2b_sandbox(self, code: str) -> Optional[SandboxRunResult]:
|
|
144
|
+
sbx_config = self.sandbox_config_manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.E2B, actor=self.user)
|
|
145
|
+
sbx = self.get_running_e2b_sandbox_with_same_state(sbx_config)
|
|
146
|
+
if not sbx or self.force_recreate:
|
|
147
|
+
sbx = self.create_e2b_sandbox_with_metadata_hash(sandbox_config=sbx_config)
|
|
148
|
+
|
|
149
|
+
# Since this sandbox was used, we extend its lifecycle by the timeout
|
|
150
|
+
sbx.set_timeout(sbx_config.get_e2b_config().timeout)
|
|
151
|
+
|
|
152
|
+
# Get environment variables for the sandbox
|
|
153
|
+
# TODO: We set limit to 100 here, but maybe we want it uncapped? Realistically this should be fine.
|
|
154
|
+
env_vars = self.sandbox_config_manager.get_sandbox_env_vars_as_dict(sandbox_config_id=sbx_config.id, actor=self.user, limit=100)
|
|
155
|
+
execution = sbx.run_code(code, envs=env_vars)
|
|
156
|
+
if execution.error is not None:
|
|
157
|
+
raise Exception(f"Executing tool {self.tool_name} failed with {execution.error}. Generated code: \n\n{code}")
|
|
158
|
+
elif len(execution.results) == 0:
|
|
159
|
+
return None
|
|
160
|
+
else:
|
|
161
|
+
func_return, agent_state = self.parse_best_effort(execution.results[0].text)
|
|
162
|
+
return SandboxRunResult(
|
|
163
|
+
func_return=func_return,
|
|
164
|
+
agent_state=agent_state,
|
|
165
|
+
stdout=execution.logs.stdout + execution.logs.stderr,
|
|
166
|
+
sandbox_config_fingerprint=sbx_config.fingerprint(),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def get_running_e2b_sandbox_with_same_state(self, sandbox_config: SandboxConfig) -> Optional["Sandbox"]:
|
|
170
|
+
from e2b_code_interpreter import Sandbox
|
|
171
|
+
|
|
172
|
+
# List running sandboxes and access metadata.
|
|
173
|
+
running_sandboxes = self.list_running_e2b_sandboxes()
|
|
174
|
+
|
|
175
|
+
# Hash the config to check the state
|
|
176
|
+
state_hash = sandbox_config.fingerprint()
|
|
177
|
+
for sandbox in running_sandboxes:
|
|
178
|
+
if self.METADATA_CONFIG_STATE_KEY in sandbox.metadata and sandbox.metadata[self.METADATA_CONFIG_STATE_KEY] == state_hash:
|
|
179
|
+
return Sandbox.connect(sandbox.sandbox_id)
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def create_e2b_sandbox_with_metadata_hash(self, sandbox_config: SandboxConfig) -> "Sandbox":
|
|
184
|
+
from e2b_code_interpreter import Sandbox
|
|
185
|
+
|
|
186
|
+
state_hash = sandbox_config.fingerprint()
|
|
187
|
+
e2b_config = sandbox_config.get_e2b_config()
|
|
188
|
+
if e2b_config.template:
|
|
189
|
+
sbx = Sandbox(sandbox_config.get_e2b_config().template, metadata={self.METADATA_CONFIG_STATE_KEY: state_hash})
|
|
190
|
+
else:
|
|
191
|
+
# no template
|
|
192
|
+
sbx = Sandbox(metadata={self.METADATA_CONFIG_STATE_KEY: state_hash}, **e2b_config.model_dump(exclude={"pip_requirements"}))
|
|
193
|
+
|
|
194
|
+
# install pip requirements
|
|
195
|
+
if e2b_config.pip_requirements:
|
|
196
|
+
for package in e2b_config.pip_requirements:
|
|
197
|
+
sbx.commands.run(f"pip install {package}")
|
|
198
|
+
return sbx
|
|
199
|
+
|
|
200
|
+
def list_running_e2b_sandboxes(self):
|
|
201
|
+
from e2b_code_interpreter import Sandbox
|
|
202
|
+
|
|
203
|
+
# List running sandboxes and access metadata.
|
|
204
|
+
return Sandbox.list()
|
|
205
|
+
|
|
206
|
+
# general utility functions
|
|
207
|
+
|
|
208
|
+
def parse_best_effort(self, text: str) -> Any:
|
|
209
|
+
result = pickle.loads(base64.b64decode(text))
|
|
210
|
+
agent_state = None
|
|
211
|
+
if not result["agent_state"] is None:
|
|
212
|
+
agent_state = result["agent_state"]
|
|
213
|
+
return result["results"], agent_state
|
|
214
|
+
|
|
215
|
+
def parse_function_arguments(self, source_code: str, tool_name: str):
|
|
216
|
+
"""Get arguments of a function from its source code"""
|
|
217
|
+
tree = ast.parse(source_code)
|
|
218
|
+
args = []
|
|
219
|
+
for node in ast.walk(tree):
|
|
220
|
+
if isinstance(node, ast.FunctionDef) and node.name == tool_name:
|
|
221
|
+
for arg in node.args.args:
|
|
222
|
+
args.append(arg.arg)
|
|
223
|
+
return args
|
|
224
|
+
|
|
225
|
+
def generate_execution_script(self, agent_state: AgentState) -> str:
|
|
226
|
+
"""
|
|
227
|
+
Generate code to run inside of execution sandbox.
|
|
228
|
+
Passes into a serialized agent state into the code, to be accessed by the tool.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
agent_state (AgentState): The agent state
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
code (str): The generated code strong
|
|
235
|
+
"""
|
|
236
|
+
# dump JSON representation of agent state to re-load
|
|
237
|
+
code = "from typing import *\n"
|
|
238
|
+
code += "import pickle\n"
|
|
239
|
+
code += "import sys\n"
|
|
240
|
+
code += "import base64\n"
|
|
241
|
+
|
|
242
|
+
# Load the agent state data into the program
|
|
243
|
+
if agent_state:
|
|
244
|
+
code += "import letta\n"
|
|
245
|
+
code += "from letta import * \n"
|
|
246
|
+
import pickle
|
|
247
|
+
|
|
248
|
+
agent_state_pickle = pickle.dumps(agent_state)
|
|
249
|
+
code += f"agent_state = pickle.loads({agent_state_pickle})\n"
|
|
250
|
+
else:
|
|
251
|
+
# agent state is None
|
|
252
|
+
code += "agent_state = None\n"
|
|
253
|
+
|
|
254
|
+
for param in self.args:
|
|
255
|
+
code += self.initialize_param(param, self.args[param])
|
|
256
|
+
|
|
257
|
+
if "agent_state" in self.parse_function_arguments(self.tool.source_code, self.tool.name):
|
|
258
|
+
inject_agent_state = True
|
|
259
|
+
else:
|
|
260
|
+
inject_agent_state = False
|
|
261
|
+
|
|
262
|
+
code += "\n" + self.tool.source_code + "\n"
|
|
263
|
+
|
|
264
|
+
# TODO: handle wrapped print
|
|
265
|
+
|
|
266
|
+
code += (
|
|
267
|
+
self.LOCAL_SANDBOX_RESULT_VAR_NAME
|
|
268
|
+
+ ' = {"results": '
|
|
269
|
+
+ self.invoke_function_call(inject_agent_state=inject_agent_state)
|
|
270
|
+
+ ', "agent_state": agent_state}\n'
|
|
271
|
+
)
|
|
272
|
+
code += (
|
|
273
|
+
f"{self.LOCAL_SANDBOX_RESULT_VAR_NAME} = base64.b64encode(pickle.dumps({self.LOCAL_SANDBOX_RESULT_VAR_NAME})).decode('utf-8')\n"
|
|
274
|
+
)
|
|
275
|
+
code += f"{self.LOCAL_SANDBOX_RESULT_VAR_NAME}\n"
|
|
276
|
+
|
|
277
|
+
return code
|
|
278
|
+
|
|
279
|
+
def initialize_param(self, name: str, raw_value: str) -> str:
|
|
280
|
+
params = self.tool.json_schema["parameters"]["properties"]
|
|
281
|
+
spec = params.get(name)
|
|
282
|
+
if spec is None:
|
|
283
|
+
# ignore extra params (like 'self') for now
|
|
284
|
+
return ""
|
|
285
|
+
|
|
286
|
+
param_type = spec.get("type")
|
|
287
|
+
if param_type is None and spec.get("parameters"):
|
|
288
|
+
param_type = spec["parameters"].get("type")
|
|
289
|
+
|
|
290
|
+
if param_type == "string":
|
|
291
|
+
value = '"' + raw_value + '"'
|
|
292
|
+
elif param_type == "integer" or param_type == "boolean":
|
|
293
|
+
value = raw_value
|
|
294
|
+
else:
|
|
295
|
+
raise TypeError(f"unsupported type: {param_type}")
|
|
296
|
+
|
|
297
|
+
return name + " = " + str(value) + "\n"
|
|
298
|
+
|
|
299
|
+
def invoke_function_call(self, inject_agent_state: bool) -> str:
|
|
300
|
+
"""
|
|
301
|
+
Generate the code string to call the function.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
inject_agent_state (bool): Whether to inject the agent's state as an input into the tool
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
str: Generated code string for calling the tool
|
|
308
|
+
"""
|
|
309
|
+
kwargs = []
|
|
310
|
+
for name in self.args:
|
|
311
|
+
if name in self.tool.json_schema["parameters"]["properties"]:
|
|
312
|
+
kwargs.append(name)
|
|
313
|
+
|
|
314
|
+
param_list = [f"{arg}={arg}" for arg in kwargs]
|
|
315
|
+
if inject_agent_state:
|
|
316
|
+
param_list.append("agent_state=agent_state")
|
|
317
|
+
params = ", ".join(param_list)
|
|
318
|
+
# if "agent_state" in kwargs:
|
|
319
|
+
# params += ", agent_state=agent_state"
|
|
320
|
+
# TODO: fix to figure out when to insert agent state or not
|
|
321
|
+
# params += "agent_state=agent_state"
|
|
322
|
+
|
|
323
|
+
func_call_str = self.tool.name + "(" + params + ")"
|
|
324
|
+
return func_call_str
|
|
325
|
+
|
|
326
|
+
#
|
letta/services/tool_manager.py
CHANGED
|
@@ -37,11 +37,8 @@ class ToolManager:
|
|
|
37
37
|
# Derive json_schema
|
|
38
38
|
derived_json_schema = pydantic_tool.json_schema or derive_openai_json_schema(source_code=pydantic_tool.source_code)
|
|
39
39
|
derived_name = pydantic_tool.name or derived_json_schema["name"]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# NOTE: We use the organization id here
|
|
43
|
-
# This is important, because even if it's a different user, adding the same tool to the org should not happen
|
|
44
|
-
tool = self.get_tool_by_name(tool_name=derived_name, actor=actor)
|
|
40
|
+
tool = self.get_tool_by_name(tool_name=derived_name, actor=actor)
|
|
41
|
+
if tool:
|
|
45
42
|
# Put to dict and remove fields that should not be reset
|
|
46
43
|
update_data = pydantic_tool.model_dump(exclude={"module"}, exclude_unset=True, exclude_none=True)
|
|
47
44
|
# Remove redundant update fields
|
|
@@ -54,7 +51,7 @@ class ToolManager:
|
|
|
54
51
|
printd(
|
|
55
52
|
f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={pydantic_tool.name}, but found existing tool with nothing to update."
|
|
56
53
|
)
|
|
57
|
-
|
|
54
|
+
else:
|
|
58
55
|
pydantic_tool.json_schema = derived_json_schema
|
|
59
56
|
pydantic_tool.name = derived_name
|
|
60
57
|
tool = self.create_tool(pydantic_tool, actor=actor)
|
|
@@ -88,11 +85,14 @@ class ToolManager:
|
|
|
88
85
|
return tool.to_pydantic()
|
|
89
86
|
|
|
90
87
|
@enforce_types
|
|
91
|
-
def get_tool_by_name(self, tool_name: str, actor: PydanticUser):
|
|
88
|
+
def get_tool_by_name(self, tool_name: str, actor: PydanticUser) -> Optional[PydanticTool]:
|
|
92
89
|
"""Retrieve a tool by its name and a user. We derive the organization from the user, and retrieve that tool."""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
try:
|
|
91
|
+
with self.session_maker() as session:
|
|
92
|
+
tool = ToolModel.read(db_session=session, name=tool_name, actor=actor)
|
|
93
|
+
return tool.to_pydantic()
|
|
94
|
+
except NoResultFound:
|
|
95
|
+
return None
|
|
96
96
|
|
|
97
97
|
@enforce_types
|
|
98
98
|
def list_tools(self, actor: PydanticUser, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticTool]:
|
|
File without changes
|
letta/settings.py
CHANGED
|
@@ -10,6 +10,10 @@ from letta.local_llm.constants import DEFAULT_WRAPPER_NAME
|
|
|
10
10
|
class ToolSettings(BaseSettings):
|
|
11
11
|
composio_api_key: Optional[str] = None
|
|
12
12
|
|
|
13
|
+
# Sandbox configurations
|
|
14
|
+
e2b_api_key: Optional[str] = None
|
|
15
|
+
e2b_sandbox_template_id: Optional[str] = None # Updated manually
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
class ModelSettings(BaseSettings):
|
|
15
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: letta-nightly
|
|
3
|
-
Version: 0.5.4.
|
|
3
|
+
Version: 0.5.4.dev20241123104112
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -11,7 +11,9 @@ Classifier: Programming Language :: Python :: 3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Provides-Extra: all
|
|
14
15
|
Provides-Extra: autogen
|
|
16
|
+
Provides-Extra: cloud-tool-sandbox
|
|
15
17
|
Provides-Extra: dev
|
|
16
18
|
Provides-Extra: external-tools
|
|
17
19
|
Provides-Extra: milvus
|
|
@@ -21,48 +23,47 @@ Provides-Extra: qdrant
|
|
|
21
23
|
Provides-Extra: server
|
|
22
24
|
Provides-Extra: tests
|
|
23
25
|
Requires-Dist: alembic (>=1.13.3,<2.0.0)
|
|
24
|
-
Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev"
|
|
25
|
-
Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev"
|
|
26
|
+
Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev" or extra == "all"
|
|
27
|
+
Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev" or extra == "all"
|
|
26
28
|
Requires-Dist: chromadb (>=0.4.24,<0.5.0)
|
|
27
|
-
Requires-Dist: composio-core (>=0.5.34,<0.6.0) ; extra == "external-tools"
|
|
28
|
-
Requires-Dist: composio-langchain (>=0.5.28,<0.6.0) ; extra == "external-tools"
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist: crewai-tools (>=0.8.3,<0.9.0) ; extra == "external-tools"
|
|
31
|
-
Requires-Dist: datasets (>=2.14.6,<3.0.0) ; extra == "dev"
|
|
29
|
+
Requires-Dist: composio-core (>=0.5.34,<0.6.0) ; extra == "external-tools" or extra == "all"
|
|
30
|
+
Requires-Dist: composio-langchain (>=0.5.28,<0.6.0) ; extra == "external-tools" or extra == "all"
|
|
31
|
+
Requires-Dist: datasets (>=2.14.6,<3.0.0) ; extra == "dev" or extra == "all"
|
|
32
32
|
Requires-Dist: demjson3 (>=3.0.6,<4.0.0)
|
|
33
|
-
Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools"
|
|
33
|
+
Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools" or extra == "all"
|
|
34
34
|
Requires-Dist: docstring-parser (>=0.16,<0.17)
|
|
35
35
|
Requires-Dist: docx2txt (>=0.8,<0.9)
|
|
36
|
-
Requires-Dist:
|
|
36
|
+
Requires-Dist: e2b-code-interpreter (>=1.0.1,<2.0.0) ; extra == "cloud-tool-sandbox"
|
|
37
|
+
Requires-Dist: fastapi (>=0.104.1,<0.105.0) ; extra == "server" or extra == "all"
|
|
37
38
|
Requires-Dist: html2text (>=2020.1.16,<2021.0.0)
|
|
38
39
|
Requires-Dist: httpx (>=0.27.2,<0.28.0)
|
|
39
40
|
Requires-Dist: httpx-sse (>=0.4.0,<0.5.0)
|
|
40
|
-
Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev"
|
|
41
|
+
Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
|
|
41
42
|
Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
|
42
|
-
Requires-Dist: langchain (>=0.
|
|
43
|
-
Requires-Dist: langchain-community (>=0.
|
|
43
|
+
Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
|
|
44
|
+
Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
|
|
44
45
|
Requires-Dist: llama-index (>=0.11.9,<0.12.0)
|
|
45
|
-
Requires-Dist: llama-index-embeddings-ollama (>=0.3.1,<0.4.0) ; extra == "ollama"
|
|
46
|
+
Requires-Dist: llama-index-embeddings-ollama (>=0.3.1,<0.4.0) ; extra == "ollama" or extra == "all"
|
|
46
47
|
Requires-Dist: llama-index-embeddings-openai (>=0.2.5,<0.3.0)
|
|
47
|
-
Requires-Dist: locust (>=2.31.5,<3.0.0)
|
|
48
|
+
Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "all"
|
|
48
49
|
Requires-Dist: nltk (>=3.8.1,<4.0.0)
|
|
49
50
|
Requires-Dist: numpy (>=1.26.2,<2.0.0)
|
|
50
51
|
Requires-Dist: pathvalidate (>=3.2.1,<4.0.0)
|
|
51
|
-
Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "dev"
|
|
52
|
-
Requires-Dist: pg8000 (>=1.30.3,<2.0.0) ; extra == "postgres"
|
|
53
|
-
Requires-Dist: pgvector (>=0.2.3,<0.3.0) ; extra == "postgres"
|
|
54
|
-
Requires-Dist: pre-commit (>=3.5.0,<4.0.0) ; extra == "dev"
|
|
52
|
+
Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "dev" or extra == "all"
|
|
53
|
+
Requires-Dist: pg8000 (>=1.30.3,<2.0.0) ; extra == "postgres" or extra == "all"
|
|
54
|
+
Requires-Dist: pgvector (>=0.2.3,<0.3.0) ; extra == "postgres" or extra == "all"
|
|
55
|
+
Requires-Dist: pre-commit (>=3.5.0,<4.0.0) ; extra == "dev" or extra == "all"
|
|
55
56
|
Requires-Dist: prettytable (>=3.9.0,<4.0.0)
|
|
56
|
-
Requires-Dist: psycopg2 (>=2.9.10,<3.0.0) ; extra == "postgres"
|
|
57
|
-
Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "postgres"
|
|
57
|
+
Requires-Dist: psycopg2 (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
|
|
58
|
+
Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
|
|
58
59
|
Requires-Dist: pyautogen (==0.2.22) ; extra == "autogen"
|
|
59
60
|
Requires-Dist: pydantic (>=2.7.4,<2.10.0)
|
|
60
61
|
Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
|
|
61
62
|
Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
|
|
62
63
|
Requires-Dist: pymilvus (>=2.4.3,<3.0.0) ; extra == "milvus"
|
|
63
|
-
Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev"
|
|
64
|
-
Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev"
|
|
65
|
-
Requires-Dist: pytest-order (>=1.2.0,<2.0.0) ; extra == "dev"
|
|
64
|
+
Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev" or extra == "all"
|
|
65
|
+
Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev" or extra == "all"
|
|
66
|
+
Requires-Dist: pytest-order (>=1.2.0,<2.0.0) ; extra == "dev" or extra == "all"
|
|
66
67
|
Requires-Dist: python-box (>=7.1.1,<8.0.0)
|
|
67
68
|
Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
|
|
68
69
|
Requires-Dist: pytz (>=2023.3.post1,<2024.0)
|
|
@@ -77,9 +78,9 @@ Requires-Dist: sqlmodel (>=0.0.16,<0.0.17)
|
|
|
77
78
|
Requires-Dist: tiktoken (>=0.7.0,<0.8.0)
|
|
78
79
|
Requires-Dist: tqdm (>=4.66.1,<5.0.0)
|
|
79
80
|
Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
|
|
80
|
-
Requires-Dist: uvicorn (>=0.24.0.post1,<0.25.0) ; extra == "server"
|
|
81
|
-
Requires-Dist: websockets (>=12.0,<13.0) ; extra == "server"
|
|
82
|
-
Requires-Dist: wikipedia (>=1.4.0,<2.0.0) ; extra == "external-tools" or extra == "tests"
|
|
81
|
+
Requires-Dist: uvicorn (>=0.24.0.post1,<0.25.0) ; extra == "server" or extra == "all"
|
|
82
|
+
Requires-Dist: websockets (>=12.0,<13.0) ; extra == "server" or extra == "all"
|
|
83
|
+
Requires-Dist: wikipedia (>=1.4.0,<2.0.0) ; extra == "external-tools" or extra == "tests" or extra == "all"
|
|
83
84
|
Description-Content-Type: text/markdown
|
|
84
85
|
|
|
85
86
|
<p align="center">
|