letta-nightly 0.6.4.dev20241213193437__py3-none-any.whl → 0.6.4.dev20241214104034__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 +54 -45
- letta/chat_only_agent.py +6 -8
- letta/cli/cli.py +2 -10
- letta/client/client.py +121 -138
- letta/config.py +0 -161
- letta/main.py +3 -8
- letta/memory.py +3 -14
- letta/o1_agent.py +1 -5
- letta/offline_memory_agent.py +2 -6
- letta/orm/__init__.py +2 -0
- letta/orm/agent.py +109 -0
- letta/orm/agents_tags.py +10 -18
- letta/orm/block.py +29 -4
- letta/orm/blocks_agents.py +5 -11
- letta/orm/custom_columns.py +152 -0
- letta/orm/message.py +3 -38
- letta/orm/organization.py +2 -7
- letta/orm/passage.py +10 -32
- letta/orm/source.py +5 -25
- letta/orm/sources_agents.py +13 -0
- letta/orm/sqlalchemy_base.py +54 -30
- letta/orm/tool.py +1 -19
- letta/orm/tools_agents.py +7 -24
- letta/orm/user.py +3 -4
- letta/schemas/agent.py +48 -65
- letta/schemas/memory.py +2 -1
- letta/schemas/sandbox_config.py +12 -1
- letta/server/rest_api/app.py +0 -5
- letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +1 -1
- letta/server/rest_api/routers/v1/agents.py +99 -78
- letta/server/rest_api/routers/v1/blocks.py +22 -25
- letta/server/rest_api/routers/v1/jobs.py +4 -4
- letta/server/rest_api/routers/v1/sandbox_configs.py +10 -10
- letta/server/rest_api/routers/v1/sources.py +12 -12
- letta/server/rest_api/routers/v1/tools.py +35 -15
- letta/server/rest_api/routers/v1/users.py +0 -46
- letta/server/server.py +172 -716
- letta/server/ws_api/server.py +0 -5
- letta/services/agent_manager.py +405 -0
- letta/services/block_manager.py +13 -21
- letta/services/helpers/agent_manager_helper.py +90 -0
- letta/services/organization_manager.py +0 -1
- letta/services/passage_manager.py +62 -62
- letta/services/sandbox_config_manager.py +3 -3
- letta/services/source_manager.py +22 -1
- letta/services/user_manager.py +11 -6
- letta/utils.py +2 -2
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/RECORD +53 -57
- letta/metadata.py +0 -407
- letta/schemas/agents_tags.py +0 -33
- letta/schemas/api_key.py +0 -21
- letta/schemas/blocks_agents.py +0 -32
- letta/schemas/tools_agents.py +0 -32
- letta/server/rest_api/routers/openai/assistants/threads.py +0 -338
- letta/services/agents_tags_manager.py +0 -64
- letta/services/blocks_agents_manager.py +0 -106
- letta/services/tools_agents_manager.py +0 -94
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.4.dev20241213193437.dist-info → letta_nightly-0.6.4.dev20241214104034.dist-info}/entry_points.txt +0 -0
letta/config.py
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import configparser
|
|
2
|
-
import inspect
|
|
3
|
-
import json
|
|
4
2
|
import os
|
|
5
3
|
from dataclasses import dataclass
|
|
6
4
|
from typing import Optional
|
|
7
5
|
|
|
8
6
|
import letta
|
|
9
|
-
import letta.utils as utils
|
|
10
7
|
from letta.constants import (
|
|
11
8
|
CORE_MEMORY_HUMAN_CHAR_LIMIT,
|
|
12
9
|
CORE_MEMORY_PERSONA_CHAR_LIMIT,
|
|
@@ -16,7 +13,6 @@ from letta.constants import (
|
|
|
16
13
|
LETTA_DIR,
|
|
17
14
|
)
|
|
18
15
|
from letta.log import get_logger
|
|
19
|
-
from letta.schemas.agent import PersistedAgentState
|
|
20
16
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
21
17
|
from letta.schemas.llm_config import LLMConfig
|
|
22
18
|
|
|
@@ -312,160 +308,3 @@ class LettaConfig:
|
|
|
312
308
|
for folder in folders:
|
|
313
309
|
if not os.path.exists(os.path.join(LETTA_DIR, folder)):
|
|
314
310
|
os.makedirs(os.path.join(LETTA_DIR, folder))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
@dataclass
|
|
318
|
-
class AgentConfig:
|
|
319
|
-
"""
|
|
320
|
-
|
|
321
|
-
NOTE: this is a deprecated class, use AgentState instead. This class is only used for backcompatibility.
|
|
322
|
-
Configuration for a specific instance of an agent
|
|
323
|
-
"""
|
|
324
|
-
|
|
325
|
-
def __init__(
|
|
326
|
-
self,
|
|
327
|
-
persona,
|
|
328
|
-
human,
|
|
329
|
-
# model info
|
|
330
|
-
model=None,
|
|
331
|
-
model_endpoint_type=None,
|
|
332
|
-
model_endpoint=None,
|
|
333
|
-
model_wrapper=None,
|
|
334
|
-
context_window=None,
|
|
335
|
-
# embedding info
|
|
336
|
-
embedding_endpoint_type=None,
|
|
337
|
-
embedding_endpoint=None,
|
|
338
|
-
embedding_model=None,
|
|
339
|
-
embedding_dim=None,
|
|
340
|
-
embedding_chunk_size=None,
|
|
341
|
-
# other
|
|
342
|
-
preset=None,
|
|
343
|
-
data_sources=None,
|
|
344
|
-
# agent info
|
|
345
|
-
agent_config_path=None,
|
|
346
|
-
name=None,
|
|
347
|
-
create_time=None,
|
|
348
|
-
letta_version=None,
|
|
349
|
-
# functions
|
|
350
|
-
functions=None, # schema definitions ONLY (linked at runtime)
|
|
351
|
-
):
|
|
352
|
-
|
|
353
|
-
assert name, f"Agent name must be provided"
|
|
354
|
-
self.name = name
|
|
355
|
-
|
|
356
|
-
config = LettaConfig.load() # get default values
|
|
357
|
-
self.persona = config.persona if persona is None else persona
|
|
358
|
-
self.human = config.human if human is None else human
|
|
359
|
-
self.preset = config.preset if preset is None else preset
|
|
360
|
-
self.context_window = config.default_llm_config.context_window if context_window is None else context_window
|
|
361
|
-
self.model = config.default_llm_config.model if model is None else model
|
|
362
|
-
self.model_endpoint_type = config.default_llm_config.model_endpoint_type if model_endpoint_type is None else model_endpoint_type
|
|
363
|
-
self.model_endpoint = config.default_llm_config.model_endpoint if model_endpoint is None else model_endpoint
|
|
364
|
-
self.model_wrapper = config.default_llm_config.model_wrapper if model_wrapper is None else model_wrapper
|
|
365
|
-
self.llm_config = LLMConfig(
|
|
366
|
-
model=self.model,
|
|
367
|
-
model_endpoint_type=self.model_endpoint_type,
|
|
368
|
-
model_endpoint=self.model_endpoint,
|
|
369
|
-
model_wrapper=self.model_wrapper,
|
|
370
|
-
context_window=self.context_window,
|
|
371
|
-
)
|
|
372
|
-
self.embedding_endpoint_type = (
|
|
373
|
-
config.default_embedding_config.embedding_endpoint_type if embedding_endpoint_type is None else embedding_endpoint_type
|
|
374
|
-
)
|
|
375
|
-
self.embedding_endpoint = config.default_embedding_config.embedding_endpoint if embedding_endpoint is None else embedding_endpoint
|
|
376
|
-
self.embedding_model = config.default_embedding_config.embedding_model if embedding_model is None else embedding_model
|
|
377
|
-
self.embedding_dim = config.default_embedding_config.embedding_dim if embedding_dim is None else embedding_dim
|
|
378
|
-
self.embedding_chunk_size = (
|
|
379
|
-
config.default_embedding_config.embedding_chunk_size if embedding_chunk_size is None else embedding_chunk_size
|
|
380
|
-
)
|
|
381
|
-
self.embedding_config = EmbeddingConfig(
|
|
382
|
-
embedding_endpoint_type=self.embedding_endpoint_type,
|
|
383
|
-
embedding_endpoint=self.embedding_endpoint,
|
|
384
|
-
embedding_model=self.embedding_model,
|
|
385
|
-
embedding_dim=self.embedding_dim,
|
|
386
|
-
embedding_chunk_size=self.embedding_chunk_size,
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
# agent metadata
|
|
390
|
-
self.data_sources = data_sources if data_sources is not None else []
|
|
391
|
-
self.create_time = create_time if create_time is not None else utils.get_local_time()
|
|
392
|
-
if letta_version is None:
|
|
393
|
-
import letta
|
|
394
|
-
|
|
395
|
-
self.letta_version = letta.__version__
|
|
396
|
-
else:
|
|
397
|
-
self.letta_version = letta_version
|
|
398
|
-
|
|
399
|
-
# functions
|
|
400
|
-
self.functions = functions
|
|
401
|
-
|
|
402
|
-
# save agent config
|
|
403
|
-
self.agent_config_path = (
|
|
404
|
-
os.path.join(LETTA_DIR, "agents", self.name, "config.json") if agent_config_path is None else agent_config_path
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
def attach_data_source(self, data_source: str):
|
|
408
|
-
# TODO: add warning that only once source can be attached
|
|
409
|
-
# i.e. previous source will be overriden
|
|
410
|
-
self.data_sources.append(data_source)
|
|
411
|
-
self.save()
|
|
412
|
-
|
|
413
|
-
def save_dir(self):
|
|
414
|
-
return os.path.join(LETTA_DIR, "agents", self.name)
|
|
415
|
-
|
|
416
|
-
def save_state_dir(self):
|
|
417
|
-
# directory to save agent state
|
|
418
|
-
return os.path.join(LETTA_DIR, "agents", self.name, "agent_state")
|
|
419
|
-
|
|
420
|
-
def save_persistence_manager_dir(self):
|
|
421
|
-
# directory to save persistent manager state
|
|
422
|
-
return os.path.join(LETTA_DIR, "agents", self.name, "persistence_manager")
|
|
423
|
-
|
|
424
|
-
def save_agent_index_dir(self):
|
|
425
|
-
# save llama index inside of persistent manager directory
|
|
426
|
-
return os.path.join(self.save_persistence_manager_dir(), "index")
|
|
427
|
-
|
|
428
|
-
def save(self):
|
|
429
|
-
# save state of persistence manager
|
|
430
|
-
os.makedirs(os.path.join(LETTA_DIR, "agents", self.name), exist_ok=True)
|
|
431
|
-
# save version
|
|
432
|
-
self.letta_version = letta.__version__
|
|
433
|
-
with open(self.agent_config_path, "w", encoding="utf-8") as f:
|
|
434
|
-
json.dump(vars(self), f, indent=4)
|
|
435
|
-
|
|
436
|
-
def to_agent_state(self):
|
|
437
|
-
return PersistedAgentState(
|
|
438
|
-
name=self.name,
|
|
439
|
-
preset=self.preset,
|
|
440
|
-
persona=self.persona,
|
|
441
|
-
human=self.human,
|
|
442
|
-
llm_config=self.llm_config,
|
|
443
|
-
embedding_config=self.embedding_config,
|
|
444
|
-
create_time=self.create_time,
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
@staticmethod
|
|
448
|
-
def exists(name: str):
|
|
449
|
-
"""Check if agent config exists"""
|
|
450
|
-
agent_config_path = os.path.join(LETTA_DIR, "agents", name)
|
|
451
|
-
return os.path.exists(agent_config_path)
|
|
452
|
-
|
|
453
|
-
@classmethod
|
|
454
|
-
def load(cls, name: str):
|
|
455
|
-
"""Load agent config from JSON file"""
|
|
456
|
-
agent_config_path = os.path.join(LETTA_DIR, "agents", name, "config.json")
|
|
457
|
-
assert os.path.exists(agent_config_path), f"Agent config file does not exist at {agent_config_path}"
|
|
458
|
-
with open(agent_config_path, "r", encoding="utf-8") as f:
|
|
459
|
-
agent_config = json.load(f)
|
|
460
|
-
# allow compatibility accross versions
|
|
461
|
-
try:
|
|
462
|
-
class_args = inspect.getargspec(cls.__init__).args
|
|
463
|
-
except AttributeError:
|
|
464
|
-
# https://github.com/pytorch/pytorch/issues/15344
|
|
465
|
-
class_args = inspect.getfullargspec(cls.__init__).args
|
|
466
|
-
agent_fields = list(agent_config.keys())
|
|
467
|
-
for key in agent_fields:
|
|
468
|
-
if key not in class_args:
|
|
469
|
-
utils.printd(f"Removing missing argument {key} from agent config")
|
|
470
|
-
del agent_config[key]
|
|
471
|
-
return cls(**agent_config)
|
letta/main.py
CHANGED
|
@@ -19,7 +19,6 @@ from letta.cli.cli_config import add, add_tool, configure, delete, list, list_to
|
|
|
19
19
|
from letta.cli.cli_load import app as load_app
|
|
20
20
|
from letta.config import LettaConfig
|
|
21
21
|
from letta.constants import FUNC_FAILED_HEARTBEAT_MESSAGE, REQ_HEARTBEAT_MESSAGE
|
|
22
|
-
from letta.metadata import MetadataStore
|
|
23
22
|
|
|
24
23
|
# from letta.interface import CLIInterface as interface # for printing to terminal
|
|
25
24
|
from letta.streaming_interface import AgentRefreshStreamingInterface
|
|
@@ -62,7 +61,6 @@ def run_agent_loop(
|
|
|
62
61
|
letta_agent: agent.Agent,
|
|
63
62
|
config: LettaConfig,
|
|
64
63
|
first: bool,
|
|
65
|
-
ms: MetadataStore,
|
|
66
64
|
no_verify: bool = False,
|
|
67
65
|
strip_ui: bool = False,
|
|
68
66
|
stream: bool = False,
|
|
@@ -92,7 +90,6 @@ def run_agent_loop(
|
|
|
92
90
|
|
|
93
91
|
# create client
|
|
94
92
|
client = create_client()
|
|
95
|
-
ms = MetadataStore(config) # TODO: remove
|
|
96
93
|
|
|
97
94
|
# run loops
|
|
98
95
|
while True:
|
|
@@ -130,11 +127,11 @@ def run_agent_loop(
|
|
|
130
127
|
# updated agent save functions
|
|
131
128
|
if user_input.lower() == "/exit":
|
|
132
129
|
# letta_agent.save()
|
|
133
|
-
agent.save_agent(letta_agent
|
|
130
|
+
agent.save_agent(letta_agent)
|
|
134
131
|
break
|
|
135
132
|
elif user_input.lower() == "/save" or user_input.lower() == "/savechat":
|
|
136
133
|
# letta_agent.save()
|
|
137
|
-
agent.save_agent(letta_agent
|
|
134
|
+
agent.save_agent(letta_agent)
|
|
138
135
|
continue
|
|
139
136
|
elif user_input.lower() == "/attach":
|
|
140
137
|
# TODO: check if agent already has it
|
|
@@ -378,7 +375,6 @@ def run_agent_loop(
|
|
|
378
375
|
first_message=False,
|
|
379
376
|
skip_verify=no_verify,
|
|
380
377
|
stream=stream,
|
|
381
|
-
ms=ms,
|
|
382
378
|
)
|
|
383
379
|
else:
|
|
384
380
|
step_response = letta_agent.step_user_message(
|
|
@@ -386,7 +382,6 @@ def run_agent_loop(
|
|
|
386
382
|
first_message=False,
|
|
387
383
|
skip_verify=no_verify,
|
|
388
384
|
stream=stream,
|
|
389
|
-
ms=ms,
|
|
390
385
|
)
|
|
391
386
|
new_messages = step_response.messages
|
|
392
387
|
heartbeat_request = step_response.heartbeat_request
|
|
@@ -394,7 +389,7 @@ def run_agent_loop(
|
|
|
394
389
|
token_warning = step_response.in_context_memory_warning
|
|
395
390
|
step_response.usage
|
|
396
391
|
|
|
397
|
-
agent.save_agent(letta_agent
|
|
392
|
+
agent.save_agent(letta_agent)
|
|
398
393
|
skip_next_user_input = False
|
|
399
394
|
if token_warning:
|
|
400
395
|
user_message = system.get_token_limit_warning()
|
letta/memory.py
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import Callable, Dict, List, Tuple, Union
|
|
1
|
+
from typing import Callable, Dict, List
|
|
4
2
|
|
|
5
3
|
from letta.constants import MESSAGE_SUMMARY_REQUEST_ACK, MESSAGE_SUMMARY_WARNING_FRAC
|
|
6
|
-
from letta.embeddings import embedding_model, parse_and_chunk_text, query_embedding
|
|
7
4
|
from letta.llm_api.llm_api_tools import create
|
|
8
5
|
from letta.prompts.gpt_summarize import SYSTEM as SUMMARY_PROMPT_SYSTEM
|
|
9
6
|
from letta.schemas.agent import AgentState
|
|
10
7
|
from letta.schemas.enums import MessageRole
|
|
11
8
|
from letta.schemas.memory import Memory
|
|
12
9
|
from letta.schemas.message import Message
|
|
13
|
-
from letta.
|
|
14
|
-
from letta.utils import (
|
|
15
|
-
count_tokens,
|
|
16
|
-
extract_date_from_timestamp,
|
|
17
|
-
get_local_time,
|
|
18
|
-
printd,
|
|
19
|
-
validate_date_format,
|
|
20
|
-
)
|
|
10
|
+
from letta.utils import count_tokens, printd
|
|
21
11
|
|
|
22
12
|
|
|
23
13
|
def get_memory_functions(cls: Memory) -> Dict[str, Callable]:
|
|
@@ -67,7 +57,6 @@ def summarize_messages(
|
|
|
67
57
|
+ message_sequence_to_summarize[cutoff:]
|
|
68
58
|
)
|
|
69
59
|
|
|
70
|
-
agent_state.user_id
|
|
71
60
|
dummy_agent_id = agent_state.id
|
|
72
61
|
message_sequence = []
|
|
73
62
|
message_sequence.append(Message(agent_id=dummy_agent_id, role=MessageRole.system, text=summary_prompt))
|
|
@@ -79,7 +68,7 @@ def summarize_messages(
|
|
|
79
68
|
llm_config_no_inner_thoughts.put_inner_thoughts_in_kwargs = False
|
|
80
69
|
response = create(
|
|
81
70
|
llm_config=llm_config_no_inner_thoughts,
|
|
82
|
-
user_id=agent_state.
|
|
71
|
+
user_id=agent_state.created_by_id,
|
|
83
72
|
messages=message_sequence,
|
|
84
73
|
stream=False,
|
|
85
74
|
)
|
letta/o1_agent.py
CHANGED
|
@@ -2,7 +2,6 @@ from typing import List, Optional, Union
|
|
|
2
2
|
|
|
3
3
|
from letta.agent import Agent, save_agent
|
|
4
4
|
from letta.interface import AgentInterface
|
|
5
|
-
from letta.metadata import MetadataStore
|
|
6
5
|
from letta.schemas.agent import AgentState
|
|
7
6
|
from letta.schemas.message import Message
|
|
8
7
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
@@ -56,7 +55,6 @@ class O1Agent(Agent):
|
|
|
56
55
|
messages: Union[Message, List[Message]],
|
|
57
56
|
chaining: bool = True,
|
|
58
57
|
max_chaining_steps: Optional[int] = None,
|
|
59
|
-
ms: Optional[MetadataStore] = None,
|
|
60
58
|
**kwargs,
|
|
61
59
|
) -> LettaUsageStatistics:
|
|
62
60
|
"""Run Agent.inner_step in a loop, terminate when final thinking message is sent or max_thinking_steps is reached"""
|
|
@@ -70,7 +68,6 @@ class O1Agent(Agent):
|
|
|
70
68
|
if counter > 0:
|
|
71
69
|
next_input_message = []
|
|
72
70
|
|
|
73
|
-
kwargs["ms"] = ms
|
|
74
71
|
kwargs["first_message"] = False
|
|
75
72
|
step_response = self.inner_step(
|
|
76
73
|
messages=next_input_message,
|
|
@@ -84,7 +81,6 @@ class O1Agent(Agent):
|
|
|
84
81
|
# check if it is final thinking message
|
|
85
82
|
if step_response.messages[-1].name == "send_final_message":
|
|
86
83
|
break
|
|
87
|
-
|
|
88
|
-
save_agent(self, ms)
|
|
84
|
+
save_agent(self)
|
|
89
85
|
|
|
90
86
|
return LettaUsageStatistics(**total_usage.model_dump(), step_count=step_count)
|
letta/offline_memory_agent.py
CHANGED
|
@@ -2,7 +2,6 @@ from typing import List, Optional, Union
|
|
|
2
2
|
|
|
3
3
|
from letta.agent import Agent, AgentState, save_agent
|
|
4
4
|
from letta.interface import AgentInterface
|
|
5
|
-
from letta.metadata import MetadataStore
|
|
6
5
|
from letta.orm import User
|
|
7
6
|
from letta.schemas.message import Message
|
|
8
7
|
from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
@@ -130,7 +129,7 @@ class OfflineMemoryAgent(Agent):
|
|
|
130
129
|
# extras
|
|
131
130
|
first_message_verify_mono: bool = False,
|
|
132
131
|
max_memory_rethinks: int = 10,
|
|
133
|
-
initial_message_sequence: Optional[List[Message]] = None,
|
|
132
|
+
initial_message_sequence: Optional[List[Message]] = None,
|
|
134
133
|
):
|
|
135
134
|
super().__init__(interface, agent_state, user, initial_message_sequence=initial_message_sequence)
|
|
136
135
|
self.first_message_verify_mono = first_message_verify_mono
|
|
@@ -141,7 +140,6 @@ class OfflineMemoryAgent(Agent):
|
|
|
141
140
|
messages: Union[Message, List[Message]],
|
|
142
141
|
chaining: bool = True,
|
|
143
142
|
max_chaining_steps: Optional[int] = None,
|
|
144
|
-
ms: Optional[MetadataStore] = None,
|
|
145
143
|
**kwargs,
|
|
146
144
|
) -> LettaUsageStatistics:
|
|
147
145
|
"""Go through what is currently in memory core memory and integrate information."""
|
|
@@ -153,7 +151,6 @@ class OfflineMemoryAgent(Agent):
|
|
|
153
151
|
while counter < self.max_memory_rethinks:
|
|
154
152
|
if counter > 0:
|
|
155
153
|
next_input_message = []
|
|
156
|
-
kwargs["ms"] = ms
|
|
157
154
|
kwargs["first_message"] = False
|
|
158
155
|
step_response = self.inner_step(
|
|
159
156
|
messages=next_input_message,
|
|
@@ -172,7 +169,6 @@ class OfflineMemoryAgent(Agent):
|
|
|
172
169
|
counter += 1
|
|
173
170
|
self.interface.step_complete()
|
|
174
171
|
|
|
175
|
-
|
|
176
|
-
save_agent(self, ms)
|
|
172
|
+
save_agent(self)
|
|
177
173
|
|
|
178
174
|
return LettaUsageStatistics(**total_usage.model_dump(), step_count=step_count)
|
letta/orm/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from letta.orm.agent import Agent
|
|
1
2
|
from letta.orm.agents_tags import AgentsTags
|
|
2
3
|
from letta.orm.base import Base
|
|
3
4
|
from letta.orm.block import Block
|
|
@@ -9,6 +10,7 @@ from letta.orm.organization import Organization
|
|
|
9
10
|
from letta.orm.passage import Passage
|
|
10
11
|
from letta.orm.sandbox_config import SandboxConfig, SandboxEnvironmentVariable
|
|
11
12
|
from letta.orm.source import Source
|
|
13
|
+
from letta.orm.sources_agents import SourcesAgents
|
|
12
14
|
from letta.orm.tool import Tool
|
|
13
15
|
from letta.orm.tools_agents import ToolsAgents
|
|
14
16
|
from letta.orm.user import User
|
letta/orm/agent.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import JSON, String, UniqueConstraint
|
|
5
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
|
+
|
|
7
|
+
from letta.orm.block import Block
|
|
8
|
+
from letta.orm.custom_columns import (
|
|
9
|
+
EmbeddingConfigColumn,
|
|
10
|
+
LLMConfigColumn,
|
|
11
|
+
ToolRulesColumn,
|
|
12
|
+
)
|
|
13
|
+
from letta.orm.message import Message
|
|
14
|
+
from letta.orm.mixins import OrganizationMixin
|
|
15
|
+
from letta.orm.organization import Organization
|
|
16
|
+
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
17
|
+
from letta.schemas.agent import AgentState as PydanticAgentState
|
|
18
|
+
from letta.schemas.agent import AgentType
|
|
19
|
+
from letta.schemas.embedding_config import EmbeddingConfig
|
|
20
|
+
from letta.schemas.llm_config import LLMConfig
|
|
21
|
+
from letta.schemas.memory import Memory
|
|
22
|
+
from letta.schemas.tool_rule import ToolRule
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from letta.orm.agents_tags import AgentsTags
|
|
26
|
+
from letta.orm.organization import Organization
|
|
27
|
+
from letta.orm.source import Source
|
|
28
|
+
from letta.orm.tool import Tool
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Agent(SqlalchemyBase, OrganizationMixin):
|
|
32
|
+
__tablename__ = "agents"
|
|
33
|
+
__pydantic_model__ = PydanticAgentState
|
|
34
|
+
__table_args__ = (UniqueConstraint("organization_id", "name", name="unique_org_agent_name"),)
|
|
35
|
+
|
|
36
|
+
# agent generates its own id
|
|
37
|
+
# TODO: We want to migrate all the ORM models to do this, so we will need to move this to the SqlalchemyBase
|
|
38
|
+
# TODO: Move this in this PR? at the very end?
|
|
39
|
+
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: f"agent-{uuid.uuid4()}")
|
|
40
|
+
|
|
41
|
+
# Descriptor fields
|
|
42
|
+
agent_type: Mapped[Optional[AgentType]] = mapped_column(String, nullable=True, doc="The type of Agent")
|
|
43
|
+
name: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="a human-readable identifier for an agent, non-unique.")
|
|
44
|
+
description: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The description of the agent.")
|
|
45
|
+
|
|
46
|
+
# System prompt
|
|
47
|
+
system: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="The system prompt used by the agent.")
|
|
48
|
+
|
|
49
|
+
# In context memory
|
|
50
|
+
# TODO: This should be a separate mapping table
|
|
51
|
+
# This is dangerously flexible with the JSON type
|
|
52
|
+
message_ids: Mapped[Optional[List[str]]] = mapped_column(JSON, nullable=True, doc="List of message IDs in in-context memory.")
|
|
53
|
+
|
|
54
|
+
# Metadata and configs
|
|
55
|
+
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, doc="metadata for the agent.")
|
|
56
|
+
llm_config: Mapped[Optional[LLMConfig]] = mapped_column(
|
|
57
|
+
LLMConfigColumn, nullable=True, doc="the LLM backend configuration object for this agent."
|
|
58
|
+
)
|
|
59
|
+
embedding_config: Mapped[Optional[EmbeddingConfig]] = mapped_column(
|
|
60
|
+
EmbeddingConfigColumn, doc="the embedding configuration object for this agent."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Tool rules
|
|
64
|
+
tool_rules: Mapped[Optional[List[ToolRule]]] = mapped_column(ToolRulesColumn, doc="the tool rules for this agent.")
|
|
65
|
+
|
|
66
|
+
# relationships
|
|
67
|
+
organization: Mapped["Organization"] = relationship("Organization", back_populates="agents")
|
|
68
|
+
tools: Mapped[List["Tool"]] = relationship("Tool", secondary="tools_agents", lazy="selectin", passive_deletes=True)
|
|
69
|
+
sources: Mapped[List["Source"]] = relationship("Source", secondary="sources_agents", lazy="selectin")
|
|
70
|
+
core_memory: Mapped[List["Block"]] = relationship("Block", secondary="blocks_agents", lazy="selectin")
|
|
71
|
+
messages: Mapped[List["Message"]] = relationship(
|
|
72
|
+
"Message",
|
|
73
|
+
back_populates="agent",
|
|
74
|
+
lazy="selectin",
|
|
75
|
+
cascade="all, delete-orphan", # Ensure messages are deleted when the agent is deleted
|
|
76
|
+
passive_deletes=True,
|
|
77
|
+
)
|
|
78
|
+
tags: Mapped[List["AgentsTags"]] = relationship(
|
|
79
|
+
"AgentsTags",
|
|
80
|
+
back_populates="agent",
|
|
81
|
+
cascade="all, delete-orphan",
|
|
82
|
+
lazy="selectin",
|
|
83
|
+
doc="Tags associated with the agent.",
|
|
84
|
+
)
|
|
85
|
+
# passages: Mapped[List["Passage"]] = relationship("Passage", back_populates="agent", lazy="selectin")
|
|
86
|
+
|
|
87
|
+
def to_pydantic(self) -> PydanticAgentState:
|
|
88
|
+
"""converts to the basic pydantic model counterpart"""
|
|
89
|
+
state = {
|
|
90
|
+
"id": self.id,
|
|
91
|
+
"name": self.name,
|
|
92
|
+
"description": self.description,
|
|
93
|
+
"message_ids": self.message_ids,
|
|
94
|
+
"tools": self.tools,
|
|
95
|
+
"sources": self.sources,
|
|
96
|
+
"tags": [t.tag for t in self.tags],
|
|
97
|
+
"tool_rules": self.tool_rules,
|
|
98
|
+
"system": self.system,
|
|
99
|
+
"agent_type": self.agent_type,
|
|
100
|
+
"llm_config": self.llm_config,
|
|
101
|
+
"embedding_config": self.embedding_config,
|
|
102
|
+
"metadata_": self.metadata_,
|
|
103
|
+
"memory": Memory(blocks=[b.to_pydantic() for b in self.core_memory]),
|
|
104
|
+
"created_by_id": self.created_by_id,
|
|
105
|
+
"last_updated_by_id": self.last_updated_by_id,
|
|
106
|
+
"created_at": self.created_at,
|
|
107
|
+
"updated_at": self.updated_at,
|
|
108
|
+
}
|
|
109
|
+
return self.__pydantic_model__(**state)
|
letta/orm/agents_tags.py
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
|
|
3
1
|
from sqlalchemy import ForeignKey, String, UniqueConstraint
|
|
4
2
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
5
3
|
|
|
6
|
-
from letta.orm.
|
|
7
|
-
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
8
|
-
from letta.schemas.agents_tags import AgentsTags as PydanticAgentsTags
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from letta.orm.organization import Organization
|
|
12
|
-
|
|
4
|
+
from letta.orm.base import Base
|
|
13
5
|
|
|
14
|
-
class AgentsTags(SqlalchemyBase, OrganizationMixin):
|
|
15
|
-
"""Associates tags with agents, allowing agents to have multiple tags and supporting tag-based filtering."""
|
|
16
6
|
|
|
7
|
+
class AgentsTags(Base):
|
|
17
8
|
__tablename__ = "agents_tags"
|
|
18
|
-
__pydantic_model__ = PydanticAgentsTags
|
|
19
9
|
__table_args__ = (UniqueConstraint("agent_id", "tag", name="unique_agent_tag"),)
|
|
20
10
|
|
|
21
|
-
#
|
|
22
|
-
|
|
11
|
+
# # agent generates its own id
|
|
12
|
+
# # TODO: We want to migrate all the ORM models to do this, so we will need to move this to the SqlalchemyBase
|
|
13
|
+
# # TODO: Move this in this PR? at the very end?
|
|
14
|
+
# id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: f"agents_tags-{uuid.uuid4()}")
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
tag: Mapped[str] = mapped_column(String,
|
|
16
|
+
agent_id: Mapped[String] = mapped_column(String, ForeignKey("agents.id"), primary_key=True)
|
|
17
|
+
tag: Mapped[str] = mapped_column(String, doc="The name of the tag associated with the agent.", primary_key=True)
|
|
26
18
|
|
|
27
|
-
#
|
|
28
|
-
|
|
19
|
+
# Relationships
|
|
20
|
+
agent: Mapped["Agent"] = relationship("Agent", back_populates="tags")
|
letta/orm/block.py
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Optional, Type
|
|
2
2
|
|
|
3
|
-
from sqlalchemy import JSON, BigInteger, Integer, UniqueConstraint
|
|
4
|
-
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
3
|
+
from sqlalchemy import JSON, BigInteger, Integer, UniqueConstraint, event
|
|
4
|
+
from sqlalchemy.orm import Mapped, attributes, mapped_column, relationship
|
|
5
5
|
|
|
6
6
|
from letta.constants import CORE_MEMORY_BLOCK_CHAR_LIMIT
|
|
7
|
+
from letta.orm.blocks_agents import BlocksAgents
|
|
7
8
|
from letta.orm.mixins import OrganizationMixin
|
|
8
9
|
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
9
10
|
from letta.schemas.block import Block as PydanticBlock
|
|
10
11
|
from letta.schemas.block import Human, Persona
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
|
-
from letta.orm import
|
|
14
|
+
from letta.orm import Organization
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Block(OrganizationMixin, SqlalchemyBase):
|
|
@@ -35,7 +36,6 @@ class Block(OrganizationMixin, SqlalchemyBase):
|
|
|
35
36
|
|
|
36
37
|
# relationships
|
|
37
38
|
organization: Mapped[Optional["Organization"]] = relationship("Organization")
|
|
38
|
-
blocks_agents: Mapped[list["BlocksAgents"]] = relationship("BlocksAgents", back_populates="block", cascade="all, delete")
|
|
39
39
|
|
|
40
40
|
def to_pydantic(self) -> Type:
|
|
41
41
|
match self.label:
|
|
@@ -46,3 +46,28 @@ class Block(OrganizationMixin, SqlalchemyBase):
|
|
|
46
46
|
case _:
|
|
47
47
|
Schema = PydanticBlock
|
|
48
48
|
return Schema.model_validate(self)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@event.listens_for(Block, "after_update") # Changed from 'before_update'
|
|
52
|
+
def block_before_update(mapper, connection, target):
|
|
53
|
+
"""Handle updating BlocksAgents when a block's label changes."""
|
|
54
|
+
label_history = attributes.get_history(target, "label")
|
|
55
|
+
if not label_history.has_changes():
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
blocks_agents = BlocksAgents.__table__
|
|
59
|
+
connection.execute(
|
|
60
|
+
blocks_agents.update()
|
|
61
|
+
.where(blocks_agents.c.block_id == target.id, blocks_agents.c.block_label == label_history.deleted[0])
|
|
62
|
+
.values(block_label=label_history.added[0])
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@event.listens_for(Block, "before_insert")
|
|
67
|
+
@event.listens_for(Block, "before_update")
|
|
68
|
+
def validate_value_length(mapper, connection, target):
|
|
69
|
+
"""Ensure the value length does not exceed the limit."""
|
|
70
|
+
if target.value and len(target.value) > target.limit:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"Value length ({len(target.value)}) exceeds the limit ({target.limit}) for block with label '{target.label}' and id '{target.id}'."
|
|
73
|
+
)
|
letta/orm/blocks_agents.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from sqlalchemy import ForeignKey, ForeignKeyConstraint, String, UniqueConstraint
|
|
2
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
2
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
3
3
|
|
|
4
|
-
from letta.orm.
|
|
5
|
-
from letta.schemas.blocks_agents import BlocksAgents as PydanticBlocksAgents
|
|
4
|
+
from letta.orm.base import Base
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
class BlocksAgents(
|
|
7
|
+
class BlocksAgents(Base):
|
|
9
8
|
"""Agents must have one or many blocks to make up their core memory."""
|
|
10
9
|
|
|
11
10
|
__tablename__ = "blocks_agents"
|
|
12
|
-
__pydantic_model__ = PydanticBlocksAgents
|
|
13
11
|
__table_args__ = (
|
|
14
12
|
UniqueConstraint(
|
|
15
13
|
"agent_id",
|
|
@@ -17,16 +15,12 @@ class BlocksAgents(SqlalchemyBase):
|
|
|
17
15
|
name="unique_label_per_agent",
|
|
18
16
|
),
|
|
19
17
|
ForeignKeyConstraint(
|
|
20
|
-
["block_id", "block_label"],
|
|
21
|
-
["block.id", "block.label"],
|
|
22
|
-
name="fk_block_id_label",
|
|
18
|
+
["block_id", "block_label"], ["block.id", "block.label"], name="fk_block_id_label", deferrable=True, initially="DEFERRED"
|
|
23
19
|
),
|
|
20
|
+
UniqueConstraint("agent_id", "block_id", name="unique_agent_block"),
|
|
24
21
|
)
|
|
25
22
|
|
|
26
23
|
# unique agent + block label
|
|
27
24
|
agent_id: Mapped[str] = mapped_column(String, ForeignKey("agents.id"), primary_key=True)
|
|
28
25
|
block_id: Mapped[str] = mapped_column(String, primary_key=True)
|
|
29
26
|
block_label: Mapped[str] = mapped_column(String, primary_key=True)
|
|
30
|
-
|
|
31
|
-
# relationships
|
|
32
|
-
block: Mapped["Block"] = relationship("Block", back_populates="blocks_agents")
|