Skip to content

Chapter 27 · Capstone 2: Multi-Source Research Assistant Team ​

Learning objectives: Build a "multi-source retrieval → collaborative writing → line-by-line verification" research pipeline — Researcher A handles public web search, Researcher B handles local document library retrieval, the Writer synthesizes a report, the Fact-checker verifies each key claim and annotates confidence levels, and the final output is a research-report.md with cited sources.

27.1 Requirements and Team Design ​

The hard part of writing a credible research report is not "writing" but breadth of source material and credibility of claims. We split these two concerns across different roles:

text
Research topic: "The 2025 landscape of edge AI inference frameworks"
    │
    ▼
┌──────────────────────────────────────────────┐
│  Research Team (coordinate mode)             │
│                                              │
│  Leader: decompose sub-tasks, dispatch, synthesize   │
│   ├── Researcher A: DuckDuckGo web search     │
│   │     (news, blogs, official announcements) │
│   └── Researcher B: local Knowledge retrieval │
│         (arXiv paper abstract library, LanceDb)│
└──────────────────────────────────────────────┘
    │  Material pack (with source annotations)
    ▼
Writer Agent ──▶ Report draft
    │
    ▼
Fact-checker Agent ──▶ Annotate confidence for each key claim
    │
    ▼
research-report.md (with cited-source list)

Why use Team (coordinate) rather than a linear Workflow?

  • The two researchers' retrievals are independent; in coordinate mode the leader delegates concurrently, so total time ≈ the slowest member; a linear Workflow can only wait serially;
  • Retrieval quality is unpredictable — the leader needs to decide dynamically whether to dispatch more (e.g., "public results are too sparse; have B search the paper library again") — this dynamism is exactly what coordinate mode excels at;
  • The latter half "write → verify" has strong sequential dependency — putting it inside a Team would be overkill, so we split it into two independent Agents wired in series.

Install dependencies:

bash
pip install agno duckduckgo-search "agno[lancedb]" pypdf python-dotenv

27.2 Preparing the Model and the Local Paper Knowledge Base ​

Use DeepSeek uniformly as the underlying model; Researcher B检索的是预先灌入 LanceDb 的 arXiv 论文摘要库 (retrieves from an arXiv paper abstract library pre-loaded into LanceDb):

python
# research_common.py —— Shared infrastructure for the team
import os
from agno.knowledge.knowledge import Knowledge
from agno.knowledge.embedder.openai import OpenAIEmbedder
from agno.models.openai import OpenAIChat
from agno.vectordb.lancedb import LanceDb, SearchType

def make_model() -> OpenAIChat:
    """All roles share the same third-party model config for easy model swaps."""
    return OpenAIChat(
        id="deepseek-chat",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com/v1",
        temperature=0.3,          # Research scenarios demand restraint; lower divergence
    )

# Researcher B's ammunition: arXiv paper abstract vector library
paper_kb = Knowledge(
    vector_db=LanceDb(
        table_name="arxiv_abstracts",
        uri="tmp/lancedb",
        search_type=SearchType.vector,
        embedder=OpenAIEmbedder(
            id="Qwen/Qwen3-Embedding-8B",
            api_key=os.getenv("EMBEDDING_API_KEY"),
            base_url="https://api.siliconflow.cn/v1",
        ),
    )
)

# Insert local paper PDF directory on first run (skip_if_exists ensures idempotency)
# paper_kb.insert(path="docs/papers/", skip_if_exists=True)

Where do materials come from?

insert(path="docs/papers/") auto-detects PDFs in the directory, chunks them, and indexes them. No papers handy? Drop any domain materials in there — this chapter demonstrates retrieval capability, not the data itself.

27.3 Two Researchers: Complementary Tools ​

Researcher A uses DuckDuckGo to search the public web; Researcher B mounts Knowledge and lets the model decide when to retrieve. Both unify their output format to 「发现 + 来源」(finding + source), making downstream referencing straightforward:

