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.

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:
- En LLM som kan anropa funktioner
- En uppsÀttning verktyg som LLM:en kan anvÀnda
- En loop som exekverar dessa verktyg
- 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:
- Vad verktyget gör (beskrivning)
- Vilken input det behöver (schema med Zod)
- 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:
- Installera beroenden:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
- 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
- Databas-setup:
pnpm db:push # Skapar tabeller frÄn schemat
- 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:
- Full kÀllkod: github.com/giftedunicorn/my-ai-agent
- Byggt med Create T3 Turbo
- LangChain Docs: js.langchain.com
- HĂ€mta Gemini API-nyckel: aistudio.google.com
Dela detta

Feng Liu
shenjian8628@gmail.com