2828 build_user_prompt ,
2929)
3030from rlm .utils .rlm_utils import filter_sensitive_keys
31+ from rlm .utils .trace_markdown import build_trace_markdown
3132
3233
3334class RLM :
@@ -100,8 +101,7 @@ def __init__(
100101 self .system_prompt = self .system_prompt_session
101102 self .logger = logger
102103 self .verbose = VerbosePrinter (enabled = verbose )
103-
104- # Persistence support
104+ self .trace_markdown_history = ""
105105 self .persistent = persistent
106106 self ._persistent_env : SupportsPersistence | None = None
107107
@@ -226,12 +226,15 @@ def completion(
226226 Returns:
227227 A final answer as a string.
228228 """
229- completion , _ = self ._run_completion (
229+ self .trace_markdown_history = ""
230+ completion , _ , trace_history = self ._run_completion (
230231 prompt = prompt ,
231232 root_prompt = root_prompt ,
232233 message_history = None ,
234+ trace_history = "" ,
233235 session_mode = False ,
234236 )
237+ self .trace_markdown_history = trace_history
235238 return completion
236239
237240 def _run_completion (
@@ -240,19 +243,38 @@ def _run_completion(
240243 prompt : str | dict [str , Any ],
241244 root_prompt : str | None ,
242245 message_history : list [dict [str , Any ]] | None ,
246+ trace_history : str ,
243247 session_mode : bool ,
244- ) -> tuple [RLMChatCompletion , list [dict [str , Any ]]]:
248+ ) -> tuple [RLMChatCompletion , list [dict [str , Any ]], str ]:
245249 time_start = time .perf_counter ()
246250
251+ run_context_entry = None
252+ if self .logger :
253+ run_context_entry = self .logger .log_run_context (
254+ prompt = prompt ,
255+ root_prompt = root_prompt ,
256+ environment_type = self .environment_type ,
257+ environment_kwargs = filter_sensitive_keys (self .environment_kwargs ),
258+ session_mode = session_mode ,
259+ )
260+
261+ run_context = run_context_entry or {
262+ "prompt" : prompt ,
263+ "root_prompt" : root_prompt ,
264+ "environment_type" : self .environment_type ,
265+ "environment_kwargs" : filter_sensitive_keys (self .environment_kwargs ),
266+ "session_mode" : session_mode ,
267+ }
268+
247269 # If we're at max depth, the RLM is an LM, so we fallback to the regular LM.
248270 if self .depth >= self .max_depth :
249271 client : BaseLM = get_client (self .backend , self .backend_kwargs )
250272 fallback_start = time .perf_counter ()
251273 response = client .completion (prompt )
252274 fallback_end = time .perf_counter ()
253275 usage = client .get_last_usage ()
254- if message_history is None :
255- message_history = self . _setup_prompt ( prompt , session_mode = session_mode )
276+ run_trace = build_trace_markdown ( run_context , [])
277+ trace_history = append_trace_history ( trace_history , run_trace )
256278 completion = RLMChatCompletion (
257279 root_model = self .backend_kwargs .get ("model_name" , "unknown" )
258280 if self .backend_kwargs
@@ -261,15 +283,19 @@ def _run_completion(
261283 response = response ,
262284 usage_summary = usage ,
263285 execution_time = fallback_end - fallback_start ,
286+ trace_markdown = trace_history ,
264287 )
265- return completion , message_history
288+ if message_history is None :
289+ message_history = self ._setup_prompt (prompt , session_mode = session_mode )
290+ return completion , message_history , trace_history
266291
267292 with self ._spawn_completion_context (
268293 prompt ,
269294 session_mode = session_mode ,
270295 ) as (lm_handler , environment ):
271296 if message_history is None :
272297 message_history = self ._setup_prompt (prompt , session_mode = session_mode )
298+ iterations : list [RLMIteration ] = []
273299
274300 for i in range (self .max_iterations ):
275301 # Current prompt = message history + additional prompt suffix
@@ -289,10 +315,13 @@ def _run_completion(
289315 prompt = current_prompt ,
290316 lm_handler = lm_handler ,
291317 environment = environment ,
318+ iteration_index = i + 1 ,
292319 )
320+ iterations .append (iteration )
293321
294322 # Check if RLM is done and has a final answer.
295- final_answer = find_final_answer (iteration .response , environment = environment )
323+ raw_final_answer = find_final_answer (iteration .response , environment = environment )
324+ final_answer = raw_final_answer or None
296325 iteration .final_answer = final_answer
297326
298327 # If logger is used, log the iteration.
@@ -316,6 +345,8 @@ def _run_completion(
316345 ):
317346 environment .add_history (message_history )
318347
348+ run_trace = build_trace_markdown (run_context , iterations )
349+ trace_history = append_trace_history (trace_history , run_trace )
319350 completion = RLMChatCompletion (
320351 root_model = self .backend_kwargs .get ("model_name" , "unknown" )
321352 if self .backend_kwargs
@@ -324,8 +355,9 @@ def _run_completion(
324355 response = final_answer ,
325356 usage_summary = usage ,
326357 execution_time = time_end - time_start ,
358+ trace_markdown = trace_history ,
327359 )
328- return completion , message_history
360+ return completion , message_history , trace_history
329361
330362 # Format the iteration for the next prompt.
331363 new_messages = format_iteration (iteration )
@@ -335,15 +367,19 @@ def _run_completion(
335367
336368 # Default behavior: we run out of iterations, provide one final answer
337369 time_end = time .perf_counter ()
338- final_answer = self ._default_answer (message_history , lm_handler )
370+ default_iteration = self ._default_answer (message_history , lm_handler )
371+ iterations .append (default_iteration )
372+ message_history .extend (format_iteration (default_iteration ))
373+ final_answer = default_iteration .final_answer or ""
339374 usage = lm_handler .get_usage_summary ()
340375 self .verbose .print_final_answer (final_answer )
341376 self .verbose .print_summary (self .max_iterations , time_end - time_start , usage .to_dict ())
342-
343377 # Store message history in persistent environment
344378 if session_mode and self .persistent and isinstance (environment , SupportsPersistence ):
345379 environment .add_history (message_history )
346380
381+ run_trace = build_trace_markdown (run_context , iterations )
382+ trace_history = append_trace_history (trace_history , run_trace )
347383 completion = RLMChatCompletion (
348384 root_model = self .backend_kwargs .get ("model_name" , "unknown" )
349385 if self .backend_kwargs
@@ -352,14 +388,16 @@ def _run_completion(
352388 response = final_answer ,
353389 usage_summary = usage ,
354390 execution_time = time_end - time_start ,
391+ trace_markdown = trace_history ,
355392 )
356- return completion , message_history
393+ return completion , message_history , trace_history
357394
358395 def _completion_turn (
359396 self ,
360397 prompt : str | dict [str , Any ],
361398 lm_handler : LMHandler ,
362399 environment : BaseEnv ,
400+ iteration_index : int ,
363401 ) -> RLMIteration :
364402 """
365403 Perform a single iteration of the RLM, including prompting the model
@@ -382,7 +420,9 @@ def _completion_turn(
382420 iteration_time = iteration_time ,
383421 )
384422
385- def _default_answer (self , message_history : list [dict [str , Any ]], lm_handler : LMHandler ) -> str :
423+ def _default_answer (
424+ self , message_history : list [dict [str , Any ]], lm_handler : LMHandler
425+ ) -> RLMIteration :
386426 """
387427 Default behavior if the RLM runs out of iterations and does not find a final answer.
388428 It will take the message history, and try to generate a final answer from it.
@@ -394,26 +434,17 @@ def _default_answer(self, message_history: list[dict[str, Any]], lm_handler: LMH
394434 }
395435 ]
396436 response = lm_handler .completion (current_prompt )
437+ iteration = RLMIteration (
438+ prompt = current_prompt ,
439+ response = response ,
440+ final_answer = response ,
441+ code_blocks = [],
442+ )
397443
398444 if self .logger :
399- self .logger .log (
400- RLMIteration (
401- prompt = current_prompt ,
402- response = response ,
403- final_answer = response ,
404- code_blocks = [],
405- )
406- )
445+ self .logger .log (iteration )
407446
408- return response
409-
410- def _fallback_answer (self , message : str | dict [str , Any ]) -> str :
411- """
412- Fallback behavior if the RLM is actually at max depth, and should be treated as an LM.
413- """
414- client : BaseLM = get_client (self .backend , self .backend_kwargs )
415- response = client .completion (message )
416- return response
447+ return iteration
417448
418449 def start_session (self ) -> "RLMSession" :
419450 return RLMSession (self )
@@ -468,13 +499,21 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
468499 return False
469500
470501
502+ def append_trace_history (existing : str , new_trace : str ) -> str :
503+ if not existing :
504+ return new_trace
505+ return f"{ existing } \n \n ---\n \n { new_trace } "
506+
507+
471508class RLMSession :
472509 def __init__ (self , rlm : RLM ):
473510 self .rlm = rlm
474511 self .message_history : list [dict [str , Any ]] | None = None
512+ self .trace_markdown_history = ""
475513
476514 def reset (self ) -> None :
477515 self .message_history = None
516+ self .trace_markdown_history = ""
478517
479518 def chat (
480519 self , prompt : str | dict [str , Any ], root_prompt : str | None = None
@@ -489,11 +528,13 @@ def chat(
489528 )[1 ]
490529 self .message_history .append (metadata_prompt )
491530
492- completion , message_history = self .rlm ._run_completion (
531+ completion , message_history , trace_history = self .rlm ._run_completion (
493532 prompt = prompt_payload ,
494533 root_prompt = root_prompt ,
495534 message_history = self .message_history ,
535+ trace_history = self .trace_markdown_history ,
496536 session_mode = True ,
497537 )
498538 self .message_history = message_history
539+ self .trace_markdown_history = trace_history
499540 return completion
0 commit comments