Bölüm 2: İlk AI Agent'ınızı Oluşturma: LangChain ile Pratik Rehber

Çoğu AI agent eğitimi işin karmaşık taraflarını es geçer. İşte LangChain, tRPC ve PostgreSQL ile çalışan bir agent'ı nasıl geliştirdiğim —yolda yaptığım hatalar da dahil.

Bölüm 2: İlk AI Agent'ınızı Oluşturma: LangChain ile Pratik Rehber
Feng LiuFeng Liu
19 Aralık 2025

AI ajanlarıyla ilgili o büyük heyecan (hype) gerçek. Herkes düşünebilen, plan yapabilen ve görevleri yerine getirebilen otonom sistemlerden bahsediyor. Ama kimsenin size söylemediği bir şey var: çoğu eğitim (tutorial) size sadece "mutlu yolu" (happy path) gösteriyor ve işlerin patladığı kısımları atlıyor.

Geçen hafta, sıfırdan bir AI agent (ajan) inşa etmek için iki gün harcadım. Öyle oyuncak bir örnek değil; bir blog platformunu yöneten, kullanıcılar oluşturan, yazılar yazan ve gerçekten çalışan bir sistem. Size tam olarak bunu nasıl yaptığımı, ilk denemede çalışmayan kısımlar da dahil olmak üzere göstereceğim.

Tam kaynak kodu: github.com/giftedunicorn/my-ai-agent

Aslında Ne İnşa Ediyoruz?

Soyut örnekleri unutun. Şunları yapabilen bir ajan inşa ediyoruz:

  • PostgreSQL veritabanında kullanıcı oluşturup yöneten
  • İsteğe bağlı olarak blog yazıları üreten
  • Araçları (tools) kullanırken sohbet edebilen
  • Konuşma geçmişini hafızasında tutan
  • Gerçekten deploy edilen (sadece localhost demosu olmayan)

Kullandığımız stack: Next.js, tRPC, Drizzle ORM, LangChain ve Google'ın Gemini'si. Moda olduğu için değil; tip güvenli (type-safe), hızlı ve prodüksiyonda gerçekten çalıştığı için.

Mimari (Sandığınızdan Daha Basit)

Beni şaşırtan şey şu oldu: AI ajanları o kadar da karmaşık değil. Özünde sadece şunlardan ibaretler:

  1. Fonksiyon çağırabilen bir LLM
  2. LLM'in kullanabileceği bir araç seti (tools)
  3. Bu araçları çalıştıran bir döngü
  4. Bağlamı (context) korumak için hafıza

Hepsi bu. Karmaşıklık, bu parçaların güvenilir bir şekilde birlikte çalışmasını sağlamaktan geliyor.

Veritabanı Şeması

Önce temel. Kullanıcılar, yazılar ve mesajlar için tablolara ihtiyacımız var:

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

Öyle abartılı bir şey yok. PostgreSQL ile temiz, ilişkisel veriler. Message tablosu konuşma geçmişini saklıyor; bu, istekler arasındaki bağlamı korumak için hayati önem taşıyor.

Araçları İnşa Etmek (Sihrin Gerçekleştiği Yer)

Çoğu rehberin muğlaklaştığı yer burası. "Sadece birkaç araç oluşturun" deyip geçerler. Size bunun gerçekte neye benzediğini göstereyim.

Araçlar, yapay zekanızın çağırabileceği fonksiyonlardır. LangChain'in DynamicStructuredTool yapısı ile şunları tanımlarsınız:

  1. Aracın ne yaptığı (açıklama/description)
  2. Hangi girdilere ihtiyaç duyduğu (Zod şeması ile)
  3. Gerçekte neyi çalıştırdığı (fonksiyon)

İşte kullanıcı oluşturmak için yazdığım araç:

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

Buradaki açıklama (description) sandığınızdan çok daha önemli. LLM, bu aracı ne zaman çağıracağına karar vermek için bu açıklamayı kullanır. Ne zaman kullanılacağı konusunda net olun.

Dönüş değeri mi? LLM'in gördüğü şey budur. Ben ilgili tüm detayları içeren yapılandırılmış bir metin döndürüyorum - ID'ler, isimler, onay mesajı. Bu, LLM'in kullanıcılara daha iyi yanıtlar vermesine yardımcı oluyor.

Ajan: Parçaları Birleştirmek

İşin ilginçleştiği yer burası. Yeni LangChain API'si (v1.2+) her şeyi basitleştirdi:

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

Hepsi bu. ChatPromptTemplate yok, AgentExecutor yok, karmaşık zincirler (chains) yok. Sadece createAgent ve invoke.

Sistem Promptu (Ajanınızın Kişiliği)

Ajanınıza nasıl davranması gerektiğini öğrettiğiniz yer burası:

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

Bunu zor yoldan öğrendim: Açık ve net olun. Ajana tam olarak ne yapacağını, nasıl yanıt vereceğini ve hangi detayları dahil edeceğini söyleyin. Muğlak promptlar, muğlak davranışlara yol açar.

Konuşma Geçmişini Yönetmek

Çoğu örnek bunu atlar ama iyi bir kullanıcı deneyimi için bu kritiktir. Ben şöyle hallediyorum:

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

Basit ama etkili. Ajan artık son 10 etkileşimi hatırlıyor. Bağlamı korumak için yeterli, kafasının karışmasına veya maliyetin artmasına neden olacak kadar fazla değil.

İşin Kirli Kısımları (Neler Gerçekten Bozuldu)

Döngüsel Bağımlılıklar (Circular Dependencies): İlk denemem başarısız oldu çünkü agent.ts, appRouter'ı import ediyordu, o da agentRouter'ı import ediyordu ve bir döngü oluşuyordu. Çözüm? Sadece araçlar için ihtiyacınız olan router'ları içeren geçici bir router'ı inline (satır içi) olarak oluşturmak.

Araç Yanıtını Ayıklama: LangChain'in yanıt formatı v1.2'de değişti. Sonuç artık result.output içinde değil, result.messages[result.messages.length - 1].content içinde geliyor. Bunu çözmek bir saatimi aldı.

Tip Güvenliği (Type Safety): Aracın func parametresi açık bir tiplemeye ihtiyaç duyuyor. Sadece destructuring yapamazsınız; önce input'u cast etmeniz gerekiyor. Burada TypeScript size otomatik olarak yardım etmeyecek.

Kendi Ajanınızı Kurmak

İşte gerçekten ihtiyacınız olanlar:

  1. Bağımlılıkları yükleyin:
pnpm add @langchain/core @langchain/google-genai langchain drizzle-orm
  1. Ortam değişkenleri (Environment variables):
POSTGRES_URL="your-database-url"  # Vercel Postgres, Supabase veya yerel PostgreSQL deneyin
GOOGLE_GENERATIVE_AI_API_KEY="your-gemini-key"  # https://aistudio.google.com/app/apikey adresinden alın
  1. Veritabanı kurulumu:
pnpm db:push  # Şemadan tabloları oluşturur
  1. İnşa etmeye başlayın:
  • Veritabanı şemanızı tanımlayın
  • CRUD işlemleri için tRPC prosedürleri oluşturun
  • Bu prosedürleri kapsayan LangChain araçları (tools) yazın
  • Araçlarınızla ajanı oluşturun
  • Bunu frontend'inize bağlayın

Neyi Farklı Yapardım?

Yarın baştan başlayacak olsam:

Daha az araçla başlardım. Başlangıçta 7 araç inşa ettim. Önce 3-4 temel araçla başlayın. Onları mükemmel şekilde çalıştırın, sonra genişletin.

Araçları bağımsız test edin. Araçlarınızı test etmek için ajanın bitmesini beklemeyin. Önce test verileriyle doğrudan çağırarak deneyin.

Araç kullanımını izleyin (Monitor). Ajanın hangi araçları neden çağırdığını görmek için loglama ekledim. Bu, araç açıklamalarımın (description) iyileştirilmesi gerektiğini ortaya çıkardı.

Streaming (Akış) kullanın. Şu anda kullanıcılar tam yanıtı bekliyor. Streaming, süre aynı olsa bile sistemin daha hızlı hissedilmesini sağlardı.

Gerçeklerle Yüzleşme

AI ajanları inşa etmek sihir değil ama çocuk oyuncağı da değil. Asıl yapay zeka kısmından çok şunlara zaman harcayacaksınız:

  • Araç tasarımı (her araç ne yapmalı?)
  • Prompt mühendisliği (ajanın doğru davranmasını nasıl sağlarım?)
  • Hata yönetimi (veritabanı çökerse ne olur? LLM halüsinasyon görürse ne olur?)
  • Tip güvenliği (dinamik LLM yanıtlarıyla TypeScript'i mutlu etmek)

Kendiniz Deneyin

Bu rehberdeki kodlar gerçek; bu yazıyı yazarken inşa ettim. Şunları yapabilirsiniz:

  • Test edin: "create 3 mock users" (3 sahte kullanıcı oluştur)
  • Deneyin: "create 2 blog posts for user 1" (1. kullanıcı için 2 blog yazısı oluştur)
  • Sorun: "how many users do we have?" (kaç kullanıcımız var?)

Ajan, hangi araçları çağıracağına karar vererek, bunları çalıştırarak ve sohbet havasında yanıt vererek tüm bunları hallediyor.

Sırada Ne Var?

Bu sadece temel. Buradan şunları yapabilirsiniz:

  • Kimlik doğrulama eklemek (kim neyi oluşturabilir?)
  • Streaming yanıtları uygulamak
  • Daha karmaşık araçlar eklemek (arama, analitik, entegrasyonlar)
  • Bir geri bildirim döngüsü kurmak (araç çağrısı başarılı oldu mu?)
  • Rate limiting (hız sınırlaması) eklemek (kullanıcıların 10.000 yazı oluşturmasına izin vermeyin)

Ama basit başlayın. On tane vasat araç eklemeden önce, bir tanesini adamakıllı çalıştırın.

En iyi kısmı ne biliyor musunuz? Bu kalıbı (araçlar + LLM + hafıza) bir kez anladığınızda, her şey için ajanlar inşa edebilirsiniz. Veritabanı yönetimi, müşteri desteği, içerik üretimi, aklınıza ne gelirse.

Zor olan kısım kod değil. Gerçek problemleri çözen araçlar tasarlamak.


Kaynaklar:

Bunu paylaş

Feng Liu

Feng Liu

shenjian8628@gmail.com

Bölüm 2: İlk AI Agent'ınızı Oluşturma: LangChain ile Pratik Rehber | Feng Liu