letta-nightly 0.5.4.dev20241121104201__py3-none-any.whl → 0.5.4.dev20241122104229__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
@@ -1208,6 +1208,30 @@ class Agent(BaseAgent):
1208
1208
  new_messages = [new_system_message_obj] + self._messages[1:] # swap index 0 (system)
1209
1209
  self._messages = new_messages
1210
1210
 
1211
+ def update_memory_blocks_from_db(self):
1212
+ for block in self.memory.to_dict()["memory"].values():
1213
+ if block.get("templates", False):
1214
+ # we don't expect to update shared memory blocks that
1215
+ # are templates. this is something we could update in the
1216
+ # future if we expect templates to change often.
1217
+ continue
1218
+ block_id = block.get("id")
1219
+
1220
+ # TODO: This is really hacky and we should probably figure out how to
1221
+ db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
1222
+ if db_block is None:
1223
+ # this case covers if someone has deleted a shared block by interacting
1224
+ # with some other agent.
1225
+ # in that case we should remove this shared block from the agent currently being
1226
+ # evaluated.
1227
+ printd(f"removing block: {block_id=}")
1228
+ continue
1229
+ if not isinstance(db_block.value, str):
1230
+ printd(f"skipping block update, unexpected value: {block_id=}")
1231
+ continue
1232
+ # TODO: we may want to update which columns we're updating from shared memory e.g. the limit
1233
+ self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
1234
+
1211
1235
  def rebuild_memory(self, force=False, update_timestamp=True, ms: Optional[MetadataStore] = None):
1212
1236
  """Rebuilds the system message with the latest memory object and any shared memory block updates"""
1213
1237
  curr_system_message = self.messages[0] # this is the system + memory bank, not just the system prompt
@@ -1219,28 +1243,7 @@ class Agent(BaseAgent):
1219
1243
  return
1220
1244
 
1221
1245
  if ms:
1222
- for block in self.memory.to_dict()["memory"].values():
1223
- if block.get("templates", False):
1224
- # we don't expect to update shared memory blocks that
1225
- # are templates. this is something we could update in the
1226
- # future if we expect templates to change often.
1227
- continue
1228
- block_id = block.get("id")
1229
-
1230
- # TODO: This is really hacky and we should probably figure out how to
1231
- db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
1232
- if db_block is None:
1233
- # this case covers if someone has deleted a shared block by interacting
1234
- # with some other agent.
1235
- # in that case we should remove this shared block from the agent currently being
1236
- # evaluated.
1237
- printd(f"removing block: {block_id=}")
1238
- continue
1239
- if not isinstance(db_block.value, str):
1240
- printd(f"skipping block update, unexpected value: {block_id=}")
1241
- continue
1242
- # TODO: we may want to update which columns we're updating from shared memory e.g. the limit
1243
- self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
1246
+ self.update_memory_blocks_from_db()
1244
1247
 
1245
1248
  # If the memory didn't update, we probably don't want to update the timestamp inside
1246
1249
  # For example, if we're doing a system prompt swap, this should probably be False
letta/client/client.py CHANGED
@@ -190,9 +190,6 @@ class AbstractClient(object):
190
190
  def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
191
191
  raise NotImplementedError
192
192
 
