letta-nightly 0.5.4.dev20241127104220__py3-none-any.whl → 0.5.4.dev20241128104217__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.

Files changed (38) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +102 -140
  3. letta/agent_store/chroma.py +2 -0
  4. letta/cli/cli.py +3 -5
  5. letta/client/client.py +360 -117
  6. letta/config.py +2 -2
  7. letta/constants.py +5 -0
  8. letta/functions/function_sets/base.py +38 -1
  9. letta/helpers/tool_rule_solver.py +6 -5
  10. letta/main.py +1 -1
  11. letta/metadata.py +39 -41
  12. letta/o1_agent.py +1 -4
  13. letta/persistence_manager.py +1 -0
  14. letta/schemas/agent.py +57 -52
  15. letta/schemas/block.py +69 -25
  16. letta/schemas/enums.py +14 -0
  17. letta/schemas/letta_base.py +1 -1
  18. letta/schemas/letta_request.py +11 -23
  19. letta/schemas/letta_response.py +1 -2
  20. letta/schemas/memory.py +31 -100
  21. letta/schemas/message.py +3 -3
  22. letta/schemas/tool_rule.py +13 -5
  23. letta/server/rest_api/interface.py +12 -19
  24. letta/server/rest_api/routers/openai/assistants/threads.py +2 -3
  25. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -2
  26. letta/server/rest_api/routers/v1/agents.py +90 -86
  27. letta/server/rest_api/routers/v1/blocks.py +50 -5
  28. letta/server/server.py +237 -459
  29. letta/server/static_files/assets/index-9fa459a2.js +1 -1
  30. letta/services/block_manager.py +6 -3
  31. letta/services/blocks_agents_manager.py +15 -0
  32. letta/services/tool_execution_sandbox.py +1 -1
  33. letta/services/tool_manager.py +2 -1
  34. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/METADATA +1 -1
  35. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/RECORD +38 -38
  36. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/LICENSE +0 -0
  37. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/WHEEL +0 -0
  38. {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128104217.dist-info}/entry_points.txt +0 -0
letta/config.py CHANGED
@@ -16,7 +16,7 @@ from letta.constants import (
16
16
  LETTA_DIR,
17
17
  )
18
18
  from letta.log import get_logger
19
- from letta.schemas.agent import AgentState
19
+ from letta.schemas.agent import PersistedAgentState
20
20
  from letta.schemas.embedding_config import EmbeddingConfig
21
21
  from letta.schemas.llm_config import LLMConfig
22
22
 
@@ -434,7 +434,7 @@ class AgentConfig:
434
434
  json.dump(vars(self), f, indent=4)
435
435
 
436
436
  def to_agent_state(self):
437
- return AgentState(
437
+ return PersistedAgentState(
438
438
  name=self.name,
439
439
  preset=self.preset,
440
440
  persona=self.persona,
letta/constants.py CHANGED
@@ -38,6 +38,8 @@ DEFAULT_PRESET = "memgpt_chat"
38
38
 
39
39
  # Base tools that cannot be edited, as they access agent state directly
40
40
  BASE_TOOLS = ["send_message", "conversation_search", "conversation_search_date", "archival_memory_insert", "archival_memory_search"]
41
+ # Base memory tools CAN be edited, and are added by default by the server
42
+ BASE_MEMORY_TOOLS = ["core_memory_append", "core_memory_replace"]
41
43
 
42
44
  # The name of the tool used to send message to the user
43
45
  # May not be relevant in cases where the agent has multiple ways to message to user (send_imessage, send_discord_mesasge, ...)
@@ -127,6 +129,9 @@ MESSAGE_SUMMARY_REQUEST_ACK = "Understood, I will respond with a summary of the
127
129
  # These serve as in-context examples of how to use functions / what user messages look like
128
130
  MESSAGE_SUMMARY_TRUNC_KEEP_N_LAST = 3
129
131
 
132
+ # Maximum length of an error message
133
+ MAX_ERROR_MESSAGE_CHAR_LIMIT = 500
134
+
130
135
  # Default memory limits
131
136
  CORE_MEMORY_PERSONA_CHAR_LIMIT: int = 5000
132
137
  CORE_MEMORY_HUMAN_CHAR_LIMIT: int = 5000
@@ -11,7 +11,7 @@ from letta.constants import MAX_PAUSE_HEARTBEATS
11
11
  # If the function fails, throw an exception
12
12
 
13
13
 
14
- def send_message(self: Agent, message: str) -> Optional[str]:
14
+ def send_message(self: "Agent", message: str) -> Optional[str]:
15
15
  """
16
16
  Sends a message to the human user.
17
17
 
@@ -172,3 +172,40 @@ def archival_memory_search(self: Agent, query: str, page: Optional[int] = 0) ->
172
172
  results_formatted = [f"timestamp: {d['timestamp']}, memory: {d['content']}" for d in results]
173
173
  results_str = f"{results_pref} {json_dumps(results_formatted)}"
174
174
  return results_str
175
+
176
+
177
+ def core_memory_append(agent_state: "AgentState", label: str, content: str) -> Optional[str]: # type: ignore
178
+ """
179
+ Append to the contents of core memory.
180
+
181
+ Args:
182
+ label (str): Section of the memory to be edited (persona or human).
183
+ content (str): Content to write to the memory. All unicode (including emojis) are supported.
184
+
185
+ Returns:
186
+ Optional[str]: None is always returned as this function does not produce a response.
187
+ """
188
+ current_value = str(agent_state.memory.get_block(label).value)
189
+ new_value = current_value + "\n" + str(content)
190
+ agent_state.memory.update_block_value(label=label, value=new_value)
191
+ return None
192
+
193
+
194
+ def core_memory_replace(agent_state: "AgentState", label: str, old_content: str, new_content: str) -> Optional[str]: # type: ignore
195
+ """
196
+ Replace the contents of core memory. To delete memories, use an empty string for new_content.
197
+
198
+ Args:
199
+ label (str): Section of the memory to be edited (persona or human).
200
+ old_content (str): String to replace. Must be an exact match.
201
+ new_content (str): Content to write to the memory. All unicode (including emojis) are supported.
202
+
203
+ Returns:
204
+ Optional[str]: None is always returned as this function does not produce a response.
205
+ """
206
+ current_value = str(agent_state.memory.get_block(label).value)
207
+ if old_content not in current_value:
208
+ raise ValueError(f"Old content '{old_content}' not found in memory block '{label}'")
209
+ new_value = current_value.replace(str(old_content), str(new_content))
210
+ agent_state.memory.update_block_value(label=label, value=new_value)
211
+ return None
@@ -2,11 +2,12 @@ from typing import Dict, List, Optional, Set
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
+ from letta.schemas.enums import ToolRuleType
5
6
  from letta.schemas.tool_rule import (
6
7
  BaseToolRule,
8
+ ChildToolRule,
7
9
  InitToolRule,
8
10
  TerminalToolRule,
9
- ToolRule,
10
11
  )
11
12
 
12
13
 
@@ -21,7 +22,7 @@ class ToolRulesSolver(BaseModel):
21
22
  init_tool_rules: List[InitToolRule] = Field(
22
23
  default_factory=list, description="Initial tool rules to be used at the start of tool execution."
23
24
  )
24
- tool_rules: List[ToolRule] = Field(
25
+ tool_rules: List[ChildToolRule] = Field(
25
26
  default_factory=list, description="Standard tool rules for controlling execution sequence and allowed transitions."
26
27
  )
27
28
  terminal_tool_rules: List[TerminalToolRule] = Field(
@@ -33,11 +34,11 @@ class ToolRulesSolver(BaseModel):
33
34
  super().__init__(**kwargs)
34
35
  # Separate the provided tool rules into init, standard, and terminal categories
35
36
  for rule in tool_rules:
36
- if isinstance(rule, InitToolRule):
37
+ if rule.type == ToolRuleType.run_first:
37
38
  self.init_tool_rules.append(rule)
38
- elif isinstance(rule, ToolRule):
39
+ elif rule.type == ToolRuleType.constrain_child_tools:
39
40
  self.tool_rules.append(rule)
40
- elif isinstance(rule, TerminalToolRule):
41
+ elif rule.type == ToolRuleType.exit_loop:
41
42
  self.terminal_tool_rules.append(rule)
42
43
 
43
44
  # Validate the tool rules to ensure they form a DAG
letta/main.py CHANGED
@@ -189,7 +189,7 @@ def run_agent_loop(
189
189
 
190
190
  elif user_input.lower() == "/memory":
191
191
  print(f"\nDumping memory contents:\n")
192
- print(f"{letta_agent.memory.compile()}")
192
+ print(f"{letta_agent.agent_state.memory.compile()}")
193
193
  print(f"{letta_agent.persistence_manager.archival_memory.compile()}")
194
194
  print(f"{letta_agent.persistence_manager.recall_memory.compile()}")
195
195
  continue
letta/metadata.py CHANGED
@@ -3,27 +3,21 @@
3
3
  import os
4
4
  import secrets
5
5
  import warnings
6
- from typing import List, Optional
6
+ from typing import List, Optional, Union
7
7
 
8
8
  from sqlalchemy import JSON, Column, DateTime, Index, String, TypeDecorator
9
9
  from sqlalchemy.sql import func
10
10
 
11
11
  from letta.config import LettaConfig
12
12
  from letta.orm.base import Base
13
- from letta.schemas.agent import AgentState
13
+ from letta.schemas.agent import PersistedAgentState
14
14
  from letta.schemas.api_key import APIKey
15
15
  from letta.schemas.embedding_config import EmbeddingConfig
16
- from letta.schemas.enums import JobStatus
16
+ from letta.schemas.enums import JobStatus, ToolRuleType
17
17
  from letta.schemas.job import Job
18
18
  from letta.schemas.llm_config import LLMConfig
19
- from letta.schemas.memory import Memory
20
19
  from letta.schemas.openai.chat_completions import ToolCall, ToolCallFunction
21
- from letta.schemas.tool_rule import (
22
- BaseToolRule,
23
- InitToolRule,
24
- TerminalToolRule,
25
- ToolRule,
26
- )
20
+ from letta.schemas.tool_rule import ChildToolRule, InitToolRule, TerminalToolRule
27
21
  from letta.schemas.user import User
28
22
  from letta.services.per_agent_lock_manager import PerAgentLockManager
29
23
  from letta.settings import settings
@@ -165,28 +159,35 @@ class ToolRulesColumn(TypeDecorator):
165
159
  def load_dialect_impl(self, dialect):
166
160
  return dialect.type_descriptor(JSON())
167
161
 
168
- def process_bind_param(self, value: List[BaseToolRule], dialect):
162
+ def process_bind_param(self, value, dialect):
169
163
  """Convert a list of ToolRules to JSON-serializable format."""
170
164
  if value:
171
- return [rule.model_dump() for rule in value]
165
+ data = [rule.model_dump() for rule in value]
166
+ for d in data:
167
+ d["type"] = d["type"].value
168
+
169
+ for d in data:
170
+ assert not (d["type"] == "ToolRule" and "children" not in d), "ToolRule does not have children field"
171
+ return data
172
172
  return value
173
173
 
174
- def process_result_value(self, value, dialect) -> List[BaseToolRule]:
174
+ def process_result_value(self, value, dialect) -> List[Union[ChildToolRule, InitToolRule, TerminalToolRule]]:
175
175
  """Convert JSON back to a list of ToolRules."""
176
176
  if value:
177
177
  return [self.deserialize_tool_rule(rule_data) for rule_data in value]
178
178
  return value
179
179
 
180
180
  @staticmethod
181
- def deserialize_tool_rule(data: dict) -> BaseToolRule:
181
+ def deserialize_tool_rule(data: dict) -> Union[ChildToolRule, InitToolRule, TerminalToolRule]:
182
182
  """Deserialize a dictionary to the appropriate ToolRule subclass based on the 'type'."""
183
- rule_type = data.get("type") # Remove 'type' field if it exists since it is a class var
184
- if rule_type == "InitToolRule":
183
+ rule_type = ToolRuleType(data.get("type")) # Remove 'type' field if it exists since it is a class var
184
+ if rule_type == ToolRuleType.run_first:
185
185
  return InitToolRule(**data)
186
- elif rule_type == "TerminalToolRule":
186
+ elif rule_type == ToolRuleType.exit_loop:
187
187
  return TerminalToolRule(**data)
188
- elif rule_type == "ToolRule":
189
- return ToolRule(**data)
188
+ elif rule_type == ToolRuleType.constrain_child_tools:
189
+ rule = ChildToolRule(**data)
190
+ return rule
190
191
  else:
191
192
  raise ValueError(f"Unknown tool rule type: {rule_type}")
192
193
 
@@ -205,7 +206,6 @@ class AgentModel(Base):
205
206
 
206
207
  # state (context compilation)
207
208
  message_ids = Column(JSON)
208
- memory = Column(JSON)
209
209
  system = Column(String)
210
210
 
211
211
  # configs
@@ -217,7 +217,7 @@ class AgentModel(Base):
217
217
  metadata_ = Column(JSON)
218
218
 
219
219
  # tools
220
- tools = Column(JSON)
220
+ tool_names = Column(JSON)
221
221
  tool_rules = Column(ToolRulesColumn)
222
222
 
223
223
  Index(__tablename__ + "_idx_user", user_id),
@@ -225,24 +225,22 @@ class AgentModel(Base):
225
225
  def __repr__(self) -> str:
226
226
  return f"<Agent(id='{self.id}', name='{self.name}')>"
227
227
 
228
- def to_record(self) -> AgentState:
229
- agent_state = AgentState(
228
+ def to_record(self) -> PersistedAgentState:
229
+ agent_state = PersistedAgentState(
230
230
  id=self.id,
231
231
  user_id=self.user_id,
232
232
  name=self.name,
233
233
  created_at=self.created_at,
234
234
  description=self.description,
235
235
  message_ids=self.message_ids,
236
- memory=Memory.load(self.memory), # load dictionary
237
236
  system=self.system,
238
- tools=self.tools,
237
+ tool_names=self.tool_names,
239
238
  tool_rules=self.tool_rules,
240
239
  agent_type=self.agent_type,
241
240
  llm_config=self.llm_config,
242
241
  embedding_config=self.embedding_config,
243
242
  metadata_=self.metadata_,
244
243
  )
245
- assert isinstance(agent_state.memory, Memory), f"Memory object is not of type Memory: {type(agent_state.memory)}"
246
244
  return agent_state
247
245
 
248
246
 
@@ -347,18 +345,18 @@ class MetadataStore:
347
345
  return tokens
348
346
 
349
347
  @enforce_types
350
- def create_agent(self, agent: AgentState):
348
+ def create_agent(self, agent: PersistedAgentState):
351
349
  # insert into agent table
352
350
  # make sure agent.name does not already exist for user user_id
353
351
  with self.session_maker() as session:
354
352
  if session.query(AgentModel).filter(AgentModel.name == agent.name).filter(AgentModel.user_id == agent.user_id).count() > 0:
355
353
  raise ValueError(f"Agent with name {agent.name} already exists")
356
354
  fields = vars(agent)
357
- fields["memory"] = agent.memory.to_dict()
358
- if "_internal_memory" in fields:
359
- del fields["_internal_memory"]
360
- else:
361
- warnings.warn(f"Agent {agent.id} has no _internal_memory field")
355
+ # fields["memory"] = agent.memory.to_dict()
356
+ # if "_internal_memory" in fields:
357
+ # del fields["_internal_memory"]
358
+ # else:
359
+ # warnings.warn(f"Agent {agent.id} has no _internal_memory field")
362
360
  if "tags" in fields:
363
361
  del fields["tags"]
364
362
  else:
@@ -367,15 +365,15 @@ class MetadataStore:
367
365
  session.commit()
368
366
 
369
367
  @enforce_types
370
- def update_agent(self, agent: AgentState):
368
+ def update_agent(self, agent: PersistedAgentState):
371
369
  with self.session_maker() as session:
372
370
  fields = vars(agent)
373
- if isinstance(agent.memory, Memory): # TODO: this is nasty but this whole class will soon be removed so whatever
374
- fields["memory"] = agent.memory.to_dict()
375
- if "_internal_memory" in fields:
376
- del fields["_internal_memory"]
377
- else:
378
- warnings.warn(f"Agent {agent.id} has no _internal_memory field")
371
+ # if isinstance(agent.memory, Memory): # TODO: this is nasty but this whole class will soon be removed so whatever
372
+ # fields["memory"] = agent.memory.to_dict()
373
+ # if "_internal_memory" in fields:
374
+ # del fields["_internal_memory"]
375
+ # else:
376
+ # warnings.warn(f"Agent {agent.id} has no _internal_memory field")
379
377
  if "tags" in fields:
380
378
  del fields["tags"]
381
379
  else:
@@ -400,7 +398,7 @@ class MetadataStore:
400
398
  session.commit()
401
399
 
402
400
  @enforce_types
403
- def list_agents(self, user_id: str) -> List[AgentState]:
401
+ def list_agents(self, user_id: str) -> List[PersistedAgentState]:
404
402
  with self.session_maker() as session:
405
403
  results = session.query(AgentModel).filter(AgentModel.user_id == user_id).all()
406
404
  return [r.to_record() for r in results]
@@ -408,7 +406,7 @@ class MetadataStore:
408
406
  @enforce_types
409
407
  def get_agent(
410
408
  self, agent_id: Optional[str] = None, agent_name: Optional[str] = None, user_id: Optional[str] = None
411
- ) -> Optional[AgentState]:
409
+ ) -> Optional[PersistedAgentState]:
412
410
  with self.session_maker() as session:
413
411
  if agent_id:
414
412
  results = session.query(AgentModel).filter(AgentModel.id == agent_id).all()
letta/o1_agent.py CHANGED
@@ -6,7 +6,6 @@ from letta.metadata import MetadataStore
6
6
  from letta.schemas.agent import AgentState
7
7
  from letta.schemas.message import Message
8
8
  from letta.schemas.openai.chat_completion_response import UsageStatistics
9
- from letta.schemas.tool import Tool
10
9
  from letta.schemas.usage import LettaUsageStatistics
11
10
  from letta.schemas.user import User
12
11
 
@@ -45,13 +44,11 @@ class O1Agent(Agent):
45
44
  interface: AgentInterface,
46
45
  agent_state: AgentState,
47
46
  user: User,
48
- tools: List[Tool] = [],
49
47
  max_thinking_steps: int = 10,
50
48
  first_message_verify_mono: bool = False,
51
49
  ):
52
- super().__init__(interface, agent_state, tools, user)
50
+ super().__init__(interface, agent_state, user)
53
51
  self.max_thinking_steps = max_thinking_steps
54
- self.tools = tools
55
52
  self.first_message_verify_mono = first_message_verify_mono
56
53
 
57
54
  def step(
@@ -121,6 +121,7 @@ class LocalStateManager(PersistenceManager):
121
121
  # self.messages = [self.messages[0]] + added_messages + self.messages[1:]
122
122
 
123
123
  # add to recall memory
124
+ self.recall_memory.insert_many([m for m in added_messages])
124
125
 
125
126
  def append_to_messages(self, added_messages: List[Message]):
126
127
  # first tag with timestamps
letta/schemas/agent.py CHANGED
@@ -2,15 +2,18 @@ from datetime import datetime
2
2
  from enum import Enum
3
3
  from typing import Dict, List, Optional
4
4
 
5
- from pydantic import BaseModel, Field, field_validator, model_validator
5
+ from pydantic import BaseModel, Field, field_validator
6
6
 
7
+ from letta.schemas.block import CreateBlock
7
8
  from letta.schemas.embedding_config import EmbeddingConfig
8
9
  from letta.schemas.letta_base import LettaBase
9
10
  from letta.schemas.llm_config import LLMConfig
10
11
  from letta.schemas.memory import Memory
11
12
  from letta.schemas.message import Message
12
13
  from letta.schemas.openai.chat_completion_response import UsageStatistics
13
- from letta.schemas.tool_rule import BaseToolRule
14
+ from letta.schemas.source import Source
15
+ from letta.schemas.tool import Tool
16
+ from letta.schemas.tool_rule import ToolRule
14
17
 
15
18
 
16
19
  class BaseAgent(LettaBase, validate_assignment=True):
@@ -32,23 +35,8 @@ class AgentType(str, Enum):
32
35
  o1_agent = "o1_agent"
33
36
 
34
37
 
35
- class AgentState(BaseAgent, validate_assignment=True):
36
- """
37
- Representation of an agent's state. This is the state of the agent at a given time, and is persisted in the DB backend. The state has all the information needed to recreate a persisted agent.
38
-
39
- Parameters:
40
- id (str): The unique identifier of the agent.
41
- name (str): The name of the agent (must be unique to the user).
42
- created_at (datetime): The datetime the agent was created.
43
- message_ids (List[str]): The ids of the messages in the agent's in-context memory.
44
- memory (Memory): The in-context memory of the agent.
45
- tools (List[str]): The tools used by the agent. This includes any memory editing functions specified in `memory`.
46
- system (str): The system prompt used by the agent.
47
- llm_config (LLMConfig): The LLM configuration used by the agent.
48
- embedding_config (EmbeddingConfig): The embedding configuration used by the agent.
49
-
50
- """
51
-
38
+ class PersistedAgentState(BaseAgent, validate_assignment=True):
39
+ # NOTE: this has been changed to represent the data stored in the ORM, NOT what is passed around internally or returned to the user
52
40
  id: str = BaseAgent.generate_id_field()
53
41
  name: str = Field(..., description="The name of the agent.")
54
42
  created_at: datetime = Field(..., description="The datetime the agent was created.", default_factory=datetime.now)
@@ -56,16 +44,12 @@ class AgentState(BaseAgent, validate_assignment=True):
56
44
  # in-context memory
57
45
  message_ids: Optional[List[str]] = Field(default=None, description="The ids of the messages in the agent's in-context memory.")
58
46
 
59
- memory: Memory = Field(default_factory=Memory, description="The in-context memory of the agent.")
60
-
61
47
  # tools
62
- tools: List[str] = Field(..., description="The tools used by the agent.")
48
+ # TODO: move to ORM mapping
49
+ tool_names: List[str] = Field(..., description="The tools used by the agent.")
63
50
 
64
51
  # tool rules
65
- tool_rules: Optional[List[BaseToolRule]] = Field(default=None, description="The list of tool rules.")
66
-
67
- # tags
68
- tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
52
+ tool_rules: Optional[List[ToolRule]] = Field(default=None, description="The list of tool rules.")
69
53
 
70
54
  # system prompt
71
55
  system: str = Field(..., description="The system prompt used by the agent.")
@@ -77,40 +61,62 @@ class AgentState(BaseAgent, validate_assignment=True):
77
61
  llm_config: LLMConfig = Field(..., description="The LLM configuration used by the agent.")
78
62
  embedding_config: EmbeddingConfig = Field(..., description="The embedding configuration used by the agent.")
79
63
 
80
- def __init__(self, **data):
81
- super().__init__(**data)
82
- self._internal_memory = self.memory
64
+ class Config:
65
+ arbitrary_types_allowed = True
66
+ validate_assignment = True
83
67
 
84
- @model_validator(mode="after")
85
- def verify_memory_type(self):
86
- try:
87
- assert isinstance(self.memory, Memory)
88
- except Exception as e:
89
- raise e
90
- return self
91
68
 
92
- @property
93
- def memory(self) -> Memory:
94
- return self._internal_memory
69
+ class AgentState(PersistedAgentState):
70
+ """
71
+ Representation of an agent's state. This is the state of the agent at a given time, and is persisted in the DB backend. The state has all the information needed to recreate a persisted agent.
72
+
73
+ Parameters:
74
+ id (str): The unique identifier of the agent.
75
+ name (str): The name of the agent (must be unique to the user).
76
+ created_at (datetime): The datetime the agent was created.
77
+ message_ids (List[str]): The ids of the messages in the agent's in-context memory.
78
+ memory (Memory): The in-context memory of the agent.
79
+ tools (List[str]): The tools used by the agent. This includes any memory editing functions specified in `memory`.
80
+ system (str): The system prompt used by the agent.
81
+ llm_config (LLMConfig): The LLM configuration used by the agent.
82
+ embedding_config (EmbeddingConfig): The embedding configuration used by the agent.
95
83
 
96
- @memory.setter
97
- def memory(self, value):
98
- if not isinstance(value, Memory):
99
- raise TypeError(f"Expected Memory, got {type(value).__name__}")
100
- self._internal_memory = value
84
+ """
101
85
 
102
- class Config:
103
- arbitrary_types_allowed = True
104
- validate_assignment = True
86
+ # NOTE: this is what is returned to the client and also what is used to initialize `Agent`
87
+
88
+ # This is an object representing the in-process state of a running `Agent`
89
+ # Field in this object can be theoretically edited by tools, and will be persisted by the ORM
90
+ memory: Memory = Field(..., description="The in-context memory of the agent.")
91
+ tools: List[Tool] = Field(..., description="The tools used by the agent.")
92
+ sources: List[Source] = Field(..., description="The sources used by the agent.")
93
+ tags: List[str] = Field(..., description="The tags associated with the agent.")
94
+ # TODO: add in context message objects
105
95
 
96
+ def to_persisted_agent_state(self) -> PersistedAgentState:
97
+ # turn back into persisted agent
98
+ data = self.model_dump()
99
+ del data["memory"]
100
+ del data["tools"]
101
+ del data["sources"]
102
+ del data["tags"]
103
+ return PersistedAgentState(**data)
106
104
 
107
- class CreateAgent(BaseAgent):
105
+
106
+ class CreateAgent(BaseAgent): #
108
107
  # all optional as server can generate defaults
109
108
  name: Optional[str] = Field(None, description="The name of the agent.")
110
109
  message_ids: Optional[List[str]] = Field(None, description="The ids of the messages in the agent's in-context memory.")
111
- memory: Optional[Memory] = Field(None, description="The in-context memory of the agent.")
110
+
111
+ # memory creation
112
+ memory_blocks: List[CreateBlock] = Field(
113
+ # [CreateHuman(), CreatePersona()], description="The blocks to create in the agent's in-context memory."
114
+ ...,
115
+ description="The blocks to create in the agent's in-context memory.",
116
+ )
117
+
112
118
  tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
113
- tool_rules: Optional[List[BaseToolRule]] = Field(None, description="The tool rules governing the agent.")
119
+ tool_rules: Optional[List[ToolRule]] = Field(None, description="The tool rules governing the agent.")
114
120
  tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
115
121
  system: Optional[str] = Field(None, description="The system prompt used by the agent.")
116
122
  agent_type: Optional[AgentType] = Field(None, description="The type of agent.")
@@ -151,7 +157,7 @@ class CreateAgent(BaseAgent):
151
157
  class UpdateAgentState(BaseAgent):
152
158
  id: str = Field(..., description="The id of the agent.")
153
159
  name: Optional[str] = Field(None, description="The name of the agent.")
154
- tools: Optional[List[str]] = Field(None, description="The tools used by the agent.")
160
+ tool_names: Optional[List[str]] = Field(None, description="The tools used by the agent.")
155
161
  tags: Optional[List[str]] = Field(None, description="The tags associated with the agent.")
156
162
  system: Optional[str] = Field(None, description="The system prompt used by the agent.")
157
163
  llm_config: Optional[LLMConfig] = Field(None, description="The LLM configuration used by the agent.")
@@ -159,7 +165,6 @@ class UpdateAgentState(BaseAgent):
159
165
 
160
166
  # TODO: determine if these should be editable via this schema?
161
167
  message_ids: Optional[List[str]] = Field(None, description="The ids of the messages in the agent's in-context memory.")
162
- memory: Optional[Memory] = Field(None, description="The in-context memory of the agent.")
163
168
 
164
169
 
165
170
  class AgentStepResponse(BaseModel):
letta/schemas/block.py CHANGED
@@ -28,6 +28,12 @@ class BaseBlock(LettaBase, validate_assignment=True):
28
28
  description: Optional[str] = Field(None, description="Description of the block.")
29
29
  metadata_: Optional[dict] = Field({}, description="Metadata of the block.")
30
30
 
31
+ # def __len__(self):
32
+ # return len(self.value)
33
+
34
+ class Config:
35
+ extra = "ignore" # Ignores extra fields
36
+
31
37
  @model_validator(mode="after")
32
38
  def verify_char_limit(self) -> Self:
33
39
  if self.value and len(self.value) > self.limit:
@@ -36,9 +42,6 @@ class BaseBlock(LettaBase, validate_assignment=True):
36
42
 
37
43
  return self
38
44
 
39
- # def __len__(self):
40
- # return len(self.value)
41
-
42
45
  def __setattr__(self, name, value):
43
46
  """Run validation if self.value is updated"""
44
47
  super().__setattr__(name, value)
@@ -46,9 +49,6 @@ class BaseBlock(LettaBase, validate_assignment=True):
46
49
  # run validation
47
50
  self.__class__.model_validate(self.model_dump(exclude_unset=True))
48
51
 
49
- class Config:
50
- extra = "ignore" # Ignores extra fields
51
-
52
52
 
53
53
  class Block(BaseBlock):
54
54
  """
@@ -88,11 +88,11 @@ class Persona(Block):
88
88
  label: str = "persona"
89
89
 
90
90
 
91
- class BlockCreate(BaseBlock):
92
- """Create a block"""
93
-
94
- is_template: bool = True
95
- label: str = Field(..., description="Label of the block.")
91
+ # class CreateBlock(BaseBlock):
92
+ # """Create a block"""
93
+ #
94
+ # is_template: bool = True
95
+ # label: str = Field(..., description="Label of the block.")
96
96
 
97
97
 
98
98
  class BlockLabelUpdate(BaseModel):
@@ -102,16 +102,16 @@ class BlockLabelUpdate(BaseModel):
102
102
  new_label: str = Field(..., description="New label of the block.")
103
103
 
104
104
 
105
- class CreatePersona(BlockCreate):
106
- """Create a persona block"""
107
-
108
- label: str = "persona"
109
-
110
-
111
- class CreateHuman(BlockCreate):
112
- """Create a human block"""
113
-
114
- label: str = "human"
105
+ # class CreatePersona(CreateBlock):
106
+ # """Create a persona block"""
107
+ #
108
+ # label: str = "persona"
109
+ #
110
+ #
111
+ # class CreateHuman(CreateBlock):
112
+ # """Create a human block"""
113
+ #
114
+ # label: str = "human"
115
115
 
116
116
 
117
117
  class BlockUpdate(BaseBlock):
@@ -131,13 +131,57 @@ class BlockLimitUpdate(BaseModel):
131
131
  limit: int = Field(..., description="New limit of the block.")
132
132
 
133
133
 
134
- class UpdatePersona(BlockUpdate):
135
- """Update a persona block"""
134
+ # class UpdatePersona(BlockUpdate):
135
+ # """Update a persona block"""
136
+ #
137
+ # label: str = "persona"
138
+ #
139
+ #
140
+ # class UpdateHuman(BlockUpdate):
141
+ # """Update a human block"""
142
+ #
143
+ # label: str = "human"
144
+
145
+
146
+ class CreateBlock(BaseBlock):
147
+ """Create a block"""
148
+
149
+ label: str = Field(..., description="Label of the block.")
150
+ limit: int = Field(2000, description="Character limit of the block.")
151
+ value: str = Field(..., description="Value of the block.")
152
+
153
+ # block templates
154
+ is_template: bool = False
155
+ template_name: Optional[str] = Field(None, description="Name of the block if it is a template.", alias="name")
156
+
157
+
158
+ class CreateHuman(CreateBlock):
159
+ """Create a human block"""
160
+
161
+ label: str = "human"
162
+
163
+
164
+ class CreatePersona(CreateBlock):
165
+ """Create a persona block"""
136
166
 
137
167
  label: str = "persona"
138
168
 
139
169
 
140
- class UpdateHuman(BlockUpdate):
141
- """Update a human block"""
170
+ class CreateBlockTemplate(CreateBlock):
171
+ """Create a block template"""
172
+
173
+ is_template: bool = True
174
+
175
+
176
+ class CreateHumanBlockTemplate(CreateHuman):
177
+ """Create a human block template"""
142
178
 
179
+ is_template: bool = True
143
180
  label: str = "human"
181
+
182
+
183
+ class CreatePersonaBlockTemplate(CreatePersona):
184
+ """Create a persona block template"""
185
+
186
+ is_template: bool = True
187
+ label: str = "persona"