letta-nightly 0.6.19.dev20250131104014__py3-none-any.whl → 0.6.20.dev20250131222205__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/__init__.py +1 -1
- letta/llm_api/anthropic.py +3 -26
- letta/llm_api/llm_api_tools.py +21 -21
- letta/llm_api/openai.py +12 -15
- letta/orm/agent.py +6 -0
- letta/orm/custom_columns.py +3 -0
- letta/schemas/agent.py +3 -0
- letta/schemas/sandbox_config.py +43 -1
- letta/schemas/tool_rule.py +9 -6
- letta/server/rest_api/app.py +4 -1
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/sandbox_configs.py +78 -1
- letta/server/rest_api/routers/v1/steps.py +78 -0
- letta/server/rest_api/routers/v1/tools.py +23 -9
- letta/server/server.py +0 -3
- letta/services/helpers/tool_execution_helper.py +155 -0
- letta/services/step_manager.py +55 -0
- letta/services/tool_execution_sandbox.py +25 -36
- letta/settings.py +1 -1
- letta/system.py +14 -1
- {letta_nightly-0.6.19.dev20250131104014.dist-info → letta_nightly-0.6.20.dev20250131222205.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.19.dev20250131104014.dist-info → letta_nightly-0.6.20.dev20250131222205.dist-info}/RECORD +25 -23
- {letta_nightly-0.6.19.dev20250131104014.dist-info → letta_nightly-0.6.20.dev20250131222205.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.19.dev20250131104014.dist-info → letta_nightly-0.6.20.dev20250131222205.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.19.dev20250131104014.dist-info → letta_nightly-0.6.20.dev20250131222205.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
letta/llm_api/anthropic.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import re
|
|
3
3
|
import time
|
|
4
|
-
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Generator, List, Optional, Union
|
|
5
6
|
|
|
6
7
|
import anthropic
|
|
7
8
|
from anthropic import PermissionDeniedError
|
|
@@ -36,7 +37,7 @@ from letta.schemas.openai.chat_completion_response import MessageDelta, ToolCall
|
|
|
36
37
|
from letta.services.provider_manager import ProviderManager
|
|
37
38
|
from letta.settings import model_settings
|
|
38
39
|
from letta.streaming_interface import AgentChunkStreamingInterface, AgentRefreshStreamingInterface
|
|
39
|
-
from letta.utils import get_utc_time
|
|
40
|
+
from letta.utils import get_utc_time
|
|
40
41
|
|
|
41
42
|
BASE_URL = "https://api.anthropic.com/v1"
|
|
42
43
|
|
|
@@ -567,30 +568,6 @@ def _prepare_anthropic_request(
|
|
|
567
568
|
return data
|
|
568
569
|
|
|
569
570
|
|
|
570
|
-
def get_anthropic_endpoint_and_headers(
|
|
571
|
-
base_url: str,
|
|
572
|
-
api_key: str,
|
|
573
|
-
version: str = "2023-06-01",
|
|
574
|
-
beta: Optional[str] = "tools-2024-04-04",
|
|
575
|
-
) -> Tuple[str, dict]:
|
|
576
|
-
"""
|
|
577
|
-
Dynamically generate the Anthropic endpoint and headers.
|
|
578
|
-
"""
|
|
579
|
-
url = smart_urljoin(base_url, "messages")
|
|
580
|
-
|
|
581
|
-
headers = {
|
|
582
|
-
"Content-Type": "application/json",
|
|
583
|
-
"x-api-key": api_key,
|
|
584
|
-
"anthropic-version": version,
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
# Add beta header if specified
|
|
588
|
-
if beta:
|
|
589
|
-
headers["anthropic-beta"] = beta
|
|
590
|
-
|
|
591
|
-
return url, headers
|
|
592
|
-
|
|
593
|
-
|
|
594
571
|
def anthropic_chat_completions_request(
|
|
595
572
|
data: ChatCompletionRequest,
|
|
596
573
|
inner_thoughts_xml_tag: Optional[str] = "thinking",
|
letta/llm_api/llm_api_tools.py
CHANGED
|
@@ -29,7 +29,6 @@ from letta.schemas.openai.chat_completion_request import ChatCompletionRequest,
|
|
|
29
29
|
from letta.schemas.openai.chat_completion_response import ChatCompletionResponse
|
|
30
30
|
from letta.settings import ModelSettings
|
|
31
31
|
from letta.streaming_interface import AgentChunkStreamingInterface, AgentRefreshStreamingInterface
|
|
32
|
-
from letta.utils import run_async_task
|
|
33
32
|
|
|
34
33
|
LLM_API_PROVIDER_OPTIONS = ["openai", "azure", "anthropic", "google_ai", "cohere", "local", "groq"]
|
|
35
34
|
|
|
@@ -57,7 +56,9 @@ def retry_with_exponential_backoff(
|
|
|
57
56
|
while True:
|
|
58
57
|
try:
|
|
59
58
|
return func(*args, **kwargs)
|
|
60
|
-
|
|
59
|
+
except KeyboardInterrupt:
|
|
60
|
+
# Stop retrying if user hits Ctrl-C
|
|
61
|
+
raise KeyboardInterrupt("User intentionally stopped thread. Stopping...")
|
|
61
62
|
except requests.exceptions.HTTPError as http_err:
|
|
62
63
|
|
|
63
64
|
if not hasattr(http_err, "response") or not http_err.response:
|
|
@@ -142,6 +143,11 @@ def create(
|
|
|
142
143
|
if model_settings.openai_api_key is None and llm_config.model_endpoint == "https://api.openai.com/v1":
|
|
143
144
|
# only is a problem if we are *not* using an openai proxy
|
|
144
145
|
raise LettaConfigurationError(message="OpenAI key is missing from letta config file", missing_fields=["openai_api_key"])
|
|
146
|
+
elif model_settings.openai_api_key is None:
|
|
147
|
+
# the openai python client requires a dummy API key
|
|
148
|
+
api_key = "DUMMY_API_KEY"
|
|
149
|
+
else:
|
|
150
|
+
api_key = model_settings.openai_api_key
|
|
145
151
|
|
|
146
152
|
if function_call is None and functions is not None and len(functions) > 0:
|
|
147
153
|
# force function calling for reliability, see https://platform.openai.com/docs/api-reference/chat/create#chat-create-tool_choice
|
|
@@ -157,25 +163,21 @@ def create(
|
|
|
157
163
|
assert isinstance(stream_interface, AgentChunkStreamingInterface) or isinstance(
|
|
158
164
|
stream_interface, AgentRefreshStreamingInterface
|
|
159
165
|
), type(stream_interface)
|
|
160
|
-
response =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
stream_interface=stream_interface,
|
|
166
|
-
)
|
|
166
|
+
response = openai_chat_completions_process_stream(
|
|
167
|
+
url=llm_config.model_endpoint,
|
|
168
|
+
api_key=api_key,
|
|
169
|
+
chat_completion_request=data,
|
|
170
|
+
stream_interface=stream_interface,
|
|
167
171
|
)
|
|
168
172
|
else: # Client did not request token streaming (expect a blocking backend response)
|
|
169
173
|
data.stream = False
|
|
170
174
|
if isinstance(stream_interface, AgentChunkStreamingInterface):
|
|
171
175
|
stream_interface.stream_start()
|
|
172
176
|
try:
|
|
173
|
-
response =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
chat_completion_request=data,
|
|
178
|
-
)
|
|
177
|
+
response = openai_chat_completions_request(
|
|
178
|
+
url=llm_config.model_endpoint,
|
|
179
|
+
api_key=api_key,
|
|
180
|
+
chat_completion_request=data,
|
|
179
181
|
)
|
|
180
182
|
finally:
|
|
181
183
|
if isinstance(stream_interface, AgentChunkStreamingInterface):
|
|
@@ -349,12 +351,10 @@ def create(
|
|
|
349
351
|
stream_interface.stream_start()
|
|
350
352
|
try:
|
|
351
353
|
# groq uses the openai chat completions API, so this component should be reusable
|
|
352
|
-
response =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
chat_completion_request=data,
|
|
357
|
-
)
|
|
354
|
+
response = openai_chat_completions_request(
|
|
355
|
+
url=llm_config.model_endpoint,
|
|
356
|
+
api_key=model_settings.groq_api_key,
|
|
357
|
+
chat_completion_request=data,
|
|
358
358
|
)
|
|
359
359
|
finally:
|
|
360
360
|
if isinstance(stream_interface, AgentChunkStreamingInterface):
|
letta/llm_api/openai.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Generator, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
|
-
from openai import
|
|
5
|
+
from openai import OpenAI
|
|
6
6
|
|
|
7
7
|
from letta.llm_api.helpers import add_inner_thoughts_to_functions, convert_to_structured_output, make_post_request
|
|
8
8
|
from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION, INNER_THOUGHTS_KWARG_DESCRIPTION_GO_FIRST
|
|
@@ -158,7 +158,7 @@ def build_openai_chat_completions_request(
|
|
|
158
158
|
return data
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
def openai_chat_completions_process_stream(
|
|
162
162
|
url: str,
|
|
163
163
|
api_key: str,
|
|
164
164
|
chat_completion_request: ChatCompletionRequest,
|
|
@@ -231,7 +231,7 @@ async def openai_chat_completions_process_stream(
|
|
|
231
231
|
n_chunks = 0 # approx == n_tokens
|
|
232
232
|
chunk_idx = 0
|
|
233
233
|
try:
|
|
234
|
-
|
|
234
|
+
for chat_completion_chunk in openai_chat_completions_request_stream(
|
|
235
235
|
url=url, api_key=api_key, chat_completion_request=chat_completion_request
|
|
236
236
|
):
|
|
237
237
|
assert isinstance(chat_completion_chunk, ChatCompletionChunkResponse), type(chat_completion_chunk)
|
|
@@ -382,24 +382,21 @@ async def openai_chat_completions_process_stream(
|
|
|
382
382
|
return chat_completion_response
|
|
383
383
|
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
def openai_chat_completions_request_stream(
|
|
386
386
|
url: str,
|
|
387
387
|
api_key: str,
|
|
388
388
|
chat_completion_request: ChatCompletionRequest,
|
|
389
|
-
) ->
|
|
389
|
+
) -> Generator[ChatCompletionChunkResponse, None, None]:
|
|
390
390
|
data = prepare_openai_payload(chat_completion_request)
|
|
391
391
|
data["stream"] = True
|
|
392
|
-
client =
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
)
|
|
396
|
-
stream = await client.chat.completions.create(**data)
|
|
397
|
-
async for chunk in stream:
|
|
392
|
+
client = OpenAI(api_key=api_key, base_url=url, max_retries=0)
|
|
393
|
+
stream = client.chat.completions.create(**data)
|
|
394
|
+
for chunk in stream:
|
|
398
395
|
# TODO: Use the native OpenAI objects here?
|
|
399
396
|
yield ChatCompletionChunkResponse(**chunk.model_dump(exclude_none=True))
|
|
400
397
|
|
|
401
398
|
|
|
402
|
-
|
|
399
|
+
def openai_chat_completions_request(
|
|
403
400
|
url: str,
|
|
404
401
|
api_key: str,
|
|
405
402
|
chat_completion_request: ChatCompletionRequest,
|
|
@@ -412,8 +409,8 @@ async def openai_chat_completions_request(
|
|
|
412
409
|
https://platform.openai.com/docs/guides/text-generation?lang=curl
|
|
413
410
|
"""
|
|
414
411
|
data = prepare_openai_payload(chat_completion_request)
|
|
415
|
-
client =
|
|
416
|
-
chat_completion =
|
|
412
|
+
client = OpenAI(api_key=api_key, base_url=url, max_retries=0)
|
|
413
|
+
chat_completion = client.chat.completions.create(**data)
|
|
417
414
|
return ChatCompletionResponse(**chat_completion.model_dump())
|
|
418
415
|
|
|
419
416
|
|
letta/orm/agent.py
CHANGED
|
@@ -56,6 +56,9 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
56
56
|
embedding_config: Mapped[Optional[EmbeddingConfig]] = mapped_column(
|
|
57
57
|
EmbeddingConfigColumn, doc="the embedding configuration object for this agent."
|
|
58
58
|
)
|
|
59
|
+
project_id: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The id of the project the agent belongs to.")
|
|
60
|
+
template_id: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The id of the template the agent belongs to.")
|
|
61
|
+
base_template_id: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The base template id of the agent.")
|
|
59
62
|
|
|
60
63
|
# Tool rules
|
|
61
64
|
tool_rules: Mapped[Optional[List[ToolRule]]] = mapped_column(ToolRulesColumn, doc="the tool rules for this agent.")
|
|
@@ -146,6 +149,9 @@ class Agent(SqlalchemyBase, OrganizationMixin):
|
|
|
146
149
|
"created_at": self.created_at,
|
|
147
150
|
"updated_at": self.updated_at,
|
|
148
151
|
"tool_exec_environment_variables": self.tool_exec_environment_variables,
|
|
152
|
+
"project_id": self.project_id,
|
|
153
|
+
"template_id": self.template_id,
|
|
154
|
+
"base_template_id": self.base_template_id,
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
return self.__pydantic_model__(**state)
|
letta/orm/custom_columns.py
CHANGED
|
@@ -85,10 +85,13 @@ class ToolRulesColumn(TypeDecorator):
|
|
|
85
85
|
"""Deserialize a dictionary to the appropriate ToolRule subclass based on the 'type'."""
|
|
86
86
|
rule_type = ToolRuleType(data.get("type")) # Remove 'type' field if it exists since it is a class var
|
|
87
87
|
if rule_type == ToolRuleType.run_first or rule_type == "InitToolRule":
|
|
88
|
+
data["type"] = ToolRuleType.run_first
|
|
88
89
|
return InitToolRule(**data)
|
|
89
90
|
elif rule_type == ToolRuleType.exit_loop or rule_type == "TerminalToolRule":
|
|
91
|
+
data["type"] = ToolRuleType.exit_loop
|
|
90
92
|
return TerminalToolRule(**data)
|
|
91
93
|
elif rule_type == ToolRuleType.constrain_child_tools or rule_type == "ToolRule":
|
|
94
|
+
data["type"] = ToolRuleType.constrain_child_tools
|
|
92
95
|
rule = ChildToolRule(**data)
|
|
93
96
|
return rule
|
|
94
97
|
elif rule_type == ToolRuleType.conditional:
|
letta/schemas/agent.py
CHANGED
|
@@ -81,6 +81,9 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
|
|
|
81
81
|
tool_exec_environment_variables: List[AgentEnvironmentVariable] = Field(
|
|
82
82
|
default_factory=list, description="The environment variables for tool execution specific to this agent."
|
|
83
83
|
)
|
|
84
|
+
project_id: Optional[str] = Field(None, description="The id of the project the agent belongs to.")
|
|
85
|
+
template_id: Optional[str] = Field(None, description="The id of the template the agent belongs to.")
|
|
86
|
+
base_template_id: Optional[str] = Field(None, description="The base template id of the agent.")
|
|
84
87
|
|
|
85
88
|
def get_agent_env_vars_as_dict(self) -> Dict[str, str]:
|
|
86
89
|
# Get environment variables for this agent specifically
|
letta/schemas/sandbox_config.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import json
|
|
3
|
+
import re
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import Any, Dict, List, Literal, Optional, Union
|
|
5
6
|
|
|
@@ -25,18 +26,55 @@ class SandboxRunResult(BaseModel):
|
|
|
25
26
|
sandbox_config_fingerprint: str = Field(None, description="The fingerprint of the config for the sandbox")
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class PipRequirement(BaseModel):
|
|
30
|
+
name: str = Field(..., min_length=1, description="Name of the pip package.")
|
|
31
|
+
version: Optional[str] = Field(None, description="Optional version of the package, following semantic versioning.")
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_version(cls, version: Optional[str]) -> Optional[str]:
|
|
35
|
+
if version is None:
|
|
36
|
+
return None
|
|
37
|
+
semver_pattern = re.compile(r"^\d+(\.\d+){0,2}(-[a-zA-Z0-9.]+)?$")
|
|
38
|
+
if not semver_pattern.match(version):
|
|
39
|
+
raise ValueError(f"Invalid version format: {version}. Must follow semantic versioning (e.g., 1.2.3, 2.0, 1.5.0-alpha).")
|
|
40
|
+
return version
|
|
41
|
+
|
|
42
|
+
def __init__(self, **data):
|
|
43
|
+
super().__init__(**data)
|
|
44
|
+
self.version = self.validate_version(self.version)
|
|
45
|
+
|
|
46
|
+
|
|
28
47
|
class LocalSandboxConfig(BaseModel):
|
|
29
|
-
sandbox_dir: str = Field(
|
|
48
|
+
sandbox_dir: Optional[str] = Field(None, description="Directory for the sandbox environment.")
|
|
30
49
|
use_venv: bool = Field(False, description="Whether or not to use the venv, or run directly in the same run loop.")
|
|
31
50
|
venv_name: str = Field(
|
|
32
51
|
"venv",
|
|
33
52
|
description="The name for the venv in the sandbox directory. We first search for an existing venv with this name, otherwise, we make it from the requirements.txt.",
|
|
34
53
|
)
|
|
54
|
+
pip_requirements: List[PipRequirement] = Field(
|
|
55
|
+
default_factory=list,
|
|
56
|
+
description="List of pip packages to install with mandatory name and optional version following semantic versioning. This only is considered when use_venv is True.",
|
|
57
|
+
)
|
|
35
58
|
|
|
36
59
|
@property
|
|
37
60
|
def type(self) -> "SandboxType":
|
|
38
61
|
return SandboxType.LOCAL
|
|
39
62
|
|
|
63
|
+
@model_validator(mode="before")
|
|
64
|
+
@classmethod
|
|
65
|
+
def set_default_sandbox_dir(cls, data):
|
|
66
|
+
# If `data` is not a dict (e.g., it's another Pydantic model), just return it
|
|
67
|
+
if not isinstance(data, dict):
|
|
68
|
+
return data
|
|
69
|
+
|
|
70
|
+
if data.get("sandbox_dir") is None:
|
|
71
|
+
if tool_settings.local_sandbox_dir:
|
|
72
|
+
data["sandbox_dir"] = tool_settings.local_sandbox_dir
|
|
73
|
+
else:
|
|
74
|
+
data["sandbox_dir"] = "~/.letta"
|
|
75
|
+
|
|
76
|
+
return data
|
|
77
|
+
|
|
40
78
|
|
|
41
79
|
class E2BSandboxConfig(BaseModel):
|
|
42
80
|
timeout: int = Field(5 * 60, description="Time limit for the sandbox (in seconds).")
|
|
@@ -53,6 +91,10 @@ class E2BSandboxConfig(BaseModel):
|
|
|
53
91
|
"""
|
|
54
92
|
Assign a default template value if the template field is not provided.
|
|
55
93
|
"""
|
|
94
|
+
# If `data` is not a dict (e.g., it's another Pydantic model), just return it
|
|
95
|
+
if not isinstance(data, dict):
|
|
96
|
+
return data
|
|
97
|
+
|
|
56
98
|
if data.get("template") is None:
|
|
57
99
|
data["template"] = tool_settings.e2b_sandbox_template_id
|
|
58
100
|
return data
|
letta/schemas/tool_rule.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Optional, Union
|
|
1
|
+
from typing import Annotated, Any, Dict, List, Literal, Optional, Union
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ class ChildToolRule(BaseToolRule):
|
|
|
17
17
|
A ToolRule represents a tool that can be invoked by the agent.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
type: ToolRuleType = ToolRuleType.constrain_child_tools
|
|
20
|
+
type: Literal[ToolRuleType.constrain_child_tools] = ToolRuleType.constrain_child_tools
|
|
21
21
|
children: List[str] = Field(..., description="The children tools that can be invoked.")
|
|
22
22
|
|
|
23
23
|
|
|
@@ -26,7 +26,7 @@ class ConditionalToolRule(BaseToolRule):
|
|
|
26
26
|
A ToolRule that conditionally maps to different child tools based on the output.
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
-
type: ToolRuleType = ToolRuleType.conditional
|
|
29
|
+
type: Literal[ToolRuleType.conditional] = ToolRuleType.conditional
|
|
30
30
|
default_child: Optional[str] = Field(None, description="The default child tool to be called. If None, any tool can be called.")
|
|
31
31
|
child_output_mapping: Dict[Any, str] = Field(..., description="The output case to check for mapping")
|
|
32
32
|
require_output_mapping: bool = Field(default=False, description="Whether to throw an error when output doesn't match any case")
|
|
@@ -37,7 +37,7 @@ class InitToolRule(BaseToolRule):
|
|
|
37
37
|
Represents the initial tool rule configuration.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
type: ToolRuleType = ToolRuleType.run_first
|
|
40
|
+
type: Literal[ToolRuleType.run_first] = ToolRuleType.run_first
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class TerminalToolRule(BaseToolRule):
|
|
@@ -45,7 +45,10 @@ class TerminalToolRule(BaseToolRule):
|
|
|
45
45
|
Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop.
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
type: ToolRuleType = ToolRuleType.exit_loop
|
|
48
|
+
type: Literal[ToolRuleType.exit_loop] = ToolRuleType.exit_loop
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
ToolRule =
|
|
51
|
+
ToolRule = Annotated[
|
|
52
|
+
Union[ChildToolRule, InitToolRule, TerminalToolRule, ConditionalToolRule],
|
|
53
|
+
Field(discriminator="type"),
|
|
54
|
+
]
|
letta/server/rest_api/app.py
CHANGED
|
@@ -97,7 +97,10 @@ class CheckPasswordMiddleware(BaseHTTPMiddleware):
|
|
|
97
97
|
if request.url.path == "/v1/health/" or request.url.path == "/latest/health/":
|
|
98
98
|
return await call_next(request)
|
|
99
99
|
|
|
100
|
-
if
|
|
100
|
+
if (
|
|
101
|
+
request.headers.get("X-BARE-PASSWORD") == f"password {random_password}"
|
|
102
|
+
or request.headers.get("Authorization") == f"Bearer {random_password}"
|
|
103
|
+
):
|
|
101
104
|
return await call_next(request)
|
|
102
105
|
|
|
103
106
|
return JSONResponse(
|
|
@@ -7,6 +7,7 @@ from letta.server.rest_api.routers.v1.providers import router as providers_route
|
|
|
7
7
|
from letta.server.rest_api.routers.v1.runs import router as runs_router
|
|
8
8
|
from letta.server.rest_api.routers.v1.sandbox_configs import router as sandbox_configs_router
|
|
9
9
|
from letta.server.rest_api.routers.v1.sources import router as sources_router
|
|
10
|
+
from letta.server.rest_api.routers.v1.steps import router as steps_router
|
|
10
11
|
from letta.server.rest_api.routers.v1.tags import router as tags_router
|
|
11
12
|
from letta.server.rest_api.routers.v1.tools import router as tools_router
|
|
12
13
|
|
|
@@ -21,5 +22,6 @@ ROUTERS = [
|
|
|
21
22
|
sandbox_configs_router,
|
|
22
23
|
providers_router,
|
|
23
24
|
runs_router,
|
|
25
|
+
steps_router,
|
|
24
26
|
tags_router,
|
|
25
27
|
]
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
1
3
|
from typing import List, Optional
|
|
2
4
|
|
|
3
|
-
from fastapi import APIRouter, Depends, Query
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
4
6
|
|
|
7
|
+
from letta.log import get_logger
|
|
5
8
|
from letta.schemas.environment_variables import SandboxEnvironmentVariable as PydanticEnvVar
|
|
6
9
|
from letta.schemas.environment_variables import SandboxEnvironmentVariableCreate, SandboxEnvironmentVariableUpdate
|
|
10
|
+
from letta.schemas.sandbox_config import LocalSandboxConfig
|
|
7
11
|
from letta.schemas.sandbox_config import SandboxConfig as PydanticSandboxConfig
|
|
8
12
|
from letta.schemas.sandbox_config import SandboxConfigCreate, SandboxConfigUpdate, SandboxType
|
|
9
13
|
from letta.server.rest_api.utils import get_letta_server, get_user_id
|
|
10
14
|
from letta.server.server import SyncServer
|
|
15
|
+
from letta.services.helpers.tool_execution_helper import create_venv_for_local_sandbox, install_pip_requirements_for_sandbox
|
|
11
16
|
|
|
12
17
|
router = APIRouter(prefix="/sandbox-config", tags=["sandbox-config"])
|
|
13
18
|
|
|
19
|
+
logger = get_logger(__name__)
|
|
14
20
|
|
|
15
21
|
### Sandbox Config Routes
|
|
16
22
|
|
|
@@ -44,6 +50,34 @@ def create_default_local_sandbox_config(
|
|
|
44
50
|
return server.sandbox_config_manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.LOCAL, actor=actor)
|
|
45
51
|
|
|
46
52
|
|
|
53
|
+
@router.post("/local", response_model=PydanticSandboxConfig)
|
|
54
|
+
def create_custom_local_sandbox_config(
|
|
55
|
+
local_sandbox_config: LocalSandboxConfig,
|
|
56
|
+
server: SyncServer = Depends(get_letta_server),
|
|
57
|
+
user_id: str = Depends(get_user_id),
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Create or update a custom LocalSandboxConfig, including pip_requirements.
|
|
61
|
+
"""
|
|
62
|
+
# Ensure the incoming config is of type LOCAL
|
|
63
|
+
if local_sandbox_config.type != SandboxType.LOCAL:
|
|
64
|
+
raise HTTPException(
|
|
65
|
+
status_code=400,
|
|
66
|
+
detail=f"Provided config must be of type '{SandboxType.LOCAL.value}'.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Retrieve the user (actor)
|
|
70
|
+
actor = server.user_manager.get_user_or_default(user_id=user_id)
|
|
71
|
+
|
|
72
|
+
# Wrap the LocalSandboxConfig into a SandboxConfigCreate
|
|
73
|
+
sandbox_config_create = SandboxConfigCreate(config=local_sandbox_config)
|
|
74
|
+
|
|
75
|
+
# Use the manager to create or update the sandbox config
|
|
76
|
+
sandbox_config = server.sandbox_config_manager.create_or_update_sandbox_config(sandbox_config_create, actor=actor)
|
|
77
|
+
|
|
78
|
+
return sandbox_config
|
|
79
|
+
|
|
80
|
+
|
|
47
81
|
@router.patch("/{sandbox_config_id}", response_model=PydanticSandboxConfig)
|
|
48
82
|
def update_sandbox_config(
|
|
49
83
|
sandbox_config_id: str,
|
|
@@ -77,6 +111,49 @@ def list_sandbox_configs(
|
|
|
77
111
|
return server.sandbox_config_manager.list_sandbox_configs(actor, limit=limit, after=after, sandbox_type=sandbox_type)
|
|
78
112
|
|
|
79
113
|
|
|
114
|
+
@router.post("/local/recreate-venv", response_model=PydanticSandboxConfig)
|
|
115
|
+
def force_recreate_local_sandbox_venv(
|
|
116
|
+
server: SyncServer = Depends(get_letta_server),
|
|
117
|
+
user_id: str = Depends(get_user_id),
|
|
118
|
+
):
|
|
119
|
+
"""
|
|
120
|
+
Forcefully recreate the virtual environment for the local sandbox.
|
|
121
|
+
Deletes and recreates the venv, then reinstalls required dependencies.
|
|
122
|
+
"""
|
|
123
|
+
actor = server.user_manager.get_user_or_default(user_id=user_id)
|
|
124
|
+
|
|
125
|
+
# Retrieve the local sandbox config
|
|
126
|
+
sbx_config = server.sandbox_config_manager.get_or_create_default_sandbox_config(sandbox_type=SandboxType.LOCAL, actor=actor)
|
|
127
|
+
|
|
128
|
+
local_configs = sbx_config.get_local_config()
|
|
129
|
+
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
|
|
130
|
+
venv_path = os.path.join(sandbox_dir, local_configs.venv_name)
|
|
131
|
+
|
|
132
|
+
# Check if venv exists, and delete if necessary
|
|
133
|
+
if os.path.isdir(venv_path):
|
|
134
|
+
try:
|
|
135
|
+
shutil.rmtree(venv_path)
|
|
136
|
+
logger.info(f"Deleted existing virtual environment at: {venv_path}")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise HTTPException(status_code=500, detail=f"Failed to delete existing venv: {e}")
|
|
139
|
+
|
|
140
|
+
# Recreate the virtual environment
|
|
141
|
+
try:
|
|
142
|
+
create_venv_for_local_sandbox(sandbox_dir_path=sandbox_dir, venv_path=str(venv_path), env=os.environ.copy(), force_recreate=True)
|
|
143
|
+
logger.info(f"Successfully recreated virtual environment at: {venv_path}")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
raise HTTPException(status_code=500, detail=f"Failed to recreate venv: {e}")
|
|
146
|
+
|
|
147
|
+
# Install pip requirements
|
|
148
|
+
try:
|
|
149
|
+
install_pip_requirements_for_sandbox(local_configs=local_configs, env=os.environ.copy())
|
|
150
|
+
logger.info(f"Successfully installed pip requirements for venv at: {venv_path}")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
raise HTTPException(status_code=500, detail=f"Failed to install pip requirements: {e}")
|
|
153
|
+
|
|
154
|
+
return sbx_config
|
|
155
|
+
|
|
156
|
+
|
|
80
157
|
### Sandbox Environment Variable Routes
|
|
81
158
|
|
|
82
159
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Depends, Header, HTTPException, Query
|
|
5
|
+
|
|
6
|
+
from letta.orm.errors import NoResultFound
|
|
7
|
+
from letta.schemas.step import Step
|
|
8
|
+
from letta.server.rest_api.utils import get_letta_server
|
|
9
|
+
from letta.server.server import SyncServer
|
|
10
|
+
|
|
11
|
+
router = APIRouter(prefix="/steps", tags=["steps"])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("", response_model=List[Step], operation_id="list_steps")
|
|
15
|
+
def list_steps(
|
|
16
|
+
before: Optional[str] = Query(None, description="Return steps before this step ID"),
|
|
17
|
+
after: Optional[str] = Query(None, description="Return steps after this step ID"),
|
|
18
|
+
limit: Optional[int] = Query(50, description="Maximum number of steps to return"),
|
|
19
|
+
order: Optional[str] = Query("desc", description="Sort order (asc or desc)"),
|
|
20
|
+
start_date: Optional[str] = Query(None, description='Return steps after this ISO datetime (e.g. "2025-01-29T15:01:19-08:00")'),
|
|
21
|
+
end_date: Optional[str] = Query(None, description='Return steps before this ISO datetime (e.g. "2025-01-29T15:01:19-08:00")'),
|
|
22
|
+
model: Optional[str] = Query(None, description="Filter by the name of the model used for the step"),
|
|
23
|
+
server: SyncServer = Depends(get_letta_server),
|
|
24
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
List steps with optional pagination and date filters.
|
|
28
|
+
Dates should be provided in ISO 8601 format (e.g. 2025-01-29T15:01:19-08:00)
|
|
29
|
+
"""
|
|
30
|
+
actor = server.user_manager.get_user_or_default(user_id=user_id)
|
|
31
|
+
|
|
32
|
+
# Convert ISO strings to datetime objects if provided
|
|
33
|
+
start_dt = datetime.fromisoformat(start_date) if start_date else None
|
|
34
|
+
end_dt = datetime.fromisoformat(end_date) if end_date else None
|
|
35
|
+
|
|
36
|
+
return server.step_manager.list_steps(
|
|
37
|
+
actor=actor,
|
|
38
|
+
before=before,
|
|
39
|
+
after=after,
|
|
40
|
+
start_date=start_dt,
|
|
41
|
+
end_date=end_dt,
|
|
42
|
+
limit=limit,
|
|
43
|
+
order=order,
|
|
44
|
+
model=model,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router.get("/{step_id}", response_model=Step, operation_id="retrieve_step")
|
|
49
|
+
def retrieve_step(
|
|
50
|
+
step_id: str,
|
|
51
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
52
|
+
server: SyncServer = Depends(get_letta_server),
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Get a step by ID.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
return server.step_manager.get_step(step_id=step_id)
|
|
59
|
+
except NoResultFound:
|
|
60
|
+
raise HTTPException(status_code=404, detail="Step not found")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@router.patch("/{step_id}/transaction/{transaction_id}", response_model=Step, operation_id="update_step_transaction_id")
|
|
64
|
+
def update_step_transaction_id(
|
|
65
|
+
step_id: str,
|
|
66
|
+
transaction_id: str,
|
|
67
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
68
|
+
server: SyncServer = Depends(get_letta_server),
|
|
69
|
+
):
|
|
70
|
+
"""
|
|
71
|
+
Update the transaction ID for a step.
|
|
72
|
+
"""
|
|
73
|
+
actor = server.user_manager.get_user_or_default(user_id=user_id)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
return server.step_manager.update_step_transaction_id(actor, step_id=step_id, transaction_id=transaction_id)
|
|
77
|
+
except NoResultFound:
|
|
78
|
+
raise HTTPException(status_code=404, detail="Step not found")
|
|
@@ -8,15 +8,19 @@ from composio.tools.base.abs import InvalidClassDefinition
|
|
|
8
8
|
from fastapi import APIRouter, Body, Depends, Header, HTTPException
|
|
9
9
|
|
|
10
10
|
from letta.errors import LettaToolCreateError
|
|
11
|
+
from letta.log import get_logger
|
|
11
12
|
from letta.orm.errors import UniqueConstraintViolationError
|
|
12
13
|
from letta.schemas.letta_message import ToolReturnMessage
|
|
13
14
|
from letta.schemas.tool import Tool, ToolCreate, ToolRunFromSource, ToolUpdate
|
|
14
15
|
from letta.schemas.user import User
|
|
15
16
|
from letta.server.rest_api.utils import get_letta_server
|
|
16
17
|
from letta.server.server import SyncServer
|
|
18
|
+
from letta.settings import tool_settings
|
|
17
19
|
|
|
18
20
|
router = APIRouter(prefix="/tools", tags=["tools"])
|
|
19
21
|
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
@router.delete("/{tool_id}", operation_id="delete_tool")
|
|
22
26
|
def delete_tool(
|
|
@@ -52,6 +56,7 @@ def retrieve_tool(
|
|
|
52
56
|
def list_tools(
|
|
53
57
|
after: Optional[str] = None,
|
|
54
58
|
limit: Optional[int] = 50,
|
|
59
|
+
name: Optional[str] = None,
|
|
55
60
|
server: SyncServer = Depends(get_letta_server),
|
|
56
61
|
user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
|
|
57
62
|
):
|
|
@@ -60,6 +65,9 @@ def list_tools(
|
|
|
60
65
|
"""
|
|
61
66
|
try:
|
|
62
67
|
actor = server.user_manager.get_user_or_default(user_id=user_id)
|
|
68
|
+
if name is not None:
|
|
69
|
+
tool = server.tool_manager.get_tool_by_name(name=name, actor=actor)
|
|
70
|
+
return [tool] if tool else []
|
|
63
71
|
return server.tool_manager.list_tools(actor=actor, after=after, limit=limit)
|
|
64
72
|
except Exception as e:
|
|
65
73
|
# Log or print the full exception here for debugging
|
|
@@ -293,12 +301,18 @@ def add_composio_tool(
|
|
|
293
301
|
def get_composio_key(server: SyncServer, actor: User):
|
|
294
302
|
api_keys = server.sandbox_config_manager.list_sandbox_env_vars_by_key(key="COMPOSIO_API_KEY", actor=actor)
|
|
295
303
|
if not api_keys:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
logger.warning(f"No API keys found for Composio. Defaulting to the environment variable...")
|
|
305
|
+
|
|
306
|
+
if tool_settings.composio_api_key:
|
|
307
|
+
return tool_settings.composio_api_key
|
|
308
|
+
else:
|
|
309
|
+
# Nothing, raise fatal warning
|
|
310
|
+
raise HTTPException(
|
|
311
|
+
status_code=400, # Bad Request
|
|
312
|
+
detail=f"No API keys found for Composio. Please add your Composio API Key as an environment variable for your sandbox configuration, or set it as environment variable COMPOSIO_API_KEY.",
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
# TODO: Add more protections around this
|
|
316
|
+
# Ideally, not tied to a specific sandbox, but for now we just get the first one
|
|
317
|
+
# Theoretically possible for someone to have different composio api keys per sandbox
|
|
318
|
+
return api_keys[0].value
|
letta/server/server.py
CHANGED
|
@@ -404,9 +404,6 @@ class SyncServer(Server):
|
|
|
404
404
|
if model_settings.lmstudio_base_url.endswith("/v1")
|
|
405
405
|
else model_settings.lmstudio_base_url + "/v1"
|
|
406
406
|
)
|
|
407
|
-
# Set the OpenAI API key to something non-empty
|
|
408
|
-
if model_settings.openai_api_key is None:
|
|
409
|
-
model_settings.openai_api_key = "DUMMY"
|
|
410
407
|
self._enabled_providers.append(LMStudioOpenAIProvider(base_url=lmstudio_url))
|
|
411
408
|
|
|
412
409
|
def load_agent(self, agent_id: str, actor: User, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import venv
|
|
5
|
+
from typing import Dict, Optional
|
|
6
|
+
|
|
7
|
+
from letta.log import get_logger
|
|
8
|
+
from letta.schemas.sandbox_config import LocalSandboxConfig
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_python_executable(local_configs: LocalSandboxConfig) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Determines the Python executable path based on sandbox configuration and platform.
|
|
16
|
+
Resolves any '~' (tilde) paths to absolute paths.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: Full path to the Python binary.
|
|
20
|
+
"""
|
|
21
|
+
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
|
|
22
|
+
|
|
23
|
+
if not local_configs.use_venv:
|
|
24
|
+
return "python.exe" if platform.system().lower().startswith("win") else "python3"
|
|
25
|
+
|
|
26
|
+
venv_path = os.path.join(sandbox_dir, local_configs.venv_name)
|
|
27
|
+
python_exec = (
|
|
28
|
+
os.path.join(venv_path, "Scripts", "python.exe")
|
|
29
|
+
if platform.system().startswith("Win")
|
|
30
|
+
else os.path.join(venv_path, "bin", "python3")
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if not os.path.isfile(python_exec):
|
|
34
|
+
raise FileNotFoundError(f"Python executable not found: {python_exec}. Ensure the virtual environment exists.")
|
|
35
|
+
|
|
36
|
+
return python_exec
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_subprocess(command: list, env: Optional[Dict[str, str]] = None, fail_msg: str = "Command failed"):
|
|
40
|
+
"""
|
|
41
|
+
Helper to execute a subprocess with logging and error handling.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
command (list): The command to run as a list of arguments.
|
|
45
|
+
env (dict, optional): The environment variables to use for the process.
|
|
46
|
+
fail_msg (str): The error message to log in case of failure.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
RuntimeError: If the subprocess execution fails.
|
|
50
|
+
"""
|
|
51
|
+
logger.info(f"Running command: {' '.join(command)}")
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(command, check=True, capture_output=True, text=True, env=env)
|
|
54
|
+
logger.info(f"Command successful. Output:\n{result.stdout}")
|
|
55
|
+
return result.stdout
|
|
56
|
+
except subprocess.CalledProcessError as e:
|
|
57
|
+
logger.error(f"{fail_msg}\nSTDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
|
|
58
|
+
raise RuntimeError(f"{fail_msg}: {e.stderr.strip()}") from e
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ensure_pip_is_up_to_date(python_exec: str, env: Optional[Dict[str, str]] = None):
|
|
62
|
+
"""
|
|
63
|
+
Ensures pip, setuptools, and wheel are up to date before installing any other dependencies.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
python_exec (str): Path to the Python executable to use.
|
|
67
|
+
env (dict, optional): Environment variables to pass to subprocess.
|
|
68
|
+
"""
|
|
69
|
+
run_subprocess(
|
|
70
|
+
[python_exec, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"],
|
|
71
|
+
env=env,
|
|
72
|
+
fail_msg="Failed to upgrade pip, setuptools, and wheel.",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def install_pip_requirements_for_sandbox(
|
|
77
|
+
local_configs: LocalSandboxConfig,
|
|
78
|
+
upgrade: bool = True,
|
|
79
|
+
user_install_if_no_venv: bool = False,
|
|
80
|
+
env: Optional[Dict[str, str]] = None,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Installs the specified pip requirements inside the correct environment (venv or system).
|
|
84
|
+
"""
|
|
85
|
+
if not local_configs.pip_requirements:
|
|
86
|
+
logger.debug("No pip requirements specified; skipping installation.")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
|
|
90
|
+
local_configs.sandbox_dir = sandbox_dir # Update the object to store the absolute path
|
|
91
|
+
|
|
92
|
+
python_exec = find_python_executable(local_configs)
|
|
93
|
+
|
|
94
|
+
# If using a virtual environment, upgrade pip before installing dependencies.
|
|
95
|
+
if local_configs.use_venv:
|
|
96
|
+
ensure_pip_is_up_to_date(python_exec, env=env)
|
|
97
|
+
|
|
98
|
+
# Construct package list
|
|
99
|
+
packages = [f"{req.name}=={req.version}" if req.version else req.name for req in local_configs.pip_requirements]
|
|
100
|
+
|
|
101
|
+
# Construct pip install command
|
|
102
|
+
pip_cmd = [python_exec, "-m", "pip", "install"]
|
|
103
|
+
if upgrade:
|
|
104
|
+
pip_cmd.append("--upgrade")
|
|
105
|
+
pip_cmd += packages
|
|
106
|
+
|
|
107
|
+
if user_install_if_no_venv and not local_configs.use_venv:
|
|
108
|
+
pip_cmd.append("--user")
|
|
109
|
+
|
|
110
|
+
run_subprocess(pip_cmd, env=env, fail_msg=f"Failed to install packages: {', '.join(packages)}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def create_venv_for_local_sandbox(sandbox_dir_path: str, venv_path: str, env: Dict[str, str], force_recreate: bool):
|
|
114
|
+
"""
|
|
115
|
+
Creates a virtual environment for the sandbox. If force_recreate is True, deletes and recreates the venv.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
sandbox_dir_path (str): Path to the sandbox directory.
|
|
119
|
+
venv_path (str): Path to the virtual environment directory.
|
|
120
|
+
env (dict): Environment variables to use.
|
|
121
|
+
force_recreate (bool): If True, delete and recreate the virtual environment.
|
|
122
|
+
"""
|
|
123
|
+
sandbox_dir_path = os.path.expanduser(sandbox_dir_path)
|
|
124
|
+
venv_path = os.path.expanduser(venv_path)
|
|
125
|
+
|
|
126
|
+
# If venv exists and force_recreate is True, delete it
|
|
127
|
+
if force_recreate and os.path.isdir(venv_path):
|
|
128
|
+
logger.warning(f"Force recreating virtual environment at: {venv_path}")
|
|
129
|
+
import shutil
|
|
130
|
+
|
|
131
|
+
shutil.rmtree(venv_path)
|
|
132
|
+
|
|
133
|
+
# Create venv if it does not exist
|
|
134
|
+
if not os.path.isdir(venv_path):
|
|
135
|
+
logger.info(f"Creating new virtual environment at {venv_path}")
|
|
136
|
+
venv.create(venv_path, with_pip=True)
|
|
137
|
+
|
|
138
|
+
pip_path = os.path.join(venv_path, "bin", "pip")
|
|
139
|
+
try:
|
|
140
|
+
# Step 2: Upgrade pip
|
|
141
|
+
logger.info("Upgrading pip in the virtual environment...")
|
|
142
|
+
subprocess.run([pip_path, "install", "--upgrade", "pip"], env=env, check=True)
|
|
143
|
+
|
|
144
|
+
# Step 3: Install packages from requirements.txt if available
|
|
145
|
+
requirements_txt_path = os.path.join(sandbox_dir_path, "requirements.txt")
|
|
146
|
+
if os.path.isfile(requirements_txt_path):
|
|
147
|
+
logger.info(f"Installing packages from requirements file: {requirements_txt_path}")
|
|
148
|
+
subprocess.run([pip_path, "install", "-r", requirements_txt_path], env=env, check=True)
|
|
149
|
+
logger.info("Successfully installed packages from requirements.txt")
|
|
150
|
+
else:
|
|
151
|
+
logger.warning("No requirements.txt file found. Skipping package installation.")
|
|
152
|
+
|
|
153
|
+
except subprocess.CalledProcessError as e:
|
|
154
|
+
logger.error(f"Error while setting up the virtual environment: {e}")
|
|
155
|
+
raise RuntimeError(f"Failed to set up the virtual environment: {e}")
|
letta/services/step_manager.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
from typing import List, Literal, Optional
|
|
2
3
|
|
|
3
4
|
from sqlalchemy import select
|
|
@@ -20,6 +21,34 @@ class StepManager:
|
|
|
20
21
|
|
|
21
22
|
self.session_maker = db_context
|
|
22
23
|
|
|
24
|
+
@enforce_types
|
|
25
|
+
def list_steps(
|
|
26
|
+
self,
|
|
27
|
+
actor: PydanticUser,
|
|
28
|
+
before: Optional[str] = None,
|
|
29
|
+
after: Optional[str] = None,
|
|
30
|
+
start_date: Optional[datetime] = None,
|
|
31
|
+
end_date: Optional[datetime] = None,
|
|
32
|
+
limit: Optional[int] = 50,
|
|
33
|
+
order: Optional[str] = None,
|
|
34
|
+
model: Optional[str] = None,
|
|
35
|
+
) -> List[PydanticStep]:
|
|
36
|
+
"""List all jobs with optional pagination and status filter."""
|
|
37
|
+
with self.session_maker() as session:
|
|
38
|
+
filter_kwargs = {"organization_id": actor.organization_id, "model": model}
|
|
39
|
+
|
|
40
|
+
steps = StepModel.list(
|
|
41
|
+
db_session=session,
|
|
42
|
+
before=before,
|
|
43
|
+
after=after,
|
|
44
|
+
start_date=start_date,
|
|
45
|
+
end_date=end_date,
|
|
46
|
+
limit=limit,
|
|
47
|
+
ascending=True if order == "asc" else False,
|
|
48
|
+
**filter_kwargs,
|
|
49
|
+
)
|
|
50
|
+
return [step.to_pydantic() for step in steps]
|
|
51
|
+
|
|
23
52
|
@enforce_types
|
|
24
53
|
def log_step(
|
|
25
54
|
self,
|
|
@@ -58,6 +87,32 @@ class StepManager:
|
|
|
58
87
|
step = StepModel.read(db_session=session, identifier=step_id)
|
|
59
88
|
return step.to_pydantic()
|
|
60
89
|
|
|
90
|
+
@enforce_types
|
|
91
|
+
def update_step_transaction_id(self, actor: PydanticUser, step_id: str, transaction_id: str) -> PydanticStep:
|
|
92
|
+
"""Update the transaction ID for a step.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
actor: The user making the request
|
|
96
|
+
step_id: The ID of the step to update
|
|
97
|
+
transaction_id: The new transaction ID to set
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The updated step
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
NoResultFound: If the step does not exist
|
|
104
|
+
"""
|
|
105
|
+
with self.session_maker() as session:
|
|
106
|
+
step = session.get(StepModel, step_id)
|
|
107
|
+
if not step:
|
|
108
|
+
raise NoResultFound(f"Step with id {step_id} does not exist")
|
|
109
|
+
if step.organization_id != actor.organization_id:
|
|
110
|
+
raise Exception("Unauthorized")
|
|
111
|
+
|
|
112
|
+
step.tid = transaction_id
|
|
113
|
+
session.commit()
|
|
114
|
+
return step.to_pydantic()
|
|
115
|
+
|
|
61
116
|
def _verify_job_access(
|
|
62
117
|
self,
|
|
63
118
|
session: Session,
|
|
@@ -9,7 +9,6 @@ import sys
|
|
|
9
9
|
import tempfile
|
|
10
10
|
import traceback
|
|
11
11
|
import uuid
|
|
12
|
-
import venv
|
|
13
12
|
from typing import Any, Dict, Optional
|
|
14
13
|
|
|
15
14
|
from letta.log import get_logger
|
|
@@ -17,6 +16,11 @@ from letta.schemas.agent import AgentState
|
|
|
17
16
|
from letta.schemas.sandbox_config import SandboxConfig, SandboxRunResult, SandboxType
|
|
18
17
|
from letta.schemas.tool import Tool
|
|
19
18
|
from letta.schemas.user import User
|
|
19
|
+
from letta.services.helpers.tool_execution_helper import (
|
|
20
|
+
create_venv_for_local_sandbox,
|
|
21
|
+
find_python_executable,
|
|
22
|
+
install_pip_requirements_for_sandbox,
|
|
23
|
+
)
|
|
20
24
|
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
21
25
|
from letta.services.tool_manager import ToolManager
|
|
22
26
|
from letta.settings import tool_settings
|
|
@@ -38,7 +42,9 @@ class ToolExecutionSandbox:
|
|
|
38
42
|
# We make this a long random string to avoid collisions with any variables in the user's code
|
|
39
43
|
LOCAL_SANDBOX_RESULT_VAR_NAME = "result_ZQqiequkcFwRwwGQMqkt"
|
|
40
44
|
|
|
41
|
-
def __init__(
|
|
45
|
+
def __init__(
|
|
46
|
+
self, tool_name: str, args: dict, user: User, force_recreate=True, force_recreate_venv=False, tool_object: Optional[Tool] = None
|
|
47
|
+
):
|
|
42
48
|
self.tool_name = tool_name
|
|
43
49
|
self.args = args
|
|
44
50
|
self.user = user
|
|
@@ -58,6 +64,7 @@ class ToolExecutionSandbox:
|
|
|
58
64
|
|
|
59
65
|
self.sandbox_config_manager = SandboxConfigManager(tool_settings)
|
|
60
66
|
self.force_recreate = force_recreate
|
|
67
|
+
self.force_recreate_venv = force_recreate_venv
|
|
61
68
|
|
|
62
69
|
def run(self, agent_state: Optional[AgentState] = None, additional_env_vars: Optional[Dict] = None) -> SandboxRunResult:
|
|
63
70
|
"""
|
|
@@ -150,36 +157,41 @@ class ToolExecutionSandbox:
|
|
|
150
157
|
|
|
151
158
|
def run_local_dir_sandbox_venv(self, sbx_config: SandboxConfig, env: Dict[str, str], temp_file_path: str) -> SandboxRunResult:
|
|
152
159
|
local_configs = sbx_config.get_local_config()
|
|
153
|
-
|
|
160
|
+
sandbox_dir = os.path.expanduser(local_configs.sandbox_dir) # Expand tilde
|
|
161
|
+
venv_path = os.path.join(sandbox_dir, local_configs.venv_name)
|
|
154
162
|
|
|
155
|
-
#
|
|
156
|
-
if not os.path.isdir(venv_path):
|
|
163
|
+
# Recreate venv if required
|
|
164
|
+
if self.force_recreate_venv or not os.path.isdir(venv_path):
|
|
157
165
|
logger.warning(f"Virtual environment directory does not exist at: {venv_path}, creating one now...")
|
|
158
|
-
|
|
166
|
+
create_venv_for_local_sandbox(
|
|
167
|
+
sandbox_dir_path=sandbox_dir, venv_path=venv_path, env=env, force_recreate=self.force_recreate_venv
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
install_pip_requirements_for_sandbox(local_configs, env=env)
|
|
159
171
|
|
|
160
|
-
# Ensure
|
|
161
|
-
python_executable =
|
|
172
|
+
# Ensure Python executable exists
|
|
173
|
+
python_executable = find_python_executable(local_configs)
|
|
162
174
|
if not os.path.isfile(python_executable):
|
|
163
175
|
raise FileNotFoundError(f"Python executable not found in virtual environment: {python_executable}")
|
|
164
176
|
|
|
165
|
-
# Set up
|
|
177
|
+
# Set up environment variables
|
|
166
178
|
env["VIRTUAL_ENV"] = venv_path
|
|
167
179
|
env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
|
|
168
|
-
# Suppress all warnings
|
|
169
180
|
env["PYTHONWARNINGS"] = "ignore"
|
|
170
181
|
|
|
171
|
-
# Execute the code
|
|
182
|
+
# Execute the code
|
|
172
183
|
try:
|
|
173
184
|
result = subprocess.run(
|
|
174
|
-
[
|
|
185
|
+
[python_executable, temp_file_path],
|
|
175
186
|
env=env,
|
|
176
|
-
cwd=
|
|
187
|
+
cwd=sandbox_dir,
|
|
177
188
|
timeout=60,
|
|
178
189
|
capture_output=True,
|
|
179
190
|
text=True,
|
|
180
191
|
)
|
|
181
192
|
func_result, stdout = self.parse_out_function_results_markers(result.stdout)
|
|
182
193
|
func_return, agent_state = self.parse_best_effort(func_result)
|
|
194
|
+
|
|
183
195
|
return SandboxRunResult(
|
|
184
196
|
func_return=func_return,
|
|
185
197
|
agent_state=agent_state,
|
|
@@ -260,29 +272,6 @@ class ToolExecutionSandbox:
|
|
|
260
272
|
end_index = text.index(self.LOCAL_SANDBOX_RESULT_END_MARKER)
|
|
261
273
|
return text[start_index:end_index], text[: start_index - marker_len] + text[end_index + +marker_len :]
|
|
262
274
|
|
|
263
|
-
def create_venv_for_local_sandbox(self, sandbox_dir_path: str, venv_path: str, env: Dict[str, str]):
|
|
264
|
-
# Step 1: Create the virtual environment
|
|
265
|
-
venv.create(venv_path, with_pip=True)
|
|
266
|
-
|
|
267
|
-
pip_path = os.path.join(venv_path, "bin", "pip")
|
|
268
|
-
try:
|
|
269
|
-
# Step 2: Upgrade pip
|
|
270
|
-
logger.info("Upgrading pip in the virtual environment...")
|
|
271
|
-
subprocess.run([pip_path, "install", "--upgrade", "pip"], env=env, check=True)
|
|
272
|
-
|
|
273
|
-
# Step 3: Install packages from requirements.txt if provided
|
|
274
|
-
requirements_txt_path = os.path.join(sandbox_dir_path, self.REQUIREMENT_TXT_NAME)
|
|
275
|
-
if os.path.isfile(requirements_txt_path):
|
|
276
|
-
logger.info(f"Installing packages from requirements file: {requirements_txt_path}")
|
|
277
|
-
subprocess.run([pip_path, "install", "-r", requirements_txt_path], env=env, check=True)
|
|
278
|
-
logger.info("Successfully installed packages from requirements.txt")
|
|
279
|
-
else:
|
|
280
|
-
logger.warning("No requirements.txt file provided or the file does not exist. Skipping package installation.")
|
|
281
|
-
|
|
282
|
-
except subprocess.CalledProcessError as e:
|
|
283
|
-
logger.error(f"Error while setting up the virtual environment: {e}")
|
|
284
|
-
raise RuntimeError(f"Failed to set up the virtual environment: {e}")
|
|
285
|
-
|
|
286
275
|
# e2b sandbox specific functions
|
|
287
276
|
|
|
288
277
|
def run_e2b_sandbox(self, agent_state: Optional[AgentState] = None, additional_env_vars: Optional[Dict] = None) -> SandboxRunResult:
|
letta/settings.py
CHANGED
|
@@ -121,7 +121,7 @@ if "--use-file-pg-uri" in sys.argv:
|
|
|
121
121
|
try:
|
|
122
122
|
with open(Path.home() / ".letta/pg_uri", "r") as f:
|
|
123
123
|
default_pg_uri = f.read()
|
|
124
|
-
print("Read pg_uri from ~/.letta/pg_uri")
|
|
124
|
+
print(f"Read pg_uri from ~/.letta/pg_uri: {default_pg_uri}")
|
|
125
125
|
except FileNotFoundError:
|
|
126
126
|
pass
|
|
127
127
|
|
letta/system.py
CHANGED
|
@@ -152,6 +152,15 @@ def package_function_response(was_success, response_string, timestamp=None):
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
def package_system_message(system_message, message_type="system_alert", time=None):
|
|
155
|
+
# error handling for recursive packaging
|
|
156
|
+
try:
|
|
157
|
+
message_json = json.loads(system_message)
|
|
158
|
+
if "type" in message_json and message_json["type"] == message_type:
|
|
159
|
+
warnings.warn(f"Attempted to pack a system message that is already packed. Not packing: '{system_message}'")
|
|
160
|
+
return system_message
|
|
161
|
+
except:
|
|
162
|
+
pass # do nothing, expected behavior that the message is not JSON
|
|
163
|
+
|
|
155
164
|
formatted_time = time if time else get_local_time()
|
|
156
165
|
packaged_message = {
|
|
157
166
|
"type": message_type,
|
|
@@ -214,7 +223,7 @@ def unpack_message(packed_message) -> str:
|
|
|
214
223
|
try:
|
|
215
224
|
message_json = json.loads(packed_message)
|
|
216
225
|
except:
|
|
217
|
-
warnings.warn(f"Was unable to load message as JSON to unpack: '
|
|
226
|
+
warnings.warn(f"Was unable to load message as JSON to unpack: '{packed_message}'")
|
|
218
227
|
return packed_message
|
|
219
228
|
|
|
220
229
|
if "message" not in message_json:
|
|
@@ -224,4 +233,8 @@ def unpack_message(packed_message) -> str:
|
|
|
224
233
|
warnings.warn(f"Was unable to find 'message' field in packed message object: '{packed_message}'")
|
|
225
234
|
return packed_message
|
|
226
235
|
else:
|
|
236
|
+
message_type = message_json["type"]
|
|
237
|
+
if message_type != "user_message":
|
|
238
|
+
warnings.warn(f"Expected type to be 'user_message', but was '{message_type}', so not unpacking: '{packed_message}'")
|
|
239
|
+
return packed_message
|
|
227
240
|
return message_json.get("message")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
letta/__init__.py,sha256=
|
|
1
|
+
letta/__init__.py,sha256=EApmTks3-qbEB16_xaPs03NX3OVfVYtxNA3MTpE-oRs,919
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
3
|
letta/agent.py,sha256=3-YDxRLMKPrXnmvZ1qstG2MmH9FU9cUQ0cDYZbFQ9eM,56575
|
|
4
4
|
letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
|
|
@@ -32,16 +32,16 @@ letta/humans/examples/basic.txt,sha256=Lcp8YESTWvOJgO4Yf_yyQmgo5bKakeB1nIVrwEGG6
|
|
|
32
32
|
letta/humans/examples/cs_phd.txt,sha256=9C9ZAV_VuG7GB31ksy3-_NAyk8rjE6YtVOkhp08k1xw,297
|
|
33
33
|
letta/interface.py,sha256=JszHyhIK34dpV0h5KL0CD1W4svh4eijaHGgfOYyZOhg,12755
|
|
34
34
|
letta/llm_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
letta/llm_api/anthropic.py,sha256=
|
|
35
|
+
letta/llm_api/anthropic.py,sha256=ZPPjpYZh8hleSjHkbRF27EEwSp-pg23JlSo-b1wbWBY,33602
|
|
36
36
|
letta/llm_api/aws_bedrock.py,sha256=-ms9tdROu8DLrEZJ9XgL-IyIOU_0UJKuhfRbjLs0_Gc,3838
|
|
37
37
|
letta/llm_api/azure_openai.py,sha256=Y1HKPog1XzM_f7ujUK_Gv2zQkoy5pU-1bKiUnvSxSrs,6297
|
|
38
38
|
letta/llm_api/azure_openai_constants.py,sha256=_f7NKjKBPxGPFQPfP1e0umHk4Jmf56qNjyecI0PqWqU,267
|
|
39
39
|
letta/llm_api/cohere.py,sha256=H5kzYH_aQAnGNq7lip7XyKGLEOKC318Iw0_tiTP6kc4,14772
|
|
40
40
|
letta/llm_api/google_ai.py,sha256=MIX4nmyC6448AvyPPSE8JZ_tzSpKJTArkZSfQGGoy0M,17920
|
|
41
41
|
letta/llm_api/helpers.py,sha256=ov9WHsLSvkceIpSNJ3PUgCvufD862Bcrum-bWrUVJko,16193
|
|
42
|
-
letta/llm_api/llm_api_tools.py,sha256=
|
|
42
|
+
letta/llm_api/llm_api_tools.py,sha256=UXm1t_DPyJVhBtzBGP8wv1LPTKyfsng31X0yfIAEusI,20292
|
|
43
43
|
letta/llm_api/mistral.py,sha256=fHdfD9ug-rQIk2qn8tRKay1U6w9maF11ryhKi91FfXM,1593
|
|
44
|
-
letta/llm_api/openai.py,sha256=
|
|
44
|
+
letta/llm_api/openai.py,sha256=gE2RTYsyATYjicgE4VwATUAwTD38B74ZVqy8oVemzdQ,20277
|
|
45
45
|
letta/local_llm/README.md,sha256=hFJyw5B0TU2jrh9nb0zGZMgdH-Ei1dSRfhvPQG_NSoU,168
|
|
46
46
|
letta/local_llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
letta/local_llm/chat_completion_proxy.py,sha256=ElYR0M5SY2zL4NQzInye21MxqtiP3AUXX9Ia0KbkD4Y,12948
|
|
@@ -87,12 +87,12 @@ letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
87
87
|
letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
|
|
88
88
|
letta/orm/__all__.py,sha256=2gh2MZTkA3Hw67VWVKK3JIStJOqTeLdpCvYSVYNeEDA,692
|
|
89
89
|
letta/orm/__init__.py,sha256=wPokP-EvOri2LhKLjmYVtI_FWchaxgFvJeF_NAfqJIo,842
|
|
90
|
-
letta/orm/agent.py,sha256=
|
|
90
|
+
letta/orm/agent.py,sha256=ucVnPMP7S9YuWcMPxG_CWtT5QH8P2TWSU-Cp1HLuXIU,7385
|
|
91
91
|
letta/orm/agents_tags.py,sha256=dYSnHz4IWBjyOiQ4RJomX3P0QN76JTlEZEw5eJM6Emg,925
|
|
92
92
|
letta/orm/base.py,sha256=VjvxF9TwKF9Trf8BJkDgf7D6KrWWopOkUiF18J3IElk,3071
|
|
93
93
|
letta/orm/block.py,sha256=EjH8lXexHtFIHJ8G-RjNo2oAH834x0Hbn4CER9S4U74,3880
|
|
94
94
|
letta/orm/blocks_agents.py,sha256=W0dykl9OchAofSuAYZD5zNmhyMabPr9LTJrz-I3A0m4,983
|
|
95
|
-
letta/orm/custom_columns.py,sha256=
|
|
95
|
+
letta/orm/custom_columns.py,sha256=APR3ylle9hUutQoy8m-toTV53F1mpcQhEcnf32XTmQA,5447
|
|
96
96
|
letta/orm/enums.py,sha256=HzX3eXhBH-PnpxhBWtWbkV4J6wrStlJaX_OVdZgAdLU,428
|
|
97
97
|
letta/orm/errors.py,sha256=Se0Guz-gqi-D36NUWSh7AP9zTVCSph9KgZh_trwng4o,734
|
|
98
98
|
letta/orm/file.py,sha256=7_p7LxityP3NQZVURQYG0kgcZhEkVuMN0Fj4h9YOe1w,1780
|
|
@@ -139,7 +139,7 @@ letta/prompts/system/memgpt_modified_o1.txt,sha256=objnDgnxpF3-MmU28ZqZ7-TOG8UlH
|
|
|
139
139
|
letta/prompts/system/memgpt_offline_memory.txt,sha256=rWEJeF-6aiinjkJM9hgLUYCmlEcC_HekYB1bjEUYq6M,2460
|
|
140
140
|
letta/prompts/system/memgpt_offline_memory_chat.txt,sha256=ituh7gDuio7nC2UKFB7GpBq6crxb8bYedQfJ0ADoPgg,3949
|
|
141
141
|
letta/pytest.ini,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
|
-
letta/schemas/agent.py,sha256=
|
|
142
|
+
letta/schemas/agent.py,sha256=M09-BY37tqxRkQ8sh16oq7Ay_TOJWoP63Cm6aJxp-SY,12091
|
|
143
143
|
letta/schemas/block.py,sha256=FYYmRWjH4d4QHMUx_nmIXjv_qJna_l-Ip_4i51wDBPA,5942
|
|
144
144
|
letta/schemas/embedding_config.py,sha256=RkLbUorFkMWr1tPkn6c2aHrnICjWTbhPY86tIncwXyA,3373
|
|
145
145
|
letta/schemas/embedding_config_overrides.py,sha256=lkTa4y-EQ2RnaEKtKDM0sEAk7EwNa67REw8DGNNtGQY,84
|
|
@@ -165,18 +165,18 @@ letta/schemas/organization.py,sha256=WWbUWVSp_VQRFwWN4fdHg1yObiV6x9rZnvIY8x5BPs0
|
|
|
165
165
|
letta/schemas/passage.py,sha256=pdCLZgOn0gWK1gB6aFHLS0gfdWCBqLaiHDA0iQ12Zd8,3704
|
|
166
166
|
letta/schemas/providers.py,sha256=Wd0d0jgv6z3X5t7cT1ZVoX_Qa85ecsm1gQzkOPgQFUo,34890
|
|
167
167
|
letta/schemas/run.py,sha256=SRqPRziINIiPunjOhE_NlbnQYgxTvqmbauni_yfBQRA,2085
|
|
168
|
-
letta/schemas/sandbox_config.py,sha256=
|
|
168
|
+
letta/schemas/sandbox_config.py,sha256=Nz8K5brqe6jpf66KnTJ0-E7ZeFdPoBFGN-XOI35OeaY,5926
|
|
169
169
|
letta/schemas/source.py,sha256=-BQVolcXA2ziCu2ztR6cbTdGUc8G7vGJy7rvpdf1hpg,2880
|
|
170
170
|
letta/schemas/step.py,sha256=cCmDChQMndy7aMJGH0Z19VbzJkAeFTYuA0cJpzjW2g0,1928
|
|
171
171
|
letta/schemas/tool.py,sha256=uv3WxTt9SzaoXzwTLNHT2wegWTcaBFQBnBvNJrxeYvs,11022
|
|
172
|
-
letta/schemas/tool_rule.py,sha256=
|
|
172
|
+
letta/schemas/tool_rule.py,sha256=tS7ily6NJD8E4n7Hla38jMUe6OIdhdc1ckq0AiRpu5Y,1893
|
|
173
173
|
letta/schemas/usage.py,sha256=8oYRH-JX0PfjIu2zkT5Uu3UWQ7Unnz_uHiO8hRGI4m0,912
|
|
174
174
|
letta/schemas/user.py,sha256=V32Tgl6oqB3KznkxUz12y7agkQicjzW7VocSpj78i6Q,1526
|
|
175
175
|
letta/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
176
176
|
letta/server/constants.py,sha256=yAdGbLkzlOU_dLTx0lKDmAnj0ZgRXCEaIcPJWO69eaE,92
|
|
177
177
|
letta/server/generate_openapi_schema.sh,sha256=0OtBhkC1g6CobVmNEd_m2B6sTdppjbJLXaM95icejvE,371
|
|
178
178
|
letta/server/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
179
|
-
letta/server/rest_api/app.py,sha256=
|
|
179
|
+
letta/server/rest_api/app.py,sha256=9cf9H6vZhN-iBJqkqjBdFWjA3PlKfok-q48ltI71qls,11805
|
|
180
180
|
letta/server/rest_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
181
181
|
letta/server/rest_api/auth/index.py,sha256=fQBGyVylGSRfEMLQ17cZzrHd5Y1xiVylvPqH5Rl-lXQ,1378
|
|
182
182
|
letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfNHkzxDo,774
|
|
@@ -186,7 +186,7 @@ letta/server/rest_api/optimistic_json_parser.py,sha256=1z4d9unmxMb0ou7owJ62uUQoN
|
|
|
186
186
|
letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
187
187
|
letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
188
188
|
letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=TjeiyEMkpWR3PBazUWs4W2XVwGVEW5NRTOFXWag62oU,6657
|
|
189
|
-
letta/server/rest_api/routers/v1/__init__.py,sha256=
|
|
189
|
+
letta/server/rest_api/routers/v1/__init__.py,sha256=tzD8Oh6ynPkg8ULcITWcwalLL81SIh6eztPqV9l7VGk,1162
|
|
190
190
|
letta/server/rest_api/routers/v1/agents.py,sha256=-eRRQDyWyBnP6aj6z9rJTiaidqPls1TQB1xSqGdIYGA,25041
|
|
191
191
|
letta/server/rest_api/routers/v1/blocks.py,sha256=oJYOpGUTd4AhKwVolVlZPIXO2EoOrBHkyi2PdrmbtmA,3888
|
|
192
192
|
letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
|
|
@@ -195,14 +195,15 @@ letta/server/rest_api/routers/v1/llms.py,sha256=lYp5URXtZk1yu_Pe-p1Wq1uQ0qeb6aWt
|
|
|
195
195
|
letta/server/rest_api/routers/v1/organizations.py,sha256=8n-kA9LHtKImdY2xL-v7m6nYAbFWqH1vjBCJhQbv7Is,2077
|
|
196
196
|
letta/server/rest_api/routers/v1/providers.py,sha256=EOwSy4KsU63RY_NjzjjR4K6uaAmewXYTgbNOL4aO-X8,2444
|
|
197
197
|
letta/server/rest_api/routers/v1/runs.py,sha256=4w06j5CYfRzVf5Jf9Fzh7zQyVxC1R6q9gpglERYKeHU,6062
|
|
198
|
-
letta/server/rest_api/routers/v1/sandbox_configs.py,sha256=
|
|
198
|
+
letta/server/rest_api/routers/v1/sandbox_configs.py,sha256=_6WcwgKrgRfnTEC_EgoN-gt8yTkLPgMdM56g1SwX_eo,8502
|
|
199
199
|
letta/server/rest_api/routers/v1/sources.py,sha256=g7NKgbZkS7y1vlukJHZ_yoWrk3AxyoWKTVGszp0Ky18,8414
|
|
200
|
+
letta/server/rest_api/routers/v1/steps.py,sha256=SuZmneaeSSAHjOMatMt_QILLzkNpkbwuVgKiMYr52cE,3038
|
|
200
201
|
letta/server/rest_api/routers/v1/tags.py,sha256=45G0cmcP-ER0OO5OanT_fGtGQfl9ZjRKU97mFwtwyfo,878
|
|
201
|
-
letta/server/rest_api/routers/v1/tools.py,sha256=
|
|
202
|
+
letta/server/rest_api/routers/v1/tools.py,sha256=BS4HsHZLv2n4MauLSrVNlch2TuijzlyEfqKc3fP2b94,12633
|
|
202
203
|
letta/server/rest_api/routers/v1/users.py,sha256=G5DBHSkPfBgVHN2Wkm-rVYiLQAudwQczIq2Z3YLdbVo,2277
|
|
203
204
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
204
205
|
letta/server/rest_api/utils.py,sha256=dsjkZzgo9Rk3fjUf1ajjiiql1eeO5DAzmXprttI7bJU,3993
|
|
205
|
-
letta/server/server.py,sha256=
|
|
206
|
+
letta/server/server.py,sha256=GensF4fGmRP5-3hGxm6Zr6rQea2XyX-T2dIK277Shjk,59827
|
|
206
207
|
letta/server/startup.sh,sha256=722uKJWB2C4q3vjn39De2zzPacaZNw_1fN1SpLGjKIo,1569
|
|
207
208
|
letta/server/static_files/assets/index-048c9598.js,sha256=mR16XppvselwKCcNgONs4L7kZEVa4OEERm4lNZYtLSk,146819
|
|
208
209
|
letta/server/static_files/assets/index-0e31b727.css,sha256=SBbja96uiQVLDhDOroHgM6NSl7tS4lpJRCREgSS_hA8,7672
|
|
@@ -219,6 +220,7 @@ letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
219
220
|
letta/services/agent_manager.py,sha256=I6F-nED9Q6O_FY2-O1gHDY-OaQIxbt3FsL88DENVTfE,50287
|
|
220
221
|
letta/services/block_manager.py,sha256=u56TXG46QDMbQZadDGCO7fY1vreJ69Xr_0MUF53xw4k,5519
|
|
221
222
|
letta/services/helpers/agent_manager_helper.py,sha256=RH0MXLZASkP2LVbVNUfSYHrcBYZnVxFd9ejGjRK90Hw,11283
|
|
223
|
+
letta/services/helpers/tool_execution_helper.py,sha256=q8uSiQcX6VH_iNg5VNloZgC2JkH9lIOXBKCXYPx2Yac,6097
|
|
222
224
|
letta/services/job_manager.py,sha256=7awEHraoEmqlDQvcQTEkb_dcZsG9eIYGWYct8exzm2E,13117
|
|
223
225
|
letta/services/message_manager.py,sha256=w6-B9Zz5z9UXcd6mKhinsaCINTsmxDsH9JJsV2_qlH4,8878
|
|
224
226
|
letta/services/organization_manager.py,sha256=h3hrknBhA3YQt90OeBzFnqjYM9NWKnk8jDKzXGm4AUg,3392
|
|
@@ -227,17 +229,17 @@ letta/services/per_agent_lock_manager.py,sha256=porM0cKKANQ1FvcGXOO_qM7ARk5Fgi1H
|
|
|
227
229
|
letta/services/provider_manager.py,sha256=jEal0A0XWobWH5CVfmzPtcFhsflI-sanqyg26FqpDKk,3575
|
|
228
230
|
letta/services/sandbox_config_manager.py,sha256=eWDNTscRG9Gt_Ixho3-daOOno_9KcebxeA9v_CbzYu0,13371
|
|
229
231
|
letta/services/source_manager.py,sha256=0JLKIv405oS5wc6bY5k2bxxZpS9O-VwUGHVdGPbJ3e4,7676
|
|
230
|
-
letta/services/step_manager.py,sha256=
|
|
231
|
-
letta/services/tool_execution_sandbox.py,sha256=
|
|
232
|
+
letta/services/step_manager.py,sha256=Zpbz6o-KgvGePiQkgd6lHagtpYo3H906vjDE0_hpZVo,4871
|
|
233
|
+
letta/services/tool_execution_sandbox.py,sha256=4XBYkCEBLG6GqijxgqeLIQQJ9zRbsJa8vZ4dZG04Pq8,22080
|
|
232
234
|
letta/services/tool_manager.py,sha256=0zc7bFxGvl_wjs7CC4cyeUWFZAUQK_yVKsUjhrW3Vao,9181
|
|
233
235
|
letta/services/user_manager.py,sha256=1U8BQ_-MBkEW2wnSFV_OsTwBmRAZLN8uHLFjnDjK3hA,4308
|
|
234
|
-
letta/settings.py,sha256=
|
|
236
|
+
letta/settings.py,sha256=r9KUE1YU2VfLPsdjTRvv5OfyEhV6CqXwqkeeT6NMznI,6090
|
|
235
237
|
letta/streaming_interface.py,sha256=lo2VAQRUJOdWTijwnXuKOC9uejqr2siUAEmZiQUXkj8,15710
|
|
236
238
|
letta/streaming_utils.py,sha256=jLqFTVhUL76FeOuYk8TaRQHmPTf3HSRc2EoJwxJNK6U,11946
|
|
237
|
-
letta/system.py,sha256=
|
|
239
|
+
letta/system.py,sha256=S_0cod77iEttkFd1bSh2wenLCKA8YL487AuVenIDUng,8425
|
|
238
240
|
letta/utils.py,sha256=lgBDWKmrQrmJGPxcgamFC2aJyi6I0dX7bzLBt3YC6j0,34051
|
|
239
|
-
letta_nightly-0.6.
|
|
240
|
-
letta_nightly-0.6.
|
|
241
|
-
letta_nightly-0.6.
|
|
242
|
-
letta_nightly-0.6.
|
|
243
|
-
letta_nightly-0.6.
|
|
241
|
+
letta_nightly-0.6.20.dev20250131222205.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
242
|
+
letta_nightly-0.6.20.dev20250131222205.dist-info/METADATA,sha256=8ZADA-ivhmqJvEe_nRcwlYJR9OADcnZkkZQkjJmuXic,22156
|
|
243
|
+
letta_nightly-0.6.20.dev20250131222205.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
244
|
+
letta_nightly-0.6.20.dev20250131222205.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
245
|
+
letta_nightly-0.6.20.dev20250131222205.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|