letta-nightly 0.5.2.dev20241119104253__py3-none-any.whl → 0.5.3.dev20241120104224__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/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.2"
1
+ __version__ = "0.5.3"
2
2
 
3
3
  # import clients
4
4
  from letta.client.client import LocalClient, RESTClient, create_client
letta/agent.py CHANGED
@@ -27,6 +27,7 @@ from letta.llm_api.llm_api_tools import create
27
27
  from letta.local_llm.utils import num_tokens_from_functions, num_tokens_from_messages
28
28
  from letta.memory import ArchivalMemory, RecallMemory, summarize_messages
29
29
  from letta.metadata import MetadataStore
30
+ from letta.orm import User
30
31
  from letta.persistence_manager import LocalStateManager
31
32
  from letta.schemas.agent import AgentState, AgentStepResponse
32
33
  from letta.schemas.block import Block
@@ -46,6 +47,7 @@ from letta.schemas.passage import Passage
46
47
  from letta.schemas.tool import Tool
47
48
  from letta.schemas.tool_rule import TerminalToolRule
48
49
  from letta.schemas.usage import LettaUsageStatistics
50
+ from letta.services.block_manager import BlockManager
49
51
  from letta.services.source_manager import SourceManager
50
52
  from letta.services.user_manager import UserManager
51
53
  from letta.streaming_interface import StreamingRefreshCLIInterface
@@ -234,6 +236,7 @@ class Agent(BaseAgent):
234
236
  # agents can be created from providing agent_state
235
237
  agent_state: AgentState,
236
238
  tools: List[Tool],
239
+ user: User,
237
240
  # memory: Memory,
238
241
  # extras
239
242
  messages_total: Optional[int] = None, # TODO remove?
@@ -245,6 +248,8 @@ class Agent(BaseAgent):
245
248
  self.agent_state = agent_state
246
249
  assert isinstance(self.agent_state.memory, Memory), f"Memory object is not of type Memory: {type(self.agent_state.memory)}"
247
250
 
251
+ self.user = user
252
+
248
253
  # link tools
249
254
  self.link_tools(tools)
250
255
 
@@ -1221,7 +1226,9 @@ class Agent(BaseAgent):
1221
1226
  # future if we expect templates to change often.
1222
1227
  continue
1223
1228
  block_id = block.get("id")
1224
- db_block = ms.get_block(block_id=block_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)
1225
1232
  if db_block is None:
1226
1233
  # this case covers if someone has deleted a shared block by interacting
1227
1234
  # with some other agent.
@@ -1598,7 +1605,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
1598
1605
 
1599
1606
  # NOTE: we're saving agent memory before persisting the agent to ensure
1600
1607
  # that allocated block_ids for each memory block are present in the agent model
1601
- save_agent_memory(agent=agent, ms=ms)
1608
+ save_agent_memory(agent=agent)
1602
1609
 
1603
1610
  if ms.get_agent(agent_id=agent.agent_state.id):
1604
1611
  ms.update_agent(agent_state)
@@ -1609,7 +1616,7 @@ def save_agent(agent: Agent, ms: MetadataStore):
1609
1616
  assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
1610
1617
 
1611
1618
 
