Del 2: Bygg din första AI-agent: En praktisk guide med LangChain

De flesta tutorials om AI-agenter skippar de stökiga detaljerna. HĂ€r visar jag hur jag byggde en fungerande agent med LangChain, tRPC och PostgreSQL – inklusive misstagen jag gjorde lĂ€ngs vĂ€gen.

Del 2: Bygg din första AI-agent: En praktisk guide med LangChain
Feng LiuFeng Liu
19 december 2025

Hypen kring AI-agenter Àr verklig. Alla pratar om autonoma system som kan tÀnka, planera och utföra uppgifter. Men hÀr Àr grejen ingen berÀttar för dig: de flesta tutorials visar bara "the happy path" och hoppar över delarna dÀr saker gÄr sönder.

Förra veckan spenderade jag tvĂ„ dagar pĂ„ att bygga en AI-agent frĂ„n grunden. Inte ett leksaksexempel – utan en riktig en som hanterar en bloggplattform, skapar anvĂ€ndare, skriver inlĂ€gg och faktiskt fungerar. Jag tĂ€nker visa exakt hur jag gjorde det, inklusive de delar som inte fungerade vid första försöket.

Hela koden: github.com/giftedunicorn/my-ai-agent

Vad vi faktiskt bygger

Glöm de abstrakta exemplen. Vi bygger en agent som:

  • Skapar och hanterar anvĂ€ndare i en PostgreSQL-databas
  • Genererar blogginlĂ€gg pĂ„ begĂ€ran
  • Svarar i konversationsform samtidigt som den anvĂ€nder verktyg
  • BehĂ„ller konversationshistorik
  • Faktiskt deployas (inte bara localhost-demos)

Stacken: Next.js, tRPC, Drizzle ORM, LangChain och Googles Gemini. Inte för att det Ă€r trendigt – utan för att det Ă€r typsĂ€kert, snabbt och faktiskt fungerar i produktion.

Arkitekturen (Enklare Àn du tror)

HÀr Àr vad som förvÄnade mig: AI-agenter Àr inte sÄ komplicerade. I grund och botten Àr de bara:

  1. En LLM som kan anropa funktioner
  2. En uppsÀttning verktyg som LLM:en kan anvÀnda
  3. En loop som exekverar dessa verktyg
  4. Minne för att behÄlla kontext

Det Àr allt. Komplexiteten ligger i att fÄ dessa delar att fungera pÄlitligt tillsammans.

Databasschemat

Först, grunden. Vi behöver tabeller för anvÀndare, inlÀgg och meddelanden:

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(),
}));

Inget mĂ€rkvĂ€rdigt. Bara ren, relationell data med PostgreSQL. Message-tabellen lagrar konversationshistorik – vilket Ă€r avgörande för att behĂ„lla kontexten mellan förfrĂ„gningar.

Bygga verktygen (DĂ€r magin sker)

Det Àr hÀr de flesta tutorials blir luddiga. "Skapa bara nÄgra verktyg," sÀger de. LÄt mig visa hur det faktiskt ser ut.

Verktyg Àr funktioner din AI kan anropa. Med LangChains DynamicStructuredTool definierar du:

  1. Vad verktyget gör (beskrivning)
  2. Vilken input det behöver (schema med Zod)
  3. Vad det faktiskt exekverar (funktion)

HÀr Àr verktyget för att skapa anvÀndare:

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})`;
  },
});

Beskrivningen Àr viktigare Àn du tror. LLM:en anvÀnder den för att avgöra nÀr detta verktyg ska anropas. Var specifik med nÀr det ska anvÀndas.

ReturvĂ€rdet? Det Ă€r vad LLM:en ser. Jag returnerar strukturerad text med alla relevanta detaljer – ID:n, namn, bekrĂ€ftelse. Detta hjĂ€lper LLM:en att ge bĂ€ttre svar till anvĂ€ndarna.

Agenten: Att sÀtta ihop allt

HÀr blir det intressant. Det nya LangChain API:et (v1.2+) förenklade allt:

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,
});

Det Àr allt. Ingen ChatPromptTemplate, ingen AgentExecutor, inga komplexa kedjor. Bara createAgent och invoke.

System-prompten (Din agents personlighet)

Det Àr hÀr du lÀr din agent hur den ska bete sig:

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.`;

Jag lÀrde mig detta den hÄrda vÀgen: var tydlig. Tala om för agenten exakt vad den ska göra, hur den ska svara och vilka detaljer den ska inkludera. Vaga prompter leder till vagt beteende.

Hantera konversationshistorik

De flesta exempel hoppar över detta, men det Àr kritiskt för en bra anvÀndarupplevelse. SÄ hÀr hanterar jag det:

// 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 },
];

Enkelt, men effektivt. Agenten kommer nu ihÄg de senaste 10 utbytena. TillrÀckligt för kontext, men inte sÄ mycket att den blir förvirrad eller dyr i drift.

De stökiga delarna (Det som faktiskt gick sönder)

CirkulÀra beroenden: Mitt första försök misslyckades eftersom agent.ts importerade appRouter, som importerade agentRouter, vilket skapade ett cirkulÀrt beroende. Lösningen? Skapa en temporÀr router inline med bara de routrar du behöver för verktygen.

Extrahering av verktygssvar: LangChains svarsformat Àndrades i v1.2. Resultatet ligger nu i result.messages[result.messages.length - 1].content, inte result.output. Det tog mig en timme att lista ut.

TypsĂ€kerhet: Verktygets func-parameter behöver explicit typning. Du kan inte bara destrukturera – du mĂ„ste casta input först. TypeScript kommer inte att hjĂ€lpa dig hĂ€r.

SĂ€tt upp din egen

HÀr Àr vad du faktiskt behöver:

  1. Installera beroenden:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
  1. Miljövariabler:
POSTGRES_URL="your-database-url"  # Testa Vercel Postgres, Supabase eller lokal PostgreSQL
GOOGLE_GENERATIVE_AI_API_KEY="your-gemini-key"  # HÀmta frÄn https://aistudio.google.com/app/apikey
  1. Databas-setup:
pnpm db:push  # Skapar tabeller frÄn schemat
  1. Börja bygga:
  • Definiera ditt databasschema
  • Skapa tRPC-procedurer för CRUD-operationer
  • Bygg LangChain-verktyg som wrappar dessa procedurer
  • Skapa agenten med dina verktyg
  • Koppla ihop det med din frontend

Vad jag skulle gjort annorlunda

Om jag började om imorgon:

Börja med fÀrre verktyg. Jag byggde 7 verktyg initialt. HÄll dig till 3-4 kÀrnverktyg först. FÄ dem att fungera perfekt, expandera sedan.

Testa verktyg oberoende. VÀnta inte tills agenten Àr byggd för att testa dina verktyg. Anropa dem direkt med testdata först.

Övervaka verktygsanvĂ€ndning. Jag lade till loggning för att se vilka verktyg agenten anropar och varför. Detta avslöjade att mina verktygsbeskrivningar behövde förbĂ€ttras.

AnvÀnd streaming. Just nu vÀntar anvÀndarna pÄ det fullstÀndiga svaret. Streaming skulle fÄ det att kÀnnas snabbare, Àven om det tar lika lÄng tid.

Verklighetskollen

Att bygga AI-agenter Àr inte magi, men det Àr inte trivialt heller. Du kommer att spendera mer tid pÄ:

  • Verktygsdesign (vad ska varje verktyg göra?)
  • Prompt engineering (hur fĂ„r jag agenten att bete sig korrekt?)
  • Felhantering (vad hĂ€nder om databasen Ă€r nere? vad hĂ€nder om LLM:en hallucinerar?)
  • TypsĂ€kerhet (att göra TypeScript nöjd med dynamiska LLM-svar)

Än pĂ„ sjĂ€lva AI-delen.

Testa sjÀlv

Koden för den hĂ€r guiden Ă€r pĂ„ riktigt – jag byggde den medan jag skrev detta. Du kan:

  • Testa den med: "create 3 mock users"
  • Prova: "create 2 blog posts for user 1"
  • FrĂ„ga: "how many users do we have?"

Agenten hanterar allt detta genom att besluta vilka verktyg som ska anropas, exekvera dem och svara i konversationsform.

Vad hÀnder hÀrnÀst

Detta Àr bara grunden. HÀrifrÄn kan du:

  • LĂ€gga till autentisering (vem fĂ„r skapa vad?)
  • Implementera strömmande svar (streaming)
  • LĂ€gga till mer komplexa verktyg (sök, analys, integrationer)
  • Bygga en feedback-loop (lyckades verktygsanropet?)
  • LĂ€gga till rate limiting (lĂ„t inte anvĂ€ndare skapa 10 000 inlĂ€gg)

Men börja enkelt. FÄ ett verktyg att fungera bra innan du lÀgger till tio mediokra.

Det bĂ€sta? NĂ€r du vĂ€l förstĂ„r detta mönster – verktyg + LLM + minne – kan du bygga agenter för vad som helst. Databashantering, kundsupport, innehĂ„llsgenerering, vad som helst.

Den svÄra delen Àr inte koden. Det Àr att designa verktyg som faktiskt löser riktiga problem.


Resurser:

Dela detta

Feng Liu

Feng Liu

shenjian8628@gmail.com

Del 2: Bygg din första AI-agent: En praktisk guide med LangChain | Feng Liu