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.

Files changed (40) hide show
  1. letta/agent.py +48 -25
  2. letta/agent_store/db.py +1 -1
  3. letta/client/client.py +361 -7
  4. letta/constants.py +5 -14
  5. letta/functions/helpers.py +5 -42
  6. letta/functions/schema_generator.py +24 -4
  7. letta/local_llm/utils.py +6 -3
  8. letta/log.py +7 -9
  9. letta/metadata.py +17 -4
  10. letta/orm/__init__.py +2 -0
  11. letta/orm/block.py +5 -2
  12. letta/orm/blocks_agents.py +29 -0
  13. letta/orm/mixins.py +8 -0
  14. letta/orm/organization.py +8 -1
  15. letta/orm/sandbox_config.py +56 -0
  16. letta/orm/sqlalchemy_base.py +9 -3
  17. letta/schemas/block.py +15 -1
  18. letta/schemas/blocks_agents.py +32 -0
  19. letta/schemas/letta_base.py +9 -0
  20. letta/schemas/memory.py +42 -8
  21. letta/schemas/sandbox_config.py +114 -0
  22. letta/schemas/tool.py +2 -45
  23. letta/server/rest_api/routers/v1/__init__.py +4 -9
  24. letta/server/rest_api/routers/v1/agents.py +85 -1
  25. letta/server/rest_api/routers/v1/sandbox_configs.py +108 -0
  26. letta/server/rest_api/routers/v1/tools.py +3 -5
  27. letta/server/rest_api/utils.py +6 -0
  28. letta/server/server.py +159 -12
  29. letta/services/block_manager.py +3 -1
  30. letta/services/blocks_agents_manager.py +84 -0
  31. letta/services/sandbox_config_manager.py +256 -0
  32. letta/services/tool_execution_sandbox.py +326 -0
  33. letta/services/tool_manager.py +10 -10
  34. letta/services/tool_sandbox_env/.gitkeep +0 -0
  35. letta/settings.py +4 -0
  36. {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/METADATA +28 -27
  37. {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/RECORD +40 -31
  38. {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/LICENSE +0 -0
  39. {letta_nightly-0.5.4.dev20241121104201.dist-info → letta_nightly-0.5.4.dev20241123104112.dist-info}/WHEEL +0 -0
  40. {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
+ #
@@ -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
- try:
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
- except NoResultFound:
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
- with self.session_maker() as session:
94
- tool = ToolModel.read(db_session=session, name=tool_name, actor=actor)
95
- return tool.to_pydantic()
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.dev20241121104201
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: crewai (>=0.41.1,<0.42.0) ; extra == "external-tools"
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: fastapi (>=0.104.1,<0.105.0) ; extra == "server"
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.2.16,<0.3.0) ; extra == "external-tools"
43
- Requires-Dist: langchain-community (>=0.2.17,<0.3.0) ; extra == "external-tools"
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">