Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

ในโพสต์นี้ ผมเล่าเกี่ยวกับการสร้าง Custom Content AI Chatbot แบบโอเพนซอร์ส ด้วย Upstash, Next.js, LangChain และ Fly.io Upstash ช่วย schedule model training, rate limiting ที่กว้างขวาง และ caching OpenAI API responses

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

สิ่งที่เราจะใช้

  • Next.js (Front-end และ Back-end)
  • LangChain (framework พัฒนาแอปขับเคลื่อนด้วย language models)
  • Upstash (Scheduling Training Model via QStash, Rate Limiting & Caching OpenAI Responses)
  • Tailwind CSS (Styling)
  • Fly.io (Deployment)

สิ่งที่คุณต้องการ

  • Node.js 18
  • บัญชี Upstash
  • บัญชี OpenAI (สำหรับ OpenAI API Key)

ตั้งค่า Upstash Redis

สร้างบัญชี Upstash login ไปแท็บ Redis สร้าง database

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

สร้างเสร็จ ไปแท็บ Details เลื่อนลงหา Connect your database คัดลอกเก็บไว้

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

เลื่อนลงหา REST API กด .env คัดลอกเก็บไว้

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

ตั้งค่า Upstash QStash

Login ไปแท็บ QStash ดึง QSTASH_URL, QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY คัดลอกเก็บไว้

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

ตั้งค่าโปรเจกต์

โคลน repo ติดตามคู่มือ:

git clone https://github.com/rishi-raj-jain/custom-content-ai-chatbot
cd custom-content-ai-chatbot
npm install

สร้าง .env ใส่ค่าจากข้างต้น:

# .env
# Upstash Redis Secrets
UPSTASH_REDIS_REST_URL="https://....upstash.io"
UPSTASH_REDIS_REST_TOKEN="..."
# Upstash QStash Secrets
QSTASH_URL="https://qstash.upstash.io/v1/publish/"
QSTASH_TOKEN="..."
QSTASH_CURRENT_SIGNING_KEY="sig_..."
QSTASH_NEXT_SIGNING_KEY="sig_..."
# OpenAI Key
OPENAI_API_KEY="sk-..."
# Admin Access Key
# ใช้ verify training request ว่าทำโดย admin เท่านั้น
ADMIN_KEY="..."

รัน local:

npm run dev

โครงสร้าง Repository

โครงสร้างโฟลเดอร์หลัก (ไฟล์สีแดงจะอธิบายต่อ):

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

High-Level Data Flow และ Operations

High-level diagram ของ data flow และ operations:

สร้าง Custom AI Chatbot ด้วย LangChain, Faiss และ Next.js – คู่มือปฏิบัติจริง

  • User ถาม chatbot → check IP rate limit → response จาก OpenAI (ถ้าไม่ cached) → cache → stream ให้ user
  • Admin request training บน URLs → Upstash QStash ส่ง POST request หลัง delay (10s) fetch content URLs update model (background)

Setup Chat และ Train API Routes ใน Next.js

Route pages/api/chat.js enable CORS, Rate Limit Chat API, Cache และ Stream Responses, expose method schedule content training, และ pages/api/train.js training บน URLs ใน background

1. Enable CORS

ใช้ cors package enable CORS ใช้ chatbot หลายที่ (bot บนเว็บไซต์) รัน cors setup ตอน API Route initialize:

// File: pages/api/chat.js
import { runMiddleware } from '@/lib/cors'
export default async function (req, res) {
  try {
    await runMiddleware(req, res)
    // ...
  } catch (e) { console.log(e.message || e.toString()) }
  return res.end()
}
// Cors Function
// File: lib/cors.js
import Cors from 'cors'
const cors = Cors({ methods: ['POST', 'OPTIONS', 'HEAD'] })
export function runMiddleware(req, res, fn = cors) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) return reject(result)
      return resolve(result)
    })
  })
}

2. Schedule Content Training Request(s) บน URLs ที่กำหนด

Upstash QStash สร้าง APIs แบบ fire-and-forget ไม่ต้องรอ main function จบ ส่ง background (optionally หลัง delay) เหมือน cron-job แต่รันต่อ request ไม่ใช่ scheduled intervals

ใน chat api route เดียวกัน รับ request ที่มี header admin-key match กับ ADMIN_KEY schedule content training บน URLs ใน body หลัง delay 10s ไป endpoint https://custom-content-ai-chatbot.fly.dev/api/train

// File: pages/api/chat.js
if (req.headers['admin-key'] === process.env.ADMIN_KEY) {
  if (!req.body.urls) return res.status(400).send('No urls to train on.')
  await qstashClient.publishJSON({
    delay: 10,
    body: { urls: req.body.urls },
    url: 'https://custom-content-ai-chatbot.fly.dev/api/train'
  })
  return res.status(200).end()
}

Train API route (pages/api/train.js):

// File: pages/api/train.js
import train from '@/lib/train'
import * as dotenv from 'dotenv'
import { redis } from '@/lib/redis'
import { runMiddleware } from '@/lib/cors'
import { verifySignature } from '@upstash/qstash/nextjs'
dotenv.config()

export const config = { api: { bodyParser: false } }

