Skip to content

Conversation

vblagoje
Copy link
Member

DRAFT only - for feedback

@github-actions github-actions bot added topic:core type:documentation Improvements on the docs labels Sep 30, 2025
@vblagoje vblagoje added ignore-for-release-notes PRs with this flag won't be included in the release notes. and removed type:documentation Improvements on the docs labels Sep 30, 2025
@coveralls
Copy link
Collaborator

Pull Request Test Coverage Report for Build 18124993824

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 46 unchanged lines in 3 files lost coverage.
  • Overall coverage decreased (-0.07%) to 91.987%

Files with Coverage Reduction New Missed Lines %
core/pipeline/utils.py 2 97.8%
tools/toolset.py 18 78.72%
core/pipeline/base.py 26 87.0%
Totals Coverage Status
Change from base Build 18123485503: -0.07%
Covered Lines: 13237
Relevant Lines: 14390

💛 - Coveralls

Copy link
Member

@anakin87 anakin87 left a comment

Choose a reason for hiding this comment

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

I would like to see a test that fails with the Haystack code in main and passes with this PR. This would be helpful to effectively understand the problem we are trying to solve.

return self.tools[index]


class _ToolsetWrapper(Toolset):
Copy link
Member

Choose a reason for hiding this comment

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

could you explain why we need _ToolsetWrapper?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure we need it in this form (there should be something simpler), it was a hack to enable:

all_tools = perplexity_mcp + routing_mcp + some_other_tools

If they are all MCPToolset and not warmed up when addition above is executed (with current code) we'll just get garbage. We need to preserve their configs (URLs) and then after they've been warmed up they could be added (using our current code) - this way we effectively add them and preserve configs so that when we warm them up they all connect to the right servers.

if isinstance(value, (Tool, Toolset)):
return (value,)

if isinstance(value, Iterable) and not isinstance(value, (str, bytes, dict)):
Copy link
Member

Choose a reason for hiding this comment

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

Iterables are needed for Toolset? Could you please explain?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah that's just trick to search for Tools. Essentially if it's an iterable (like a list) BUT NOT a string, bytes, or dict then return it so we can iterate through it. See above how we search for (Tool, Toolset) without encoding specifically ChatGenerator ToolInvoker or any other component (Agent in pipeline has tools) also some others tomorrow perhaps. Rather than hardcoding this we look through fields of a component to find tools to warmup

Copy link
Member Author

Choose a reason for hiding this comment

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

Perhaps an overkill, can simplify as well.

@vblagoje
Copy link
Member Author

vblagoje commented Sep 30, 2025

The issue is the following (somewhat elaborated in #9807):

  1. Pipeline template contains MCPToolset with Secret token
  2. During deserialization, MCPToolset.init is called
  3. MCPToolset tries to connect immediately (eager connection)
  4. Secret resolution happens during client creation, but connection attempt happened already
  5. Connection fails with TimeoutError/MCPConnectionError because it didn't have resolved token.

The key is the following: Secret.resolve_value() happens when creating the client, but the actual connection attempt happens in init before the Secret is fully resolved in the deserialization context.

The solution proposed:

Have lazy init for some tools via warm_up. These are tools that need connection. Like MCP. All the Secret values get properly set in init but connection is made in Tool warm_up or first connect to server. First connect to server would work nicely if we didn't have two separate instances of MCPTool/MCPToolset in ChatGenerator and ToolInvoker (in pipeline scenario). Nobody calls "warm_up" on MCPTool/MCPToolset given to deserialized ChatGenerator and we end up with dysfunctional Pipeline. So we need "warm_up" on tools.

Truth to be told, there might be another solution that also might be possible but I'm not sure 100%. Revamp MCPToolset/MCPTool to pass Secret directly to its init and call resolve on it prior to connection init. But I'm not sure this is possible (in terms of getting correct values from Secret passed). Do you know? That's why I went with the warm_up solution because it seemed less kludgey to me.

I prepared the most basic test to demonstrate the above. LMK how we should proceed @anakin87 @sjrl

@anakin87
Copy link
Member

Is it possible to add a simple test to show what was failing before this PR? I think that this might help clarifying the issue and looking for the best solution.

@sjrl
Copy link
Contributor

sjrl commented Oct 1, 2025

Maybe as a general comment I would have thought we should add a warm_up function to MCPToolset and then update components like Agent, ChatGenerator and ToolInvoker to call warm_up on their tools in their own warm_up calls.

And I agree with @anakin87 seeing a test/example of showing the behavior being fixed would help a lot.

@vblagoje
Copy link
Member Author

vblagoje commented Oct 1, 2025

Maybe as a general comment I would have thought we should add a warm_up function to MCPToolset and then update components like Agent, ChatGenerator and ToolInvoker to call warm_up on their tools in their own warm_up calls.

And I agree with @anakin87 seeing a test/example of showing the behavior being fixed would help a lot.

That was my instinct as well, clean, make sense, has nice hierarchy of calls but I'm afraid users will forget and we'll get tons of confused devs. Plus we'll have to add warm_up to every single chat generator with the same code. We can warm_up tools regardless (for Pipelines and for Agents) and if they are idempotent - we are ok. wdyt?

@vblagoje
Copy link
Member Author

vblagoje commented Oct 8, 2025

Closing - superseded by #9856

@vblagoje vblagoje closed this Oct 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ignore-for-release-notes PRs with this flag won't be included in the release notes. topic:core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants