返回洞察
Agent · 智能体

研究与报告智能体实战(Capstone)

AI Workforce Factory·2026-07-20·11 分钟·来源 · superml.org

Capstone:构建一个研究与报告智能体

构建一个会搜索、综合并产出结构化报告的完整智能体。

  • 理解并应用本课涵盖的核心概念

你将构建什么

这个 capstone 把 Agentic AI 课程里的一切——多智能体编排、工具使用、记忆、安全与评估——汇聚成一个连贯的项目。

研究与报告智能体以一个研究问题作为输入,产出一个带来源引用的结构化 markdown 报告。它是研究者、分析师或技术写作者真正会用的那种工具。

输入:「RAG 与微调(fine-tuning)在企业 LLM 应用中的权衡是什么?」

输出: 一份结构化报告,含执行摘要、详细分析章节、权衡表、建议与引用来源——在 2 分钟内产出。

系统架构

系统依次使用四个专门智能体:

User Question
     |
     v
[Planner Agent]
Breaks the question into 4-6 specific sub-questions
     |
     v
[Research Agent]
Searches the web for each sub-question (can run in parallel)
     |
     v
[Synthesis Agent]
Combines all research findings into coherent prose
     |
     v
[Formatter Agent]
Structures the content into a polished markdown report
     |
     v
Final Report (with sources, sections, and executive summary)

每个智能体只有单一职责。这让系统比单一庞然大物般的智能体更易调试、改进与扩展。

第一部分:Planner 智能体

Planner 的职责是把一个宽泛的研究问题分解成具体的、可搜索的子问题。这一点很关键,因为「RAG 与微调的权衡是什么」对一个单一搜索来说太宽泛——你会得到散乱的结果。把它拆成子问题,能产出聚焦、高质量的研究。

import anthropic
import json

client = anthropic.Anthropic()

PLANNER_SYSTEM = """You are a research planning specialist. Your job is to decompose
a broad research question into 4-6 specific sub-questions that together cover the topic completely.

Each sub-question should:
- Be specific enough to search for directly
- Cover a distinct aspect of the main question
- Together provide comprehensive coverage of the topic

Return a JSON object with:
{
  "main_question": "the original question",
  "sub_questions": ["question 1", "question 2", ...],
  "key_concepts": ["concept 1", "concept 2", ...],
  "report_sections": ["suggested section titles for the final report"]
}"""

def run_planner(research_question: str) -> dict:
    """Break a research question into a research plan."""

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1024,
        system=PLANNER_SYSTEM,
        messages=[{
            "role": "user",
            "content": f"Create a research plan for: {research_question}"
        }]
    )

    try:
        # Extract JSON from response
        text = response.content[0].text
        # Find JSON block
        start = text.find('{')
        end = text.rfind('}') + 1
        return json.loads(text[start:end])
    except (json.JSONDecodeError, ValueError):
        # Fallback if JSON parsing fails
        return {
            "main_question": research_question,
            "sub_questions": [research_question],
            "key_concepts": [],
            "report_sections": ["Overview", "Analysis", "Conclusion"]
        }

# Test the planner
plan = run_planner("What are the trade-offs between RAG and fine-tuning for enterprise LLM applications?")
print(json.dumps(plan, indent=2))

对我们的测试问题,planner 应当产出类似这样的子问题:

  1. 什么是 RAG(检索增强生成),它如何工作?
  2. 什么是 LLM 微调,何时使用它?
  3. RAG 与微调在成本与基础设施要求上各是什么?
  4. RAG 与微调在知识新鲜度与准确性上如何比较?
  5. 延迟与可扩展性上的权衡是什么?
  6. 企业通常选哪种方案,为什么?

第二部分:Research 智能体

Research 智能体接收一个单一子问题,并找到相关、准确的信息。对每个子问题,它执行 1-3 次定向搜索并返回结构化的发现。

import requests
import time

