Custom tool on Github API call: when there is empty string in the optimal filed, it will lead API call failed. #5176

Closed
opened 2026-02-21 18:09:45 -05:00 by yindo · 4 comments
Owner

Originally created by @AmosZhu on GitHub (Aug 22, 2024).

Self Checks

  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report (我已阅读并同意 Language Policy).
  • [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:)
  • Please do not modify this template :) and fill in all the required fields.

Dify version

0.7.1

Cloud or Self Hosted

Self Hosted (Docker), Self Hosted (Source)

Steps to reproduce

  1. I am trying to deploy a Github API to list files of the given repository through custom tools.
  2. Below is my schema, and screen shot of the variables and errors.
  3. When I test with the variable in the screen shot, it leads API response error.
openapi: 3.1.0
info:
  title: GitHub Repository Connector API
  description: API for interacting with GitHub repositories, including listing repositories for organizations, users, and
    listing files in a repository.
  version: 1.0.0
servers:
  - url: https://api.github.com
    description: GitHub API Server
paths:
  /repos/{owner}/{repo}/contents/{path}:
    get:
      operationId: listRepoFiles
      summary: Recursively list all files in a repository
      description: >
        Returns the contents of a directory in a GitHub repository. This operation should be implemented recursively to
        traverse subdirectories and list all files in the repository.
      parameters:
        - name: owner
          in: path
          required: true
          description: The owner of the repository.
          schema:
            type: string
        - name: repo
          in: path
          required: true
          description: The name of the repository.
          schema:
            type: string
        - name: path
          in: path
          required: false
          description: The content path, such as a directory or file.
          schema:
            type: string
        - name: ref
          in: query
          required: false
          description: The name of the commit/branch/tag. Defaults to the repository’s default branch (usually `main`).
          schema:
            type: string
      responses:
        "200":
          description: A list of files in the specified directory.
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    name:
                      type: string
                      description: The name of the file or directory.
                    path:
                      type: string
                      description: The full path of the file or directory.
                    sha:
                      type: string
                      description: The SHA hash of the file or directory.
                    size:
                      type: integer
                      description: The size of the file in bytes.
                    type:
                      type: string
                      description: The type of content (file, dir).
                    download_url:
                      type: string
                      description: The URL to download the file.
        "401":
          description: Unauthorized. Invalid or missing authentication.
        "403":
          description: Forbidden due to lack of permissions.
        "404":
          description: The specified repository, directory, or file was not found.

1
2

✔️ Expected Behavior

I am using the exactly same schema for ChatGPT customisation which works no problem:
image

I am expecting the custom tool reply the data without error.

Actual Behavior

It returns as the screen shot shows:

image

Root cause:

As the figure shows, it sent the ref as a parameter to the API which leading the error, while the ChatGPT action send other parameters without problems.

Originally created by @AmosZhu on GitHub (Aug 22, 2024). ### Self Checks - [X] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general). - [X] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones. - [X] I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)). - [X] [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:) - [X] Please do not modify this template :) and fill in all the required fields. ### Dify version 0.7.1 ### Cloud or Self Hosted Self Hosted (Docker), Self Hosted (Source) ### Steps to reproduce 1. I am trying to deploy a Github API to list files of the given repository through custom tools. 2. Below is my schema, and screen shot of the variables and errors. 3. When I test with the variable in the screen shot, it leads API response error. ``` openapi: 3.1.0 info: title: GitHub Repository Connector API description: API for interacting with GitHub repositories, including listing repositories for organizations, users, and listing files in a repository. version: 1.0.0 servers: - url: https://api.github.com description: GitHub API Server paths: /repos/{owner}/{repo}/contents/{path}: get: operationId: listRepoFiles summary: Recursively list all files in a repository description: > Returns the contents of a directory in a GitHub repository. This operation should be implemented recursively to traverse subdirectories and list all files in the repository. parameters: - name: owner in: path required: true description: The owner of the repository. schema: type: string - name: repo in: path required: true description: The name of the repository. schema: type: string - name: path in: path required: false description: The content path, such as a directory or file. schema: type: string - name: ref in: query required: false description: The name of the commit/branch/tag. Defaults to the repository’s default branch (usually `main`). schema: type: string responses: "200": description: A list of files in the specified directory. content: application/json: schema: type: array items: type: object properties: name: type: string description: The name of the file or directory. path: type: string description: The full path of the file or directory. sha: type: string description: The SHA hash of the file or directory. size: type: integer description: The size of the file in bytes. type: type: string description: The type of content (file, dir). download_url: type: string description: The URL to download the file. "401": description: Unauthorized. Invalid or missing authentication. "403": description: Forbidden due to lack of permissions. "404": description: The specified repository, directory, or file was not found. ``` ![1](https://github.com/user-attachments/assets/822a9310-fb93-4547-8689-acb248429d52) ![2](https://github.com/user-attachments/assets/da193ab2-65a3-409a-aec3-6d729aea6ec2) ### ✔️ Expected Behavior I am using the exactly same schema for ChatGPT customisation which works no problem: ![image](https://github.com/user-attachments/assets/081836a1-b341-42c0-9ac7-ff05719f96ac) I am expecting the custom tool reply the data without error. ### ❌ Actual Behavior It returns as the screen shot shows: ![image](https://github.com/user-attachments/assets/eae1d08c-367b-4478-81c4-0cd9b49d33dc) # Root cause: As the figure shows, it sent the ref as a parameter to the API which leading the error, while the ChatGPT action send other parameters without problems.
yindo added the 🐞 bug label 2026-02-21 18:09:45 -05:00
yindo closed this issue 2026-02-21 18:09:45 -05:00
Author
Owner

@AmosZhu commented on GitHub (Aug 22, 2024):

Root cause:

As it indicates: it sending an empty string in parameter ref leading the error:
Which the file path is: api/core/tools/tool/api_tool.py
image

Solution:

Remove this parameter before sending:

This was the code:

image

After chaging

image

It return correct data:

image

Underlying impacts:

As most Github API calls not accept empty string in the query parameters, this could be fine. But are there any other APIs do require a place holder of the parameter even the value is null.

Comments:

As I just found this tool which is amazing, I am really appreciate all of you deliver this. I am keen to contribute

@AmosZhu commented on GitHub (Aug 22, 2024): ## Root cause: As it indicates: it sending an empty string in parameter ref leading the error: Which the file path is: `api/core/tools/tool/api_tool.py` ![image](https://github.com/user-attachments/assets/57528cf8-7576-4b1a-97cc-72f353c4c1ad) ## Solution: Remove this parameter before sending: #### This was the code: ![image](https://github.com/user-attachments/assets/176b4b88-b725-4f1f-8720-a1f5f7e89638) #### After chaging ![image](https://github.com/user-attachments/assets/97d82dec-ae6a-45d2-b1ce-5e00740caec9) #### It return correct data: ![image](https://github.com/user-attachments/assets/23b9f548-5889-4cb5-abcd-4577f3b989d5) ## Underlying impacts: As most Github API calls not accept empty string in the query parameters, this could be fine. But are there any other APIs do require a place holder of the parameter even the value is null. ## Comments: As I just found this tool which is amazing, I am really appreciate all of you deliver this. I am keen to contribute
Author
Owner

@dosubot[bot] commented on GitHub (Aug 22, 2024):

Hello @AmosZhu! I'm here to help you with any bugs, questions, or contributions you have in mind. Let's squash those bugs together! 🤖

The issue you're encountering is due to the GitHub API not accepting empty string values for certain parameters, which leads to a 404 error. The solution involves modifying the code to exclude parameters with empty string values before sending the API request.

Here's the relevant section of the api_tool.py file that processes and sends the API request:

def do_http_request(self, url: str, method: str, headers: dict[str, Any],
                    parameters: dict[str, Any]) -> httpx.Response:
    method = method.lower()

    params = {}
    path_params = {}
    body = {}
    cookies = {}

    # check parameters
    for parameter in self.api_bundle.openapi.get('parameters', []):
        value = self.get_parameter_value(parameter, parameters)
        if parameter['in'] == 'path':
            path_params[parameter['name']] = value
        elif parameter['in'] == 'query':
            if value:  # Only include non-empty values
                params[parameter['name']] = value
        elif parameter['in'] == 'cookie':
            cookies[parameter['name']] = value
        elif parameter['in'] == 'header':
            headers[parameter['name']] = value

    # check if there is a request body and handle it
    if 'requestBody' in self.api_bundle.openapi and self.api_bundle.openapi['requestBody'] is not None:
        # handle json request body
        if 'content' in self.api_bundle.openapi['requestBody']:
            for content_type in self.api_bundle.openapi['requestBody']['content']:
                headers['Content-Type'] = content_type
                body_schema = self.api_bundle.openapi['requestBody']['content'][content_type]['schema']
                required = body_schema.get('required', [])
                properties = body_schema.get('properties', {})
                for name, property in properties.items():
                    if name in parameters:
                        # convert type
                        body[name] = self._convert_body_property_type(property, parameters[name])
                    elif name in required:
                        raise ToolParameterValidationError(
                            f"Missing required parameter {name} in operation {self.api_bundle.operation_id}"
                        )
                    elif 'default' in property:
                        body[name] = property['default']
                    else:
                        body[name] = None
                break

    # replace path parameters
    for name, value in path_params.items():
        url = url.replace(f'{{{name}}}', f'{value}')

    # parse http body data if needed, for GET/HEAD/OPTIONS/TRACE, the body is ignored
    if 'Content-Type' in headers:
        if headers['Content-Type'] == 'application/json':
            body = json.dumps(body)
        elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
            body = urlencode(body)
        else:
            body = body

    if method in ('get', 'head', 'post', 'put', 'delete', 'patch'):
        response = getattr(ssrf_proxy, method)(url, params=params, headers=headers, cookies=cookies, data=body,
                                               timeout=API_TOOL_DEFAULT_TIMEOUT, follow_redirects=True)
        return response
    else:
        raise ValueError(f'Invalid http method {self.method}')

