letta-nightly 0.5.4.dev20241127104220__py3-none-any.whl → 0.5.4.dev20241128000451__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 +1 -1
- letta/agent.py +102 -140
- letta/agent_store/chroma.py +2 -0
- letta/cli/cli.py +3 -5
- letta/client/client.py +360 -117
- letta/config.py +2 -2
- letta/constants.py +5 -0
- letta/functions/function_sets/base.py +38 -1
- letta/helpers/tool_rule_solver.py +6 -5
- letta/main.py +1 -1
- letta/metadata.py +39 -41
- letta/o1_agent.py +1 -4
- letta/persistence_manager.py +1 -0
- letta/schemas/agent.py +57 -52
- letta/schemas/block.py +69 -25
- letta/schemas/enums.py +14 -0
- letta/schemas/letta_base.py +1 -1
- letta/schemas/letta_request.py +11 -23
- letta/schemas/letta_response.py +1 -2
- letta/schemas/memory.py +31 -100
- letta/schemas/message.py +3 -3
- letta/schemas/tool_rule.py +13 -5
- letta/server/rest_api/interface.py +12 -19
- letta/server/rest_api/routers/openai/assistants/threads.py +2 -3
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -2
- letta/server/rest_api/routers/v1/agents.py +90 -86
- letta/server/rest_api/routers/v1/blocks.py +50 -5
- letta/server/server.py +237 -459
- letta/server/static_files/assets/index-9fa459a2.js +1 -1
- letta/services/block_manager.py +6 -3
- letta/services/blocks_agents_manager.py +15 -0
- letta/services/tool_execution_sandbox.py +1 -1
- letta/services/tool_manager.py +2 -1
- {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/RECORD +38 -38
- {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.4.dev20241127104220.dist-info → letta_nightly-0.5.4.dev20241128000451.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
|
|
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
|
|
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[
|
|
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
|
|
37
|
+
if rule.type == ToolRuleType.run_first:
|
|
37
38
|
self.init_tool_rules.append(rule)
|
|
38
|
-
elif
|
|
39
|
+
elif rule.type == ToolRuleType.constrain_child_tools:
|
|
39
40
|
self.tool_rules.append(rule)
|
|
40
|
-
elif
|
|
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
|
|
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
|
|
162
|
+
def process_bind_param(self, value, dialect):
|
|
169
163
|
"""Convert a list of ToolRules to JSON-serializable format."""
|
|
170
164
|
if value:
|
|
171
|
-
|
|
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[
|
|
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) ->
|
|
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 ==
|
|
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 ==
|
|
186
|
+
elif rule_type == ToolRuleType.exit_loop:
|
|
187
187
|
return TerminalToolRule(**data)
|
|
188
|
-
elif rule_type ==
|
|
189
|
-
|
|
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
|
-
|
|
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) ->
|
|
229
|
-
agent_state =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
360
|
-
else:
|
|
361
|
-
|
|
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:
|
|
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
|
-
|
|
375
|
-
if "_internal_memory" in fields:
|
|
376
|
-
|
|
377
|
-
else:
|
|
378
|
-
|
|
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[
|
|
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[
|
|
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,
|
|
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(
|
letta/persistence_manager.py
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
|
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(
|
|
106
|
-
"""Create a persona block"""
|
|
107
|
-
|
|
108
|
-
label: str = "persona"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class CreateHuman(
|
|
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
|
|
141
|
-
"""
|
|
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"
|