1612
- def save_agent_memory(agent: Agent, ms: MetadataStore):
1619
+ def save_agent_memory(agent: Agent):
1613
1620
  """
1614
1621
  Save agent memory to metadata store. Memory is a collection of blocks and each block is persisted to the block table.
1615
1622
 
@@ -1618,14 +1625,12 @@ def save_agent_memory(agent: Agent, ms: MetadataStore):
1618
1625
 
1619
1626
  for block_dict in agent.memory.to_dict()["memory"].values():
1620
1627
  # TODO: block creation should happen in one place to enforce these sort of constraints consistently.
1621
- if block_dict.get("user_id", None) is None:
1622
- block_dict["user_id"] = agent.agent_state.user_id
1623
1628
  block = Block(**block_dict)
1624
1629
  # FIXME: should we expect for block values to be None? If not, we need to figure out why that is
1625
1630
  # the case in some tests, if so we should relax the DB constraint.
1626
1631
  if block.value is None:
1627
1632
  block.value = ""
1628
- ms.update_or_create_block(block)
1633
+ BlockManager().create_or_update_block(block, actor=agent.user)
1629
1634
 
1630
1635
 
1631
1636
  def strip_name_field_from_user_message(user_message_text: str) -> Tuple[str, Optional[str]]:
letta/agent_store/db.py CHANGED
@@ -380,9 +380,10 @@ class PostgresStorageConnector(SQLStorageConnector):
380
380
  else:
381
381
  raise ValueError(f"Table type {table_type} not implemented")
382
382
 
383
- for c in self.db_model.__table__.columns:
384
- if c.name == "embedding":
385
- assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
383
+ if settings.pg_uri:
384
+ for c in self.db_model.__table__.columns:
385
+ if c.name == "embedding":
386
+ assert isinstance(c.type, Vector), f"Embedding column must be of type Vector, got {c.type}"
386
387
 
387
388
  from letta.server.server import db_context
388
389
 
letta/cli/cli.py CHANGED
@@ -220,7 +220,7 @@ def run(
220
220
 
221
221
  # create agent
222
222
  tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.tools]
223
- letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools)
223
+ letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools, user=client.user)
224
224
 
225
225
  else: # create new agent
226
226
  # create new agent config: override defaults with args if provided
@@ -320,6 +320,7 @@ def run(
320
320
  tools=tools,
321
321
  # gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
322
322
  first_message_verify_mono=True if (model is not None and "gpt-4" in model) else False,
323
+ user=client.user,
323
324
  )
324
325
  save_agent(agent=letta_agent, ms=ms)
325
326
  typer.secho(f"🎉 Created new agent '{letta_agent.agent_state.name}' (id={letta_agent.agent_state.id})", fg=typer.colors.GREEN)
letta/client/client.py CHANGED
@@ -12,12 +12,10 @@ from letta.memory import get_memory_functions
12
12
  from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
13
13
  from letta.schemas.block import (
14
14
  Block,
15
- CreateBlock,
16
- CreateHuman,
17
- CreatePersona,
15
+ BlockCreate,
16
+ BlockUpdate,
18
17
  Human,
19
18
  Persona,
20
- UpdateBlock,
21
19
  UpdateHuman,
22
20
  UpdatePersona,
23
21
  )
@@ -883,8 +881,8 @@ class RESTClient(AbstractClient):
883
881
  else:
884
882
  return [Block(**block) for block in response.json()]
885
883
 
886
- def create_block(self, label: str, text: str, template_name: Optional[str] = None, template: bool = False) -> Block: #
887
- request = CreateBlock(label=label, value=text, template=template, template_name=template_name)
884
+ def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
885
+ request = BlockCreate(label=label, value=value, template=is_template, template_name=template_name)
888
886
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
889
887
  if response.status_code != 200:
890
888
  raise ValueError(f"Failed to create block: {response.text}")
@@ -896,7 +894,7 @@ class RESTClient(AbstractClient):
896
894
  return Block(**response.json())
897
895
 
898
896
  def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
899
- request = UpdateBlock(id=block_id, template_name=name, value=text)
897
+ request = BlockUpdate(id=block_id, template_name=name, value=text)
900
898
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks/{block_id}", json=request.model_dump(), headers=self.headers)
901
899
  if response.status_code != 200:
902
900
  raise ValueError(f"Failed to update block: {response.text}")
@@ -950,7 +948,7 @@ class RESTClient(AbstractClient):
950
948
  Returns:
951
949
  human (Human): Human block
952
950
  """
953
- return self.create_block(label="human", template_name=name, text=text, template=True)
951
+ return self.create_block(label="human", template_name=name, value=text, is_template=True)
954
952
 