python
# researchers.py —— Two non-overlapping retrieval channels
from agno.agent import Agent
from agno.tools.duckduckgo import DuckDuckGoTools
from research_common import make_model, paper_kb

researcher_web = Agent(
    id="researcher-web",
    name="Researcher A - Web",
    role="Collect latest updates, product launches, and industry news via search engines.",
    model=make_model(),
    tools=[DuckDuckGoTools(news=True, fixed_max_results=8)],
    instructions=[
        "Run 2–3 searches with different keywords around the topic; breadth over depth.",
        "Every finding must include a source URL and approximate date.",
        "Distinguish 'official announcements' from 'opinion pieces' and label accordingly.",
        "Output a numbered list at the end; each line formatted as: [Finding] —— [Source URL] —— [Date]",
    ],
    markdown=True,
)

researcher_local = Agent(
    id="researcher-local",
    name="Researcher B - Literature",
    role="Retrieve academic perspectives and technical comparisons from the local paper knowledge base.",
    model=make_model(),
    knowledge=paper_kb,
    search_knowledge=True,          # Model decides when to trigger retrieval on its own
    instructions=[
        "Execute at least 2 knowledge-base retrievals from different angles (e.g., by tech stack, by benchmark).",
        "When citing a paper, note the paper title or filename as the source.",
        "If the knowledge base has nothing relevant, say so explicitly — do not fabricate claims.",
        "Output a numbered list at the end; each line formatted as: [Finding] —— [Paper/Source Name]",
    ],
    markdown=True,
)

Note that both members' instructions enforce structured output — this is the prerequisite for the downstream verification step to locate claims line-by-line.

27.4 Research Team: coordinate Mode Assembly ​

python
# research_team.py
from agno.team import Team, TeamMode
from researchers import researcher_web, researcher_local
from research_common import make_model

research_team = Team(
    id="research-team",
    name="Research Team",
    mode=TeamMode.coordinate,
    model=make_model(),
    members=[researcher_web, researcher_local],
    instructions=[
        "On receiving the topic, decompose it into 'web dynamics' and 'literature perspectives' sub-tasks.",
        "Delegate sub-tasks in parallel to the corresponding researchers; wait for both returns.",
        "If either side produces fewer than 3 items, dispatch an additional round with more specific keywords.",
        "When synthesizing, deduplicate, merge similar viewpoints, and form a structured material pack.",
        "Preserve original source annotations for each item in the material pack.",
    ],
    markdown=True,
)

if __name__ == "__main__":
    research_team.print_response(
        "The 2025 landscape of edge AI inference frameworks: mainstream solutions, performance comparisons, and trends",
        stream_intermediate_steps=True,   # Stream-print each member's intermediate output for observation
    )

stream_intermediate_steps=True streams the leader's dispatch decisions and each member's output to stdout — highly recommended to enable on the first run; you'll see the coordinate delegation process in real time.

27.5 Writer and Fact-checker ​

The Writer only consumes the material pack to write the report; the checker receives the draft report and verifies every key claim line-by-line before producing the revised version:

python
# writer.py —— Writer: consumes material pack only
from agno.agent import Agent
from research_common import make_model

writer = Agent(
    id="report-writer",
    name="Writer",
    role="Write a structured research report based on the material pack.",
    model=make_model(),
    instructions=[
        "Report structure: Executive Summary / Landscape Overview / Key Comparisons / Trend Assessment.",
        "After each key claim, annotate its supporting sources with [S1][S2]… numbers.",
        "Present opposing viewpoints faithfully; do not flatten disagreements for narrative flow.",
        "Append a 'References' section at the end listing all numbered URLs or paper names.",
    ],
    markdown=True,
)

The checker receives the draft report and verifies every key claim line-by-line:

python
# checker.py —— Fact-checker: line-by-line review with confidence annotation
from agno.agent import Agent
from research_common import make_model

