letta-nightly 0.5.4.dev20241125104219__py3-none-any.whl → 0.5.4.dev20241127104220__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 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.create_tool(func=func, name=name, tags=tags, update=update)
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.create_tool(func, name=func_name, tags=["memory", "letta-base"])
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
- # TODO: check tool update code
1445
- # TODO: check if tool already exists
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
- # TODO: how to load modules?
1448
- # parse source code/schema
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.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
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.create_tool(func, name=func_name, tags=["memory", "letta-base"])
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/errors.py CHANGED
@@ -10,6 +10,18 @@ class LettaError(Exception):
10
10
  """Base class for all Letta related errors."""
11
11
 
12
12
 
13
+ class LettaToolCreateError(LettaError):
14
+ """Error raised when a tool cannot be created."""
15
+
16
+ default_error_message = "Error creating tool."
17
+
18
+ def __init__(self, message=None):
19
+ if message is None:
20
+ message = self.default_error_message
21
+ self.message = message
22
+ super().__init__(self.message)
23
+
24
+
13
25
  class LLMError(LettaError):
14
26
  pass
15
27
 
@@ -3,9 +3,10 @@ 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, List
6
+ from typing import Dict, List, Optional
7
7
 
8
8
  from letta.constants import CLI_WARNING_PREFIX
9
+ from letta.errors import LettaToolCreateError
9
10
  from letta.functions.schema_generator import generate_schema
10
11
 
11
12
 
@@ -13,10 +14,7 @@ def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> d
13
14
  # auto-generate openai schema
14
15
  try:
15
16
  # Define a custom environment with necessary imports
16
- env = {
17
- "Optional": Optional, # Add any other required imports here
18
- "List": List
19
- }
17
+ env = {"Optional": Optional, "List": List, "Dict": Dict} # Add any other required imports here
20
18
 
21
19
  env.update(globals())
22
20
  exec(source_code, env)
@@ -29,7 +27,7 @@ def derive_openai_json_schema(source_code: str, name: Optional[str] = None) -> d
29
27
  json_schema = generate_schema(func, name=name)
30
28
  return json_schema
31
29
  except Exception as e:
32
- raise RuntimeError(f"Failed to execute source code: {e}")
30
+ raise LettaToolCreateError(f"Failed to derive JSON schema from source code: {e}")
33
31
 
34
32
 
35
33
  def parse_source_code(func) -> str:
@@ -131,11 +131,12 @@ def generate_schema(function, name: Optional[str] = None, description: Optional[
131
131
  else:
132
132
  # Add parameter details to the schema
133
133
  param_doc = next((d for d in docstring.params if d.arg_name == param.name), None)
134
- schema["parameters"]["properties"][param.name] = {
135
- # "type": "string" if param.annotation == str else str(param.annotation),
136
- "type": type_to_json_schema_type(param.annotation) if param.annotation != inspect.Parameter.empty else "string",
137
- "description": param_doc.description,
138
- }
134
+ if param_doc:
135
+ schema["parameters"]["properties"][param.name] = {
136
+ # "type": "string" if param.annotation == str else str(param.annotation),
137
+ "type": type_to_json_schema_type(param.annotation) if param.annotation != inspect.Parameter.empty else "string",
138
+ "description": param_doc.description,
139
+ }
139
140
  if param.default == inspect.Parameter.empty:
140
141
  schema["parameters"]["required"].append(param.name)
141
142
 
letta/metadata.py CHANGED
@@ -25,6 +25,7 @@ from letta.schemas.tool_rule import (
25
25
  ToolRule,
26
26
  )
27
27
  from letta.schemas.user import User
28
+ from letta.services.per_agent_lock_manager import PerAgentLockManager
28
29
  from letta.settings import settings
29
30
  from letta.utils import enforce_types, get_utc_time, printd
30
31
 
@@ -383,7 +384,11 @@ class MetadataStore:
383
384
  session.commit()
384
385
 
385
386
  @enforce_types
386
- def delete_agent(self, agent_id: str):
387
+ def delete_agent(self, agent_id: str, per_agent_lock_manager: PerAgentLockManager):
388
+ # TODO: Remove this once Agent is on the ORM
389
+ # TODO: To prevent unbounded growth
390
+ per_agent_lock_manager.clear_lock(agent_id)
391
+
387
392
  with self.session_maker() as session:
388
393
 
389
394
  # delete agents
letta/orm/block.py CHANGED
@@ -10,7 +10,7 @@ from letta.schemas.block import Block as PydanticBlock
10
10
  from letta.schemas.block import Human, Persona
11
11
 
12
12
  if TYPE_CHECKING:
13
- from letta.orm.organization import Organization
13
+ from letta.orm import BlocksAgents, Organization
14
14
 
15
15
 
16
16
  class Block(OrganizationMixin, SqlalchemyBase):
@@ -35,6 +35,7 @@ class Block(OrganizationMixin, SqlalchemyBase):
35
35
 
36
36
  # relationships
37
37
  organization: Mapped[Optional["Organization"]] = relationship("Organization")
38
+ blocks_agents: Mapped[list["BlocksAgents"]] = relationship("BlocksAgents", back_populates="block", cascade="all, delete")
38
39
 
39
40
  def to_pydantic(self) -> Type:
40
41
  match self.label:
@@ -1,5 +1,5 @@
1
1
  from sqlalchemy import ForeignKey, ForeignKeyConstraint, String, UniqueConstraint
2
- from sqlalchemy.orm import Mapped, mapped_column
2
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
3
3
 
4
4
  from letta.orm.sqlalchemy_base import SqlalchemyBase
5
5
  from letta.schemas.blocks_agents import BlocksAgents as PydanticBlocksAgents
@@ -27,3 +27,6 @@ class BlocksAgents(SqlalchemyBase):
27
27
  agent_id: Mapped[str] = mapped_column(String, ForeignKey("agents.id"), primary_key=True)
28
28
  block_id: Mapped[str] = mapped_column(String, primary_key=True)
29
29
  block_label: Mapped[str] = mapped_column(String, primary_key=True)
30
+
31
+ # relationships
32
+ block: Mapped["Block"] = relationship("Block", back_populates="blocks_agents")
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."""
@@ -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 NoResultFound
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
- with db_session as session:
107
- session.add(self)
108
- session.commit()
109
- session.refresh(self)
110
- return self
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,51 @@ 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
+ error_message = str(orig) if orig else str(e)
184
+ logger.info(f"Handling DBAPIError: {error_message}")
185
+
186
+ # Handle SQLite-specific errors
187
+ if "UNIQUE constraint failed" in error_message:
188
+ raise UniqueConstraintViolationError(
189
+ f"A unique constraint was violated for {cls.__name__}. Check your input for duplicates: {e}"
190
+ ) from e
191
+
192
+ if "FOREIGN KEY constraint failed" in error_message:
193
+ raise ForeignKeyConstraintViolationError(
194
+ f"A foreign key constraint was violated for {cls.__name__}. Check your input for missing or invalid references: {e}"
195
+ ) from e
196
+
197
+ # For psycopg2
198
+ if hasattr(orig, "pgcode"):
199
+ error_code = orig.pgcode
200
+ # For pg8000
201
+ elif hasattr(orig, "args") and len(orig.args) > 0:
202
+ # The first argument contains the error details as a dictionary
203
+ err_dict = orig.args[0]
204
+ if isinstance(err_dict, dict):
205
+ error_code = err_dict.get("C") # 'C' is the error code field
206
+ logger.info(f"Extracted error_code: {error_code}")
207
+
208
+ # Handle unique constraint violations
209
+ if error_code == "23505":
210
+ raise UniqueConstraintViolationError(
211
+ f"A unique constraint was violated for {cls.__name__}. Check your input for duplicates: {e}"
212
+ ) from e
213
+
214
+ # Handle foreign key violations
215
+ if error_code == "23503":
216
+ raise ForeignKeyConstraintViolationError(
217
+ f"A foreign key constraint was violated for {cls.__name__}. Check your input for missing or invalid references: {e}"
218
+ ) from e
219
+
220
+ # Re-raise for other unhandled DBAPI errors
221
+ raise
222
+
171
223
  @property
172
224
  def __pydantic_model__(self) -> Type["BaseModel"]:
173
225
  raise NotImplementedError("Sqlalchemy models must declare a __pydantic_model__ property to be convertable.")
letta/schemas/block.py CHANGED
@@ -30,7 +30,7 @@ class BaseBlock(LettaBase, validate_assignment=True):
30
30
 
31
31
  @model_validator(mode="after")
32
32
  def verify_char_limit(self) -> Self:
33
- if len(self.value) > self.limit:
33
+ if self.value and len(self.value) > self.limit:
34
34
  error_msg = f"Edit failed: Exceeds {self.limit} character limit (requested {len(self.value)}) - {str(self)}."
35
35
  raise ValueError(error_msg)
36
36
 
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.
@@ -475,19 +475,21 @@ async def send_message(
475
475
  """
476
476
  actor = server.get_user_or_default(user_id=user_id)
477
477
 
478
- result = await send_message_to_agent(
479
- server=server,
480
- agent_id=agent_id,
481
- user_id=actor.id,
482
- messages=request.messages,
483
- stream_steps=request.stream_steps,
484
- stream_tokens=request.stream_tokens,
485
- return_message_object=request.return_message_object,
486
- # Support for AssistantMessage
487
- use_assistant_message=request.use_assistant_message,
488
- assistant_message_function_name=request.assistant_message_function_name,
489
- assistant_message_function_kwarg=request.assistant_message_function_kwarg,
490
- )
478
+ agent_lock = server.per_agent_lock_manager.get_lock(agent_id)
479
+ async with agent_lock:
480
+ result = await send_message_to_agent(
481
+ server=server,
482
+ agent_id=agent_id,
483
+ user_id=actor.id,
484
+ messages=request.messages,
485
+ stream_steps=request.stream_steps,
486
+ stream_tokens=request.stream_tokens,
487
+ return_message_object=request.return_message_object,
488
+ # Support for AssistantMessage
489
+ use_assistant_message=request.use_assistant_message,
490
+ assistant_message_function_name=request.assistant_message_function_name,
491
+ assistant_message_function_kwarg=request.assistant_message_function_kwarg,
492
+ )
491
493
  return result
492
494
 
493
495
 
@@ -2,6 +2,8 @@ from typing import List, Optional
2
2
 
3
3
  from fastapi import APIRouter, Body, Depends, Header, HTTPException
4
4
 
5
+ from letta.errors import LettaToolCreateError
6
+ from letta.orm.errors import UniqueConstraintViolationError
5
7
  from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
6
8
  from letta.server.rest_api.utils import get_letta_server
7
9
  from letta.server.server import SyncServer
@@ -13,12 +15,13 @@ router = APIRouter(prefix="/tools", tags=["tools"])
13
15
  def delete_tool(
14
16
  tool_id: str,
15
17
  server: SyncServer = Depends(get_letta_server),
18
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
16
19
  ):
17
20
  """
18
21
  Delete a tool by name
19
22
  """
20
- # actor = server.get_user_or_default(user_id=user_id)
21
- server.tool_manager.delete_tool(tool_id=tool_id)
23
+ actor = server.get_user_or_default(user_id=user_id)
24
+ server.tool_manager.delete_tool_by_id(tool_id=tool_id, actor=actor)
22
25
 
23
26
 
24
27
  @router.get("/{tool_id}", response_model=Tool, operation_id="get_tool")
@@ -83,12 +86,50 @@ def create_tool(
83
86
  """
84
87
  Create a new tool
85
88
  """
86
- # Derive user and org id from actor
87
- actor = server.get_user_or_default(user_id=user_id)
89
+ try:
90
+ actor = server.get_user_or_default(user_id=user_id)
91
+ tool = Tool(**request.model_dump())
92
+ return server.tool_manager.create_tool(pydantic_tool=tool, actor=actor)
93
+ except UniqueConstraintViolationError as e:
94
+ # Log or print the full exception here for debugging
95
+ print(f"Error occurred: {e}")
96
+ clean_error_message = f"Tool with name {request.name} already exists."
97
+ raise HTTPException(status_code=409, detail=clean_error_message)
98
+ except LettaToolCreateError as e:
99
+ # HTTP 400 == Bad Request
100
+ print(f"Error occurred during tool creation: {e}")
101
+ # print the full stack trace
102
+ import traceback
103
+
104
+ print(traceback.format_exc())
105
+ raise HTTPException(status_code=400, detail=str(e))
106
+ except Exception as e:
107
+ # Catch other unexpected errors and raise an internal server error
108
+ print(f"Unexpected error occurred: {e}")
109
+ raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
110
+
88
111
 
89
- # Send request to create the tool
90
- tool = Tool(**request.model_dump())
91
- return server.tool_manager.create_or_update_tool(pydantic_tool=tool, actor=actor)
112
+ @router.put("/", response_model=Tool, operation_id="upsert_tool")
113
+ def upsert_tool(
114
+ request: ToolCreate = Body(...),
115
+ server: SyncServer = Depends(get_letta_server),
116
+ user_id: Optional[str] = Header(None, alias="user_id"),
117
+ ):
118
+ """
119
+ Create or update a tool
120
+ """
121
+ try:
122
+ actor = server.get_user_or_default(user_id=user_id)
123
+ tool = server.tool_manager.create_or_update_tool(pydantic_tool=Tool(**request.model_dump()), actor=actor)
124
+ return tool
125
+ except UniqueConstraintViolationError as e:
126
+ # Log the error and raise a conflict exception
127
+ print(f"Unique constraint violation occurred: {e}")
128
+ raise HTTPException(status_code=409, detail=str(e))
129
+ except Exception as e:
130
+ # Catch other unexpected errors and raise an internal server error
131
+ print(f"Unexpected error occurred: {e}")
132
+ raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
92
133
 
93
134
 
94
135
  @router.patch("/{tool_id}", response_model=Tool, operation_id="update_tool")
letta/server/server.py CHANGED
@@ -3,6 +3,7 @@ import os
3
3
  import traceback
4
4
  import warnings
5
5
  from abc import abstractmethod
6
+ from asyncio import Lock
6
7
  from datetime import datetime
7
8
  from typing import Callable, Dict, List, Optional, Tuple, Union
8
9
 
@@ -79,6 +80,7 @@ from letta.services.agents_tags_manager import AgentsTagsManager
79
80
  from letta.services.block_manager import BlockManager
80
81
  from letta.services.blocks_agents_manager import BlocksAgentsManager
81
82
  from letta.services.organization_manager import OrganizationManager
83
+ from letta.services.per_agent_lock_manager import PerAgentLockManager
82
84
  from letta.services.sandbox_config_manager import SandboxConfigManager
83
85
  from letta.services.source_manager import SourceManager
84
86
  from letta.services.tool_manager import ToolManager
@@ -231,6 +233,9 @@ class SyncServer(Server):
231
233
 
232
234
  self.credentials = LettaCredentials.load()
233
235
 
236
+ # Locks
237
+ self.send_message_lock = Lock()
238
+
234
239
  # Initialize the metadata store
235
240
  config = LettaConfig.load()
236
241
  if settings.letta_pg_uri_no_default:
@@ -252,6 +257,9 @@ class SyncServer(Server):
252
257
  self.blocks_agents_manager = BlocksAgentsManager()
253
258
  self.sandbox_config_manager = SandboxConfigManager(tool_settings)
254
259
 
260
+ # Managers that interface with parallelism
261
+ self.per_agent_lock_manager = PerAgentLockManager()
262
+
255
263
  # Make default user and org
256
264
  if init_with_default_org_and_user:
257
265
  self.default_org = self.organization_manager.create_default_organization()
@@ -925,7 +933,7 @@ class SyncServer(Server):
925
933
  logger.exception(e)
926
934
  try:
927
935
  if agent:
928
- self.ms.delete_agent(agent_id=agent.agent_state.id)
936
+ self.ms.delete_agent(agent_id=agent.agent_state.id, per_agent_lock_manager=self.per_agent_lock_manager)
929
937
  except Exception as delete_e:
930
938
  logger.exception(f"Failed to delete_agent:\n{delete_e}")
931
939
  raise e
@@ -1522,7 +1530,7 @@ class SyncServer(Server):
1522
1530
 
1523
1531
  # Next, attempt to delete it from the actual database
1524
1532
  try:
1525
- self.ms.delete_agent(agent_id=agent_id)
1533
+ self.ms.delete_agent(agent_id=agent_id, per_agent_lock_manager=self.per_agent_lock_manager)
1526
1534
  except Exception as e:
1527
1535
  logger.exception(f"Failed to delete agent {agent_id} via ID with:\n{str(e)}")
1528
1536
  raise ValueError(f"Failed to delete agent {agent_id} in database")
@@ -7,6 +7,7 @@ from letta.schemas.block import Block
7
7
  from letta.schemas.block import Block as PydanticBlock
8
8
  from letta.schemas.block import BlockUpdate, Human, Persona
9
9
  from letta.schemas.user import User as PydanticUser
10
+ from letta.services.blocks_agents_manager import BlocksAgentsManager
10
11
  from letta.utils import enforce_types, list_human_files, list_persona_files
11
12
 
12
13
 
@@ -38,13 +39,28 @@ class BlockManager:
38
39
  @enforce_types
39
40
  def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock:
40
41
  """Update a block by its ID with the given BlockUpdate object."""
42
+ # TODO: REMOVE THIS ONCE AGENT IS ON ORM -> Update blocks_agents
43
+ blocks_agents_manager = BlocksAgentsManager()
44
+ agent_ids = []
45
+ if block_update.label:
46
+ agent_ids = blocks_agents_manager.list_agent_ids_with_block(block_id=block_id)
47
+ for agent_id in agent_ids:
48
+ blocks_agents_manager.remove_block_with_id_from_agent(agent_id=agent_id, block_id=block_id)
49
+
41
50
  with self.session_maker() as session:
51
+ # Update block
42
52
  block = BlockModel.read(db_session=session, identifier=block_id, actor=actor)
43
53
  update_data = block_update.model_dump(exclude_unset=True, exclude_none=True)
44
54
  for key, value in update_data.items():
45
55
  setattr(block, key, value)
46
56
  block.update(db_session=session, actor=actor)
47
- return block.to_pydantic()
57
+
58
+ # TODO: REMOVE THIS ONCE AGENT IS ON ORM -> Update blocks_agents
59
+ if block_update.label:
60
+ for agent_id in agent_ids:
61
+ blocks_agents_manager.add_block_to_agent(agent_id=agent_id, block_id=block_id, block_label=block_update.label)
62
+
63
+ return block.to_pydantic()
48
64
 
49
65
  @enforce_types
50
66
  def delete_block(self, block_id: str, actor: PydanticUser) -> PydanticBlock:
@@ -71,11 +71,18 @@ class BlocksAgentsManager:
71
71
 
72
72
  @enforce_types
73
73
  def list_block_ids_for_agent(self, agent_id: str) -> List[str]:
74
- """List all blocks associated with a specific agent."""
74
+ """List all block ids associated with a specific agent."""
75
75
  with self.session_maker() as session:
76
76
  blocks_agents_record = BlocksAgentsModel.list(db_session=session, agent_id=agent_id)
77
77
  return [record.block_id for record in blocks_agents_record]
78
78
 
79
+ @enforce_types
80
+ def list_block_labels_for_agent(self, agent_id: str) -> List[str]:
81
+ """List all block labels associated with a specific agent."""
82
+ with self.session_maker() as session:
83
+ blocks_agents_record = BlocksAgentsModel.list(db_session=session, agent_id=agent_id)
84
+ return [record.block_label for record in blocks_agents_record]
85
+
79
86
  @enforce_types
80
87
  def list_agent_ids_with_block(self, block_id: str) -> List[str]:
81
88
  """List all agents associated with a specific block."""
@@ -0,0 +1,18 @@
1
+ import asyncio
2
+ from collections import defaultdict
3
+
4
+
5
+ class PerAgentLockManager:
6
+ """Manages per-agent locks."""
7
+
8
+ def __init__(self):
9
+ self.locks = defaultdict(asyncio.Lock)
10
+
11
+ def get_lock(self, agent_id: str) -> asyncio.Lock:
12
+ """Retrieve the lock for a specific agent_id."""
13
+ return self.locks[agent_id]
14
+
15
+ def clear_lock(self, agent_id: str):
16
+ """Optionally remove a lock if no longer needed (to prevent unbounded growth)."""
17
+ if agent_id in self.locks:
18
+ del self.locks[agent_id]
@@ -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
- derived_json_schema = pydantic_tool.json_schema or derive_openai_json_schema(source_code=pydantic_tool.source_code)
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
- # The description is most likely auto-generated via the json_schema,
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
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.5.4.dev20241125104219
3
+ Version: 0.5.4.dev20241127104220
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -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=fydftv5wyhc0mg7EksYCxjxXMVsN_nipk94YoMYXSvk,78656
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=D-CpSZI1cDvdSQr3-zhGuDrJnZo1Ko7bi_wuxcBxxqo,8555
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=Ds0ELRyeJRfivpqS7N5h4HJosX0nZb8C-qsrS75QkpM,115188
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
@@ -22,13 +22,13 @@ letta/credentials.py,sha256=D9mlcPsdDWlIIXQQD8wSPE9M_QvsRrb0p3LB5i9OF5Q,5806
22
22
  letta/data_sources/connectors.py,sha256=5VKxfeV-QyUlK1wexLlpgar99dGm6PHxFaEbSeByo_U,9923
23
23
  letta/data_sources/connectors_helper.py,sha256=2TQjCt74fCgT5sw1AP8PalDEk06jPBbhrPG4HVr-WLs,3371
24
24
  letta/embeddings.py,sha256=3vJaQ8RMLLp6yiYZGsthq6Xsu4keb9gp7DYN_2GPBFU,8459
25
- letta/errors.py,sha256=cDOo4cSYL-LA0w0b0GdsxXd5k2I1LLOY8nhtXk9YqYs,2875
25
+ letta/errors.py,sha256=mFeTpZP37otDMr68s9hyGOnafJPrWeblQOI79cgP4nQ,3209
26
26
  letta/functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  letta/functions/function_sets/base.py,sha256=N4QmOjL6gDEyOg67ocF6zVKM-NquTo-yXG_T8r18buA,6440
28
28
  letta/functions/function_sets/extras.py,sha256=Jik3UiDqYTm4Lam1XPTvuVjvgUHwIAhopsnbmVhGMBg,4732
29
- letta/functions/functions.py,sha256=VyA_7O56KRUj88iuMkLJTRfascaTCj1qFGT0BnDgC6k,4140
29
+ letta/functions/functions.py,sha256=7FankCCKpUgBUnrzmF96sRy8fHyfkfYFFJ_f45MsZh0,4196
30
30
  letta/functions/helpers.py,sha256=K84kqAN1RXZIhjb7-btS0C2p-SInYNv6FvSfo-16Y6g,8578
31
- letta/functions/schema_generator.py,sha256=xkHpyXCidiqvdx6isWhSZNBQjziyGLBNzu9DgslJk6k,9030
31
+ letta/functions/schema_generator.py,sha256=PIuey3ZsMvOTBViPWlqajleu48LOEEkcxnLhRPkyRsE,9076
32
32
  letta/helpers/__init__.py,sha256=p0luQ1Oe3Skc6sH4O58aHHA3Qbkyjifpuq0DZ1GAY0U,59
33
33
  letta/helpers/tool_rule_solver.py,sha256=AZiUjW_oDlQx5uMI7oaL50KkI1InTW8qRkFdg6S54RQ,4744
34
34
  letta/humans/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -85,7 +85,7 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
85
85
  letta/log.py,sha256=FxkAk2f8Bl-u9dfImSj1DYnjAsmV6PL3tjTSnEiNP48,2218
86
86
  letta/main.py,sha256=cFnjnbzyrRRM5sZeRAnGVq_rPIgJRHRFyFNCY--sRI4,19163
87
87
  letta/memory.py,sha256=YupXOvzVJXH59RW4XWBrd7qMNEYaMbtWXCheKeWZwpU,17873
88
- letta/metadata.py,sha256=jcvkzrCfSlmUbv5puCbeFP6cqM-Ct_U49C5WFLpYJ0s,18180
88
+ letta/metadata.py,sha256=c_jw9eFDoGLborQ-oS4Bm9uQ59p3QbZHEbCs-pA8C_E,18445
89
89
  letta/o1_agent.py,sha256=qYyAdLnKu7dQw6OxxsFQz32d-lLJLo64MnH165oQm7s,3180
90
90
  letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
@@ -93,16 +93,16 @@ letta/orm/__all__.py,sha256=2gh2MZTkA3Hw67VWVKK3JIStJOqTeLdpCvYSVYNeEDA,692
93
93
  letta/orm/__init__.py,sha256=3n3rOtlDySptVPM3H1LpT3Ey5mdvQZjK0V0TJwDmaoI,382
94
94
  letta/orm/agents_tags.py,sha256=Qa7Yt9imL8xbGP57fflccAMy7Z32CQiU_7eZKSSPngc,1119
95
95
  letta/orm/base.py,sha256=K_LpNUURbsj44ycHbzvNXG_n8pBOjf1YvDaikIPDpQA,2716
96
- letta/orm/block.py,sha256=ZD2LdESD2KH2fY1_W4mZKtKQnFYlo_DbjVJZLaNo7xI,2229
97
- letta/orm/blocks_agents.py,sha256=tFeQ5OzehyZi9Qafvecr8W0v6EkJ48PqvR3rEjBzJ5c,1046
96
+ letta/orm/block.py,sha256=xymYeCTJJFkzADW6wjfP2LXNZZN9yg4mCSybbvEEMMM,2356
97
+ letta/orm/blocks_agents.py,sha256=o6cfblODja7so4444npW0vusqKcvDPp8YJdsWsOePus,1164
98
98
  letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
99
- letta/orm/errors.py,sha256=somsGtotFlb3SDM6tKdZ5TDGwEEP3ppx47ICAvNMnkg,225
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=63o9puvIBicI6AvOqiRB6EERvo0q1KVCnMLGLvrpMC0,7768
105
+ letta/orm/sqlalchemy_base.py,sha256=PGkbEb7nz3aNMScQEu55KXxE48Xw7DA01tV9famFmO0,9980
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
@@ -133,7 +133,7 @@ letta/pytest.ini,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
133
133
  letta/schemas/agent.py,sha256=f0khTBWIRGZva4_C15Nm_tkmn1cwaVQlWa7_7laRbEE,7866
134
134
  letta/schemas/agents_tags.py,sha256=9DGr8fN2DHYdWvZ_qcXmrKI0w7YKCGz2lfEcrX2KAkI,1130
135
135
  letta/schemas/api_key.py,sha256=u07yzzMn-hBAHZIIKbWY16KsgiFjSNR8lAghpMUo3_4,682
136
- letta/schemas/block.py,sha256=4xYoyfLezvyjA7TN3c5AvPCyRh_Pz0_XVqVCwIaXoTo,4758
136
+ letta/schemas/block.py,sha256=9iDBuWpjo0Jla_YjbtURkyq2XiWKu9Ee6g0cYfxbrPE,4773
137
137
  letta/schemas/blocks_agents.py,sha256=31KoTqQ9cGvqxqmuagtXiQLoaHLq7DlyGdjJTGncxTs,1333
138
138
  letta/schemas/embedding_config.py,sha256=1kD6NpiXeH4roVumxqDAKk7xt8SpXGWNhZs_XXUSlEU,2855
139
139
  letta/schemas/enums.py,sha256=WfRYpLh_pD-VyhEnp3Y6pPfx063zq2o4jky6PulqO8w,629
@@ -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=Ehdl4fB1tLG-kgUTVD7MAhvSs_H_Tim2aROu0S0diB8,8083
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
@@ -178,7 +178,7 @@ letta/server/rest_api/routers/openai/assistants/threads.py,sha256=WXVGBaBvSNPB7Z
178
178
  letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
179
179
  letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=-uye6cm4SnoQGwxhr1N1FrSXOlnO2Hvbfj6k8JSc45k,4918
180
180
  letta/server/rest_api/routers/v1/__init__.py,sha256=RZc0fIHNN4BGretjU6_TGK7q49RyV4jfYNudoiK_sUo,762
181
- letta/server/rest_api/routers/v1/agents.py,sha256=yKpbqQauHHbvuV8IWgAPgri0lPmr6EWbYUx_C64RBGA,25541
181
+ letta/server/rest_api/routers/v1/agents.py,sha256=dTuKFbQKWDzQ4QnyjmMZagCkFsoBcb1NUAjFeobM0yo,25686
182
182
  letta/server/rest_api/routers/v1/blocks.py,sha256=ogJdn-Ir7h1ZEv28bHtUNNsR2zq9-wxXAMpu2t1EoIA,2946
183
183
  letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
184
184
  letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
@@ -186,11 +186,11 @@ 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=iefxuMHkTVW5RiiYYONVacI8SPjmuKBOIXzJQJhLMaM,4236
189
+ letta/server/rest_api/routers/v1/tools.py,sha256=eDzRv7ETP8Tu0kPxFutLC1Lz4J411sHOqbHF9nD5MLs,6204
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
193
- letta/server/server.py,sha256=65jq0QyuKy5yPrtBklAOloApLi7WDJML1zPpJI4vl9w,84080
193
+ letta/server/server.py,sha256=I3IykEt5b_GVbqOMGiEoWDOREH3AtSDtP58ARPiiOIE,84448
194
194
  letta/server/startup.sh,sha256=wTOQOJJZw_Iec57WIu0UW0AVflk0ZMWYZWg8D3T_gSQ,698
195
195
  letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
196
196
  letta/server/static_files/assets/index-9fa459a2.js,sha256=j2oMcDJO9dWJaH5e-tsflbVpWK20gLWpZKJk4-Kuy6A,1815592
@@ -205,13 +205,14 @@ letta/server/ws_api/protocol.py,sha256=M_-gM5iuDBwa1cuN2IGNCG5GxMJwU2d3XW93XALv9
205
205
  letta/server/ws_api/server.py,sha256=C2Kv48PCwl46DQFb0ZP30s86KJLQ6dZk2AhWQEZn9pY,6004
206
206
  letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
207
  letta/services/agents_tags_manager.py,sha256=zNqeXDpaf4dQ77jrRHiQfITdk4FawBzcND-9tWrj8gw,3127
208
- letta/services/block_manager.py,sha256=4RMkUgpiEZiyzujC8a-_EOkw8CFeA-3m3t-3PB0GwPY,4513
209
- letta/services/blocks_agents_manager.py,sha256=XlZrVWRuJEdoLhoz692lTbYl77hZaUF2ZFhkU3Z58aw,4492
208
+ letta/services/block_manager.py,sha256=tC1FvHXLA8BJ33uxGW3D5rqEa2jjbxMMW9GQLVlDKm4,5297
209
+ letta/services/blocks_agents_manager.py,sha256=_LaI6XjFUarGjOB8afRFY2pZjjD5pYCVHB9AuXh6eiI,4874
210
210
  letta/services/organization_manager.py,sha256=OfE2_NMmhqXURX4sg7hCOiFQVQpV5ZiPu7J3sboCSYc,3555
211
+ letta/services/per_agent_lock_manager.py,sha256=02iw5e-xoLiKGqqn2KdJdk-QlrDHPz5oMuEs1ibwXHA,540
211
212
  letta/services/sandbox_config_manager.py,sha256=9BCu59nHR4nIMFXgFyEMOY2UTmZvBMS3GlDBWWCHB4I,12648
212
213
  letta/services/source_manager.py,sha256=StX5Wfd7XSCKJet8qExIu3GMoI-eMIbEarAeTv2gq0s,6555
213
214
  letta/services/tool_execution_sandbox.py,sha256=6PX9uKt_si3cqpRjwOm8Hyn9PfLNn0k7AwKoTTtz6qs,13128
214
- letta/services/tool_manager.py,sha256=zCvIAHLS4QculmYid-25G7OEZT8MaBRsQdh_voyKUVw,8077
215
+ letta/services/tool_manager.py,sha256=wzk5hoVdH3FYdysSLdqBeRpbUiE2-NbfDjmqIsUUC9M,7730
215
216
  letta/services/tool_sandbox_env/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
217
  letta/services/user_manager.py,sha256=UJa0hqCjz0yXtvrCR8OVBqlSR5lC_Ejn-uG__58zLds,4398
217
218
  letta/settings.py,sha256=ZcUcwvl7hStawZ0JOA0133jNk3j5qBd7qlFAAaIPsU8,3608
@@ -219,8 +220,8 @@ letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,
219
220
  letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
220
221
  letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
221
222
  letta/utils.py,sha256=COwQLAt02eEM9tjp6p5kN8YeTqGXr714l5BvffLVCLU,32376
222
- letta_nightly-0.5.4.dev20241125104219.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
223
- letta_nightly-0.5.4.dev20241125104219.dist-info/METADATA,sha256=bQcE4fB8cNS_SAqyDIUCkGWsy4ty7UuEwJKPr3DK1e8,11515
224
- letta_nightly-0.5.4.dev20241125104219.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
225
- letta_nightly-0.5.4.dev20241125104219.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
226
- letta_nightly-0.5.4.dev20241125104219.dist-info/RECORD,,
223
+ letta_nightly-0.5.4.dev20241127104220.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
224
+ letta_nightly-0.5.4.dev20241127104220.dist-info/METADATA,sha256=4_Sc3YHHXY2JS8n9geT4tEoT8sfCRrenjPeDkHt_u5o,11515
225
+ letta_nightly-0.5.4.dev20241127104220.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
226
+ letta_nightly-0.5.4.dev20241127104220.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
227
+ letta_nightly-0.5.4.dev20241127104220.dist-info/RECORD,,