In the modified code, the elif parameter['in'] == 'query': block now includes a check to ensure that only non-empty values are added to the params dictionary. This prevents empty string values from being sent in the query parameters, which resolves the issue with the GitHub API.

This change should address the problem you're facing with empty string parameters causing API call failures [1].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Aug 22, 2024): <!-- Greeting --> Hello @AmosZhu! I'm here to help you with any bugs, questions, or contributions you have in mind. Let's squash those bugs together! 🤖 <!-- Answer --> The issue you're encountering is due to the GitHub API not accepting empty string values for certain parameters, which leads to a 404 error. The solution involves modifying the code to exclude parameters with empty string values before sending the API request. Here's the relevant section of the `api_tool.py` file that processes and sends the API request: ```python def do_http_request(self, url: str, method: str, headers: dict[str, Any], parameters: dict[str, Any]) -> httpx.Response: method = method.lower() params = {} path_params = {} body = {} cookies = {} # check parameters for parameter in self.api_bundle.openapi.get('parameters', []): value = self.get_parameter_value(parameter, parameters) if parameter['in'] == 'path': path_params[parameter['name']] = value elif parameter['in'] == 'query': if value: # Only include non-empty values params[parameter['name']] = value elif parameter['in'] == 'cookie': cookies[parameter['name']] = value elif parameter['in'] == 'header': headers[parameter['name']] = value # check if there is a request body and handle it if 'requestBody' in self.api_bundle.openapi and self.api_bundle.openapi['requestBody'] is not None: # handle json request body if 'content' in self.api_bundle.openapi['requestBody']: for content_type in self.api_bundle.openapi['requestBody']['content']: headers['Content-Type'] = content_type body_schema = self.api_bundle.openapi['requestBody']['content'][content_type]['schema'] required = body_schema.get('required', []) properties = body_schema.get('properties', {}) for name, property in properties.items(): if name in parameters: # convert type body[name] = self._convert_body_property_type(property, parameters[name]) elif name in required: raise ToolParameterValidationError( f"Missing required parameter {name} in operation {self.api_bundle.operation_id}" ) elif 'default' in property: body[name] = property['default'] else: body[name] = None break # replace path parameters for name, value in path_params.items(): url = url.replace(f'{{{name}}}', f'{value}') # parse http body data if needed, for GET/HEAD/OPTIONS/TRACE, the body is ignored if 'Content-Type' in headers: if headers['Content-Type'] == 'application/json': body = json.dumps(body) elif headers['Content-Type'] == 'application/x-www-form-urlencoded': body = urlencode(body) else: body = body if method in ('get', 'head', 'post', 'put', 'delete', 'patch'): response = getattr(ssrf_proxy, method)(url, params=params, headers=headers, cookies=cookies, data=body, timeout=API_TOOL_DEFAULT_TIMEOUT, follow_redirects=True) return response else: raise ValueError(f'Invalid http method {self.method}') ``` In the modified code, the `elif parameter['in'] == 'query':` block now includes a check to ensure that only non-empty values are added to the `params` dictionary. This prevents empty string values from being sent in the query parameters, which resolves the issue with the GitHub API. This change should address the problem you're facing with empty string parameters causing API call failures <sup>[[1]](https://github.com/langgenius/dify/blob/main/api/core/tools/tool/api_tool.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@AmosZhu commented on GitHub (Aug 23, 2024):

@dosu Amazing! Did you solve it by my first post only or all posts so far?

@AmosZhu commented on GitHub (Aug 23, 2024): @dosu Amazing! Did you solve it by my first post only or all posts so far?
Author
Owner

@dosubot[bot] commented on GitHub (Aug 23, 2024):

I reviewed all your posts to understand the issue thoroughly. Your detailed explanation and code snippets were very helpful in identifying and confirming the root cause and solution. If you have any more questions or need further assistance, feel free to ask!

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Aug 23, 2024): <!-- Answer --> I reviewed all your posts to understand the issue thoroughly. Your detailed explanation and code snippets were very helpful in identifying and confirming the root cause and solution. If you have any more questions or need further assistance, feel free to ask! <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#5176