Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||
|
@sjrl I'd appreciate your feedback on this draft PR
WDYT? |
|
@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 WDYT? |
|
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? |
|
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... |
| "data": { | ||
| "name": "research", | ||
| "description": "Research a question.", | ||
| "parameters": {"type": "object", "properties": {"messages": MESSAGES_SCHEMA}, "required": ["messages"]}, |
There was a problem hiding this comment.
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
| "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"}, |
There was a problem hiding this comment.
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 I refactored this. I hope it's cleaner. |
sjrl
left a comment
There was a problem hiding this comment.
I like the refactor! Overwriting the component tools parameter schema method makes sense.
Related Issues
Proposed Changes:
AgentTool: a tool that wraps an Agent with sensible defaults, so it can be used out of the box in multi-agent systemsHow did you test it?
CI, new tests
Notes for the reviewer
I'd add docs in another PR.
Checklist
fix:,feat:,build:,chore:,ci:,docs:,style:,refactor:,perf:,test:and added!in case the PR includes breaking changes.