Skip to content

feat: add AgentTool - #12186

Merged
anakin87 merged 12 commits into
mainfrom
subagents
Jul 30, 2026
Merged

feat: add AgentTool#12186
anakin87 merged 12 commits into
mainfrom
subagents

Conversation

@anakin87

@anakin87 anakin87 commented Jul 29, 2026

Copy link
Copy Markdown
Member

Related Issues

Proposed Changes:

  • add AgentTool: a tool that wraps an Agent with sensible defaults, so it can be used out of the box in multi-agent systems

How did you test it?

CI, new tests

Notes for the reviewer

I'd add docs in another PR.

Checklist

  • I have read the contributors guidelines and the code of conduct.
  • I have updated the related issue with new insights and changes.
  • I have added unit tests and updated the docstrings.
  • I've used one of the conventional commit types for my PR title: fix:, feat:, build:, chore:, ci:, docs:, style:, refactor:, perf:, test: and added ! in case the PR includes breaking changes.
  • I have documented my code.
  • I have added a release note file, following the contributors guidelines.
  • I have run pre-commit hooks and fixed any issue.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
haystack-docs Ignored Ignored Preview Jul 30, 2026 3:08pm

Request Review

@github-actions github-actions Bot added the type:documentation Improvements on the docs label Jul 29, 2026
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/core/pipeline
  pipeline.py
  haystack/tools
  __init__.py
  agent_tool.py
  component_tool.py
Project Total  

This report was generated by python-coverage-comment-action

@anakin87

Copy link
Copy Markdown
Member Author

@sjrl I'd appreciate your feedback on this draft PR

  • As you recommended, I created an AgentTool: name and description are required to make the multi-agent system work properly; Agent is not enough.
  • Currently, the AgentTool is only a ComponentTool with different (hopefully better) defaults for parameters and outputs_to_string.

WDYT?

@anakin87
anakin87 requested a review from sjrl July 29, 2026 10:35
Comment thread haystack/tools/agent_tool.py Outdated
@sjrl

sjrl commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@anakin87 overall looks good! I think the only point of additional feedback I would have is potentially changing the logic around the default parameters schema. I'm still thinking of the use case of a user configuring a generalist sub-Agent and then the coordinator Agent might want to specify both a system prompt and a user prompt and not just a user prompt.

Perhaps we could allow for both in the default parameters schema or perhaps inspect if the incoming agent provided to AgentTool doesn't have a system prompt set then we update the default parameters schema to allow for two input messages?

Similar idea in this direction, it could be cool to expose the Agent's tools param as part of the parameters schema as well. It could be restricted to type list[str] and we could list all possible values that are allowed based on the Agent's available tools.

WDYT?

Comment thread haystack/tools/agent_tool.py Outdated
@anakin87

anakin87 commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

Regarding token usage: in general, users might use different models for different Agents, so I'd not sum them by default.

At the moment, we can do something similar

from typing import Any

from haystack.components.agents import Agent
from haystack.components.agents.agent import _accumulate_usage
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.tools import AgentTool, ComponentTool
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch

web_search = ComponentTool(
    component=SerperDevWebSearch(top_k=3),
    name="web_search",
    description="Search the web for current information on any topic.",
)

researcher = Agent(
    chat_generator=OpenAIChatGenerator(model="gpt-5.4-mini"),
    tools=[web_search],
    system_prompt=(
        "You are a research specialist. Search the web, then answer the question concisely, in two or three "
        "sentences, citing the sources you used."
    ),
)

research = AgentTool(
    researcher,
    name="research",
    description="Delegate a single focused research question and get back a short answer.",
    outputs_to_state={"sub_usage": {"source": "token_usage", "handler": _accumulate_usage}},
)

coordinator = Agent(
    chat_generator=OpenAIChatGenerator(model="gpt-5.4"),
    tools=[research],
    system_prompt=(
        "You coordinate specialists. Break the user request into separate research questions and delegate each one "
        "with its own call to the research tool, then write the final answer yourself."
    ),
    state_schema={"sub_usage": {"type": dict[str, Any]}},
)

result = coordinator.run(
    [ChatMessage.from_user("What is Haystack 3.0 and how does its Agent differ from the one in Haystack 2.x?")]
)

own = result["token_usage"]
sub = result["sub_usage"]


def fmt(usage: dict[str, Any]) -> str:
    return f"{usage['prompt_tokens']:>6} prompt + {usage['completion_tokens']:>5} completion = {usage['total_tokens']:>6}"


print(result["last_message"].text)
print()
print("delegations:", result["tool_call_counts"]["research"])
print("coordinator:", fmt(own))
print("sub-agents: ", fmt(sub))

I see the con of this: manual, and we are also using a private function (we can make it public, though). WDYT?

@anakin87

anakin87 commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

About letting the orchestrator agent set the system prompt and select the tools of the Subagent: this seems to go in the direction of having more dynamic subagents.

If we want to achieve this goal (not in this PR), my impression is that code execution is required (LC).

Of course, a middle ground is always possible but I'm not sure if the additional complexity is worth it.

Happy to discuss and hear your opinion...

@anakin87
anakin87 marked this pull request as ready for review July 30, 2026 10:05
@anakin87
anakin87 requested a review from a team as a code owner July 30, 2026 10:05
@anakin87
anakin87 requested review from davidsbatista and sjrl and removed request for a team and davidsbatista July 30, 2026 10:05
Comment thread haystack/tools/agent_tool.py
Comment thread haystack/tools/agent_tool.py Outdated
Comment thread haystack/tools/agent_tool.py Outdated
Comment thread test/tools/test_agent_tool.py Outdated
"data": {
"name": "research",
"description": "Research a question.",
"parameters": {"type": "object", "properties": {"messages": MESSAGES_SCHEMA}, "required": ["messages"]},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

small nit: Maybe it would be better if parameters is kept as None if not passed in by the user instead of setting it by default. This way it is regenerated every time the Agent is loaded from_dict in case the users change some other values like inputs_from_state in the yaml and they forget to reset parameters to None

Comment thread test/tools/test_agent_tool.py Outdated
"parameters": {"type": "object", "properties": {"messages": MESSAGES_SCHEMA}, "required": ["messages"]},
"inputs_from_state": None,
"outputs_to_state": {"notes": {"source": "last_message"}},
"outputs_to_string": {"handler": "haystack.tools.agent_tool._agent_result_to_string"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

small nit: I think you mentioned this before, but I'd consider making this a public method since it will appear in a users yaml

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good!

@anakin87

Copy link
Copy Markdown
Member Author

@sjrl I refactored this. I hope it's cleaner.
I'd appreciate it if you could take a final look.

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the refactor! Overwriting the component tools parameter schema method makes sense.

@anakin87
anakin87 merged commit e3aa021 into main Jul 30, 2026
25 checks passed
@anakin87
anakin87 deleted the subagents branch July 30, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:tests type:documentation Improvements on the docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants