letta-nightly 0.5.4.dev20241126104249__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/errors.py +12 -0
- letta/functions/function_sets/base.py +38 -1
- letta/functions/functions.py +4 -6
- letta/functions/schema_generator.py +6 -5
- letta/helpers/tool_rule_solver.py +6 -5
- letta/main.py +1 -1
- letta/metadata.py +45 -42
- letta/o1_agent.py +1 -4
- letta/orm/block.py +2 -1
- letta/orm/blocks_agents.py +4 -1
- letta/orm/sqlalchemy_base.py +13 -0
- letta/persistence_manager.py +1 -0
- letta/schemas/agent.py +57 -52
- letta/schemas/block.py +70 -26
- 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 +100 -94
- letta/server/rest_api/routers/v1/blocks.py +50 -5
- letta/server/rest_api/routers/v1/tools.py +14 -3
- letta/server/server.py +246 -460
- letta/server/static_files/assets/index-9fa459a2.js +1 -1
- letta/services/block_manager.py +23 -4
- letta/services/blocks_agents_manager.py +23 -1
- letta/services/per_agent_lock_manager.py +18 -0
- letta/services/tool_execution_sandbox.py +1 -1
- letta/services/tool_manager.py +2 -1
- {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/RECORD +46 -45
- {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.4.dev20241126104249.dist-info → letta_nightly-0.5.4.dev20241128000451.dist-info}/entry_points.txt +0 -0
letta/__init__.py
CHANGED
|
@@ -4,7 +4,7 @@ __version__ = "0.5.4"
|
|
|
4
4
|
from letta.client.client import LocalClient, RESTClient, create_client
|
|
5
5
|
|
|
6
6
|
# imports for easier access
|
|
7
|
-
from letta.schemas.agent import AgentState
|
|
7
|
+
from letta.schemas.agent import AgentState, PersistedAgentState
|
|
8
8
|
from letta.schemas.block import Block
|
|
9
9
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
10
10
|
from letta.schemas.enums import JobStatus
|
letta/agent.py
CHANGED
|
@@ -31,7 +31,7 @@ from letta.metadata import MetadataStore
|
|
|
31
31
|
from letta.orm import User
|
|
32
32
|
from letta.persistence_manager import LocalStateManager
|
|
33
33
|
from letta.schemas.agent import AgentState, AgentStepResponse
|
|
34
|
-
from letta.schemas.block import
|
|
34
|
+
from letta.schemas.block import BlockUpdate
|
|
35
35
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
36
36
|
from letta.schemas.enums import MessageRole
|
|
37
37
|
from letta.schemas.memory import ContextWindowOverview, Memory
|
|
@@ -235,11 +235,8 @@ class Agent(BaseAgent):
|
|
|
235
235
|
def __init__(
|
|
236
236
|
self,
|
|
237
237
|
interface: Optional[Union[AgentInterface, StreamingRefreshCLIInterface]],
|
|
238
|
-
#
|
|
239
|
-
agent_state: AgentState,
|
|
240
|
-
tools: List[Tool],
|
|
238
|
+
agent_state: AgentState, # in-memory representation of the agent state (read from multiple tables)
|
|
241
239
|
user: User,
|
|
242
|
-
# memory: Memory,
|
|
243
240
|
# extras
|
|
244
241
|
messages_total: Optional[int] = None, # TODO remove?
|
|
245
242
|
first_message_verify_mono: bool = True, # TODO move to config?
|
|
@@ -253,7 +250,7 @@ class Agent(BaseAgent):
|
|
|
253
250
|
self.user = user
|
|
254
251
|
|
|
255
252
|
# link tools
|
|
256
|
-
self.link_tools(tools)
|
|
253
|
+
self.link_tools(agent_state.tools)
|
|
257
254
|
|
|
258
255
|
# initialize a tool rules solver
|
|
259
256
|
if agent_state.tool_rules:
|
|
@@ -265,26 +262,14 @@ class Agent(BaseAgent):
|
|
|
265
262
|
# add default rule for having send_message be a terminal tool
|
|
266
263
|
if agent_state.tool_rules is None:
|
|
267
264
|
agent_state.tool_rules = []
|
|
268
|
-
# Define the rule to add
|
|
269
|
-
send_message_terminal_rule = TerminalToolRule(tool_name="send_message")
|
|
270
|
-
# Check if an equivalent rule is already present
|
|
271
|
-
if not any(
|
|
272
|
-
isinstance(rule, TerminalToolRule) and rule.tool_name == send_message_terminal_rule.tool_name for rule in agent_state.tool_rules
|
|
273
|
-
):
|
|
274
|
-
agent_state.tool_rules.append(send_message_terminal_rule)
|
|
275
265
|
|
|
276
266
|
self.tool_rules_solver = ToolRulesSolver(tool_rules=agent_state.tool_rules)
|
|
277
267
|
|
|
278
268
|
# gpt-4, gpt-3.5-turbo, ...
|
|
279
269
|
self.model = self.agent_state.llm_config.model
|
|
280
270
|
|
|
281
|
-
#
|
|
282
|
-
self.
|
|
283
|
-
|
|
284
|
-
# Initialize the memory object
|
|
285
|
-
self.memory = self.agent_state.memory
|
|
286
|
-
assert isinstance(self.memory, Memory), f"Memory object is not of type Memory: {type(self.memory)}"
|
|
287
|
-
printd("Initialized memory object", self.memory.compile())
|
|
271
|
+
# state managers
|
|
272
|
+
self.block_manager = BlockManager()
|
|
288
273
|
|
|
289
274
|
# Interface must implement:
|
|
290
275
|
# - internal_monologue
|
|
@@ -322,8 +307,8 @@ class Agent(BaseAgent):
|
|
|
322
307
|
# Generate a sequence of initial messages to put in the buffer
|
|
323
308
|
init_messages = initialize_message_sequence(
|
|
324
309
|
model=self.model,
|
|
325
|
-
system=self.system,
|
|
326
|
-
memory=self.memory,
|
|
310
|
+
system=self.agent_state.system,
|
|
311
|
+
memory=self.agent_state.memory,
|
|
327
312
|
archival_memory=None,
|
|
328
313
|
recall_memory=None,
|
|
329
314
|
memory_edit_timestamp=get_utc_time(),
|
|
@@ -345,8 +330,8 @@ class Agent(BaseAgent):
|
|
|
345
330
|
# Basic "more human than human" initial message sequence
|
|
346
331
|
init_messages = initialize_message_sequence(
|
|
347
332
|
model=self.model,
|
|
348
|
-
system=self.system,
|
|
349
|
-
memory=self.memory,
|
|
333
|
+
system=self.agent_state.system,
|
|
334
|
+
memory=self.agent_state.memory,
|
|
350
335
|
archival_memory=None,
|
|
351
336
|
recall_memory=None,
|
|
352
337
|
memory_edit_timestamp=get_utc_time(),
|
|
@@ -380,6 +365,76 @@ class Agent(BaseAgent):
|
|
|
380
365
|
# Create the agent in the DB
|
|
381
366
|
self.update_state()
|
|
382
367
|
|
|
368
|
+
def update_memory_if_change(self, new_memory: Memory) -> bool:
|
|
369
|
+
"""
|
|
370
|
+
Update internal memory object and system prompt if there have been modifications.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
new_memory (Memory): the new memory object to compare to the current memory object
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
modified (bool): whether the memory was updated
|
|
377
|
+
"""
|
|
378
|
+
if self.agent_state.memory.compile() != new_memory.compile():
|
|
379
|
+
# update the blocks (LRW) in the DB
|
|
380
|
+
for label in self.agent_state.memory.list_block_labels():
|
|
381
|
+
updated_value = new_memory.get_block(label).value
|
|
382
|
+
if updated_value != self.agent_state.memory.get_block(label).value:
|
|
383
|
+
# update the block if it's changed
|
|
384
|
+
block_id = self.agent_state.memory.get_block(label).id
|
|
385
|
+
block = self.block_manager.update_block(
|
|
386
|
+
block_id=block_id, block_update=BlockUpdate(value=updated_value), actor=self.user
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# refresh memory from DB (using block ids)
|
|
390
|
+
self.agent_state.memory = Memory(
|
|
391
|
+
blocks=[self.block_manager.get_block_by_id(block.id, actor=self.user) for block in self.agent_state.memory.get_blocks()]
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# NOTE: don't do this since re-buildin the memory is handled at the start of the step
|
|
395
|
+
# rebuild memory - this records the last edited timestamp of the memory
|
|
396
|
+
# TODO: pass in update timestamp from block edit time
|
|
397
|
+
self.rebuild_system_prompt()
|
|
398
|
+
|
|
399
|
+
return True
|
|
400
|
+
return False
|
|
401
|
+
|
|
402
|
+
def execute_tool_and_persist_state(self, function_name, function_to_call, function_args):
|
|
403
|
+
"""
|
|
404
|
+
Execute tool modifications and persist the state of the agent.
|
|
405
|
+
Note: only some agent state modifications will be persisted, such as data in the AgentState ORM and block data
|
|
406
|
+
"""
|
|
407
|
+
# TODO: add agent manager here
|
|
408
|
+
orig_memory_str = self.agent_state.memory.compile()
|
|
409
|
+
|
|
410
|
+
# TODO: need to have an AgentState object that actually has full access to the block data
|
|
411
|
+
# this is because the sandbox tools need to be able to access block.value to edit this data
|
|
412
|
+
try:
|
|
413
|
+
if function_name in BASE_TOOLS:
|
|
414
|
+
# base tools are allowed to access the `Agent` object and run on the database
|
|
415
|
+
function_args["self"] = self # need to attach self to arg since it's dynamically linked
|
|
416
|
+
function_response = function_to_call(**function_args)
|
|
417
|
+
else:
|
|
418
|
+
# execute tool in a sandbox
|
|
419
|
+
# TODO: allow agent_state to specify which sandbox to execute tools in
|
|
420
|
+
sandbox_run_result = ToolExecutionSandbox(function_name, function_args, self.agent_state.user_id).run(
|
|
421
|
+
agent_state=self.agent_state.__deepcopy__()
|
|
422
|
+
)
|
|
423
|
+
function_response, updated_agent_state = sandbox_run_result.func_return, sandbox_run_result.agent_state
|
|
424
|
+
assert orig_memory_str == self.agent_state.memory.compile(), "Memory should not be modified in a sandbox tool"
|
|
425
|
+
self.update_memory_if_change(updated_agent_state.memory)
|
|
426
|
+
except Exception as e:
|
|
427
|
+
# Need to catch error here, or else trunction wont happen
|
|
428
|
+
# TODO: modify to function execution error
|
|
429
|
+
from letta.constants import MAX_ERROR_MESSAGE_CHAR_LIMIT
|
|
430
|
+
|
|
431
|
+
error_msg = f"Error executing tool {function_name}: {e}"
|
|
432
|
+
if len(error_msg) > MAX_ERROR_MESSAGE_CHAR_LIMIT:
|
|
433
|
+
error_msg = error_msg[:MAX_ERROR_MESSAGE_CHAR_LIMIT]
|
|
434
|
+
raise ValueError(error_msg)
|
|
435
|
+
|
|
436
|
+
return function_response
|
|
437
|
+
|
|
383
438
|
@property
|
|
384
439
|
def messages(self) -> List[dict]:
|
|
385
440
|
"""Getter method that converts the internal Message list into OpenAI-style dicts"""
|
|
@@ -392,16 +447,6 @@ class Agent(BaseAgent):
|
|
|
392
447
|
def link_tools(self, tools: List[Tool]):
|
|
393
448
|
"""Bind a tool object (schema + python function) to the agent object"""
|
|
394
449
|
|
|
395
|
-
# tools
|
|
396
|
-
for tool in tools:
|
|
397
|
-
assert tool, f"Tool is None - must be error in querying tool from DB"
|
|
398
|
-
assert tool.name in self.agent_state.tools, f"Tool {tool} not found in agent_state.tools"
|
|
399
|
-
for tool_name in self.agent_state.tools:
|
|
400
|
-
assert tool_name in [tool.name for tool in tools], f"Tool name {tool_name} not included in agent tool list"
|
|
401
|
-
|
|
402
|
-
# Update tools
|
|
403
|
-
self.tools = tools
|
|
404
|
-
|
|
405
450
|
# Store the functions schemas (this is passed as an argument to ChatCompletion)
|
|
406
451
|
self.functions = []
|
|
407
452
|
self.functions_python = {}
|
|
@@ -416,9 +461,8 @@ class Agent(BaseAgent):
|
|
|
416
461
|
exec(tool.source_code, env)
|
|
417
462
|
self.functions_python[tool.json_schema["name"]] = env[tool.json_schema["name"]]
|
|
418
463
|
self.functions.append(tool.json_schema)
|
|
419
|
-
except Exception
|
|
464
|
+
except Exception:
|
|
420
465
|
warnings.warn(f"WARNING: tool {tool.name} failed to link")
|
|
421
|
-
print(e)
|
|
422
466
|
assert all([callable(f) for k, f in self.functions_python.items()]), self.functions_python
|
|
423
467
|
|
|
424
468
|
def _load_messages_from_recall(self, message_ids: List[str]) -> List[Message]:
|
|
@@ -727,27 +771,10 @@ class Agent(BaseAgent):
|
|
|
727
771
|
if isinstance(function_args[name], dict):
|
|
728
772
|
function_args[name] = spec[name](**function_args[name])
|
|
729
773
|
|
|
730
|
-
#
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if function_name in BASE_TOOLS:
|
|
734
|
-
function_args["self"] = self # need to attach self to arg since it's dynamically linked
|
|
735
|
-
function_response = function_to_call(**function_args)
|
|
736
|
-
else:
|
|
737
|
-
# execute tool in a sandbox
|
|
738
|
-
# TODO: allow agent_state to specify which sandbox to execute tools in
|
|
739
|
-
sandbox_run_result = ToolExecutionSandbox(function_name, function_args, self.agent_state.user_id).run(
|
|
740
|
-
agent_state=self.agent_state
|
|
741
|
-
)
|
|
742
|
-
function_response, updated_agent_state = sandbox_run_result.func_return, sandbox_run_result.agent_state
|
|
743
|
-
# update agent state
|
|
744
|
-
if self.agent_state != updated_agent_state and updated_agent_state is not None:
|
|
745
|
-
self.agent_state = updated_agent_state
|
|
746
|
-
self.memory = self.agent_state.memory # TODO: don't duplicate
|
|
747
|
-
|
|
748
|
-
# rebuild memory
|
|
749
|
-
self.rebuild_memory()
|
|
774
|
+
# handle tool execution (sandbox) and state updates
|
|
775
|
+
function_response = self.execute_tool_and_persist_state(function_name, function_to_call, function_args)
|
|
750
776
|
|
|
777
|
+
# handle trunction
|
|
751
778
|
if function_name in ["conversation_search", "conversation_search_date", "archival_memory_search"]:
|
|
752
779
|
# with certain functions we rely on the paging mechanism to handle overflow
|
|
753
780
|
truncate = False
|
|
@@ -820,7 +847,7 @@ class Agent(BaseAgent):
|
|
|
820
847
|
|
|
821
848
|
# rebuild memory
|
|
822
849
|
# TODO: @charles please check this
|
|
823
|
-
self.
|
|
850
|
+
self.rebuild_system_prompt()
|
|
824
851
|
|
|
825
852
|
# Update ToolRulesSolver state with last called function
|
|
826
853
|
self.tool_rules_solver.update_tool_usage(function_name)
|
|
@@ -936,17 +963,10 @@ class Agent(BaseAgent):
|
|
|
936
963
|
|
|
937
964
|
# Step 0: update core memory
|
|
938
965
|
# only pulling latest block data if shared memory is being used
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if not block.get("template", False):
|
|
944
|
-
should_update = True
|
|
945
|
-
if should_update:
|
|
946
|
-
# TODO: the force=True can be optimized away
|
|
947
|
-
# once we ensure we're correctly comparing whether in-memory core
|
|
948
|
-
# data is different than persisted core data.
|
|
949
|
-
self.rebuild_memory(force=True, ms=ms)
|
|
966
|
+
current_persisted_memory = Memory(
|
|
967
|
+
blocks=[self.block_manager.get_block_by_id(block.id, actor=self.user) for block in self.agent_state.memory.get_blocks()]
|
|
968
|
+
) # read blocks from DB
|
|
969
|
+
self.update_memory_if_change(current_persisted_memory)
|
|
950
970
|
|
|
951
971
|
# Step 1: add user message
|
|
952
972
|
if isinstance(messages, Message):
|
|
@@ -1229,43 +1249,10 @@ class Agent(BaseAgent):
|
|
|
1229
1249
|
new_messages = [new_system_message_obj] + self._messages[1:] # swap index 0 (system)
|
|
1230
1250
|
self._messages = new_messages
|
|
1231
1251
|
|
|
1232
|
-
def
|
|
1233
|
-
for block in self.memory.to_dict()["memory"].values():
|
|
1234
|
-
if block.get("templates", False):
|
|
1235
|
-
# we don't expect to update shared memory blocks that
|
|
1236
|
-
# are templates. this is something we could update in the
|
|
1237
|
-
# future if we expect templates to change often.
|
|
1238
|
-
continue
|
|
1239
|
-
block_id = block.get("id")
|
|
1240
|
-
|
|
1241
|
-
# TODO: This is really hacky and we should probably figure out how to
|
|
1242
|
-
db_block = BlockManager().get_block_by_id(block_id=block_id, actor=self.user)
|
|
1243
|
-
if db_block is None:
|
|
1244
|
-
# this case covers if someone has deleted a shared block by interacting
|
|
1245
|
-
# with some other agent.
|
|
1246
|
-
# in that case we should remove this shared block from the agent currently being
|
|
1247
|
-
# evaluated.
|
|
1248
|
-
printd(f"removing block: {block_id=}")
|
|
1249
|
-
continue
|
|
1250
|
-
if not isinstance(db_block.value, str):
|
|
1251
|
-
printd(f"skipping block update, unexpected value: {block_id=}")
|
|
1252
|
-
continue
|
|
1253
|
-
# TODO: we may want to update which columns we're updating from shared memory e.g. the limit
|
|
1254
|
-
self.memory.update_block_value(label=block.get("label", ""), value=db_block.value)
|
|
1255
|
-
|
|
1256
|
-
def rebuild_memory(self, force=False, update_timestamp=True, ms: Optional[MetadataStore] = None):
|
|
1252
|
+
def rebuild_system_prompt(self, force=False, update_timestamp=True):
|
|
1257
1253
|
"""Rebuilds the system message with the latest memory object and any shared memory block updates"""
|
|
1258
1254
|
curr_system_message = self.messages[0] # this is the system + memory bank, not just the system prompt
|
|
1259
1255
|
|
|
1260
|
-
# NOTE: This is a hacky way to check if the memory has changed
|
|
1261
|
-
memory_repr = self.memory.compile()
|
|
1262
|
-
if not force and memory_repr == curr_system_message["content"][-(len(memory_repr)) :]:
|
|
1263
|
-
printd(f"Memory has not changed, not rebuilding system")
|
|
1264
|
-
return
|
|
1265
|
-
|
|
1266
|
-
if ms:
|
|
1267
|
-
self.update_memory_blocks_from_db()
|
|
1268
|
-
|
|
1269
1256
|
# If the memory didn't update, we probably don't want to update the timestamp inside
|
|
1270
1257
|
# For example, if we're doing a system prompt swap, this should probably be False
|
|
1271
1258
|
if update_timestamp:
|
|
@@ -1276,8 +1263,8 @@ class Agent(BaseAgent):
|
|
|
1276
1263
|
|
|
1277
1264
|
# update memory (TODO: potentially update recall/archival stats seperately)
|
|
1278
1265
|
new_system_message_str = compile_system_message(
|
|
1279
|
-
system_prompt=self.system,
|
|
1280
|
-
in_context_memory=self.memory,
|
|
1266
|
+
system_prompt=self.agent_state.system,
|
|
1267
|
+
in_context_memory=self.agent_state.memory,
|
|
1281
1268
|
in_context_memory_last_edit=memory_edit_timestamp,
|
|
1282
1269
|
archival_memory=self.persistence_manager.archival_memory,
|
|
1283
1270
|
recall_memory=self.persistence_manager.recall_memory,
|
|
@@ -1304,13 +1291,13 @@ class Agent(BaseAgent):
|
|
|
1304
1291
|
"""Update the system prompt of the agent (requires rebuilding the memory block if there's a difference)"""
|
|
1305
1292
|
assert isinstance(new_system_prompt, str)
|
|
1306
1293
|
|
|
1307
|
-
if new_system_prompt == self.system:
|
|
1294
|
+
if new_system_prompt == self.agent_state.system:
|
|
1308
1295
|
return
|
|
1309
1296
|
|
|
1310
|
-
self.system = new_system_prompt
|
|
1297
|
+
self.agent_state.system = new_system_prompt
|
|
1311
1298
|
|
|
1312
1299
|
# updating the system prompt requires rebuilding the memory block inside the compiled system message
|
|
1313
|
-
self.
|
|
1300
|
+
self.rebuild_system_prompt(force=True, update_timestamp=False)
|
|
1314
1301
|
|
|
1315
1302
|
# make sure to persist the change
|
|
1316
1303
|
_ = self.update_state()
|
|
@@ -1324,6 +1311,7 @@ class Agent(BaseAgent):
|
|
|
1324
1311
|
raise NotImplementedError
|
|
1325
1312
|
|
|
1326
1313
|
def update_state(self) -> AgentState:
|
|
1314
|
+
# TODO: this should be removed and self._messages should be moved into self.agent_state.in_context_messages
|
|
1327
1315
|
message_ids = [msg.id for msg in self._messages]
|
|
1328
1316
|
|
|
1329
1317
|
# Assert that these are all strings
|
|
@@ -1331,12 +1319,8 @@ class Agent(BaseAgent):
|
|
|
1331
1319
|
warnings.warn(f"Non-string message IDs found in agent state: {message_ids}")
|
|
1332
1320
|
message_ids = [m_id for m_id in message_ids if isinstance(m_id, str)]
|
|
1333
1321
|
|
|
1334
|
-
assert isinstance(self.memory, Memory), f"Memory is not a Memory object: {type(self.memory)}"
|
|
1335
|
-
|
|
1336
1322
|
# override any fields that may have been updated
|
|
1337
1323
|
self.agent_state.message_ids = message_ids
|
|
1338
|
-
self.agent_state.memory = self.memory
|
|
1339
|
-
self.agent_state.system = self.system
|
|
1340
1324
|
|
|
1341
1325
|
return self.agent_state
|
|
1342
1326
|
|
|
@@ -1537,7 +1521,7 @@ class Agent(BaseAgent):
|
|
|
1537
1521
|
|
|
1538
1522
|
system_prompt = self.agent_state.system # TODO is this the current system or the initial system?
|
|
1539
1523
|
num_tokens_system = count_tokens(system_prompt)
|
|
1540
|
-
core_memory = self.memory.compile()
|
|
1524
|
+
core_memory = self.agent_state.memory.compile()
|
|
1541
1525
|
num_tokens_core_memory = count_tokens(core_memory)
|
|
1542
1526
|
|
|
1543
1527
|
# conversion of messages to OpenAI dict format, which is passed to the token counter
|
|
@@ -1629,37 +1613,15 @@ def save_agent(agent: Agent, ms: MetadataStore):
|
|
|
1629
1613
|
|
|
1630
1614
|
agent.update_state()
|
|
1631
1615
|
agent_state = agent.agent_state
|
|
1632
|
-
agent_id = agent_state.id
|
|
1633
1616
|
assert isinstance(agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
|
|
1634
1617
|
|
|
1635
|
-
#
|
|
1636
|
-
#
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
ms.update_agent(agent_state)
|
|
1618
|
+
# TODO: move this to agent manager
|
|
1619
|
+
# convert to persisted model
|
|
1620
|
+
persisted_agent_state = agent.agent_state.to_persisted_agent_state()
|
|
1621
|
+
if ms.get_agent(agent_id=persisted_agent_state.id):
|
|
1622
|
+
ms.update_agent(persisted_agent_state)
|
|
1641
1623
|
else:
|
|
1642
|
-
ms.create_agent(
|
|
1643
|
-
|
|
1644
|
-
agent.agent_state = ms.get_agent(agent_id=agent_id)
|
|
1645
|
-
assert isinstance(agent.agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
def save_agent_memory(agent: Agent):
|
|
1649
|
-
"""
|
|
1650
|
-
Save agent memory to metadata store. Memory is a collection of blocks and each block is persisted to the block table.
|
|
1651
|
-
|
|
1652
|
-
NOTE: we are assuming agent.update_state has already been called.
|
|
1653
|
-
"""
|
|
1654
|
-
|
|
1655
|
-
for block_dict in agent.memory.to_dict()["memory"].values():
|
|
1656
|
-
# TODO: block creation should happen in one place to enforce these sort of constraints consistently.
|
|
1657
|
-
block = Block(**block_dict)
|
|
1658
|
-
# FIXME: should we expect for block values to be None? If not, we need to figure out why that is
|
|
1659
|
-
# the case in some tests, if so we should relax the DB constraint.
|
|
1660
|
-
if block.value is None:
|
|
1661
|
-
block.value = ""
|
|
1662
|
-
BlockManager().create_or_update_block(block, actor=agent.user)
|
|
1624
|
+
ms.create_agent(persisted_agent_state)
|
|
1663
1625
|
|
|
1664
1626
|
|
|
1665
1627
|
def strip_name_field_from_user_message(user_message_text: str) -> Tuple[str, Optional[str]]:
|
letta/agent_store/chroma.py
CHANGED
|
@@ -125,6 +125,8 @@ class ChromaStorageConnector(StorageConnector):
|
|
|
125
125
|
ids, filters = self.get_filters(filters)
|
|
126
126
|
if self.collection.count() == 0:
|
|
127
127
|
return []
|
|
128
|
+
if ids == []:
|
|
129
|
+
ids = None
|
|
128
130
|
if limit:
|
|
129
131
|
results = self.collection.get(ids=ids, include=self.include, where=filters, limit=limit)
|
|
130
132
|
else:
|
letta/cli/cli.py
CHANGED
|
@@ -219,7 +219,7 @@ def run(
|
|
|
219
219
|
)
|
|
220
220
|
|
|
221
221
|
# create agent
|
|
222
|
-
tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.
|
|
222
|
+
tools = [server.tool_manager.get_tool_by_name(tool_name=tool_name, actor=client.user) for tool_name in agent_state.tool_names]
|
|
223
223
|
letta_agent = Agent(agent_state=agent_state, interface=interface(), tools=tools, user=client.user)
|
|
224
224
|
|
|
225
225
|
else: # create new agent
|
|
@@ -311,13 +311,11 @@ def run(
|
|
|
311
311
|
metadata=metadata,
|
|
312
312
|
)
|
|
313
313
|
assert isinstance(agent_state.memory, Memory), f"Expected Memory, got {type(agent_state.memory)}"
|
|
314
|
-
typer.secho(f"-> 🛠️ {len(agent_state.tools)} tools: {', '.join([t for t in agent_state.
|
|
315
|
-
tools = [server.tool_manager.get_tool_by_name(tool_name, actor=client.user) for tool_name in agent_state.tools]
|
|
314
|
+
typer.secho(f"-> 🛠️ {len(agent_state.tools)} tools: {', '.join([t for t in agent_state.tool_names])}", fg=typer.colors.WHITE)
|
|
316
315
|
|
|
317
316
|
letta_agent = Agent(
|
|
318
317
|
interface=interface(),
|
|
319
|
-
agent_state=agent_state,
|
|
320
|
-
tools=tools,
|
|
318
|
+
agent_state=client.get_agent(agent_state.id),
|
|
321
319
|
# gpt-3.5-turbo tends to omit inner monologue, relax this requirement for now
|
|
322
320
|
first_message_verify_mono=True if (model is not None and "gpt-4" in model) else False,
|
|
323
321
|
user=client.user,
|