@@ -248,6 +248,7 @@ def safe_format(template: str, variables: dict) -> str:
248248 return escaped .format_map (PreserveMapping (variables ))
249249
250250
251+ @trace_method
251252def compile_system_message (
252253 system_prompt : str ,
253254 in_context_memory : Memory ,
@@ -327,6 +328,87 @@ def compile_system_message(
327328 return formatted_prompt
328329
329330
331+ @trace_method
332+ async def compile_system_message_async (
333+ system_prompt : str ,
334+ in_context_memory : Memory ,
335+ in_context_memory_last_edit : datetime , # TODO move this inside of BaseMemory?
336+ timezone : str ,
337+ user_defined_variables : Optional [dict ] = None ,
338+ append_icm_if_missing : bool = True ,
339+ template_format : Literal ["f-string" , "mustache" , "jinja2" ] = "f-string" ,
340+ previous_message_count : int = 0 ,
341+ archival_memory_size : int = 0 ,
342+ tool_rules_solver : Optional [ToolRulesSolver ] = None ,
343+ sources : Optional [List ] = None ,
344+ max_files_open : Optional [int ] = None ,
345+ ) -> str :
346+ """Prepare the final/full system message that will be fed into the LLM API
347+
348+ The base system message may be templated, in which case we need to render the variables.
349+
350+ The following are reserved variables:
351+ - CORE_MEMORY: the in-context memory of the LLM
352+ """
353+
354+ # Add tool rule constraints if available
355+ tool_constraint_block = None
356+ if tool_rules_solver is not None :
357+ tool_constraint_block = tool_rules_solver .compile_tool_rule_prompts ()
358+
359+ if user_defined_variables is not None :
360+ # TODO eventually support the user defining their own variables to inject
361+ raise NotImplementedError
362+ else :
363+ variables = {}
364+
365+ # Add the protected memory variable
366+ if IN_CONTEXT_MEMORY_KEYWORD in variables :
367+ raise ValueError (f"Found protected variable '{ IN_CONTEXT_MEMORY_KEYWORD } ' in user-defined vars: { str (user_defined_variables )} " )
368+ else :
369+ # TODO should this all put into the memory.__repr__ function?
370+ memory_metadata_string = compile_memory_metadata_block (
371+ memory_edit_timestamp = in_context_memory_last_edit ,
372+ previous_message_count = previous_message_count ,
373+ archival_memory_size = archival_memory_size ,
374+ timezone = timezone ,
375+ )
376+
377+ memory_with_sources = await in_context_memory .compile_async (
378+ tool_usage_rules = tool_constraint_block , sources = sources , max_files_open = max_files_open
379+ )
380+ full_memory_string = memory_with_sources + "\n \n " + memory_metadata_string
381+
382+ # Add to the variables list to inject
383+ variables [IN_CONTEXT_MEMORY_KEYWORD ] = full_memory_string
384+
385+ if template_format == "f-string" :
386+ memory_variable_string = "{" + IN_CONTEXT_MEMORY_KEYWORD + "}"
387+
388+ # Catch the special case where the system prompt is unformatted
389+ if append_icm_if_missing :
390+ if memory_variable_string not in system_prompt :
391+ # In this case, append it to the end to make sure memory is still injected
392+ # warnings.warn(f"{IN_CONTEXT_MEMORY_KEYWORD} variable was missing from system prompt, appending instead")
393+ system_prompt += "\n \n " + memory_variable_string
394+
395+ # render the variables using the built-in templater
396+ try :
397+ if user_defined_variables :
398+ formatted_prompt = safe_format (system_prompt , variables )
399+ else :
400+ formatted_prompt = system_prompt .replace (memory_variable_string , full_memory_string )
401+ except Exception as e :
402+ raise ValueError (f"Failed to format system prompt - { str (e )} . System prompt value:\n { system_prompt } " )
403+
404+ else :
405+ # TODO support for mustache and jinja2
406+ raise NotImplementedError (template_format )
407+
408+ return formatted_prompt
409+
410+
411+ @trace_method
330412def initialize_message_sequence (
331413 agent_state : AgentState ,
332414 memory_edit_timestamp : Optional [datetime ] = None ,
@@ -396,6 +478,76 @@ def initialize_message_sequence(
396478 return messages
397479
398480
481+ @trace_method
482+ async def initialize_message_sequence_async (
483+ agent_state : AgentState ,
484+ memory_edit_timestamp : Optional [datetime ] = None ,
485+ include_initial_boot_message : bool = True ,
486+ previous_message_count : int = 0 ,
487+ archival_memory_size : int = 0 ,
488+ ) -> List [dict ]:
489+ if memory_edit_timestamp is None :
490+ memory_edit_timestamp = get_local_time ()
491+
492+ full_system_message = await compile_system_message_async (
493+ system_prompt = agent_state .system ,
494+ in_context_memory = agent_state .memory ,
495+ in_context_memory_last_edit = memory_edit_timestamp ,
496+ timezone = agent_state .timezone ,
497+ user_defined_variables = None ,
498+ append_icm_if_missing = True ,
499+ previous_message_count = previous_message_count ,
500+ archival_memory_size = archival_memory_size ,
501+ sources = agent_state .sources ,
502+ max_files_open = agent_state .max_files_open ,
503+ )
504+ first_user_message = get_login_event (agent_state .timezone ) # event letting Letta know the user just logged in
505+
506+ if include_initial_boot_message :
507+ llm_config = agent_state .llm_config
508+ uuid_str = str (uuid .uuid4 ())
509+
510+ # Some LMStudio models (e.g. ministral) require the tool call ID to be 9 alphanumeric characters
511+ tool_call_id = uuid_str [:9 ] if llm_config .provider_name == "lmstudio_openai" else uuid_str
512+
513+ if agent_state .agent_type == AgentType .sleeptime_agent :
514+ initial_boot_messages = []
515+ elif llm_config .model is not None and "gpt-3.5" in llm_config .model :
516+ initial_boot_messages = get_initial_boot_messages ("startup_with_send_message_gpt35" , agent_state .timezone , tool_call_id )
517+ else :
518+ initial_boot_messages = get_initial_boot_messages ("startup_with_send_message" , agent_state .timezone , tool_call_id )
519+
520+ # Some LMStudio models (e.g. meta-llama-3.1) require the user message before any tool calls
521+ if llm_config .provider_name == "lmstudio_openai" :
522+ messages = (
523+ [
524+ {"role" : "system" , "content" : full_system_message },
525+ ]
526+ + [
527+ {"role" : "user" , "content" : first_user_message },
528+ ]
529+ + initial_boot_messages
530+ )
531+ else :
532+ messages = (
533+ [
534+ {"role" : "system" , "content" : full_system_message },
535+ ]
536+ + initial_boot_messages
537+ + [
538+ {"role" : "user" , "content" : first_user_message },
539+ ]
540+ )
541+
542+ else :
543+ messages = [
544+ {"role" : "system" , "content" : full_system_message },
545+ {"role" : "user" , "content" : first_user_message },
546+ ]
547+
548+ return messages
549+
550+
399551def package_initial_message_sequence (
400552 agent_id : str , initial_message_sequence : List [MessageCreate ], model : str , timezone : str , actor : User
401553) -> List [Message ]:
0 commit comments