193
- def load_crewai_tool(self, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
194
- raise NotImplementedError
195
-
196
193
  def load_composio_tool(self, action: "ActionType") -> Tool:
197
194
  raise NotImplementedError
198
195
 
@@ -899,8 +896,8 @@ class RESTClient(AbstractClient):
899
896
  else:
900
897
  return Block(**response.json())
901
898
 
902
- def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
903
- request = BlockUpdate(id=block_id, template_name=name, value=text)
899
+ def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
900
+ request = BlockUpdate(id=block_id, template_name=name, value=text, limit=limit if limit else self.get_block(block_id).limit)
904
901
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
905
902
  if response.status_code != 200:
906
903
  raise ValueError(f"Failed to update block: {response.text}")
@@ -1568,6 +1565,53 @@ class RESTClient(AbstractClient):
1568
1565
  # Parse and return the deleted organization
1569
1566
  return Organization(**response.json())
1570
1567
 
1568
+ def update_agent_memory_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
1569
+
1570
+ # @router.patch("/{agent_id}/memory/label", response_model=Memory, operation_id="update_agent_memory_label")
1571
+ response = requests.patch(
1572
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/label",
1573
+ headers=self.headers,
1574
+ json={"current_label": current_label, "new_label": new_label},
1575
+ )
1576
+ if response.status_code != 200:
1577
+ raise ValueError(f"Failed to update agent memory label: {response.text}")
1578
+ return Memory(**response.json())
1579
+
1580
+ def add_agent_memory_block(self, agent_id: str, create_block: BlockCreate) -> Memory:
1581
+
1582
+ # @router.post("/{agent_id}/memory/block", response_model=Memory, operation_id="add_agent_memory_block")
1583
+ response = requests.post(
1584
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block",
1585
+ headers=self.headers,
1586
+ json=create_block.model_dump(),
1587
+ )
1588
+ if response.status_code != 200:
1589
+ raise ValueError(f"Failed to add agent memory block: {response.text}")
1590
+ return Memory(**response.json())
1591
+
1592
+ def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
1593
+
1594
+ # @router.delete("/{agent_id}/memory/block/{block_label}", response_model=Memory, operation_id="remove_agent_memory_block")
1595
+ response = requests.delete(
1596
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block/{block_label}",
1597
+ headers=self.headers,
1598
+ )
1599
+ if response.status_code != 200:
1600
+ raise ValueError(f"Failed to remove agent memory block: {response.text}")
1601
+ return Memory(**response.json())
1602
+
1603
+ def update_agent_memory_limit(self, agent_id: str, block_label: str, limit: int) -> Memory:
1604
+
1605
+ # @router.patch("/{agent_id}/memory/limit", response_model=Memory, operation_id="update_agent_memory_limit")
1606
+ response = requests.patch(
1607
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/limit",
1608
+ headers=self.headers,
1609
+ json={"label": block_label, "limit": limit},
1610
+ )
1611
+ if response.status_code != 200:
1612
+ raise ValueError(f"Failed to update agent memory limit: {response.text}")
1613
+ return Memory(**response.json())
1614
+
1571
1615
 
1572
1616
  class LocalClient(AbstractClient):
1573
1617
  """
@@ -2690,7 +2734,7 @@ class LocalClient(AbstractClient):
2690
2734
  Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
2691
2735
  )
2692
2736
 
2693
- def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
2737
+ def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
2694
2738
  """
2695
2739
  Update a block
2696
2740
 
@@ -2703,7 +2747,9 @@ class LocalClient(AbstractClient):
2703
2747
  block (Block): Updated block
2704
2748
  """
2705
2749
  return self.server.block_manager.update_block(
2706
- block_id=block_id, block_update=BlockUpdate(template_name=name, value=text), actor=self.user
2750
+ block_id=block_id,
2751
+ block_update=BlockUpdate(template_name=name, value=text, limit=limit if limit else self.get_block(block_id).limit),
2752
+ actor=self.user,
2707
2753
  )
2708
2754
 
2709
2755
  def get_block(self, block_id: str) -> Block:
@@ -2774,3 +2820,21 @@ class LocalClient(AbstractClient):
2774
2820
 
2775
2821
  def delete_org(self, org_id: str) -> Organization:
2776
2822
  return self.server.organization_manager.delete_organization_by_id(org_id=org_id)
2823
+
2824
+ def update_agent_memory_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
2825
+ return self.server.update_agent_memory_label(
2826
+ user_id=self.user_id, agent_id=agent_id, current_block_label=current_label, new_block_label=new_label
2827
+ )
2828
+
2829
+ def add_agent_memory_block(self, agent_id: str, create_block: BlockCreate) -> Memory:
2830
+ block_req = Block(**create_block.model_dump())
2831
+ block = self.server.block_manager.create_or_update_block(actor=self.user, block=block_req)
2832
+ # Link the block to the agent
2833
+ updated_memory = self.server.link_block_to_agent_memory(user_id=self.user_id, agent_id=agent_id, block_id=block.id)
2834
+ return updated_memory
2835
+
2836
+ def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
2837
+ return self.server.unlink_block_from_agent_memory(user_id=self.user_id, agent_id=agent_id, block_label=block_label)
2838
+
2839
+ def update_agent_memory_limit(self, agent_id: str, block_label: str, limit: int) -> Memory:
2840
+ return self.server.update_agent_memory_limit(user_id=self.user_id, agent_id=agent_id, block_label=block_label, limit=limit)
@@ -61,36 +61,6 @@ def {func_name}(**kwargs):
61
61
  return func_name, wrapper_function_str
62
62
 
63
63
 
64
- def generate_crewai_tool_wrapper(tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> tuple[str, str]:
65
- tool_name = tool.__class__.__name__
66
- import_statement = f"from crewai_tools import {tool_name}"
67
- extra_module_imports = generate_import_code(additional_imports_module_attr_map)
68
-
69
- # Safety check that user has passed in all required imports:
70
- assert_all_classes_are_imported(tool, additional_imports_module_attr_map)
71
-
72
- tool_instantiation = f"tool = {generate_imported_tool_instantiation_call_str(tool)}"
73
- run_call = f"return tool._run(**kwargs)"
74
- func_name = humps.decamelize(tool_name)
75
-
76
- # Combine all parts into the wrapper function
77
- wrapper_function_str = f"""
78
- def {func_name}(**kwargs):
79
- if 'self' in kwargs:
80
- del kwargs['self']
81
- import importlib
82
- {import_statement}
83
- {extra_module_imports}
84
- {tool_instantiation}
85
- {run_call}
86
- """
87
-
88
- # Compile safety check
89
- assert_code_gen_compilable(wrapper_function_str)
90
-
91
- return func_name, wrapper_function_str
92
-
93
-
94
64
  def assert_code_gen_compilable(code_str):
95
65
  try:
96
66
  compile(code_str, "<string>", "exec")
@@ -98,9 +68,7 @@ def assert_code_gen_compilable(code_str):
98
68
  print(f"Syntax error in code: {e}")
99
69
 
100
70
 
101
- def assert_all_classes_are_imported(
102
- tool: Union["LangChainBaseTool", "CrewAIBaseTool"], additional_imports_module_attr_map: dict[str, str]
103
- ) -> None:
71
+ def assert_all_classes_are_imported(tool: Union["LangChainBaseTool"], additional_imports_module_attr_map: dict[str, str]) -> None:
104
72
  # Safety check that user has passed in all required imports:
105
73
  tool_name = tool.__class__.__name__
106
74
  current_class_imports = {tool_name}
@@ -114,7 +82,7 @@ def assert_all_classes_are_imported(
114
82
  raise RuntimeError(err_msg)
115
83
 
116
84
 
117
- def find_required_class_names_for_import(obj: Union["LangChainBaseTool", "CrewAIBaseTool", BaseModel]) -> list[str]:
85
+ def find_required_class_names_for_import(obj: Union["LangChainBaseTool", BaseModel]) -> list[str]:
118
86
  """
119
87
  Finds all the class names for required imports when instantiating the `obj`.
120
88
  NOTE: This does not return the full import path, only the class name.
@@ -202,10 +170,10 @@ def generate_imported_tool_instantiation_call_str(obj: Any) -> Optional[str]:
202
170
  else:
203
171
  # Otherwise, if it is none of the above, that usually means it is a custom Python class that is NOT a BaseModel
204
172
  # Thus, we cannot get enough information about it to stringify it
205
- # This may cause issues, but we are making the assumption that any of these custom Python types are handled correctly by the parent library, such as LangChain or CrewAI
173
+ # This may cause issues, but we are making the assumption that any of these custom Python types are handled correctly by the parent library, such as LangChain
206
174
  # An example would be that WikipediaAPIWrapper has an argument that is a wikipedia (pip install wikipedia) object
207
175
  # We cannot stringify this easily, but WikipediaAPIWrapper handles the setting of this parameter internally
208
- # This assumption seems fair to me, since usually they are external imports, and LangChain and CrewAI should be bundling those as module-level imports within the tool
176
+ # This assumption seems fair to me, since usually they are external imports, and LangChain should be bundling those as module-level imports within the tool
209
177
  # We throw a warning here anyway and provide the class name
210
178
  print(
211
179
  f"[WARNING] Skipping parsing unknown class {obj.__class__.__name__} (does not inherit from the Pydantic BaseModel and is not a basic Python type)"
@@ -219,10 +187,9 @@ def generate_imported_tool_instantiation_call_str(obj: Any) -> Optional[str]:
219
187
 
220
188
 
221
189
  def is_base_model(obj: Any):
222
- from crewai_tools.tools.base_tool import BaseModel as CrewAiBaseModel
223
190
  from langchain_core.pydantic_v1 import BaseModel as LangChainBaseModel
224
191
 
225
- return isinstance(obj, BaseModel) or isinstance(obj, LangChainBaseModel) or isinstance(obj, CrewAiBaseModel)
192
+ return isinstance(obj, BaseModel) or isinstance(obj, LangChainBaseModel)
226
193
 
227
194
 
228
195
  def generate_import_code(module_attr_map: Optional[dict]):
letta/metadata.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import secrets
5
+ import warnings
5
6
  from typing import List, Optional
6
7
 
7
8
  from sqlalchemy import JSON, Column, DateTime, Index, String, TypeDecorator
@@ -353,8 +354,14 @@ class MetadataStore:
353
354
  raise ValueError(f"Agent with name {agent.name} already exists")
354
355
  fields = vars(agent)
355
356
  fields["memory"] = agent.memory.to_dict()
356
- del fields["_internal_memory"]
357
- del fields["tags"]
357
+ if "_internal_memory" in fields:
358
+ del fields["_internal_memory"]
359
+ else:
360
+ warnings.warn(f"Agent {agent.id} has no _internal_memory field")
361
+ if "tags" in fields:
362
+ del fields["tags"]
363
+ else:
364
+ warnings.warn(f"Agent {agent.id} has no tags field")
358
365
  session.add(AgentModel(**fields))
359
366
  session.commit()
360
367
 
@@ -364,8 +371,14 @@ class MetadataStore:
364
371
  fields = vars(agent)
365
372
  if isinstance(agent.memory, Memory): # TODO: this is nasty but this whole class will soon be removed so whatever
366
373
  fields["memory"] = agent.memory.to_dict()
367
- del fields["_internal_memory"]
368
- del fields["tags"]
374
+ if "_internal_memory" in fields:
375
+ del fields["_internal_memory"]
376
+ else:
377
+ warnings.warn(f"Agent {agent.id} has no _internal_memory field")
378
+ if "tags" in fields:
379
+ del fields["tags"]
380
+ else:
381
+ warnings.warn(f"Agent {agent.id} has no tags field")
369
382
  session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
370
383
  session.commit()
371
384
 
letta/schemas/block.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from pydantic import Field, model_validator
3
+ from pydantic import BaseModel, Field, model_validator
4
4
  from typing_extensions import Self
5
5
 
6
6
  from letta.schemas.letta_base import LettaBase
@@ -95,6 +95,13 @@ class BlockCreate(BaseBlock):
95
95
  label: str = Field(..., description="Label of the block.")
96
96
 
97
97
 
98
+ class BlockLabelUpdate(BaseModel):
99
+ """Update the label of a block"""
100
+
101
+ current_label: str = Field(..., description="Current label of the block.")
102
+ new_label: str = Field(..., description="New label of the block.")
103
+
104
+
98
105
  class CreatePersona(BlockCreate):
99
106
  """Create a persona block"""
100
107
 
@@ -117,6 +124,13 @@ class BlockUpdate(BaseBlock):
117
124
  extra = "ignore" # Ignores extra fields
118
125
 
119
126
 
127
+ class BlockLimitUpdate(BaseModel):
128
+ """Update the limit of a block"""
129
+
130
+ label: str = Field(..., description="Label of the block.")
131
+ limit: int = Field(..., description="New limit of the block.")
132
+
133
+
120
134
  class UpdatePersona(BlockUpdate):
121
135
  """Update a persona block"""
122
136
 
letta/schemas/memory.py CHANGED
@@ -158,6 +158,13 @@ class Memory(BaseModel, validate_assignment=True):
158
158
 
159
159
  self.memory[block.label] = block
160
160
 
161
+ def unlink_block(self, block_label: str) -> Block:
162
+ """Unlink a block from the memory object"""
163
+ if block_label not in self.memory:
164
+ raise ValueError(f"Block with label {block_label} does not exist")
165
+
166
+ return self.memory.pop(block_label)
167
+
161
168
  def update_block_value(self, label: str, value: str):
162
169
  """Update the value of a block"""
163
170
  if label not in self.memory:
@@ -167,6 +174,32 @@ class Memory(BaseModel, validate_assignment=True):
167
174
 
168
175
  self.memory[label].value = value
169
176
 
177
+ def update_block_label(self, current_label: str, new_label: str):
178
+ """Update the label of a block"""
179
+ if current_label not in self.memory:
180
+ raise ValueError(f"Block with label {current_label} does not exist")
181
+ if not isinstance(new_label, str):
182
+ raise ValueError(f"Provided new label must be a string")
183
+
184
+ # First change the label of the block
185
+ self.memory[current_label].label = new_label
186
+
187
+ # Then swap the block to the new label
188
+ self.memory[new_label] = self.memory.pop(current_label)
189
+
190
+ def update_block_limit(self, label: str, limit: int):
191
+ """Update the limit of a block"""
192
+ if label not in self.memory:
193
+ raise ValueError(f"Block with label {label} does not exist")
194
+ if not isinstance(limit, int):
195
+ raise ValueError(f"Provided limit must be an integer")
196
+
197
+ # Check to make sure the new limit is greater than the current length of the block
198
+ if len(self.memory[label].value) > limit:
199
+ raise ValueError(f"New limit {limit} is less than the current length of the block {len(self.memory[label].value)}")
200
+
201
+ self.memory[label].limit = limit
202
+
170
203
 
171
204
  # TODO: ideally this is refactored into ChatMemory and the subclasses are given more specific names.
172
205
  class BasicBlockMemory(Memory):
letta/schemas/tool.py CHANGED
@@ -4,13 +4,9 @@ from pydantic import Field
4
4
 
5
5
  from letta.functions.helpers import (
6
6
  generate_composio_tool_wrapper,
7
- generate_crewai_tool_wrapper,
8
7
  generate_langchain_tool_wrapper,
9
8
  )
10
- from letta.functions.schema_generator import (
11
- generate_schema_from_args_schema_v1,
12
- generate_schema_from_args_schema_v2,
13
- )
9
+ from letta.functions.schema_generator import generate_schema_from_args_schema_v2
14
10
  from letta.schemas.letta_base import LettaBase
15
11
  from letta.schemas.openai.chat_completions import ToolCall
16
12
 
@@ -132,37 +128,7 @@ class ToolCreate(LettaBase):
132
128
  tags = ["langchain"]
133
129
  # NOTE: langchain tools may come from different packages
134
130
  wrapper_func_name, wrapper_function_str = generate_langchain_tool_wrapper(langchain_tool, additional_imports_module_attr_map)
135
- json_schema = generate_schema_from_args_schema_v1(langchain_tool.args_schema, name=wrapper_func_name, description=description)
136
-
137
- return cls(
138
- name=wrapper_func_name,
139
- description=description,
140
- source_type=source_type,
141
- tags=tags,
142
- source_code=wrapper_function_str,
143
- json_schema=json_schema,
144
- )
145
-
146
- @classmethod
147
- def from_crewai(
148
- cls,
149
- crewai_tool: "CrewAIBaseTool",
150
- additional_imports_module_attr_map: dict[str, str] = None,
151
- ) -> "ToolCreate":
152
- """
153
- Class method to create an instance of Tool from a crewAI BaseTool object.
154
-
155
- Args:
156
- crewai_tool (CrewAIBaseTool): An instance of a crewAI BaseTool (BaseTool from crewai)
157
-
158
- Returns:
159
- Tool: A Letta Tool initialized with attributes derived from the provided crewAI BaseTool object.
160
- """
161
- description = crewai_tool.description
162
- source_type = "python"
163
- tags = ["crew-ai"]
164
- wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool, additional_imports_module_attr_map)
165
- json_schema = generate_schema_from_args_schema_v1(crewai_tool.args_schema, name=wrapper_func_name, description=description)
131
+ json_schema = generate_schema_from_args_schema_v2(langchain_tool.args_schema, name=wrapper_func_name, description=description)
166
132
 
167
133
  return cls(
168
134
  name=wrapper_func_name,
@@ -185,15 +151,6 @@ class ToolCreate(LettaBase):
185
151
 
186
152
  return [wikipedia_tool]
187
153
 
188
- @classmethod
189
- def load_default_crewai_tools(cls) -> List["ToolCreate"]:
190
- # For now, we only support scrape website tool
191
- from crewai_tools import ScrapeWebsiteTool
192
-
193
- web_scrape_tool = ToolCreate.from_crewai(ScrapeWebsiteTool())
194
-
195
- return [web_scrape_tool]
196
-
197
154
  @classmethod
198
155
  def load_default_composio_tools(cls) -> List["ToolCreate"]:
199
156
  from composio_langchain import Action
@@ -7,6 +7,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
7
7
 
8
8
  from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
9
9
  from letta.schemas.agent import AgentState, CreateAgent, UpdateAgentState
10
+ from letta.schemas.block import Block, BlockCreate, BlockLabelUpdate, BlockLimitUpdate
10
11
  from letta.schemas.enums import MessageStreamStatus
11
12
  from letta.schemas.letta_message import (
12
13
  LegacyLettaMessage,
@@ -217,7 +218,9 @@ def update_agent_memory(
217
218
  ):
218
219
  """
219
220
  Update the core memory of a specific agent.
220
- This endpoint accepts new memory contents (human and persona) and updates the core memory of the agent identified by the user ID and agent ID.
221
+ This endpoint accepts new memory contents (labels as keys, and values as values) and updates the core memory of the agent identified by the user ID and agent ID.
222
+ This endpoint accepts new memory contents to update the core memory of the agent.
223
+ This endpoint only supports modifying existing blocks; it does not support deleting/unlinking or creating/linking blocks.
221
224
  """
222
225
  actor = server.get_user_or_default(user_id=user_id)
223
226
 
@@ -225,6 +228,87 @@ def update_agent_memory(
225
228
  return memory
226
229
 
227
230
 
231
+ @router.patch("/{agent_id}/memory/label", response_model=Memory, operation_id="update_agent_memory_label")
232
+ def update_agent_memory_label(
233
+ agent_id: str,
234
+ update_label: BlockLabelUpdate = Body(...),
235
+ server: "SyncServer" = Depends(get_letta_server),
236
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
237
+ ):
238
+ """
239
+ Update the label of a block in an agent's memory.
240
+ """
241
+ actor = server.get_user_or_default(user_id=user_id)
242
+
243
+ memory = server.update_agent_memory_label(
244
+ user_id=actor.id, agent_id=agent_id, current_block_label=update_label.current_label, new_block_label=update_label.new_label
245
+ )
246
+ return memory
247
+
248
+
249
+ @router.post("/{agent_id}/memory/block", response_model=Memory, operation_id="add_agent_memory_block")
250
+ def add_agent_memory_block(
251
+ agent_id: str,
252
+ create_block: BlockCreate = Body(...),
253
+ server: "SyncServer" = Depends(get_letta_server),
254
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
255
+ ):
256
+ """
257
+ Creates a memory block and links it to the agent.
258
+ """
259
+ actor = server.get_user_or_default(user_id=user_id)
260
+
261
+ # Copied from POST /blocks
262
+ block_req = Block(**create_block.model_dump())
263
+ block = server.block_manager.create_or_update_block(actor=actor, block=block_req)
264
+
265
+ # Link the block to the agent
266
+ updated_memory = server.link_block_to_agent_memory(user_id=actor.id, agent_id=agent_id, block_id=block.id)
267
+
268
+ return updated_memory
269
+
270
+
271
+ @router.delete("/{agent_id}/memory/block/{block_label}", response_model=Memory, operation_id="remove_agent_memory_block")
272
+ def remove_agent_memory_block(
273
+ agent_id: str,
274
+ # TODO should this be block_id, or the label?
275
+ # I think label is OK since it's user-friendly + guaranteed to be unique within a Memory object
276
+ block_label: str,
277
+ server: "SyncServer" = Depends(get_letta_server),
278
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
279
+ ):
280
+ """
281
+ Removes a memory block from an agent by unlnking it. If the block is not linked to any other agent, it is deleted.
282
+ """
283
+ actor = server.get_user_or_default(user_id=user_id)
284
+
285
+ # Unlink the block from the agent
286
+ updated_memory = server.unlink_block_from_agent_memory(user_id=actor.id, agent_id=agent_id, block_label=block_label)
287
+
288
+ return updated_memory
289
+
290
+
291
+ @router.patch("/{agent_id}/memory/limit", response_model=Memory, operation_id="update_agent_memory_limit")
292
+ def update_agent_memory_limit(
293
+ agent_id: str,
294
+ update_label: BlockLimitUpdate = Body(...),
295
+ server: "SyncServer" = Depends(get_letta_server),
296
+ user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
297
+ ):
298
+ """
299
+ Update the limit of a block in an agent's memory.
300
+ """
301
+ actor = server.get_user_or_default(user_id=user_id)
302
+
303
+ memory = server.update_agent_memory_limit(
304
+ user_id=actor.id,
305
+ agent_id=agent_id,
306
+ block_label=update_label.label,
307
+ limit=update_label.limit,
308
+ )
309
+ return memory
310
+
311
+
228
312
  @router.get("/{agent_id}/memory/recall", response_model=RecallMemorySummary, operation_id="get_agent_recall_memory_summary")
229
313
  def get_agent_recall_memory_summary(
230
314
  agent_id: str,
letta/server/server.py CHANGED
@@ -328,6 +328,15 @@ class SyncServer(Server):
328
328
  )
329
329
  )
330
330
 
331
+ def save_agents(self):
332
+ """Saves all the agents that are in the in-memory object store"""
333
+ for agent_d in self.active_agents:
334
+ try:
335
+ save_agent(agent_d["agent"], self.ms)
336
+ logger.info(f"Saved agent {agent_d['agent_id']}")
337
+ except Exception as e:
338
+ logger.exception(f"Error occurred while trying to save agent {agent_d['agent_id']}:\n{e}")
339
+
331
340
  def _get_agent(self, user_id: str, agent_id: str) -> Union[Agent, None]:
332
341
  """Get the agent object from the in-memory object store"""
333
342
  for d in self.active_agents:
@@ -400,8 +409,10 @@ class SyncServer(Server):
400
409
  logger.exception(f"Error occurred while trying to get agent {agent_id}:\n{e}")
401
410
  raise
402
411
 
403
- def _get_or_load_agent(self, agent_id: str) -> Agent:
412
+ def _get_or_load_agent(self, agent_id: str, caching: bool = True) -> Agent:
404
413
  """Check if the agent is in-memory, then load"""
414
+
415
+ # Gets the agent state
405
416
  agent_state = self.ms.get_agent(agent_id=agent_id)
406
417
  if not agent_state:
407
418
  raise ValueError(f"Agent does not exist")
@@ -409,11 +420,24 @@ class SyncServer(Server):
409
420
  actor = self.user_manager.get_user_by_id(user_id)
410
421
 
411
422
  logger.debug(f"Checking for agent user_id={user_id} agent_id={agent_id}")
412
- # TODO: consider disabling loading cached agents due to potential concurrency issues
413
- letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
414
- if not letta_agent:
415
- logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
423
+ if caching:
424
+ # TODO: consider disabling loading cached agents due to potential concurrency issues
425
+ letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
426
+ if not letta_agent:
427
+ logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
428
+ letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
429
+ else:
430
+ # This breaks unit tests in test_local_client.py
416
431
  letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
432
+
433
+ # letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
434
+ # if not letta_agent:
435
+ # logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
436
+
437
+ # NOTE: no longer caching, always forcing a lot from the database
438
+ # Loads the agent objects
439
+ # letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
440
+
417
441
  return letta_agent
418
442
 
419
443
  def _step(
@@ -1376,8 +1400,9 @@ class SyncServer(Server):
1376
1400
  # Get the agent object (loaded in memory)
1377
1401
  letta_agent = self._get_or_load_agent(agent_id=agent_id)
1378
1402
  assert isinstance(letta_agent.memory, Memory)
1379
- agent_state = letta_agent.agent_state.model_copy(deep=True)
1380
1403
 
1404
+ letta_agent.update_memory_blocks_from_db()
1405
+ agent_state = letta_agent.agent_state.model_copy(deep=True)
1381
1406
  # Load the tags in for the agent_state
1382
1407
  agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
1383
1408
  return agent_state
@@ -1431,6 +1456,7 @@ class SyncServer(Server):
1431
1456
  # If we modified the memory contents, we need to rebuild the memory block inside the system message
1432
1457
  if modified:
1433
1458
  letta_agent.rebuild_memory()
1459
+ # letta_agent.rebuild_memory(force=True, ms=self.ms) # This breaks unit tests in test_local_client.py
1434
1460
  # save agent
1435
1461
  save_agent(letta_agent, self.ms)
1436
1462
 
@@ -1723,7 +1749,7 @@ class SyncServer(Server):
1723
1749
  def add_default_external_tools(self, actor: User) -> bool:
1724
1750
  """Add default langchain tools. Return true if successful, false otherwise."""
1725
1751
  success = True
1726
- tool_creates = ToolCreate.load_default_langchain_tools() + ToolCreate.load_default_crewai_tools()
1752
+ tool_creates = ToolCreate.load_default_langchain_tools()
1727
1753
  if tool_settings.composio_api_key:
1728
1754
  tool_creates += ToolCreate.load_default_composio_tools()
1729
1755
  for tool_create in tool_creates:
@@ -1817,3 +1843,119 @@ class SyncServer(Server):
1817
1843
  # Get the current message
1818
1844
  letta_agent = self._get_or_load_agent(agent_id=agent_id)
1819
1845
  return letta_agent.get_context_window()
1846
+
1847
+ def update_agent_memory_label(self, user_id: str, agent_id: str, current_block_label: str, new_block_label: str) -> Memory:
1848
+ """Update the label of a block in an agent's memory"""
1849
+
1850
+ # Get the user
1851
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1852
+
1853
+ # Link a block to an agent's memory
1854
+ letta_agent = self._get_or_load_agent(agent_id=agent_id)
1855
+ letta_agent.memory.update_block_label(current_label=current_block_label, new_label=new_block_label)
1856
+ assert new_block_label in letta_agent.memory.list_block_labels()
1857
+ self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(new_block_label), actor=user)
1858
+
1859
+ # check that the block was updated
1860
+ updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(new_block_label).id, actor=user)
1861
+
1862
+ # Recompile the agent memory
1863
+ letta_agent.rebuild_memory(force=True, ms=self.ms)
1864
+
1865
+ # save agent
1866
+ save_agent(letta_agent, self.ms)
1867
+
1868
+ updated_agent = self.ms.get_agent(agent_id=agent_id)
1869
+ if updated_agent is None:
1870
+ raise ValueError(f"Agent with id {agent_id} not found after linking block")
1871
+ assert new_block_label in updated_agent.memory.list_block_labels()
1872
+ assert current_block_label not in updated_agent.memory.list_block_labels()
1873
+ return updated_agent.memory
1874
+
1875
+ def link_block_to_agent_memory(self, user_id: str, agent_id: str, block_id: str) -> Memory:
1876
+ """Link a block to an agent's memory"""
1877
+
1878
+ # Get the user
1879
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1880
+
1881
+ # Get the block first
1882
+ block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
1883
+ if block is None:
1884
+ raise ValueError(f"Block with id {block_id} not found")
1885
+
1886
+ # Link a block to an agent's memory
1887
+ letta_agent = self._get_or_load_agent(agent_id=agent_id)
1888
+ letta_agent.memory.link_block(block=block)
1889
+ assert block.label in letta_agent.memory.list_block_labels()
1890
+
1891
+ # Recompile the agent memory
1892
+ letta_agent.rebuild_memory(force=True, ms=self.ms)
1893
+
1894
+ # save agent
1895
+ save_agent(letta_agent, self.ms)
1896
+
1897
+ updated_agent = self.ms.get_agent(agent_id=agent_id)
1898
+ if updated_agent is None:
1899
+ raise ValueError(f"Agent with id {agent_id} not found after linking block")
1900
+ assert block.label in updated_agent.memory.list_block_labels()
1901
+
1902
+ return updated_agent.memory
1903
+
1904
+ def unlink_block_from_agent_memory(self, user_id: str, agent_id: str, block_label: str, delete_if_no_ref: bool = True) -> Memory:
1905
+ """Unlink a block from an agent's memory. If the block is not linked to any agent, delete it."""
1906
+
1907
+ # Get the user
1908
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1909
+
1910
+ # Link a block to an agent's memory
1911
+ letta_agent = self._get_or_load_agent(agent_id=agent_id)
1912
+ unlinked_block = letta_agent.memory.unlink_block(block_label=block_label)
1913
+ assert unlinked_block.label not in letta_agent.memory.list_block_labels()
1914
+
1915
+ # Check if the block is linked to any other agent
1916
+ # TODO needs reference counting GC to handle loose blocks
1917
+ # block = self.block_manager.get_block_by_id(block_id=unlinked_block.id, actor=user)
1918
+ # if block is None:
1919
+ # raise ValueError(f"Block with id {block_id} not found")
1920
+
1921
+ # Recompile the agent memory
1922
+ letta_agent.rebuild_memory(force=True, ms=self.ms)
1923
+
1924
+ # save agent
1925
+ save_agent(letta_agent, self.ms)
1926
+
1927
+ updated_agent = self.ms.get_agent(agent_id=agent_id)
1928
+ if updated_agent is None:
1929
+ raise ValueError(f"Agent with id {agent_id} not found after linking block")
1930
+ assert unlinked_block.label not in updated_agent.memory.list_block_labels()
1931
+ return updated_agent.memory
1932
+
1933
+ def update_agent_memory_limit(self, user_id: str, agent_id: str, block_label: str, limit: int) -> Memory:
1934
+ """Update the limit of a block in an agent's memory"""
1935
+
1936
+ # Get the user
1937
+ user = self.user_manager.get_user_by_id(user_id=user_id)
1938
+
1939
+ # Link a block to an agent's memory
1940
+ letta_agent = self._get_or_load_agent(agent_id=agent_id)
1941
+ letta_agent.memory.update_block_limit(label=block_label, limit=limit)
1942
+ assert block_label in letta_agent.memory.list_block_labels()
1943
+
1944
+ # write out the update the database
1945
+ self.block_manager.create_or_update_block(block=letta_agent.memory.get_block(block_label), actor=user)
1946
+
1947
+ # check that the block was updated
1948
+ updated_block = self.block_manager.get_block_by_id(block_id=letta_agent.memory.get_block(block_label).id, actor=user)
1949
+ assert updated_block and updated_block.limit == limit
1950
+
1951
+ # Recompile the agent memory
1952
+ letta_agent.rebuild_memory(force=True, ms=self.ms)
1953
+
1954
+ # save agent
1955
+ save_agent(letta_agent, self.ms)
1956
+
1957
+ updated_agent = self.ms.get_agent(agent_id=agent_id)
1958
+ if updated_agent is None:
1959
+ raise ValueError(f"Agent with id {agent_id} not found after linking block")
1960
+ assert updated_agent.memory.get_block(label=block_label).limit == limit
1961
+ return updated_agent.memory
@@ -34,7 +34,7 @@ class BlockManager:
34
34
  return block.to_pydantic()
35
35
 
36
36
  @enforce_types
37
- def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock:
37
+ def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser, limit: Optional[int] = None) -> PydanticBlock:
38
38
  """Update a block by its ID with the given BlockUpdate object."""
39
39
  with self.session_maker() as session:
40
40
  block = BlockModel.read(db_session=session, identifier=block_id, actor=actor)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: letta-nightly
3
- Version: 0.5.4.dev20241121104201
3
+ Version: 0.5.4.dev20241122104229
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  License: Apache License
6
6
  Author: Letta Team
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Provides-Extra: all
14
15
  Provides-Extra: autogen
15
16
  Provides-Extra: dev
16
17
  Provides-Extra: external-tools
@@ -21,48 +22,46 @@ Provides-Extra: qdrant
21
22
  Provides-Extra: server
22
23
  Provides-Extra: tests
23
24
  Requires-Dist: alembic (>=1.13.3,<2.0.0)
24
- Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev"
25
- Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev"
25
+ Requires-Dist: autoflake (>=2.3.0,<3.0.0) ; extra == "dev" or extra == "all"
26
+ Requires-Dist: black[jupyter] (>=24.2.0,<25.0.0) ; extra == "dev" or extra == "all"
26
27
  Requires-Dist: chromadb (>=0.4.24,<0.5.0)
27
- Requires-Dist: composio-core (>=0.5.34,<0.6.0) ; extra == "external-tools"
28
- Requires-Dist: composio-langchain (>=0.5.28,<0.6.0) ; extra == "external-tools"
29
- Requires-Dist: crewai (>=0.41.1,<0.42.0) ; extra == "external-tools"
30
- Requires-Dist: crewai-tools (>=0.8.3,<0.9.0) ; extra == "external-tools"
31
- Requires-Dist: datasets (>=2.14.6,<3.0.0) ; extra == "dev"
28
+ Requires-Dist: composio-core (>=0.5.34,<0.6.0) ; extra == "external-tools" or extra == "all"
29
+ Requires-Dist: composio-langchain (>=0.5.28,<0.6.0) ; extra == "external-tools" or extra == "all"
30
+ Requires-Dist: datasets (>=2.14.6,<3.0.0) ; extra == "dev" or extra == "all"
32
31
  Requires-Dist: demjson3 (>=3.0.6,<4.0.0)
33
- Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools"
32
+ Requires-Dist: docker (>=7.1.0,<8.0.0) ; extra == "external-tools" or extra == "all"
34
33
  Requires-Dist: docstring-parser (>=0.16,<0.17)
35
34
  Requires-Dist: docx2txt (>=0.8,<0.9)
36
- Requires-Dist: fastapi (>=0.104.1,<0.105.0) ; extra == "server"
35
+ Requires-Dist: fastapi (>=0.104.1,<0.105.0) ; extra == "server" or extra == "all"
37
36
  Requires-Dist: html2text (>=2020.1.16,<2021.0.0)
38
37
  Requires-Dist: httpx (>=0.27.2,<0.28.0)
39
38
  Requires-Dist: httpx-sse (>=0.4.0,<0.5.0)
40
- Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev"
39
+ Requires-Dist: isort (>=5.13.2,<6.0.0) ; extra == "dev" or extra == "all"
41
40
  Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
42
- Requires-Dist: langchain (>=0.2.16,<0.3.0) ; extra == "external-tools"
43
- Requires-Dist: langchain-community (>=0.2.17,<0.3.0) ; extra == "external-tools"
41
+ Requires-Dist: langchain (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
42
+ Requires-Dist: langchain-community (>=0.3.7,<0.4.0) ; extra == "external-tools" or extra == "all"
44
43
  Requires-Dist: llama-index (>=0.11.9,<0.12.0)
45
- Requires-Dist: llama-index-embeddings-ollama (>=0.3.1,<0.4.0) ; extra == "ollama"
44
+ Requires-Dist: llama-index-embeddings-ollama (>=0.3.1,<0.4.0) ; extra == "ollama" or extra == "all"
46
45
  Requires-Dist: llama-index-embeddings-openai (>=0.2.5,<0.3.0)
47
- Requires-Dist: locust (>=2.31.5,<3.0.0)
46
+ Requires-Dist: locust (>=2.31.5,<3.0.0) ; extra == "dev" or extra == "all"
48
47
  Requires-Dist: nltk (>=3.8.1,<4.0.0)
49
48
  Requires-Dist: numpy (>=1.26.2,<2.0.0)
50
49
  Requires-Dist: pathvalidate (>=3.2.1,<4.0.0)
51
- Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "dev"
52
- Requires-Dist: pg8000 (>=1.30.3,<2.0.0) ; extra == "postgres"
53
- Requires-Dist: pgvector (>=0.2.3,<0.3.0) ; extra == "postgres"
54
- Requires-Dist: pre-commit (>=3.5.0,<4.0.0) ; extra == "dev"
50
+ Requires-Dist: pexpect (>=4.9.0,<5.0.0) ; extra == "dev" or extra == "all"
51
+ Requires-Dist: pg8000 (>=1.30.3,<2.0.0) ; extra == "postgres" or extra == "all"
52
+ Requires-Dist: pgvector (>=0.2.3,<0.3.0) ; extra == "postgres" or extra == "all"
53
+ Requires-Dist: pre-commit (>=3.5.0,<4.0.0) ; extra == "dev" or extra == "all"
55
54
  Requires-Dist: prettytable (>=3.9.0,<4.0.0)
56
- Requires-Dist: psycopg2 (>=2.9.10,<3.0.0) ; extra == "postgres"
57
- Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "postgres"
55
+ Requires-Dist: psycopg2 (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
56
+ Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "postgres" or extra == "all"
58
57
  Requires-Dist: pyautogen (==0.2.22) ; extra == "autogen"
59
58
  Requires-Dist: pydantic (>=2.7.4,<2.10.0)
60
59
  Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
61
60
  Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
62
61
  Requires-Dist: pymilvus (>=2.4.3,<3.0.0) ; extra == "milvus"
63
- Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev"
64
- Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev"
65
- Requires-Dist: pytest-order (>=1.2.0,<2.0.0) ; extra == "dev"
62
+ Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev" or extra == "all"
63
+ Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev" or extra == "all"
64
+ Requires-Dist: pytest-order (>=1.2.0,<2.0.0) ; extra == "dev" or extra == "all"
66
65
  Requires-Dist: python-box (>=7.1.1,<8.0.0)
67
66
  Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
68
67
  Requires-Dist: pytz (>=2023.3.post1,<2024.0)
@@ -77,9 +76,9 @@ Requires-Dist: sqlmodel (>=0.0.16,<0.0.17)
77
76
  Requires-Dist: tiktoken (>=0.7.0,<0.8.0)
78
77
  Requires-Dist: tqdm (>=4.66.1,<5.0.0)
79
78
  Requires-Dist: typer[all] (>=0.9.0,<0.10.0)
80
- Requires-Dist: uvicorn (>=0.24.0.post1,<0.25.0) ; extra == "server"
81
- Requires-Dist: websockets (>=12.0,<13.0) ; extra == "server"
82
- Requires-Dist: wikipedia (>=1.4.0,<2.0.0) ; extra == "external-tools" or extra == "tests"
79
+ Requires-Dist: uvicorn (>=0.24.0.post1,<0.25.0) ; extra == "server" or extra == "all"
80
+ Requires-Dist: websockets (>=12.0,<13.0) ; extra == "server" or extra == "all"
81
+ Requires-Dist: wikipedia (>=1.4.0,<2.0.0) ; extra == "external-tools" or extra == "tests" or extra == "all"
83
82
  Description-Content-Type: text/markdown
84
83
 
85
84
  <p align="center">
@@ -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=Jirt5D89k3s91nsScfjsOTfx3SS_eAOF8iQvZ0I3hjM,77421
3
+ letta/agent.py,sha256=7uc2v0mfAX46RdxPY-HIlfeO15mgBORfnr7bi84Ik4o,77430
4
4
  letta/agent_store/chroma.py,sha256=upR5zGnGs6I6btulEYbiZdGG87BgKjxUJOQZ4Y-RQ_M,12492
5
5
  letta/agent_store/db.py,sha256=dVBnNwVX57_kVdrD1oHnqze154QOoxWYSElJJUojyMs,23465
6
6
  letta/agent_store/lancedb.py,sha256=i63d4VZwj9UIOTNs5f0JZ_r5yZD-jKWz4FAH4RMpXOE,5104
@@ -13,7 +13,7 @@ letta/cli/cli.py,sha256=1dJIZ8DIGM8mg0G0UGLKMTa3fwgHzrN8Hkxd5Uxx7X4,16946
13
13
  letta/cli/cli_config.py,sha256=D-CpSZI1cDvdSQr3-zhGuDrJnZo1Ko7bi_wuxcBxxqo,8555
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=1eLqzAUKoO63Ee_p4LjJjrlqmv4phUZc2MPMXhPpEw0,98717
16
+ letta/client/client.py,sha256=GjSCpp7oy2e9SXcxRdFfsvGm0nmuYDO8yNB1yS7MWrE,102261
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
@@ -27,7 +27,7 @@ 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
29
  letta/functions/functions.py,sha256=VyA_7O56KRUj88iuMkLJTRfascaTCj1qFGT0BnDgC6k,4140
30
- letta/functions/helpers.py,sha256=JU3e5xkkTVx4EevBmtyCRnidf0ncAeASvH2ZT_aBvPc,9905
30
+ letta/functions/helpers.py,sha256=_GcgDLeR7YAGY0HIGxxM_EoXGFgWISZaX-FfS39nLGc,8682
31
31
  letta/functions/schema_generator.py,sha256=CoDZQfXsOKBp5VOv-024efcR833wyrchQbQIN7mL11A,8407
32
32
  letta/helpers/__init__.py,sha256=p0luQ1Oe3Skc6sH4O58aHHA3Qbkyjifpuq0DZ1GAY0U,59
33
33
  letta/helpers/tool_rule_solver.py,sha256=AZiUjW_oDlQx5uMI7oaL50KkI1InTW8qRkFdg6S54RQ,4744
@@ -85,7 +85,7 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
85
85
  letta/log.py,sha256=Oy5b71AXfrnQShxI_4ULo5U3kmZJG01bXbP_64Nr4Fk,2105
86
86
  letta/main.py,sha256=cFnjnbzyrRRM5sZeRAnGVq_rPIgJRHRFyFNCY--sRI4,19163
87
87
  letta/memory.py,sha256=YupXOvzVJXH59RW4XWBrd7qMNEYaMbtWXCheKeWZwpU,17873
88
- letta/metadata.py,sha256=DoLXPfxqgWEWYpwg1B8g19Vae6v6jnljSwUr8wXEF3M,17620
88
+ letta/metadata.py,sha256=jcvkzrCfSlmUbv5puCbeFP6cqM-Ct_U49C5WFLpYJ0s,18180
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
@@ -131,7 +131,7 @@ letta/pytest.ini,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
131
  letta/schemas/agent.py,sha256=f0khTBWIRGZva4_C15Nm_tkmn1cwaVQlWa7_7laRbEE,7866
132
132
  letta/schemas/agents_tags.py,sha256=9DGr8fN2DHYdWvZ_qcXmrKI0w7YKCGz2lfEcrX2KAkI,1130
133
133
  letta/schemas/api_key.py,sha256=u07yzzMn-hBAHZIIKbWY16KsgiFjSNR8lAghpMUo3_4,682
134
- letta/schemas/block.py,sha256=drnVWEmsx9NevCgq2STIxdSoCDPrp09Sy0Vq42Xbzz4,4315
134
+ letta/schemas/block.py,sha256=4xYoyfLezvyjA7TN3c5AvPCyRh_Pz0_XVqVCwIaXoTo,4758
135
135
  letta/schemas/embedding_config.py,sha256=1kD6NpiXeH4roVumxqDAKk7xt8SpXGWNhZs_XXUSlEU,2855
136
136
  letta/schemas/enums.py,sha256=WfRYpLh_pD-VyhEnp3Y6pPfx063zq2o4jky6PulqO8w,629
137
137
  letta/schemas/file.py,sha256=ChN2CWzLI2TT9WLItcfElEH0E8b7kzPylF2OQBr6Beg,1550
@@ -142,7 +142,7 @@ letta/schemas/letta_message.py,sha256=RuVVtwFbi85yP3dXQxowofQ6cI2cO_CdGtgpHGQzgH
142
142
  letta/schemas/letta_request.py,sha256=_oiDshc_AoFWIfXRk2VX5-AxO5vDlyN-9r-gnyLj_30,1890
143
143
  letta/schemas/letta_response.py,sha256=li_j4VUF_WtxdJy7ufRmmmchzvhVmr1idbOxtgFGiy0,6253
144
144
  letta/schemas/llm_config.py,sha256=RbgnCaqYd_yl-Xs7t-DEI1NhpKD8WiVWjxcwq5mZd5M,4467
145
- letta/schemas/memory.py,sha256=fHkJZr8CrGcHhbJlckWgfRYMhLkRliKCU-hRxqr19ks,11725
145
+ letta/schemas/memory.py,sha256=skuTu-aYpEs9Q76yINWLgmLFELG5m8-JdCeTjtR41rU,13192
146
146
  letta/schemas/message.py,sha256=DQxnRYrYgHXpTKfMzfS-bpCAe-BO_Rmcfc1Wf-4GHjw,33703
147
147
  letta/schemas/openai/chat_completion_request.py,sha256=AOIwgbN3CZKVqkuXeMHeSa53u4h0wVq69t3T_LJ0vIE,3389
148
148
  letta/schemas/openai/chat_completion_response.py,sha256=ub-oVSyLpuJd-5_yzCSIRR8tD3GM83IeDO1c1uAATa4,3970
@@ -152,7 +152,7 @@ letta/schemas/openai/openai.py,sha256=Hilo5BiLAGabzxCwnwfzK5QrWqwYD8epaEKFa4Pwnd
152
152
  letta/schemas/organization.py,sha256=d2oN3IK2HeruEHKXwIzCbJ3Fxdi_BEe9JZ8J9aDbHwQ,698
153
153
  letta/schemas/passage.py,sha256=eYQMxD_XjHAi72jmqcGBU4wM4VZtSU0XK8uhQxxN3Ug,3563
154
154
  letta/schemas/source.py,sha256=B1VbaDJV-EGPv1nQXwCx_RAzeAJd50UqP_1m1cIRT8c,2854
155
- letta/schemas/tool.py,sha256=HCUW4RXUS9RtvfpTPeNhauFN3s23BojSOceaIxv9bKI,9596
155
+ letta/schemas/tool.py,sha256=Ehdl4fB1tLG-kgUTVD7MAhvSs_H_Tim2aROu0S0diB8,8083
156
156
  letta/schemas/tool_rule.py,sha256=zv4jE0b8LW78idP4UbJARnrZcnmaqjGNUk_YV99Y0c0,884
157
157
  letta/schemas/usage.py,sha256=lvn1ooHwLEdv6gwQpw5PBUbcwn_gwdT6HA-fCiix6sY,817
158
158
  letta/schemas/user.py,sha256=V32Tgl6oqB3KznkxUz12y7agkQicjzW7VocSpj78i6Q,1526
@@ -174,7 +174,7 @@ letta/server/rest_api/routers/openai/assistants/threads.py,sha256=WXVGBaBvSNPB7Z
174
174
  letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
175
175
  letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=-uye6cm4SnoQGwxhr1N1FrSXOlnO2Hvbfj6k8JSc45k,4918
176
176
  letta/server/rest_api/routers/v1/__init__.py,sha256=sqlVZa-u9DJwdRsp0_8YUGrac9DHguIB4wETlEDRylA,666
177
- letta/server/rest_api/routers/v1/agents.py,sha256=bvlKgARwXWHq8_gJkiqYsU9ZXKBm2iDRTuosLnY8d9w,22043
177
+ letta/server/rest_api/routers/v1/agents.py,sha256=yKpbqQauHHbvuV8IWgAPgri0lPmr6EWbYUx_C64RBGA,25541
178
178
  letta/server/rest_api/routers/v1/blocks.py,sha256=ogJdn-Ir7h1ZEv28bHtUNNsR2zq9-wxXAMpu2t1EoIA,2946
179
179
  letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
180
180
  letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
@@ -185,7 +185,7 @@ letta/server/rest_api/routers/v1/tools.py,sha256=Bkb9oKswOycj5S3fBeim7LpDrZf37Sy
185
185
  letta/server/rest_api/routers/v1/users.py,sha256=M1wEr2IyHzuRwINYxLXTkrbAH3osLe_cWjzrWrzR1aw,3729
186
186
  letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
187
187
  letta/server/rest_api/utils.py,sha256=GdHYCzXtbM5VCAYDPR0z5gnNZpRhwPld2BGZV7xT6cU,2924
188
- letta/server/server.py,sha256=hYlHYb93kjgtBvnQ6RoRstA80wTRfsKpSECVQVkgR7s,77287
188
+ letta/server/server.py,sha256=uD7wO6f6iDZO1IUzAclIADpalhk-JrVNnzbVA1tt-YY,83736
189
189
  letta/server/startup.sh,sha256=wTOQOJJZw_Iec57WIu0UW0AVflk0ZMWYZWg8D3T_gSQ,698
190
190
  letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
191
191
  letta/server/static_files/assets/index-9fa459a2.js,sha256=j2oMcDJO9dWJaH5e-tsflbVpWK20gLWpZKJk4-Kuy6A,1815592
@@ -200,7 +200,7 @@ letta/server/ws_api/protocol.py,sha256=M_-gM5iuDBwa1cuN2IGNCG5GxMJwU2d3XW93XALv9
200
200
  letta/server/ws_api/server.py,sha256=C2Kv48PCwl46DQFb0ZP30s86KJLQ6dZk2AhWQEZn9pY,6004
201
201
  letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
202
202
  letta/services/agents_tags_manager.py,sha256=zNqeXDpaf4dQ77jrRHiQfITdk4FawBzcND-9tWrj8gw,3127
203
- letta/services/block_manager.py,sha256=usUbUzHjQXtJ0GLAGlCVl3RgZeEaPz7BGMCjiF_2X4k,4439
203
+ letta/services/block_manager.py,sha256=oxSeBTPILihcM65mZgvW9OD51z20wU232BFORxC-wlo,4468
204
204
  letta/services/organization_manager.py,sha256=OfE2_NMmhqXURX4sg7hCOiFQVQpV5ZiPu7J3sboCSYc,3555
205
205
  letta/services/source_manager.py,sha256=StX5Wfd7XSCKJet8qExIu3GMoI-eMIbEarAeTv2gq0s,6555
206
206
  letta/services/tool_manager.py,sha256=Vr2_JQ3TQUSPSPNbmGwY26HIFjYw0NhzJGgpMvS6GV8,8163
@@ -210,8 +210,8 @@ letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,
210
210
  letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
211
211
  letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
212
212
  letta/utils.py,sha256=COwQLAt02eEM9tjp6p5kN8YeTqGXr714l5BvffLVCLU,32376
213
- letta_nightly-0.5.4.dev20241121104201.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
214
- letta_nightly-0.5.4.dev20241121104201.dist-info/METADATA,sha256=NMAuQrVAjCVN75tHqdPuKoZh9xmwP9mOeOgQ3HbTfcg,11070
215
- letta_nightly-0.5.4.dev20241121104201.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
216
- letta_nightly-0.5.4.dev20241121104201.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
217
- letta_nightly-0.5.4.dev20241121104201.dist-info/RECORD,,
213
+ letta_nightly-0.5.4.dev20241122104229.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
214
+ letta_nightly-0.5.4.dev20241122104229.dist-info/METADATA,sha256=iD_Sb8bXV-LOgGdFN7FlJmO9h0Mwv8zuPhhqEErsUek,11395
215
+ letta_nightly-0.5.4.dev20241122104229.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
216
+ letta_nightly-0.5.4.dev20241122104229.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
217
+ letta_nightly-0.5.4.dev20241122104229.dist-info/RECORD,,