fact_checker = Agent(
    id="fact-checker",
    name="Fact-checker",
    role="Verify every key claim in the report line-by-line and annotate confidence.",
    model=make_model(),
    instructions=[
        "Extract all claims marked with [Sn] numbers; verify each one:",
        "Does the claim exceed what the source material supports? Do numbers/dates/names match?",
        "For each claim output: ✅ Supported / ⚠️ Partially supported / ❌ Unsubstantiated, with a one-sentence reason.",
        "Give an overall high/medium/low confidence verdict.",
        "Output the revised complete report: delete or rewrite unsubstantiated claims as 'pending verification',",
        "and append a 'Verification Log' section at the end.",
    ],
    markdown=True,
)

The checker's three-state output spec (✅/⚠️/❌) is far more actionable than a blanket "verified" — low-confidence claims can be handled separately before delivery.

27.6 Wiring the Full Pipeline and Writing to Disk ​

python
# run_research.py —— Entry script
import datetime
from pathlib import Path
from research_team import research_team
from writer_and_checker import writer, fact_checker

TOPIC = "The 2025 landscape of edge AI inference frameworks"

# Leg 1: Team multi-source retrieval → material pack
material_pack = research_team.run(f"Research topic: {TOPIC}").content

# Leg 2: Draft the report
draft = writer.run(
    f"Research topic: {TOPIC}\n\nBelow is the research material pack:\n{material_pack}"
).content

# Leg 3: Verify line-by-line and revise
final = fact_checker.run(
    f"Please verify and revise the following research report draft:\n\n{draft}"
).content

# Write to disk: date-stamped archive for traceability
out_dir = Path("reports"); out_dir.mkdir(exist_ok=True)
today = datetime.date.today().isoformat()
report_path = out_dir / f"research-report-{today}.md"
report_path.write_text(final, encoding="utf-8")
print(f"Report generated: {report_path}")

Open reports/research-report-<date>.md after the run: [S1][S2] numbers in the body map to entries in the References section; the Verification Log appendix tells you which claims were downgraded to "pending verification."

Cost note

This pipeline runs entirely on deepseek-chat; the dominant token cost comes from the Team's multi-turn coordination and the long material injection. If budget-sensitive, swap the checker for a smaller model — verification tasks demand less reasoning power than writing.

Chapter Summary ​

  • Team selection: independent parallel retrievals → TeamMode.coordinate (concurrent delegation, dynamic re-dispatch); strongly sequential tail → independent Agents in series;
  • Structured intermediates: researchers output a unified 「finding + source」format, the key enabler for downstream writer/checker to wire automatically;
  • Separation of concerns: Writer focuses on expression; checker focuses on verification — two perspectives on the same draft are far more reliable than one Agent self-checking;
  • Confidence tiering: the ✅/⚠️/❌ three-state check log makes delivery quality quantitatively manageable.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Why are the two researchers' retrieval tasks better suited to Team (coordinate) than a linear Workflow?

2. The primary purpose of enforcing a "[Finding] —— [Source]" output format in researchers' instructions is?

3. Compared to a flat "verified" reply, what is the advantage of the fact-checker outputting ✅/⚠️/❌ three states per claim?

4. Regarding cost optimization for this pipeline, which approach is most reasonable?

🛠️ Hands-On Exercises ​

  1. Give Researcher A an RSS subscription reader tool (e.g., agno.tools.rss.RssTools); compare the report's freshness with and without it.
  2. Hook the checker's output into a new Agent "revision executor" that only rewrites the report per the checker's意见, fully separating "verify" from "revise."
  3. Add cost instrumentation to the pipeline: print model usage before and after each of the three stages, identify the token-heavy环节, and attempt optimizations.

Next chapter pivots from "research" back to "service": put the customer-service knowledge-base Agent onto the production line — Chapter 28: Customer-Support Knowledge Base Agent on AgentOS.