async function handler(req, res) {
  try {
    await runMiddleware(req, res)
    if (req.method !== 'POST') return res.status(403).send('No other methods allowed.')
    if (!req.body.urls) return res.status(400).send('No urls to train on.')
    await train(req.body.urls)
    
    // Clear cached responses ใน Upstash
    let allKeys = await redis.keys('*')
    if (allKeys) {
      allKeys = allKeys.filter(i => !i.includes('@upstash/ratelimit:'))
      const p = redis.pipeline()
      allKeys.forEach(i => p.del(i))
      await p.exec()
      console.log('Cleaned cached responses in Upstash.')
    }
    return res.status(200).end()
  } catch (e) { console.log(e.message || e.toString()) }
  return res.end()
}
export default verifySignature(handler)

3 critical actions:

  • Verify incoming request ด้วย QStash verifySignature (check Upstash-Signature header)
  • Call train function fetch URL content เพิ่ม vector store (และ save)
  • Clear cached responses ใน Upstash Redis หลัง filter rate limiter keys ผ่าน Redis Transactions

3. Rate Limiting

ใช้ Upstash Redis client และ @upstash/ratelimit library:

// File: lib/redis.js
import * as dotenv from 'dotenv'
import { Redis } from '@upstash/redis'
import { Ratelimit } from '@upstash/ratelimit'
dotenv.config()

export const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
})

export const ratelimit = {
  chat: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(30, '86400s'),
  }),
}

Rate Limiting ทำให้ service ฟรีและ public ได้! ใครก็ถาม 30 คำถาม/วัน ผ่าน website Enforce rate limit 30 questions/day ตาม IP address key

// File: pages/api/chat.js
import requestIp from 'request-ip'
import { ratelimit } from '@/lib/redis'

const detectedIp = requestIp.getClientIp(req)
if (!detectedIp) return res.status(400).send('Bad request.')
const result = await ratelimit.chat.limit(detectedIp)
if (!result.success) return res.status(400).send('Rate limit exceeded.')
// Continue serving chat responses

4. Load saved indexed vector store และถาม OpenAI หาคำตอบ

4.1: Retrieving Saved Vector Store

// File: pages/api/chat.js
import { loadVectorStore } from '@/lib/vectorStore'
const vectorStore = await loadVectorStore()

// Vector Store Function
// File: lib/vectorStore.js
import { join } from 'path'
import { existsSync } from 'fs'
import { Document } from 'langchain/document'
import { FaissStore } from 'langchain/vectorstores/faiss'
import { OpenAIEmbeddings } from 'langchain/embeddings/openai'

export async function loadVectorStore() {
  const directory = join(process.cwd(), 'loadedVectorStore')
  const docStoreJSON = join(process.cwd(), 'loadedVectorStore', 'docstore.json')
  if (existsSync(docStoreJSON)) {
    return await FaissStore.load(directory, new OpenAIEmbeddings())
  } else {
    return await FaissStore.fromDocuments([new Document({ pageContent: 'Hey' })], new OpenAIEmbeddings())
  }
}

4.2: เพิ่ม Prompt guidelines ให้ user queries

ใช้ PromptTemplate โดย LangChain ส่ง instructions ให้ AI ตอบอย่างไร:

// File: pages/api/chat.js
import { z } from 'zod'
import { PromptTemplate } from 'langchain/prompts'
import { RetrievalQAChain } from 'langchain/chains'
import { OutputFixingParser, StructuredOutputParser } from 'langchain/output_parsers'

const outputParser = StructuredOutputParser.fromZodSchema(
  z.object({
    answer: z.string().describe('answer to question in HTML friendly format, use all of the tags wherever possible and including reference links'),
  }),
)

const outputFixingParser = OutputFixingParser.fromLLM(model, outputParser)

const prompt = new PromptTemplate({
  template: `Answer the user's question as best and be as detailed as possible:\n{format_instructions}\n{query}`,
  inputVariables: ['query'],
  partialVariables: { format_instructions: outputFixingParser.getFormatInstructions() },
})

const chain = RetrievalQAChain.fromLLM(model, vectorStore.asRetriever(), prompt)

4.3: Stream และ Cache Responses

Cache responses ด้วย Upstash Redis ใช้ UpstashRedisCache ของ LangChain ส่ง Redis instance มีอยู่ ให้ caching handler ไป ChatOpenAI wrapper:

// File: pages/api/chat.js
import { redis } from '@/lib/redis'
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { UpstashRedisCache } from 'langchain/cache/upstash_redis'

const upstashRedisCache = new UpstashRedisCache({ client: redis })
let doesToken = false
const model = new ChatOpenAI({
  streaming: true,
  cache: upstashRedisCache,
  callbacks: [{
    handleLLMNewToken(token) {
      doesToken = true
      res.write(token)
    },
  }],
})

const chainOutput = await chain.call({ query: req.body.input })
if (!doesToken) return res.status(200).send(chainOutput.text)

ปรับใช้งานบน Fly.io

Repo มี setup Fly.io แล้ว (Dockerfile, fly.toml, .dockerignore)

Deploy ต้องมี Fly.io account สร้าง app:

fly launch

Deploy:

fly deploy

สรุป

โปรเจกต์นี้ให้ประสบการณ์มีค่าในการ implement OpenAI response caching, rate limiting, และ scheduled API requests เพื่อ train model พร้อมใช้ service ที่ scale ตามความต้องการ คือ Upstash

Next.js, Redis, TailwindCSS, LangChain, Serverless Scheduling