# Simple web search using DuckDuckGo's instant answer API
def web_search(query: str, max_results: int = 3) -> list[dict]:
    """Search the web and return clean results."""
    try:
        # Using DuckDuckGo HTML scraping (for production, use Serper or Tavily API)
        headers = {"User-Agent": "Mozilla/5.0 (research bot)"}
        params = {"q": query, "format": "json"}

        response = requests.get(
            "https://api.duckduckgo.com/",
            params=params,
            headers=headers,
            timeout=10
        )
        data = response.json()

        results = []
        # Abstract (main result)
        if data.get("Abstract"):
            results.append({
                "source": data.get("AbstractURL", "DuckDuckGo"),
                "title": data.get("Heading", query),
                "content": data["Abstract"]
            })

        # Related topics
        for topic in data.get("RelatedTopics", [])[:max_results - 1]:
            if isinstance(topic, dict) and topic.get("Text"):
                results.append({
                    "source": topic.get("FirstURL", ""),
                    "title": topic.get("Text", "")[:50],
                    "content": topic.get("Text", "")
                })

        return results if results else [{"source": "", "title": query, "content": "No results found"}]

    except Exception as e:
        return [{"source": "", "title": "Search failed", "content": str(e)}]

RESEARCH_TOOLS = [
    {
        "name": "search_web",
        "description": """Search the web for information about a specific topic.
        Use for current information, technical comparisons, and expert opinions.
        Returns: list of results with source URL, title, and content snippet.""",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Specific search query"}
            },
            "required": ["query"]
        }
    }
]

RESEARCH_SYSTEM = """You are a research analyst. Given a specific research question,
search for accurate and relevant information.

Your output should be structured research notes containing:
- Key facts and data points (with sources)
- Expert opinions or consensus views
- Any important nuances or contradictions found
- 2-4 credible sources

Be factual and specific. Avoid vague generalities."""

def run_research_agent(sub_question: str) -> dict:
    """Research a single sub-question. Returns findings dict."""

    messages = [{"role": "user", "content": f"Research this question: {sub_question}"}]
    sources = []

    for _ in range(5):
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=1024,
            system=RESEARCH_SYSTEM,
            tools=RESEARCH_TOOLS,
            messages=messages
        )

        if response.stop_reason == "end_turn":
            return {
                "question": sub_question,
                "findings": response.content[0].text,
                "sources": sources
            }

        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": response.content})
            tool_results = []

            for block in response.content:
                if block.type == "tool_use":
                    results = web_search(block.input["query"])
                    # Collect sources for citation
                    sources.extend([r["source"] for r in results if r.get("source")])

                    formatted = "\n\n".join([
                        f"Source: {r['source']}\n{r['content']}"
                        for r in results
                    ])
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": formatted
                    })

            messages.append({"role": "user", "content": tool_results})

    return {"question": sub_question, "findings": "Research incomplete", "sources": sources}

第三部分:并行运行研究

对我们 planner 生成的六个子问题,我们可以并行运行研究以节省时间:

import concurrent.futures
from typing import List

def research_all_questions(sub_questions: List[str]) -> List[dict]:
    """Run research agents for all sub-questions in parallel."""

    print(f"Researching {len(sub_questions)} sub-questions in parallel...")

    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        # Submit all research tasks
        future_to_question = {
            executor.submit(run_research_agent, q): q
            for q in sub_questions
        }

        results = []
        for future in concurrent.futures.as_completed(future_to_question):
            question = future_to_question[future]
            try:
                result = future.result(timeout=60)
                results.append(result)
                print(f"  Completed: {question[:60]}...")
            except Exception as e:
                print(f"  Failed: {question[:60]}... ({e})")
                results.append({
                    "question": question,
                    "findings": f"Research failed: {str(e)}",
                    "sources": []
                })

    return results

使用 3 个并行 worker 在速度与限流之间取得平衡。有 6 个子问题时,这能把总研究时间从约 90 秒(顺序)降到约 30 秒(并行)。

第四部分:Synthesis 智能体

Synthesis 智能体接收所有研究发现,并把它们编织成连贯的散文。它的挑战是避免重复,并让行文自然流畅。

