letta-nightly 0.6.6.dev20241220190343__py3-none-any.whl → 0.6.6.dev20241221104005__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/agent.py CHANGED
@@ -1,25 +1,23 @@
1
- import datetime
2
1
  import inspect
3
2
  import json
4
3
  import time
5
4
  import traceback
6
5
  import warnings
7
6
  from abc import ABC, abstractmethod
8
- from typing import List, Literal, Optional, Tuple, Union
7
+ from typing import List, Optional, Tuple, Union
9
8
 
10
9
  from letta.constants import (
11
10
  BASE_TOOLS,
12
11
  CLI_WARNING_PREFIX,
12
+ ERROR_MESSAGE_PREFIX,
13
13
  FIRST_MESSAGE_ATTEMPTS,
14
14
  FUNC_FAILED_HEARTBEAT_MESSAGE,
15
- IN_CONTEXT_MEMORY_KEYWORD,
16
15
  LLM_MAX_TOKENS,
17
16
  MESSAGE_SUMMARY_TRUNC_KEEP_N_LAST,
18
17
  MESSAGE_SUMMARY_TRUNC_TOKEN_FRAC,
19
18
  MESSAGE_SUMMARY_WARNING_FRAC,
20
19
  O1_BASE_TOOLS,
21
20
  REQ_HEARTBEAT_MESSAGE,
22
- STRUCTURED_OUTPUT_MODELS,
23
21
  )
24
22
  from letta.errors import ContextWindowExceededError
25
23
  from letta.helpers import ToolRulesSolver
@@ -34,7 +32,7 @@ from letta.schemas.block import BlockUpdate
34
32
  from letta.schemas.embedding_config import EmbeddingConfig
35
33
  from letta.schemas.enums import MessageRole
36
34
  from letta.schemas.memory import ContextWindowOverview, Memory
37
- from letta.schemas.message import Message, MessageUpdate
35
+ from letta.schemas.message import Message
38
36
  from letta.schemas.openai.chat_completion_request import (
39
37
  Tool as ChatCompletionRequestTool,
40
38
  )
@@ -46,18 +44,18 @@ from letta.schemas.openai.chat_completion_response import UsageStatistics
46
44
  from letta.schemas.tool import Tool
47
45
  from letta.schemas.tool_rule import TerminalToolRule
48
46
  from letta.schemas.usage import LettaUsageStatistics
49
- from letta.schemas.user import User as PydanticUser
50
47
  from letta.services.agent_manager import AgentManager
51
48
  from letta.services.block_manager import BlockManager
49
+ from letta.services.helpers.agent_manager_helper import (
50
+ check_supports_structured_output,
51
+ compile_memory_metadata_block,
52
+ )
52
53
  from letta.services.message_manager import MessageManager
53
54
  from letta.services.passage_manager import PassageManager
54
- from letta.services.source_manager import SourceManager
55
55
  from letta.services.tool_execution_sandbox import ToolExecutionSandbox
56
56
  from letta.streaming_interface import StreamingRefreshCLIInterface
