Deel 2: Je eerste AI Agent bouwen: Een praktische gids met LangChain

De meeste tutorials over AI-agents slaan de rommelige details over. Zo bouwde ik een werkende agent met LangChain, tRPC en PostgreSQL – inclusief de fouten die ik onderweg maakte.

Deel 2: Je eerste AI Agent bouwen: Een praktische gids met LangChain
Feng LiuFeng Liu
19 december 2025

De hype rond AI-agents is echt. Iedereen heeft het over autonome systemen die kunnen denken, plannen en taken kunnen uitvoeren. Maar dit is wat niemand je vertelt: de meeste tutorials laten je alleen het ideale scenario zien (de 'happy path') en slaan de momenten over waarop alles in de soep loopt.

Vorige week heb ik twee dagen besteed aan het bouwen van een AI-agent vanaf nul. Geen speelgoedvoorbeeld, maar een echte die een blogplatform beheert, gebruikers aanmaakt, posts schrijft en daadwerkelijk werkt. Ik ga je precies laten zien hoe ik het heb gedaan, inclusief de onderdelen die de eerste keer niet werkten.

Volledige code: github.com/giftedunicorn/my-ai-agent

Wat We Eigenlijk Bouwen

Vergeet de abstracte voorbeelden. We bouwen een agent die:

  • Gebruikers aanmaakt en beheert in een PostgreSQL-database
  • Blogposts genereert op verzoek
  • Conversationeel reageert terwijl hij tools gebruikt
  • De gespreksgeschiedenis bijhoudt
  • Daadwerkelijk deployed (niet alleen localhost demo's)

De tech stack: Next.js, tRPC, Drizzle ORM, LangChain, en Google's Gemini. Niet omdat het hip is, maar omdat het type-safe en snel is, en echt werkt in productie.

De Architectuur (Simpeler Dan Je Denkt)

Dit verbaasde me: AI-agents zijn niet zo ingewikkeld. In de kern zijn het gewoon:

  1. Een LLM die functies kan aanroepen
  2. Een set tools die de LLM kan gebruiken
  3. Een loop die deze tools uitvoert
  4. Geheugen om context te bewaren

Dat is het. De complexiteit zit hem in het betrouwbaar laten samenwerken van deze onderdelen.

Het Database Schema

Eerst de fundering. We hebben tabellen nodig voor gebruikers, posts en berichten:

export const User = pgTable("user", (t) => ({
  id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
  name: t.varchar({ length: 255 }).notNull(),
  email: t.varchar({ length: 255 }).notNull().unique(),
  bio: t.text(),
  createdAt: t.timestamp().defaultNow().notNull(),
  updatedAt: t.timestamp().defaultNow().notNull(),
}));

export const Post = pgTable("post", (t) => ({
  id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
  userId: t
    .integer()
    .notNull()
    .references(() => User.id, { onDelete: "cascade" }),
  title: t.varchar({ length: 500 }).notNull(),
  content: t.text().notNull(),
  published: t.boolean().default(false).notNull(),
  createdAt: t.timestamp().defaultNow().notNull(),
  updatedAt: t.timestamp().defaultNow().notNull(),
}));

Geen poespas. Gewoon schone, relationele data met PostgreSQL. De Message tabel slaat de gespreksgeschiedenis op – cruciaal voor het behouden van context tussen requests.

De Tools Bouwen (Waar de Magie Gebeurt)

Dit is waar de meeste tutorials vaag worden. "Maak gewoon wat tools aan," zeggen ze dan. Laat me je tonen hoe dat er daadwerkelijk uitziet.

Tools zijn functies die je AI kan aanroepen. Met LangChain's DynamicStructuredTool definieer je:

  1. Wat de tool doet (beschrijving)
  2. Welke input hij nodig heeft (schema met Zod)
  3. Wat hij daadwerkelijk uitvoert (functie)

Hier is de tool voor het aanmaken van gebruikers:

const createUserTool = new DynamicStructuredTool({
  name: "create_user",
  description:
    "Create a new user in the database. Use this when asked to add, create, or register a user.",
  schema: z.object({
    name: z.string().describe("The user's full name"),
    email: z.string().email().describe("The user's email address"),
    bio: z.string().optional().describe("Optional biography"),
  }),
  func: async (input) => {
    const { name, email, bio } = input as {
      name: string;
      email: string;
      bio?: string;
    };
    const user = await caller.user.create({ name, email, bio });
    return `Successfully created user: ${user.name} (ID: ${user.id}, Email: ${user.email})`;
  },
});

De beschrijving is belangrijker dan je zou denken. De LLM gebruikt deze om te beslissen wanneer hij deze tool moet aanroepen. Wees specifiek over het gebruik.

De return value? Dat is wat de LLM ziet. Ik stuur gestructureerde tekst terug met alle relevante details – ID's, namen, bevestiging. Dit helpt de LLM om betere antwoorden aan gebruikers te geven.

De Agent: Alles Samenvoegen

Hier wordt het interessant. De nieuwe LangChain API (v1.2+) heeft alles vereenvoudigd:

const agent = createAgent({
  model: new ChatGoogleGenerativeAI({
    apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
    model: "gemini-2.0-flash-exp",
    temperature: 0.7,
  }),
  tools: [...createUserTools(caller), ...createPostTools(caller)],
  systemPrompt: AGENT_SYSTEM_PROMPT,
});

const result = await agent.invoke({
  messages: conversationMessages,
});

Dat is alles. Geen ChatPromptTemplate, geen AgentExecutor, geen complexe chains. Gewoon createAgent en invoke.

De System Prompt (De Persoonlijkheid van Je Agent)

Dit is waar je je agent leert hoe hij zich moet gedragen:

const AGENT_SYSTEM_PROMPT = `You are an AI assistant that helps manage a blog platform.

You have access to tools for:
- User management (create, read, list, count)
- Post management (create, list)

When users ask you to perform actions:
1. Use the appropriate tools to complete the task
2. Be conversational and friendly
3. Provide clear confirmation with specific details
4. When creating mock data, use realistic names and content

Always confirm successful operations with relevant details.`;

Ik heb dit op de harde manier geleerd: wees expliciet. Vertel de agent precies wat hij moet doen, hoe hij moet reageren en welke details hij moet opnemen. Vage prompts leiden tot vaag gedrag.

Gespreksgeschiedenis Afhandelen

De meeste voorbeelden slaan dit over, maar het is cruciaal voor een goede gebruikerservaring. Zo pak ik het aan:

// Get last 10 messages from database
const history = await ctx.db
  .select()
  .from(Message)
  .orderBy(desc(Message.createdAt))
  .limit(10);

// Convert to LangChain format
const conversationMessages = [
  ...history.reverse().map((msg) => ({
    role: msg.role === "user" ? "user" : "assistant",
    content: msg.content,
  })),
  { role: "user", content: input.message },
];

Simpel, maar effectief. De agent onthoudt nu de laatste 10 uitwisselingen. Genoeg voor context, maar niet zoveel dat hij in de war raakt of het te duur wordt.

De Rommelige Delen (Wat Er Echt Kapot Ging)

Circulaire Afhankelijkheden: Mijn eerste poging mislukte omdat agent.ts appRouter importeerde, die weer agentRouter importeerde, wat zorgde voor een circulaire afhankelijkheid. De oplossing? Maak een tijdelijke router inline met alleen de routers die je nodig hebt voor de tools.

Tool Response Extractie: Het response-formaat van LangChain is veranderd in v1.2. Het resultaat zit nu in result.messages[result.messages.length - 1].content, niet in result.output. Het kostte me een uur om daar achter te komen.

Type Safety: De func parameter van de tool heeft expliciete typing nodig. Je kunt niet zomaar destructuren – je moet input eerst casten. TypeScript gaat je hier niet automatisch redden.

Je Eigen Agent Opzetten

Dit is wat je daadwerkelijk nodig hebt:

  1. Installeer dependencies:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
  1. Omgevingsvariabelen:
POSTGRES_URL="your-database-url"  # Probeer Vercel Postgres, Supabase, of lokale PostgreSQL
GOOGLE_GENERATIVE_AI_API_KEY="your-gemini-key"  # Haal op via https://aistudio.google.com/app/apikey
  1. Database setup:
pnpm db:push  # Maakt tabellen aan vanuit schema
  1. Begin met bouwen:
  • Definieer je database schema
  • Maak tRPC procedures voor CRUD-operaties
  • Bouw LangChain tools die deze procedures wrappen
  • Maak de agent aan met je tools
  • Koppel het aan je frontend

Wat Ik Anders Zou Doen

Als ik morgen opnieuw zou beginnen:

Begin met minder tools. Ik bouwde er in het begin 7. Hou het eerst bij 3-4 kerntools. Zorg dat die perfect werken en breid dan pas uit.

Test tools onafhankelijk. Wacht niet tot de agent gebouwd is om je tools te testen. Roep ze eerst direct aan met testdata.

Monitor tool-gebruik. Ik heb logging toegevoegd om te zien welke tools de agent aanroept en waarom. Dit onthulde dat mijn tool-beschrijvingen verbetering nodig hadden.

Gebruik streaming. Op dit moment wachten gebruikers op het volledige antwoord. Streaming zou het sneller laten voelen, zelfs als het even lang duurt.

De Reality Check

Het bouwen van AI-agents is geen magie, maar het is ook niet triviaal. Je zult meer tijd besteden aan:

  • Tool design (wat moet elke tool doen?)
  • Prompt engineering (hoe zorg ik dat de agent zich correct gedraagt?)
  • Foutafhandeling (wat als de database down is? wat als de LLM hallucineert?)
  • Type safety (TypeScript tevreden houden met dynamische LLM-responses)

Dan aan het eigenlijke AI-gedeelte.

Probeer Het Zelf

De code voor deze tutorial is echt – ik heb het gebouwd terwijl ik dit schreef. Je kunt:

  • Testen met: "create 3 mock users"
  • Proberen: "create 2 blog posts for user 1"
  • Vragen: "how many users do we have?"

De agent handelt dit allemaal af door te beslissen welke tools hij moet aanroepen, deze uit te voeren en conversationeel te reageren.

Wat Nu?

Dit is slechts de fundering. Vanaf hier zou je:

  • Authenticatie kunnen toevoegen (wie mag wat aanmaken?)
  • Streaming responses implementeren
  • Complexere tools toevoegen (zoeken, analytics, integraties)
  • Een feedback loop bouwen (is de tool-aanroep gelukt?)
  • Rate limiting toevoegen (laat gebruikers geen 10.000 posts aanmaken)

Maar begin simpel. Zorg dat één tool goed werkt voordat je tien middelmatige toevoegt.

Het beste deel? Zodra je dit patroon begrijpt – tools + LLM + geheugen – kun je agents bouwen voor alles. Databasebeheer, klantenservice, contentgeneratie, wat dan ook.

Het moeilijke deel is niet de code. Het is het ontwerpen van tools die daadwerkelijk echte problemen oplossen.


Bronnen:

Deel dit

Feng Liu

Feng Liu

shenjian8628@gmail.com

Deel 2: Je eerste AI Agent bouwen: Een praktische gids met LangChain | Feng Liu