Parte 2: Costruisci il tuo primo AI Agent: Guida pratica con LangChain

La maggior parte dei tutorial sugli agenti AI salta le parti difficili. Ecco come ho costruito un agente funzionante con LangChain, tRPC e PostgreSQL — inclusi gli errori commessi lungo il percorso.

Parte 2: Costruisci il tuo primo AI Agent: Guida pratica con LangChain
Feng LiuFeng Liu
19 dicembre 2025

L'hype sugli agenti AI è reale. Tutti parlano di sistemi autonomi in grado di pensare, pianificare ed eseguire compiti. Ma ecco quello che nessuno vi dice: la maggior parte dei tutorial vi mostra lo scenario ideale (il cosiddetto "happy path") e salta le parti in cui tutto si rompe.

La scorsa settimana ho trascorso due giorni a costruire un agente AI da zero. Non un esempio giocattolo, ma uno vero che gestisce una piattaforma blog, crea utenti, scrive post e funziona davvero. Vi mostrerò esattamente come ho fatto, incluse le parti che non hanno funzionato al primo colpo.

Codice completo: github.com/giftedunicorn/my-ai-agent

Cosa Stiamo Costruendo Davvero

Lasciate perdere gli esempi astratti. Stiamo costruendo un agente che:

  • Crea e gestisce utenti in un database PostgreSQL
  • Genera post per il blog su richiesta
  • Risponde in modo conversazionale mentre utilizza dei tool
  • Mantiene la cronologia della conversazione
  • Fa il deploy vero (non solo demo su localhost)

Lo stack: Next.js, tRPC, Drizzle ORM, LangChain e Gemini di Google. Non perché vada di moda, ma perché è type-safe, veloce e funziona davvero in produzione.

L'Architettura (Più Semplice di Quanto Pensi)

Ecco cosa mi ha sorpreso: gli agenti AI non sono così complicati. Al loro nucleo, sono semplicemente:

  1. Un LLM che può chiamare funzioni
  2. Un set di tool che l'LLM può utilizzare
  3. Un loop che esegue questi tool
  4. Memoria per mantenere il contesto

Tutto qui. La complessità deriva dal far lavorare insieme questi pezzi in modo affidabile.

Lo Schema del Database

Prima di tutto, le fondamenta. Abbiamo bisogno di tabelle per utenti, post e messaggi:

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

Niente di stravagante. Solo dati relazionali puliti con PostgreSQL. La tabella Message memorizza la cronologia delle conversazioni: cruciale per mantenere il contesto tra le richieste.

Costruire i Tool (Dove Avviene la Magia)

È qui che la maggior parte dei tutorial diventa vaga. "Crea solo alcuni tool", dicono. Lasciate che vi mostri come appare davvero.

I tool sono funzioni che la vostra AI può chiamare. Con DynamicStructuredTool di LangChain, definite:

  1. Cosa fa il tool (descrizione)
  2. Di quali input ha bisogno (schema con Zod)
  3. Cosa esegue effettivamente (funzione)

Ecco il tool per creare utenti:

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

La descrizione conta più di quanto pensiate. L'LLM la usa per decidere quando chiamare questo tool. Siate specifici su quando usarlo.

Il valore di ritorno? È quello che vede l'LLM. Io restituisco testo strutturato con tutti i dettagli rilevanti: ID, nomi, conferme. Questo aiuta l'LLM a dare risposte migliori agli utenti.

L'Agente: Mettiamo Tutto Insieme

Qui è dove la cosa si fa interessante. La nuova API di LangChain (v1.2+) ha semplificato tutto:

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

Fatto. Niente ChatPromptTemplate, niente AgentExecutor, niente catene complesse. Solo createAgent e invoke.

Il System Prompt (La Personalità del Tuo Agente)

È qui che insegnate al vostro agente come comportarsi:

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

L'ho imparato a mie spese: siate espliciti. Dite all'agente esattamente cosa fare, come rispondere e quali dettagli includere. Prompt vaghi portano a comportamenti vaghi.

Gestire la Cronologia della Conversazione

La maggior parte degli esempi salta questo passaggio, ma è fondamentale per una buona user experience. Ecco come lo gestisco io:

// Recupera gli ultimi 10 messaggi dal database
const history = await ctx.db
  .select()
  .from(Message)
  .orderBy(desc(Message.createdAt))
  .limit(10);

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

Semplice, ma efficace. L'agente ora ricorda gli ultimi 10 scambi. Abbastanza per il contesto, non così tanto da confondersi o diventare costoso.

Le Parti Complicate (Cosa Si È Rotto Davvero)

Dipendenze Circolari: Il mio primo tentativo è fallito perché agent.ts importava appRouter, che importava agentRouter, creando una dipendenza circolare. Soluzione? Creare un router temporaneo inline solo con i router necessari per i tool.

Estrazione della Risposta del Tool: Il formato di risposta di LangChain è cambiato nella v1.2. Il risultato ora è in result.messages[result.messages.length - 1].content, non in result.output. Mi ci è voluta un'ora per capirlo.

Type Safety: Il parametro func del tool necessita di una tipizzazione esplicita. Non potete semplicemente destrutturare: dovete prima fare il cast di input. TypeScript qui non vi aiuterà automaticamente.

Configura il Tuo

Ecco di cosa avete effettivamente bisogno:

  1. Installare le dipendenze:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
  1. Variabili d'ambiente:
POSTGRES_URL="your-database-url"  # Provate Vercel Postgres, Supabase o PostgreSQL locale
GOOGLE_GENERATIVE_AI_API_KEY="your-gemini-key"  # Ottenibile da https://aistudio.google.com/app/apikey
  1. Setup del database:
pnpm db:push  # Crea le tabelle dallo schema
  1. Iniziare a costruire:
  • Definite lo schema del database
  • Create procedure tRPC per le operazioni CRUD
  • Costruite i tool LangChain che avvolgono quelle procedure
  • Create l'agente con i vostri tool
  • Collegatelo al vostro frontend

Cosa Farei Diversamente

Se dovessi ricominciare domani:

Inizierei con meno tool. Inizialmente ne ho costruiti 7. Rimanete su 3-4 tool principali all'inizio. Fateli funzionare perfettamente, poi espandete.

Testerei i tool indipendentemente. Non aspettate che l'agente sia costruito per testare i vostri tool. Chiamateli direttamente con dati di test prima.

Monitorerei l'utilizzo dei tool. Ho aggiunto il logging per vedere quali tool chiama l'agente e perché. Questo ha rivelato che le descrizioni dei miei tool avevano bisogno di lavoro.

Userei lo streaming. Al momento, gli utenti aspettano la risposta completa. Lo streaming lo farebbe sembrare più veloce, anche se impiega lo stesso tempo.

Il Confronto con la Realtà

Costruire agenti AI non è magia, ma non è nemmeno banale. Passerete più tempo su:

  • Design dei tool (cosa dovrebbe fare ogni tool?)
  • Prompt engineering (come faccio a far comportare correttamente l'agente?)
  • Gestione degli errori (cosa succede se il database è giù? cosa succede se l'LLM ha allucinazioni?)
  • Type safety (rendere felice TypeScript con le risposte dinamiche dell'LLM)

Che sulla parte AI vera e propria.

Provatelo Voi Stessi

Il codice di questo tutorial è reale: l'ho costruito mentre scrivevo questo articolo. Potete:

  • Testarlo con: "create 3 mock users"
  • Provare: "create 2 blog posts for user 1"
  • Chiedere: "how many users do we have?"

L'agente gestisce tutto questo decidendo quali tool chiamare, eseguendoli e rispondendo in modo conversazionale.

E Adesso?

Questa è solo la base. Da qui, potreste:

  • Aggiungere l'autenticazione (chi può creare cosa?)
  • Implementare le risposte in streaming
  • Aggiungere tool più complessi (ricerca, analytics, integrazioni)
  • Costruire un loop di feedback (la chiamata al tool ha avuto successo?)
  • Aggiungere rate limiting (non lasciate che gli utenti creino 10.000 post)

Ma iniziate in modo semplice. Fate funzionare bene un tool prima di aggiungerne dieci mediocri.

La parte migliore? Una volta capito questo pattern - tool + LLM + memoria - potete costruire agenti per qualsiasi cosa. Gestione database, supporto clienti, generazione contenuti, qualsiasi cosa.

La parte difficile non è il codice. È progettare tool che risolvano davvero problemi reali.


Risorse:

Condividi questo

Feng Liu

Feng Liu

shenjian8628@gmail.com