SYNTHESIS_SYSTEM = """You are an expert technical writer and analyst. You receive research
findings from multiple sources and synthesize them into clear, coherent prose.

Guidelines:
- Integrate findings across sub-questions into flowing paragraphs, not bullet dumps
- Identify and highlight areas of consensus and disagreement
- Use specific facts and data points from the research
- Write for a senior technical audience — no hand-holding, no padding
- Maintain a neutral, analytical tone
- Flag anywhere the research found contradictions or uncertainty"""

def run_synthesis_agent(plan: dict, research_results: List[dict]) -> str:
    """Synthesize all research findings into coherent analysis."""

    # Format research for the synthesis agent
    research_text = ""
    all_sources = []

    for result in research_results:
        research_text += f"\n\n### Sub-question: {result['question']}\n"
        research_text += result['findings']
        all_sources.extend(result.get('sources', []))

    # Deduplicate sources
    unique_sources = list(dict.fromkeys(s for s in all_sources if s))

    prompt = f"""Main research question: {plan['main_question']}

Suggested report sections: {', '.join(plan.get('report_sections', []))}

Research findings:
{research_text}

Write a comprehensive synthesis that covers all aspects of the research.
This synthesis will be used as the basis for the final report."""

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=3000,
        system=SYNTHESIS_SYSTEM,
        messages=[{"role": "user", "content": prompt}]
    )

    return response.content[0].text, unique_sources

第五部分:Formatter 智能体

Formatter 智能体接收综合结果,并产出一份带恰当结构、执行摘要与格式化引用的精美 markdown 报告。

FORMATTER_SYSTEM = """You are a report formatter. Convert research synthesis into a
polished, professional markdown report.

Required report structure:
# [Title]

## Executive Summary
(2-3 paragraphs, standalone summary of key findings and recommendation)

## [Section 1 Title]
(prose content)

## [Section 2 Title]
(prose content)

... (additional sections)

## Recommendation
(clear, actionable recommendation based on the research)

## Sources
(formatted citation list)

---
*Report generated by Research Agent | [Date]*

Keep all technical content from the synthesis. The formatting should make the
content more readable, not change its substance."""

def run_formatter_agent(synthesis: str, sources: List[str], research_question: str) -> str:
    """Format synthesis into a polished markdown report."""

    # Format sources as a numbered list
    source_list = "\n".join([
        f"{i+1}. {source}"
        for i, source in enumerate(sources[:10])  # Limit to 10 sources
    ]) if sources else "Web research (sources available on request)"

    prompt = f"""Research question: {research_question}

Synthesis:
{synthesis}

Available sources:
{source_list}

Format this into a polished markdown report following the required structure."""

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=4096,
        system=FORMATTER_SYSTEM,
        messages=[{"role": "user", "content": prompt}]
    )

    return response.content[0].text

第六部分:编排器(Orchestrator)

现在把所有东西串起来:

from datetime import datetime

def run_research_report_agent(research_question: str) -> dict:
    """
    Full pipeline: Plan → Research → Synthesize → Format → Report
    Returns the final report and metadata.
    """

    start_time = datetime.now()
    print(f"\n{'='*70}")
    print(f"Research Report Agent")
    print(f"Question: {research_question}")
    print(f"{'='*70}\n")

    # Step 1: Plan
    print("[1/4] Planning research structure...")
    plan = run_planner(research_question)
    print(f"Generated {len(plan['sub_questions'])} sub-questions")
    for i, q in enumerate(plan['sub_questions'], 1):
        print(f"  {i}. {q}")

    # Step 2: Research (parallel)
    print(f"\n[2/4] Researching {len(plan['sub_questions'])} sub-questions...")
    research_results = research_all_questions(plan['sub_questions'])
    successful = sum(1 for r in research_results if "failed" not in r['findings'].lower())
    print(f"Research complete: {successful}/{len(research_results)} successful")

    # Step 3: Synthesize
    print("\n[3/4] Synthesizing findings...")
    synthesis, sources = run_synthesis_agent(plan, research_results)
    print(f"Synthesis complete ({len(synthesis)} chars)")

    # Step 4: Format
    print("\n[4/4] Formatting final report...")
    report = run_formatter_agent(synthesis, sources, research_question)

    elapsed = (datetime.now() - start_time).total_seconds()

    print(f"\nReport complete in {elapsed:.1f} seconds")
    print(f"Report length: {len(report)} characters")

    return {
        "question": research_question,
        "report": report,
        "plan": plan,
        "sources": sources,
        "elapsed_seconds": elapsed,
        "timestamp": start_time.isoformat()
    }

第七部分:运行这个 Capstone

if __name__ == "__main__":
    result = run_research_report_agent(
        "What are the trade-offs between RAG and fine-tuning for enterprise LLM applications?"
    )

    # Save the report
    with open("rag_vs_finetune_report.md", "w") as f:
        f.write(result["report"])

    print("\n" + "="*70)
    print("FINAL REPORT")
    print("="*70)
    print(result["report"])

预期的输出结构

在我们的测试问题上运行,会产出具备如下结构的报告:

# RAG vs Fine-Tuning for Enterprise LLM Applications: A Comparative Analysis

## Executive Summary
Enterprise organizations adopting LLMs face a fundamental architectural choice...
[2-3 paragraphs covering the key trade-offs and bottom-line recommendation]

## Understanding RAG and Fine-Tuning
RAG (Retrieval-Augmented Generation) augments LLM responses by retrieving...
Fine-tuning modifies the model's weights through additional training on...

## Cost and Infrastructure Requirements
RAG typically requires a vector database (Pinecone, Weaviate, ChromaDB)...
Fine-tuning costs are front-loaded: a full fine-tune of a 7B parameter model...

## Knowledge Freshness and Accuracy
RAG's key advantage is knowledge currency — the retrieval index can be updated...
Fine-tuned models bake knowledge into weights, creating a staleness problem...

## Latency and Scalability
RAG adds retrieval latency (50-200ms typical) to each inference call...
Fine-tuned models have no retrieval overhead but require serving a custom model...

## Enterprise Adoption Patterns
Based on available data, enterprises with dynamic knowledge bases prefer RAG...

## Recommendation
For most enterprise use cases, start with RAG. It has lower upfront cost...
Consider fine-tuning when: you need consistent output format/style, the task...

## Sources
1. https://arxiv.org/abs/2312.10997
2. https://huggingface.co/blog/rag-vs-fine-tuning
...

---
*Report generated by Research Agent | 2026-06-06*

扩展系统

一旦基础管道能跑起来,这些扩展会显著增加价值:

添加引用校验: 在包含一个来源前,抓取并校验 URL 是活的:

def verify_source(url: str) -> bool:
    try:
        response = requests.head(url, timeout=5)
        return response.status_code == 200
    except:
        return False

添加批判步骤: 在格式化前,跑一个「critic 智能体」,识别缺口或薄弱声明:

CRITIC_SYSTEM = """Review this research synthesis. Identify: (1) claims that lack evidence,
(2) important aspects of the topic that weren't covered, (3) any logical inconsistencies."""

添加导出格式: 除了 markdown,formatter 还能产出 PDF、HTML 或 Notion 格式。

添加评估: 用第 6 课(Lesson 6)的评估框架自动给每份报告打分。

你构建内容的总结

这个 capstone 项目组装了课程里的每个概念:

  • 多智能体架构(Planner、Research、Synthesis、Formatter)
  • 工具使用(把网页搜索包装成智能体工具)
  • 并行执行(并发研究子问题)
  • 真实 API 集成(带错误处理的 DuckDuckGo 搜索)
  • 安全(来源限制、输出大小限制、超时处理)
  • 结构化输出(formatter 产出一致的 markdown)

结果是一个系统,能在约 60-90 秒内把一个研究问题变成一份精美、带引用的报告——而人类研究者要花 2-4 小时。

这个架构可自然扩展:添加更多研究工具(arXiv、新闻 API、内部数据库)、在不触碰其他智能体的前提下改进任一智能体的提示、在综合步骤为超长报告加并行、或用为该任务微调过的专门模型替换任一组件。

生产级智能体系统就是这样构建的:从一个清晰的管道开始,把每个阶段实现为一个聚焦的智能体,端到端测试管道,然后基于质量最弱处逐步改进组件。

#AI#tutorial#superml#agent#multi-agent#capstone#report

有类似的场景想落地?聊聊看。