Skip to content

Conversation

@akshaylive
Copy link
Collaborator

@akshaylive akshaylive commented Jan 7, 2026

Scenario:

  1. LLM invokes multiple instances of the same tool, such as ["Web Search", "Web Search"]
  2. This line schedules multiple tool nodes to run in parallel.
  3. Each tool node picks up the first matching tool call from the state. This means that both of the web search tools will execute the first tool call.

Fix is to send the correct ToolCall details to the tool node. This also simplifies the tool node implementation.

Development Package

  • Add this package as a dependency in your pyproject.toml:
[project]
dependencies = [
  # Exact version:
  "uipath-langchain==0.3.3.dev1003891788",

  # Any version from PR
  "uipath-langchain>=0.3.3.dev1003890000,<0.3.3.dev1003900000"
]

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath-langchain = { index = "testpypi" }

Scenario:
1. LLM invokes multiple instances of the same tool, such as ["Web Search", "Web Search"]
1. This line schedules multiple tool nodes to run in parallel.
1. Each tool node picks up the first matching tool call from the state.
This means that both of the web search tools will execute the first tool call.
Copy link
Contributor

@andreitava-uip andreitava-uip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design is overall what I was going for as well, however this PR has 2 issues:

  1. We still need to pass the entire state to the node, even with the send API. The wrapper requires the state to be passed as many of them rely on it (static args wrapper, job attachments wrapper)
  2. This is more of a nit, but It would be nice for our ToolNode to support both direct ToolCalls and entire state passing. These changes as they are now force anybody wanting to use the node into the Send API.


async def _afunc(
self, state: Any, config: RunnableConfig | None = None
self, state: AgentGraphState, config: RunnableConfig | None = None
Copy link
Contributor

@andreitava-uip andreitava-uip Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the entire state here, including the input args, for which there is the runtime dynamically created type CompleteAgentGraphState. Unfortunately since it's created at runtime we cannot annotate with it.

Generally for nodes langgraph validates the state using the annotation info, that being said, now that I think about it more, I think here it's always passing the entire state no matter what.

We should also probably configure AgentGraphState to allow extra fields on model validation... hmm

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with Any. Both of them are fine because there's no type enforcement at runtime anyway.

Comment on lines +37 to +39
tool_call: Optional[ToolCall] = (
None # This field is used to pass tool inputs to tool nodes.
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. why is this added to the state?
  2. it should be added under internal_state

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to merge it back to the state, right?

@akshaylive
Copy link
Collaborator Author

The design is overall what I was going for as well, however this PR has 2 issues:

1. We still need to pass the entire state to the node, even with the send API. The wrapper requires the state to be passed as many of them rely on it (static args wrapper, job attachments wrapper)

2. This is more of a nit, but It would be nice for our ToolNode to support both direct ToolCalls and entire state passing. These changes as they are now force anybody wanting to use the node into the Send API.

Great, so the change to this PR is quite small, we can just pass in something along the lines of current_state | {"tool_call": ..}. It should address both these if I understand correctly.

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