LionAG2: Recursive Exploratory Research with AG2 beta — Typed multi-agent handoff
In the day 2 tutorial, we explored how to use ag2 to create structured output. To recap, the agent was granted the exa search tool and was…
LionAG2: Recursive Exploratory Research with AG2 beta — Typed multi-agent handoff
In the day 2 tutorial, we explored how to use ag2 to create structured output. To recap, the agent was granted the exa search tool and was asked to create a structured output as a Finding with citations.
from pydantic import BaseModel
class Citation(BaseModel):
title: str = Field(description="The cited work or source.")
relevance: str = Field(description="One sentence on why this source supports the finding.")
class Finding(BaseModel):
"""One self-contained research result."""
topic: str = Field(description="The specific aspect of the question this finding addresses.")
summary: str = Field(description="2-3 sentences capturing the core mechanism or claim.")
citations: list[Citation] = Field(description="Sources behind the summary; emit [] if none.")
novelty: float = Field(ge=0.0, le=1.0, description="0 = textbook, 1 = cutting edge.")
The structured output is handy for downstream data manipulation and adding conditions to the agentic workflows. In today’s tutorial, we will look into how to use structured output to create a multi-agent handoff workflow.
Setup
import os
from dotenv import load_dotenv
from IPython.display import Markdown, display
from pydantic import BaseModel, Field
from autogen.beta import Agent
from autogen.beta.config import OpenAIConfig
from autogen.beta.tools import ExaToolkit
load_dotenv()
config = OpenAIConfig(
model="gpt-5.4-mini",
api_key=os.getenv("OPENAI_API_KEY"),
base_url="https://api.openai.com/v1",
)
exa_tool = ExaToolkit(api_key=os.getenv("EXA_API_KEY"))
The schema from before was quite straightforward — produce a finding with citations from search results. Let’s add some richer schemas:
class OpenQuestion(BaseModel):
question: str = Field(description="A specific unresolved question surfaced by the survey.")
novelty: float = Field(
ge=0.0, le=1.0,
description="0 = well-studied, 1 = barely explored.",
)
class Survey(BaseModel):
"""Surveyor output: landscape of a topic plus open frontiers."""
topic: str
overview: str = Field(description="2-3 sentence summary of the current state of knowledge.")
key_findings: list[str] = Field(description="Major established results (3-5 bullets).")
open_questions: list[OpenQuestion] = Field(
description="Unresolved questions ranked by novelty. At least 2.",
)
class Hypothesis(BaseModel):
"""Theorist output: one falsifiable claim with a test."""
claim: str = Field(description="One-sentence falsifiable claim.")
mechanism: str = Field(description="Why this claim might hold — the causal story.")
testable_prediction: str = Field(
description="An observable that would confirm or refute the claim."
)
confidence: float = Field(ge=0.0, le=1.0, description="Subjective confidence in the claim.")
source_question: str = Field(description="The open question this hypothesis addresses.")
OpenQuestion carries a novelty score, this will become the key signal for deciding which questions are worth drilling deeper into.
Pipeline
The pipeline today will go as follows:
- a
surveyoragent will conduct survey via search tools over literature and produce a survey object with open questions - for each open question, we will launch one
theoristagent will then take to research further and create hypothesis
topic = "high-Tc superconductivity"
surveyor = Agent(
name="surveyor",
prompt=(
f"You are a meticulous research surveyor. Search broadly on {topic}"
", then summarize what is known and what remains open. Be specific "
"about novelty — textbook material is 0.0, active frontiers are 0.7+."
),
config=config,
response_schema=Survey,
tools=[exa_tool],
)
survey_reply = await surveyor.ask(
"Survey the current state of high-Tc superconductivity research."
)
survey = await survey_reply.content()
The survey is a pydantic object of type Survey(BaseModel) as defined above, and now since we have more than one open questions, we can launch parallel theorists to investigate them at the same time.
async def hypothesize(question: OpenQuestion, survey=survey) -> Hypothesis:
theorist = Agent(
name="theorist",
prompt=(
"You are a theoretical physicist. Given a survey and an open question, "
"produce a precise, falsifiable hypothesis. Be concrete about the "
"mechanism and what experiment would test it."
),
config=config,
response_schema=Hypothesis,
tools=[exa_tool],
)
theorist_prompt = (
f"Survey topic: {survey.topic}\n"
f"Overview: {survey.overview}\n\n"
f"Key findings:\n"
+ "\n".join(f"- {kf}" for kf in survey.key_findings)
+ f"\n\nFocus on this open question (novelty={question.novelty:.2f}):\n"
f"{question.question}\n\n"
f"Produce a falsifiable hypothesis with a concrete testable prediction."
hyp_reply = await theorist.ask(theorist_prompt)
return await hyp_reply.content()
results = await asyncio.gather(*(hypothesize(q) for q in survey.open_questions))
Let’s view the result
for i, hyp in enumerate(results):
lines = [
f"### Hypothesis {i+1}",
f"**Claim:** {hyp.claim}",
f"**Mechanism:** {hyp.mechanism}",
f"**Test:** {hyp.testable_prediction}",
f"**Confidence:** {hyp.confidence:.2f}",
f"**Addresses:** {hyp.source_question}",
]
display(Markdown("\n\n".join(lines)))


Note there are multiple hypothesis because each open question was processed by one theorist, and this is the basic mechanism of hierarchical multi-agent orchestration.
In the next tutorial we will introduce the concept of event in ag2, and dive deeper into multi agent orchestration.
check the notebook here
메타데이터
- post_id
- fb752f9c0265
- slug
- lionag2-recursive-exploratory-research-with-ag2-beta-typed-multi-agent-handoff-fb752f9c0265
- url
- https://medium.com/@haiyangli_38602/lionag2-recursive-exploratory-research-with-ag2-beta-typed-multi-agent-handoff-fb752f9c0265
- canonical_url
- https://medium.com/@haiyangli_38602/lionag2-recursive-exploratory-research-with-ag2-beta-typed-multi-agent-handoff-fb752f9c0265
- author_url
- https://medium.com/@haiyangli_38602
- status
- ok
- fetched_at
- 2026-06-09 15:37:30