letta-nightly 0.13.0.dev20251031104146__py3-none-any.whl → 0.13.1.dev20251101010313__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/adapters/simple_llm_stream_adapter.py +1 -0
- letta/agents/letta_agent_v2.py +8 -0
- letta/agents/letta_agent_v3.py +127 -27
- letta/agents/temporal/activities/__init__.py +25 -0
- letta/agents/temporal/activities/create_messages.py +26 -0
- letta/agents/temporal/activities/create_step.py +57 -0
- letta/agents/temporal/activities/example_activity.py +9 -0
- letta/agents/temporal/activities/execute_tool.py +130 -0
- letta/agents/temporal/activities/llm_request.py +114 -0
- letta/agents/temporal/activities/prepare_messages.py +27 -0
- letta/agents/temporal/activities/refresh_context.py +160 -0
- letta/agents/temporal/activities/summarize_conversation_history.py +77 -0
- letta/agents/temporal/activities/update_message_ids.py +25 -0
- letta/agents/temporal/activities/update_run.py +43 -0
- letta/agents/temporal/constants.py +59 -0
- letta/agents/temporal/temporal_agent_workflow.py +704 -0
- letta/agents/temporal/types.py +275 -0
- letta/constants.py +11 -0
- letta/errors.py +4 -0
- letta/functions/function_sets/base.py +0 -11
- letta/groups/helpers.py +7 -1
- letta/groups/sleeptime_multi_agent_v4.py +4 -3
- letta/interfaces/anthropic_streaming_interface.py +0 -1
- letta/interfaces/openai_streaming_interface.py +103 -100
- letta/llm_api/anthropic_client.py +57 -12
- letta/llm_api/bedrock_client.py +1 -0
- letta/llm_api/deepseek_client.py +3 -2
- letta/llm_api/google_vertex_client.py +5 -4
- letta/llm_api/groq_client.py +1 -0
- letta/llm_api/llm_client_base.py +15 -1
- letta/llm_api/openai.py +2 -2
- letta/llm_api/openai_client.py +17 -3
- letta/llm_api/xai_client.py +1 -0
- letta/orm/agent.py +3 -0
- letta/orm/organization.py +4 -0
- letta/orm/sqlalchemy_base.py +7 -0
- letta/otel/tracing.py +131 -4
- letta/schemas/agent.py +108 -40
- letta/schemas/agent_file.py +10 -10
- letta/schemas/block.py +22 -3
- letta/schemas/enums.py +21 -0
- letta/schemas/environment_variables.py +3 -2
- letta/schemas/group.py +3 -3
- letta/schemas/letta_response.py +36 -4
- letta/schemas/llm_batch_job.py +3 -3
- letta/schemas/llm_config.py +123 -4
- letta/schemas/mcp.py +3 -2
- letta/schemas/mcp_server.py +3 -2
- letta/schemas/message.py +167 -49
- letta/schemas/model.py +265 -0
- letta/schemas/organization.py +2 -1
- letta/schemas/passage.py +2 -1
- letta/schemas/provider_trace.py +2 -1
- letta/schemas/providers/openrouter.py +1 -2
- letta/schemas/run_metrics.py +2 -1
- letta/schemas/sandbox_config.py +3 -1
- letta/schemas/step_metrics.py +2 -1
- letta/schemas/tool_rule.py +2 -2
- letta/schemas/user.py +2 -1
- letta/server/rest_api/app.py +5 -1
- letta/server/rest_api/routers/v1/__init__.py +4 -0
- letta/server/rest_api/routers/v1/agents.py +71 -9
- letta/server/rest_api/routers/v1/blocks.py +7 -7
- letta/server/rest_api/routers/v1/groups.py +40 -0
- letta/server/rest_api/routers/v1/identities.py +2 -2
- letta/server/rest_api/routers/v1/internal_agents.py +31 -0
- letta/server/rest_api/routers/v1/internal_blocks.py +177 -0
- letta/server/rest_api/routers/v1/internal_runs.py +25 -1
- letta/server/rest_api/routers/v1/runs.py +2 -22
- letta/server/rest_api/routers/v1/tools.py +12 -1
- letta/server/server.py +20 -4
- letta/services/agent_manager.py +4 -4
- letta/services/archive_manager.py +16 -0
- letta/services/group_manager.py +44 -0
- letta/services/helpers/run_manager_helper.py +2 -2
- letta/services/lettuce/lettuce_client.py +148 -0
- letta/services/mcp/base_client.py +9 -3
- letta/services/run_manager.py +148 -37
- letta/services/source_manager.py +91 -3
- letta/services/step_manager.py +2 -3
- letta/services/streaming_service.py +52 -13
- letta/services/summarizer/summarizer.py +28 -2
- letta/services/tool_executor/builtin_tool_executor.py +1 -1
- letta/services/tool_executor/core_tool_executor.py +2 -117
- letta/services/tool_sandbox/e2b_sandbox.py +4 -1
- letta/services/tool_schema_generator.py +2 -2
- letta/validators.py +21 -0
- {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/METADATA +1 -1
- {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/RECORD +93 -87
- letta/agent.py +0 -1758
- letta/cli/cli_load.py +0 -16
- letta/client/__init__.py +0 -0
- letta/client/streaming.py +0 -95
- letta/client/utils.py +0 -78
- letta/functions/async_composio_toolset.py +0 -109
- letta/functions/composio_helpers.py +0 -96
- letta/helpers/composio_helpers.py +0 -38
- letta/orm/job_messages.py +0 -33
- letta/schemas/providers.py +0 -1617
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -132
- letta/services/tool_executor/composio_tool_executor.py +0 -57
- {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/WHEEL +0 -0
- {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/licenses/LICENSE +0 -0
letta/cli/cli_load.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file contains functions for loading data into Letta's archival storage.
|
|
3
|
-
|
|
4
|
-
Data can be loaded with the following command, once a load function is defined:
|
|
5
|
-
```
|
|
6
|
-
letta load <data-connector-type> --name <dataset-name> [ADDITIONAL ARGS]
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import typer
|
|
12
|
-
|
|
13
|
-
app = typer.Typer()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
default_extensions = "txt,md,pdf"
|
letta/client/__init__.py
DELETED
|
File without changes
|
letta/client/streaming.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Generator, Union, get_args
|
|
3
|
-
|
|
4
|
-
import httpx
|
|
5
|
-
from httpx_sse import SSEError, connect_sse
|
|
6
|
-
from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
|
|
7
|
-
|
|
8
|
-
from letta.constants import OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING
|
|
9
|
-
from letta.errors import LLMError
|
|
10
|
-
from letta.log import get_logger
|
|
11
|
-
from letta.schemas.enums import MessageStreamStatus
|
|
12
|
-
from letta.schemas.letta_message import AssistantMessage, HiddenReasoningMessage, ReasoningMessage, ToolCallMessage, ToolReturnMessage
|
|
13
|
-
from letta.schemas.letta_response import LettaStreamingResponse
|
|
14
|
-
from letta.schemas.usage import LettaUsageStatistics
|
|
15
|
-
|
|
16
|
-
logger = get_logger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _sse_post(url: str, data: dict, headers: dict) -> Generator[Union[LettaStreamingResponse, ChatCompletionChunk], None, None]:
|
|
20
|
-
"""
|
|
21
|
-
Sends an SSE POST request and yields parsed response chunks.
|
|
22
|
-
"""
|
|
23
|
-
# TODO: Please note his is a very generous timeout for e2b reasons
|
|
24
|
-
with httpx.Client(timeout=httpx.Timeout(5 * 60.0, read=5 * 60.0)) as client:
|
|
25
|
-
with connect_sse(client, method="POST", url=url, json=data, headers=headers) as event_source:
|
|
26
|
-
# Check for immediate HTTP errors before processing the SSE stream
|
|
27
|
-
if not event_source.response.is_success:
|
|
28
|
-
response_bytes = event_source.response.read()
|
|
29
|
-
logger.warning(f"SSE request error: {vars(event_source.response)}")
|
|
30
|
-
logger.warning(response_bytes.decode("utf-8"))
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
response_dict = json.loads(response_bytes.decode("utf-8"))
|
|
34
|
-
error_message = response_dict.get("error", {}).get("message", "")
|
|
35
|
-
|
|
36
|
-
if OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING in error_message:
|
|
37
|
-
logger.error(error_message)
|
|
38
|
-
raise LLMError(error_message)
|
|
39
|
-
except LLMError:
|
|
40
|
-
raise
|
|
41
|
-
except Exception:
|
|
42
|
-
logger.error("Failed to parse SSE message, raising HTTP error")
|
|
43
|
-
event_source.response.raise_for_status()
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
for sse in event_source.iter_sse():
|
|
47
|
-
if sse.data in {status.value for status in MessageStreamStatus}:
|
|
48
|
-
yield MessageStreamStatus(sse.data)
|
|
49
|
-
if sse.data == MessageStreamStatus.done.value:
|
|
50
|
-
# We received the [DONE], so stop reading the stream.
|
|
51
|
-
break
|
|
52
|
-
else:
|
|
53
|
-
chunk_data = json.loads(sse.data)
|
|
54
|
-
|
|
55
|
-
if "reasoning" in chunk_data:
|
|
56
|
-
yield ReasoningMessage(**chunk_data)
|
|
57
|
-
elif chunk_data.get("message_type") == "assistant_message":
|
|
58
|
-
yield AssistantMessage(**chunk_data)
|
|
59
|
-
elif "hidden_reasoning" in chunk_data:
|
|
60
|
-
yield HiddenReasoningMessage(**chunk_data)
|
|
61
|
-
elif "tool_call" in chunk_data:
|
|
62
|
-
yield ToolCallMessage(**chunk_data)
|
|
63
|
-
elif "tool_return" in chunk_data:
|
|
64
|
-
yield ToolReturnMessage(**chunk_data)
|
|
65
|
-
elif "step_count" in chunk_data:
|
|
66
|
-
yield LettaUsageStatistics(**chunk_data)
|
|
67
|
-
elif chunk_data.get("object") == get_args(ChatCompletionChunk.__annotations__["object"])[0]:
|
|
68
|
-
yield ChatCompletionChunk(**chunk_data)
|
|
69
|
-
else:
|
|
70
|
-
raise ValueError(f"Unknown message type in chunk_data: {chunk_data}")
|
|
71
|
-
|
|
72
|
-
except SSEError as e:
|
|
73
|
-
logger.error(f"SSE stream error: {e}")
|
|
74
|
-
|
|
75
|
-
if "application/json" in str(e):
|
|
76
|
-
response = client.post(url=url, json=data, headers=headers)
|
|
77
|
-
|
|
78
|
-
if response.headers.get("Content-Type", "").startswith("application/json"):
|
|
79
|
-
error_details = response.json()
|
|
80
|
-
logger.error(f"POST Error: {error_details}")
|
|
81
|
-
else:
|
|
82
|
-
logger.error("Failed to retrieve JSON error message via retry.")
|
|
83
|
-
|
|
84
|
-
raise e
|
|
85
|
-
|
|
86
|
-
except Exception as e:
|
|
87
|
-
logger.error(f"Unexpected exception: {e}")
|
|
88
|
-
|
|
89
|
-
if event_source.response.request:
|
|
90
|
-
logger.error(f"HTTP Request: {vars(event_source.response.request)}")
|
|
91
|
-
if event_source.response:
|
|
92
|
-
logger.error(f"HTTP Status: {event_source.response.status_code}")
|
|
93
|
-
logger.error(f"HTTP Headers: {event_source.response.headers}")
|
|
94
|
-
|
|
95
|
-
raise e
|
letta/client/utils.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from IPython.display import HTML, display
|
|
6
|
-
from sqlalchemy.testing.plugin.plugin_base import warnings
|
|
7
|
-
|
|
8
|
-
from letta.local_llm.constants import ASSISTANT_MESSAGE_CLI_SYMBOL, INNER_THOUGHTS_CLI_SYMBOL
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def pprint(messages):
|
|
12
|
-
"""Utility function for pretty-printing the output of client.send_message in notebooks"""
|
|
13
|
-
|
|
14
|
-
css_styles = """
|
|
15
|
-
<style>
|
|
16
|
-
.terminal {
|
|
17
|
-
background-color: #002b36;
|
|
18
|
-
color: #839496;
|
|
19
|
-
font-family: 'Courier New', Courier, monospace;
|
|
20
|
-
padding: 10px;
|
|
21
|
-
border-radius: 5px;
|
|
22
|
-
}
|
|
23
|
-
.terminal strong {
|
|
24
|
-
color: #b58900;
|
|
25
|
-
}
|
|
26
|
-
.terminal .function-return {
|
|
27
|
-
color: #2aa198;
|
|
28
|
-
}
|
|
29
|
-
.terminal .internal-monologue {
|
|
30
|
-
color: #d33682;
|
|
31
|
-
}
|
|
32
|
-
.terminal .function-call {
|
|
33
|
-
color: #2aa198;
|
|
34
|
-
}
|
|
35
|
-
.terminal .assistant-message {
|
|
36
|
-
color: #859900;
|
|
37
|
-
}
|
|
38
|
-
.terminal pre {
|
|
39
|
-
color: #839496;
|
|
40
|
-
}
|
|
41
|
-
</style>
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
html_content = css_styles + "<div class='terminal'>"
|
|
45
|
-
for message in messages:
|
|
46
|
-
date_str = message["date"]
|
|
47
|
-
date_formatted = datetime.fromisoformat(date_str.replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S")
|
|
48
|
-
|
|
49
|
-
if "function_return" in message:
|
|
50
|
-
return_string = message["function_return"]
|
|
51
|
-
return_status = message["status"]
|
|
52
|
-
html_content += f"<p><strong>🛠️ [{date_formatted}] Function Return ({return_status}):</strong></p>"
|
|
53
|
-
html_content += f"<p class='function-return'>{return_string}</p>"
|
|
54
|
-
elif "internal_monologue" in message:
|
|
55
|
-
html_content += f"<p><strong>{INNER_THOUGHTS_CLI_SYMBOL} [{date_formatted}] Internal Monologue:</strong></p>"
|
|
56
|
-
html_content += f"<p class='internal-monologue'>{message['internal_monologue']}</p>"
|
|
57
|
-
elif "function_call" in message:
|
|
58
|
-
html_content += f"<p><strong>🛠️ [[{date_formatted}] Function Call:</strong></p>"
|
|
59
|
-
html_content += f"<p class='function-call'>{message['function_call']}</p>"
|
|
60
|
-
elif "assistant_message" in message:
|
|
61
|
-
html_content += f"<p><strong>{ASSISTANT_MESSAGE_CLI_SYMBOL} [{date_formatted}] Assistant Message:</strong></p>"
|
|
62
|
-
html_content += f"<p class='assistant-message'>{message['assistant_message']}</p>"
|
|
63
|
-
html_content += "<br>"
|
|
64
|
-
html_content += "</div>"
|
|
65
|
-
|
|
66
|
-
display(HTML(html_content))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def derive_function_name_regex(function_string: str) -> Optional[str]:
|
|
70
|
-
# Regular expression to match the function name
|
|
71
|
-
match = re.search(r"def\s+([a-zA-Z_]\w*)\s*\(", function_string)
|
|
72
|
-
|
|
73
|
-
if match:
|
|
74
|
-
function_name = match.group(1)
|
|
75
|
-
return function_name
|
|
76
|
-
else:
|
|
77
|
-
warnings.warn("No function name found.")
|
|
78
|
-
return None
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
import aiohttp
|
|
5
|
-
from composio import ComposioToolSet as BaseComposioToolSet
|
|
6
|
-
from composio.exceptions import (
|
|
7
|
-
ApiKeyNotProvidedError,
|
|
8
|
-
ComposioSDKError,
|
|
9
|
-
ConnectedAccountNotFoundError,
|
|
10
|
-
EnumMetadataNotFound,
|
|
11
|
-
EnumStringNotFound,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class AsyncComposioToolSet(BaseComposioToolSet, runtime="letta", description_char_limit=1024):
|
|
16
|
-
"""
|
|
17
|
-
Async version of ComposioToolSet client for interacting with Composio API
|
|
18
|
-
Used to asynchronously hit the execute action endpoint
|
|
19
|
-
|
|
20
|
-
https://docs.composio.dev/api-reference/api-reference/v3/tools/post-api-v-3-tools-execute-action
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
def __init__(self, api_key: str, entity_id: str, lock: bool = True):
|
|
24
|
-
"""
|
|
25
|
-
Initialize the AsyncComposioToolSet client
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
api_key (str): Your Composio API key
|
|
29
|
-
entity_id (str): Your Composio entity ID
|
|
30
|
-
lock (bool): Whether to use locking (default: True)
|
|
31
|
-
"""
|
|
32
|
-
super().__init__(api_key=api_key, entity_id=entity_id, lock=lock)
|
|
33
|
-
|
|
34
|
-
self.headers = {
|
|
35
|
-
"Content-Type": "application/json",
|
|
36
|
-
"X-API-Key": self._api_key,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async def execute_action(
|
|
40
|
-
self,
|
|
41
|
-
action: str,
|
|
42
|
-
params: dict[str, Any] = {},
|
|
43
|
-
) -> dict[str, Any]:
|
|
44
|
-
"""
|
|
45
|
-
Execute an action asynchronously using the Composio API
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
action (str): The name of the action to execute
|
|
49
|
-
params (dict[str, Any], optional): Parameters for the action
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
dict[str, Any]: The API response
|
|
53
|
-
|
|
54
|
-
Raises:
|
|
55
|
-
ApiKeyNotProvidedError: if the API key is not provided
|
|
56
|
-
ComposioSDKError: if a general Composio SDK error occurs
|
|
57
|
-
ConnectedAccountNotFoundError: if the connected account is not found
|
|
58
|
-
EnumMetadataNotFound: if enum metadata is not found
|
|
59
|
-
EnumStringNotFound: if enum string is not found
|
|
60
|
-
aiohttp.ClientError: if a network-related error occurs
|
|
61
|
-
ValueError: if an error with the parameters or response occurs
|
|
62
|
-
"""
|
|
63
|
-
API_VERSION = "v3"
|
|
64
|
-
endpoint = f"{self._base_url}/{API_VERSION}/tools/execute/{action}"
|
|
65
|
-
|
|
66
|
-
json_payload = {
|
|
67
|
-
"entity_id": self.entity_id,
|
|
68
|
-
"arguments": params or {},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
async with aiohttp.ClientSession() as session:
|
|
73
|
-
async with session.post(endpoint, headers=self.headers, json=json_payload) as response:
|
|
74
|
-
print(response, response.status, response.reason, response.content)
|
|
75
|
-
if response.status == 200:
|
|
76
|
-
return await response.json()
|
|
77
|
-
else:
|
|
78
|
-
error_text = await response.text()
|
|
79
|
-
try:
|
|
80
|
-
error_json = json.loads(error_text)
|
|
81
|
-
error_message = error_json.get("message", error_text)
|
|
82
|
-
error_code = error_json.get("code")
|
|
83
|
-
|
|
84
|
-
# Handle specific error codes from Composio API
|
|
85
|
-
if error_code == 10401 or "API_KEY_NOT_FOUND" in error_message:
|
|
86
|
-
raise ApiKeyNotProvidedError()
|
|
87
|
-
if (
|
|
88
|
-
"connected account not found" in error_message.lower()
|
|
89
|
-
or "no connected account found" in error_message.lower()
|
|
90
|
-
):
|
|
91
|
-
raise ConnectedAccountNotFoundError(f"Connected account not found: {error_message}")
|
|
92
|
-
if "enum metadata not found" in error_message.lower():
|
|
93
|
-
raise EnumMetadataNotFound(f"Enum metadata not found: {error_message}")
|
|
94
|
-
if "enum string not found" in error_message.lower():
|
|
95
|
-
raise EnumStringNotFound(f"Enum string not found: {error_message}")
|
|
96
|
-
except json.JSONDecodeError:
|
|
97
|
-
error_message = error_text
|
|
98
|
-
|
|
99
|
-
# If no specific error was identified, raise a general error
|
|
100
|
-
raise ValueError(f"API request failed with status {response.status}: {error_message}")
|
|
101
|
-
except aiohttp.ClientError as e:
|
|
102
|
-
# Wrap network errors in ComposioSDKError
|
|
103
|
-
raise ComposioSDKError(f"Network error when calling Composio API: {str(e)}")
|
|
104
|
-
except ValueError:
|
|
105
|
-
# Re-raise ValueError (which could be our custom error message or a JSON parsing error)
|
|
106
|
-
raise
|
|
107
|
-
except Exception as e:
|
|
108
|
-
# Catch any other exceptions and wrap them in ComposioSDKError
|
|
109
|
-
raise ComposioSDKError(f"Unexpected error when calling Composio API: {str(e)}")
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Any, Optional
|
|
3
|
-
|
|
4
|
-
from composio.constants import DEFAULT_ENTITY_ID
|
|
5
|
-
from composio.exceptions import (
|
|
6
|
-
ApiKeyNotProvidedError,
|
|
7
|
-
ComposioSDKError,
|
|
8
|
-
ConnectedAccountNotFoundError,
|
|
9
|
-
EnumMetadataNotFound,
|
|
10
|
-
EnumStringNotFound,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY
|
|
14
|
-
from letta.functions.async_composio_toolset import AsyncComposioToolSet
|
|
15
|
-
from letta.utils import run_async_task
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# TODO: This is kind of hacky, as this is used to search up the action later on composio's side
|
|
19
|
-
# TODO: So be very careful changing/removing these pair of functions
|
|
20
|
-
def _generate_func_name_from_composio_action(action_name: str) -> str:
|
|
21
|
-
"""
|
|
22
|
-
Generates the composio function name from the composio action.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
action_name: The composio action name
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
function name
|
|
29
|
-
"""
|
|
30
|
-
return action_name.lower()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def generate_composio_action_from_func_name(func_name: str) -> str:
|
|
34
|
-
"""
|
|
35
|
-
Generates the composio action from the composio function name.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
func_name: The composio function name
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
composio action name
|
|
42
|
-
"""
|
|
43
|
-
return func_name.upper()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def generate_composio_tool_wrapper(action_name: str) -> tuple[str, str]:
|
|
47
|
-
# Generate func name
|
|
48
|
-
func_name = _generate_func_name_from_composio_action(action_name)
|
|
49
|
-
|
|
50
|
-
wrapper_function_str = f"""\
|
|
51
|
-
def {func_name}(**kwargs):
|
|
52
|
-
raise RuntimeError("Something went wrong - we should never be using the persisted source code for Composio. Please reach out to Letta team")
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
# Compile safety check
|
|
56
|
-
_assert_code_gen_compilable(wrapper_function_str.strip())
|
|
57
|
-
|
|
58
|
-
return func_name, wrapper_function_str.strip()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async def execute_composio_action_async(
|
|
62
|
-
action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None
|
|
63
|
-
) -> tuple[str, str]:
|
|
64
|
-
entity_id = entity_id or os.getenv(COMPOSIO_ENTITY_ENV_VAR_KEY, DEFAULT_ENTITY_ID)
|
|
65
|
-
composio_toolset = AsyncComposioToolSet(api_key=api_key, entity_id=entity_id, lock=False)
|
|
66
|
-
try:
|
|
67
|
-
response = await composio_toolset.execute_action(action=action_name, params=args)
|
|
68
|
-
except ApiKeyNotProvidedError as e:
|
|
69
|
-
raise RuntimeError(f"API key not provided or invalid for Composio action '{action_name}': {str(e)}")
|
|
70
|
-
except ConnectedAccountNotFoundError as e:
|
|
71
|
-
raise RuntimeError(f"Connected account not found for Composio action '{action_name}': {str(e)}")
|
|
72
|
-
except EnumMetadataNotFound as e:
|
|
73
|
-
raise RuntimeError(f"Enum metadata not found for Composio action '{action_name}': {str(e)}")
|
|
74
|
-
except EnumStringNotFound as e:
|
|
75
|
-
raise RuntimeError(f"Enum string not found for Composio action '{action_name}': {str(e)}")
|
|
76
|
-
except ComposioSDKError as e:
|
|
77
|
-
raise RuntimeError(f"Composio SDK error while executing action '{action_name}': {str(e)}")
|
|
78
|
-
except Exception as e:
|
|
79
|
-
print(type(e))
|
|
80
|
-
raise RuntimeError(f"An unexpected error occurred in Composio SDK while executing action '{action_name}': {str(e)}")
|
|
81
|
-
|
|
82
|
-
if "error" in response and response["error"]:
|
|
83
|
-
raise RuntimeError(f"Error while executing action '{action_name}': {str(response['error'])}")
|
|
84
|
-
|
|
85
|
-
return response.get("data")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def execute_composio_action(action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None) -> Any:
|
|
89
|
-
return run_async_task(execute_composio_action_async(action_name, args, api_key, entity_id))
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _assert_code_gen_compilable(code_str):
|
|
93
|
-
try:
|
|
94
|
-
compile(code_str, "<string>", "exec")
|
|
95
|
-
except SyntaxError as e:
|
|
96
|
-
print(f"Syntax error in code: {e}")
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from logging import Logger
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
from letta.schemas.user import User
|
|
5
|
-
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
6
|
-
from letta.settings import tool_settings
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_composio_api_key(actor: User, logger: Optional[Logger] = None) -> Optional[str]:
|
|
10
|
-
api_keys = SandboxConfigManager().list_sandbox_env_vars_by_key(key="COMPOSIO_API_KEY", actor=actor)
|
|
11
|
-
if not api_keys:
|
|
12
|
-
if logger:
|
|
13
|
-
logger.debug("No API keys found for Composio. Defaulting to the environment variable...")
|
|
14
|
-
if tool_settings.composio_api_key:
|
|
15
|
-
return tool_settings.composio_api_key
|
|
16
|
-
else:
|
|
17
|
-
return None
|
|
18
|
-
else:
|
|
19
|
-
# TODO: Add more protections around this
|
|
20
|
-
# Ideally, not tied to a specific sandbox, but for now we just get the first one
|
|
21
|
-
# Theoretically possible for someone to have different composio api keys per sandbox
|
|
22
|
-
return api_keys[0].value
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
async def get_composio_api_key_async(actor: User, logger: Optional[Logger] = None) -> Optional[str]:
|
|
26
|
-
api_keys = await SandboxConfigManager().list_sandbox_env_vars_by_key_async(key="COMPOSIO_API_KEY", actor=actor)
|
|
27
|
-
if not api_keys:
|
|
28
|
-
if logger:
|
|
29
|
-
logger.debug("No API keys found for Composio. Defaulting to the environment variable...")
|
|
30
|
-
if tool_settings.composio_api_key:
|
|
31
|
-
return tool_settings.composio_api_key
|
|
32
|
-
else:
|
|
33
|
-
return None
|
|
34
|
-
else:
|
|
35
|
-
# TODO: Add more protections around this
|
|
36
|
-
# Ideally, not tied to a specific sandbox, but for now we just get the first one
|
|
37
|
-
# Theoretically possible for someone to have different composio api keys per sandbox
|
|
38
|
-
return api_keys[0].value
|
letta/orm/job_messages.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
|
|
3
|
-
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
4
|
-
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
|
-
|
|
6
|
-
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from letta.orm.job import Job
|
|
10
|
-
from letta.orm.message import Message
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class JobMessage(SqlalchemyBase):
|
|
14
|
-
"""Tracks messages that were created during job execution."""
|
|
15
|
-
|
|
16
|
-
__tablename__ = "job_messages"
|
|
17
|
-
__table_args__ = (UniqueConstraint("job_id", "message_id", name="unique_job_message"),)
|
|
18
|
-
|
|
19
|
-
id: Mapped[int] = mapped_column(primary_key=True, doc="Unique identifier for the job message")
|
|
20
|
-
job_id: Mapped[str] = mapped_column(
|
|
21
|
-
ForeignKey("jobs.id", ondelete="CASCADE"),
|
|
22
|
-
nullable=False, # A job message must belong to a job
|
|
23
|
-
doc="ID of the job that created the message",
|
|
24
|
-
)
|
|
25
|
-
message_id: Mapped[str] = mapped_column(
|
|
26
|
-
ForeignKey("messages.id", ondelete="CASCADE"),
|
|
27
|
-
nullable=False, # A job message must have a message
|
|
28
|
-
doc="ID of the message created by the job",
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Relationships
|
|
32
|
-
job: Mapped["Job"] = relationship("Job", back_populates="job_messages")
|
|
33
|
-
message: Mapped["Message"] = relationship("Message", back_populates="job_message")
|