Phase 1 delivers the complete durability and storage layer:
- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
and multi-cycle durability
New crates: stemedb-wal, stemedb-storage, stemedb-ingest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
492 lines
14 KiB
Markdown
492 lines
14 KiB
Markdown
# Building a Research Document Generator with ADK-Go
|
|
|
|
This guide walks through building a multi-agent system that researches a topic and produces a comprehensive, well-structured document.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The research document generator uses a **sequential pipeline** of specialized agents:
|
|
|
|
```
|
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
│ Planner │───▶│ Researcher │───▶│ Writer │───▶│ Editor │
|
|
│ │ │ │ │ │ │ │
|
|
│ Creates │ │ Gathers │ │ Synthesizes │ │ Polishes │
|
|
│ outline │ │ sources │ │ into prose │ │ final doc │
|
|
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Project Setup
|
|
|
|
```bash
|
|
mkdir research-agent && cd research-agent
|
|
go mod init research-agent
|
|
go get google.golang.org/adk
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Implementation
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
|
|
"google.golang.org/adk/agent"
|
|
"google.golang.org/adk/agent/llmagent"
|
|
"google.golang.org/adk/agent/sequentialagent"
|
|
"google.golang.org/adk/cmd/launcher/adk"
|
|
"google.golang.org/adk/cmd/launcher/full"
|
|
"google.golang.org/adk/model/gemini"
|
|
"google.golang.org/adk/server/restapi/services"
|
|
"google.golang.org/adk/tool"
|
|
"google.golang.org/adk/tool/functiontool"
|
|
"google.golang.org/adk/tool/geminitool"
|
|
"google.golang.org/genai"
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
// Initialize model
|
|
model, err := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
|
|
APIKey: os.Getenv("GOOGLE_API_KEY"),
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("Failed to create model: %v", err)
|
|
}
|
|
|
|
// Build the research pipeline
|
|
pipeline, err := buildResearchPipeline(model)
|
|
if err != nil {
|
|
log.Fatalf("Failed to build pipeline: %v", err)
|
|
}
|
|
|
|
// Launch
|
|
l := full.NewLauncher()
|
|
cfg := &adk.Config{AgentLoader: services.NewSingleAgentLoader(pipeline)}
|
|
if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
|
|
log.Fatalf("Execution failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func buildResearchPipeline(model *gemini.Model) (agent.Agent, error) {
|
|
// Stage 1: Research Planner
|
|
planner, err := llmagent.New(llmagent.Config{
|
|
Name: "research_planner",
|
|
Model: model,
|
|
Description: "Creates a structured research plan and outline",
|
|
Instruction: `You are a research planner. Given a topic, create a comprehensive research plan.
|
|
|
|
Your output must include:
|
|
1. **Research Questions**: 5-7 key questions to investigate
|
|
2. **Outline**: Hierarchical document structure with sections and subsections
|
|
3. **Search Queries**: Specific search queries to find relevant information
|
|
4. **Source Types**: What kinds of sources to prioritize (academic, news, official, etc.)
|
|
|
|
Format your plan clearly with headers and bullet points.
|
|
Be thorough but focused on the most important aspects of the topic.`,
|
|
OutputKey: "research_plan",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 2: Researcher (with web search)
|
|
researcher, err := llmagent.New(llmagent.Config{
|
|
Name: "researcher",
|
|
Model: model,
|
|
Description: "Gathers information from multiple sources",
|
|
Instruction: `You are a thorough researcher. Using the research plan below, gather comprehensive information.
|
|
|
|
RESEARCH PLAN:
|
|
{research_plan}
|
|
|
|
Your task:
|
|
1. Execute the search queries from the plan
|
|
2. Gather facts, statistics, quotes, and evidence
|
|
3. Note the source of each piece of information
|
|
4. Identify conflicting information or debates
|
|
5. Find recent developments and updates
|
|
|
|
For each section in the outline, compile relevant findings.
|
|
Organize your research notes by section.
|
|
Include direct quotes where impactful (with attribution).
|
|
Flag any gaps where information couldn't be found.`,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "research_notes",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 3: Writer
|
|
writer, err := llmagent.New(llmagent.Config{
|
|
Name: "writer",
|
|
Model: model,
|
|
Description: "Synthesizes research into a cohesive document",
|
|
Instruction: `You are an expert writer. Transform the research into a polished document.
|
|
|
|
RESEARCH PLAN:
|
|
{research_plan}
|
|
|
|
RESEARCH NOTES:
|
|
{research_notes}
|
|
|
|
Writing guidelines:
|
|
1. Follow the outline structure from the research plan
|
|
2. Synthesize information into flowing prose (no bullet points in body)
|
|
3. Use topic sentences and smooth transitions
|
|
4. Integrate evidence naturally with proper attribution
|
|
5. Maintain objectivity; present multiple perspectives where relevant
|
|
6. Include an executive summary at the beginning
|
|
7. Add a references section at the end
|
|
|
|
Target length: Comprehensive but concise (aim for depth over breadth)
|
|
Tone: Professional, authoritative, accessible
|
|
Format: Use markdown with headers (## for main sections, ### for subsections)`,
|
|
OutputKey: "draft_document",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Stage 4: Editor
|
|
editor, err := llmagent.New(llmagent.Config{
|
|
Name: "editor",
|
|
Model: model,
|
|
Description: "Reviews and polishes the final document",
|
|
Instruction: `You are a meticulous editor. Review and improve the document.
|
|
|
|
DRAFT DOCUMENT:
|
|
{draft_document}
|
|
|
|
Editorial checklist:
|
|
1. **Clarity**: Simplify complex sentences; remove jargon or define it
|
|
2. **Flow**: Ensure logical progression; improve transitions
|
|
3. **Accuracy**: Flag any claims that seem unsupported
|
|
4. **Completeness**: Note any gaps in coverage
|
|
5. **Consistency**: Standardize formatting, terminology, tone
|
|
6. **Grammar**: Fix errors in spelling, punctuation, syntax
|
|
7. **Engagement**: Strengthen the opening; ensure compelling conclusion
|
|
|
|
Output the complete, polished document.
|
|
Add an "Editor's Note" section at the end with:
|
|
- Summary of major changes made
|
|
- Any remaining concerns or suggestions for future updates
|
|
- Confidence assessment (how well-supported is this document?)`,
|
|
OutputKey: "final_document",
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Assemble pipeline
|
|
pipeline, err := sequentialagent.New(sequentialagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "research_document_generator",
|
|
Description: "Researches a topic and produces a comprehensive document",
|
|
SubAgents: []agent.Agent{planner, researcher, writer, editor},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pipeline, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Enhanced Version with Custom Tools
|
|
|
|
Add tools for saving drafts and managing sources:
|
|
|
|
```go
|
|
// Source tracking tool
|
|
type AddSourceInput struct {
|
|
Title string `json:"title" jsonschema:"Source title"`
|
|
URL string `json:"url" jsonschema:"Source URL"`
|
|
Type string `json:"type" jsonschema:"Type: academic, news, official, blog"`
|
|
Quote string `json:"quote,omitempty" jsonschema:"Key quote from source"`
|
|
Date string `json:"date,omitempty" jsonschema:"Publication date"`
|
|
}
|
|
|
|
type AddSourceOutput struct {
|
|
SourceID int `json:"source_id"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var sourceCounter int
|
|
var sources []AddSourceInput
|
|
|
|
func addSource(ctx tool.Context, input AddSourceInput) AddSourceOutput {
|
|
sourceCounter++
|
|
sources = append(sources, input)
|
|
|
|
// Store in session state for other agents
|
|
state := ctx.Session().State()
|
|
state.Set("sources", sources)
|
|
|
|
return AddSourceOutput{
|
|
SourceID: sourceCounter,
|
|
Message: fmt.Sprintf("Added source #%d: %s", sourceCounter, input.Title),
|
|
}
|
|
}
|
|
|
|
// Document section tool
|
|
type SaveSectionInput struct {
|
|
Section string `json:"section" jsonschema:"Section name (e.g., 'Introduction')"`
|
|
Content string `json:"content" jsonschema:"Section content in markdown"`
|
|
}
|
|
|
|
type SaveSectionOutput struct {
|
|
Message string `json:"message"`
|
|
WordCount int `json:"word_count"`
|
|
TotalSections int `json:"total_sections"`
|
|
}
|
|
|
|
var documentSections = make(map[string]string)
|
|
|
|
func saveSection(ctx tool.Context, input SaveSectionInput) SaveSectionOutput {
|
|
documentSections[input.Section] = input.Content
|
|
wordCount := len(strings.Fields(input.Content))
|
|
|
|
// Persist to state
|
|
ctx.Session().State().Set("document_sections", documentSections)
|
|
|
|
return SaveSectionOutput{
|
|
Message: fmt.Sprintf("Saved section: %s", input.Section),
|
|
WordCount: wordCount,
|
|
TotalSections: len(documentSections),
|
|
}
|
|
}
|
|
|
|
// Create tools
|
|
func createResearchTools() ([]tool.Tool, error) {
|
|
sourceTool, err := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "add_source",
|
|
Description: "Track a source used in research. Call this for every source you reference.",
|
|
},
|
|
addSource,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sectionTool, err := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "save_section",
|
|
Description: "Save a completed section of the document",
|
|
},
|
|
saveSection,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []tool.Tool{
|
|
geminitool.GoogleSearch{},
|
|
sourceTool,
|
|
sectionTool,
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Adding Parallel Research
|
|
|
|
For faster research, use parallel agents to investigate different aspects simultaneously:
|
|
|
|
```go
|
|
import "google.golang.org/adk/agent/parallelagent"
|
|
|
|
func buildParallelResearcher(model *gemini.Model) (agent.Agent, error) {
|
|
// Background/history researcher
|
|
historyResearcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "history_researcher",
|
|
Model: model,
|
|
Instruction: `Research the historical background and evolution of: {topic}
|
|
Focus on: origins, key milestones, how it developed over time.`,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "history_research",
|
|
})
|
|
|
|
// Current state researcher
|
|
currentResearcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "current_researcher",
|
|
Model: model,
|
|
Instruction: `Research the current state and recent developments of: {topic}
|
|
Focus on: latest news, current statistics, recent changes.`,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "current_research",
|
|
})
|
|
|
|
// Expert opinions researcher
|
|
expertResearcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "expert_researcher",
|
|
Model: model,
|
|
Instruction: `Research expert opinions and analysis on: {topic}
|
|
Focus on: thought leaders, academic perspectives, industry experts.`,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "expert_research",
|
|
})
|
|
|
|
// Future outlook researcher
|
|
futureResearcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "future_researcher",
|
|
Model: model,
|
|
Instruction: `Research predictions and future outlook for: {topic}
|
|
Focus on: trends, forecasts, potential developments.`,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "future_research",
|
|
})
|
|
|
|
// Run all in parallel
|
|
parallel, err := parallelagent.New(parallelagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "parallel_research_team",
|
|
SubAgents: []agent.Agent{
|
|
historyResearcher,
|
|
currentResearcher,
|
|
expertResearcher,
|
|
futureResearcher,
|
|
},
|
|
},
|
|
})
|
|
|
|
return parallel, err
|
|
}
|
|
```
|
|
|
|
Then modify the main pipeline to use the parallel researcher:
|
|
|
|
```go
|
|
// In buildResearchPipeline, replace single researcher with:
|
|
parallelResearcher, err := buildParallelResearcher(model)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Update writer instruction to use all research outputs:
|
|
writer, err := llmagent.New(llmagent.Config{
|
|
Instruction: `Synthesize all research into a cohesive document.
|
|
|
|
OUTLINE:
|
|
{research_plan}
|
|
|
|
HISTORICAL BACKGROUND:
|
|
{history_research}
|
|
|
|
CURRENT STATE:
|
|
{current_research}
|
|
|
|
EXPERT ANALYSIS:
|
|
{expert_research}
|
|
|
|
FUTURE OUTLOOK:
|
|
{future_research}
|
|
|
|
Create a comprehensive document that weaves all perspectives together.`,
|
|
// ...
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Running the Agent
|
|
|
|
```bash
|
|
# Console mode - interactive
|
|
go run main.go
|
|
|
|
# Example prompts:
|
|
# > Write a research document on the impact of AI on healthcare
|
|
# > Create a comprehensive report on renewable energy trends in 2024
|
|
# > Research and document the history and future of quantum computing
|
|
|
|
# Web UI mode - for debugging and visualization
|
|
go run main.go web api webui
|
|
# Open http://localhost:8080
|
|
```
|
|
|
|
---
|
|
|
|
## Output Example
|
|
|
|
For the prompt "Research the impact of remote work on urban planning":
|
|
|
|
```markdown
|
|
# The Impact of Remote Work on Urban Planning
|
|
|
|
## Executive Summary
|
|
|
|
The rise of remote work, accelerated by the COVID-19 pandemic, is fundamentally
|
|
reshaping urban planning paradigms. This document examines...
|
|
|
|
## 1. Historical Context
|
|
|
|
### 1.1 Pre-Pandemic Work Patterns
|
|
|
|
Before 2020, remote work was limited to approximately 5% of the workforce...
|
|
|
|
### 1.2 The Pandemic Catalyst
|
|
|
|
The COVID-19 pandemic forced an unprecedented experiment in remote work...
|
|
|
|
## 2. Current Urban Impacts
|
|
|
|
### 2.1 Commercial Real Estate Transformation
|
|
|
|
Office vacancy rates in major cities have reached historic highs...
|
|
|
|
### 2.2 Residential Migration Patterns
|
|
|
|
Data from the U.S. Census Bureau shows significant population shifts...
|
|
|
|
## 3. Planning Responses
|
|
|
|
### 3.1 Zoning Adaptations
|
|
|
|
Cities are revising zoning codes to allow mixed-use developments...
|
|
|
|
## 4. Future Outlook
|
|
|
|
...
|
|
|
|
## References
|
|
|
|
1. Smith, J. (2024). "Remote Work and Urban Form." Journal of Planning...
|
|
2. ...
|
|
|
|
---
|
|
|
|
**Editor's Note**: This document synthesizes 23 sources spanning academic
|
|
research, government data, and industry reports. Confidence: High for
|
|
current trends; Medium for long-term predictions.
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Be specific with prompts**: "Research quantum computing" is too broad; "Research the current state of quantum error correction and its implications for practical quantum computers" is better.
|
|
|
|
2. **Monitor intermediate outputs**: Use the web UI to inspect `research_plan`, `research_notes`, etc. to debug issues.
|
|
|
|
3. **Adjust pipeline stages**: Some topics may need more research depth; others may need multiple editing passes.
|
|
|
|
4. **Handle long documents**: For very comprehensive documents, consider chunking the writing phase by section.
|
|
|
|
5. **Add human review points**: Insert a "reviewer" agent or callback that flags sections needing human verification.
|