When using the Function Calling, ToolCall.id will be randomly generated instead of the set value #5494

Closed
opened 2026-02-21 18:11:15 -05:00 by yindo · 13 comments
Owner

Originally created by @HeyDorvin on GitHub (Sep 9, 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.2

Cloud or Self Hosted

Self Hosted (Source)

Steps to reproduce

  • set model_providers config:
features:
  - agent-thought
  - tool-call
  - multi-tool-call
  - stream-tool-call
  1. first round
[{'role': 'user', 'content': 'What day is today'}]
  1. second round
[{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0'}]
  1. third round
[{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': '2235c7b1-1b77-42f1-a0d8-6b4be977be3d', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': '2235c7b1-1b77-42f1-a0d8-6b4be977be3d'}, {'role': 'assistant', 'content': 'According to the information in API-REPONSE, today is Friday.'}]

ToolCall.id changed during the third session.

✔️ Expected Behavior

Keep the tool_call_id in the third round consistent with that in the second round

[{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0'}, {'role': 'assistant', 'content': 'According to the information in API-REPONSE, today is Friday.'}]

I noticed what the document said "Tool ID, effective only in OpenAI tool calls. It's the unique ID for tool invocation and the same tool can be called multiple times." Does this mean it is only effective for model provider OpenAI? If yes, how to keep the tool_call_id ?

Actual Behavior

tool_call_id changed in the third round.

Originally created by @HeyDorvin on GitHub (Sep 9, 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.2 ### Cloud or Self Hosted Self Hosted (Source) ### Steps to reproduce - set model_providers config: ```yaml features: - agent-thought - tool-call - multi-tool-call - stream-tool-call ``` 1. first round ``` [{'role': 'user', 'content': 'What day is today'}] ``` 2. second round ``` [{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0'}] ``` 3. third round ``` [{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': '2235c7b1-1b77-42f1-a0d8-6b4be977be3d', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': '2235c7b1-1b77-42f1-a0d8-6b4be977be3d'}, {'role': 'assistant', 'content': 'According to the information in API-REPONSE, today is Friday.'}] ``` ToolCall.id changed during the third session. ### ✔️ Expected Behavior Keep the tool_call_id in the third round consistent with that in the second round ``` [{'role': 'user', 'content': 'What day is today'}, {'role': 'assistant', 'tool_calls': [{'id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0', 'type': 'function', 'function': {'name': 'weekday', 'arguments': '{"year": 2023, "month": 4, "day": 14}'}}]}, {'role': 'tool', 'content': 'April 14, 2023 is Friday.', 'name': 'weekday', 'tool_call_id': 'call_66aa5915-79bf-4853-a843-33bc60ead235_0'}, {'role': 'assistant', 'content': 'According to the information in API-REPONSE, today is Friday.'}] ``` I noticed what the document said "Tool ID, effective only in OpenAI tool calls. It's the unique ID for tool invocation and the same tool can be called multiple times." Does this mean it is only effective for model provider OpenAI? If yes, how to keep the tool_call_id ? ### ❌ Actual Behavior tool_call_id changed in the third round.
yindo added the 🔨 feat:tools label 2026-02-21 18:11:15 -05:00
yindo closed this issue 2026-02-21 18:11:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 9, 2024):

I found a similar unsolved discussion regarding the ToolCall.id being randomly generated instead of using the set value. You can check it out here [1].

To keep the tool_call_id consistent across multiple rounds when using Function Calling in Dify version 0.7.2 for a self-hosted setup, you need to ensure that the tool_call_id is properly managed and reused in subsequent calls. The provided code already includes mechanisms to handle and update tool_call_id. Specifically, the increase_tool_call function is designed to manage and update tool calls, including their IDs.

Here is the relevant part of the code that handles the tool calls and their IDs:

def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
    def get_tool_call(tool_name: str):
        if not tool_name:
            return tools_calls[-1]

        tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None)
        if tool_call is None:
            tool_call = AssistantPromptMessage.ToolCall(
                id='',
                type='',
                function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="")
            )
            tools_calls.append(tool_call)

        return tool_call

    for new_tool_call in new_tool_calls:
        # get tool call
        tool_call = get_tool_call(new_tool_call.function.name)
        # update tool call
        if new_tool_call.id:
            tool_call.id = new_tool_call.id
        if new_tool_call.type:
            tool_call.type = new_tool_call.type
        if new_tool_call.function.name:
            tool_call.function.name = new_tool_call.function.name
        if new_tool_call.function.arguments:
            tool_call.function.arguments += new_tool_call.function.arguments

To ensure consistency, you should:

  1. Initialize and Maintain tools_calls List: Ensure that the tools_calls list is maintained across multiple rounds of function calls. This list should be initialized once and updated as new tool calls are made.

  2. Reuse tool_call_id: When making subsequent function calls, reuse the tool_call_id from the previous calls. The get_tool_call function in the code helps in retrieving the existing tool call by its ID.

  3. Update Tool Call Information: The increase_tool_call function updates the tool call information, including the ID, type, function name, and arguments. Ensure that this function is called with the new tool calls to keep the information consistent.

Additionally, the BaseAgentRunner class in the api/core/agent/base_agent_runner.py file provides mechanisms to manage and organize agent history, which includes handling tool calls and their IDs. The organize_agent_history method ensures that tool calls are properly organized and their IDs are maintained:

def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[PromptMessage]:
    result = []
    # check if there is a system message in the beginning of the conversation
    for prompt_message in prompt_messages:
        if isinstance(prompt_message, SystemPromptMessage):
            result.append(prompt_message)

    messages: list[Message] = db.session.query(Message).filter(
        Message.conversation_id == self.message.conversation_id,
    ).order_by(Message.created_at.asc()).all()

    for message in messages:
        if message.id == self.message.id:
            continue
        result.append(self.organize_agent_user_prompt(message))
        agent_thoughts: list[MessageAgentThought] = message.agent_thoughts
        if agent_thoughts:
            for agent_thought in agent_thoughts:
                tools = agent_thought.tool
                if tools:
                    tools = tools.split(';')
                    tool_calls: list[AssistantPromptMessage.ToolCall] = []
                    tool_call_response: list[ToolPromptMessage] = []
                    try:
                        tool_inputs = json.loads(agent_thought.tool_input)
                    except Exception as e:
                        tool_inputs = { tool: {} for tool in tools }
                    try:
                        tool_responses = json.loads(agent_thought.observation)
                    except Exception as e:
                        tool_responses = dict.fromkeys(tools, agent_thought.observation)

                    for tool in tools:
                        # generate a uuid for tool call
                        tool_call_id = str(uuid.uuid4())
                        tool_calls.append(AssistantPromptMessage.ToolCall(
                            id=tool_call_id,
                            type='function',
                            function=AssistantPromptMessage.ToolCall.ToolCallFunction(
                                name=tool,
                                arguments=json.dumps(tool_inputs.get(tool, {})),
                            )
                        ))
                        tool_call_response.append(ToolPromptMessage(
                            content=tool_responses.get(tool, agent_thought.observation),
                            name=tool,
                            tool_call_id=tool_call_id,
                        ))
                    result.extend([
                        AssistantPromptMessage(
                            content=agent_thought.thought,
                            tool_calls=tool_calls,
                        ),
                        *tool_call_response
                    ])
                if not tools:
                    result.append(AssistantPromptMessage(content=agent_thought.thought))
        else:
            if message.answer:
                result.append(AssistantPromptMessage(content=message.answer))

    db.session.close()

    return result

By following these steps and utilizing the provided functions, you can keep the tool_call_id consistent across multiple rounds in your Dify setup [2][3][4][5].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 9, 2024): <!-- Answer --> I found a similar unsolved discussion regarding the `ToolCall.id` being randomly generated instead of using the set value. You can check it out [here](https://github.com/langgenius/dify/discussions/8127) <sup>[[1]](https://github.com/langgenius/dify/discussions/8127)</sup>. To keep the `tool_call_id` consistent across multiple rounds when using Function Calling in Dify version 0.7.2 for a self-hosted setup, you need to ensure that the `tool_call_id` is properly managed and reused in subsequent calls. The provided code already includes mechanisms to handle and update `tool_call_id`. Specifically, the `increase_tool_call` function is designed to manage and update tool calls, including their IDs. Here is the relevant part of the code that handles the tool calls and their IDs: ```python def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]): def get_tool_call(tool_name: str): if not tool_name: return tools_calls[-1] tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None) if tool_call is None: tool_call = AssistantPromptMessage.ToolCall( id='', type='', function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="") ) tools_calls.append(tool_call) return tool_call for new_tool_call in new_tool_calls: # get tool call tool_call = get_tool_call(new_tool_call.function.name) # update tool call if new_tool_call.id: tool_call.id = new_tool_call.id if new_tool_call.type: tool_call.type = new_tool_call.type if new_tool_call.function.name: tool_call.function.name = new_tool_call.function.name if new_tool_call.function.arguments: tool_call.function.arguments += new_tool_call.function.arguments ``` To ensure consistency, you should: 1. **Initialize and Maintain `tools_calls` List**: Ensure that the `tools_calls` list is maintained across multiple rounds of function calls. This list should be initialized once and updated as new tool calls are made. 2. **Reuse `tool_call_id`**: When making subsequent function calls, reuse the `tool_call_id` from the previous calls. The `get_tool_call` function in the code helps in retrieving the existing tool call by its ID. 3. **Update Tool Call Information**: The `increase_tool_call` function updates the tool call information, including the ID, type, function name, and arguments. Ensure that this function is called with the new tool calls to keep the information consistent. Additionally, the `BaseAgentRunner` class in the `api/core/agent/base_agent_runner.py` file provides mechanisms to manage and organize agent history, which includes handling tool calls and their IDs. The `organize_agent_history` method ensures that tool calls are properly organized and their IDs are maintained: ```python def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[PromptMessage]: result = [] # check if there is a system message in the beginning of the conversation for prompt_message in prompt_messages: if isinstance(prompt_message, SystemPromptMessage): result.append(prompt_message) messages: list[Message] = db.session.query(Message).filter( Message.conversation_id == self.message.conversation_id, ).order_by(Message.created_at.asc()).all() for message in messages: if message.id == self.message.id: continue result.append(self.organize_agent_user_prompt(message)) agent_thoughts: list[MessageAgentThought] = message.agent_thoughts if agent_thoughts: for agent_thought in agent_thoughts: tools = agent_thought.tool if tools: tools = tools.split(';') tool_calls: list[AssistantPromptMessage.ToolCall] = [] tool_call_response: list[ToolPromptMessage] = [] try: tool_inputs = json.loads(agent_thought.tool_input) except Exception as e: tool_inputs = { tool: {} for tool in tools } try: tool_responses = json.loads(agent_thought.observation) except Exception as e: tool_responses = dict.fromkeys(tools, agent_thought.observation) for tool in tools: # generate a uuid for tool call tool_call_id = str(uuid.uuid4()) tool_calls.append(AssistantPromptMessage.ToolCall( id=tool_call_id, type='function', function=AssistantPromptMessage.ToolCall.ToolCallFunction( name=tool, arguments=json.dumps(tool_inputs.get(tool, {})), ) )) tool_call_response.append(ToolPromptMessage( content=tool_responses.get(tool, agent_thought.observation), name=tool, tool_call_id=tool_call_id, )) result.extend([ AssistantPromptMessage( content=agent_thought.thought, tool_calls=tool_calls, ), *tool_call_response ]) if not tools: result.append(AssistantPromptMessage(content=agent_thought.thought)) else: if message.answer: result.append(AssistantPromptMessage(content=message.answer)) db.session.close() return result ``` By following these steps and utilizing the provided functions, you can keep the `tool_call_id` consistent across multiple rounds in your Dify setup <sup>[[2]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py)</sup><sup>[[3]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/stepfun/llm/llm.py)</sup><sup>[[4]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/moonshot/llm/llm.py)</sup><sup>[[5]](https://github.com/langgenius/dify/blob/main/api/core/agent/base_agent_runner.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

organize_agent_history function generate a uuid for tool call, instead of keeping the tool call id. @dosu

@HeyDorvin commented on GitHub (Sep 9, 2024): `organize_agent_history` function generate a uuid for tool call, instead of keeping the tool call id. @dosu
Author
Owner

@dosubot[bot] commented on GitHub (Sep 9, 2024):

The organize_agent_history function generates a UUID for each tool call to ensure that every tool call has a unique identifier. This is crucial for tracking and managing tool calls effectively. Here is the relevant code snippet:

for tool in tools:
    # generate a uuid for tool call
    tool_call_id = str(uuid.uuid4())
    tool_calls.append(AssistantPromptMessage.ToolCall(
        id=tool_call_id,
        type='function',
        function=AssistantPromptMessage.ToolCall.ToolCallFunction(
            name=tool,
            arguments=json.dumps(tool_inputs.get(tool, {})),
        )
    ))
    tool_call_response.append(ToolPromptMessage(
        content=tool_responses.get(tool, agent_thought.observation),
        name=tool,
        tool_call_id=tool_call_id,
    ))

This approach ensures that each tool call within the agent's history is uniquely identifiable [1].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 9, 2024): <!-- Answer --> The `organize_agent_history` function generates a UUID for each tool call to ensure that every tool call has a unique identifier. This is crucial for tracking and managing tool calls effectively. Here is the relevant code snippet: ```python for tool in tools: # generate a uuid for tool call tool_call_id = str(uuid.uuid4()) tool_calls.append(AssistantPromptMessage.ToolCall( id=tool_call_id, type='function', function=AssistantPromptMessage.ToolCall.ToolCallFunction( name=tool, arguments=json.dumps(tool_inputs.get(tool, {})), ) )) tool_call_response.append(ToolPromptMessage( content=tool_responses.get(tool, agent_thought.observation), name=tool, tool_call_id=tool_call_id, )) ``` This approach ensures that each tool call within the agent's history is uniquely identifiable <sup>[[1]](https://github.com/langgenius/dify/blob/main/api/core/agent/base_agent_runner.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

This is why ToolCall.id changed. How to keep the old ToolCall.id, instead using uuid. @dosu

@HeyDorvin commented on GitHub (Sep 9, 2024): This is why ToolCall.id changed. How to keep the old ToolCall.id, instead using uuid. @dosu
Author
Owner

@dosubot[bot] commented on GitHub (Sep 9, 2024):

To keep the old ToolCall.id instead of generating a new UUID for each tool call, you can modify the organize_agent_history function to avoid overwriting the existing id if it already exists. Here is the updated function:

def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[PromptMessage]:
    result = []
    for prompt_message in prompt_messages:
        if isinstance(prompt_message, SystemPromptMessage):
            result.append(prompt_message)

    messages: list[Message] = db.session.query(Message).filter(
        Message.conversation_id == self.message.conversation_id,
    ).order_by(Message.created_at.asc()).all()

    for message in messages:
        if message.id == self.message.id:
            continue
        result.append(self.organize_agent_user_prompt(message))
        agent_thoughts: list[MessageAgentThought] = message.agent_thoughts
        if agent_thoughts:
            for agent_thought in agent_thoughts:
                tools = agent_thought.tool
                if tools:
                    tools = tools.split(';')
                    tool_calls: list[AssistantPromptMessage.ToolCall] = []
                    tool_call_response: list[ToolPromptMessage] = []
                    try:
                        tool_inputs = json.loads(agent_thought.tool_input)
                    except Exception as e:
                        tool_inputs = { tool: {} for tool in tools }
                    try:
                        tool_responses = json.loads(agent_thought.observation)
                    except Exception as e:
                        tool_responses = dict.fromkeys(tools, agent_thought.observation)

                    for tool in tools:
                        # Check if tool_call_id already exists
                        existing_tool_call = next((tc for tc in tool_calls if tc.function.name == tool), None)
                        if existing_tool_call:
                            tool_call_id = existing_tool_call.id
                        else:
                            tool_call_id = str(uuid.uuid4())

                        tool_calls.append(AssistantPromptMessage.ToolCall(
                            id=tool_call_id,
                            type='function',
                            function=AssistantPromptMessage.ToolCall.ToolCallFunction(
                                name=tool,
                                arguments=json.dumps(tool_inputs.get(tool, {})),
                            )
                        ))
                        tool_call_response.append(ToolPromptMessage(
                            content=tool_responses.get(tool, agent_thought.observation),
                            name=tool,
                            tool_call_id=tool_call_id,
                        ))
                    result.extend([
                        AssistantPromptMessage(
                            content=agent_thought.thought,
                            tool_calls=tool_calls,
                        ),
                        *tool_call_response
                    ])
                if not tools:
                    result.append(AssistantPromptMessage(content=agent_thought.thought))
        else:
            if message.answer:
                result.append(AssistantPromptMessage(content=message.answer))

    db.session.close()

    return result

This modification ensures that the id is reused if it already exists, thereby preserving the old ToolCall.id [1].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 9, 2024): <!-- Answer --> To keep the old `ToolCall.id` instead of generating a new UUID for each tool call, you can modify the `organize_agent_history` function to avoid overwriting the existing `id` if it already exists. Here is the updated function: ```python def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[PromptMessage]: result = [] for prompt_message in prompt_messages: if isinstance(prompt_message, SystemPromptMessage): result.append(prompt_message) messages: list[Message] = db.session.query(Message).filter( Message.conversation_id == self.message.conversation_id, ).order_by(Message.created_at.asc()).all() for message in messages: if message.id == self.message.id: continue result.append(self.organize_agent_user_prompt(message)) agent_thoughts: list[MessageAgentThought] = message.agent_thoughts if agent_thoughts: for agent_thought in agent_thoughts: tools = agent_thought.tool if tools: tools = tools.split(';') tool_calls: list[AssistantPromptMessage.ToolCall] = [] tool_call_response: list[ToolPromptMessage] = [] try: tool_inputs = json.loads(agent_thought.tool_input) except Exception as e: tool_inputs = { tool: {} for tool in tools } try: tool_responses = json.loads(agent_thought.observation) except Exception as e: tool_responses = dict.fromkeys(tools, agent_thought.observation) for tool in tools: # Check if tool_call_id already exists existing_tool_call = next((tc for tc in tool_calls if tc.function.name == tool), None) if existing_tool_call: tool_call_id = existing_tool_call.id else: tool_call_id = str(uuid.uuid4()) tool_calls.append(AssistantPromptMessage.ToolCall( id=tool_call_id, type='function', function=AssistantPromptMessage.ToolCall.ToolCallFunction( name=tool, arguments=json.dumps(tool_inputs.get(tool, {})), ) )) tool_call_response.append(ToolPromptMessage( content=tool_responses.get(tool, agent_thought.observation), name=tool, tool_call_id=tool_call_id, )) result.extend([ AssistantPromptMessage( content=agent_thought.thought, tool_calls=tool_calls, ), *tool_call_response ]) if not tools: result.append(AssistantPromptMessage(content=agent_thought.thought)) else: if message.answer: result.append(AssistantPromptMessage(content=message.answer)) db.session.close() return result ``` This modification ensures that the `id` is reused if it already exists, thereby preserving the old `ToolCall.id` <sup>[[1]](https://github.com/langgenius/dify/blob/main/api/core/agent/base_agent_runner.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

This doesn't work. The doc saied "Tool ID, effective only in OpenAI tool calls. It's the unique ID for tool invocation and the same tool can be called multiple times.", why my ToolCall.id changed? @dosu

@HeyDorvin commented on GitHub (Sep 9, 2024): This doesn't work. The doc saied "Tool ID, effective only in OpenAI tool calls. It's the unique ID for tool invocation and the same tool can be called multiple times.", why my ToolCall.id changed? @dosu
Author
Owner

@dosubot[bot] commented on GitHub (Sep 9, 2024):

The ToolCall.id might change due to the logic in the _handle_generate_stream_response method. Specifically, the increase_tool_call function updates the ToolCall.id if a new tool call has an id:

def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]):
    def get_tool_call(tool_name: str):
        if not tool_name:
            return tools_calls[-1]

        tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None)
        if tool_call is None:
            tool_call = AssistantPromptMessage.ToolCall(
                id='',
                type='',
                function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="")
            )
            tools_calls.append(tool_call)

        return tool_call

    for new_tool_call in new_tool_calls:
        # get tool call
        tool_call = get_tool_call(new_tool_call.function.name)
        # update tool call
        if new_tool_call.id:
            tool_call.id = new_tool_call.id
        if new_tool_call.type:
            tool_call.type = new_tool_call.type
        if new_tool_call.function.name:
            tool_call.function.name = new_tool_call.function.name
        if new_tool_call.function.arguments:
            tool_call.function.arguments += new_tool_call.function.arguments

This function updates the ToolCall.id if the new tool call has an id, which could lead to the ToolCall.id changing across multiple invocations. Additionally, the _handle_generate_stream_response method processes streamed responses and updates tool calls dynamically, which might also contribute to the ToolCall.id changing [1][2].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Sep 9, 2024): <!-- Answer --> The `ToolCall.id` might change due to the logic in the `_handle_generate_stream_response` method. Specifically, the `increase_tool_call` function updates the `ToolCall.id` if a new tool call has an `id`: ```python def increase_tool_call(new_tool_calls: list[AssistantPromptMessage.ToolCall]): def get_tool_call(tool_name: str): if not tool_name: return tools_calls[-1] tool_call = next((tool_call for tool_call in tools_calls if tool_call.function.name == tool_name), None) if tool_call is None: tool_call = AssistantPromptMessage.ToolCall( id='', type='', function=AssistantPromptMessage.ToolCall.ToolCallFunction(name=tool_name, arguments="") ) tools_calls.append(tool_call) return tool_call for new_tool_call in new_tool_calls: # get tool call tool_call = get_tool_call(new_tool_call.function.name) # update tool call if new_tool_call.id: tool_call.id = new_tool_call.id if new_tool_call.type: tool_call.type = new_tool_call.type if new_tool_call.function.name: tool_call.function.name = new_tool_call.function.name if new_tool_call.function.arguments: tool_call.function.arguments += new_tool_call.function.arguments ``` This function updates the `ToolCall.id` if the new tool call has an `id`, which could lead to the `ToolCall.id` changing across multiple invocations. Additionally, the `_handle_generate_stream_response` method processes streamed responses and updates tool calls dynamically, which might also contribute to the `ToolCall.id` changing <sup>[[1]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py)</sup><sup>[[2]](https://github.com/langgenius/dify/blob/main/api/core/model_runtime/model_providers/stepfun/llm/llm.py)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

I have set the TooCall.id, but it changed. @dosu

function = AssistantPromptMessage.ToolCall.ToolCallFunction(
       name=response_function_call["name"],
       arguments=response_function_call["arguments"],
)

tool_call = AssistantPromptMessage.ToolCall(
       id=response_function_call["id"], type="function", function=function
)
@HeyDorvin commented on GitHub (Sep 9, 2024): I have set the TooCall.id, but it changed. @dosu ```python function = AssistantPromptMessage.ToolCall.ToolCallFunction( name=response_function_call["name"], arguments=response_function_call["arguments"], ) tool_call = AssistantPromptMessage.ToolCall( id=response_function_call["id"], type="function", function=function ) ```
Author
Owner

@crazywoola commented on GitHub (Sep 9, 2024):

I think it should be changed by design.

@crazywoola commented on GitHub (Sep 9, 2024): I think it should be changed by design.
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

I think it should be changed by design.

But the API uses this search parameter. If changed, it can't work.

@HeyDorvin commented on GitHub (Sep 9, 2024): > I think it should be changed by design. But the API uses this search parameter. If changed, it can't work.
Author
Owner

@crazywoola commented on GitHub (Sep 9, 2024):

According to our dev:

It should be the same toolcall.id if this is completion (one round)
It will have different if this is a multi-round conversation

I think the api will use the content but id to make the call.

Different providers does not have this id field. I guess it's not that crucial.

@crazywoola commented on GitHub (Sep 9, 2024): According to our dev: > It should be the same toolcall.id if this is completion (one round) > It will have different if this is a multi-round conversation I think the api will use the content but id to make the call. Different providers does not have this id field. I guess it's not that crucial.
Author
Owner

@HeyDorvin commented on GitHub (Sep 9, 2024):

According to our dev:

It should be the same toolcall.id if this is completion (one round)
It will have different if this is a multi-round conversation

I think the api will use the content but id to make the call.

Different providers does not have this id field. I guess it's not that crucial.

Thank you for your reply.

@HeyDorvin commented on GitHub (Sep 9, 2024): > According to our dev: > > > It should be the same toolcall.id if this is completion (one round) > > It will have different if this is a multi-round conversation > > I think the api will use the content but id to make the call. > > Different providers does not have this id field. I guess it's not that crucial. Thank you for your reply.
Author
Owner

@k1eNdn commented on GitHub (Feb 4, 2025):

According to our dev:

It should be the same toolcall.id if this is completion (one round)
It will have different if this is a multi-round conversation

I think the api will use the content but id to make the call.
Different providers does not have this id field. I guess it's not that crucial.

Thank you for your reply.

Dify currently uses function calling instead of tool calling. The key difference between functions and tools is that tools support parallel function calling, which allows the model to output multiple tool calls in a list, enabling the user to execute them in parallel. On the other hand, Dify executes only one function at a time. Therefore, tool call IDs are not necessary in Dify, as they are primarily useful for distinguishing outputs from multiple tool calls

Image

@k1eNdn commented on GitHub (Feb 4, 2025): > > According to our dev: > > > It should be the same toolcall.id if this is completion (one round) > > > It will have different if this is a multi-round conversation > > > > > > I think the api will use the content but id to make the call. > > Different providers does not have this id field. I guess it's not that crucial. > > Thank you for your reply. Dify currently uses function calling instead of tool calling. The key difference between functions and tools is that tools support parallel function calling, which allows the model to output multiple tool calls in a list, enabling the user to execute them in parallel. On the other hand, Dify executes only one function at a time. Therefore, tool call IDs are not necessary in Dify, as they are primarily useful for distinguishing outputs from multiple tool calls ![Image](https://github.com/user-attachments/assets/1eaf37e8-1dd0-4548-852d-1d6a0cf0ff0f)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#5494