Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@RuanHeleno
Copy link
Contributor

@RuanHeleno RuanHeleno commented Nov 13, 2025

User description

https://weni.atlassian.net/browse/NEXUS-4078


PR Type

Bug fix


Description

  • Remove duplicated tool_started call in on_tool_end methods

  • Enhance parameter extraction with fallback to tool_calls

  • Support multiple parameter formats (dict, JSON string)

  • Change default parameters from dict to list format


Diagram Walkthrough

flowchart LR
  A["on_tool_end hook"] -->|removed duplicate call| B["tool_started"]
  C["tool_started method"] -->|enhanced| D["Parameter extraction"]
  D -->|fallback to| E["tool_calls data"]
  E -->|convert| F["Standardized format"]
Loading

File Walkthrough

Relevant files
Bug fix
hooks.py
Fix duplicate tool logging and improve parameter extraction

inline_agents/backends/openai/hooks.py

  • Removed duplicate await self.tool_started() calls from two on_tool_end
    methods to prevent redundant logging
  • Enhanced tool_started method with fallback parameter extraction from
    tool_calls when tool_info parameters are empty
  • Added support for converting tool call arguments from dict and JSON
    string formats to standardized parameter list format
  • Changed default parameters type from empty dict {} to empty list []
    for consistency
  • Added error handling for JSON parsing of tool call arguments
+29/-5   

@qodo-merge-pro
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Audit context missing: New logging around tool execution lacks explicit user identifier and outcome fields in the
structured trace/event payload, which may limit auditability.

Referred Code
parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
if not parameters:
    tool_call_args = self.hooks_state.tool_calls.get(tool.name)
    if tool_call_args:
        # Convert tool call arguments to parameters format if needed
        if isinstance(tool_call_args, dict):
            parameters = [{"name": k, "value": v} for k, v in tool_call_args.items()]
        elif isinstance(tool_call_args, str):
            try:
                args_dict = json.loads(tool_call_args)
                parameters = [{"name": k, "value": v} for k, v in args_dict.items()]
            except Exception:
                parameters = []

