letta-nightly 0.5.4.dev20241125104219__py3-none-any.whl → 0.5.4.dev20241126104249__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 +6 -0
- letta/cli/cli_config.py +1 -1
- letta/client/client.py +72 -47
- letta/orm/errors.py +8 -0
- letta/orm/sqlalchemy_base.py +46 -7
- letta/schemas/tool.py +25 -1
- letta/server/rest_api/routers/v1/tools.py +35 -5
- letta/services/tool_manager.py +5 -12
- {letta_nightly-0.5.4.dev20241125104219.dist-info → letta_nightly-0.5.4.dev20241126104249.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.4.dev20241125104219.dist-info → letta_nightly-0.5.4.dev20241126104249.dist-info}/RECORD +13 -13
- {letta_nightly-0.5.4.dev20241125104219.dist-info → letta_nightly-0.5.4.dev20241126104249.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.4.dev20241125104219.dist-info → letta_nightly-0.5.4.dev20241126104249.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.4.dev20241125104219.dist-info → letta_nightly-0.5.4.dev20241126104249.dist-info}/entry_points.txt +0 -0
letta/agent.py
CHANGED
|
@@ -1325,6 +1325,12 @@ class Agent(BaseAgent):
|
|
|
1325
1325
|
|
|
1326
1326
|
def update_state(self) -> AgentState:
|
|
1327
1327
|
message_ids = [msg.id for msg in self._messages]
|
|
1328
|
+
|
|
1329
|
+
# Assert that these are all strings
|
|
1330
|
+
if any(not isinstance(m_id, str) for m_id in message_ids):
|
|
1331
|
+
warnings.warn(f"Non-string message IDs found in agent state: {message_ids}")
|
|
1332
|
+
message_ids = [m_id for m_id in message_ids if isinstance(m_id, str)]
|
|
1333
|
+
|
|
1328
1334
|
assert isinstance(self.memory, Memory), f"Memory is not a Memory object: {type(self.memory)}"
|
|
1329
1335
|
|
|
1330
1336
|
# override any fields that may have been updated
|
letta/cli/cli_config.py
CHANGED
|
@@ -136,7 +136,7 @@ def add_tool(
|
|
|
136
136
|
func = eval(func_def.name)
|
|
137
137
|
|
|
138
138
|
# 4. Add or update the tool
|
|
139
|
-
tool = client.
|
|
139
|
+
tool = client.create_or_update_tool(func=func, name=name, tags=tags, update=update)
|
|
140
140
|
print(f"Tool {tool.name} added successfully")
|
|
141
141
|
|
|
142
142
|
|
letta/client/client.py
CHANGED
|
@@ -211,6 +211,14 @@ class AbstractClient(object):
|
|
|
211
211
|
) -> Tool:
|
|
212
212
|
raise NotImplementedError
|
|
213
213
|
|
|
214
|
+
def create_or_update_tool(
|
|
215
|
+
self,
|
|
216
|
+
func,
|
|
217
|
+
name: Optional[str] = None,
|
|
218
|
+
tags: Optional[List[str]] = None,
|
|
219
|
+
) -> Tool:
|
|
220
|
+
raise NotImplementedError
|
|
221
|
+
|
|
214
222
|
def update_tool(
|
|
215
223
|
self,
|
|
216
224
|
id: str,
|
|
@@ -532,7 +540,7 @@ class RESTClient(AbstractClient):
|
|
|
532
540
|
# add memory tools
|
|
533
541
|
memory_functions = get_memory_functions(memory)
|
|
534
542
|
for func_name, func in memory_functions.items():
|
|
535
|
-
tool = self.
|
|
543
|
+
tool = self.create_or_update_tool(func, name=func_name, tags=["memory", "letta-base"])
|
|
536
544
|
tool_names.append(tool.name)
|
|
537
545
|
|
|
538
546
|
# check if default configs are provided
|
|
@@ -1440,18 +1448,39 @@ class RESTClient(AbstractClient):
|
|
|
1440
1448
|
Returns:
|
|
1441
1449
|
tool (Tool): The created tool.
|
|
1442
1450
|
"""
|
|
1451
|
+
source_code = parse_source_code(func)
|
|
1452
|
+
source_type = "python"
|
|
1443
1453
|
|
|
1444
|
-
#
|
|
1445
|
-
|
|
1454
|
+
# call server function
|
|
1455
|
+
request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
|
|
1456
|
+
response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
|
|
1457
|
+
if response.status_code != 200:
|
|
1458
|
+
raise ValueError(f"Failed to create tool: {response.text}")
|
|
1459
|
+
return Tool(**response.json())
|
|
1446
1460
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1461
|
+
def create_or_update_tool(
|
|
1462
|
+
self,
|
|
1463
|
+
func: Callable,
|
|
1464
|
+
name: Optional[str] = None,
|
|
1465
|
+
tags: Optional[List[str]] = None,
|
|
1466
|
+
) -> Tool:
|
|
1467
|
+
"""
|
|
1468
|
+
Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
|
|
1469
|
+
|
|
1470
|
+
Args:
|
|
1471
|
+
func (callable): The function to create a tool for.
|
|
1472
|
+
name: (str): Name of the tool (must be unique per-user.)
|
|
1473
|
+
tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
|
|
1474
|
+
|
|
1475
|
+
Returns:
|
|
1476
|
+
tool (Tool): The created tool.
|
|
1477
|
+
"""
|
|
1449
1478
|
source_code = parse_source_code(func)
|
|
1450
1479
|
source_type = "python"
|
|
1451
1480
|
|
|
1452
1481
|
# call server function
|
|
1453
1482
|
request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
|
|
1454
|
-
response = requests.
|
|
1483
|
+
response = requests.put(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
|
|
1455
1484
|
if response.status_code != 200:
|
|
1456
1485
|
raise ValueError(f"Failed to create tool: {response.text}")
|
|
1457
1486
|
return Tool(**response.json())
|
|
@@ -1489,45 +1518,6 @@ class RESTClient(AbstractClient):
|
|
|
1489
1518
|
raise ValueError(f"Failed to update tool: {response.text}")
|
|
1490
1519
|
return Tool(**response.json())
|
|
1491
1520
|
|
|
1492
|
-
# def create_tool(
|
|
1493
|
-
# self,
|
|
1494
|
-
# func,
|
|
1495
|
-
# name: Optional[str] = None,
|
|
1496
|
-
# update: Optional[bool] = True, # TODO: actually use this
|
|
1497
|
-
# tags: Optional[List[str]] = None,
|
|
1498
|
-
# ):
|
|
1499
|
-
# """Create a tool
|
|
1500
|
-
|
|
1501
|
-
# Args:
|
|
1502
|
-
# func (callable): The function to create a tool for.
|
|
1503
|
-
# tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
|
|
1504
|
-
# update (bool, optional): Update the tool if it already exists. Defaults to True.
|
|
1505
|
-
|
|
1506
|
-
# Returns:
|
|
1507
|
-
# Tool object
|
|
1508
|
-
# """
|
|
1509
|
-
|
|
1510
|
-
# # TODO: check if tool already exists
|
|
1511
|
-
# # TODO: how to load modules?
|
|
1512
|
-
# # parse source code/schema
|
|
1513
|
-
# source_code = parse_source_code(func)
|
|
1514
|
-
# json_schema = generate_schema(func, name)
|
|
1515
|
-
# source_type = "python"
|
|
1516
|
-
# json_schema["name"]
|
|
1517
|
-
|
|
1518
|
-
# # create data
|
|
1519
|
-
# data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema, "update": update}
|
|
1520
|
-
# try:
|
|
1521
|
-
# CreateToolRequest(**data) # validate data
|
|
1522
|
-
# except Exception as e:
|
|
1523
|
-
# raise ValueError(f"Failed to create tool: {e}, invalid input {data}")
|
|
1524
|
-
|
|
1525
|
-
# # make REST request
|
|
1526
|
-
# response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=data, headers=self.headers)
|
|
1527
|
-
# if response.status_code != 200:
|
|
1528
|
-
# raise ValueError(f"Failed to create tool: {response.text}")
|
|
1529
|
-
# return ToolModel(**response.json())
|
|
1530
|
-
|
|
1531
1521
|
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
|
|
1532
1522
|
"""
|
|
1533
1523
|
List available tools for the user.
|
|
@@ -1977,7 +1967,7 @@ class LocalClient(AbstractClient):
|
|
|
1977
1967
|
# add memory tools
|
|
1978
1968
|
memory_functions = get_memory_functions(memory)
|
|
1979
1969
|
for func_name, func in memory_functions.items():
|
|
1980
|
-
tool = self.
|
|
1970
|
+
tool = self.create_or_update_tool(func, name=func_name, tags=["memory", "letta-base"])
|
|
1981
1971
|
tool_names.append(tool.name)
|
|
1982
1972
|
|
|
1983
1973
|
self.interface.clear()
|
|
@@ -2573,7 +2563,6 @@ class LocalClient(AbstractClient):
|
|
|
2573
2563
|
tool_create = ToolCreate.from_composio(action=action)
|
|
2574
2564
|
return self.server.tool_manager.create_or_update_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=self.user)
|
|
2575
2565
|
|
|
2576
|
-
# TODO: Use the above function `add_tool` here as there is duplicate logic
|
|
2577
2566
|
def create_tool(
|
|
2578
2567
|
self,
|
|
2579
2568
|
func,
|
|
@@ -2601,6 +2590,42 @@ class LocalClient(AbstractClient):
|
|
|
2601
2590
|
if not tags:
|
|
2602
2591
|
tags = []
|
|
2603
2592
|
|
|
2593
|
+
# call server function
|
|
2594
|
+
return self.server.tool_manager.create_tool(
|
|
2595
|
+
Tool(
|
|
2596
|
+
source_type=source_type,
|
|
2597
|
+
source_code=source_code,
|
|
2598
|
+
name=name,
|
|
2599
|
+
tags=tags,
|
|
2600
|
+
description=description,
|
|
2601
|
+
),
|
|
2602
|
+
actor=self.user,
|
|
2603
|
+
)
|
|
2604
|
+
|
|
2605
|
+
def create_or_update_tool(
|
|
2606
|
+
self,
|
|
2607
|
+
func,
|
|
2608
|
+
name: Optional[str] = None,
|
|
2609
|
+
tags: Optional[List[str]] = None,
|
|
2610
|
+
description: Optional[str] = None,
|
|
2611
|
+
) -> Tool:
|
|
2612
|
+
"""
|
|
2613
|
+
Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
|
|
2614
|
+
|
|
2615
|
+
Args:
|
|
2616
|
+
func (callable): The function to create a tool for.
|
|
2617
|
+
name: (str): Name of the tool (must be unique per-user.)
|
|
2618
|
+
tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
|
|
2619
|
+
description (str, optional): The description.
|
|
2620
|
+
|
|
2621
|
+
Returns:
|
|
2622
|
+
tool (Tool): The created tool.
|
|
2623
|
+
"""
|
|
2624
|
+
source_code = parse_source_code(func)
|
|
2625
|
+
source_type = "python"
|
|
2626
|
+
if not tags:
|
|
2627
|
+
tags = []
|
|
2628
|
+
|
|
2604
2629
|
# call server function
|
|
2605
2630
|
return self.server.tool_manager.create_or_update_tool(
|
|
2606
2631
|
Tool(
|
letta/orm/errors.py
CHANGED
|
@@ -4,3 +4,11 @@ class NoResultFound(Exception):
|
|
|
4
4
|
|
|
5
5
|
class MalformedIdError(Exception):
|
|
6
6
|
"""An id not in the right format, most likely violating uuid4 format."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UniqueConstraintViolationError(ValueError):
|
|
10
|
+
"""Custom exception for unique constraint violations."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ForeignKeyConstraintViolationError(ValueError):
|
|
14
|
+
"""Custom exception for foreign key constraint violations."""
|
letta/orm/sqlalchemy_base.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, List, Literal, Optional, Type
|
|
2
2
|
|
|
3
3
|
from sqlalchemy import String, select
|
|
4
|
+
from sqlalchemy.exc import DBAPIError
|
|
4
5
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
5
6
|
|
|
6
7
|
from letta.log import get_logger
|
|
7
8
|
from letta.orm.base import Base, CommonSqlalchemyMetaMixins
|
|
8
|
-
from letta.orm.errors import
|
|
9
|
+
from letta.orm.errors import (
|
|
10
|
+
ForeignKeyConstraintViolationError,
|
|
11
|
+
NoResultFound,
|
|
12
|
+
UniqueConstraintViolationError,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
if TYPE_CHECKING:
|
|
11
16
|
from pydantic import BaseModel
|
|
@@ -102,12 +107,14 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
102
107
|
|
|
103
108
|
if actor:
|
|
104
109
|
self._set_created_and_updated_by_fields(actor.id)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
try:
|
|
111
|
+
with db_session as session:
|
|
112
|
+
session.add(self)
|
|
113
|
+
session.commit()
|
|
114
|
+
session.refresh(self)
|
|
115
|
+
return self
|
|
116
|
+
except DBAPIError as e:
|
|
117
|
+
self._handle_dbapi_error(e)
|
|
111
118
|
|
|
112
119
|
def delete(self, db_session: "Session", actor: Optional["User"] = None) -> Type["SqlalchemyBase"]:
|
|
113
120
|
logger.debug(f"Soft deleting {self.__class__.__name__} with ID: {self.id} with actor={actor}")
|
|
@@ -168,6 +175,38 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
168
175
|
raise ValueError(f"object {actor} has no organization accessor")
|
|
169
176
|
return query.where(cls.organization_id == org_id, cls.is_deleted == False)
|
|
170
177
|
|
|
178
|
+
@classmethod
|
|
179
|
+
def _handle_dbapi_error(cls, e: DBAPIError):
|
|
180
|
+
"""Handle database errors and raise appropriate custom exceptions."""
|
|
181
|
+
orig = e.orig # Extract the original error from the DBAPIError
|
|
182
|
+
error_code = None
|
|
183
|
+
|
|
184
|
+
# For psycopg2
|
|
185
|
+
if hasattr(orig, "pgcode"):
|
|
186
|
+
error_code = orig.pgcode
|
|
187
|
+
# For pg8000
|
|
188
|
+
elif hasattr(orig, "args") and len(orig.args) > 0:
|
|
189
|
+
# The first argument contains the error details as a dictionary
|
|
190
|
+
err_dict = orig.args[0]
|
|
191
|
+
if isinstance(err_dict, dict):
|
|
192
|
+
error_code = err_dict.get("C") # 'C' is the error code field
|
|
193
|
+
logger.info(f"Extracted error_code: {error_code}")
|
|
194
|
+
|
|
195
|
+
# Handle unique constraint violations
|
|
196
|
+
if error_code == "23505":
|
|
197
|
+
raise UniqueConstraintViolationError(
|
|
198
|
+
f"A unique constraint was violated for {cls.__name__}. Check your input for duplicates: {e}"
|
|
199
|
+
) from e
|
|
200
|
+
|
|
201
|
+
# Handle foreign key violations
|
|
202
|
+
if error_code == "23503":
|
|
203
|
+
raise ForeignKeyConstraintViolationError(
|
|
204
|
+
f"A foreign key constraint was violated for {cls.__name__}. Check your input for missing or invalid references: {e}"
|
|
205
|
+
) from e
|
|
206
|
+
|
|
207
|
+
# Re-raise for other unhandled DBAPI errors
|
|
208
|
+
raise
|
|
209
|
+
|
|
171
210
|
@property
|
|
172
211
|
def __pydantic_model__(self) -> Type["BaseModel"]:
|
|
173
212
|
raise NotImplementedError("Sqlalchemy models must declare a __pydantic_model__ property to be convertable.")
|
letta/schemas/tool.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
|
-
from pydantic import Field
|
|
3
|
+
from pydantic import Field, model_validator
|
|
4
4
|
|
|
5
|
+
from letta.functions.functions import derive_openai_json_schema
|
|
5
6
|
from letta.functions.helpers import (
|
|
6
7
|
generate_composio_tool_wrapper,
|
|
7
8
|
generate_langchain_tool_wrapper,
|
|
@@ -44,6 +45,29 @@ class Tool(BaseTool):
|
|
|
44
45
|
created_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
|
45
46
|
last_updated_by_id: Optional[str] = Field(None, description="The id of the user that made this Tool.")
|
|
46
47
|
|
|
48
|
+
@model_validator(mode="after")
|
|
49
|
+
def populate_missing_fields(self):
|
|
50
|
+
"""
|
|
51
|
+
Populate missing fields: name, description, and json_schema.
|
|
52
|
+
"""
|
|
53
|
+
# Derive JSON schema if not provided
|
|
54
|
+
if not self.json_schema:
|
|
55
|
+
self.json_schema = derive_openai_json_schema(source_code=self.source_code)
|
|
56
|
+
|
|
57
|
+
# Derive name from the JSON schema if not provided
|
|
58
|
+
if not self.name:
|
|
59
|
+
# TODO: This in theory could error, but name should always be on json_schema
|
|
60
|
+
# TODO: Make JSON schema a typed pydantic object
|
|
61
|
+
self.name = self.json_schema.get("name")
|
|
62
|
+
|
|
63
|
+
# Derive description from the JSON schema if not provided
|
|
64
|
+
if not self.description:
|
|
65
|
+
# TODO: This in theory could error, but description should always be on json_schema
|
|
66
|
+
# TODO: Make JSON schema a typed pydantic object
|
|
67
|
+
self.description = self.json_schema.get("description")
|
|
68
|
+
|
|
69
|
+
return self
|
|
70
|
+
|
|
47
71
|
def to_dict(self):
|
|
48
72
|
"""
|
|
49
73
|
Convert tool into OpenAI representation.
|
|
@@ -2,6 +2,7 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Body, Depends, Header, HTTPException
|
|
4
4
|
|
|
5
|
+
from letta.orm.errors import UniqueConstraintViolationError
|
|
5
6
|
from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
|
|
6
7
|
from letta.server.rest_api.utils import get_letta_server
|
|
7
8
|
from letta.server.server import SyncServer
|
|
@@ -83,12 +84,41 @@ def create_tool(
|
|
|
83
84
|
"""
|
|
84
85
|
Create a new tool
|
|
85
86
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
try:
|
|
88
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
89
|
+
tool = Tool(**request.model_dump())
|
|
90
|
+
return server.tool_manager.create_tool(pydantic_tool=tool, actor=actor)
|
|
91
|
+
except UniqueConstraintViolationError as e:
|
|
92
|
+
# Log or print the full exception here for debugging
|
|
93
|
+
print(f"Error occurred: {e}")
|
|
94
|
+
raise HTTPException(status_code=409, detail=str(e))
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Catch other unexpected errors and raise an internal server error
|
|
97
|
+
print(f"Unexpected error occurred: {e}")
|
|
98
|
+
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
|
|
101
|
+
@router.put("/", response_model=Tool, operation_id="upsert_tool")
|
|
102
|
+
def upsert_tool(
|
|
103
|
+
request: ToolCreate = Body(...),
|
|
104
|
+
server: SyncServer = Depends(get_letta_server),
|
|
105
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Create or update a tool
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
112
|
+
tool = server.tool_manager.create_or_update_tool(pydantic_tool=Tool(**request.model_dump()), actor=actor)
|
|
113
|
+
return tool
|
|
114
|
+
except UniqueConstraintViolationError as e:
|
|
115
|
+
# Log the error and raise a conflict exception
|
|
116
|
+
print(f"Unique constraint violation occurred: {e}")
|
|
117
|
+
raise HTTPException(status_code=409, detail=str(e))
|
|
118
|
+
except Exception as e:
|
|
119
|
+
# Catch other unexpected errors and raise an internal server error
|
|
120
|
+
print(f"Unexpected error occurred: {e}")
|
|
121
|
+
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
|
|
92
122
|
|
|
93
123
|
|
|
94
124
|
@router.patch("/{tool_id}", response_model=Tool, operation_id="update_tool")
|
letta/services/tool_manager.py
CHANGED
|
@@ -35,9 +35,7 @@ class ToolManager:
|
|
|
35
35
|
def create_or_update_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
|
|
36
36
|
"""Create a new tool based on the ToolCreate schema."""
|
|
37
37
|
# Derive json_schema
|
|
38
|
-
|
|
39
|
-
derived_name = pydantic_tool.name or derived_json_schema["name"]
|
|
40
|
-
tool = self.get_tool_by_name(tool_name=derived_name, actor=actor)
|
|
38
|
+
tool = self.get_tool_by_name(tool_name=pydantic_tool.name, actor=actor)
|
|
41
39
|
if tool:
|
|
42
40
|
# Put to dict and remove fields that should not be reset
|
|
43
41
|
update_data = pydantic_tool.model_dump(exclude={"module"}, exclude_unset=True, exclude_none=True)
|
|
@@ -52,8 +50,6 @@ class ToolManager:
|
|
|
52
50
|
f"`create_or_update_tool` was called with user_id={actor.id}, organization_id={actor.organization_id}, name={pydantic_tool.name}, but found existing tool with nothing to update."
|
|
53
51
|
)
|
|
54
52
|
else:
|
|
55
|
-
pydantic_tool.json_schema = derived_json_schema
|
|
56
|
-
pydantic_tool.name = derived_name
|
|
57
53
|
tool = self.create_tool(pydantic_tool, actor=actor)
|
|
58
54
|
|
|
59
55
|
return tool
|
|
@@ -61,18 +57,15 @@ class ToolManager:
|
|
|
61
57
|
@enforce_types
|
|
62
58
|
def create_tool(self, pydantic_tool: PydanticTool, actor: PydanticUser) -> PydanticTool:
|
|
63
59
|
"""Create a new tool based on the ToolCreate schema."""
|
|
64
|
-
# Create the tool
|
|
65
60
|
with self.session_maker() as session:
|
|
66
61
|
# Set the organization id at the ORM layer
|
|
67
62
|
pydantic_tool.organization_id = actor.organization_id
|
|
63
|
+
# Auto-generate description if not provided
|
|
64
|
+
if pydantic_tool.description is None:
|
|
65
|
+
pydantic_tool.description = pydantic_tool.json_schema.get("description", None)
|
|
68
66
|
tool_data = pydantic_tool.model_dump()
|
|
69
67
|
tool = ToolModel(**tool_data)
|
|
70
|
-
#
|
|
71
|
-
# so copy it over into the top-level description field
|
|
72
|
-
if tool.description is None:
|
|
73
|
-
tool.description = tool.json_schema.get("description", None)
|
|
74
|
-
tool.create(session, actor=actor)
|
|
75
|
-
|
|
68
|
+
tool.create(session, actor=actor) # Re-raise other database-related errors
|
|
76
69
|
return tool.to_pydantic()
|
|
77
70
|
|
|
78
71
|
@enforce_types
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
letta/__init__.py,sha256=lrj66PR9vRWLWUvQAgk4Qi8BebVsYk8J2poTTbuuBFQ,1014
|
|
2
2
|
letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
|
3
|
-
letta/agent.py,sha256
|
|
3
|
+
letta/agent.py,sha256=-LajA72btWKrVn0bg6DRr494Wp4gdc4hIVeG9aOXhhU,78940
|
|
4
4
|
letta/agent_store/chroma.py,sha256=upR5zGnGs6I6btulEYbiZdGG87BgKjxUJOQZ4Y-RQ_M,12492
|
|
5
5
|
letta/agent_store/db.py,sha256=n15t8qhHfqhtFDxSQg_9uwvMntpWml8Jz_Y-ofL0loQ,23467
|
|
6
6
|
letta/agent_store/lancedb.py,sha256=i63d4VZwj9UIOTNs5f0JZ_r5yZD-jKWz4FAH4RMpXOE,5104
|
|
@@ -10,10 +10,10 @@ letta/agent_store/storage.py,sha256=4gKvMRYBGm9cwyaDOzljxDKgqr4MxGXcC4yGhAdKcAA,
|
|
|
10
10
|
letta/benchmark/benchmark.py,sha256=ebvnwfp3yezaXOQyGXkYCDYpsmre-b9hvNtnyx4xkG0,3701
|
|
11
11
|
letta/benchmark/constants.py,sha256=aXc5gdpMGJT327VuxsT5FngbCK2J41PQYeICBO7g_RE,536
|
|
12
12
|
letta/cli/cli.py,sha256=1dJIZ8DIGM8mg0G0UGLKMTa3fwgHzrN8Hkxd5Uxx7X4,16946
|
|
13
|
-
letta/cli/cli_config.py,sha256=
|
|
13
|
+
letta/cli/cli_config.py,sha256=tB0Wgz3O9j6KiCsU1HWfsKmhNM9RqLsAxzxEDFQFGnM,8565
|
|
14
14
|
letta/cli/cli_load.py,sha256=x4L8s15GwIW13xrhKYFWHo_y-IVGtoPDHWWKcHDRP10,4587
|
|
15
15
|
letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
letta/client/client.py,sha256=
|
|
16
|
+
letta/client/client.py,sha256=AI5HYD8O945QM8vHomJ7NyCh0i8TYK7ksoxh-DqkVXQ,116083
|
|
17
17
|
letta/client/streaming.py,sha256=Hh5pjlyrdCuO2V75ZCxSSOCPd3BmHdKFGaIUJC6fBp0,4775
|
|
18
18
|
letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
|
|
19
19
|
letta/config.py,sha256=eK-ip06ELHNYriInkgfidDvJxQ2tD1u49I-VLXB87nE,18929
|
|
@@ -96,13 +96,13 @@ letta/orm/base.py,sha256=K_LpNUURbsj44ycHbzvNXG_n8pBOjf1YvDaikIPDpQA,2716
|
|
|
96
96
|
letta/orm/block.py,sha256=ZD2LdESD2KH2fY1_W4mZKtKQnFYlo_DbjVJZLaNo7xI,2229
|
|
97
97
|
letta/orm/blocks_agents.py,sha256=tFeQ5OzehyZi9Qafvecr8W0v6EkJ48PqvR3rEjBzJ5c,1046
|
|
98
98
|
letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
|
|
99
|
-
letta/orm/errors.py,sha256=
|
|
99
|
+
letta/orm/errors.py,sha256=nv1HnF3z4-u9m_g7SO5TO5u2nmJN677_n8F0iIjluUI,460
|
|
100
100
|
letta/orm/file.py,sha256=FtfZlJLXfac4ntaw3kC0N9VRoD255m8EK4p-pC2lcHk,1519
|
|
101
101
|
letta/orm/mixins.py,sha256=LfwePamGyOwCtAEUm-sZpIBJjODIMe4MnA_JTUcppLs,1155
|
|
102
102
|
letta/orm/organization.py,sha256=mliw4q7-SRfRcGIG8paNfCNn6ITTjR7nFalZzlRszqU,2272
|
|
103
103
|
letta/orm/sandbox_config.py,sha256=PCMHE-eJPzBT-90OYtXjEMRF4f9JB8AJIGETE7bu-f0,2870
|
|
104
104
|
letta/orm/source.py,sha256=Ib0XHCMt345RjBSC30A398rZ21W5mA4PXX00XNXyd24,2021
|
|
105
|
-
letta/orm/sqlalchemy_base.py,sha256=
|
|
105
|
+
letta/orm/sqlalchemy_base.py,sha256=c-ZsFvpr5Cf9fta_JoMUc1O3YmbD-wc2CiL9Jzn6VP0,9318
|
|
106
106
|
letta/orm/tool.py,sha256=d0GclU_7qg8Z6ZE6kkH1kmrUAMCiV-ZM8BGaT1mnBU4,2089
|
|
107
107
|
letta/orm/user.py,sha256=bB4qGIT-ZoECZeeVqG-z3Z7WFXGqpC-GPcoYQoJZOuc,1137
|
|
108
108
|
letta/persistence_manager.py,sha256=LlLgEDpSafCPAiyKmuq0NvVAnfBkZo6TWbGIKYQjQBs,5200
|
|
@@ -156,7 +156,7 @@ letta/schemas/organization.py,sha256=d2oN3IK2HeruEHKXwIzCbJ3Fxdi_BEe9JZ8J9aDbHwQ
|
|
|
156
156
|
letta/schemas/passage.py,sha256=eYQMxD_XjHAi72jmqcGBU4wM4VZtSU0XK8uhQxxN3Ug,3563
|
|
157
157
|
letta/schemas/sandbox_config.py,sha256=LC0hnB3TbFJmY7lXqVsseJkqTbxry0xmBB0bwI8Y7Rc,4769
|
|
158
158
|
letta/schemas/source.py,sha256=B1VbaDJV-EGPv1nQXwCx_RAzeAJd50UqP_1m1cIRT8c,2854
|
|
159
|
-
letta/schemas/tool.py,sha256=
|
|
159
|
+
letta/schemas/tool.py,sha256=DolG1PZDacREGQco7gYFVJNZFLjfBa1jDRmuCPKNiuM,9131
|
|
160
160
|
letta/schemas/tool_rule.py,sha256=zv4jE0b8LW78idP4UbJARnrZcnmaqjGNUk_YV99Y0c0,884
|
|
161
161
|
letta/schemas/usage.py,sha256=lvn1ooHwLEdv6gwQpw5PBUbcwn_gwdT6HA-fCiix6sY,817
|
|
162
162
|
letta/schemas/user.py,sha256=V32Tgl6oqB3KznkxUz12y7agkQicjzW7VocSpj78i6Q,1526
|
|
@@ -186,7 +186,7 @@ letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFl
|
|
|
186
186
|
letta/server/rest_api/routers/v1/organizations.py,sha256=tyqVzXTpMtk3sKxI3Iz4aS6RhbGEbXDzFBB_CpW18v4,2080
|
|
187
187
|
letta/server/rest_api/routers/v1/sandbox_configs.py,sha256=j-q9coHFhrsRwyswGyrVPUjawI0Iy6eYaG4iKd6ZKMA,4219
|
|
188
188
|
letta/server/rest_api/routers/v1/sources.py,sha256=5Cs2YTSooh_WNT2C18PsdKzkyr4ZvaHt5Xjubyz0yJw,9196
|
|
189
|
-
letta/server/rest_api/routers/v1/tools.py,sha256=
|
|
189
|
+
letta/server/rest_api/routers/v1/tools.py,sha256=DeIs0AVAY13ALY5JqJ1CAcRzl4VYREsuS_PifwIpNOI,5634
|
|
190
190
|
letta/server/rest_api/routers/v1/users.py,sha256=M1wEr2IyHzuRwINYxLXTkrbAH3osLe_cWjzrWrzR1aw,3729
|
|
191
191
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
192
192
|
letta/server/rest_api/utils.py,sha256=6c5a_-ZFTlwZ1IuzpRQtqxSG1eD56nNhKhWlrdgBYWk,3103
|
|
@@ -211,7 +211,7 @@ letta/services/organization_manager.py,sha256=OfE2_NMmhqXURX4sg7hCOiFQVQpV5ZiPu7
|
|
|
211
211
|
letta/services/sandbox_config_manager.py,sha256=9BCu59nHR4nIMFXgFyEMOY2UTmZvBMS3GlDBWWCHB4I,12648
|
|
212
212
|
letta/services/source_manager.py,sha256=StX5Wfd7XSCKJet8qExIu3GMoI-eMIbEarAeTv2gq0s,6555
|
|
213
213
|
letta/services/tool_execution_sandbox.py,sha256=6PX9uKt_si3cqpRjwOm8Hyn9PfLNn0k7AwKoTTtz6qs,13128
|
|
214
|
-
letta/services/tool_manager.py,sha256=
|
|
214
|
+
letta/services/tool_manager.py,sha256=wzk5hoVdH3FYdysSLdqBeRpbUiE2-NbfDjmqIsUUC9M,7730
|
|
215
215
|
letta/services/tool_sandbox_env/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
216
216
|
letta/services/user_manager.py,sha256=UJa0hqCjz0yXtvrCR8OVBqlSR5lC_Ejn-uG__58zLds,4398
|
|
217
217
|
letta/settings.py,sha256=ZcUcwvl7hStawZ0JOA0133jNk3j5qBd7qlFAAaIPsU8,3608
|
|
@@ -219,8 +219,8 @@ letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,
|
|
|
219
219
|
letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
|
|
220
220
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
221
221
|
letta/utils.py,sha256=COwQLAt02eEM9tjp6p5kN8YeTqGXr714l5BvffLVCLU,32376
|
|
222
|
-
letta_nightly-0.5.4.
|
|
223
|
-
letta_nightly-0.5.4.
|
|
224
|
-
letta_nightly-0.5.4.
|
|
225
|
-
letta_nightly-0.5.4.
|
|
226
|
-
letta_nightly-0.5.4.
|
|
222
|
+
letta_nightly-0.5.4.dev20241126104249.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
223
|
+
letta_nightly-0.5.4.dev20241126104249.dist-info/METADATA,sha256=xyNUVuIvBQVZeWiK9xRllN8AADQ4oP9DJCsq6CL8KU8,11515
|
|
224
|
+
letta_nightly-0.5.4.dev20241126104249.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
225
|
+
letta_nightly-0.5.4.dev20241126104249.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
226
|
+
letta_nightly-0.5.4.dev20241126104249.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|