955
953
  def update_human(self, human_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Human:
956
954
  """
@@ -990,7 +988,7 @@ class RESTClient(AbstractClient):
990
988
  Returns:
991
989
  persona (Persona): Persona block
992
990
  """
993
- return self.create_block(label="persona", template_name=name, text=text, template=True)
991
+ return self.create_block(label="persona", template_name=name, value=text, is_template=True)
994
992
 
995
993
  def update_persona(self, persona_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Persona:
996
994
  """
@@ -2125,8 +2123,7 @@ class LocalClient(AbstractClient):
2125
2123
  # humans / personas
2126
2124
 
2127
2125
  def get_block_id(self, name: str, label: str) -> str:
2128
-
2129
- block = self.server.get_blocks(name=name, label=label, user_id=self.user_id, template=True)
2126
+ block = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label=label, is_template=True)
2130
2127
  if not block:
2131
2128
  return None
2132
2129
  return block[0].id
@@ -2142,7 +2139,7 @@ class LocalClient(AbstractClient):
2142
2139
  Returns:
2143
2140
  human (Human): Human block
2144
2141
  """
2145
- return self.server.create_block(CreateHuman(template_name=name, value=text, user_id=self.user_id), user_id=self.user_id)
2142
+ return self.server.block_manager.create_or_update_block(Human(template_name=name, value=text), actor=self.user)
2146
2143
 
2147
2144
  def create_persona(self, name: str, text: str):
2148
2145
  """
@@ -2155,7 +2152,7 @@ class LocalClient(AbstractClient):
2155
2152
  Returns:
2156
2153
  persona (Persona): Persona block
2157
2154
  """
2158
- return self.server.create_block(CreatePersona(template_name=name, value=text, user_id=self.user_id), user_id=self.user_id)
2155
+ return self.server.block_manager.create_or_update_block(Persona(template_name=name, value=text), actor=self.user)
2159
2156
 
2160
2157
  def list_humans(self):
2161
2158
  """
@@ -2164,7 +2161,7 @@ class LocalClient(AbstractClient):
2164
2161
  Returns:
2165
2162
  humans (List[Human]): List of human blocks
2166
2163
  """
2167
- return self.server.get_blocks(label="human", user_id=self.user_id, template=True)
2164
+ return self.server.block_manager.get_blocks(actor=self.user, label="human", is_template=True)
2168
2165
 
2169
2166
  def list_personas(self) -> List[Persona]:
2170
2167
  """
@@ -2173,7 +2170,7 @@ class LocalClient(AbstractClient):
2173
2170
  Returns:
2174
2171
  personas (List[Persona]): List of persona blocks
2175
2172
  """
2176
- return self.server.get_blocks(label="persona", user_id=self.user_id, template=True)
2173
+ return self.server.block_manager.get_blocks(actor=self.user, label="persona", is_template=True)
2177
2174
 
2178
2175
  def update_human(self, human_id: str, text: str):
2179
2176
  """
@@ -2186,7 +2183,9 @@ class LocalClient(AbstractClient):
2186
2183
  Returns:
2187
2184
  human (Human): Updated human block
2188
2185
  """
2189
- return self.server.update_block(UpdateHuman(id=human_id, value=text, user_id=self.user_id, template=True))
2186
+ return self.server.block_manager.update_block(
2187
+ block_id=human_id, block_update=UpdateHuman(value=text, is_template=True), actor=self.user
2188
+ )
2190
2189
 
2191
2190
  def update_persona(self, persona_id: str, text: str):
2192
2191
  """
@@ -2199,7 +2198,9 @@ class LocalClient(AbstractClient):
2199
2198
  Returns:
2200
2199
  persona (Persona): Updated persona block
2201
2200
  """
2202
- return self.server.update_block(UpdatePersona(id=persona_id, value=text, user_id=self.user_id, template=True))
2201
+ return self.server.block_manager.update_block(
2202
+ block_id=persona_id, block_update=UpdatePersona(value=text, is_template=True), actor=self.user
2203
+ )
2203
2204
 
2204
2205
  def get_persona(self, id: str) -> Persona:
2205
2206
  """
@@ -2212,7 +2213,7 @@ class LocalClient(AbstractClient):
2212
2213
  persona (Persona): Persona block
2213
2214
  """
2214
2215
  assert id, f"Persona ID must be provided"
2215
- return Persona(**self.server.get_block(id).model_dump())
2216
+ return Persona(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
2216
2217
 
2217
2218
  def get_human(self, id: str) -> Human:
2218
2219
  """
@@ -2225,7 +2226,7 @@ class LocalClient(AbstractClient):
2225
2226
  human (Human): Human block
2226
2227
  """
2227
2228
  assert id, f"Human ID must be provided"
2228
- return Human(**self.server.get_block(id).model_dump())
2229
+ return Human(**self.server.block_manager.get_block_by_id(id, actor=self.user).model_dump())
2229
2230
 
2230
2231
  def get_persona_id(self, name: str) -> str:
2231
2232
  """
@@ -2237,7 +2238,7 @@ class LocalClient(AbstractClient):
2237
2238
  Returns:
2238
2239
  id (str): ID of the persona block
2239
2240
  """
2240
- persona = self.server.get_blocks(name=name, label="persona", user_id=self.user_id, template=True)
2241
+ persona = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="persona", is_template=True)
2241
2242
  if not persona:
2242
2243
  return None
2243
2244
  return persona[0].id
@@ -2252,7 +2253,7 @@ class LocalClient(AbstractClient):
2252
2253
  Returns:
2253
2254
  id (str): ID of the human block
2254
2255
  """
2255
- human = self.server.get_blocks(name=name, label="human", user_id=self.user_id, template=True)
2256
+ human = self.server.block_manager.get_blocks(actor=self.user, template_name=name, label="human", is_template=True)
2256
2257
  if not human:
2257
2258
  return None
2258
2259
  return human[0].id
@@ -2264,7 +2265,7 @@ class LocalClient(AbstractClient):
2264
2265
  Args:
2265
2266
  id (str): ID of the persona block
2266
2267
  """
2267
- self.server.delete_block(id)
2268
+ self.delete_block(id)
2268
2269
 
2269
2270
  def delete_human(self, id: str):
2270
2271
  """
@@ -2273,7 +2274,7 @@ class LocalClient(AbstractClient):
2273
2274
  Args:
2274
2275
  id (str): ID of the human block
2275
2276
  """
2276
- self.server.delete_block(id)
2277
+ self.delete_block(id)
2277
2278
 
2278
2279
  # tools
2279
2280
  def load_langchain_tool(self, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> Tool:
@@ -2661,9 +2662,9 @@ class LocalClient(AbstractClient):
2661
2662
  Returns:
2662
2663
  blocks (List[Block]): List of blocks
2663
2664
  """
2664
- return self.server.get_blocks(label=label, template=templates_only)
2665
+ return self.server.block_manager.get_blocks(actor=self.user, label=label, is_template=templates_only)
2665
2666
 
2666
- def create_block(self, label: str, text: str, template_name: Optional[str] = None, template: bool = False) -> Block: #
2667
+ def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
2667
2668
  """
2668
2669
  Create a block
2669
2670
 
@@ -2675,8 +2676,8 @@ class LocalClient(AbstractClient):
2675
2676
  Returns:
2676
2677
  block (Block): Created block
2677
2678
  """
2678
- return self.server.create_block(
2679
- CreateBlock(label=label, template_name=template_name, value=text, user_id=self.user_id, template=template), user_id=self.user_id
2679
+ return self.server.block_manager.create_or_update_block(
2680
+ Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
2680
2681
  )
2681
2682
 
2682
2683
  def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None) -> Block:
@@ -2691,7 +2692,9 @@ class LocalClient(AbstractClient):
2691
2692
  Returns:
2692
2693
  block (Block): Updated block
2693
2694
  """
2694
- return self.server.update_block(UpdateBlock(id=block_id, template_name=name, value=text))
2695
+ return self.server.block_manager.update_block(
2696
+ block_id=block_id, block_update=BlockUpdate(template_name=name, value=text), actor=self.user
2697
+ )
2695
2698
 
2696
2699
  def get_block(self, block_id: str) -> Block:
2697
2700
  """
@@ -2703,7 +2706,7 @@ class LocalClient(AbstractClient):
2703
2706
  Returns:
2704
2707
  block (Block): Block
2705
2708
  """
2706
- return self.server.get_block(block_id)
2709
+ return self.server.block_manager.get_block_by_id(block_id, actor=self.user)
2707
2710
 
2708
2711
  def delete_block(self, id: str) -> Block:
2709
2712
  """
@@ -2715,7 +2718,7 @@ class LocalClient(AbstractClient):
2715
2718
  Returns:
2716
2719
  block (Block): Deleted block
2717
2720
  """
2718
- return self.server.delete_block(id)
2721
+ return self.server.block_manager.delete_block(id, actor=self.user)
2719
2722
 
2720
2723
  def set_default_llm_config(self, llm_config: LLMConfig):
2721
2724
  """
letta/constants.py CHANGED
@@ -158,3 +158,6 @@ RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE = 5
158
158
  # TODO Is this config or constant?
159
159
  CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 2000
160
160
  CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 2000
161
+
162
+ MAX_FILENAME_LENGTH = 255
163
+ RESERVED_FILENAMES = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"}
letta/metadata.py CHANGED
@@ -4,23 +4,13 @@ import os
4
4
  import secrets
5
5
  from typing import List, Optional
6
6
 
7
- from sqlalchemy import (
8
- BIGINT,
9
- JSON,
10
- Boolean,
11
- Column,
12
- DateTime,
13
- Index,
14
- String,
15
- TypeDecorator,
16
- )
7
+ from sqlalchemy import JSON, Column, DateTime, Index, String, TypeDecorator
17
8
  from sqlalchemy.sql import func
18
9
 
19
10
  from letta.config import LettaConfig
20
11
  from letta.orm.base import Base
21
12
  from letta.schemas.agent import AgentState
22
13
  from letta.schemas.api_key import APIKey
23
- from letta.schemas.block import Block, Human, Persona
24
14
  from letta.schemas.embedding_config import EmbeddingConfig
25
15
  from letta.schemas.enums import JobStatus
26
16
  from letta.schemas.job import Job
@@ -269,63 +259,6 @@ class AgentSourceMappingModel(Base):
269
259
  return f"<AgentSourceMapping(user_id='{self.user_id}', agent_id='{self.agent_id}', source_id='{self.source_id}')>"
270
260
 
271
261
 
272
- class BlockModel(Base):
273
- __tablename__ = "block"
274
- __table_args__ = {"extend_existing": True}
275
-
276
- id = Column(String, primary_key=True, nullable=False)
277
- value = Column(String, nullable=False)
278
- limit = Column(BIGINT)
279
- template_name = Column(String, nullable=True, default=None)
280
- template = Column(Boolean, default=False) # True: listed as possible human/persona
281
- label = Column(String, nullable=False)
282
- metadata_ = Column(JSON)
283
- description = Column(String)
284
- user_id = Column(String)
285
- Index(__tablename__ + "_idx_user", user_id),
286
-
287
- def __repr__(self) -> str:
288
- return f"<Block(id='{self.id}', template_name='{self.template_name}', template='{self.template_name}', label='{self.label}', user_id='{self.user_id}')>"
289
-
290
- def to_record(self) -> Block:
291
- if self.label == "persona":
292
- return Persona(
293
- id=self.id,
294
- value=self.value,
295
- limit=self.limit,
296
- template_name=self.template_name,
297
- template=self.template,
298
- label=self.label,
299
- metadata_=self.metadata_,
300
- description=self.description,
301
- user_id=self.user_id,
302
- )
303
- elif self.label == "human":
304
- return Human(
305
- id=self.id,
306
- value=self.value,
307
- limit=self.limit,
308
- template_name=self.template_name,
309
- template=self.template,
310
- label=self.label,
311
- metadata_=self.metadata_,
312
- description=self.description,
313
- user_id=self.user_id,
314
- )
315
- else:
316
- return Block(
317
- id=self.id,
318
- value=self.value,
319
- limit=self.limit,
320
- template_name=self.template_name,
321
- template=self.template,
322
- label=self.label,
323
- metadata_=self.metadata_,
324
- description=self.description,
325
- user_id=self.user_id,
326
- )
327
-
328
-
329
262
  class JobModel(Base):
330
263
  __tablename__ = "jobs"
331
264
  __table_args__ = {"extend_existing": True}
@@ -425,27 +358,6 @@ class MetadataStore:
425
358
  session.add(AgentModel(**fields))
426
359
  session.commit()
427
360
 
428
- @enforce_types
429
- def create_block(self, block: Block):
430
- with self.session_maker() as session:
431
- # TODO: fix?
432
- # we are only validating that more than one template block
433
- # with a given name doesn't exist.
434
- if (
435
- session.query(BlockModel)
436
- .filter(BlockModel.template_name == block.template_name)
437
- .filter(BlockModel.user_id == block.user_id)
438
- .filter(BlockModel.template == True)
439
- .filter(BlockModel.label == block.label)
440
- .count()
441
- > 0
442
- ):
443
-
444
- raise ValueError(f"Block with name {block.template_name} already exists")
445
-
446
- session.add(BlockModel(**vars(block)))
447
- session.commit()
448
-
449
361
  @enforce_types
450
362
  def update_agent(self, agent: AgentState):
451
363
  with self.session_maker() as session:
@@ -457,28 +369,6 @@ class MetadataStore:
457
369
  session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
458
370
  session.commit()
459
371
 
460
- @enforce_types
461
- def update_block(self, block: Block):
462
- with self.session_maker() as session:
463
- session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
464
- session.commit()
465
-
466
- @enforce_types
467
- def update_or_create_block(self, block: Block):
468
- with self.session_maker() as session:
469
- existing_block = session.query(BlockModel).filter(BlockModel.id == block.id).first()
470
- if existing_block:
471
- session.query(BlockModel).filter(BlockModel.id == block.id).update(vars(block))
472
- else:
473
- session.add(BlockModel(**vars(block)))
474
- session.commit()
475
-
476
- @enforce_types
477
- def delete_block(self, block_id: str):
478
- with self.session_maker() as session:
479
- session.query(BlockModel).filter(BlockModel.id == block_id).delete()
480
- session.commit()
481
-
482
372
  @enforce_types
483
373
  def delete_agent(self, agent_id: str):
484
374
  with self.session_maker() as session:
@@ -513,50 +403,6 @@ class MetadataStore:
513
403
  assert len(results) == 1, f"Expected 1 result, got {len(results)}" # should only be one result
514
404
  return results[0].to_record()
515
405
 
516
- @enforce_types
517
- def get_block(self, block_id: str) -> Optional[Block]:
518
- with self.session_maker() as session:
519
- results = session.query(BlockModel).filter(BlockModel.id == block_id).all()
520
- if len(results) == 0:
521
- return None
522
- assert len(results) == 1, f"Expected 1 result, got {len(results)}"
523
- return results[0].to_record()
524
-
525
- @enforce_types
526
- def get_blocks(
527
- self,
528
- user_id: Optional[str],
529
- label: Optional[str] = None,
530
- template: Optional[bool] = None,
531
- template_name: Optional[str] = None,
532
- id: Optional[str] = None,
533
- ) -> Optional[List[Block]]:
534
- """List available blocks"""
535
- with self.session_maker() as session:
536
- query = session.query(BlockModel)
537
-
538
- if user_id:
539
- query = query.filter(BlockModel.user_id == user_id)
540
-
541
- if label:
542
- query = query.filter(BlockModel.label == label)
543
-
544
- if template_name:
545
- query = query.filter(BlockModel.template_name == template_name)
546
-
547
- if id:
548
- query = query.filter(BlockModel.id == id)
549
-
550
- if template:
551
- query = query.filter(BlockModel.template == template)
552
-
553
- results = query.all()
554
-
555
- if len(results) == 0:
556
- return None
557
-
558
- return [r.to_record() for r in results]
559
-
560
406
  # agent source metadata
561
407
  @enforce_types
562
408
  def attach_source(self, user_id: str, agent_id: str, source_id: str):
letta/o1_agent.py CHANGED
@@ -8,6 +8,7 @@ from letta.schemas.message import Message
8
8
  from letta.schemas.openai.chat_completion_response import UsageStatistics
9
9
  from letta.schemas.tool import Tool
10
10
  from letta.schemas.usage import LettaUsageStatistics
11
+ from letta.schemas.user import User
11
12
 
12
13
 
13
14
  def send_thinking_message(self: "Agent", message: str) -> Optional[str]:
@@ -43,11 +44,12 @@ class O1Agent(Agent):
43
44
  self,
44
45
  interface: AgentInterface,
45
46
  agent_state: AgentState,
47
+ user: User,
46
48
  tools: List[Tool] = [],
47
49
  max_thinking_steps: int = 10,
48
50
  first_message_verify_mono: bool = False,
49
51
  ):
50
- super().__init__(interface, agent_state, tools)
52
+ super().__init__(interface, agent_state, tools, user)
51
53
  self.max_thinking_steps = max_thinking_steps
52
54
  self.tools = tools
53
55
  self.first_message_verify_mono = first_message_verify_mono
letta/orm/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from letta.orm.base import Base
2
+ from letta.orm.block import Block
2
3
  from letta.orm.file import FileMetadata
3
4
  from letta.orm.organization import Organization
4
5
  from letta.orm.source import Source
letta/orm/block.py ADDED
@@ -0,0 +1,44 @@
1
+ from typing import TYPE_CHECKING, Optional, Type
2
+
3
+ from sqlalchemy import JSON, BigInteger, Integer
4
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
5
+
6
+ from letta.orm.mixins import OrganizationMixin
7
+ from letta.orm.sqlalchemy_base import SqlalchemyBase
8
+ from letta.schemas.block import Block as PydanticBlock
9
+ from letta.schemas.block import Human, Persona
10
+
11
+ if TYPE_CHECKING:
12
+ from letta.orm.organization import Organization
13
+
14
+
15
+ class Block(OrganizationMixin, SqlalchemyBase):
16
+ """Blocks are sections of the LLM context, representing a specific part of the total Memory"""
17
+
18
+ __tablename__ = "block"
19
+ __pydantic_model__ = PydanticBlock
20
+
21
+ template_name: Mapped[Optional[str]] = mapped_column(
22
+ nullable=True, doc="the unique name that identifies a block in a human-readable way"
23
+ )
24
+ description: Mapped[Optional[str]] = mapped_column(nullable=True, doc="a description of the block for context")
25
+ label: Mapped[str] = mapped_column(doc="the type of memory block in use, ie 'human', 'persona', 'system'")
26
+ is_template: Mapped[bool] = mapped_column(
27
+ doc="whether the block is a template (e.g. saved human/persona options as baselines for other templates)", default=False
28
+ )
29
+ value: Mapped[str] = mapped_column(doc="Text content of the block for the respective section of core memory.")
30
+ limit: Mapped[BigInteger] = mapped_column(Integer, default=2000, doc="Character limit of the block.")
31
+ metadata_: Mapped[Optional[dict]] = mapped_column(JSON, default={}, doc="arbitrary information related to the block.")
32
+
33
+ # relationships
34
+ organization: Mapped[Optional["Organization"]] = relationship("Organization")
35
+
36
+ def to_pydantic(self) -> Type:
37
+ match self.label:
38
+ case "human":
39
+ Schema = Human
40
+ case "persona":
41
+ Schema = Persona
42
+ case _:
43
+ Schema = PydanticBlock
44
+ return Schema.model_validate(self)
letta/orm/organization.py CHANGED
@@ -23,6 +23,7 @@ class Organization(SqlalchemyBase):
23
23
  # relationships
24
24
  users: Mapped[List["User"]] = relationship("User", back_populates="organization", cascade="all, delete-orphan")
25
25
  tools: Mapped[List["Tool"]] = relationship("Tool", back_populates="organization", cascade="all, delete-orphan")
26
+ blocks: Mapped[List["Block"]] = relationship("Block", back_populates="organization", cascade="all, delete-orphan")
26
27
  sources: Mapped[List["Source"]] = relationship("Source", back_populates="organization", cascade="all, delete-orphan")
27
28
  agents_tags: Mapped[List["AgentsTags"]] = relationship("AgentsTags", back_populates="organization", cascade="all, delete-orphan")
28
29
  files: Mapped[List["FileMetadata"]] = relationship("FileMetadata", back_populates="organization", cascade="all, delete-orphan")