letta-nightly 0.1.7.dev20241001104147__py3-none-any.whl → 0.1.7.dev20241002104051__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/agent.py +18 -55
- letta/client/client.py +1 -1
- letta/functions/functions.py +1 -24
- letta/functions/helpers.py +27 -11
- letta/schemas/tool.py +3 -3
- letta/server/static_files/assets/{index-0cbf7ad5.js → index-4d08d8a3.js} +75 -75
- letta/server/static_files/index.html +1 -1
- {letta_nightly-0.1.7.dev20241001104147.dist-info → letta_nightly-0.1.7.dev20241002104051.dist-info}/METADATA +12 -10
- {letta_nightly-0.1.7.dev20241001104147.dist-info → letta_nightly-0.1.7.dev20241002104051.dist-info}/RECORD +12 -13
- letta/server/static_files/assets/index-486e3228.js +0 -274
- {letta_nightly-0.1.7.dev20241001104147.dist-info → letta_nightly-0.1.7.dev20241002104051.dist-info}/LICENSE +0 -0
- {letta_nightly-0.1.7.dev20241001104147.dist-info → letta_nightly-0.1.7.dev20241002104051.dist-info}/WHEEL +0 -0
- {letta_nightly-0.1.7.dev20241001104147.dist-info → letta_nightly-0.1.7.dev20241002104051.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -237,10 +237,8 @@ class Agent(BaseAgent):
|
|
|
237
237
|
self.agent_state = agent_state
|
|
238
238
|
assert isinstance(self.agent_state.memory, Memory), f"Memory object is not of type Memory: {type(self.agent_state.memory)}"
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
except Exception as e:
|
|
243
|
-
raise ValueError(f"Encountered an error while trying to link agent tools during initialization:\n{str(e)}")
|
|
240
|
+
# link tools
|
|
241
|
+
self.link_tools(tools)
|
|
244
242
|
|
|
245
243
|
# gpt-4, gpt-3.5-turbo, ...
|
|
246
244
|
self.model = self.agent_state.llm_config.model
|
|
@@ -345,16 +343,18 @@ class Agent(BaseAgent):
|
|
|
345
343
|
env = {}
|
|
346
344
|
env.update(globals())
|
|
347
345
|
for tool in tools:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
346
|
+
try:
|
|
347
|
+
# WARNING: name may not be consistent?
|
|
348
|
+
if tool.module: # execute the whole module
|
|
349
|
+
exec(tool.module, env)
|
|
350
|
+
else:
|
|
351
|
+
exec(tool.source_code, env)
|
|
354
352
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
self.functions_python[tool.json_schema["name"]] = env[tool.json_schema["name"]]
|
|
354
|
+
self.functions.append(tool.json_schema)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
warnings.warn(f"WARNING: tool {tool.name} failed to link")
|
|
357
|
+
print(e)
|
|
358
358
|
assert all([callable(f) for k, f in self.functions_python.items()]), self.functions_python
|
|
359
359
|
|
|
360
360
|
def _load_messages_from_recall(self, message_ids: List[str]) -> List[Message]:
|
|
@@ -551,18 +551,19 @@ class Agent(BaseAgent):
|
|
|
551
551
|
) # extend conversation with assistant's reply
|
|
552
552
|
printd(f"Function call message: {messages[-1]}")
|
|
553
553
|
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
if response_message.content:
|
|
555
|
+
# The content if then internal monologue, not chat
|
|
556
|
+
self.interface.internal_monologue(response_message.content, msg_obj=messages[-1])
|
|
556
557
|
|
|
557
558
|
# Step 3: call the function
|
|
558
559
|
# Note: the JSON response may not always be valid; be sure to handle errors
|
|
559
|
-
|
|
560
|
-
# Failure case 1: function name is wrong
|
|
561
560
|
function_call = (
|
|
562
561
|
response_message.function_call if response_message.function_call is not None else response_message.tool_calls[0].function
|
|
563
562
|
)
|
|
564
563
|
function_name = function_call.name
|
|
565
564
|
printd(f"Request to call function {function_name} with tool_call_id: {tool_call_id}")
|
|
565
|
+
|
|
566
|
+
# Failure case 1: function name is wrong
|
|
566
567
|
try:
|
|
567
568
|
function_to_call = self.functions_python[function_name]
|
|
568
569
|
except KeyError:
|
|
@@ -1117,48 +1118,10 @@ class Agent(BaseAgent):
|
|
|
1117
1118
|
def add_function(self, function_name: str) -> str:
|
|
1118
1119
|
# TODO: refactor
|
|
1119
1120
|
raise NotImplementedError
|
|
1120
|
-
# if function_name in self.functions_python.keys():
|
|
1121
|
-
# msg = f"Function {function_name} already loaded"
|
|
1122
|
-
# printd(msg)
|
|
1123
|
-
# return msg
|
|
1124
|
-
|
|
1125
|
-
# available_functions = load_all_function_sets()
|
|
1126
|
-
# if function_name not in available_functions.keys():
|
|
1127
|
-
# raise ValueError(f"Function {function_name} not found in function library")
|
|
1128
|
-
|
|
1129
|
-
# self.functions.append(available_functions[function_name]["json_schema"])
|
|
1130
|
-
# self.functions_python[function_name] = available_functions[function_name]["python_function"]
|
|
1131
|
-
|
|
1132
|
-
# msg = f"Added function {function_name}"
|
|
1133
|
-
## self.save()
|
|
1134
|
-
# self.update_state()
|
|
1135
|
-
# printd(msg)
|
|
1136
|
-
# return msg
|
|
1137
1121
|
|
|
1138
1122
|
def remove_function(self, function_name: str) -> str:
|
|
1139
1123
|
# TODO: refactor
|
|
1140
1124
|
raise NotImplementedError
|
|
1141
|
-
# if function_name not in self.functions_python.keys():
|
|
1142
|
-
# msg = f"Function {function_name} not loaded, ignoring"
|
|
1143
|
-
# printd(msg)
|
|
1144
|
-
# return msg
|
|
1145
|
-
|
|
1146
|
-
## only allow removal of user defined functions
|
|
1147
|
-
# user_func_path = Path(USER_FUNCTIONS_DIR)
|
|
1148
|
-
# func_path = Path(inspect.getfile(self.functions_python[function_name]))
|
|
1149
|
-
# is_subpath = func_path.resolve().parts[: len(user_func_path.resolve().parts)] == user_func_path.resolve().parts
|
|
1150
|
-
|
|
1151
|
-
# if not is_subpath:
|
|
1152
|
-
# raise ValueError(f"Function {function_name} is not user defined and cannot be removed")
|
|
1153
|
-
|
|
1154
|
-
# self.functions = [f_schema for f_schema in self.functions if f_schema["name"] != function_name]
|
|
1155
|
-
# self.functions_python.pop(function_name)
|
|
1156
|
-
|
|
1157
|
-
# msg = f"Removed function {function_name}"
|
|
1158
|
-
## self.save()
|
|
1159
|
-
# self.update_state()
|
|
1160
|
-
# printd(msg)
|
|
1161
|
-
# return msg
|
|
1162
1125
|
|
|
1163
1126
|
def update_state(self) -> AgentState:
|
|
1164
1127
|
message_ids = [msg.id for msg in self._messages]
|
letta/client/client.py
CHANGED
letta/functions/functions.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import inspect
|
|
3
3
|
import os
|
|
4
|
-
import sys
|
|
5
4
|
from textwrap import dedent # remove indentation
|
|
6
5
|
from types import ModuleType
|
|
7
6
|
|
|
8
|
-
from letta.constants import CLI_WARNING_PREFIX
|
|
7
|
+
from letta.constants import CLI_WARNING_PREFIX
|
|
9
8
|
from letta.functions.schema_generator import generate_schema
|
|
10
9
|
|
|
11
|
-
USER_FUNCTIONS_DIR = os.path.join(LETTA_DIR, "functions")
|
|
12
|
-
|
|
13
|
-
sys.path.append(USER_FUNCTIONS_DIR)
|
|
14
|
-
|
|
15
10
|
|
|
16
11
|
def parse_source_code(func) -> str:
|
|
17
12
|
"""Parse the source code of a function and remove indendation"""
|
|
@@ -68,24 +63,6 @@ def validate_function(module_name, module_full_path):
|
|
|
68
63
|
return True, None
|
|
69
64
|
|
|
70
65
|
|
|
71
|
-
def write_function(module_name: str, function_name: str, function_code: str):
|
|
72
|
-
"""Write a function to a file in the user functions directory"""
|
|
73
|
-
# Create the user functions directory if it doesn't exist
|
|
74
|
-
if not os.path.exists(USER_FUNCTIONS_DIR):
|
|
75
|
-
os.makedirs(USER_FUNCTIONS_DIR)
|
|
76
|
-
|
|
77
|
-
# Write the function to a file
|
|
78
|
-
file_path = os.path.join(USER_FUNCTIONS_DIR, f"{module_name}.py")
|
|
79
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
80
|
-
f.write(function_code)
|
|
81
|
-
succ, error = validate_function(module_name, file_path)
|
|
82
|
-
|
|
83
|
-
# raise error if function cannot be loaded
|
|
84
|
-
if not succ:
|
|
85
|
-
raise ValueError(error)
|
|
86
|
-
return file_path
|
|
87
|
-
|
|
88
|
-
|
|
89
66
|
def load_function_file(filepath: str) -> dict:
|
|
90
67
|
file = os.path.basename(filepath)
|
|
91
68
|
module_name = file[:-3] # Remove '.py' from filename
|
letta/functions/helpers.py
CHANGED
|
@@ -3,21 +3,15 @@ from typing import Any, Optional, Union
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def generate_langchain_tool_wrapper(
|
|
6
|
+
def generate_langchain_tool_wrapper(
|
|
7
|
+
tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None
|
|
8
|
+
) -> tuple[str, str]:
|
|
7
9
|
tool_name = tool.__class__.__name__
|
|
8
10
|
import_statement = f"from langchain_community.tools import {tool_name}"
|
|
9
11
|
extra_module_imports = generate_import_code(additional_imports_module_attr_map)
|
|
10
12
|
|
|
11
13
|
# Safety check that user has passed in all required imports:
|
|
12
|
-
|
|
13
|
-
if additional_imports_module_attr_map:
|
|
14
|
-
current_class_imports.update(set(additional_imports_module_attr_map.values()))
|
|
15
|
-
required_class_imports = set(find_required_class_names_for_import(tool))
|
|
16
|
-
|
|
17
|
-
if not current_class_imports.issuperset(required_class_imports):
|
|
18
|
-
err_msg = f"[ERROR] You are missing module_attr pairs in `additional_imports_module_attr_map`. Currently, you have imports for {current_class_imports}, but the required classes for import are {required_class_imports}"
|
|
19
|
-
print(err_msg)
|
|
20
|
-
raise RuntimeError(err_msg)
|
|
14
|
+
assert_all_classes_are_imported(tool, additional_imports_module_attr_map)
|
|
21
15
|
|
|
22
16
|
tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
|
|
23
17
|
run_call = f"return tool._run(**kwargs)"
|
|
@@ -37,9 +31,14 @@ def {func_name}(**kwargs):
|
|
|
37
31
|
return func_name, wrapper_function_str
|
|
38
32
|
|
|
39
33
|
|
|
40
|
-
def generate_crewai_tool_wrapper(tool: "CrewAIBaseTool") -> tuple[str, str]:
|
|
34
|
+
def generate_crewai_tool_wrapper(tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> tuple[str, str]:
|
|
41
35
|
tool_name = tool.__class__.__name__
|
|
42
36
|
import_statement = f"from crewai_tools import {tool_name}"
|
|
37
|
+
extra_module_imports = generate_import_code(additional_imports_module_attr_map)
|
|
38
|
+
|
|
39
|
+
# Safety check that user has passed in all required imports:
|
|
40
|
+
assert_all_classes_are_imported(tool, additional_imports_module_attr_map)
|
|
41
|
+
|
|
43
42
|
tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
|
|
44
43
|
run_call = f"return tool._run(**kwargs)"
|
|
45
44
|
func_name = f"run_{tool_name.lower()}"
|
|
@@ -50,12 +49,29 @@ def {func_name}(**kwargs):
|
|
|
50
49
|
if 'self' in kwargs:
|
|
51
50
|
del kwargs['self']
|
|
52
51
|
{import_statement}
|
|
52
|
+
{extra_module_imports}
|
|
53
53
|
{tool_instantiation}
|
|
54
54
|
{run_call}
|
|
55
55
|
"""
|
|
56
56
|
return func_name, wrapper_function_str
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def assert_all_classes_are_imported(
|
|
60
|
+
tool: Union["LangChainBaseTool", "CrewAIBaseTool"], additional_imports_module_attr_map: dict[str, str]
|
|
61
|
+
) -> None:
|
|
62
|
+
# Safety check that user has passed in all required imports:
|
|
63
|
+
tool_name = tool.__class__.__name__
|
|
64
|
+
current_class_imports = {tool_name}
|
|
65
|
+
if additional_imports_module_attr_map:
|
|
66
|
+
current_class_imports.update(set(additional_imports_module_attr_map.values()))
|
|
67
|
+
required_class_imports = set(find_required_class_names_for_import(tool))
|
|
68
|
+
|
|
69
|
+
if not current_class_imports.issuperset(required_class_imports):
|
|
70
|
+
err_msg = f"[ERROR] You are missing module_attr pairs in `additional_imports_module_attr_map`. Currently, you have imports for {current_class_imports}, but the required classes for import are {required_class_imports}"
|
|
71
|
+
print(err_msg)
|
|
72
|
+
raise RuntimeError(err_msg)
|
|
73
|
+
|
|
74
|
+
|
|
59
75
|
def find_required_class_names_for_import(obj: Union["LangChainBaseTool", "CrewAIBaseTool", BaseModel]) -> list[str]:
|
|
60
76
|
"""
|
|
61
77
|
Finds all the class names for required imports when instantiating the `obj`.
|
letta/schemas/tool.py
CHANGED
|
@@ -93,7 +93,7 @@ class Tool(BaseTool):
|
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
@classmethod
|
|
96
|
-
def from_crewai(cls, crewai_tool: "CrewAIBaseTool") -> "Tool":
|
|
96
|
+
def from_crewai(cls, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> "Tool":
|
|
97
97
|
"""
|
|
98
98
|
Class method to create an instance of Tool from a crewAI BaseTool object.
|
|
99
99
|
|
|
@@ -106,7 +106,7 @@ class Tool(BaseTool):
|
|
|
106
106
|
description = crewai_tool.description
|
|
107
107
|
source_type = "python"
|
|
108
108
|
tags = ["crew-ai"]
|
|
109
|
-
wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool)
|
|
109
|
+
wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool, additional_imports_module_attr_map)
|
|
110
110
|
json_schema = generate_schema_from_args_schema(crewai_tool.args_schema, name=wrapper_func_name, description=description)
|
|
111
111
|
|
|
112
112
|
# append heartbeat (necessary for triggering another reasoning step after this tool call)
|
|
@@ -128,7 +128,7 @@ class Tool(BaseTool):
|
|
|
128
128
|
|
|
129
129
|
class ToolCreate(BaseTool):
|
|
130
130
|
name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
|
|
131
|
-
tags: List[str] = Field(
|
|
131
|
+
tags: List[str] = Field([], description="Metadata tags.")
|
|
132
132
|
source_code: str = Field(..., description="The source code of the function.")
|
|
133
133
|
json_schema: Optional[Dict] = Field(
|
|
134
134
|
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
|