Capstone Project

Build a complete AI Agent end-to-end β€” from design to deployment

Objective

Apply everything you’ve learned from the previous 13 lessons to build a complete AI Agent. You’ll create a Personal Research Agent β€” an agent that researches topics, synthesizes information, and generates reports.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Personal Research Agent        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Planningβ”‚ Research β”‚ Synthesis β”‚ Memory β”‚
β”‚ Agent   β”‚ Agent    β”‚ Agent     β”‚ System β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚              Tool Layer                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Web     β”‚ β”‚MCP   β”‚ β”‚Supabase       β”‚  β”‚
β”‚  β”‚Search  β”‚ β”‚Tools β”‚ β”‚(pgvector +    β”‚  β”‚
β”‚  β”‚        β”‚ β”‚      β”‚ β”‚ memories)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚           Observability (Langfuse)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 1: Setup Project

mkdir research-agent && cd research-agent
npm init -y
npm install ai @ai-sdk/openai zod langfuse @supabase/supabase-js
npm install -D typescript @types/node tsx
npx tsc --init
// .env
OPENAI_API_KEY=sk-...
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=eyJ...
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...

Step 2: Memory System (Lesson 12)

// src/memory.ts
import { createClient } from "@supabase/supabase-js";
import OpenAI from "openai";

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);

const openaiClient = new OpenAI();

export async function getEmbedding(text: string): Promise<number[]> {
  const response = await openaiClient.embeddings.create({
    model: "text-embedding-3-small",
    input: text
  });
  return response.data[0].embedding;
}

export async function saveResearch(topic: string, content: string, sources: string[]) {
  const embedding = await getEmbedding(topic);

  await supabase.from("research_memories").insert({
    topic,
    content,
    sources,
    embedding,
    created_at: new Date().toISOString()
  });
}

export async function recallResearch(query: string, limit = 5) {
  const embedding = await getEmbedding(query);

  const { data } = await supabase.rpc("match_research", {
    query_embedding: embedding,
    match_count: limit
  });

  return data || [];
}

Step 3: Tools (Lessons 3, 4, 10)

// src/tools.ts
import { tool } from "ai";
import { z } from "zod";
import { saveResearch, recallResearch } from "./memory";

export const searchWeb = tool({
  description: "Search the web for information on a topic",
  parameters: z.object({
    query: z.string().describe("Search keywords"),
    maxResults: z.number().default(5)
  }),
  execute: async ({ query, maxResults }) => {
    const response = await fetch(
      `https://api.tavily.com/search?query=${encodeURIComponent(query)}&max_results=${maxResults}`,
      { headers: { Authorization: `Bearer ${process.env.TAVILY_API_KEY}` } }
    );
    const data = await response.json();
    return { results: data.results };
  }
});

export const saveNote = tool({
  description: "Save important research notes to long-term memory",
  parameters: z.object({
    topic: z.string(),
    content: z.string(),
    sources: z.array(z.string())
  }),
  execute: async ({ topic, content, sources }) => {
    await saveResearch(topic, content, sources);
    return { saved: true };
  }
});

export const recallNotes = tool({
  description: "Search for previously saved research notes",
  parameters: z.object({
    query: z.string()
  }),
  execute: async ({ query }) => {
    const memories = await recallResearch(query);
    return { memories };
  }
});

Step 4: Agent With Planning + Metacognition (Lessons 6, 8)

// src/agent.ts
import { generateText, generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
import { searchWeb, saveNote, recallNotes } from "./tools";

const ResearchPlanSchema = z.object({
  topic: z.string(),
  subQuestions: z.array(z.string()),
  approach: z.string(),
  estimatedSteps: z.number()
});

const QualityCheckSchema = z.object({
  completeness: z.number().min(1).max(10),
  accuracy: z.number().min(1).max(10),
  missingAreas: z.array(z.string()),
  shouldContinue: z.boolean()
});

export async function researchAgent(topic: string): Promise<string> {
  // Phase 1: Planning
  console.log("πŸ“‹ Phase 1: Planning...");
  const { object: plan } = await generateObject({
    model: openai("gpt-4o"),
    schema: ResearchPlanSchema,
    prompt: `Create a research plan for: "${topic}".
Break into 3-5 sub-questions to answer.`
  });

  // Phase 2: Research
  console.log("πŸ” Phase 2: Researching...");
  const findings: string[] = [];

  for (const question of plan.subQuestions) {
    const result = await generateText({
      model: openai("gpt-4o"),
      tools: { searchWeb, recallNotes },
      maxSteps: 5,
      system: "You are a researcher. Find accurate and detailed information.",
      prompt: question
    });
    findings.push(`### ${question}\n${result.text}`);
  }

  // Phase 3: Synthesis
  console.log("✍️ Phase 3: Synthesizing...");
  const report = await generateText({
    model: openai("gpt-4o"),
    prompt: `Synthesize these findings into a complete report:
Topic: ${topic}
Findings:
${findings.join("\n\n")}`
  });

  // Phase 4: Metacognition β€” Self-check
  console.log("🧠 Phase 4: Quality check...");
  const { object: quality } = await generateObject({
    model: openai("gpt-4o"),
    schema: QualityCheckSchema,
    prompt: `Evaluate report quality:\n${report.text}`
  });

  if (quality.shouldContinue && quality.completeness < 7) {
    console.log("πŸ”„ Improving report...");
    const improved = await generateText({
      model: openai("gpt-4o"),
      tools: { searchWeb },
      maxSteps: 3,
      prompt: `Improve the report. Missing areas: ${quality.missingAreas.join(", ")}
Original: ${report.text}`
    });
    return improved.text;
  }

  await saveNote.execute({
    topic,
    content: report.text,
    sources: ["web-search"]
  });

  return report.text;
}

Step 5: Observability (Lesson 9)

// src/tracing.ts
import { Langfuse } from "langfuse";

export const langfuse = new Langfuse({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
  secretKey: process.env.LANGFUSE_SECRET_KEY!
});

export function createTrace(name: string, userId: string) {
  return langfuse.trace({ name, userId });
}

Step 6: Entry Point

// src/index.ts
import { researchAgent } from "./agent";

async function main() {
  const topic = process.argv[2] || "AI Agent trends 2025";
  console.log(`\nπŸš€ Researching: "${topic}"\n`);

  const report = await researchAgent(topic);

  console.log("\n" + "=".repeat(60));
  console.log("πŸ“„ RESEARCH REPORT");
  console.log("=".repeat(60));
  console.log(report);
}

main().catch(console.error);

Run It

npx tsx src/index.ts "Comparing Vercel AI SDK vs LangChain for production agents"

Concepts Applied

LessonConceptUsed In Project
00Agent basicsAgent loop, tools, environment
01FrameworksVercel AI SDK (TypeScript)
03Tool UsesearchWeb, saveNote tools
04Agentic RAGrecallNotes + semantic search
05TrustworthyError handling, rate limiting
06PlanningResearchPlanSchema, sub-questions
08MetacognitionQualityCheckSchema, self-improvement
09ProductionLangfuse tracing
11Context Eng.Scratchpad pattern (findings)
12MemorySupabase pgvector memory system

Next Steps

From here, you can extend:

  • Multi-agent: Add writer agent, editor agent (Lesson 07)
  • MCP server: Create an MCP server for the agent (Lesson 10)
  • Web UI: Create a chat interface with Next.js + Vercel AI SDK
  • Deploy: Deploy to Vercel or Cloudflare Workers

Congratulations! You’ve completed the AI Agents series! πŸŽ‰