letta-nightly 0.5.5.dev20241122170833__py3-none-any.whl → 0.6.0.dev20241204052927__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 +2 -2
- letta/agent.py +155 -166
- letta/agent_store/chroma.py +2 -0
- letta/agent_store/db.py +1 -1
- letta/cli/cli.py +12 -8
- letta/cli/cli_config.py +1 -1
- letta/client/client.py +765 -137
- letta/config.py +2 -2
- letta/constants.py +10 -14
- letta/errors.py +12 -0
- letta/functions/function_sets/base.py +38 -1
- letta/functions/functions.py +40 -57
- letta/functions/helpers.py +0 -4
- letta/functions/schema_generator.py +279 -18
- letta/helpers/tool_rule_solver.py +6 -5
- letta/llm_api/helpers.py +99 -5
- letta/llm_api/openai.py +8 -2
- letta/local_llm/utils.py +13 -6
- letta/log.py +7 -9
- letta/main.py +1 -1
- letta/metadata.py +53 -38
- letta/o1_agent.py +1 -4
- letta/orm/__init__.py +2 -0
- letta/orm/block.py +7 -3
- letta/orm/blocks_agents.py +32 -0
- letta/orm/errors.py +8 -0
- letta/orm/mixins.py +8 -0
- letta/orm/organization.py +8 -1
- letta/orm/sandbox_config.py +56 -0
- letta/orm/sqlalchemy_base.py +68 -10
- letta/persistence_manager.py +1 -0
- letta/schemas/agent.py +57 -52
- letta/schemas/block.py +85 -26
- letta/schemas/blocks_agents.py +32 -0
- letta/schemas/enums.py +14 -0
- letta/schemas/letta_base.py +10 -1
- letta/schemas/letta_request.py +11 -23
- letta/schemas/letta_response.py +1 -2
- letta/schemas/memory.py +41 -76
- letta/schemas/message.py +3 -3
- letta/schemas/sandbox_config.py +114 -0
- letta/schemas/tool.py +37 -1
- letta/schemas/tool_rule.py +13 -5
- letta/server/rest_api/app.py +5 -4
- 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/__init__.py +4 -9
- letta/server/rest_api/routers/v1/agents.py +145 -61
- letta/server/rest_api/routers/v1/blocks.py +50 -5
- letta/server/rest_api/routers/v1/sandbox_configs.py +127 -0
- letta/server/rest_api/routers/v1/sources.py +8 -1
- letta/server/rest_api/routers/v1/tools.py +139 -13
- letta/server/rest_api/utils.py +6 -0
- letta/server/server.py +397 -340
- letta/server/static_files/assets/index-9fa459a2.js +1 -1
- letta/services/block_manager.py +23 -2
- letta/services/blocks_agents_manager.py +106 -0
- letta/services/per_agent_lock_manager.py +18 -0
- letta/services/sandbox_config_manager.py +256 -0
- letta/services/tool_execution_sandbox.py +352 -0
- letta/services/tool_manager.py +16 -22
- letta/services/tool_sandbox_env/.gitkeep +0 -0
- letta/settings.py +4 -0
- letta/utils.py +0 -7
- {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204052927.dist-info}/METADATA +10 -8
- {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204052927.dist-info}/RECORD +70 -60
- {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204052927.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204052927.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204052927.dist-info}/entry_points.txt +0 -0
letta/server/server.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# inspecting tools
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
import traceback
|
|
4
5
|
import warnings
|
|
5
6
|
from abc import abstractmethod
|
|
7
|
+
from asyncio import Lock
|
|
6
8
|
from datetime import datetime
|
|
7
9
|
from typing import Callable, Dict, List, Optional, Tuple, Union
|
|
8
10
|
|
|
11
|
+
from composio.client import Composio
|
|
12
|
+
from composio.client.collections import ActionModel, AppModel
|
|
9
13
|
from fastapi import HTTPException
|
|
10
14
|
|
|
11
15
|
import letta.constants as constants
|
|
@@ -17,24 +21,10 @@ from letta.agent_store.storage import StorageConnector, TableType
|
|
|
17
21
|
from letta.credentials import LettaCredentials
|
|
18
22
|
from letta.data_sources.connectors import DataConnector, load_data
|
|
19
23
|
|
|
20
|
-
# from letta.data_types import (
|
|
21
|
-
# AgentState,
|
|
22
|
-
# EmbeddingConfig,
|
|
23
|
-
# LLMConfig,
|
|
24
|
-
# Message,
|
|
25
|
-
# Preset,
|
|
26
|
-
# Source,
|
|
27
|
-
# Token,
|
|
28
|
-
# User,
|
|
29
|
-
# )
|
|
30
|
-
from letta.functions.functions import generate_schema, parse_source_code
|
|
31
|
-
from letta.functions.schema_generator import generate_schema
|
|
32
|
-
|
|
33
24
|
# TODO use custom interface
|
|
34
25
|
from letta.interface import AgentInterface # abstract
|
|
35
26
|
from letta.interface import CLIInterface # for printing to terminal
|
|
36
27
|
from letta.log import get_logger
|
|
37
|
-
from letta.memory import get_memory_functions
|
|
38
28
|
from letta.metadata import MetadataStore
|
|
39
29
|
from letta.o1_agent import O1Agent
|
|
40
30
|
from letta.orm import Base
|
|
@@ -53,14 +43,21 @@ from letta.providers import (
|
|
|
53
43
|
VLLMChatCompletionsProvider,
|
|
54
44
|
VLLMCompletionsProvider,
|
|
55
45
|
)
|
|
56
|
-
from letta.schemas.agent import
|
|
46
|
+
from letta.schemas.agent import (
|
|
47
|
+
AgentState,
|
|
48
|
+
AgentType,
|
|
49
|
+
CreateAgent,
|
|
50
|
+
PersistedAgentState,
|
|
51
|
+
UpdateAgentState,
|
|
52
|
+
)
|
|
57
53
|
from letta.schemas.api_key import APIKey, APIKeyCreate
|
|
54
|
+
from letta.schemas.block import Block, BlockUpdate
|
|
58
55
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
59
56
|
|
|
60
57
|
# openai schemas
|
|
61
58
|
from letta.schemas.enums import JobStatus
|
|
62
59
|
from letta.schemas.job import Job
|
|
63
|
-
from letta.schemas.letta_message import LettaMessage
|
|
60
|
+
from letta.schemas.letta_message import FunctionReturn, LettaMessage
|
|
64
61
|
from letta.schemas.llm_config import LLMConfig
|
|
65
62
|
from letta.schemas.memory import (
|
|
66
63
|
ArchivalMemorySummary,
|
|
@@ -77,14 +74,15 @@ from letta.schemas.usage import LettaUsageStatistics
|
|
|
77
74
|
from letta.schemas.user import User
|
|
78
75
|
from letta.services.agents_tags_manager import AgentsTagsManager
|
|
79
76
|
from letta.services.block_manager import BlockManager
|
|
77
|
+
from letta.services.blocks_agents_manager import BlocksAgentsManager
|
|
80
78
|
from letta.services.organization_manager import OrganizationManager
|
|
79
|
+
from letta.services.per_agent_lock_manager import PerAgentLockManager
|
|
80
|
+
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
81
81
|
from letta.services.source_manager import SourceManager
|
|
82
|
+
from letta.services.tool_execution_sandbox import ToolExecutionSandbox
|
|
82
83
|
from letta.services.tool_manager import ToolManager
|
|
83
84
|
from letta.services.user_manager import UserManager
|
|
84
|
-
from letta.utils import create_random_username, json_dumps, json_loads
|
|
85
|
-
|
|
86
|
-
# from letta.llm_api_tools import openai_get_model_list, azure_openai_get_model_list, smart_urljoin
|
|
87
|
-
|
|
85
|
+
from letta.utils import create_random_username, get_utc_time, json_dumps, json_loads
|
|
88
86
|
|
|
89
87
|
logger = get_logger(__name__)
|
|
90
88
|
|
|
@@ -125,10 +123,11 @@ class Server(object):
|
|
|
125
123
|
@abstractmethod
|
|
126
124
|
def create_agent(
|
|
127
125
|
self,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
interface
|
|
131
|
-
|
|
126
|
+
request: CreateAgent,
|
|
127
|
+
actor: User,
|
|
128
|
+
# interface
|
|
129
|
+
interface: Union[AgentInterface, None] = None,
|
|
130
|
+
) -> AgentState:
|
|
132
131
|
"""Create a new agent using a config"""
|
|
133
132
|
raise NotImplementedError
|
|
134
133
|
|
|
@@ -229,6 +228,14 @@ class SyncServer(Server):
|
|
|
229
228
|
|
|
230
229
|
self.credentials = LettaCredentials.load()
|
|
231
230
|
|
|
231
|
+
# Locks
|
|
232
|
+
self.send_message_lock = Lock()
|
|
233
|
+
|
|
234
|
+
# Composio
|
|
235
|
+
self.composio_client = None
|
|
236
|
+
if tool_settings.composio_api_key:
|
|
237
|
+
self.composio_client = Composio(api_key=tool_settings.composio_api_key)
|
|
238
|
+
|
|
232
239
|
# Initialize the metadata store
|
|
233
240
|
config = LettaConfig.load()
|
|
234
241
|
if settings.letta_pg_uri_no_default:
|
|
@@ -247,6 +254,11 @@ class SyncServer(Server):
|
|
|
247
254
|
self.block_manager = BlockManager()
|
|
248
255
|
self.source_manager = SourceManager()
|
|
249
256
|
self.agents_tags_manager = AgentsTagsManager()
|
|
257
|
+
self.sandbox_config_manager = SandboxConfigManager(tool_settings)
|
|
258
|
+
self.blocks_agents_manager = BlocksAgentsManager()
|
|
259
|
+
|
|
260
|
+
# Managers that interface with parallelism
|
|
261
|
+
self.per_agent_lock_manager = PerAgentLockManager()
|
|
250
262
|
|
|
251
263
|
# Make default user and org
|
|
252
264
|
if init_with_default_org_and_user:
|
|
@@ -360,76 +372,29 @@ class SyncServer(Server):
|
|
|
360
372
|
}
|
|
361
373
|
)
|
|
362
374
|
|
|
363
|
-
def
|
|
364
|
-
"""
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if interface is None:
|
|
370
|
-
interface = self.default_interface_factory()
|
|
371
|
-
|
|
372
|
-
try:
|
|
373
|
-
logger.debug(f"Grabbing agent user_id={user_id} agent_id={agent_id} from database")
|
|
374
|
-
agent_state = self.ms.get_agent(agent_id=agent_id, user_id=user_id)
|
|
375
|
-
if not agent_state:
|
|
376
|
-
logger.exception(f"agent_id {agent_id} does not exist")
|
|
377
|
-
raise ValueError(f"agent_id {agent_id} does not exist")
|
|
378
|
-
|
|
379
|
-
# Instantiate an agent object using the state retrieved
|
|
380
|
-
logger.debug(f"Creating an agent object")
|
|
381
|
-
tool_objs = []
|
|
382
|
-
for name in agent_state.tools:
|
|
383
|
-
# TODO: This should be a hard failure, but for migration reasons, we patch it for now
|
|
384
|
-
try:
|
|
385
|
-
tool_obj = self.tool_manager.get_tool_by_name(tool_name=name, actor=actor)
|
|
386
|
-
tool_objs.append(tool_obj)
|
|
387
|
-
except NoResultFound:
|
|
388
|
-
warnings.warn(f"Tried to retrieve a tool with name {name} from the agent_state, but does not exist in tool db.")
|
|
389
|
-
|
|
390
|
-
# set agent_state tools to only the names of the available tools
|
|
391
|
-
agent_state.tools = [t.name for t in tool_objs]
|
|
392
|
-
|
|
393
|
-
# Make sure the memory is a memory object
|
|
394
|
-
assert isinstance(agent_state.memory, Memory)
|
|
375
|
+
def load_agent(self, agent_id: str, interface: Union[AgentInterface, None] = None) -> Agent:
|
|
376
|
+
"""Updated method to load agents from persisted storage"""
|
|
377
|
+
agent_lock = self.per_agent_lock_manager.get_lock(agent_id)
|
|
378
|
+
with agent_lock:
|
|
379
|
+
agent_state = self.get_agent(agent_id=agent_id)
|
|
380
|
+
actor = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
395
381
|
|
|
382
|
+
interface = interface or self.default_interface_factory()
|
|
396
383
|
if agent_state.agent_type == AgentType.memgpt_agent:
|
|
397
|
-
|
|
398
|
-
elif agent_state.agent_type == AgentType.o1_agent:
|
|
399
|
-
letta_agent = O1Agent(agent_state=agent_state, interface=interface, tools=tool_objs, user=actor)
|
|
384
|
+
agent = Agent(agent_state=agent_state, interface=interface, user=actor)
|
|
400
385
|
else:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
# Add the agent to the in-memory store and return its reference
|
|
404
|
-
logger.debug(f"Adding agent to the agent cache: user_id={user_id}, agent_id={agent_id}")
|
|
405
|
-
self._add_agent(user_id=user_id, agent_id=agent_id, agent_obj=letta_agent)
|
|
406
|
-
return letta_agent
|
|
386
|
+
agent = O1Agent(agent_state=agent_state, interface=interface, user=actor)
|
|
407
387
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
def _get_or_load_agent(self, agent_id: str) -> Agent:
|
|
413
|
-
"""Check if the agent is in-memory, then load"""
|
|
414
|
-
agent_state = self.ms.get_agent(agent_id=agent_id)
|
|
415
|
-
if not agent_state:
|
|
416
|
-
raise ValueError(f"Agent does not exist")
|
|
417
|
-
user_id = agent_state.user_id
|
|
418
|
-
actor = self.user_manager.get_user_by_id(user_id)
|
|
419
|
-
|
|
420
|
-
logger.debug(f"Checking for agent user_id={user_id} agent_id={agent_id}")
|
|
421
|
-
# TODO: consider disabling loading cached agents due to potential concurrency issues
|
|
422
|
-
letta_agent = self._get_agent(user_id=user_id, agent_id=agent_id)
|
|
423
|
-
if not letta_agent:
|
|
424
|
-
logger.debug(f"Agent not loaded, loading agent user_id={user_id} agent_id={agent_id}")
|
|
425
|
-
letta_agent = self._load_agent(agent_id=agent_id, actor=actor)
|
|
426
|
-
return letta_agent
|
|
388
|
+
# Persist to agent
|
|
389
|
+
save_agent(agent, self.ms)
|
|
390
|
+
return agent
|
|
427
391
|
|
|
428
392
|
def _step(
|
|
429
393
|
self,
|
|
430
394
|
user_id: str,
|
|
431
395
|
agent_id: str,
|
|
432
396
|
input_messages: Union[Message, List[Message]],
|
|
397
|
+
interface: Union[AgentInterface, None] = None, # needed to getting responses
|
|
433
398
|
# timestamp: Optional[datetime],
|
|
434
399
|
) -> LettaUsageStatistics:
|
|
435
400
|
"""Send the input message through the agent"""
|
|
@@ -445,7 +410,8 @@ class SyncServer(Server):
|
|
|
445
410
|
try:
|
|
446
411
|
|
|
447
412
|
# Get the agent object (loaded in memory)
|
|
448
|
-
letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
413
|
+
# letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
414
|
+
letta_agent = self.load_agent(agent_id=agent_id, interface=interface)
|
|
449
415
|
if letta_agent is None:
|
|
450
416
|
raise KeyError(f"Agent (user={user_id}, agent={agent_id}) is not loaded")
|
|
451
417
|
|
|
@@ -462,6 +428,9 @@ class SyncServer(Server):
|
|
|
462
428
|
skip_verify=True,
|
|
463
429
|
)
|
|
464
430
|
|
|
431
|
+
# save agent after step
|
|
432
|
+
save_agent(letta_agent, self.ms)
|
|
433
|
+
|
|
465
434
|
except Exception as e:
|
|
466
435
|
logger.error(f"Error in server._step: {e}")
|
|
467
436
|
print(traceback.print_exc())
|
|
@@ -479,7 +448,7 @@ class SyncServer(Server):
|
|
|
479
448
|
logger.debug(f"Got command: {command}")
|
|
480
449
|
|
|
481
450
|
# Get the agent object (loaded in memory)
|
|
482
|
-
letta_agent = self.
|
|
451
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
483
452
|
usage = None
|
|
484
453
|
|
|
485
454
|
if command.lower() == "exit":
|
|
@@ -516,7 +485,7 @@ class SyncServer(Server):
|
|
|
516
485
|
elif command.lower() == "memory":
|
|
517
486
|
ret_str = (
|
|
518
487
|
f"\nDumping memory contents:\n"
|
|
519
|
-
+ f"\n{str(letta_agent.memory)}"
|
|
488
|
+
+ f"\n{str(letta_agent.agent_state.memory)}"
|
|
520
489
|
+ f"\n{str(letta_agent.persistence_manager.archival_memory)}"
|
|
521
490
|
+ f"\n{str(letta_agent.persistence_manager.recall_memory)}"
|
|
522
491
|
)
|
|
@@ -708,6 +677,7 @@ class SyncServer(Server):
|
|
|
708
677
|
# whether or not to wrap user and system message as MemGPT-style stringified JSON
|
|
709
678
|
wrap_user_message: bool = True,
|
|
710
679
|
wrap_system_message: bool = True,
|
|
680
|
+
interface: Union[AgentInterface, None] = None, # needed to getting responses
|
|
711
681
|
) -> LettaUsageStatistics:
|
|
712
682
|
"""Send a list of messages to the agent
|
|
713
683
|
|
|
@@ -760,7 +730,7 @@ class SyncServer(Server):
|
|
|
760
730
|
raise ValueError(f"All messages must be of type Message or MessageCreate, got {[type(message) for message in messages]}")
|
|
761
731
|
|
|
762
732
|
# Run the agent state forward
|
|
763
|
-
return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects)
|
|
733
|
+
return self._step(user_id=user_id, agent_id=agent_id, input_messages=message_objects, interface=interface)
|
|
764
734
|
|
|
765
735
|
# @LockingServer.agent_lock_decorator
|
|
766
736
|
def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
|
|
@@ -808,129 +778,109 @@ class SyncServer(Server):
|
|
|
808
778
|
else:
|
|
809
779
|
raise ValueError(f"Invalid agent type: {request.agent_type}")
|
|
810
780
|
|
|
781
|
+
# create blocks (note: cannot be linked into the agent_id is created)
|
|
782
|
+
blocks = []
|
|
783
|
+
for create_block in request.memory_blocks:
|
|
784
|
+
block = self.block_manager.create_or_update_block(Block(**create_block.model_dump()), actor=actor)
|
|
785
|
+
blocks.append(block)
|
|
786
|
+
|
|
787
|
+
# get tools + only add if they exist
|
|
788
|
+
tool_objs = []
|
|
789
|
+
if request.tools:
|
|
790
|
+
for tool_name in request.tools:
|
|
791
|
+
tool_obj = self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=actor)
|
|
792
|
+
if tool_obj:
|
|
793
|
+
tool_objs.append(tool_obj)
|
|
794
|
+
else:
|
|
795
|
+
warnings.warn(f"Attempted to add a nonexistent tool {tool_name} to agent {request.name}, skipping.")
|
|
796
|
+
# reset the request.tools to only valid tools
|
|
797
|
+
request.tools = [t.name for t in tool_objs]
|
|
798
|
+
|
|
799
|
+
# get the user
|
|
811
800
|
logger.debug(f"Attempting to find user: {user_id}")
|
|
812
801
|
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
813
802
|
if not user:
|
|
814
803
|
raise ValueError(f"cannot find user with associated client id: {user_id}")
|
|
815
804
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
for func_name, func in memory_functions.items():
|
|
836
|
-
|
|
837
|
-
if request.tools and func_name in request.tools:
|
|
838
|
-
# tool already added
|
|
839
|
-
continue
|
|
840
|
-
source_code = parse_source_code(func)
|
|
841
|
-
# memory functions are not terminal
|
|
842
|
-
json_schema = generate_schema(func, name=func_name)
|
|
843
|
-
source_type = "python"
|
|
844
|
-
tags = ["memory", "memgpt-base"]
|
|
845
|
-
tool = self.tool_manager.create_or_update_tool(
|
|
846
|
-
Tool(
|
|
847
|
-
source_code=source_code,
|
|
848
|
-
source_type=source_type,
|
|
849
|
-
tags=tags,
|
|
850
|
-
json_schema=json_schema,
|
|
851
|
-
),
|
|
852
|
-
actor=actor,
|
|
853
|
-
)
|
|
854
|
-
tool_objs.append(tool)
|
|
855
|
-
if not request.tools:
|
|
856
|
-
request.tools = []
|
|
857
|
-
request.tools.append(tool.name)
|
|
858
|
-
|
|
859
|
-
# TODO: save the agent state
|
|
860
|
-
agent_state = AgentState(
|
|
861
|
-
name=request.name,
|
|
862
|
-
user_id=user_id,
|
|
863
|
-
tools=request.tools if request.tools else [],
|
|
864
|
-
tool_rules=request.tool_rules if request.tool_rules else [],
|
|
865
|
-
agent_type=request.agent_type or AgentType.memgpt_agent,
|
|
866
|
-
llm_config=llm_config,
|
|
867
|
-
embedding_config=embedding_config,
|
|
868
|
-
system=request.system,
|
|
869
|
-
memory=request.memory,
|
|
870
|
-
description=request.description,
|
|
871
|
-
metadata_=request.metadata_,
|
|
872
|
-
tags=request.tags,
|
|
873
|
-
)
|
|
874
|
-
if request.agent_type == AgentType.memgpt_agent:
|
|
875
|
-
agent = Agent(
|
|
876
|
-
interface=interface,
|
|
877
|
-
agent_state=agent_state,
|
|
878
|
-
tools=tool_objs,
|
|
879
|
-
# gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
|
|
880
|
-
first_message_verify_mono=(
|
|
881
|
-
True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
|
|
882
|
-
),
|
|
883
|
-
user=actor,
|
|
884
|
-
initial_message_sequence=request.initial_message_sequence,
|
|
885
|
-
)
|
|
886
|
-
elif request.agent_type == AgentType.o1_agent:
|
|
887
|
-
agent = O1Agent(
|
|
888
|
-
interface=interface,
|
|
889
|
-
agent_state=agent_state,
|
|
890
|
-
tools=tool_objs,
|
|
891
|
-
# gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
|
|
892
|
-
first_message_verify_mono=(
|
|
893
|
-
True if (llm_config and llm_config.model is not None and "gpt-4" in llm_config.model) else False
|
|
894
|
-
),
|
|
895
|
-
user=actor,
|
|
896
|
-
)
|
|
897
|
-
# rebuilding agent memory on agent create in case shared memory blocks
|
|
898
|
-
# were specified in the new agent's memory config. we're doing this for two reasons:
|
|
899
|
-
# 1. if only the ID of the shared memory block was specified, we can fetch its most recent value
|
|
900
|
-
# 2. if the shared block state changed since this agent initialization started, we can be sure to have the latest value
|
|
901
|
-
agent.rebuild_memory(force=True, ms=self.ms)
|
|
902
|
-
# FIXME: this is a hacky way to get the system prompts injected into agent into the DB
|
|
903
|
-
# self.ms.update_agent(agent.agent_state)
|
|
904
|
-
except Exception as e:
|
|
905
|
-
logger.exception(e)
|
|
906
|
-
try:
|
|
907
|
-
if agent:
|
|
908
|
-
self.ms.delete_agent(agent_id=agent.agent_state.id)
|
|
909
|
-
except Exception as delete_e:
|
|
910
|
-
logger.exception(f"Failed to delete_agent:\n{delete_e}")
|
|
911
|
-
raise e
|
|
805
|
+
# TODO: create the message objects (NOTE: do this after we migrate to `CreateMessage`)
|
|
806
|
+
|
|
807
|
+
# created and persist the agent state in the DB
|
|
808
|
+
agent_state = PersistedAgentState(
|
|
809
|
+
name=request.name,
|
|
810
|
+
user_id=user_id,
|
|
811
|
+
tool_names=request.tools if request.tools else [],
|
|
812
|
+
tool_rules=request.tool_rules,
|
|
813
|
+
agent_type=request.agent_type or AgentType.memgpt_agent,
|
|
814
|
+
llm_config=request.llm_config,
|
|
815
|
+
embedding_config=request.embedding_config,
|
|
816
|
+
system=request.system,
|
|
817
|
+
# other metadata
|
|
818
|
+
description=request.description,
|
|
819
|
+
metadata_=request.metadata_,
|
|
820
|
+
)
|
|
821
|
+
# TODO: move this to agent ORM
|
|
822
|
+
# this saves the agent ID and state into the DB
|
|
823
|
+
self.ms.create_agent(agent_state)
|
|
912
824
|
|
|
913
|
-
#
|
|
914
|
-
|
|
915
|
-
logger.debug(f"Created new agent from config: {agent}")
|
|
825
|
+
# Note: mappings (e.g. tags, blocks) are created after the agent is persisted
|
|
826
|
+
# TODO: add source mappings here as well
|
|
916
827
|
|
|
917
|
-
#
|
|
828
|
+
# create the tags
|
|
918
829
|
if request.tags:
|
|
919
830
|
for tag in request.tags:
|
|
920
|
-
self.agents_tags_manager.add_tag_to_agent(agent_id=
|
|
831
|
+
self.agents_tags_manager.add_tag_to_agent(agent_id=agent_state.id, tag=tag, actor=actor)
|
|
832
|
+
|
|
833
|
+
# create block mappins (now that agent is persisted)
|
|
834
|
+
for block in blocks:
|
|
835
|
+
# this links the created block to the agent
|
|
836
|
+
self.blocks_agents_manager.add_block_to_agent(block_id=block.id, agent_id=agent_state.id, block_label=block.label)
|
|
837
|
+
|
|
838
|
+
in_memory_agent_state = self.get_agent(agent_state.id)
|
|
839
|
+
return in_memory_agent_state
|
|
840
|
+
|
|
841
|
+
def get_agent(self, agent_id: str) -> AgentState:
|
|
842
|
+
"""
|
|
843
|
+
Retrieve the full agent state from the DB.
|
|
844
|
+
This gathers data accross multiple tables to provide the full state of an agent, which is passed into the `Agent` object for creation.
|
|
845
|
+
"""
|
|
846
|
+
|
|
847
|
+
# get data persisted from the DB
|
|
848
|
+
agent_state = self.ms.get_agent(agent_id=agent_id)
|
|
849
|
+
if agent_state is None:
|
|
850
|
+
# agent does not exist
|
|
851
|
+
return None
|
|
852
|
+
user = self.user_manager.get_user_by_id(user_id=agent_state.user_id)
|
|
853
|
+
|
|
854
|
+
# construct the in-memory, full agent state - this gather data stored in different tables but that needs to be passed to `Agent`
|
|
855
|
+
# we also return this data to the user to provide all the state related to an agent
|
|
856
|
+
|
|
857
|
+
# get `Memory` object by getting the linked block IDs and fetching the blocks, then putting that into a `Memory` object
|
|
858
|
+
# this is the "in memory" representation of the in-context memory
|
|
859
|
+
block_ids = self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
|
|
860
|
+
blocks = []
|
|
861
|
+
for block_id in block_ids:
|
|
862
|
+
block = self.block_manager.get_block_by_id(block_id=block_id, actor=user)
|
|
863
|
+
assert block, f"Block with ID {block_id} does not exist"
|
|
864
|
+
blocks.append(block)
|
|
865
|
+
memory = Memory(blocks=blocks)
|
|
866
|
+
|
|
867
|
+
# get `Tool` objects
|
|
868
|
+
tools = [self.tool_manager.get_tool_by_name(tool_name=tool_name, actor=user) for tool_name in agent_state.tool_names]
|
|
921
869
|
|
|
922
|
-
|
|
870
|
+
# get `Source` objects
|
|
871
|
+
sources = self.list_attached_sources(agent_id=agent_id)
|
|
923
872
|
|
|
924
|
-
#
|
|
925
|
-
|
|
873
|
+
# get the tags
|
|
874
|
+
tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
|
|
926
875
|
|
|
927
|
-
return agent
|
|
876
|
+
# return the full agent state - this contains all data needed to recreate the agent
|
|
877
|
+
return AgentState(**agent_state.model_dump(), memory=memory, tools=tools, sources=sources, tags=tags)
|
|
928
878
|
|
|
929
879
|
def update_agent(
|
|
930
880
|
self,
|
|
931
881
|
request: UpdateAgentState,
|
|
932
882
|
actor: User,
|
|
933
|
-
):
|
|
883
|
+
) -> AgentState:
|
|
934
884
|
"""Update the agents core memory block, return the new state"""
|
|
935
885
|
try:
|
|
936
886
|
self.user_manager.get_user_by_id(user_id=actor.id)
|
|
@@ -941,13 +891,7 @@ class SyncServer(Server):
|
|
|
941
891
|
raise ValueError(f"Agent agent_id={request.id} does not exist")
|
|
942
892
|
|
|
943
893
|
# Get the agent object (loaded in memory)
|
|
944
|
-
letta_agent = self.
|
|
945
|
-
|
|
946
|
-
# update the core memory of the agent
|
|
947
|
-
if request.memory:
|
|
948
|
-
assert isinstance(request.memory, Memory), type(request.memory)
|
|
949
|
-
new_memory_contents = request.memory.to_flat_dict()
|
|
950
|
-
_ = self.update_agent_core_memory(user_id=actor.id, agent_id=request.id, new_memory_contents=new_memory_contents)
|
|
894
|
+
letta_agent = self.load_agent(agent_id=request.id)
|
|
951
895
|
|
|
952
896
|
# update the system prompt
|
|
953
897
|
if request.system:
|
|
@@ -961,13 +905,13 @@ class SyncServer(Server):
|
|
|
961
905
|
letta_agent.set_message_buffer(message_ids=request.message_ids)
|
|
962
906
|
|
|
963
907
|
# tools
|
|
964
|
-
if request.
|
|
908
|
+
if request.tool_names:
|
|
965
909
|
# Replace tools and also re-link
|
|
966
910
|
|
|
967
911
|
# (1) get tools + make sure they exist
|
|
968
912
|
# Current and target tools as sets of tool names
|
|
969
|
-
current_tools = set(letta_agent.agent_state.
|
|
970
|
-
target_tools = set(request.
|
|
913
|
+
current_tools = set(letta_agent.agent_state.tool_names)
|
|
914
|
+
target_tools = set(request.tool_names)
|
|
971
915
|
|
|
972
916
|
# Calculate tools to add and remove
|
|
973
917
|
tools_to_add = target_tools - current_tools
|
|
@@ -984,7 +928,7 @@ class SyncServer(Server):
|
|
|
984
928
|
self.add_tool_to_agent(agent_id=request.id, tool_id=tool.id, user_id=actor.id)
|
|
985
929
|
|
|
986
930
|
# reload agent
|
|
987
|
-
letta_agent = self.
|
|
931
|
+
letta_agent = self.load_agent(agent_id=request.id)
|
|
988
932
|
|
|
989
933
|
# configs
|
|
990
934
|
if request.llm_config:
|
|
@@ -1012,7 +956,6 @@ class SyncServer(Server):
|
|
|
1012
956
|
self.agents_tags_manager.delete_tag_from_agent(agent_id=letta_agent.agent_state.id, tag=tag, actor=actor)
|
|
1013
957
|
|
|
1014
958
|
# save the agent
|
|
1015
|
-
assert isinstance(letta_agent.memory, Memory)
|
|
1016
959
|
save_agent(letta_agent, self.ms)
|
|
1017
960
|
# TODO: probably reload the agent somehow?
|
|
1018
961
|
return letta_agent.agent_state
|
|
@@ -1025,8 +968,8 @@ class SyncServer(Server):
|
|
|
1025
968
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1026
969
|
|
|
1027
970
|
# Get the agent object (loaded in memory)
|
|
1028
|
-
letta_agent = self.
|
|
1029
|
-
return letta_agent.tools
|
|
971
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
972
|
+
return letta_agent.agent_state.tools
|
|
1030
973
|
|
|
1031
974
|
def add_tool_to_agent(
|
|
1032
975
|
self,
|
|
@@ -1044,7 +987,7 @@ class SyncServer(Server):
|
|
|
1044
987
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1045
988
|
|
|
1046
989
|
# Get the agent object (loaded in memory)
|
|
1047
|
-
letta_agent = self.
|
|
990
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1048
991
|
|
|
1049
992
|
# Get all the tool objects from the request
|
|
1050
993
|
tool_objs = []
|
|
@@ -1052,7 +995,7 @@ class SyncServer(Server):
|
|
|
1052
995
|
assert tool_obj, f"Tool with id={tool_id} does not exist"
|
|
1053
996
|
tool_objs.append(tool_obj)
|
|
1054
997
|
|
|
1055
|
-
for tool in letta_agent.tools:
|
|
998
|
+
for tool in letta_agent.agent_state.tools:
|
|
1056
999
|
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
|
|
1057
1000
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1058
1001
|
|
|
@@ -1061,7 +1004,7 @@ class SyncServer(Server):
|
|
|
1061
1004
|
tool_objs.append(tool_obj)
|
|
1062
1005
|
|
|
1063
1006
|
# replace the list of tool names ("ids") inside the agent state
|
|
1064
|
-
letta_agent.agent_state.
|
|
1007
|
+
letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
|
|
1065
1008
|
|
|
1066
1009
|
# then attempt to link the tools modules
|
|
1067
1010
|
letta_agent.link_tools(tool_objs)
|
|
@@ -1086,11 +1029,11 @@ class SyncServer(Server):
|
|
|
1086
1029
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1087
1030
|
|
|
1088
1031
|
# Get the agent object (loaded in memory)
|
|
1089
|
-
letta_agent = self.
|
|
1032
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1090
1033
|
|
|
1091
1034
|
# Get all the tool_objs
|
|
1092
1035
|
tool_objs = []
|
|
1093
|
-
for tool in letta_agent.tools:
|
|
1036
|
+
for tool in letta_agent.agent_state.tools:
|
|
1094
1037
|
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id, actor=user)
|
|
1095
1038
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1096
1039
|
|
|
@@ -1099,7 +1042,7 @@ class SyncServer(Server):
|
|
|
1099
1042
|
tool_objs.append(tool_obj)
|
|
1100
1043
|
|
|
1101
1044
|
# replace the list of tool names ("ids") inside the agent state
|
|
1102
|
-
letta_agent.agent_state.
|
|
1045
|
+
letta_agent.agent_state.tool_names = [tool.name for tool in tool_objs]
|
|
1103
1046
|
|
|
1104
1047
|
# then attempt to link the tools modules
|
|
1105
1048
|
letta_agent.link_tools(tool_objs)
|
|
@@ -1108,18 +1051,9 @@ class SyncServer(Server):
|
|
|
1108
1051
|
save_agent(letta_agent, self.ms)
|
|
1109
1052
|
return letta_agent.agent_state
|
|
1110
1053
|
|
|
1111
|
-
def
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
agent_config = {
|
|
1116
|
-
"id": agent_state.id,
|
|
1117
|
-
"name": agent_state.name,
|
|
1118
|
-
"human": agent_state._metadata.get("human", None),
|
|
1119
|
-
"persona": agent_state._metadata.get("persona", None),
|
|
1120
|
-
"created_at": agent_state.created_at.isoformat(),
|
|
1121
|
-
}
|
|
1122
|
-
return agent_config
|
|
1054
|
+
def get_agent_state(self, user_id: str, agent_id: str) -> AgentState:
|
|
1055
|
+
# TODO: duplicate, remove
|
|
1056
|
+
return self.get_agent(agent_id=agent_id)
|
|
1123
1057
|
|
|
1124
1058
|
def list_agents(self, user_id: str, tags: Optional[List[str]] = None) -> List[AgentState]:
|
|
1125
1059
|
"""List all available agents to a user"""
|
|
@@ -1127,13 +1061,13 @@ class SyncServer(Server):
|
|
|
1127
1061
|
|
|
1128
1062
|
if tags is None:
|
|
1129
1063
|
agents_states = self.ms.list_agents(user_id=user_id)
|
|
1130
|
-
|
|
1064
|
+
agent_ids = [agent.id for agent in agents_states]
|
|
1131
1065
|
else:
|
|
1132
1066
|
agent_ids = []
|
|
1133
1067
|
for tag in tags:
|
|
1134
1068
|
agent_ids += self.agents_tags_manager.get_agents_by_tag(tag=tag, actor=user)
|
|
1135
1069
|
|
|
1136
|
-
|
|
1070
|
+
return [self.get_agent(agent_id=agent_id) for agent_id in agent_ids]
|
|
1137
1071
|
|
|
1138
1072
|
# convert name->id
|
|
1139
1073
|
|
|
@@ -1157,34 +1091,34 @@ class SyncServer(Server):
|
|
|
1157
1091
|
|
|
1158
1092
|
def get_agent_memory(self, agent_id: str) -> Memory:
|
|
1159
1093
|
"""Return the memory of an agent (core memory)"""
|
|
1160
|
-
agent = self.
|
|
1161
|
-
return agent.memory
|
|
1094
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1095
|
+
return agent.agent_state.memory
|
|
1162
1096
|
|
|
1163
1097
|
def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
|
|
1164
|
-
agent = self.
|
|
1098
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1165
1099
|
return ArchivalMemorySummary(size=len(agent.persistence_manager.archival_memory))
|
|
1166
1100
|
|
|
1167
1101
|
def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
|
|
1168
|
-
agent = self.
|
|
1102
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1169
1103
|
return RecallMemorySummary(size=len(agent.persistence_manager.recall_memory))
|
|
1170
1104
|
|
|
1171
1105
|
def get_in_context_message_ids(self, agent_id: str) -> List[str]:
|
|
1172
1106
|
"""Get the message ids of the in-context messages in the agent's memory"""
|
|
1173
1107
|
# Get the agent object (loaded in memory)
|
|
1174
|
-
|
|
1175
|
-
return [m.id for m in
|
|
1108
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1109
|
+
return [m.id for m in agent._messages]
|
|
1176
1110
|
|
|
1177
1111
|
def get_in_context_messages(self, agent_id: str) -> List[Message]:
|
|
1178
1112
|
"""Get the in-context messages in the agent's memory"""
|
|
1179
1113
|
# Get the agent object (loaded in memory)
|
|
1180
|
-
|
|
1181
|
-
return
|
|
1114
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1115
|
+
return agent._messages
|
|
1182
1116
|
|
|
1183
1117
|
def get_agent_message(self, agent_id: str, message_id: str) -> Message:
|
|
1184
1118
|
"""Get a single message from the agent's memory"""
|
|
1185
1119
|
# Get the agent object (loaded in memory)
|
|
1186
|
-
|
|
1187
|
-
message =
|
|
1120
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1121
|
+
message = agent.persistence_manager.recall_memory.storage.get(id=message_id)
|
|
1188
1122
|
return message
|
|
1189
1123
|
|
|
1190
1124
|
def get_agent_messages(
|
|
@@ -1192,11 +1126,10 @@ class SyncServer(Server):
|
|
|
1192
1126
|
agent_id: str,
|
|
1193
1127
|
start: int,
|
|
1194
1128
|
count: int,
|
|
1195
|
-
return_message_object: bool = True,
|
|
1196
1129
|
) -> Union[List[Message], List[LettaMessage]]:
|
|
1197
1130
|
"""Paginated query of all messages in agent message queue"""
|
|
1198
1131
|
# Get the agent object (loaded in memory)
|
|
1199
|
-
letta_agent = self.
|
|
1132
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1200
1133
|
|
|
1201
1134
|
if start < 0 or count < 0:
|
|
1202
1135
|
raise ValueError("Start and count values should be non-negative")
|
|
@@ -1214,10 +1147,6 @@ class SyncServer(Server):
|
|
|
1214
1147
|
# Slice the list for pagination
|
|
1215
1148
|
messages = reversed_messages[start:end_index]
|
|
1216
1149
|
|
|
1217
|
-
## Convert to json
|
|
1218
|
-
## Add a tag indicating in-context or not
|
|
1219
|
-
# json_messages = [{**record.to_json(), "in_context": True} for record in messages]
|
|
1220
|
-
|
|
1221
1150
|
else:
|
|
1222
1151
|
# need to access persistence manager for additional messages
|
|
1223
1152
|
db_iterator = letta_agent.persistence_manager.recall_memory.storage.get_all_paginated(page_size=count, offset=start)
|
|
@@ -1237,9 +1166,6 @@ class SyncServer(Server):
|
|
|
1237
1166
|
# for d in json_messages:
|
|
1238
1167
|
# d["in_context"] = True if str(d["id"]) in in_context_message_ids else False
|
|
1239
1168
|
|
|
1240
|
-
if not return_message_object:
|
|
1241
|
-
messages = [msg for m in messages for msg in m.to_letta_message()]
|
|
1242
|
-
|
|
1243
1169
|
return messages
|
|
1244
1170
|
|
|
1245
1171
|
def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
|
|
@@ -1250,7 +1176,7 @@ class SyncServer(Server):
|
|
|
1250
1176
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1251
1177
|
|
|
1252
1178
|
# Get the agent object (loaded in memory)
|
|
1253
|
-
letta_agent = self.
|
|
1179
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1254
1180
|
|
|
1255
1181
|
# iterate over records
|
|
1256
1182
|
db_iterator = letta_agent.persistence_manager.archival_memory.storage.get_all_paginated(page_size=count, offset=start)
|
|
@@ -1275,7 +1201,7 @@ class SyncServer(Server):
|
|
|
1275
1201
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1276
1202
|
|
|
1277
1203
|
# Get the agent object (loaded in memory)
|
|
1278
|
-
letta_agent = self.
|
|
1204
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1279
1205
|
|
|
1280
1206
|
# iterate over recorde
|
|
1281
1207
|
cursor, records = letta_agent.persistence_manager.archival_memory.storage.get_all_cursor(
|
|
@@ -1290,11 +1216,15 @@ class SyncServer(Server):
|
|
|
1290
1216
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1291
1217
|
|
|
1292
1218
|
# Get the agent object (loaded in memory)
|
|
1293
|
-
letta_agent = self.
|
|
1219
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1294
1220
|
|
|
1295
1221
|
# Insert into archival memory
|
|
1296
1222
|
passage_ids = letta_agent.persistence_manager.archival_memory.insert(memory_string=memory_contents, return_ids=True)
|
|
1297
1223
|
|
|
1224
|
+
# Update the agent
|
|
1225
|
+
# TODO: should this update the system prompt?
|
|
1226
|
+
save_agent(letta_agent, self.ms)
|
|
1227
|
+
|
|
1298
1228
|
# TODO: this is gross, fix
|
|
1299
1229
|
return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
|
|
1300
1230
|
|
|
@@ -1307,7 +1237,7 @@ class SyncServer(Server):
|
|
|
1307
1237
|
# TODO: should return a passage
|
|
1308
1238
|
|
|
1309
1239
|
# Get the agent object (loaded in memory)
|
|
1310
|
-
letta_agent = self.
|
|
1240
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1311
1241
|
|
|
1312
1242
|
# Delete by ID
|
|
1313
1243
|
# TODO check if it exists first, and throw error if not
|
|
@@ -1326,9 +1256,8 @@ class SyncServer(Server):
|
|
|
1326
1256
|
order: Optional[str] = "asc",
|
|
1327
1257
|
reverse: Optional[bool] = False,
|
|
1328
1258
|
return_message_object: bool = True,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
assistant_message_function_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
|
|
1259
|
+
assistant_message_tool_name: str = constants.DEFAULT_MESSAGE_TOOL,
|
|
1260
|
+
assistant_message_tool_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
|
|
1332
1261
|
) -> Union[List[Message], List[LettaMessage]]:
|
|
1333
1262
|
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1334
1263
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
@@ -1336,7 +1265,7 @@ class SyncServer(Server):
|
|
|
1336
1265
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1337
1266
|
|
|
1338
1267
|
# Get the agent object (loaded in memory)
|
|
1339
|
-
letta_agent = self.
|
|
1268
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1340
1269
|
|
|
1341
1270
|
# iterate over records
|
|
1342
1271
|
cursor, records = letta_agent.persistence_manager.recall_memory.storage.get_all_cursor(
|
|
@@ -1347,50 +1276,19 @@ class SyncServer(Server):
|
|
|
1347
1276
|
|
|
1348
1277
|
if not return_message_object:
|
|
1349
1278
|
# If we're GETing messages in reverse, we need to reverse the inner list (generated by to_letta_message)
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
)[::-1]
|
|
1359
|
-
]
|
|
1360
|
-
else:
|
|
1361
|
-
records = [
|
|
1362
|
-
msg
|
|
1363
|
-
for m in records
|
|
1364
|
-
for msg in m.to_letta_message(
|
|
1365
|
-
assistant_message=use_assistant_message,
|
|
1366
|
-
assistant_message_function_name=assistant_message_function_name,
|
|
1367
|
-
assistant_message_function_kwarg=assistant_message_function_kwarg,
|
|
1368
|
-
)
|
|
1369
|
-
]
|
|
1370
|
-
|
|
1371
|
-
return records
|
|
1372
|
-
|
|
1373
|
-
def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
|
|
1374
|
-
"""Return the config of an agent"""
|
|
1375
|
-
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1376
|
-
if agent_id:
|
|
1377
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1378
|
-
return None
|
|
1379
|
-
else:
|
|
1380
|
-
agent_state = self.ms.get_agent(agent_name=agent_name, user_id=user_id)
|
|
1381
|
-
if agent_state is None:
|
|
1382
|
-
raise ValueError(f"Agent agent_name={agent_name} does not exist")
|
|
1383
|
-
agent_id = agent_state.id
|
|
1279
|
+
records = [
|
|
1280
|
+
msg
|
|
1281
|
+
for m in records
|
|
1282
|
+
for msg in m.to_letta_message(
|
|
1283
|
+
assistant_message_tool_name=assistant_message_tool_name,
|
|
1284
|
+
assistant_message_tool_kwarg=assistant_message_tool_kwarg,
|
|
1285
|
+
)
|
|
1286
|
+
]
|
|
1384
1287
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
assert isinstance(letta_agent.memory, Memory)
|
|
1288
|
+
if reverse:
|
|
1289
|
+
records = records[::-1]
|
|
1388
1290
|
|
|
1389
|
-
|
|
1390
|
-
agent_state = letta_agent.agent_state.model_copy(deep=True)
|
|
1391
|
-
# Load the tags in for the agent_state
|
|
1392
|
-
agent_state.tags = self.agents_tags_manager.get_tags_for_agent(agent_id=agent_id, actor=user)
|
|
1393
|
-
return agent_state
|
|
1291
|
+
return records
|
|
1394
1292
|
|
|
1395
1293
|
def get_server_config(self, include_defaults: bool = False) -> dict:
|
|
1396
1294
|
"""Return the base config"""
|
|
@@ -1415,38 +1313,23 @@ class SyncServer(Server):
|
|
|
1415
1313
|
|
|
1416
1314
|
return response
|
|
1417
1315
|
|
|
1418
|
-
def update_agent_core_memory(self, user_id: str, agent_id: str,
|
|
1419
|
-
"""Update the
|
|
1420
|
-
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1421
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1422
|
-
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1423
|
-
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1316
|
+
def update_agent_core_memory(self, user_id: str, agent_id: str, label: str, value: str) -> Memory:
|
|
1317
|
+
"""Update the value of a block in the agent's memory"""
|
|
1424
1318
|
|
|
1425
|
-
#
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
# raise ValueError(f"Key {key} not found in agent memory {list(letta_agent.memory.list_block_names())}")
|
|
1434
|
-
raise ValueError(f"Key {key} not found in agent memory {str(letta_agent.memory.memory)}")
|
|
1435
|
-
if value is None:
|
|
1436
|
-
continue
|
|
1437
|
-
if letta_agent.memory.get_block(key) != value:
|
|
1438
|
-
letta_agent.memory.update_block_value(label=key, value=value) # update agent memory
|
|
1439
|
-
modified = True
|
|
1440
|
-
|
|
1441
|
-
# If we modified the memory contents, we need to rebuild the memory block inside the system message
|
|
1442
|
-
if modified:
|
|
1443
|
-
letta_agent.rebuild_memory()
|
|
1444
|
-
# save agent
|
|
1445
|
-
save_agent(letta_agent, self.ms)
|
|
1319
|
+
# get the block id
|
|
1320
|
+
block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=label)
|
|
1321
|
+
block_id = block.id
|
|
1322
|
+
|
|
1323
|
+
# update the block
|
|
1324
|
+
self.block_manager.update_block(
|
|
1325
|
+
block_id=block_id, block_update=BlockUpdate(value=value), actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1326
|
+
)
|
|
1446
1327
|
|
|
1447
|
-
|
|
1328
|
+
# load agent
|
|
1329
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1330
|
+
return letta_agent.agent_state.memory
|
|
1448
1331
|
|
|
1449
|
-
def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) ->
|
|
1332
|
+
def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> PersistedAgentState:
|
|
1450
1333
|
"""Update the name of the agent in the database"""
|
|
1451
1334
|
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1452
1335
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
@@ -1454,7 +1337,7 @@ class SyncServer(Server):
|
|
|
1454
1337
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
1455
1338
|
|
|
1456
1339
|
# Get the agent object (loaded in memory)
|
|
1457
|
-
letta_agent = self.
|
|
1340
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1458
1341
|
|
|
1459
1342
|
current_name = letta_agent.agent_state.name
|
|
1460
1343
|
if current_name == new_agent_name:
|
|
@@ -1476,6 +1359,7 @@ class SyncServer(Server):
|
|
|
1476
1359
|
# TODO: REMOVE THIS ONCE WE MIGRATE AGENTMODEL TO ORM MODEL
|
|
1477
1360
|
# TODO: EVENTUALLY WE GET AUTO-DELETES WHEN WE SPECIFY RELATIONSHIPS IN THE ORM
|
|
1478
1361
|
self.agents_tags_manager.delete_all_tags_from_agent(agent_id=agent_id, actor=actor)
|
|
1362
|
+
self.blocks_agents_manager.remove_all_agent_blocks(agent_id=agent_id)
|
|
1479
1363
|
|
|
1480
1364
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1481
1365
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1501,7 +1385,7 @@ class SyncServer(Server):
|
|
|
1501
1385
|
|
|
1502
1386
|
# Next, attempt to delete it from the actual database
|
|
1503
1387
|
try:
|
|
1504
|
-
self.ms.delete_agent(agent_id=agent_id)
|
|
1388
|
+
self.ms.delete_agent(agent_id=agent_id, per_agent_lock_manager=self.per_agent_lock_manager)
|
|
1505
1389
|
except Exception as e:
|
|
1506
1390
|
logger.exception(f"Failed to delete agent {agent_id} via ID with:\n{str(e)}")
|
|
1507
1391
|
raise ValueError(f"Failed to delete agent {agent_id} in database")
|
|
@@ -1643,9 +1527,10 @@ class SyncServer(Server):
|
|
|
1643
1527
|
raise ValueError(f"Need to provide at least source_id or source_name to find the source.")
|
|
1644
1528
|
# get connection to data source storage
|
|
1645
1529
|
source_connector = StorageConnector.get_storage_connector(TableType.PASSAGES, self.config, user_id=user_id)
|
|
1530
|
+
assert data_source, f"Data source with id={source_id} or name={source_name} does not exist"
|
|
1646
1531
|
|
|
1647
1532
|
# load agent
|
|
1648
|
-
agent = self.
|
|
1533
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1649
1534
|
|
|
1650
1535
|
# attach source to agent
|
|
1651
1536
|
agent.attach_source(data_source.id, source_connector, self.ms)
|
|
@@ -1670,7 +1555,7 @@ class SyncServer(Server):
|
|
|
1670
1555
|
source_id = source.id
|
|
1671
1556
|
|
|
1672
1557
|
# delete all Passage objects with source_id==source_id from agent's archival memory
|
|
1673
|
-
agent = self.
|
|
1558
|
+
agent = self.load_agent(agent_id=agent_id)
|
|
1674
1559
|
archival_memory = agent.persistence_manager.archival_memory
|
|
1675
1560
|
archival_memory.storage.delete({"source_id": source_id})
|
|
1676
1561
|
|
|
@@ -1749,34 +1634,43 @@ class SyncServer(Server):
|
|
|
1749
1634
|
def get_agent_message(self, agent_id: str, message_id: str) -> Optional[Message]:
|
|
1750
1635
|
"""Get a single message from the agent's memory"""
|
|
1751
1636
|
# Get the agent object (loaded in memory)
|
|
1752
|
-
letta_agent = self.
|
|
1637
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1753
1638
|
message = letta_agent.persistence_manager.recall_memory.storage.get(id=message_id)
|
|
1639
|
+
save_agent(letta_agent, self.ms)
|
|
1754
1640
|
return message
|
|
1755
1641
|
|
|
1756
1642
|
def update_agent_message(self, agent_id: str, request: UpdateMessage) -> Message:
|
|
1757
1643
|
"""Update the details of a message associated with an agent"""
|
|
1758
1644
|
|
|
1759
1645
|
# Get the current message
|
|
1760
|
-
letta_agent = self.
|
|
1761
|
-
|
|
1646
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1647
|
+
response = letta_agent.update_message(request=request)
|
|
1648
|
+
save_agent(letta_agent, self.ms)
|
|
1649
|
+
return response
|
|
1762
1650
|
|
|
1763
1651
|
def rewrite_agent_message(self, agent_id: str, new_text: str) -> Message:
|
|
1764
1652
|
|
|
1765
1653
|
# Get the current message
|
|
1766
|
-
letta_agent = self.
|
|
1767
|
-
|
|
1654
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1655
|
+
response = letta_agent.rewrite_message(new_text=new_text)
|
|
1656
|
+
save_agent(letta_agent, self.ms)
|
|
1657
|
+
return response
|
|
1768
1658
|
|
|
1769
1659
|
def rethink_agent_message(self, agent_id: str, new_thought: str) -> Message:
|
|
1770
1660
|
|
|
1771
1661
|
# Get the current message
|
|
1772
|
-
letta_agent = self.
|
|
1773
|
-
|
|
1662
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1663
|
+
response = letta_agent.rethink_message(new_thought=new_thought)
|
|
1664
|
+
save_agent(letta_agent, self.ms)
|
|
1665
|
+
return response
|
|
1774
1666
|
|
|
1775
1667
|
def retry_agent_message(self, agent_id: str) -> List[Message]:
|
|
1776
1668
|
|
|
1777
1669
|
# Get the current message
|
|
1778
|
-
letta_agent = self.
|
|
1779
|
-
|
|
1670
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1671
|
+
response = letta_agent.retry_message()
|
|
1672
|
+
save_agent(letta_agent, self.ms)
|
|
1673
|
+
return response
|
|
1780
1674
|
|
|
1781
1675
|
def get_user_or_default(self, user_id: Optional[str]) -> User:
|
|
1782
1676
|
"""Get the user object for user_id if it exists, otherwise return the default user object"""
|
|
@@ -1825,5 +1719,168 @@ class SyncServer(Server):
|
|
|
1825
1719
|
agent_id: str,
|
|
1826
1720
|
) -> ContextWindowOverview:
|
|
1827
1721
|
# Get the current message
|
|
1828
|
-
letta_agent = self.
|
|
1722
|
+
letta_agent = self.load_agent(agent_id=agent_id)
|
|
1829
1723
|
return letta_agent.get_context_window()
|
|
1724
|
+
|
|
1725
|
+
def link_block_to_agent_memory(self, user_id: str, agent_id: str, block_id: str) -> Memory:
|
|
1726
|
+
"""Link a block to an agent's memory"""
|
|
1727
|
+
block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
|
|
1728
|
+
if block is None:
|
|
1729
|
+
raise ValueError(f"Block with id {block_id} not found")
|
|
1730
|
+
self.blocks_agents_manager.add_block_to_agent(agent_id, block_id, block_label=block.label)
|
|
1731
|
+
|
|
1732
|
+
# get agent memory
|
|
1733
|
+
memory = self.get_agent(agent_id=agent_id).memory
|
|
1734
|
+
return memory
|
|
1735
|
+
|
|
1736
|
+
def unlink_block_from_agent_memory(self, user_id: str, agent_id: str, block_label: str, delete_if_no_ref: bool = True) -> Memory:
|
|
1737
|
+
"""Unlink a block from an agent's memory. If the block is not linked to any agent, delete it."""
|
|
1738
|
+
self.blocks_agents_manager.remove_block_with_label_from_agent(agent_id=agent_id, block_label=block_label)
|
|
1739
|
+
|
|
1740
|
+
# get agent memory
|
|
1741
|
+
memory = self.get_agent(agent_id=agent_id).memory
|
|
1742
|
+
return memory
|
|
1743
|
+
|
|
1744
|
+
def update_agent_memory_limit(self, user_id: str, agent_id: str, block_label: str, limit: int) -> Memory:
|
|
1745
|
+
"""Update the limit of a block in an agent's memory"""
|
|
1746
|
+
block = self.get_agent_block_by_label(user_id=user_id, agent_id=agent_id, label=block_label)
|
|
1747
|
+
self.block_manager.update_block(
|
|
1748
|
+
block_id=block.id, block_update=BlockUpdate(limit=limit), actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1749
|
+
)
|
|
1750
|
+
# get agent memory
|
|
1751
|
+
memory = self.get_agent(agent_id=agent_id).memory
|
|
1752
|
+
return memory
|
|
1753
|
+
|
|
1754
|
+
def upate_block(self, user_id: str, block_id: str, block_update: BlockUpdate) -> Block:
|
|
1755
|
+
"""Update a block"""
|
|
1756
|
+
return self.block_manager.update_block(
|
|
1757
|
+
block_id=block_id, block_update=block_update, actor=self.user_manager.get_user_by_id(user_id=user_id)
|
|
1758
|
+
)
|
|
1759
|
+
|
|
1760
|
+
def get_agent_block_by_label(self, user_id: str, agent_id: str, label: str) -> Block:
|
|
1761
|
+
"""Get a block by label"""
|
|
1762
|
+
# TODO: implement at ORM?
|
|
1763
|
+
for block_id in self.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id):
|
|
1764
|
+
block = self.block_manager.get_block_by_id(block_id=block_id, actor=self.user_manager.get_user_by_id(user_id=user_id))
|
|
1765
|
+
if block.label == label:
|
|
1766
|
+
return block
|
|
1767
|
+
return None
|
|
1768
|
+
|
|
1769
|
+
# def run_tool(self, tool_id: str, tool_args: str, user_id: str) -> FunctionReturn:
|
|
1770
|
+
# """Run a tool using the sandbox and return the result"""
|
|
1771
|
+
|
|
1772
|
+
# try:
|
|
1773
|
+
# tool_args_dict = json.loads(tool_args)
|
|
1774
|
+
# except json.JSONDecodeError:
|
|
1775
|
+
# raise ValueError("Invalid JSON string for tool_args")
|
|
1776
|
+
|
|
1777
|
+
# # Get the tool by ID
|
|
1778
|
+
# user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1779
|
+
# tool = self.tool_manager.get_tool_by_id(tool_id=tool_id, actor=user)
|
|
1780
|
+
# if tool.name is None:
|
|
1781
|
+
# raise ValueError(f"Tool with id {tool_id} does not have a name")
|
|
1782
|
+
|
|
1783
|
+
# # TODO eventually allow using agent state in tools
|
|
1784
|
+
# agent_state = None
|
|
1785
|
+
|
|
1786
|
+
# try:
|
|
1787
|
+
# sandbox_run_result = ToolExecutionSandbox(tool.name, tool_args_dict, user_id).run(agent_state=agent_state)
|
|
1788
|
+
# if sandbox_run_result is None:
|
|
1789
|
+
# raise ValueError(f"Tool with id {tool_id} returned execution with None")
|
|
1790
|
+
# function_response = str(sandbox_run_result.func_return)
|
|
1791
|
+
|
|
1792
|
+
# return FunctionReturn(
|
|
1793
|
+
# id="null",
|
|
1794
|
+
# function_call_id="null",
|
|
1795
|
+
# date=get_utc_time(),
|
|
1796
|
+
# status="success",
|
|
1797
|
+
# function_return=function_response,
|
|
1798
|
+
# )
|
|
1799
|
+
# except Exception as e:
|
|
1800
|
+
# # same as agent.py
|
|
1801
|
+
# from letta.constants import MAX_ERROR_MESSAGE_CHAR_LIMIT
|
|
1802
|
+
|
|
1803
|
+
# error_msg = f"Error executing tool {tool.name}: {e}"
|
|
1804
|
+
# if len(error_msg) > MAX_ERROR_MESSAGE_CHAR_LIMIT:
|
|
1805
|
+
# error_msg = error_msg[:MAX_ERROR_MESSAGE_CHAR_LIMIT]
|
|
1806
|
+
|
|
1807
|
+
# return FunctionReturn(
|
|
1808
|
+
# id="null",
|
|
1809
|
+
# function_call_id="null",
|
|
1810
|
+
# date=get_utc_time(),
|
|
1811
|
+
# status="error",
|
|
1812
|
+
# function_return=error_msg,
|
|
1813
|
+
# )
|
|
1814
|
+
|
|
1815
|
+
def run_tool_from_source(
|
|
1816
|
+
self,
|
|
1817
|
+
user_id: str,
|
|
1818
|
+
tool_args: str,
|
|
1819
|
+
tool_source: str,
|
|
1820
|
+
tool_source_type: Optional[str] = None,
|
|
1821
|
+
tool_name: Optional[str] = None,
|
|
1822
|
+
) -> FunctionReturn:
|
|
1823
|
+
"""Run a tool from source code"""
|
|
1824
|
+
|
|
1825
|
+
try:
|
|
1826
|
+
tool_args_dict = json.loads(tool_args)
|
|
1827
|
+
except json.JSONDecodeError:
|
|
1828
|
+
raise ValueError("Invalid JSON string for tool_args")
|
|
1829
|
+
|
|
1830
|
+
if tool_source_type is not None and tool_source_type != "python":
|
|
1831
|
+
raise ValueError("Only Python source code is supported at this time")
|
|
1832
|
+
|
|
1833
|
+
# NOTE: we're creating a floating Tool object and NOT persiting to DB
|
|
1834
|
+
tool = Tool(
|
|
1835
|
+
name=tool_name,
|
|
1836
|
+
source_code=tool_source,
|
|
1837
|
+
)
|
|
1838
|
+
assert tool.name is not None, "Failed to create tool object"
|
|
1839
|
+
|
|
1840
|
+
# TODO eventually allow using agent state in tools
|
|
1841
|
+
agent_state = None
|
|
1842
|
+
|
|
1843
|
+
# Next, attempt to run the tool with the sandbox
|
|
1844
|
+
try:
|
|
1845
|
+
sandbox_run_result = ToolExecutionSandbox(tool.name, tool_args_dict, user_id, tool_object=tool).run(agent_state=agent_state)
|
|
1846
|
+
if sandbox_run_result is None:
|
|
1847
|
+
raise ValueError(f"Tool with id {tool.id} returned execution with None")
|
|
1848
|
+
function_response = str(sandbox_run_result.func_return)
|
|
1849
|
+
|
|
1850
|
+
return FunctionReturn(
|
|
1851
|
+
id="null",
|
|
1852
|
+
function_call_id="null",
|
|
1853
|
+
date=get_utc_time(),
|
|
1854
|
+
status="success",
|
|
1855
|
+
function_return=function_response,
|
|
1856
|
+
)
|
|
1857
|
+
except Exception as e:
|
|
1858
|
+
# same as agent.py
|
|
1859
|
+
from letta.constants import MAX_ERROR_MESSAGE_CHAR_LIMIT
|
|
1860
|
+
|
|
1861
|
+
error_msg = f"Error executing tool {tool.name}: {e}"
|
|
1862
|
+
if len(error_msg) > MAX_ERROR_MESSAGE_CHAR_LIMIT:
|
|
1863
|
+
error_msg = error_msg[:MAX_ERROR_MESSAGE_CHAR_LIMIT]
|
|
1864
|
+
|
|
1865
|
+
return FunctionReturn(
|
|
1866
|
+
id="null",
|
|
1867
|
+
function_call_id="null",
|
|
1868
|
+
date=get_utc_time(),
|
|
1869
|
+
status="error",
|
|
1870
|
+
function_return=error_msg,
|
|
1871
|
+
)
|
|
1872
|
+
|
|
1873
|
+
# Composio wrappers
|
|
1874
|
+
def get_composio_apps(self) -> List["AppModel"]:
|
|
1875
|
+
"""Get a list of all Composio apps with actions"""
|
|
1876
|
+
apps = self.composio_client.apps.get()
|
|
1877
|
+
apps_with_actions = []
|
|
1878
|
+
for app in apps:
|
|
1879
|
+
if app.meta["actionsCount"] > 0:
|
|
1880
|
+
apps_with_actions.append(app)
|
|
1881
|
+
|
|
1882
|
+
return apps_with_actions
|
|
1883
|
+
|
|
1884
|
+
def get_composio_actions_from_app_name(self, composio_app_name: str) -> List["ActionModel"]:
|
|
1885
|
+
actions = self.composio_client.actions.get(apps=[composio_app_name])
|
|
1886
|
+
return actions
|