5050 generation_options_var ,
5151 llm_call_info_var ,
5252 raw_llm_request ,
53+ reasoning_trace_var ,
5354 streaming_handler_var ,
5455)
5556from nemoguardrails .embeddings .index import EmbeddingsIndex , IndexItem
5657from nemoguardrails .kb .kb import KnowledgeBase
5758from nemoguardrails .llm .params import llm_params
5859from nemoguardrails .llm .prompts import get_prompt
59- from nemoguardrails .llm .taskmanager import LLMTaskManager
60+ from nemoguardrails .llm .taskmanager import LLMTaskManager , ParsedTaskOutput
6061from nemoguardrails .llm .types import Task
6162from nemoguardrails .logging .explain import LLMCallInfo
6263from nemoguardrails .patch_asyncio import check_sync_call_from_async_loop
@@ -442,6 +443,7 @@ async def generate_user_intent(
442443 result = self .llm_task_manager .parse_task_output (
443444 Task .GENERATE_USER_INTENT , output = result
444445 )
446+ result = result .text
445447
446448 user_intent = get_first_nonempty_line (result )
447449 if user_intent is None :
@@ -530,6 +532,11 @@ async def generate_user_intent(
530532 text = self .llm_task_manager .parse_task_output (
531533 Task .GENERAL , output = text
532534 )
535+
536+ text = _process_parsed_output (
537+ text , self ._include_reasoning_traces ()
538+ )
539+
533540 else :
534541 # Initialize the LLMCallInfo object
535542 llm_call_info_var .set (LLMCallInfo (task = Task .GENERAL .value ))
@@ -565,6 +572,8 @@ async def generate_user_intent(
565572 text = self .llm_task_manager .parse_task_output (
566573 Task .GENERAL , output = result
567574 )
575+
576+ text = _process_parsed_output (text , self ._include_reasoning_traces ())
568577 text = text .strip ()
569578 if text .startswith ('"' ):
570579 text = text [1 :- 1 ]
@@ -646,6 +655,7 @@ async def generate_next_step(
646655 result = self .llm_task_manager .parse_task_output (
647656 Task .GENERATE_NEXT_STEPS , output = result
648657 )
658+ result = result .text
649659
650660 # If we don't have multi-step generation enabled, we only look at the first line.
651661 if not self .config .enable_multi_step_generation :
@@ -900,6 +910,10 @@ async def generate_bot_message(
900910 Task .GENERAL , output = result
901911 )
902912
913+ result = _process_parsed_output (
914+ result , self ._include_reasoning_traces ()
915+ )
916+
903917 log .info (
904918 "--- :: LLM Bot Message Generation passthrough call took %.2f seconds" ,
905919 time () - t0 ,
@@ -963,6 +977,10 @@ async def generate_bot_message(
963977 Task .GENERATE_BOT_MESSAGE , output = result
964978 )
965979
980+ result = _process_parsed_output (
981+ result , self ._include_reasoning_traces ()
982+ )
983+
966984 # TODO: catch openai.error.InvalidRequestError from exceeding max token length
967985
968986 result = get_multiline_response (result )
@@ -1055,6 +1073,7 @@ async def generate_value(
10551073 result = self .llm_task_manager .parse_task_output (
10561074 Task .GENERATE_VALUE , output = result
10571075 )
1076+ result = result .text
10581077
10591078 # We only use the first line for now
10601079 # TODO: support multi-line values?
@@ -1266,6 +1285,7 @@ async def generate_intent_steps_message(
12661285 result = self .llm_task_manager .parse_task_output (
12671286 Task .GENERATE_INTENT_STEPS_MESSAGE , output = result
12681287 )
1288+ result = result .text
12691289
12701290 # TODO: Implement logic for generating more complex Colang next steps (multi-step),
12711291 # not just a single bot intent.
@@ -1348,6 +1368,7 @@ async def generate_intent_steps_message(
13481368 result = self .llm_task_manager .parse_task_output (
13491369 Task .GENERAL , output = result
13501370 )
1371+ result = _process_parsed_output (result , self ._include_reasoning_traces ())
13511372 text = result .strip ()
13521373 if text .startswith ('"' ):
13531374 text = text [1 :- 1 ]
@@ -1360,6 +1381,10 @@ async def generate_intent_steps_message(
13601381 events = [new_event_dict ("BotMessage" , text = text )],
13611382 )
13621383
1384+ def _include_reasoning_traces (self ) -> bool :
1385+ """Get the configuration value for whether to include reasoning traces in output."""
1386+ return _get_apply_to_reasoning_traces (self .config )
1387+
13631388
13641389def clean_utterance_content (utterance : str ) -> str :
13651390 """
@@ -1377,3 +1402,27 @@ def clean_utterance_content(utterance: str) -> str:
13771402 # It should be translated to an actual \n character.
13781403 utterance = utterance .replace ("\\ n" , "\n " )
13791404 return utterance
1405+
1406+
1407+ def _record_reasoning_trace (trace : str ) -> None :
1408+ """Store the reasoning trace in context for later retrieval."""
1409+ reasoning_trace_var .set (trace )
1410+
1411+
1412+ def _assemble_response (text : str , trace : Optional [str ], include_reasoning : bool ) -> str :
1413+ """Combine trace and text if requested, otherwise just return text."""
1414+ return (trace + text ) if (trace and include_reasoning ) else text
1415+
1416+
1417+ def _process_parsed_output (
1418+ output : ParsedTaskOutput , include_reasoning_trace : bool
1419+ ) -> str :
1420+ """Record trace, then assemble the final LLM response."""
1421+ if reasoning_trace := output .reasoning_trace :
1422+ _record_reasoning_trace (reasoning_trace )
1423+ return _assemble_response (output .text , reasoning_trace , include_reasoning_trace )
1424+
1425+
1426+ def _get_apply_to_reasoning_traces (config : RailsConfig ) -> bool :
1427+ """Get the configuration value for whether to include reasoning traces in output."""
1428+ return config .rails .output .apply_to_reasoning_traces
0 commit comments