nvidia-nat 1.3.0a20250827__py3-none-any.whl → 1.3.0a20250829__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.
Files changed (87) hide show
  1. nat/agent/base.py +12 -7
  2. nat/agent/dual_node.py +7 -2
  3. nat/agent/react_agent/agent.py +15 -14
  4. nat/agent/react_agent/register.py +5 -1
  5. nat/agent/rewoo_agent/agent.py +23 -32
  6. nat/agent/rewoo_agent/register.py +8 -4
  7. nat/agent/tool_calling_agent/agent.py +15 -20
  8. nat/agent/tool_calling_agent/register.py +6 -2
  9. nat/builder/context.py +7 -2
  10. nat/builder/eval_builder.py +2 -2
  11. nat/builder/function.py +8 -8
  12. nat/builder/workflow_builder.py +21 -24
  13. nat/cli/cli_utils/config_override.py +1 -1
  14. nat/cli/commands/info/list_channels.py +1 -1
  15. nat/cli/commands/object_store/__init__.py +14 -0
  16. nat/cli/commands/object_store/object_store.py +227 -0
  17. nat/cli/commands/registry/publish.py +2 -2
  18. nat/cli/commands/registry/pull.py +2 -2
  19. nat/cli/commands/registry/remove.py +2 -2
  20. nat/cli/commands/registry/search.py +1 -1
  21. nat/cli/commands/start.py +1 -1
  22. nat/cli/commands/uninstall.py +1 -1
  23. nat/cli/commands/workflow/workflow_commands.py +4 -4
  24. nat/cli/entrypoint.py +3 -1
  25. nat/data_models/discovery_metadata.py +4 -4
  26. nat/data_models/gated_field_mixin.py +12 -14
  27. nat/data_models/temperature_mixin.py +1 -1
  28. nat/data_models/thinking_mixin.py +68 -0
  29. nat/data_models/top_p_mixin.py +1 -1
  30. nat/eval/evaluate.py +6 -6
  31. nat/eval/intermediate_step_adapter.py +1 -1
  32. nat/eval/rag_evaluator/evaluate.py +2 -2
  33. nat/eval/rag_evaluator/register.py +1 -1
  34. nat/eval/remote_workflow.py +3 -3
  35. nat/eval/swe_bench_evaluator/evaluate.py +5 -5
  36. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  37. nat/eval/tunable_rag_evaluator/evaluate.py +3 -3
  38. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +2 -2
  39. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  40. nat/front_ends/fastapi/fastapi_front_end_plugin.py +1 -1
  41. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +3 -3
  42. nat/front_ends/fastapi/message_handler.py +2 -2
  43. nat/front_ends/fastapi/message_validator.py +8 -10
  44. nat/front_ends/fastapi/response_helpers.py +4 -4
  45. nat/front_ends/fastapi/step_adaptor.py +1 -1
  46. nat/llm/aws_bedrock_llm.py +10 -9
  47. nat/llm/azure_openai_llm.py +9 -1
  48. nat/llm/nim_llm.py +2 -1
  49. nat/llm/openai_llm.py +2 -1
  50. nat/llm/utils/thinking.py +215 -0
  51. nat/observability/exporter/base_exporter.py +1 -1
  52. nat/observability/exporter/processing_exporter.py +8 -9
  53. nat/observability/exporter_manager.py +5 -5
  54. nat/observability/mixin/file_mixin.py +7 -7
  55. nat/observability/processor/batching_processor.py +4 -6
  56. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  57. nat/observability/processor/processor_factory.py +70 -0
  58. nat/profiler/calc/calc_runner.py +3 -4
  59. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  60. nat/profiler/callbacks/langchain_callback_handler.py +5 -5
  61. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  62. nat/profiler/callbacks/semantic_kernel_callback_handler.py +2 -2
  63. nat/profiler/decorators/function_tracking.py +125 -0
  64. nat/profiler/profile_runner.py +1 -1
  65. nat/profiler/utils.py +1 -1
  66. nat/registry_handlers/local/local_handler.py +2 -2
  67. nat/registry_handlers/package_utils.py +1 -1
  68. nat/registry_handlers/pypi/pypi_handler.py +3 -3
  69. nat/registry_handlers/rest/rest_handler.py +4 -4
  70. nat/retriever/milvus/retriever.py +1 -1
  71. nat/retriever/nemo_retriever/retriever.py +1 -1
  72. nat/runtime/loader.py +1 -1
  73. nat/runtime/runner.py +2 -2
  74. nat/settings/global_settings.py +1 -1
  75. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +1 -1
  76. nat/tool/nvidia_rag.py +1 -1
  77. nat/tool/retriever.py +3 -2
  78. nat/utils/io/yaml_tools.py +1 -1
  79. nat/utils/reactive/observer.py +2 -2
  80. nat/utils/settings/global_settings.py +2 -2
  81. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/METADATA +3 -1
  82. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/RECORD +87 -81
  83. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/WHEEL +0 -0
  84. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/entry_points.txt +0 -0
  85. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  86. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/licenses/LICENSE.md +0 -0
  87. {nvidia_nat-1.3.0a20250827.dist-info → nvidia_nat-1.3.0a20250829.dist-info}/top_level.txt +0 -0
nat/agent/base.py CHANGED
@@ -70,12 +70,14 @@ class BaseAgent(ABC):
70
70
  llm: BaseChatModel,
71
71
  tools: list[BaseTool],
72
72
  callbacks: list[AsyncCallbackHandler] | None = None,
73
- detailed_logs: bool = False) -> None:
73
+ detailed_logs: bool = False,
74
+ log_response_max_chars: int = 1000) -> None:
74
75
  logger.debug("Initializing Agent Graph")
75
76
  self.llm = llm
76
77
  self.tools = tools
77
78
  self.callbacks = callbacks or []
78
79
  self.detailed_logs = detailed_logs
80
+ self.log_response_max_chars = log_response_max_chars
79
81
  self.graph = None
80
82
 
81
83
  async def _stream_llm(self,
@@ -158,6 +160,11 @@ class BaseAgent(ABC):
158
160
  tool_call_id=tool.name,
159
161
  content=f"The tool {tool.name} provided an empty response.")
160
162
 
163
+ # ToolMessage only accepts str or list[str | dict] as content.
164
+ # Convert into list if the response is a dict.
165
+ if isinstance(response, dict):
166
+ response = [response]
167
+
161
168
  return ToolMessage(name=tool.name, tool_call_id=tool.name, content=response)
162
169
 
163
170
  except Exception as e:
@@ -181,10 +188,10 @@ class BaseAgent(ABC):
181
188
 
182
189
  # All retries exhausted, return error message
183
190
  error_content = "Tool call failed after all retry attempts. Last error: %s" % str(last_exception)
184
- logger.error("%s %s", AGENT_LOG_PREFIX, error_content)
191
+ logger.error("%s %s", AGENT_LOG_PREFIX, error_content, exc_info=True)
185
192
  return ToolMessage(name=tool.name, tool_call_id=tool.name, content=error_content, status="error")
186
193
 
187
- def _log_tool_response(self, tool_name: str, tool_input: Any, tool_response: str, max_chars: int = 1000) -> None:
194
+ def _log_tool_response(self, tool_name: str, tool_input: Any, tool_response: str) -> None:
188
195
  """
189
196
  Log tool response with consistent formatting and length limits.
190
197
 
@@ -196,13 +203,11 @@ class BaseAgent(ABC):
196
203
  The input that was passed to the tool
197
204
  tool_response : str
198
205
  The response from the tool
199
- max_chars : int
200
- Maximum number of characters to log (default: 1000)
201
206
  """
202
207
  if self.detailed_logs:
203
208
  # Truncate tool response if too long
204
- display_response = tool_response[:max_chars] + "...(rest of response truncated)" if len(
205
- tool_response) > max_chars else tool_response
209
+ display_response = tool_response[:self.log_response_max_chars] + "...(rest of response truncated)" if len(
210
+ tool_response) > self.log_response_max_chars else tool_response
206
211
 
207
212
  # Format the tool input for display
208
213
  tool_input_str = str(tool_input)
nat/agent/dual_node.py CHANGED
@@ -35,8 +35,13 @@ class DualNodeAgent(BaseAgent):
35
35
  llm: BaseChatModel,
36
36
  tools: list[BaseTool],
37
37
  callbacks: list[AsyncCallbackHandler] | None = None,
38
- detailed_logs: bool = False):
39
- super().__init__(llm=llm, tools=tools, callbacks=callbacks, detailed_logs=detailed_logs)
38
+ detailed_logs: bool = False,
39
+ log_response_max_chars: int = 1000):
40
+ super().__init__(llm=llm,
41
+ tools=tools,
42
+ callbacks=callbacks,
43
+ detailed_logs=detailed_logs,
44
+ log_response_max_chars=log_response_max_chars)
40
45
 
41
46
  @abstractmethod
42
47
  async def agent_node(self, state: BaseModel) -> BaseModel:
@@ -73,11 +73,16 @@ class ReActAgentGraph(DualNodeAgent):
73
73
  use_tool_schema: bool = True,
74
74
  callbacks: list[AsyncCallbackHandler] | None = None,
75
75
  detailed_logs: bool = False,
76
+ log_response_max_chars: int = 1000,
76
77
  retry_agent_response_parsing_errors: bool = True,
77
78
  parse_agent_response_max_retries: int = 1,
78
79
  tool_call_max_retries: int = 1,
79
80
  pass_tool_call_errors_to_agent: bool = True):
80
- super().__init__(llm=llm, tools=tools, callbacks=callbacks, detailed_logs=detailed_logs)
81
+ super().__init__(llm=llm,
82
+ tools=tools,
83
+ callbacks=callbacks,
84
+ detailed_logs=detailed_logs,
85
+ log_response_max_chars=log_response_max_chars)
81
86
  self.parse_agent_response_max_retries = (parse_agent_response_max_retries
82
87
  if retry_agent_response_parsing_errors else 1)
83
88
  self.tool_call_max_retries = tool_call_max_retries
@@ -124,12 +129,8 @@ class ReActAgentGraph(DualNodeAgent):
124
129
  try:
125
130
  return self.tools_dict.get(tool_name)
126
131
  except Exception as ex:
127
- logger.exception("%s Unable to find tool with the name %s\n%s",
128
- AGENT_LOG_PREFIX,
129
- tool_name,
130
- ex,
131
- exc_info=True)
132
- raise ex
132
+ logger.error("%s Unable to find tool with the name %s\n%s", AGENT_LOG_PREFIX, tool_name, ex)
133
+ raise
133
134
 
134
135
  async def agent_node(self, state: ReActGraphState):
135
136
  try:
@@ -233,8 +234,8 @@ class ReActAgentGraph(DualNodeAgent):
233
234
  working_state.append(output_message)
234
235
  working_state.append(HumanMessage(content=str(ex.observation)))
235
236
  except Exception as ex:
236
- logger.exception("%s Failed to call agent_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
237
- raise ex
237
+ logger.error("%s Failed to call agent_node: %s", AGENT_LOG_PREFIX, ex)
238
+ raise
238
239
 
239
240
  async def conditional_edge(self, state: ReActGraphState):
240
241
  try:
@@ -252,7 +253,7 @@ class ReActAgentGraph(DualNodeAgent):
252
253
  agent_output.tool_input)
253
254
  return AgentDecision.TOOL
254
255
  except Exception as ex:
255
- logger.exception("Failed to determine whether agent is calling a tool: %s", ex, exc_info=True)
256
+ logger.exception("Failed to determine whether agent is calling a tool: %s", ex)
256
257
  logger.warning("%s Ending graph traversal", AGENT_LOG_PREFIX)
257
258
  return AgentDecision.END
258
259
 
@@ -329,8 +330,8 @@ class ReActAgentGraph(DualNodeAgent):
329
330
  logger.debug("%s ReAct Graph built and compiled successfully", AGENT_LOG_PREFIX)
330
331
  return self.graph
331
332
  except Exception as ex:
332
- logger.exception("%s Failed to build ReAct Graph: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
333
- raise ex
333
+ logger.error("%s Failed to build ReAct Graph: %s", AGENT_LOG_PREFIX, ex)
334
+ raise
334
335
 
335
336
  @staticmethod
336
337
  def validate_system_prompt(system_prompt: str) -> bool:
@@ -346,7 +347,7 @@ class ReActAgentGraph(DualNodeAgent):
346
347
  errors.append(error_message)
347
348
  if errors:
348
349
  error_text = "\n".join(errors)
349
- logger.exception("%s %s", AGENT_LOG_PREFIX, error_text)
350
+ logger.error("%s %s", AGENT_LOG_PREFIX, error_text)
350
351
  raise ValueError(error_text)
351
352
  return True
352
353
 
@@ -373,7 +374,7 @@ def create_react_agent_prompt(config: "ReActAgentWorkflowConfig") -> ChatPromptT
373
374
 
374
375
  valid_prompt = ReActAgentGraph.validate_system_prompt(prompt_str)
375
376
  if not valid_prompt:
376
- logger.exception("%s Invalid system_prompt", AGENT_LOG_PREFIX)
377
+ logger.error("%s Invalid system_prompt", AGENT_LOG_PREFIX)
377
378
  raise ValueError("Invalid system_prompt")
378
379
  prompt = ChatPromptTemplate([("system", prompt_str), ("user", USER_PROMPT),
379
380
  MessagesPlaceholder(variable_name='agent_scratchpad', optional=True)])
@@ -17,6 +17,7 @@ import logging
17
17
 
18
18
  from pydantic import AliasChoices
19
19
  from pydantic import Field
20
+ from pydantic import PositiveInt
20
21
 
21
22
  from nat.builder.builder import Builder
22
23
  from nat.builder.framework_enum import LLMFrameworkEnum
@@ -65,6 +66,8 @@ class ReActAgentWorkflowConfig(FunctionBaseConfig, name="react_agent"):
65
66
  default=None,
66
67
  description="Provides the SYSTEM_PROMPT to use with the agent") # defaults to SYSTEM_PROMPT in prompt.py
67
68
  max_history: int = Field(default=15, description="Maximum number of messages to keep in the conversation history.")
69
+ log_response_max_chars: PositiveInt = Field(
70
+ default=1000, description="Maximum number of characters to display in logs when logging tool responses.")
68
71
  use_openai_api: bool = Field(default=False,
69
72
  description=("Use OpenAI API for the input/output types to the function. "
70
73
  "If False, strings will be used."))
@@ -100,6 +103,7 @@ async def react_agent_workflow(config: ReActAgentWorkflowConfig, builder: Builde
100
103
  tools=tools,
101
104
  use_tool_schema=config.include_tool_input_schema_in_tool_description,
102
105
  detailed_logs=config.verbose,
106
+ log_response_max_chars=config.log_response_max_chars,
103
107
  retry_agent_response_parsing_errors=config.retry_agent_response_parsing_errors,
104
108
  parse_agent_response_max_retries=config.parse_agent_response_max_retries,
105
109
  tool_call_max_retries=config.tool_call_max_retries,
@@ -129,7 +133,7 @@ async def react_agent_workflow(config: ReActAgentWorkflowConfig, builder: Builde
129
133
  return ChatResponse.from_string(str(output_message.content))
130
134
 
131
135
  except Exception as ex:
132
- logger.exception("%s ReAct Agent failed with exception: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
136
+ logger.exception("%s ReAct Agent failed with exception: %s", AGENT_LOG_PREFIX, ex)
133
137
  # here, we can implement custom error messages
134
138
  if config.verbose:
135
139
  return ChatResponse.from_string(str(ex))
@@ -66,8 +66,13 @@ class ReWOOAgentGraph(BaseAgent):
66
66
  tools: list[BaseTool],
67
67
  use_tool_schema: bool = True,
68
68
  callbacks: list[AsyncCallbackHandler] | None = None,
69
- detailed_logs: bool = False):
70
- super().__init__(llm=llm, tools=tools, callbacks=callbacks, detailed_logs=detailed_logs)
69
+ detailed_logs: bool = False,
70
+ log_response_max_chars: int = 1000):
71
+ super().__init__(llm=llm,
72
+ tools=tools,
73
+ callbacks=callbacks,
74
+ detailed_logs=detailed_logs,
75
+ log_response_max_chars=log_response_max_chars)
71
76
 
72
77
  logger.debug(
73
78
  "%s Filling the prompt variables 'tools' and 'tool_names', using the tools provided in the config.",
@@ -95,12 +100,8 @@ class ReWOOAgentGraph(BaseAgent):
95
100
  try:
96
101
  return self.tools_dict.get(tool_name)
97
102
  except Exception as ex:
98
- logger.exception("%s Unable to find tool with the name %s\n%s",
99
- AGENT_LOG_PREFIX,
100
- tool_name,
101
- ex,
102
- exc_info=True)
103
- raise ex
103
+ logger.error("%s Unable to find tool with the name %s\n%s", AGENT_LOG_PREFIX, tool_name, ex)
104
+ raise
104
105
 
105
106
  @staticmethod
106
107
  def _get_current_step(state: ReWOOGraphState) -> int:
@@ -202,8 +203,8 @@ class ReWOOAgentGraph(BaseAgent):
202
203
  return {"plan": plan, "steps": steps}
203
204
 
204
205
  except Exception as ex:
205
- logger.exception("%s Failed to call planner_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
206
- raise ex
206
+ logger.error("%s Failed to call planner_node: %s", AGENT_LOG_PREFIX, ex)
207
+ raise
207
208
 
208
209
  async def executor_node(self, state: ReWOOGraphState):
209
210
  try:
@@ -269,22 +270,15 @@ class ReWOOAgentGraph(BaseAgent):
269
270
  RunnableConfig(callbacks=self.callbacks),
270
271
  max_retries=3)
271
272
 
272
- # ToolMessage only accepts str or list[str | dict] as content.
273
- # Convert into list if the response is a dict.
274
- if isinstance(tool_response, dict):
275
- tool_response = [tool_response]
276
-
277
- tool_response_message = ToolMessage(name=tool, tool_call_id=tool, content=tool_response)
278
-
279
273
  if self.detailed_logs:
280
274
  self._log_tool_response(requested_tool.name, tool_input_parsed, str(tool_response))
281
275
 
282
- intermediate_results[placeholder] = tool_response_message
276
+ intermediate_results[placeholder] = tool_response
283
277
  return {"intermediate_results": intermediate_results}
284
278
 
285
279
  except Exception as ex:
286
- logger.exception("%s Failed to call executor_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
287
- raise ex
280
+ logger.error("%s Failed to call executor_node: %s", AGENT_LOG_PREFIX, ex)
281
+ raise
288
282
 
289
283
  async def solver_node(self, state: ReWOOGraphState):
290
284
  try:
@@ -327,8 +321,8 @@ class ReWOOAgentGraph(BaseAgent):
327
321
  return {"result": output_message}
328
322
 
329
323
  except Exception as ex:
330
- logger.exception("%s Failed to call solver_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
331
- raise ex
324
+ logger.error("%s Failed to call solver_node: %s", AGENT_LOG_PREFIX, ex)
325
+ raise
332
326
 
333
327
  async def conditional_edge(self, state: ReWOOGraphState):
334
328
  try:
@@ -343,10 +337,7 @@ class ReWOOAgentGraph(BaseAgent):
343
337
  return AgentDecision.TOOL
344
338
 
345
339
  except Exception as ex:
346
- logger.exception("%s Failed to determine whether agent is calling a tool: %s",
347
- AGENT_LOG_PREFIX,
348
- ex,
349
- exc_info=True)
340
+ logger.exception("%s Failed to determine whether agent is calling a tool: %s", AGENT_LOG_PREFIX, ex)
350
341
  logger.warning("%s Ending graph traversal", AGENT_LOG_PREFIX)
351
342
  return AgentDecision.END
352
343
 
@@ -372,8 +363,8 @@ class ReWOOAgentGraph(BaseAgent):
372
363
  return self.graph
373
364
 
374
365
  except Exception as ex:
375
- logger.exception("%s Failed to build ReWOO Graph: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
376
- raise ex
366
+ logger.error("%s Failed to build ReWOO Graph: %s", AGENT_LOG_PREFIX, ex)
367
+ raise
377
368
 
378
369
  async def build_graph(self):
379
370
  try:
@@ -381,8 +372,8 @@ class ReWOOAgentGraph(BaseAgent):
381
372
  logger.debug("%s ReWOO Graph built and compiled successfully", AGENT_LOG_PREFIX)
382
373
  return self.graph
383
374
  except Exception as ex:
384
- logger.exception("%s Failed to build ReWOO Graph: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
385
- raise ex
375
+ logger.error("%s Failed to build ReWOO Graph: %s", AGENT_LOG_PREFIX, ex)
376
+ raise
386
377
 
387
378
  @staticmethod
388
379
  def validate_planner_prompt(planner_prompt: str) -> bool:
@@ -398,7 +389,7 @@ class ReWOOAgentGraph(BaseAgent):
398
389
  errors.append(error_message)
399
390
  if errors:
400
391
  error_text = "\n".join(errors)
401
- logger.exception("%s %s", AGENT_LOG_PREFIX, error_text)
392
+ logger.error("%s %s", AGENT_LOG_PREFIX, error_text)
402
393
  raise ValueError(error_text)
403
394
  return True
404
395
 
@@ -409,6 +400,6 @@ class ReWOOAgentGraph(BaseAgent):
409
400
  errors.append("The solver prompt cannot be empty.")
410
401
  if errors:
411
402
  error_text = "\n".join(errors)
412
- logger.exception("%s %s", AGENT_LOG_PREFIX, error_text)
403
+ logger.error("%s %s", AGENT_LOG_PREFIX, error_text)
413
404
  raise ValueError(error_text)
414
405
  return True
@@ -17,6 +17,7 @@ import logging
17
17
 
18
18
  from pydantic import AliasChoices
19
19
  from pydantic import Field
20
+ from pydantic import PositiveInt
20
21
 
21
22
  from nat.builder.builder import Builder
22
23
  from nat.builder.framework_enum import LLMFrameworkEnum
@@ -52,6 +53,8 @@ class ReWOOAgentWorkflowConfig(FunctionBaseConfig, name="rewoo_agent"):
52
53
  default=None,
53
54
  description="Provides the SOLVER_PROMPT to use with the agent") # defaults to SOLVER_PROMPT in prompt.py
54
55
  max_history: int = Field(default=15, description="Maximum number of messages to keep in the conversation history.")
56
+ log_response_max_chars: PositiveInt = Field(
57
+ default=1000, description="Maximum number of characters to display in logs when logging tool responses.")
55
58
  use_openai_api: bool = Field(default=False,
56
59
  description=("Use OpenAI API for the input/output types to the function. "
57
60
  "If False, strings will be used."))
@@ -86,7 +89,7 @@ async def rewoo_agent_workflow(config: ReWOOAgentWorkflowConfig, builder: Builde
86
89
  if config.additional_planner_instructions:
87
90
  planner_system_prompt += f"{config.additional_planner_instructions}"
88
91
  if not ReWOOAgentGraph.validate_planner_prompt(planner_system_prompt):
89
- logger.exception("Invalid planner prompt")
92
+ logger.error("Invalid planner prompt")
90
93
  raise ValueError("Invalid planner prompt")
91
94
  planner_prompt = ChatPromptTemplate([("system", planner_system_prompt), ("user", PLANNER_USER_PROMPT)])
92
95
 
@@ -94,7 +97,7 @@ async def rewoo_agent_workflow(config: ReWOOAgentWorkflowConfig, builder: Builde
94
97
  if config.additional_solver_instructions:
95
98
  solver_system_prompt += f"{config.additional_solver_instructions}"
96
99
  if not ReWOOAgentGraph.validate_solver_prompt(solver_system_prompt):
97
- logger.exception("Invalid solver prompt")
100
+ logger.error("Invalid solver prompt")
98
101
  raise ValueError("Invalid solver prompt")
99
102
  solver_prompt = ChatPromptTemplate([("system", solver_system_prompt), ("user", SOLVER_USER_PROMPT)])
100
103
 
@@ -113,7 +116,8 @@ async def rewoo_agent_workflow(config: ReWOOAgentWorkflowConfig, builder: Builde
113
116
  solver_prompt=solver_prompt,
114
117
  tools=tools,
115
118
  use_tool_schema=config.include_tool_input_schema_in_tool_description,
116
- detailed_logs=config.verbose).build_graph()
119
+ detailed_logs=config.verbose,
120
+ log_response_max_chars=config.log_response_max_chars).build_graph()
117
121
 
118
122
  async def _response_fn(input_message: ChatRequest) -> ChatResponse:
119
123
  try:
@@ -137,7 +141,7 @@ async def rewoo_agent_workflow(config: ReWOOAgentWorkflowConfig, builder: Builde
137
141
  return ChatResponse.from_string(output_message)
138
142
 
139
143
  except Exception as ex:
140
- logger.exception("ReWOO Agent failed with exception: %s", ex, exc_info=ex)
144
+ logger.exception("ReWOO Agent failed with exception: %s", ex)
141
145
  # here, we can implement custom error messages
142
146
  if config.verbose:
143
147
  return ChatResponse.from_string(str(ex))
@@ -55,17 +55,22 @@ class ToolCallAgentGraph(DualNodeAgent):
55
55
  prompt: str | None = None,
56
56
  callbacks: list[AsyncCallbackHandler] = None,
57
57
  detailed_logs: bool = False,
58
+ log_response_max_chars: int = 1000,
58
59
  handle_tool_errors: bool = True,
59
60
  ):
60
- super().__init__(llm=llm, tools=tools, callbacks=callbacks, detailed_logs=detailed_logs)
61
+ super().__init__(llm=llm,
62
+ tools=tools,
63
+ callbacks=callbacks,
64
+ detailed_logs=detailed_logs,
65
+ log_response_max_chars=log_response_max_chars)
61
66
  # some LLMs support tool calling
62
67
  # these models accept the tool's input schema and decide when to use a tool based on the input's relevance
63
68
  try:
64
69
  # in tool calling agents, we bind the tools to the LLM, to pass the tools' input schemas at runtime
65
70
  self.bound_llm = llm.bind_tools(tools)
66
71
  except NotImplementedError as ex:
67
- logger.error("%s Failed to bind tools: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
68
- raise ex
72
+ logger.error("%s Failed to bind tools: %s", AGENT_LOG_PREFIX, ex)
73
+ raise
69
74
 
70
75
  if prompt is not None:
71
76
  system_prompt = SystemMessage(content=prompt)
@@ -100,8 +105,8 @@ class ToolCallAgentGraph(DualNodeAgent):
100
105
  state.messages += [response]
101
106
  return state
102
107
  except Exception as ex:
103
- logger.exception("%s Failed to call agent_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
104
- raise ex
108
+ logger.error("%s Failed to call agent_node: %s", AGENT_LOG_PREFIX, ex)
109
+ raise
105
110
 
106
111
  async def conditional_edge(self, state: ToolCallAgentGraphState):
107
112
  try:
@@ -115,12 +120,7 @@ class ToolCallAgentGraph(DualNodeAgent):
115
120
  logger.debug("%s Final answer:\n%s", AGENT_LOG_PREFIX, state.messages[-1].content)
116
121
  return AgentDecision.END
117
122
  except Exception as ex:
118
- logger.exception(
119
- "%s Failed to determine whether agent is calling a tool: %s",
120
- AGENT_LOG_PREFIX,
121
- ex,
122
- exc_info=True,
123
- )
123
+ logger.exception("%s Failed to determine whether agent is calling a tool: %s", AGENT_LOG_PREFIX, ex)
124
124
  logger.warning("%s Ending graph traversal", AGENT_LOG_PREFIX)
125
125
  return AgentDecision.END
126
126
 
@@ -143,8 +143,8 @@ class ToolCallAgentGraph(DualNodeAgent):
143
143
 
144
144
  return state
145
145
  except Exception as ex:
146
- logger.exception("%s Failed to call tool_node: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
147
- raise ex
146
+ logger.error("%s Failed to call tool_node: %s", AGENT_LOG_PREFIX, ex)
147
+ raise
148
148
 
149
149
  async def build_graph(self):
150
150
  try:
@@ -155,13 +155,8 @@ class ToolCallAgentGraph(DualNodeAgent):
155
155
  )
156
156
  return self.graph
157
157
  except Exception as ex:
158
- logger.exception(
159
- "%s Failed to build Tool Calling Agent Graph: %s",
160
- AGENT_LOG_PREFIX,
161
- ex,
162
- exc_info=ex,
163
- )
164
- raise ex
158
+ logger.error("%s Failed to build Tool Calling Agent Graph: %s", AGENT_LOG_PREFIX, ex)
159
+ raise
165
160
 
166
161
 
167
162
  def create_tool_calling_agent_prompt(config: "ToolCallAgentWorkflowConfig") -> str | None:
@@ -16,6 +16,7 @@
16
16
  import logging
17
17
 
18
18
  from pydantic import Field
19
+ from pydantic import PositiveInt
19
20
 
20
21
  from nat.builder.builder import Builder
21
22
  from nat.builder.framework_enum import LLMFrameworkEnum
@@ -41,6 +42,8 @@ class ToolCallAgentWorkflowConfig(FunctionBaseConfig, name="tool_calling_agent")
41
42
  handle_tool_errors: bool = Field(default=True, description="Specify ability to handle tool calling errors.")
42
43
  description: str = Field(default="Tool Calling Agent Workflow", description="Description of this functions use.")
43
44
  max_iterations: int = Field(default=15, description="Number of tool calls before stoping the tool calling agent.")
45
+ log_response_max_chars: PositiveInt = Field(
46
+ default=1000, description="Maximum number of characters to display in logs when logging tool responses.")
44
47
  system_prompt: str | None = Field(default=None, description="Provides the system prompt to use with the agent.")
45
48
  additional_instructions: str | None = Field(default=None,
46
49
  description="Additional instructions appended to the system prompt.")
@@ -70,6 +73,7 @@ async def tool_calling_agent_workflow(config: ToolCallAgentWorkflowConfig, build
70
73
  tools=tools,
71
74
  prompt=prompt,
72
75
  detailed_logs=config.verbose,
76
+ log_response_max_chars=config.log_response_max_chars,
73
77
  handle_tool_errors=config.handle_tool_errors).build_graph()
74
78
 
75
79
  async def _response_fn(input_message: str) -> str:
@@ -89,7 +93,7 @@ async def tool_calling_agent_workflow(config: ToolCallAgentWorkflowConfig, build
89
93
  output_message = state.messages[-1]
90
94
  return output_message.content
91
95
  except Exception as ex:
92
- logger.exception("%s Tool Calling Agent failed with exception: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
96
+ logger.exception("%s Tool Calling Agent failed with exception: %s", AGENT_LOG_PREFIX, ex)
93
97
  if config.verbose:
94
98
  return str(ex)
95
99
  return "I seem to be having a problem."
@@ -97,6 +101,6 @@ async def tool_calling_agent_workflow(config: ToolCallAgentWorkflowConfig, build
97
101
  try:
98
102
  yield FunctionInfo.from_fn(_response_fn, description=config.description)
99
103
  except GeneratorExit:
100
- logger.exception("%s Workflow exited early!", AGENT_LOG_PREFIX, exc_info=True)
104
+ logger.exception("%s Workflow exited early!", AGENT_LOG_PREFIX)
101
105
  finally:
102
106
  logger.debug("%s Cleaning up react_agent workflow.", AGENT_LOG_PREFIX)
nat/builder/context.py CHANGED
@@ -31,6 +31,7 @@ from nat.data_models.intermediate_step import IntermediateStep
31
31
  from nat.data_models.intermediate_step import IntermediateStepPayload
32
32
  from nat.data_models.intermediate_step import IntermediateStepType
33
33
  from nat.data_models.intermediate_step import StreamEventData
34
+ from nat.data_models.intermediate_step import TraceMetadata
34
35
  from nat.data_models.invocation_node import InvocationNode
35
36
  from nat.runtime.user_metadata import RequestAttributes
36
37
  from nat.utils.reactive.subject import Subject
@@ -174,7 +175,10 @@ class Context:
174
175
  return self._context_state.user_message_id.get()
175
176
 
176
177
  @contextmanager
177
- def push_active_function(self, function_name: str, input_data: typing.Any | None):
178
+ def push_active_function(self,
179
+ function_name: str,
180
+ input_data: typing.Any | None,
181
+ metadata: dict[str, typing.Any] | TraceMetadata | None = None):
178
182
  """
179
183
  Set the 'active_function' in context, push an invocation node,
180
184
  AND create an OTel child span for that function call.
@@ -195,7 +199,8 @@ class Context:
195
199
  IntermediateStepPayload(UUID=current_function_id,
196
200
  event_type=IntermediateStepType.FUNCTION_START,
197
201
  name=function_name,
198
- data=StreamEventData(input=input_data)))
202
+ data=StreamEventData(input=input_data),
203
+ metadata=metadata))
199
204
 
200
205
  manager = ActiveFunctionContextManager()
201
206
 
@@ -61,7 +61,7 @@ class WorkflowEvalBuilder(WorkflowBuilder, EvalBuilder):
61
61
  # Store the evaluator
62
62
  self._evaluators[name] = ConfiguredEvaluator(config=config, instance=info_obj)
63
63
  except Exception as e:
64
- logger.error("Error %s adding evaluator `%s` with config `%s`", e, name, config, exc_info=True)
64
+ logger.error("Error %s adding evaluator `%s` with config `%s`", e, name, config)
65
65
  raise
66
66
 
67
67
  @override
@@ -98,7 +98,7 @@ class WorkflowEvalBuilder(WorkflowBuilder, EvalBuilder):
98
98
  try:
99
99
  tools.append(tool_wrapper_reg.build_fn(fn_name, fn, self))
100
100
  except Exception:
101
- logger.exception("Error fetching tool `%s`", fn_name, exc_info=True)
101
+ logger.exception("Error fetching tool `%s`", fn_name)
102
102
 
103
103
  return tools
104
104
 
nat/builder/function.py CHANGED
@@ -155,8 +155,8 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
155
155
 
156
156
  return result
157
157
  except Exception as e:
158
- logger.error("Error with ainvoke in function with input: %s.", value, exc_info=True)
159
- raise e
158
+ logger.error("Error with ainvoke in function with input: %s. Error: %s", value, e)
159
+ raise
160
160
 
161
161
  @typing.final
162
162
  async def acall_invoke(self, *args, **kwargs):
@@ -186,14 +186,14 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
186
186
  input_obj = self.input_schema(*args, **kwargs)
187
187
 
188
188
  return await self.ainvoke(value=input_obj)
189
- except Exception as e:
189
+ except Exception:
190
190
  logger.error(
191
191
  "Error in acall_invoke() converting input to function schema. Both args and kwargs were "
192
192
  "supplied which could not be converted to the input schema. args: %s\nkwargs: %s\nschema: %s",
193
193
  args,
194
194
  kwargs,
195
195
  self.input_schema)
196
- raise e
196
+ raise
197
197
 
198
198
  @abstractmethod
199
199
  async def _astream(self, value: InputT) -> AsyncGenerator[StreamingOutputT]:
@@ -252,8 +252,8 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
252
252
  manager.set_output(final_output)
253
253
 
254
254
  except Exception as e:
255
- logger.error("Error with astream in function with input: %s.", value, exc_info=True)
256
- raise e
255
+ logger.error("Error with astream in function with input: %s. Error: %s", value, e)
256
+ raise
257
257
 
258
258
  @typing.final
259
259
  async def acall_stream(self, *args, **kwargs):
@@ -287,14 +287,14 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
287
287
 
288
288
  async for x in self.astream(value=input_obj):
289
289
  yield x
290
- except Exception as e:
290
+ except Exception:
291
291
  logger.error(
292
292
  "Error in acall_stream() converting input to function schema. Both args and kwargs were "
293
293
  "supplied which could not be converted to the input schema. args: %s\nkwargs: %s\nschema: %s",
294
294
  args,
295
295
  kwargs,
296
296
  self.input_schema)
297
- raise e
297
+ raise
298
298
 
299
299
 
300
300
  class LambdaFunction(Function[InputT, StreamingOutputT, SingleOutputT]):