print(f"\033[34m[HOOK] Executando ferramenta '{tool.name}'.\033[0m")
print(f"\033[33m[HOOK] Agente '{agent.name}' vai usar a ferramenta '{tool.name}'.\033[0m")
trace_data = {
    "collaboratorName": agent.name,
    "eventTime": pendulum.now().to_iso8601_string(),
    "sessionId": context_data.session.get_session_id(),
    "trace": {


 ... (clipped 35 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unhandled parsing: JSON parsing for tool parameters swallows all exceptions without logging or fallback,
potentially hiding errors and losing context.

Referred Code
try:
    args_dict = json.loads(tool_call_args)
    parameters = [{"name": k, "value": v} for k, v in args_dict.items()]
except Exception:
    parameters = []

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Verbose prints: Plain prints of tool results to stdout may expose internal details to end-users depending
on deployment, requiring verification of output routing.

Referred Code
print(f"\033[34m[HOOK] Resultado da ferramenta '{tool.name}' recebido {result}.\033[0m")

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Possible PII logs: Printing raw tool results and parameters may include sensitive data; verify that these
values are sanitized before logging.

Referred Code
print(f"\033[34m[HOOK] Resultado da ferramenta '{tool.name}' recebido {result}.\033[0m")

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input sanitization: Converted tool parameters derived from dynamic inputs are propagated without validation or
sanitization, which may introduce injection or data handling risks.

Referred Code
parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
if not parameters:
    tool_call_args = self.hooks_state.tool_calls.get(tool.name)
    if tool_call_args:
        # Convert tool call arguments to parameters format if needed
        if isinstance(tool_call_args, dict):
            parameters = [{"name": k, "value": v} for k, v in tool_call_args.items()]
        elif isinstance(tool_call_args, str):
            try:
                args_dict = json.loads(tool_call_args)
                parameters = [{"name": k, "value": v} for k, v in args_dict.items()]
            except Exception:

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-pro
Copy link

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: container-job

Failed stage: Run Django Unittests with Coverage [❌]

Failed test name: ""

Failure summary:

The action failed during Django settings initialization because a required environment variable was
missing:
- In nexus/settings.py line 599, INSTRUCTION_CLASSIFY_NAME =
env.str("INSTRUCTION_CLASSIFY_NAME") attempted to read the INSTRUCTION_CLASSIFY_NAME environment
variable.
- django-environ raised django.core.exceptions.ImproperlyConfigured: Set the
INSTRUCTION_CLASSIFY_NAME environment variable after a KeyError.
- As a result, coverage run
manage.py test --verbosity=2 --noinput exited before tests could start, causing the workflow to fail
with exit code 1.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

668:  Warning: The file chosen for install of langsmith 0.1.130 (langsmith-0.1.130-py3-none-any.whl) is yanked. Reason for being yanked: Identified some objects that don't serialize well in the optimized form
669:  Installing the current project: nexus (0.1.0)
670:  Detected GitHub Actions environment - using postgres/redis hostnames
671:  ##[group]Run coverage run manage.py test --verbosity=2 --noinput
672:  �[36;1mcoverage run manage.py test --verbosity=2 --noinput�[0m
673:  shell: sh -e {0}
674:  env:
675:  SECRET_KEY: SK
676:  ALLOWED_HOSTS: *,
677:  DJANGO_SETTINGS_MODULE: nexus.settings
678:  ##[endgroup]
679:  Traceback (most recent call last):
680:  File "/usr/local/lib/python3.10/site-packages/environ/environ.py", line 388, in get_value
681:  value = self.ENVIRON[var_name]
682:  File "/usr/local/lib/python3.10/os.py", line 680, in __getitem__
683:  raise KeyError(key) from None
684:  KeyError: 'INSTRUCTION_CLASSIFY_NAME'
685:  The above exception was the direct cause of the following exception:
...

709:  File "/usr/local/lib/python3.10/site-packages/django/conf/__init__.py", line 217, in __init__
710:  mod = importlib.import_module(self.SETTINGS_MODULE)
711:  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
712:  return _bootstrap._gcd_import(name[level:], package, level)
713:  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
714:  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
715:  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
716:  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
717:  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
718:  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
719:  File "/__w/nexus-ai/nexus-ai/nexus/settings.py", line 599, in <module>
720:  INSTRUCTION_CLASSIFY_NAME = env.str("INSTRUCTION_CLASSIFY_NAME")
721:  File "/usr/local/lib/python3.10/site-packages/environ/environ.py", line 213, in str
722:  value = self.get_value(var, cast=str, default=default)
723:  File "/usr/local/lib/python3.10/site-packages/environ/environ.py", line 392, in get_value
724:  raise ImproperlyConfigured(error_msg) from exc
725:  django.core.exceptions.ImproperlyConfigured: Set the INSTRUCTION_CLASSIFY_NAME environment variable
726:  ##[error]Process completed with exit code 1.
727:  Post job cleanup.
...

774:  waiting for server to shut down....2025-11-13 19:18:31.925 UTC [48] LOG:  aborting any active transactions
775:  2025-11-13 19:18:31.927 UTC [48] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
776:  2025-11-13 19:18:31.928 UTC [49] LOG:  shutting down
777:  2025-11-13 19:18:31.928 UTC [49] LOG:  checkpoint starting: shutdown immediate
778:  2025-11-13 19:18:31.949 UTC [49] LOG:  checkpoint complete: wrote 926 buffers (5.7%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.016 s, sync=0.004 s, total=0.021 s; sync files=301, longest=0.001 s, average=0.001 s; distance=4273 kB, estimate=4273 kB; lsn=0/191F0D0, redo lsn=0/191F0D0
779:  2025-11-13 19:18:31.956 UTC [48] LOG:  database system is shut down
780:  done
781:  server stopped
782:  PostgreSQL init process complete; ready for start up.
783:  Stop and remove container: e963511f82914160937e7b08193d5140_postgres16_2bd575
784:  ##[command]/usr/bin/docker rm --force 8e13b365173b7dd7f43904539ee357926b01fbc68066c15fe2695c0cc2d88a72
785:  8e13b365173b7dd7f43904539ee357926b01fbc68066c15fe2695c0cc2d88a72
786:  Print service container logs: 5735727def9c4ca096148947e62bd3f6_redislatest_e21c3f
787:  ##[command]/usr/bin/docker logs --details fd09a80f8d68c62e6a7c706010944560b22a8395db1c8c594cca78df31a2d8bc
788:  Starting Redis Server
789:  1:C 13 Nov 2025 19:18:34.213 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
790:  1:C 13 Nov 2025 19:18:34.214 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
791:  1:C 13 Nov 2025 19:18:34.214 * Redis version=8.2.3, bits=64, commit=00000000, modified=1, pid=1, just started
792:  1:C 13 Nov 2025 19:18:34.214 * Configuration loaded
793:  1:M 13 Nov 2025 19:18:34.214 * monotonic clock: POSIX clock_gettime
794:  1:M 13 Nov 2025 19:18:34.214 * Running mode=standalone, port=6379.
795:  1:M 13 Nov 2025 19:18:34.215 * <bf> RedisBloom version 8.2.8 (Git=unknown)
796:  1:M 13 Nov 2025 19:18:34.215 * <bf> Registering configuration options: [
797:  1:M 13 Nov 2025 19:18:34.215 * <bf> 	{ bf-error-rate       :      0.01 }
798:  1:M 13 Nov 2025 19:18:34.215 * <bf> 	{ bf-initial-size     :       100 }

@qodo-merge-pro
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Refactor duplicated logic into a base class

The logic for extracting tool parameters is duplicated in
CollaboratorHooks.tool_started and SupervisorHooks.tool_started. This should be
refactored into a shared method or base class to follow the DRY principle and
improve maintainability.

Examples:

inline_agents/backends/openai/hooks.py [238-250]
        parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
        if not parameters:
            tool_call_args = self.hooks_state.tool_calls.get(tool.name)
            if tool_call_args:
                # Convert tool call arguments to parameters format if needed
                if isinstance(tool_call_args, dict):
                    parameters = [{"name": k, "value": v} for k, v in tool_call_args.items()]
                elif isinstance(tool_call_args, str):
                    try:
                        args_dict = json.loads(tool_call_args)

 ... (clipped 3 lines)
inline_agents/backends/openai/hooks.py [415-427]
        parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
        if not parameters:
            tool_call_args = self.hooks_state.tool_calls.get(tool.name)
            if tool_call_args:
                # Convert tool call arguments to parameters format if needed
                if isinstance(tool_call_args, dict):
                    parameters = [{"name": k, "value": v} for k, v in tool_call_args.items()]
                elif isinstance(tool_call_args, str):
                    try:
                        args_dict = json.loads(tool_call_args)

 ... (clipped 3 lines)

Solution Walkthrough:

Before:

class CollaboratorHooks(AgentHooks):
    async def tool_started(self, context, agent, tool):
        parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
        if not parameters:
            tool_call_args = self.hooks_state.tool_calls.get(tool.name)
            if tool_call_args:
                # ... logic to convert tool_call_args to parameters
        # ...

class SupervisorHooks(AgentHooks):
    async def tool_started(self, context, agent, tool):
        parameters = self.hooks_state.tool_info.get(tool.name, {}).get("parameters", [])
        if not parameters:
            tool_call_args = self.hooks_state.tool_calls.get(tool.name)
            if tool_call_args:
                # ... logic to convert tool_call_args to parameters
        # ...

After:

class BaseOpenAIHooks(AgentHooks):
    def _get_tool_parameters(self, tool_name: str) -> list:
        parameters = self.hooks_state.tool_info.get(tool_name, {}).get("parameters", [])
        if not parameters:
            tool_call_args = self.hooks_state.tool_calls.get(tool_name)
            if tool_call_args:
                # ... logic to convert tool_call_args to parameters
        return parameters

class CollaboratorHooks(BaseOpenAIHooks):
    async def tool_started(self, context, agent, tool):
        parameters = self._get_tool_parameters(tool.name)
        # ...

class SupervisorHooks(BaseOpenAIHooks):
    async def tool_started(self, context, agent, tool):
        parameters = self._get_tool_parameters(tool.name)
        # ...
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies significant code duplication introduced by the PR in CollaboratorHooks.tool_started and SupervisorHooks.tool_started, and proposing a refactor is a valid way to improve code quality and maintainability.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants