letta-nightly 0.5.1.dev20241025104130__py3-none-any.whl → 0.5.1.dev20241026104101__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 +1 -6
- letta/cli/cli.py +6 -4
- letta/client/client.py +75 -92
- letta/config.py +0 -6
- letta/constants.py +0 -9
- letta/functions/functions.py +24 -0
- letta/functions/helpers.py +4 -3
- letta/functions/schema_generator.py +10 -2
- letta/metadata.py +2 -99
- letta/o1_agent.py +2 -2
- letta/orm/__all__.py +15 -0
- letta/orm/mixins.py +16 -1
- letta/orm/organization.py +2 -0
- letta/orm/sqlalchemy_base.py +17 -18
- letta/orm/tool.py +54 -0
- letta/orm/user.py +7 -1
- letta/schemas/block.py +6 -9
- letta/schemas/memory.py +27 -29
- letta/schemas/tool.py +62 -61
- letta/schemas/user.py +2 -2
- letta/server/rest_api/admin/users.py +1 -1
- letta/server/rest_api/routers/v1/tools.py +19 -23
- letta/server/server.py +42 -327
- letta/services/organization_manager.py +19 -12
- letta/services/tool_manager.py +193 -0
- letta/services/user_manager.py +16 -11
- {letta_nightly-0.5.1.dev20241025104130.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.1.dev20241025104130.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/RECORD +31 -29
- {letta_nightly-0.5.1.dev20241025104130.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241025104130.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.1.dev20241025104130.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -240,7 +240,6 @@ class Agent(BaseAgent):
|
|
|
240
240
|
assert isinstance(self.agent_state.memory, Memory), f"Memory object is not of type Memory: {type(self.agent_state.memory)}"
|
|
241
241
|
|
|
242
242
|
# link tools
|
|
243
|
-
self.tools = tools
|
|
244
243
|
self.link_tools(tools)
|
|
245
244
|
|
|
246
245
|
# gpt-4, gpt-3.5-turbo, ...
|
|
@@ -1154,7 +1153,7 @@ class Agent(BaseAgent):
|
|
|
1154
1153
|
printd(f"skipping block update, unexpected value: {block_id=}")
|
|
1155
1154
|
continue
|
|
1156
1155
|
# TODO: we may want to update which columns we're updating from shared memory e.g. the limit
|
|
1157
|
-
self.memory.update_block_value(
|
|
1156
|
+
self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
|
|
1158
1157
|
|
|
1159
1158
|
# If the memory didn't update, we probably don't want to update the timestamp inside
|
|
1160
1159
|
# For example, if we're doing a system prompt swap, this should probably be False
|
|
@@ -1521,10 +1520,6 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1521
1520
|
else:
|
|
1522
1521
|
ms.create_agent(agent_state)
|
|
1523
1522
|
|
|
1524
|
-
for tool in agent.tools:
|
|
1525
|
-
if ms.get_tool(tool_name=tool.name, user_id=tool.user_id) is None:
|
|
1526
|
-
ms.create_tool(tool)
|
|
1527
|
-
|
|
1528
1523
|
agent.agent_state = ms.get_agent(agent_id=agent_id)
|
|
1529
1524
|
assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
|
|
1530
1525
|
|
letta/cli/cli.py
CHANGED
|
@@ -133,6 +133,7 @@ def run(
|
|
|
133
133
|
# read user id from config
|
|
134
134
|
ms = MetadataStore(config)
|
|
135
135
|
client = create_client()
|
|
136
|
+
server = client.server
|
|
136
137
|
|
|
137
138
|
# determine agent to use, if not provided
|
|
138
139
|
if not yes and not agent:
|
|
@@ -217,7 +218,9 @@ def run(
|
|
|
217
218
|
)
|
|
218
219
|
|
|
219
220
|
# create agent
|
|
220
|
-
tools = [
|
|
221
|
+
tools = [
|
|
222
|
+
server.tool_manager.get_tool_by_name_and_user_id(tool_name=tool_name, user_id=client.user_id) for tool_name in agent_state.tools
|
|
223
|
+
]
|
|
221
224
|
letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools)
|
|
222
225
|
|
|
223
226
|
else: # create new agent
|
|
@@ -297,7 +300,7 @@ def run(
|
|
|
297
300
|
)
|
|
298
301
|
assert isinstance(agent_state.memory, Memory), f"Expected Memory, got {type(agent_state.memory)}"
|
|
299
302
|
typer.secho(f"-> 🛠️ {len(agent_state.tools)} tools: {', '.join([t for t in agent_state.tools])}", fg=typer.colors.WHITE)
|
|
300
|
-
tools = [
|
|
303
|
+
tools = [server.tool_manager.get_tool_by_name_and_user_id(tool_name, user_id=client.user_id) for tool_name in agent_state.tools]
|
|
301
304
|
|
|
302
305
|
letta_agent = Agent(
|
|
303
306
|
interface=interface(),
|
|
@@ -325,13 +328,12 @@ def run(
|
|
|
325
328
|
|
|
326
329
|
def delete_agent(
|
|
327
330
|
agent_name: Annotated[str, typer.Option(help="Specify agent to delete")],
|
|
328
|
-
user_id: Annotated[Optional[str], typer.Option(help="User ID to associate with the agent.")] = None,
|
|
329
331
|
):
|
|
330
332
|
"""Delete an agent from the database"""
|
|
331
333
|
# use client ID is no user_id provided
|
|
332
334
|
config = LettaConfig.load()
|
|
333
335
|
MetadataStore(config)
|
|
334
|
-
client = create_client(
|
|
336
|
+
client = create_client()
|
|
335
337
|
agent = client.get_agent_by_name(agent_name)
|
|
336
338
|
if not agent:
|
|
337
339
|
typer.secho(f"Couldn't find agent named '{agent_name}' to delete", fg=typer.colors.RED)
|
letta/client/client.py
CHANGED
|
@@ -182,6 +182,15 @@ class AbstractClient(object):
|
|
|
182
182
|
def delete_human(self, id: str):
|
|
183
183
|
raise NotImplementedError
|
|
184
184
|
|
|
185
|
+
def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
186
|
+
raise NotImplementedError
|
|
187
|
+
|
|
188
|
+
def load_crewai_tool(self, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
189
|
+
raise NotImplementedError
|
|
190
|
+
|
|
191
|
+
def load_composio_tool(self, action: "ActionType") -> Tool:
|
|
192
|
+
raise NotImplementedError
|
|
193
|
+
|
|
185
194
|
def create_tool(
|
|
186
195
|
self,
|
|
187
196
|
func,
|
|
@@ -829,8 +838,8 @@ class RESTClient(AbstractClient):
|
|
|
829
838
|
else:
|
|
830
839
|
return [Block(**block) for block in response.json()]
|
|
831
840
|
|
|
832
|
-
def create_block(self, label: str, name: str,
|
|
833
|
-
request = CreateBlock(label=label,
|
|
841
|
+
def create_block(self, label: str, text: str, name: Optional[str] = None, template: bool = False) -> Block: #
|
|
842
|
+
request = CreateBlock(label=label, value=text, template=template, name=name)
|
|
834
843
|
response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
|
|
835
844
|
if response.status_code != 200:
|
|
836
845
|
raise ValueError(f"Failed to create block: {response.text}")
|
|
@@ -890,13 +899,13 @@ class RESTClient(AbstractClient):
|
|
|
890
899
|
Create a human block template (saved human string to pre-fill `ChatMemory`)
|
|
891
900
|
|
|
892
901
|
Args:
|
|
893
|
-
name (str): Name of the human block
|
|
894
|
-
text (str): Text of the human block
|
|
902
|
+
name (str): Name of the human block template
|
|
903
|
+
text (str): Text of the human block template
|
|
895
904
|
|
|
896
905
|
Returns:
|
|
897
906
|
human (Human): Human block
|
|
898
907
|
"""
|
|
899
|
-
return self.create_block(label="human", name=name, text=text)
|
|
908
|
+
return self.create_block(label="human", name=name, text=text, template=True)
|
|
900
909
|
|
|
901
910
|
def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
|
|
902
911
|
"""
|
|
@@ -936,7 +945,7 @@ class RESTClient(AbstractClient):
|
|
|
936
945
|
Returns:
|
|
937
946
|
persona (Persona): Persona block
|
|
938
947
|
"""
|
|
939
|
-
return self.create_block(label="persona", name=name, text=text)
|
|
948
|
+
return self.create_block(label="persona", name=name, text=text, template=True)
|
|
940
949
|
|
|
941
950
|
def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
|
|
942
951
|
"""
|
|
@@ -1298,12 +1307,6 @@ class RESTClient(AbstractClient):
|
|
|
1298
1307
|
source_code = parse_source_code(func)
|
|
1299
1308
|
source_type = "python"
|
|
1300
1309
|
|
|
1301
|
-
# TODO: Check if tool already exists
|
|
1302
|
-
# if name:
|
|
1303
|
-
# tool_id = self.get_tool_id(tool_name=name)
|
|
1304
|
-
# if tool_id:
|
|
1305
|
-
# raise ValueError(f"Tool with name {name} (id={tool_id}) already exists")
|
|
1306
|
-
|
|
1307
1310
|
# call server function
|
|
1308
1311
|
request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
|
|
1309
1312
|
response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
|
|
@@ -1337,7 +1340,7 @@ class RESTClient(AbstractClient):
|
|
|
1337
1340
|
|
|
1338
1341
|
source_type = "python"
|
|
1339
1342
|
|
|
1340
|
-
request = ToolUpdate(
|
|
1343
|
+
request = ToolUpdate(source_type=source_type, source_code=source_code, tags=tags, name=name)
|
|
1341
1344
|
response = requests.patch(f"{self.base_url}/{self.api_prefix}/tools/{id}", json=request.model_dump(), headers=self.headers)
|
|
1342
1345
|
if response.status_code != 200:
|
|
1343
1346
|
raise ValueError(f"Failed to update tool: {response.text}")
|
|
@@ -1394,6 +1397,7 @@ class RESTClient(AbstractClient):
|
|
|
1394
1397
|
params["cursor"] = str(cursor)
|
|
1395
1398
|
if limit:
|
|
1396
1399
|
params["limit"] = limit
|
|
1400
|
+
|
|
1397
1401
|
response = requests.get(f"{self.base_url}/{self.api_prefix}/tools", params=params, headers=self.headers)
|
|
1398
1402
|
if response.status_code != 200:
|
|
1399
1403
|
raise ValueError(f"Failed to list tools: {response.text}")
|
|
@@ -1503,6 +1507,7 @@ class LocalClient(AbstractClient):
|
|
|
1503
1507
|
self,
|
|
1504
1508
|
auto_save: bool = False,
|
|
1505
1509
|
user_id: Optional[str] = None,
|
|
1510
|
+
org_id: Optional[str] = None,
|
|
1506
1511
|
debug: bool = False,
|
|
1507
1512
|
default_llm_config: Optional[LLMConfig] = None,
|
|
1508
1513
|
default_embedding_config: Optional[EmbeddingConfig] = None,
|
|
@@ -1529,15 +1534,19 @@ class LocalClient(AbstractClient):
|
|
|
1529
1534
|
self.interface = QueuingInterface(debug=debug)
|
|
1530
1535
|
self.server = SyncServer(default_interface_factory=lambda: self.interface)
|
|
1531
1536
|
|
|
1537
|
+
# save org_id that `LocalClient` is associated with
|
|
1538
|
+
if org_id:
|
|
1539
|
+
self.org_id = org_id
|
|
1540
|
+
else:
|
|
1541
|
+
self.org_id = self.server.organization_manager.DEFAULT_ORG_ID
|
|
1532
1542
|
# save user_id that `LocalClient` is associated with
|
|
1533
1543
|
if user_id:
|
|
1534
1544
|
self.user_id = user_id
|
|
1535
1545
|
else:
|
|
1536
1546
|
# get default user
|
|
1537
|
-
self.user_id = self.server.
|
|
1547
|
+
self.user_id = self.server.user_manager.DEFAULT_USER_ID
|
|
1538
1548
|
|
|
1539
1549
|
# agents
|
|
1540
|
-
|
|
1541
1550
|
def list_agents(self) -> List[AgentState]:
|
|
1542
1551
|
self.interface.clear()
|
|
1543
1552
|
|
|
@@ -1777,7 +1786,7 @@ class LocalClient(AbstractClient):
|
|
|
1777
1786
|
"""
|
|
1778
1787
|
self.server.delete_agent(user_id=self.user_id, agent_id=agent_id)
|
|
1779
1788
|
|
|
1780
|
-
def get_agent_by_name(self, agent_name: str
|
|
1789
|
+
def get_agent_by_name(self, agent_name: str) -> AgentState:
|
|
1781
1790
|
"""
|
|
1782
1791
|
Get an agent by its name
|
|
1783
1792
|
|
|
@@ -1788,7 +1797,7 @@ class LocalClient(AbstractClient):
|
|
|
1788
1797
|
agent_state (AgentState): State of the agent
|
|
1789
1798
|
"""
|
|
1790
1799
|
self.interface.clear()
|
|
1791
|
-
return self.server.get_agent(agent_name=agent_name, user_id=user_id, agent_id=None)
|
|
1800
|
+
return self.server.get_agent(agent_name=agent_name, user_id=self.user_id, agent_id=None)
|
|
1792
1801
|
|
|
1793
1802
|
def get_agent(self, agent_id: str) -> AgentState:
|
|
1794
1803
|
"""
|
|
@@ -2186,50 +2195,27 @@ class LocalClient(AbstractClient):
|
|
|
2186
2195
|
self.server.delete_block(id)
|
|
2187
2196
|
|
|
2188
2197
|
# tools
|
|
2198
|
+
def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
2199
|
+
tool_create = ToolCreate.from_langchain(
|
|
2200
|
+
langchain_tool=langchain_tool,
|
|
2201
|
+
user_id=self.user_id,
|
|
2202
|
+
organization_id=self.org_id,
|
|
2203
|
+
additional_imports_module_attr_map=additional_imports_module_attr_map,
|
|
2204
|
+
)
|
|
2205
|
+
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2189
2206
|
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2207
|
+
def load_crewai_tool(self, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
|
|
2208
|
+
tool_create = ToolCreate.from_crewai(
|
|
2209
|
+
crewai_tool=crewai_tool,
|
|
2210
|
+
additional_imports_module_attr_map=additional_imports_module_attr_map,
|
|
2211
|
+
user_id=self.user_id,
|
|
2212
|
+
organization_id=self.org_id,
|
|
2213
|
+
)
|
|
2214
|
+
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2194
2215
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
Returns:
|
|
2200
|
-
None
|
|
2201
|
-
"""
|
|
2202
|
-
if self.tool_with_name_and_user_id_exists(tool):
|
|
2203
|
-
if update:
|
|
2204
|
-
return self.server.update_tool(
|
|
2205
|
-
ToolUpdate(
|
|
2206
|
-
id=tool.id,
|
|
2207
|
-
description=tool.description,
|
|
2208
|
-
source_type=tool.source_type,
|
|
2209
|
-
source_code=tool.source_code,
|
|
2210
|
-
tags=tool.tags,
|
|
2211
|
-
json_schema=tool.json_schema,
|
|
2212
|
-
name=tool.name,
|
|
2213
|
-
),
|
|
2214
|
-
self.user_id,
|
|
2215
|
-
)
|
|
2216
|
-
else:
|
|
2217
|
-
raise ValueError(f"Tool with id={tool.id} and name={tool.name}already exists")
|
|
2218
|
-
else:
|
|
2219
|
-
# call server function
|
|
2220
|
-
return self.server.create_tool(
|
|
2221
|
-
ToolCreate(
|
|
2222
|
-
id=tool.id,
|
|
2223
|
-
description=tool.description,
|
|
2224
|
-
source_type=tool.source_type,
|
|
2225
|
-
source_code=tool.source_code,
|
|
2226
|
-
name=tool.name,
|
|
2227
|
-
json_schema=tool.json_schema,
|
|
2228
|
-
tags=tool.tags,
|
|
2229
|
-
),
|
|
2230
|
-
user_id=self.user_id,
|
|
2231
|
-
update=update,
|
|
2232
|
-
)
|
|
2216
|
+
def load_composio_tool(self, action: "ActionType") -> Tool:
|
|
2217
|
+
tool_create = ToolCreate.from_composio(action=action, user_id=self.user_id, organization_id=self.org_id)
|
|
2218
|
+
return self.server.tool_manager.create_or_update_tool(tool_create)
|
|
2233
2219
|
|
|
2234
2220
|
# TODO: Use the above function `add_tool` here as there is duplicate logic
|
|
2235
2221
|
def create_tool(
|
|
@@ -2262,11 +2248,16 @@ class LocalClient(AbstractClient):
|
|
|
2262
2248
|
tags = []
|
|
2263
2249
|
|
|
2264
2250
|
# call server function
|
|
2265
|
-
return self.server.
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2251
|
+
return self.server.tool_manager.create_or_update_tool(
|
|
2252
|
+
ToolCreate(
|
|
2253
|
+
user_id=self.user_id,
|
|
2254
|
+
organization_id=self.org_id,
|
|
2255
|
+
source_type=source_type,
|
|
2256
|
+
source_code=source_code,
|
|
2257
|
+
name=name,
|
|
2258
|
+
tags=tags,
|
|
2259
|
+
terminal=terminal,
|
|
2260
|
+
),
|
|
2270
2261
|
)
|
|
2271
2262
|
|
|
2272
2263
|
def update_tool(
|
|
@@ -2288,16 +2279,17 @@ class LocalClient(AbstractClient):
|
|
|
2288
2279
|
Returns:
|
|
2289
2280
|
tool (Tool): Updated tool
|
|
2290
2281
|
"""
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2282
|
+
update_data = {
|
|
2283
|
+
"source_type": "python", # Always include source_type
|
|
2284
|
+
"source_code": parse_source_code(func) if func else None,
|
|
2285
|
+
"tags": tags,
|
|
2286
|
+
"name": name,
|
|
2287
|
+
}
|
|
2295
2288
|
|
|
2296
|
-
|
|
2289
|
+
# Filter out any None values from the dictionary
|
|
2290
|
+
update_data = {key: value for key, value in update_data.items() if value is not None}
|
|
2297
2291
|
|
|
2298
|
-
return self.server.
|
|
2299
|
-
ToolUpdate(id=id, source_type=source_type, source_code=source_code, tags=tags, name=name), self.user_id
|
|
2300
|
-
)
|
|
2292
|
+
return self.server.tool_manager.update_tool_by_id(id, ToolUpdate(**update_data))
|
|
2301
2293
|
|
|
2302
2294
|
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
2303
2295
|
"""
|
|
@@ -2306,11 +2298,11 @@ class LocalClient(AbstractClient):
|
|
|
2306
2298
|
Returns:
|
|
2307
2299
|
tools (List[Tool]): List of tools
|
|
2308
2300
|
"""
|
|
2309
|
-
return self.server.
|
|
2301
|
+
return self.server.tool_manager.list_tools_for_org(cursor=cursor, limit=limit, organization_id=self.org_id)
|
|
2310
2302
|
|
|
2311
2303
|
def get_tool(self, id: str) -> Optional[Tool]:
|
|
2312
2304
|
"""
|
|
2313
|
-
Get a tool
|
|
2305
|
+
Get a tool given its ID.
|
|
2314
2306
|
|
|
2315
2307
|
Args:
|
|
2316
2308
|
id (str): ID of the tool
|
|
@@ -2318,7 +2310,7 @@ class LocalClient(AbstractClient):
|
|
|
2318
2310
|
Returns:
|
|
2319
2311
|
tool (Tool): Tool
|
|
2320
2312
|
"""
|
|
2321
|
-
return self.server.
|
|
2313
|
+
return self.server.tool_manager.get_tool_by_id(id)
|
|
2322
2314
|
|
|
2323
2315
|
def delete_tool(self, id: str):
|
|
2324
2316
|
"""
|
|
@@ -2327,11 +2319,11 @@ class LocalClient(AbstractClient):
|
|
|
2327
2319
|
Args:
|
|
2328
2320
|
id (str): ID of the tool
|
|
2329
2321
|
"""
|
|
2330
|
-
return self.server.
|
|
2322
|
+
return self.server.tool_manager.delete_tool_by_id(id)
|
|
2331
2323
|
|
|
2332
2324
|
def get_tool_id(self, name: str) -> Optional[str]:
|
|
2333
2325
|
"""
|
|
2334
|
-
Get the ID of a tool
|
|
2326
|
+
Get the ID of a tool from its name. The client will use the org_id it is configured with.
|
|
2335
2327
|
|
|
2336
2328
|
Args:
|
|
2337
2329
|
name (str): Name of the tool
|
|
@@ -2339,19 +2331,8 @@ class LocalClient(AbstractClient):
|
|
|
2339
2331
|
Returns:
|
|
2340
2332
|
id (str): ID of the tool (`None` if not found)
|
|
2341
2333
|
"""
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
def tool_with_name_and_user_id_exists(self, tool: Tool) -> bool:
|
|
2345
|
-
"""
|
|
2346
|
-
Check if the tool with name and user_id exists
|
|
2347
|
-
|
|
2348
|
-
Args:
|
|
2349
|
-
tool (Tool): the tool
|
|
2350
|
-
|
|
2351
|
-
Returns:
|
|
2352
|
-
(bool): True if the id exists, False otherwise.
|
|
2353
|
-
"""
|
|
2354
|
-
return self.server.tool_with_name_and_user_id_exists(tool, self.user_id)
|
|
2334
|
+
tool = self.server.tool_manager.get_tool_by_name_and_org_id(tool_name=name, organization_id=self.org_id)
|
|
2335
|
+
return tool.id
|
|
2355
2336
|
|
|
2356
2337
|
def load_data(self, connector: DataConnector, source_name: str):
|
|
2357
2338
|
"""
|
|
@@ -2622,7 +2603,7 @@ class LocalClient(AbstractClient):
|
|
|
2622
2603
|
"""
|
|
2623
2604
|
return self.server.get_blocks(label=label, template=templates_only)
|
|
2624
2605
|
|
|
2625
|
-
def create_block(self,
|
|
2606
|
+
def create_block(self, label: str, text: str, name: Optional[str] = None, template: bool = False) -> Block: #
|
|
2626
2607
|
"""
|
|
2627
2608
|
Create a block
|
|
2628
2609
|
|
|
@@ -2634,7 +2615,9 @@ class LocalClient(AbstractClient):
|
|
|
2634
2615
|
Returns:
|
|
2635
2616
|
block (Block): Created block
|
|
2636
2617
|
"""
|
|
2637
|
-
return self.server.create_block(
|
|
2618
|
+
return self.server.create_block(
|
|
2619
|
+
CreateBlock(label=label, name=name, value=text, user_id=self.user_id, template=template), user_id=self.user_id
|
|
2620
|
+
)
|
|
2638
2621
|
|
|
2639
2622
|
def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
|
|
2640
2623
|
"""
|
letta/config.py
CHANGED
|
@@ -13,7 +13,6 @@ from letta.constants import (
|
|
|
13
13
|
DEFAULT_HUMAN,
|
|
14
14
|
DEFAULT_PERSONA,
|
|
15
15
|
DEFAULT_PRESET,
|
|
16
|
-
DEFAULT_USER_ID,
|
|
17
16
|
LETTA_DIR,
|
|
18
17
|
)
|
|
19
18
|
from letta.log import get_logger
|
|
@@ -45,7 +44,6 @@ def set_field(config, section, field, value):
|
|
|
45
44
|
@dataclass
|
|
46
45
|
class LettaConfig:
|
|
47
46
|
config_path: str = os.getenv("MEMGPT_CONFIG_PATH") or os.path.join(LETTA_DIR, "config")
|
|
48
|
-
anon_clientid: str = DEFAULT_USER_ID
|
|
49
47
|
|
|
50
48
|
# preset
|
|
51
49
|
preset: str = DEFAULT_PRESET # TODO: rename to system prompt
|
|
@@ -182,7 +180,6 @@ class LettaConfig:
|
|
|
182
180
|
"metadata_storage_path": get_field(config, "metadata_storage", "path"),
|
|
183
181
|
"metadata_storage_uri": get_field(config, "metadata_storage", "uri"),
|
|
184
182
|
# Misc
|
|
185
|
-
"anon_clientid": get_field(config, "client", "anon_clientid"),
|
|
186
183
|
"config_path": config_path,
|
|
187
184
|
"letta_version": get_field(config, "version", "letta_version"),
|
|
188
185
|
}
|
|
@@ -278,9 +275,6 @@ class LettaConfig:
|
|
|
278
275
|
# set version
|
|
279
276
|
set_field(config, "version", "letta_version", letta.__version__)
|
|
280
277
|
|
|
281
|
-
# client
|
|
282
|
-
set_field(config, "client", "anon_clientid", self.anon_clientid)
|
|
283
|
-
|
|
284
278
|
# always make sure all directories are present
|
|
285
279
|
self.create_config_dir()
|
|
286
280
|
|
letta/constants.py
CHANGED
|
@@ -3,15 +3,6 @@ from logging import CRITICAL, DEBUG, ERROR, INFO, NOTSET, WARN, WARNING
|
|
|
3
3
|
|
|
4
4
|
LETTA_DIR = os.path.join(os.path.expanduser("~"), ".letta")
|
|
5
5
|
|
|
6
|
-
# Defaults
|
|
7
|
-
DEFAULT_USER_ID = "user-00000000-0000-4000-8000-000000000000"
|
|
8
|
-
# This UUID follows the UUID4 rules:
|
|
9
|
-
# The 13th character (4) indicates it's version 4.
|
|
10
|
-
# The first character of the third segment (8) ensures the variant is correctly set.
|
|
11
|
-
DEFAULT_ORG_ID = "organization-00000000-0000-4000-8000-000000000000"
|
|
12
|
-
DEFAULT_USER_NAME = "default_user"
|
|
13
|
-
DEFAULT_ORG_NAME = "default_org"
|
|
14
|
-
|
|
15
6
|
|
|
16
7
|
# String in the error message for when the context window is too large
|
|
17
8
|
# Example full message:
|
letta/functions/functions.py
CHANGED
|
@@ -3,9 +3,33 @@ import inspect
|
|
|
3
3
|
import os
|
|
4
4
|
from textwrap import dedent # remove indentation
|
|
5
5
|
from types import ModuleType
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from letta.constants import CLI_WARNING_PREFIX
|
|
8
9
|
from letta.functions.schema_generator import generate_schema
|
|
10
|
+
from letta.schemas.tool import ToolCreate
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def derive_openai_json_schema(tool_create: ToolCreate) -> dict:
|
|
14
|
+
# auto-generate openai schema
|
|
15
|
+
try:
|
|
16
|
+
# Define a custom environment with necessary imports
|
|
17
|
+
env = {
|
|
18
|
+
"Optional": Optional, # Add any other required imports here
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
env.update(globals())
|
|
22
|
+
exec(tool_create.source_code, env)
|
|
23
|
+
|
|
24
|
+
# get available functions
|
|
25
|
+
functions = [f for f in env if callable(env[f])]
|
|
26
|
+
|
|
27
|
+
# TODO: not sure if this always works
|
|
28
|
+
func = env[functions[-1]]
|
|
29
|
+
json_schema = generate_schema(func, terminal=tool_create.terminal, name=tool_create.name)
|
|
30
|
+
return json_schema
|
|
31
|
+
except Exception as e:
|
|
32
|
+
raise RuntimeError(f"Failed to execute source code: {e}")
|
|
9
33
|
|
|
10
34
|
|
|
11
35
|
def parse_source_code(func) -> str:
|
letta/functions/helpers.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, Optional, Union
|
|
2
2
|
|
|
3
|
+
import humps
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
|
|
5
6
|
|
|
@@ -8,7 +9,7 @@ def generate_composio_tool_wrapper(action: "ActionType") -> tuple[str, str]:
|
|
|
8
9
|
tool_instantiation_str = f"composio_toolset.get_tools(actions=[Action.{str(action)}])[0]"
|
|
9
10
|
|
|
10
11
|
# Generate func name
|
|
11
|
-
func_name =
|
|
12
|
+
func_name = action.name.lower()
|
|
12
13
|
|
|
13
14
|
wrapper_function_str = f"""
|
|
14
15
|
def {func_name}(**kwargs):
|
|
@@ -40,7 +41,7 @@ def generate_langchain_tool_wrapper(
|
|
|
40
41
|
|
|
41
42
|
tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
|
|
42
43
|
run_call = f"return tool._run(**kwargs)"
|
|
43
|
-
func_name =
|
|
44
|
+
func_name = humps.decamelize(tool_name)
|
|
44
45
|
|
|
45
46
|
# Combine all parts into the wrapper function
|
|
46
47
|
wrapper_function_str = f"""
|
|
@@ -70,7 +71,7 @@ def generate_crewai_tool_wrapper(tool: "CrewAIBaseTool", additional_imports_modu
|
|
|
70
71
|
|
|
71
72
|
tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
|
|
72
73
|
run_call = f"return tool._run(**kwargs)"
|
|
73
|
-
func_name =
|
|
74
|
+
func_name = humps.decamelize(tool_name)
|
|
74
75
|
|
|
75
76
|
# Combine all parts into the wrapper function
|
|
76
77
|
wrapper_function_str = f"""
|
|
@@ -74,7 +74,7 @@ def pydantic_model_to_open_ai(model):
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def generate_schema(function, terminal: Optional[bool], name: Optional[str] = None, description: Optional[str] = None):
|
|
77
|
+
def generate_schema(function, terminal: Optional[bool], name: Optional[str] = None, description: Optional[str] = None) -> dict:
|
|
78
78
|
# Get the signature of the function
|
|
79
79
|
sig = inspect.signature(function)
|
|
80
80
|
|
|
@@ -139,7 +139,7 @@ def generate_schema(function, terminal: Optional[bool], name: Optional[str] = No
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def generate_schema_from_args_schema(
|
|
142
|
-
args_schema: Type[BaseModel], name: Optional[str] = None, description: Optional[str] = None
|
|
142
|
+
args_schema: Type[BaseModel], name: Optional[str] = None, description: Optional[str] = None, append_heartbeat: bool = True
|
|
143
143
|
) -> Dict[str, Any]:
|
|
144
144
|
properties = {}
|
|
145
145
|
required = []
|
|
@@ -163,4 +163,12 @@ def generate_schema_from_args_schema(
|
|
|
163
163
|
"parameters": {"type": "object", "properties": properties, "required": required},
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
# append heartbeat (necessary for triggering another reasoning step after this tool call)
|
|
167
|
+
if append_heartbeat:
|
|
168
|
+
function_call_json["parameters"]["properties"]["request_heartbeat"] = {
|
|
169
|
+
"type": "boolean",
|
|
170
|
+
"description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function.",
|
|
171
|
+
}
|
|
172
|
+
function_call_json["parameters"]["required"].append("request_heartbeat")
|
|
173
|
+
|
|
166
174
|
return function_call_json
|