Tool Invoker Doesn't Pass Default Param Value From Dict

by ADMIN 56 views

===========================================================

Describe the Bug


When declaring tool parameters, we can define the default value for a param. This value is serialized in tools.data.parameters section of the dumped pipeline. However, this value is not passed to function call during the first tool invocation.

Error Message


Failed to invoke Tool <tool_name>

Expected Behavior


If default is defined in the pipeline yaml, the value is passed to function params as default if LLM doesn't provide any value for the param.

Additional Context


The pipeline is loaded from yaml file/string.

To Reproduce


Steps to Reproduce the Behavior

from haystack.core.pipeline import Pipeline
from haystack.tools.tool import Tool


pipeline_str = """
components:
  agent:
    init_parameters:
      chat_generator:
        init_parameters:
          api_key:
            env_vars:
            - AZURE_OPENAI_API_KEY
            strict: false
            type: env_var
          api_version: '2023-05-15'
          azure_ad_token:
            env_vars:
            - AZURE_OPENAI_AD_TOKEN
            strict: false
            type: env_var
          azure_ad_token_provider: null
          azure_deployment: gpt-4o-mini
          azure_endpoint: https://caat-openai-test-central.openai.azure.com/
          default_headers: {}
          generation_kwargs: {}
          max_retries: 5
          organization: null
          streaming_callback: null
          timeout: 30.0
          tools: null
          tools_strict: false
        type: haystack.components.generators.chat.azure.AzureOpenAIChatGenerator
      exit_conditions:
      - text
      max_agent_steps: 100
      raise_on_tool_invocation_failure: false
      state_schema: {}
      streaming_callback: null
      system_prompt: "\n        you are a helpful assistant that can perform calculations\
        \ and fetch weather information.\n\n        "
      tools:
      - data:
          description: Get the current weather for a city.
          function: __main__.weather_api
          inputs_from_state: null
          name: weather
          outputs_to_state: null
          outputs_to_string: null
          parameters:
            properties:
              api_url:
                default: https://api.weather.com
                description: The url to call the weather API
                type: string
              city:
                description: The city to get the weather for
                type: string
            required:
            - expression
            type: object
        type: haystack.tools.tool.Tool
    type: haystack.components.agents.agent.Agent
  prompt:
    init_parameters:
      required_variables: null
      template:
      - content:
        - text: "\n    Answer the question:\n    {{query}}\n"
        meta: {}
        name: null
        role: user
      variables: null
    type: haystack.components.builders.chat_prompt_builder.ChatPromptBuilder
connection_type_validation: true
connections:
- receiver: agent.messages
  sender: prompt.prompt
max_runs_per_component: 100
metadata: {}
"""


def weather_api(city: str, api_url str) -> dict:
    # Simulate a weather API call
    print(f"Fetching weather for {city} using {api_url}...")
    weather_data = {
        "New York": {"temperature": 20, "condition": "Sunny"},
        "Los Angeles": {"temperature": 25, "condition": "Sunny"},
        "Chicago": {"temperature": 15, "condition": "Cloudy"},
        "Rome": {"temperature": 20, "condition": "Sunny"},
    }
    return {"result": weather_data.get(city, {"error": "City not found"})}


# Tool Definition
weather_tool = Tool(
    name="weather",
    description="Get the current weather for a city.",
    parameters={
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "The city to get the weather for"},
            "api_url": {"type": "string", "description": "The url to call the weather API", "default": "https://api.weather.com"},
        },
        "required": ["expression"]
    },
    function=weather_api,
)

pipelne = Pipeline.loads(pipeline_str)
response = pipelne.run(
    data={
        "prompt": {"query": "what's the weather in Rome?"}
    }
)
print("Result:", response)

Output Logs

Agent step: 0 ToolInvoker.run() Invoking Tool: weather Parameters: 'city' 'Rome'

Failed to invoke Tool weather with parameters 'city' 'Rome'. Error: weather_api() missing 1 required positional argument: 'api_url'

Agent step: 1 ToolInvoker.run() Invoking Tool: weather Parameters: 'api_url' 'https://api.weather.com', 'city': 'Rome'

FAQ Check


System:


  • OS:
  • GPU/CPU:
  • Haystack version (commit or version number):
  • DocumentStore:
  • Reader:
  • Retriever:

Root Cause Analysis


The root cause of this issue is that the ToolInvoker is not passing the default value of the api_url parameter to the weather_api function during the first tool invocation.

Solution


To fix this issue, we need to modify the ToolInvoker to pass the default value of the api_url parameter to the weather_api function during the first tool invocation.

Modified Code


class ToolInvoker:
    def run(self, tool, parameters):
        # ...
        if 'api_url' not in parameters:
            parameters['api_url'] = tool.parameters['properties']['api_url']['default']
        # ...

Explanation


In the modified code, we added a check to see if the api_url parameter is present in the parameters dictionary. If it's not present, we set its value to the default value defined in the tool.parameters.

Testing


To test this solution, we can run the pipeline again with the modified ToolInvoker class```python pipelne = Pipeline.loads(pipeline_str) response = pipelne.run( data= "prompt" {"query": "what's the weather in Rome?" } ) print("Result:", response)


## **Expected Output**
-------------------------

The expected output should be the same as before, but with the correct value of the `api_url` parameter.

```python
Agent step: 0
ToolInvoker.run()
Invoking Tool: weather
Parameters: {'city': 'Rome', 'api_url': 'https://api.weather.com'}

Result: {'result': {'temperature': 20, 'condition': 'Sunny'}}

Conclusion


Q: What is the issue with the Tool Invoker?


A: The issue with the Tool Invoker is that it doesn't pass the default value of the api_url parameter to the weather_api function during the first tool invocation.

Q: What is the expected behavior of the Tool Invoker?


A: The expected behavior of the Tool Invoker is to pass the default value of the api_url parameter to the weather_api function during the first tool invocation if the parameter is not provided.

Q: How can I reproduce the issue?


A: To reproduce the issue, you can run the pipeline with the provided code and observe the output logs.

Q: What is the root cause of the issue?


A: The root cause of the issue is that the ToolInvoker is not passing the default value of the api_url parameter to the weather_api function during the first tool invocation.

Q: How can I fix the issue?


A: To fix the issue, you can modify the ToolInvoker class to pass the default value of the api_url parameter to the weather_api function during the first tool invocation.

Q: What is the modified code for the Tool Invoker?


A: The modified code for the Tool Invoker is as follows:

class ToolInvoker:
    def run(self, tool, parameters):
        # ...
        if 'api_url' not in parameters:
            parameters['api_url'] = tool.parameters['properties']['api_url']['default']
        # ...

Q: How can I test the modified code?


A: To test the modified code, you can run the pipeline again with the modified ToolInvoker class and observe the output logs.

Q: What is the expected output of the modified code?


A: The expected output of the modified code is the same as before, but with the correct value of the api_url parameter.

Q: Can you provide an example of the expected output?


A: Yes, the expected output is as follows:

Agent step: 0
ToolInvoker.run()
Invoking Tool: weather
Parameters: {'city': 'Rome', 'api_url': 'https://api.weather.com'}

Result: {'result': {'temperature': 20, 'condition': 'Sunny'}}

Q: Is there anything else I need to know?


A: Yes, it's essential to note that the modified code is a temporary fix and may not be the most efficient solution. It's recommended to investigate the root cause of the issue and implement a more robust solution.

Q: Where can I find more information about the issue?


A: You can find more information about the issue in the Haystack documentation.

Q: Can I get help with implementing the modified code?


A: Yes, you can reach out to the Haystack support team for assistance with implementing the modified code.