57
57
  from letta.system import (
58
58
  get_heartbeat,
59
- get_initial_boot_messages,
60
- get_login_event,
61
59
  get_token_limit_warning,
62
60
  package_function_response,
63
61
  package_summarize_message,
@@ -66,166 +64,20 @@ from letta.system import (
66
64
  from letta.utils import (
67
65
  count_tokens,
68
66
  get_friendly_error_msg,
69
- get_local_time,
70
67
  get_tool_call_id,
71
68
  get_utc_time,
72
- is_utc_datetime,
73
69
  json_dumps,
74
70
  json_loads,
75
71
  parse_json,
76
72
  printd,
77
- united_diff,
78
73
  validate_function_response,
79
- verify_first_message_correctness,
80
74
  )
81
75
 
82
76
 
83
- def compile_memory_metadata_block(
84
- actor: PydanticUser,
85
- agent_id: str,
86
- memory_edit_timestamp: datetime.datetime,
87
- agent_manager: Optional[AgentManager] = None,
88
- message_manager: Optional[MessageManager] = None,
89
- ) -> str:
90
- # Put the timestamp in the local timezone (mimicking get_local_time())
91
- timestamp_str = memory_edit_timestamp.astimezone().strftime("%Y-%m-%d %I:%M:%S %p %Z%z").strip()
92
-
93
- # Create a metadata block of info so the agent knows about the metadata of out-of-context memories
94
- memory_metadata_block = "\n".join(
95
- [
96
- f"### Memory [last modified: {timestamp_str}]",
97
- f"{message_manager.size(actor=actor, agent_id=agent_id) if message_manager else 0} previous messages between you and the user are stored in recall memory (use functions to access them)",
98
- f"{agent_manager.passage_size(actor=actor, agent_id=agent_id) if agent_manager else 0} total memories you created are stored in archival memory (use functions to access them)",
99
- "\nCore memory shown below (limited in size, additional information stored in archival / recall memory):",
100
- ]
101
- )
102
- return memory_metadata_block
103
-
104
-
105
- def compile_system_message(
106
- system_prompt: str,
107
- agent_id: str,
108
- in_context_memory: Memory,
109
- in_context_memory_last_edit: datetime.datetime, # TODO move this inside of BaseMemory?
110
- actor: PydanticUser,
111
- agent_manager: Optional[AgentManager] = None,
112
- message_manager: Optional[MessageManager] = None,
113
- user_defined_variables: Optional[dict] = None,
114
- append_icm_if_missing: bool = True,
115
- template_format: Literal["f-string", "mustache", "jinja2"] = "f-string",
116
- ) -> str:
117
- """Prepare the final/full system message that will be fed into the LLM API
118
-
119
- The base system message may be templated, in which case we need to render the variables.
120
-
121
- The following are reserved variables:
122
- - CORE_MEMORY: the in-context memory of the LLM
123
- """
124
-
125
- if user_defined_variables is not None:
126
- # TODO eventually support the user defining their own variables to inject
127
- raise NotImplementedError
128
- else:
129
- variables = {}
130
-
131
- # Add the protected memory variable
132
- if IN_CONTEXT_MEMORY_KEYWORD in variables:
133
- raise ValueError(f"Found protected variable '{IN_CONTEXT_MEMORY_KEYWORD}' in user-defined vars: {str(user_defined_variables)}")
134
- else:
135
- # TODO should this all put into the memory.__repr__ function?
136
- memory_metadata_string = compile_memory_metadata_block(
137
- actor=actor,
138
- agent_id=agent_id,
139
- memory_edit_timestamp=in_context_memory_last_edit,
140
- agent_manager=agent_manager,
141
- message_manager=message_manager,
142
- )
143
- full_memory_string = memory_metadata_string + "\n" + in_context_memory.compile()
144
-
145
- # Add to the variables list to inject
146
- variables[IN_CONTEXT_MEMORY_KEYWORD] = full_memory_string
147
-
148
- if template_format == "f-string":
149
-
150
- # Catch the special case where the system prompt is unformatted
151
- if append_icm_if_missing:
152
- memory_variable_string = "{" + IN_CONTEXT_MEMORY_KEYWORD + "}"
153
- if memory_variable_string not in system_prompt:
154
- # In this case, append it to the end to make sure memory is still injected
155
- # warnings.warn(f"{IN_CONTEXT_MEMORY_KEYWORD} variable was missing from system prompt, appending instead")
156
- system_prompt += "\n" + memory_variable_string
157
-
158
- # render the variables using the built-in templater
159
- try:
160
- formatted_prompt = system_prompt.format_map(variables)
161
- except Exception as e:
162
- raise ValueError(f"Failed to format system prompt - {str(e)}. System prompt value:\n{system_prompt}")
163
-
164
- else:
165
- # TODO support for mustache and jinja2
166
- raise NotImplementedError(template_format)
167
-
168
- return formatted_prompt
169
-
170
-
171
- def initialize_message_sequence(
172
- model: str,
173
- system: str,
174
- agent_id: str,
175
- memory: Memory,
176
- actor: PydanticUser,
177
- agent_manager: Optional[AgentManager] = None,
178
- message_manager: Optional[MessageManager] = None,
179
- memory_edit_timestamp: Optional[datetime.datetime] = None,
180
- include_initial_boot_message: bool = True,
181
- ) -> List[dict]:
182
- if memory_edit_timestamp is None:
183
- memory_edit_timestamp = get_local_time()
184
-
185
- # full_system_message = construct_system_with_memory(
186
- # system, memory, memory_edit_timestamp, agent_manager=agent_manager, recall_memory=recall_memory
187
- # )
188
- full_system_message = compile_system_message(
189
- agent_id=agent_id,
190
- system_prompt=system,
191
- in_context_memory=memory,
192
- in_context_memory_last_edit=memory_edit_timestamp,
193
- actor=actor,
194
- agent_manager=agent_manager,
195
- message_manager=message_manager,
196
- user_defined_variables=None,
197
- append_icm_if_missing=True,
198
- )
199
- first_user_message = get_login_event() # event letting Letta know the user just logged in
200
-
201
- if include_initial_boot_message:
202
- if model is not None and "gpt-3.5" in model:
203
- initial_boot_messages = get_initial_boot_messages("startup_with_send_message_gpt35")
204
- else:
205
- initial_boot_messages = get_initial_boot_messages("startup_with_send_message")
206
- messages = (
207
- [
208
- {"role": "system", "content": full_system_message},
209
- ]
210
- + initial_boot_messages
211
- + [
212
- {"role": "user", "content": first_user_message},
213
- ]
214
- )
215
-
216
- else:
217
- messages = [
218
- {"role": "system", "content": full_system_message},
219
- {"role": "user", "content": first_user_message},
220
- ]
221
-
222
- return messages
223
-
224
-
225
77
  class BaseAgent(ABC):
226
78
  """
227
79
  Abstract class for all agents.
228
- Only two interfaces are required: step and update_state.
80
+ Only one interface is required: step.
229
81
  """
230
82
 
231
83
  @abstractmethod
@@ -238,10 +90,6 @@ class BaseAgent(ABC):
238
90
  """
239
91
  raise NotImplementedError
240
92
 
241
- @abstractmethod
242
- def update_state(self) -> AgentState:
243
- raise NotImplementedError
244
-
245
93
 
246
94
  class Agent(BaseAgent):
247
95
  def __init__(
@@ -250,9 +98,7 @@ class Agent(BaseAgent):
250
98
  agent_state: AgentState, # in-memory representation of the agent state (read from multiple tables)
251
99
  user: User,
252
100
  # extras
253
- messages_total: Optional[int] = None, # TODO remove?
254
101
  first_message_verify_mono: bool = True, # TODO move to config?
255
- initial_message_sequence: Optional[List[Message]] = None,
256
102
  ):
257
103
  assert isinstance(agent_state.memory, Memory), f"Memory object is not of type Memory: {type(agent_state.memory)}"
258
104
  # Hold a copy of the state that was used to init the agent
@@ -276,7 +122,7 @@ class Agent(BaseAgent):
276
122
 
277
123
  # gpt-4, gpt-3.5-turbo, ...
278
124
  self.model = self.agent_state.llm_config.model
279
- self.check_tool_rules()
125
+ self.supports_structured_output = check_supports_structured_output(model=self.model, tool_rules=agent_state.tool_rules)
280
126
 
281
127
  # state managers
282
128
  self.block_manager = BlockManager()
@@ -304,99 +150,14 @@ class Agent(BaseAgent):
304
150
  # When the summarizer is run, set this back to False (to reset)
305
151
  self.agent_alerted_about_memory_pressure = False
306
152
 
307
- self._messages: List[Message] = []
308
-
309
- # Once the memory object is initialized, use it to "bake" the system message
310
- if self.agent_state.message_ids is not None:
311
- self.set_message_buffer(message_ids=self.agent_state.message_ids)
312
-
313
- else:
314
- printd(f"Agent.__init__ :: creating, state={agent_state.message_ids}")
315
- assert self.agent_state.id is not None and self.agent_state.created_by_id is not None
316
-
317
- # Generate a sequence of initial messages to put in the buffer
318
- init_messages = initialize_message_sequence(
319
- model=self.model,
320
- system=self.agent_state.system,
321
- agent_id=self.agent_state.id,
322
- memory=self.agent_state.memory,
323
- actor=self.user,
324
- agent_manager=None,
325
- message_manager=None,
326
- memory_edit_timestamp=get_utc_time(),
327
- include_initial_boot_message=True,
328
- )
329
-
330
- if initial_message_sequence is not None:
331
- # We always need the system prompt up front
332
- system_message_obj = Message.dict_to_message(
333
- agent_id=self.agent_state.id,
334
- user_id=self.agent_state.created_by_id,
335
- model=self.model,
336
- openai_message_dict=init_messages[0],
337
- )
338
- # Don't use anything else in the pregen sequence, instead use the provided sequence
339
- init_messages = [system_message_obj] + initial_message_sequence
340
-
341
- else:
342
- # Basic "more human than human" initial message sequence
343
- init_messages = initialize_message_sequence(
344
- model=self.model,
345
- system=self.agent_state.system,
346
- memory=self.agent_state.memory,
347
- agent_id=self.agent_state.id,
348
- actor=self.user,
349
- agent_manager=None,
350
- message_manager=None,
351
- memory_edit_timestamp=get_utc_time(),
352
- include_initial_boot_message=True,
353
- )
354
- # Cast to Message objects
355
- init_messages = [
356
- Message.dict_to_message(
357
- agent_id=self.agent_state.id, user_id=self.agent_state.created_by_id, model=self.model, openai_message_dict=msg
358
- )
359
- for msg in init_messages
360
- ]
361
-
362
- # Cast the messages to actual Message objects to be synced to the DB
363
- init_messages_objs = []
364
- for msg in init_messages:
365
- init_messages_objs.append(msg)
366
- for msg in init_messages_objs:
367
- assert isinstance(msg, Message), f"Message object is not of type Message: {type(msg)}"
368
- assert all([isinstance(msg, Message) for msg in init_messages_objs]), (init_messages_objs, init_messages)
369
-
370
- # Put the messages inside the message buffer
371
- self.messages_total = 0
372
- self._append_to_messages(added_messages=init_messages_objs)
373
- self._validate_message_buffer_is_utc()
374
-
375
153
  # Load last function response from message history
376
154
  self.last_function_response = self.load_last_function_response()
377
155
 
378
- # Keep track of the total number of messages throughout all time
379
- self.messages_total = messages_total if messages_total is not None else (len(self._messages) - 1) # (-system)
380
- self.messages_total_init = len(self._messages) - 1
381
- printd(f"Agent initialized, self.messages_total={self.messages_total}")
382
-
383
- # Create the agent in the DB
384
- self.update_state()
385
-
386
- def check_tool_rules(self):
387
- if self.model not in STRUCTURED_OUTPUT_MODELS:
388
- if len(self.tool_rules_solver.init_tool_rules) > 1:
389
- raise ValueError(
390
- "Multiple initial tools are not supported for non-structured models. Please use only one initial tool rule."
391
- )
392
- self.supports_structured_output = False
393
- else:
394
- self.supports_structured_output = True
395
-
396
156
  def load_last_function_response(self):
397
157
  """Load the last function response from message history"""
398
- for i in range(len(self._messages) - 1, -1, -1):
399
- msg = self._messages[i]
158
+ in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
159
+ for i in range(len(in_context_messages) - 1, -1, -1):
160
+ msg = in_context_messages[i]
400
161
  if msg.role == MessageRole.tool and msg.text:
401
162
  try:
402
163
  response_json = json.loads(msg.text)
@@ -435,7 +196,7 @@ class Agent(BaseAgent):
435
196
  # NOTE: don't do this since re-buildin the memory is handled at the start of the step
436
197
  # rebuild memory - this records the last edited timestamp of the memory
437
198
  # TODO: pass in update timestamp from block edit time
438
- self.rebuild_system_prompt()
199
+ self.agent_state = self.agent_manager.rebuild_system_prompt(agent_id=self.agent_state.id, actor=self.user)
439
200
 
440
201
  return True
441
202
  return False
@@ -487,109 +248,6 @@ class Agent(BaseAgent):
487
248
 
488
249
  return function_response
489
250
 
490
- @property
491
- def messages(self) -> List[dict]:
492
- """Getter method that converts the internal Message list into OpenAI-style dicts"""
493
- return [msg.to_openai_dict() for msg in self._messages]
494
-
495
- @messages.setter
496
- def messages(self, value):
497
- raise Exception("Modifying message list directly not allowed")
498
-
499
- def _load_messages_from_recall(self, message_ids: List[str]) -> List[Message]:
500
- """Load a list of messages from recall storage"""
501
-
502
- # Pull the message objects from the database
503
- message_objs = []
504
- for msg_id in message_ids:
505
- msg_obj = self.message_manager.get_message_by_id(msg_id, actor=self.user)
506
- if msg_obj:
507
- if isinstance(msg_obj, Message):
508
- message_objs.append(msg_obj)
509
- else:
510
- printd(f"Warning - message ID {msg_id} is not a Message object")
511
- warnings.warn(f"Warning - message ID {msg_id} is not a Message object")
512
- else:
513
- printd(f"Warning - message ID {msg_id} not found in recall storage")
514
- warnings.warn(f"Warning - message ID {msg_id} not found in recall storage")
515
-
516
- return message_objs
517
-
518
- def _validate_message_buffer_is_utc(self):
519
- """Iterate over the message buffer and force all messages to be UTC stamped"""
520
-
521
- for m in self._messages:
522
- # assert is_utc_datetime(m.created_at), f"created_at on message for agent {self.agent_state.name} isn't UTC:\n{vars(m)}"
523
- # TODO eventually do casting via an edit_message function
524
- if m.created_at:
525
- if not is_utc_datetime(m.created_at):
526
- printd(f"Warning - created_at on message for agent {self.agent_state.name} isn't UTC (text='{m.text}')")
527
- m.created_at = m.created_at.replace(tzinfo=datetime.timezone.utc)
528
-
529
- def set_message_buffer(self, message_ids: List[str], force_utc: bool = True):
530
- """Set the messages in the buffer to the message IDs list"""
531
-
532
- message_objs = self._load_messages_from_recall(message_ids=message_ids)
533
-
534
- # set the objects in the buffer
535
- self._messages = message_objs
536
-
537
- # bugfix for old agents that may not have had UTC specified in their timestamps
538
- if force_utc:
539
- self._validate_message_buffer_is_utc()
540
-
541
- # also sync the message IDs attribute
542
- self.agent_state.message_ids = message_ids
543
-
544
- def refresh_message_buffer(self):
545
- """Refresh the message buffer from the database"""
546
-
547
- messages_to_sync = self.agent_state.message_ids
548
- assert messages_to_sync and all([isinstance(msg_id, str) for msg_id in messages_to_sync])
549
-
550
- self.set_message_buffer(message_ids=messages_to_sync)
551
-
552
- def _trim_messages(self, num):
553
- """Trim messages from the front, not including the system message"""
554
- new_messages = [self._messages[0]] + self._messages[num:]
555
- self._messages = new_messages
556
-
557
- def _prepend_to_messages(self, added_messages: List[Message]):
558
- """Wrapper around self.messages.prepend to allow additional calls to a state/persistence manager"""
559
- assert all([isinstance(msg, Message) for msg in added_messages])
560
- self.message_manager.create_many_messages(added_messages, actor=self.user)
561
-
562
- new_messages = [self._messages[0]] + added_messages + self._messages[1:] # prepend (no system)
563
- self._messages = new_messages
564
- self.messages_total += len(added_messages) # still should increment the message counter (summaries are additions too)
565
-
566
- def _append_to_messages(self, added_messages: List[Message]):
567
- """Wrapper around self.messages.append to allow additional calls to a state/persistence manager"""
568
- assert all([isinstance(msg, Message) for msg in added_messages])
569
- self.message_manager.create_many_messages(added_messages, actor=self.user)
570
-
571
- # strip extra metadata if it exists
572
- # for msg in added_messages:
573
- # msg.pop("api_response", None)
574
- # msg.pop("api_args", None)
575
- new_messages = self._messages + added_messages # append
576
-
577
- self._messages = new_messages
578
- self.messages_total += len(added_messages)
579
-
580
- def append_to_messages(self, added_messages: List[dict]):
581
- """An external-facing message append, where dict-like messages are first converted to Message objects"""
582
- added_messages_objs = [
583
- Message.dict_to_message(
584
- agent_id=self.agent_state.id,
585
- user_id=self.agent_state.created_by_id,
586
- model=self.model,
587
- openai_message_dict=msg,
588
- )
589
- for msg in added_messages
590
- ]
591
- self._append_to_messages(added_messages_objs)
592
-
593
251
  def _get_ai_reply(
594
252
  self,
595
253
  message_sequence: List[Message],
@@ -839,7 +497,7 @@ class Agent(BaseAgent):
839
497
  function_args.pop("self", None)
840
498
  # error_msg = f"Error calling function {function_name} with args {function_args}: {str(e)}"
841
499
  # Less detailed - don't provide full args, idea is that it should be in recent context so no need (just adds noise)
842
- error_msg = f"Error calling function {function_name}: {str(e)}"
500
+ error_msg = get_friendly_error_msg(function_name=function_name, exception_name=type(e).__name__, exception_message=str(e))
843
501
  error_msg_user = f"{error_msg}\n{traceback.format_exc()}"
844
502
  printd(error_msg_user)
845
503
  function_response = package_function_response(False, error_msg)
@@ -862,8 +520,29 @@ class Agent(BaseAgent):
862
520
  self.interface.function_message(f"Error: {error_msg}", msg_obj=messages[-1])
863
521
  return messages, False, True # force a heartbeat to allow agent to handle error
864
522
 
523
+ # Step 4: check if function response is an error
524
+ if function_response_string.startswith(ERROR_MESSAGE_PREFIX):
525
+ function_response = package_function_response(False, function_response_string)
526
+ # TODO: truncate error message somehow
527
+ messages.append(
528
+ Message.dict_to_message(
529
+ agent_id=self.agent_state.id,
530
+ user_id=self.agent_state.created_by_id,
531
+ model=self.model,
532
+ openai_message_dict={
533
+ "role": "tool",
534
+ "name": function_name,
535
+ "content": function_response,
536
+ "tool_call_id": tool_call_id,
537
+ },
538
+ )
539
+ ) # extend conversation with function response
540
+ self.interface.function_message(f"Ran {function_name}({function_args})", msg_obj=messages[-1])
541
+ self.interface.function_message(f"Error: {function_response_string}", msg_obj=messages[-1])
542
+ return messages, False, True # force a heartbeat to allow agent to handle error
543
+
865
544
  # If no failures happened along the way: ...
866
- # Step 4: send the info on the function call and function response to GPT
545
+ # Step 5: send the info on the function call and function response to GPT
867
546
  messages.append(
868
547
  Message.dict_to_message(
869
548
  agent_id=self.agent_state.id,
@@ -898,7 +577,7 @@ class Agent(BaseAgent):
898
577
 
899
578
  # rebuild memory
900
579
  # TODO: @charles please check this
901
- self.rebuild_system_prompt()
580
+ self.agent_state = self.agent_manager.rebuild_system_prompt(agent_id=self.agent_state.id, actor=self.user)
902
581
 
903
582
  # Update ToolRulesSolver state with last called function
904
583
  self.tool_rules_solver.update_tool_usage(function_name)
@@ -930,6 +609,7 @@ class Agent(BaseAgent):
930
609
  messages=next_input_message,
931
610
  **kwargs,
932
611
  )
612
+
933
613
  heartbeat_request = step_response.heartbeat_request
934
614
  function_failed = step_response.function_failed
935
615
  token_warning = step_response.in_context_memory_warning
@@ -1021,33 +701,19 @@ class Agent(BaseAgent):
1021
701
  if not all(isinstance(m, Message) for m in messages):
1022
702
  raise ValueError(f"messages should be a Message or a list of Message, got {type(messages)}")
1023
703
 
1024
- input_message_sequence = self._messages + messages
704
+ in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
705
+ input_message_sequence = in_context_messages + messages
1025
706
 
1026
707
  if len(input_message_sequence) > 1 and input_message_sequence[-1].role != "user":
1027
708
  printd(f"{CLI_WARNING_PREFIX}Attempting to run ChatCompletion without user as the last message in the queue")
1028
709
 
1029
710
  # Step 2: send the conversation and available functions to the LLM
1030
- if not skip_verify and (first_message or self.messages_total == self.messages_total_init):
1031
- printd(f"This is the first message. Running extra verifier on AI response.")
1032
- counter = 0
1033
- while True:
1034
- response = self._get_ai_reply(
1035
- message_sequence=input_message_sequence, first_message=True, stream=stream # passed through to the prompt formatter
1036
- )
1037
- if verify_first_message_correctness(response, require_monologue=self.first_message_verify_mono):
1038
- break
1039
-
1040
- counter += 1
1041
- if counter > first_message_retry_limit:
1042
- raise Exception(f"Hit first message retry limit ({first_message_retry_limit})")
1043
-
1044
- else:
1045
- response = self._get_ai_reply(
1046
- message_sequence=input_message_sequence,
1047
- first_message=first_message,
1048
- stream=stream,
1049
- step_count=step_count,
1050
- )
711
+ response = self._get_ai_reply(
712
+ message_sequence=input_message_sequence,
713
+ first_message=first_message,
714
+ stream=stream,
715
+ step_count=step_count,
716
+ )
1051
717
 
1052
718
  # Step 3: check if LLM wanted to call a function
1053
719
  # (if yes) Step 4: call the function
@@ -1095,10 +761,9 @@ class Agent(BaseAgent):
1095
761
  f"last response total_tokens ({current_total_tokens}) < {MESSAGE_SUMMARY_WARNING_FRAC * int(self.agent_state.llm_config.context_window)}"
1096
762
  )
1097
763
 
1098
- self._append_to_messages(all_new_messages)
1099
-
1100
- # update state after each step
1101
- self.update_state()
764
+ self.agent_state = self.agent_manager.append_to_in_context_messages(
765
+ all_new_messages, agent_id=self.agent_state.id, actor=self.user
766
+ )
1102
767
 
1103
768
  return AgentStepResponse(
1104
769
  messages=all_new_messages,
@@ -1113,7 +778,9 @@ class Agent(BaseAgent):
1113
778
 
1114
779
  # If we got a context alert, try trimming the messages length, then try again
1115
780
  if is_context_overflow_error(e):
1116
- printd(f"context window exceeded with limit {self.agent_state.llm_config.context_window}, running summarizer to trim messages")
781
+ printd(
782
+ f"context window exceeded with limit {self.agent_state.llm_config.context_window}, running summarizer to trim messages"
783
+ )
1117
784
  # A separate API call to run a summarizer
1118
785
  self.summarize_messages_inplace()
1119
786
 
@@ -1165,15 +832,19 @@ class Agent(BaseAgent):
1165
832
  return self.inner_step(messages=[user_message], **kwargs)
1166
833
 
1167
834
  def summarize_messages_inplace(self, cutoff=None, preserve_last_N_messages=True, disallow_tool_as_first=True):
1168
- assert self.messages[0]["role"] == "system", f"self.messages[0] should be system (instead got {self.messages[0]})"
835
+ in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
836
+ in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
837
+
838
+ if in_context_messages_openai[0]["role"] != "system":
839
+ raise RuntimeError(f"in_context_messages_openai[0] should be system (instead got {in_context_messages_openai[0]})")
1169
840
 
1170
841
  # Start at index 1 (past the system message),
1171
842
  # and collect messages for summarization until we reach the desired truncation token fraction (eg 50%)
1172
843
  # Do not allow truncation of the last N messages, since these are needed for in-context examples of function calling
1173
- token_counts = [count_tokens(str(msg)) for msg in self.messages]
844
+ token_counts = [count_tokens(str(msg)) for msg in in_context_messages_openai]
1174
845
  message_buffer_token_count = sum(token_counts[1:]) # no system message
1175
846
  desired_token_count_to_summarize = int(message_buffer_token_count * MESSAGE_SUMMARY_TRUNC_TOKEN_FRAC)
1176
- candidate_messages_to_summarize = self.messages[1:]
847
+ candidate_messages_to_summarize = in_context_messages_openai[1:]
1177
848
  token_counts = token_counts[1:]
1178
849
 
1179
850
  if preserve_last_N_messages:
@@ -1193,7 +864,7 @@ class Agent(BaseAgent):
1193
864
  "Not enough messages to compress for summarization",
1194
865
  details={
1195
866
  "num_candidate_messages": len(candidate_messages_to_summarize),
1196
- "num_total_messages": len(self.messages),
867
+ "num_total_messages": len(in_context_messages_openai),
1197
868
  "preserve_N": MESSAGE_SUMMARY_TRUNC_KEEP_N_LAST,
1198
869
  },
1199
870
  )
@@ -1212,9 +883,9 @@ class Agent(BaseAgent):
1212
883
  # Try to make an assistant message come after the cutoff
1213
884
  try:
1214
885
  printd(f"Selected cutoff {cutoff} was a 'user', shifting one...")
1215
- if self.messages[cutoff]["role"] == "user":
886
+ if in_context_messages_openai[cutoff]["role"] == "user":
1216
887
  new_cutoff = cutoff + 1
1217
- if self.messages[new_cutoff]["role"] == "user":
888
+ if in_context_messages_openai[new_cutoff]["role"] == "user":
1218
889
  printd(f"Shifted cutoff {new_cutoff} is still a 'user', ignoring...")
1219
890
  cutoff = new_cutoff
1220
891
  except IndexError:
@@ -1222,23 +893,23 @@ class Agent(BaseAgent):
1222
893
 
1223
894
  # Make sure the cutoff isn't on a 'tool' or 'function'
1224
895
  if disallow_tool_as_first:
1225
- while self.messages[cutoff]["role"] in ["tool", "function"] and cutoff < len(self.messages):
896
+ while in_context_messages_openai[cutoff]["role"] in ["tool", "function"] and cutoff < len(in_context_messages_openai):
1226
897
  printd(f"Selected cutoff {cutoff} was a 'tool', shifting one...")
1227
898
  cutoff += 1
1228
899
 
1229
- message_sequence_to_summarize = self._messages[1:cutoff] # do NOT get rid of the system message
900
+ message_sequence_to_summarize = in_context_messages[1:cutoff] # do NOT get rid of the system message
1230
901
  if len(message_sequence_to_summarize) <= 1:
1231
902
  # This prevents a potential infinite loop of summarizing the same message over and over
1232
903
  raise ContextWindowExceededError(
1233
904
  "Not enough messages to compress for summarization after determining cutoff",
1234
905
  details={
1235
906
  "num_candidate_messages": len(message_sequence_to_summarize),
1236
- "num_total_messages": len(self.messages),
907
+ "num_total_messages": len(in_context_messages_openai),
1237
908
  "preserve_N": MESSAGE_SUMMARY_TRUNC_KEEP_N_LAST,
1238
909
  },
1239
910
  )
1240
911
  else:
1241
- printd(f"Attempting to summarize {len(message_sequence_to_summarize)} messages [1:{cutoff}] of {len(self._messages)}")
912
+ printd(f"Attempting to summarize {len(message_sequence_to_summarize)} messages [1:{cutoff}] of {len(in_context_messages)}")
1242
913
 
1243
914
  # We can't do summarize logic properly if context_window is undefined
1244
915
  if self.agent_state.llm_config.context_window is None:
@@ -1253,118 +924,33 @@ class Agent(BaseAgent):
1253
924
  printd(f"Got summary: {summary}")
1254
925
 
1255
926
  # Metadata that's useful for the agent to see
1256
- all_time_message_count = self.messages_total
1257
- remaining_message_count = len(self.messages[cutoff:])
927
+ all_time_message_count = self.message_manager.size(agent_id=self.agent_state.id, actor=self.user)
928
+ remaining_message_count = len(in_context_messages_openai[cutoff:])
1258
929
  hidden_message_count = all_time_message_count - remaining_message_count
1259
930
  summary_message_count = len(message_sequence_to_summarize)
1260
931
  summary_message = package_summarize_message(summary, summary_message_count, hidden_message_count, all_time_message_count)
1261
932
  printd(f"Packaged into message: {summary_message}")
1262
933
 
1263
- prior_len = len(self.messages)
1264
- self._trim_messages(cutoff)
934
+ prior_len = len(in_context_messages_openai)
935
+ self.agent_state = self.agent_manager.trim_older_in_context_messages(cutoff, agent_id=self.agent_state.id, actor=self.user)
1265
936
  packed_summary_message = {"role": "user", "content": summary_message}
1266
- self._prepend_to_messages(
1267
- [
937
+ self.agent_state = self.agent_manager.prepend_to_in_context_messages(
938
+ messages=[
1268
939
  Message.dict_to_message(
1269
940
  agent_id=self.agent_state.id,
1270
941
  user_id=self.agent_state.created_by_id,
1271
942
  model=self.model,
1272
943
  openai_message_dict=packed_summary_message,
1273
944
  )
1274
- ]
1275
- )
1276
-
1277
- # reset alert
1278
- self.agent_alerted_about_memory_pressure = False
1279
-
1280
- printd(f"Ran summarizer, messages length {prior_len} -> {len(self.messages)}")
1281
-
1282
- def _swap_system_message_in_buffer(self, new_system_message: str):
1283
- """Update the system message (NOT prompt) of the Agent (requires updating the internal buffer)"""
1284
- assert isinstance(new_system_message, str)
1285
- new_system_message_obj = Message.dict_to_message(
945
+ ],
1286
946
  agent_id=self.agent_state.id,
1287
- user_id=self.agent_state.created_by_id,
1288
- model=self.model,
1289
- openai_message_dict={"role": "system", "content": new_system_message},
1290
- )
1291
-
1292
- assert new_system_message_obj.role == "system", new_system_message_obj
1293
- assert self._messages[0].role == "system", self._messages
1294
-
1295
- self.message_manager.create_message(new_system_message_obj, actor=self.user)
1296
-
1297
- new_messages = [new_system_message_obj] + self._messages[1:] # swap index 0 (system)
1298
- self._messages = new_messages
1299
-
1300
- def rebuild_system_prompt(self, force=False, update_timestamp=True):
1301
- """Rebuilds the system message with the latest memory object and any shared memory block updates
1302
-
1303
- Updates to core memory blocks should trigger a "rebuild", which itself will create a new message object
1304
-
1305
- Updates to the memory header should *not* trigger a rebuild, since that will simply flood recall storage with excess messages
1306
- """
1307
-
1308
- curr_system_message = self.messages[0] # this is the system + memory bank, not just the system prompt
1309
-
1310
- # note: we only update the system prompt if the core memory is changed
1311
- # this means that the archival/recall memory statistics may be someout out of date
1312
- curr_memory_str = self.agent_state.memory.compile()
1313
- if curr_memory_str in curr_system_message["content"] and not force:
1314
- # NOTE: could this cause issues if a block is removed? (substring match would still work)
1315
- printd(f"Memory hasn't changed, skipping system prompt rebuild")
1316
- return
1317
-
1318
- # If the memory didn't update, we probably don't want to update the timestamp inside
1319
- # For example, if we're doing a system prompt swap, this should probably be False
1320
- if update_timestamp:
1321
- memory_edit_timestamp = get_utc_time()
1322
- else:
1323
- # NOTE: a bit of a hack - we pull the timestamp from the message created_by
1324
- memory_edit_timestamp = self._messages[0].created_at
1325
-
1326
- # update memory (TODO: potentially update recall/archival stats separately)
1327
- new_system_message_str = compile_system_message(
1328
- agent_id=self.agent_state.id,
1329
- system_prompt=self.agent_state.system,
1330
- in_context_memory=self.agent_state.memory,
1331
- in_context_memory_last_edit=memory_edit_timestamp,
1332
947
  actor=self.user,
1333
- agent_manager=self.agent_manager,
1334
- message_manager=self.message_manager,
1335
- user_defined_variables=None,
1336
- append_icm_if_missing=True,
1337
948
  )
1338
- new_system_message = {
1339
- "role": "system",
1340
- "content": new_system_message_str,
1341
- }
1342
-
1343
- diff = united_diff(curr_system_message["content"], new_system_message["content"])
1344
- if len(diff) > 0: # there was a diff
1345
- printd(f"Rebuilding system with new memory...\nDiff:\n{diff}")
1346
-
1347
- # Swap the system message out (only if there is a diff)
1348
- self._swap_system_message_in_buffer(new_system_message=new_system_message_str)
1349
- assert self.messages[0]["content"] == new_system_message["content"], (
1350
- self.messages[0]["content"],
1351
- new_system_message["content"],
1352
- )
1353
949
 
1354
- def update_system_prompt(self, new_system_prompt: str):
1355
- """Update the system prompt of the agent (requires rebuilding the memory block if there's a difference)"""
1356
- assert isinstance(new_system_prompt, str)
1357
-
1358
- if new_system_prompt == self.agent_state.system:
1359
- return
1360
-
1361
- self.agent_state.system = new_system_prompt
1362
-
1363
- # updating the system prompt requires rebuilding the memory block inside the compiled system message
1364
- self.rebuild_system_prompt(force=True, update_timestamp=False)
950
+ # reset alert
951
+ self.agent_alerted_about_memory_pressure = False
1365
952
 
1366
- # make sure to persist the change
1367
- _ = self.update_state()
953
+ printd(f"Ran summarizer, messages length {prior_len} -> {len(in_context_messages_openai)}")
1368
954
 
1369
955
  def add_function(self, function_name: str) -> str:
1370
956
  # TODO: refactor
@@ -1374,20 +960,6 @@ class Agent(BaseAgent):
1374
960
  # TODO: refactor
1375
961
  raise NotImplementedError
1376
962
 
1377
- def update_state(self) -> AgentState:
1378
- # TODO: this should be removed and self._messages should be moved into self.agent_state.in_context_messages
1379
- message_ids = [msg.id for msg in self._messages]
1380
-
1381
- # Assert that these are all strings
1382
- if any(not isinstance(m_id, str) for m_id in message_ids):
1383
- warnings.warn(f"Non-string message IDs found in agent state: {message_ids}")
1384
- message_ids = [m_id for m_id in message_ids if isinstance(m_id, str)]
1385
-
1386
- # override any fields that may have been updated
1387
- self.agent_state.message_ids = message_ids
1388
-
1389
- return self.agent_state
1390
-
1391
963
  def migrate_embedding(self, embedding_config: EmbeddingConfig):
1392
964
  """Migrate the agent to a new embedding"""
1393
965
  # TODO: archival memory
@@ -1395,149 +967,6 @@ class Agent(BaseAgent):
1395
967
  # TODO: recall memory
1396
968
  raise NotImplementedError()
1397
969
 
1398
- def attach_source(
1399
- self,
1400
- user: PydanticUser,
1401
- source_id: str,
1402
- source_manager: SourceManager,
1403
- agent_manager: AgentManager,
1404
- ):
1405
- """Attach a source to the agent using the SourcesAgents ORM relationship.
1406
-
1407
- Args:
1408
- user: User performing the action
1409
- source_id: ID of the source to attach
1410
- source_manager: SourceManager instance to verify source exists
1411
- agent_manager: AgentManager instance to manage agent-source relationship
1412
- """
1413
- # Verify source exists and user has permission to access it
1414
- source = source_manager.get_source_by_id(source_id=source_id, actor=user)
1415
- assert source is not None, f"Source {source_id} not found in user's organization ({user.organization_id})"
1416
-
1417
- # Use the agent_manager to create the relationship
1418
- agent_manager.attach_source(agent_id=self.agent_state.id, source_id=source_id, actor=user)
1419
-
1420
- printd(
1421
- f"Attached data source {source.name} to agent {self.agent_state.name}.",
1422
- )
1423
-
1424
- def update_message(self, message_id: str, request: MessageUpdate) -> Message:
1425
- """Update the details of a message associated with an agent"""
1426
- # Save the updated message
1427
- updated_message = self.message_manager.update_message_by_id(message_id=message_id, message_update=request, actor=self.user)
1428
- return updated_message
1429
-
1430
- # TODO(sarah): should we be creating a new message here, or just editing a message?
1431
- def rethink_message(self, new_thought: str) -> Message:
1432
- """Rethink / update the last message"""
1433
- for x in range(len(self.messages) - 1, 0, -1):
1434
- msg_obj = self._messages[x]
1435
- if msg_obj.role == MessageRole.assistant:
1436
- updated_message = self.update_message(
1437
- message_id=msg_obj.id,
1438
- request=MessageUpdate(
1439
- text=new_thought,
1440
- ),
1441
- )
1442
- self.refresh_message_buffer()
1443
- return updated_message
1444
- raise ValueError(f"No assistant message found to update")
1445
-
1446
- # TODO(sarah): should we be creating a new message here, or just editing a message?
1447
- def rewrite_message(self, new_text: str) -> Message:
1448
- """Rewrite / update the send_message text on the last message"""
1449
-
1450
- # Walk backwards through the messages until we find an assistant message
1451
- for x in range(len(self._messages) - 1, 0, -1):
1452
- if self._messages[x].role == MessageRole.assistant:
1453
- # Get the current message content
1454
- message_obj = self._messages[x]
1455
-
1456
- # The rewrite target is the output of send_message
1457
- if message_obj.tool_calls is not None and len(message_obj.tool_calls) > 0:
1458
-
1459
- # Check that we hit an assistant send_message call
1460
- name_string = message_obj.tool_calls[0].function.name
1461
- if name_string is None or name_string != "send_message":
1462
- raise ValueError("Assistant missing send_message function call")
1463
-
1464
- args_string = message_obj.tool_calls[0].function.arguments
1465
- if args_string is None:
1466
- raise ValueError("Assistant missing send_message function arguments")
1467
-
1468
- args_json = json_loads(args_string)
1469
- if "message" not in args_json:
1470
- raise ValueError("Assistant missing send_message message argument")
1471
-
1472
- # Once we found our target, rewrite it
1473
- args_json["message"] = new_text
1474
- new_args_string = json_dumps(args_json)
1475
- message_obj.tool_calls[0].function.arguments = new_args_string
1476
-
1477
- # Write the update to the DB
1478
- updated_message = self.update_message(
1479
- message_id=message_obj.id,
1480
- request=MessageUpdate(
1481
- tool_calls=message_obj.tool_calls,
1482
- ),
1483
- )
1484
- self.refresh_message_buffer()
1485
- return updated_message
1486
-
1487
- raise ValueError("No assistant message found to update")
1488
-
1489
- def pop_message(self, count: int = 1) -> List[Message]:
1490
- """Pop the last N messages from the agent's memory"""
1491
- n_messages = len(self._messages)
1492
- popped_messages = []
1493
- MIN_MESSAGES = 2
1494
- if n_messages <= MIN_MESSAGES:
1495
- raise ValueError(f"Agent only has {n_messages} messages in stack, none left to pop")
1496
- elif n_messages - count < MIN_MESSAGES:
1497
- raise ValueError(f"Agent only has {n_messages} messages in stack, cannot pop more than {n_messages - MIN_MESSAGES}")
1498
- else:
1499
- # print(f"Popping last {count} messages from stack")
1500
- for _ in range(min(count, len(self._messages))):
1501
- # remove the message from the internal state of the agent
1502
- deleted_message = self._messages.pop()
1503
- # then also remove it from recall storage
1504
- try:
1505
- self.message_manager.delete_message_by_id(deleted_message.id, actor=self.user)
1506
- popped_messages.append(deleted_message)
1507
- except Exception as e:
1508
- warnings.warn(f"Error deleting message {deleted_message.id} from recall memory: {e}")
1509
- self._messages.append(deleted_message)
1510
- break
1511
-
1512
- return popped_messages
1513
-
1514
- def pop_until_user(self) -> List[Message]:
1515
- """Pop all messages until the last user message"""
1516
- if MessageRole.user not in [msg.role for msg in self._messages]:
1517
- raise ValueError("No user message found in buffer")
1518
-
1519
- popped_messages = []
1520
- while len(self._messages) > 0:
1521
- if self._messages[-1].role == MessageRole.user:
1522
- # we want to pop up to the last user message
1523
- return popped_messages
1524
- else:
1525
- popped_messages.append(self.pop_message(count=1))
1526
-
1527
- raise ValueError("No user message found in buffer")
1528
-
1529
- def retry_message(self) -> List[Message]:
1530
- """Retry / regenerate the last message"""
1531
- self.pop_until_user()
1532
- user_message = self.pop_message(count=1)[0]
1533
- assert user_message.text is not None, "User message text is None"
1534
- step_response = self.step_user_message(user_message_str=user_message.text)
1535
- messages = step_response.messages
1536
-
1537
- assert messages is not None
1538
- assert all(isinstance(msg, Message) for msg in messages), "step() returned non-Message objects"
1539
- return messages
1540
-
1541
970
  def get_context_window(self) -> ContextWindowOverview:
1542
971
  """Get the context window of the agent"""
1543
972
 
@@ -1546,24 +975,28 @@ class Agent(BaseAgent):
1546
975
  core_memory = self.agent_state.memory.compile()
1547
976
  num_tokens_core_memory = count_tokens(core_memory)
1548
977
 
978
+ # Grab the in-context messages
1549
979
  # conversion of messages to OpenAI dict format, which is passed to the token counter
1550
- messages_openai_format = self.messages
980
+ in_context_messages = self.agent_manager.get_in_context_messages(agent_id=self.agent_state.id, actor=self.user)
981
+ in_context_messages_openai = [m.to_openai_dict() for m in in_context_messages]
1551
982
 
1552
983
  # Check if there's a summary message in the message queue
1553
984
  if (
1554
- len(self._messages) > 1
1555
- and self._messages[1].role == MessageRole.user
1556
- and isinstance(self._messages[1].text, str)
985
+ len(in_context_messages) > 1
986
+ and in_context_messages[1].role == MessageRole.user
987
+ and isinstance(in_context_messages[1].text, str)
1557
988
  # TODO remove hardcoding
1558
- and "The following is a summary of the previous " in self._messages[1].text
989
+ and "The following is a summary of the previous " in in_context_messages[1].text
1559
990
  ):
1560
991
  # Summary message exists
1561
- assert self._messages[1].text is not None
1562
- summary_memory = self._messages[1].text
1563
- num_tokens_summary_memory = count_tokens(self._messages[1].text)
992
+ assert in_context_messages[1].text is not None
993
+ summary_memory = in_context_messages[1].text
994
+ num_tokens_summary_memory = count_tokens(in_context_messages[1].text)
1564
995
  # with a summary message, the real messages start at index 2
1565
996
  num_tokens_messages = (
1566
- num_tokens_from_messages(messages=messages_openai_format[2:], model=self.model) if len(messages_openai_format) > 2 else 0
997
+ num_tokens_from_messages(messages=in_context_messages_openai[2:], model=self.model)
998
+ if len(in_context_messages_openai) > 2
999
+ else 0
1567
1000
  )
1568
1001
 
1569
1002
  else:
@@ -1571,17 +1004,17 @@ class Agent(BaseAgent):
1571
1004
  num_tokens_summary_memory = 0
1572
1005
  # with no summary message, the real messages start at index 1
1573
1006
  num_tokens_messages = (
1574
- num_tokens_from_messages(messages=messages_openai_format[1:], model=self.model) if len(messages_openai_format) > 1 else 0
1007
+ num_tokens_from_messages(messages=in_context_messages_openai[1:], model=self.model)
1008
+ if len(in_context_messages_openai) > 1
1009
+ else 0
1575
1010
  )
1576
1011
 
1577
1012
  agent_manager_passage_size = self.agent_manager.passage_size(actor=self.user, agent_id=self.agent_state.id)
1578
1013
  message_manager_size = self.message_manager.size(actor=self.user, agent_id=self.agent_state.id)
1579
1014
  external_memory_summary = compile_memory_metadata_block(
1580
- actor=self.user,
1581
- agent_id=self.agent_state.id,
1582
- memory_edit_timestamp=get_utc_time(), # dummy timestamp
1583
- agent_manager=self.agent_manager,
1584
- message_manager=self.message_manager,
1015
+ memory_edit_timestamp=get_utc_time(),
1016
+ previous_message_count=self.message_manager.size(actor=self.user, agent_id=self.agent_state.id),
1017
+ archival_memory_size=self.agent_manager.passage_size(actor=self.user, agent_id=self.agent_state.id),
1585
1018
  )
1586
1019
  num_tokens_external_memory_summary = count_tokens(external_memory_summary)
1587
1020
 
@@ -1606,7 +1039,7 @@ class Agent(BaseAgent):
1606
1039
 
1607
1040
  return ContextWindowOverview(
1608
1041
  # context window breakdown (in messages)
1609
- num_messages=len(self._messages),
1042
+ num_messages=len(in_context_messages),
1610
1043
  num_archival_memory=agent_manager_passage_size,
1611
1044
  num_recall_memory=message_manager_size,
1612
1045
  num_tokens_external_memory_summary=num_tokens_external_memory_summary,
@@ -1621,7 +1054,7 @@ class Agent(BaseAgent):
1621
1054
  num_tokens_summary_memory=num_tokens_summary_memory,
1622
1055
  summary_memory=summary_memory,
1623
1056
  num_tokens_messages=num_tokens_messages,
1624
- messages=self._messages,
1057
+ messages=in_context_messages,
1625
1058
  # related to functions
1626
1059
  num_tokens_functions_definitions=num_tokens_available_functions_definitions,
1627
1060
  functions_definitions=available_functions_definitions,
@@ -1635,7 +1068,6 @@ class Agent(BaseAgent):
1635
1068
 
1636
1069
  def save_agent(agent: Agent):
1637
1070
  """Save agent to metadata store"""
1638
- agent.update_state()
1639
1071
  agent_state = agent.agent_state
1640
1072
  assert isinstance(agent_state.memory, Memory), f"Memory is not a Memory object: {type(agent_state.memory)}"
1641
1073