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

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash

ในบางแอปพลิเคชัน อาจไม่เป็นไปได้ที่จะสอบถาม LLM เมื่อผู้ใช้ร้องขอ กระบวนการส่งข้อความพร้อมท์ไปยัง API และรอการตอบสนองอาจใช้เวลานานเป็นพิเศษ สำหรับงานที่ซับซ้อนมากขึ้นที่เกี่ยวข้องกับ LangChain เช่น การประมวลผลข้อมูลจาก PDF หรือไฟล์เสียงและป้อนไปยัง LLM ความล่าช้าจะกลายเป็นปัญหาใหญ่ยิ่งขึ้นต่อประสบการณ์ผู้ใช้

ขณะสตรีมมิ่ง คือ วิธีแก้ปัญหาที่เหมาะสมในหลายกรณี การทำทุกอย่าง ณ จุดใดจุดหนึ่งก่อนที่ผู้ใช้จะเข้าชมแอปพลิเคชันของคุณอาจสะดวกกว่า ด้วยวิธีนี้ ผู้ใช้จะได้รับการตอบสนองที่แคชไว้เกือบจะในทันที โดยไม่ต้องรอให้ LLM สร้างมันขึ้นมา สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับแอปพลิเคชันที่ใช้อินพุตเดียวกันสำหรับผู้ใช้ทุกคน เช่น หนังสือหรือสรุปข่าว แต่หากไม่มีอินพุตจากผู้ใช้ เราจะให้ LangChain แคชการตอบกลับได้อย่างไร

เราจำเป็นต้องมีไมโครเซอร์วิสที่สามารถเรียกใช้ได้ตามความต้องการ สามารถส่งข้อความพร้อมต์ไปยัง LangChain และแคชการตอบสนองโดยใช้ Upstash Redis นอกจากนี้เรายังจะใช้ SDK การจำกัดอัตราของ Upstash เพื่อให้แน่ใจว่าเราจะไม่ใช้เกินขีดจำกัดอัตราของเราโดยไม่ได้ตั้งใจ QStash เป็นวิธีที่หลากหลายที่สุดในการเรียกไมโครเซอร์วิสของเรา เราสามารถตั้งค่าให้ตอบสนองแคชได้ทุกเมื่อที่ต้องการ แม้กระทั่งส่งงาน cron หากแอปพลิเคชันของเราต้องการ นอกจากนี้ยังมีแดชบอร์ดที่เราสามารถใช้เพื่อตรวจสอบการใช้งานไมโครเซอร์วิสของเรา ในกรณีที่จุดสิ้นสุดของเราโอเวอร์โหลดหรือมีอย่างอื่นผิดพลาด QStash จะลองคำขอ HTTP อีกครั้งและตรวจสอบให้แน่ใจว่าข้อความของเราถูกส่งออกไป

ไมโครเซอร์วิสของเราจะถูกสร้างขึ้นโดยใช้ Hono.js ซึ่งเป็นเฟรมเวิร์กเว็บที่มีน้ำหนักเบาและรวดเร็วสำหรับ Edge ในการสาธิตนี้ เราจะใช้ Cloudflare Worker เพื่อโฮสต์ไมโครเซอร์วิสของเรา อย่างไรก็ตาม ต้องขอบคุณ Upstash ที่ทำให้สามารถติดตั้งใช้งานได้ทุกที่ รวมถึงรันไทม์ Edge/เซิร์ฟเวอร์ไร้เซิร์ฟเวอร์อื่นๆ ด้วย

คุณสามารถค้นหาซอร์สโค้ดแบบเต็มสำหรับการสาธิตนี้ได้ที่นี่

ข้อกำหนดเบื้องต้น

  • ฐานข้อมูล Upstash Redis
  • ตัวแปรสภาพแวดล้อม QStash
  • คีย์ OpenAI API

เริ่มต้นใช้งาน

การสร้างโครงการ

ไม่เหมือนกับ 02 อื่นๆ อีกมากมาย แพ็คเกจ npm 10 ต้องการให้คุณอยู่ในไดเร็กทอรีว่าง ขั้นแรก สร้างไดเร็กทอรีว่างใหม่สำหรับโปรเจ็กต์ของคุณแล้วไปที่ไดเร็กทอรี:

mkdir langchain-qstash
cd langchain-qstash

ด้วยการเปิดตัว v1.0.0 ล่าสุด Bun จะถูกใช้เพื่อสนับสนุนโปรเจ็กต์นี้ อย่างไรก็ตาม สามารถใช้ 24 ได้เช่นกัน ด้วย 33 , 48 และ 52 . อย่าลืมเลือก 67 เมื่อได้รับแจ้งสำหรับเทมเพลต คุณอาจเห็น 78 เป็นตัวเลือกแยกต่างหาก แต่นี่ไม่ใช่เทมเพลตที่ใช้ในการสาธิต

bun create hono@latest

การติดตั้งการอ้างอิง

ตอนนี้คุณสามารถรันคำสั่งต่อไปนี้กับตัวจัดการแพ็คเกจที่คุณเลือกเพื่อติดตั้งการพึ่งพาที่เหลือ:

bun install @upstash/qstash @upstash/ratelimit @upstash/redis langchain openai

การกำหนดค่าโครงการ

ณ เวลาที่เขียน 87 รวม lockfiles ไว้ใน 99 โดยค่าเริ่มต้น แม้ว่าจะไม่ได้กำหนดไว้อย่างเคร่งครัด แต่คุณสามารถอัปเดต 100 ได้ เพื่อแยกไฟล์ล็อคดังนี้:

node_modules
dist
.wrangler
.dev.vars
wrangler.toml

ตอนนี้ เราสามารถตั้งค่าตัวแปรสภาพแวดล้อมจากข้อกำหนดเบื้องต้นได้ สามารถต่อท้าย 113 ของคุณได้ ซึ่งควรแยกออกจากการควบคุมแหล่งที่มาแล้ว

[vars]
QSTASH_CURRENT_SIGNING_KEY="sig_********"
QSTASH_NEXT_SIGNING_KEY="sig_********"
UPSTASH_REDIS_REST_URL="https://********.upstash.io"
UPSTASH_REDIS_REST_TOKEN="********"
OPENAI_API_KEY="sk-********"

สุดท้าย แก้ไข 127 ของคุณ เพื่อเพิ่มการพิมพ์สำหรับตัวแปรสภาพแวดล้อมจากก่อนหน้านี้:

type Bindings = {
 QSTASH_CURRENT_SIGNING_KEY: string;
 QSTASH_NEXT_SIGNING_KEY: string;
 UPSTASH_REDIS_REST_URL: string;
 UPSTASH_REDIS_REST_TOKEN: string;
 OPENAI_API_KEY: string;
};
 
const app = new Hono<{ Bindings: Bindings }>();

ประโยชน์ของการมีคีย์การเซ็นชื่อสองคีย์มาจากการที่คุณสามารถม้วนคีย์ได้ครั้งเดียวโดยไม่ต้องอัปเดตตัวแปรสภาพแวดล้อมของคุณ เนื่องจาก QStash จะพยายามและใช้คีย์การเซ็นชื่อถัดไปโดยอัตโนมัติหากคีย์ปัจจุบันล้มเหลว

การพัฒนา

ในขั้นตอนนี้ 137 ควรจะสามารถอ่านตัวแปรสภาพแวดล้อมของคุณและปรับใช้โปรเจ็กต์ของคุณได้ ในการทำเช่นนั้น คุณสามารถรันคำสั่งต่อไปนี้กับตัวจัดการแพ็คเกจที่คุณต้องการ:

bun run dev

149 จะเปิดเซิร์ฟเวอร์ Hono.js ของคุณและมอบ URL ในเครื่องที่พอร์ต 8787 เพื่อทดสอบโปรเจ็กต์ของคุณ หากคุณต้องการเริ่มเซสชันแสดงตัวอย่าง Edge โดยตรงแทนที่จะทดสอบในเครื่อง ให้แก้ไข dev 151 ของคุณ สคริปต์ดังต่อไปนี้:

"dev": "wrangler dev src/index.ts --remote",

หากต้องการดูการกำหนดค่าและบันทึกของ Cloudflare Worker คุณจะต้องทำให้พนักงานใช้งานได้ก่อน:

bun run deploy

สิ่งนี้จะแนะนำคุณตลอดขั้นตอนการลงชื่อเข้าใช้ Cloudflare ซึ่งจะทำให้โปรเจ็กต์ของคุณใช้งานได้เป็นครั้งแรกโดยอัตโนมัติหลังจากการตรวจสอบสิทธิ์

การสร้างมิดเดิลแวร์

เพื่อวัตถุประสงค์ในการแก้ไขจุดบกพร่อง จะเป็นประโยชน์ในการบันทึกคำขอที่เราได้รับจาก QStash สิ่งเหล่านี้จะปรากฏในแดชบอร์ดสำหรับ Cloudflare Worker ของเราใต้แท็บ "บันทึก" เมื่อคุณกด 160 . Hono.js มีมิดเดิลแวร์ตัวบันทึกที่เราสามารถเพิ่มลงในเราเตอร์ของเราได้:

import { Hono } from "hono";
import { logger } from "hono/logger";
 
// snip
 
const app = new Hono<{ Bindings: Bindings }>();
app.use("*", logger());
 
// snip

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash

การเชื่อมต่อ QStash กับ Cloudflare Workers

ก่อนที่เราจะเริ่มพัฒนาจุดสิ้นสุด API โดยใช้ Hono.js เราจำเป็นต้องมีวิธีสกัดกั้นข้อความที่ส่งโดย QStash และละทิ้งคำขอหากมีลายเซ็นที่ไม่ถูกต้อง โชคดีที่ Hono.js มีวิธีการนำมิดเดิลแวร์ที่เรากำหนดเองไปใช้ ซึ่งจะทำงานก่อนตัวจัดการเสมอ มีความแข็งแกร่งเพียงพอที่เราสามารถจัดระเบียบมิดเดิลแวร์ของเราเป็นไฟล์แยกกันตามที่เห็นสมควร

ในมิดเดิลแวร์ของเรา เราสามารถใช้ประโยชน์จากตัวรับของ QStash ได้ มาสร้างไฟล์ใหม่ชื่อ 175 และส่งออกฟังก์ชันที่พิมพ์ด้วย 189 :

import { Receiver } from "@upstash/qstash";
import { type MiddlewareHandler } from "hono";
 
declare global {
 interface Response {
 locals: {
 query: string;
 };
 }
}
 
export const verify: MiddlewareHandler = async (ctx, next) => {
 const receiver = new Receiver({
 currentSigningKey: ctx.env.QSTASH_CURRENT_SIGNING_KEY,
 nextSigningKey: ctx.env.QSTASH_NEXT_SIGNING_KEY,
 });
};

Hono.js ผ่าน 192 (บริบท) วัตถุไปยังมิดเดิลแวร์และตัวจัดการแต่ละตัว สิ่งนี้ไม่เหมือนกับ 206 ของ Cloudflare Workers โดยตรง -แต่ก็มีข้อมูลเดียวกัน 210 ของ Hono วัตถุเกือบจะเทียบเท่ากับวัตถุคำขอ สภาพแวดล้อม และบริบทการดำเนินการที่ส่งไปยัง Cloudflare Workers เริ่มต้น 229 ตัวจัดการทั้งหมดรวมกันเป็นวัตถุเดียว

นอกจากนี้เรายังแก้ไข 237 ทั่วโลกด้วย อินเทอร์เฟซเพื่อรวม 243 ที่กำหนดเอง . ต่างจาก Express ตรงที่ Hono ไม่ได้สร้าง 254 วัตถุตามค่าเริ่มต้น เราจะใช้สิ่งนี้เพื่อส่งข้อความค้นหาไปยังตัวจัดการในภายหลัง ต่อไป เราเข้าถึงตัวแปรสภาพแวดล้อมที่พิมพ์จากรุ่นก่อนหน้าเพื่อสร้างตัวรับของเรา

ตอนนี้เราสามารถใช้ผู้รับเพื่อตรวจสอบลายเซ็นของคำขอ:

// snip
 
const body = await ctx.req.text();
ctx.res.locals = {
 query: JSON.parse(body).query,
};
 
const isValid = await receiver
 .verify({
 signature: ctx.req.headers.get("Upstash-Signature")!,
 body,
 })
 .catch((err) => {
 console.error(err);
 return false;
 });
 
if (!isValid) {
 return new Response("Invalid signature", { status: 401 });
}
 
await next();

อันดับแรก เราได้รับออบเจ็กต์คำขอจากบริบท Hono.js ใช้เฉพาะ Web Standard API อย่างสะดวกสบาย เช่น 264 , 274 , 285 และ 292 . แม้ว่าเราจะใช้งานบน Cloudflare Workers ในการสาธิตนี้ แต่ก็ทำให้สามารถทำงานในสภาพแวดล้อมอื่นๆ นับไม่ถ้วน รวมถึงสภาพแวดล้อม Edge/เซิร์ฟเวอร์แบบไร้เซิร์ฟเวอร์

จากออบเจ็กต์คำขอ เราจะอ่านเนื้อหาของคำขอเป็นข้อความและ 301 ส่วนหัวซึ่งมี JWT ที่กำหนดเอง เราสามารถใช้ผู้รับเพื่อตรวจสอบลายเซ็นที่มีอยู่ใน JWT โดยส่งลายเซ็นและเนื้อความของคำขอไป ใน 312 ผู้จัดการ เราต้องแน่ใจว่าได้บันทึกข้อผิดพลาดและส่งคืน 328 เพื่อระบุว่าลายเซ็นไม่ถูกต้อง

เนื่องจากเรากำลังใช้เนื้อความของคำขอ เราจึงไม่สามารถเข้าถึงได้ในตัวจัดการจริงของเราในภายหลัง เนื่องจากเนื้อหาเป็น 331 ที่สามารถบริโภคได้เพียงครั้งเดียวเท่านั้น แต่หากต้องการส่งข้อความค้นหาไปยังตัวจัดการ เราสามารถเพิ่มลงใน 340 ได้ คัดค้านการตอบสนอง สุดท้าย หากลายเซ็นไม่ถูกต้อง เราจะส่งคืนคำตอบ 401 ที่ไม่ได้รับอนุญาต มิฉะนั้น เราจะเรียก 354 เพื่อดำเนินการต่อไปยังมิดเดิลแวร์หรือตัวจัดการถัดไป

การเพิ่มการจำกัดอัตรา

เนื่องจากเรากำลังจะเชื่อมต่อตำแหน่งข้อมูลนี้กับคีย์ OpenAI API ส่วนตัวของเรา สิ่งสำคัญคือต้องแน่ใจว่าเราจะไม่เกินขีดจำกัดอัตราของเราโดยไม่ตั้งใจ โชคดีที่ Upstash มี SDK การจำกัดอัตราที่เราสามารถใช้เพื่อเพิ่มการจำกัดอัตราให้กับตำแหน่งข้อมูลของเราได้อย่างง่ายดาย หากจำนวนคำขอเกินเกณฑ์ที่กำหนด เราจะส่งคืนคำตอบ 429 คำขอมากเกินไป

เริ่มต้นด้วยการสร้างมิดเดิลแวร์ใหม่ใน 364 :

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis/cloudflare";
import { type MiddlewareHandler } from "hono";
 
export const ratelimit: MiddlewareHandler = async (ctx, next) => {
 const redis = new Redis({
 url: ctx.env.UPSTASH_REDIS_REST_URL,
 token: ctx.env.UPSTASH_REDIS_REST_TOKEN,
 });
 
 const ratelimit = new Ratelimit({
 redis,
 limiter: Ratelimit.slidingWindow(10, "10 s"),
 analytics: true,
 });
 
 await next();
};

ที่นี่ เรากำลังเชื่อมต่อฐานข้อมูล Upstash Redis ของเรากับ Rate Limiting SDK เราต้องสร้าง 375 อินสแตนซ์ด้วยตนเองโดยใช้ตัวแปรสภาพแวดล้อมจาก 383 เนื่องจาก 398 ไม่สามารถอ่านได้โดยอัตโนมัติเมื่อใช้ Cloudflare Workers เมื่อ 401 เป็นจริง SDK จะเรียก Redis โดยอัตโนมัติเพื่อเก็บแคชการเรียกสำหรับตัวระบุแต่ละตัว มันใช้ 410 เป็นคำนำหน้าตามค่าเริ่มต้น

SDK ยังสนับสนุนการใช้แคชชั่วคราวที่ใช้ 426 แทน Redis ซึ่งสามารถประหยัดเวลาและทรัพยากรภายใต้ภาระงานที่รุนแรง:

const cache = new Map(); // outside of the middleware handler
 
// snip
 
const ratelimit = new Ratelimit({
 ephemeralCache: cache,
 // snip
});

นอกจากนี้เรายังมี 433 อีกด้วย ไปยัง SDK นี่คือฟังก์ชันที่บอก SDK ถึงวิธีให้คะแนนคำขอจำกัด เรากำลังใช้ 443 ตัวจำกัด กำหนดค่าให้อนุญาตได้สูงสุด 10 คำขอทุกๆ 10 วินาที

SDK ยังช่วยให้เราระบุตัวระบุสำหรับคำขอแต่ละรายการได้ และ SDK จะติดตามจำนวนคำขอที่สร้างขึ้นภายในระยะเวลาที่กำหนดโดยอัตโนมัติ ในแอปพลิเคชันในโลกแห่งความเป็นจริง เราสามารถใช้ที่อยู่ IP ของผู้ใช้เป็นตัวระบุได้ แต่เพื่อการสาธิตนี้ เราจะใช้สตริงคงที่เพื่อจำกัดคำขอทั้งหมดด้วยขีดจำกัดอัตราเดียว:

// snip
 
const identifier = "openai";
const { success } = await ratelimit.limit(identifier);
 
if (!success) {
 return new Response("Too many requests", { status: 429 });
}
 
await next();
// snip

หากคำขอมีการจำกัดอัตรา เราจะส่งคืนคำตอบ 429 Too Many Requests มิฉะนั้น เราจะเรียก 457 เพื่อดำเนินการต่อไปยังมิดเดิลแวร์หรือตัวจัดการถัดไป ในกรณีที่เราเชื่อมต่อฐานข้อมูล Redis หลายฐานข้อมูลเข้ากับ SDK ก็จะต้องทำการซิงโครไนซ์ระหว่างฐานข้อมูลเหล่านั้น สิ่งนี้จะนำไปสู่สัญญาที่ห้อยต่องแต่งกับ Vercel Edge และ Cloudflare Workers ซึ่งเราสามารถดูแลได้ดังนี้:

const { pending, success } = await ratelimit.limit(identifier);
 
ctx.event.waitUntil(pending);

วิธีการทำเช่นนี้อาจแตกต่างกันไปขึ้นอยู่กับไลบรารีที่คุณใช้ เนื่องจาก Hono ใช้ Web Standard API เราจึงสามารถใช้ 463 ได้ วิธีการรอให้คำมั่นสัญญาคลี่คลาย อย่างไรก็ตาม สำหรับการสาธิตนี้ เราจะใช้ฐานข้อมูล Redis เดียวเท่านั้น ดังนั้นเราจึงไม่ต้องกังวลกับสัญญาที่ห้อยต่องแต่ง

การรับข้อความจาก QStash

เมื่อเราส่งคำขอ HTTP ไปยัง QStash ปลายทางที่เราระบุจะเป็น URL ของอุปกรณ์ปลายทาง Cloudflare Workers ของเรา เราสามารถใช้ Hono.js เพื่อสร้างตัวจัดการสำหรับปลายทางนี้ได้ มาเพิ่มอันใหม่ใน 474 ในขณะที่เปิดใช้งานมิดเดิลแวร์ของเราจากก่อนหน้า:

// snip
import { ratelimit } from "./middleware/ratelimit";
import { verify } from "./middleware/verify";
 
// snip
 
app.post("/api/announce", ratelimit, verify, async (ctx) => {});
 
// snip

ในตัวจัดการนี้ เราสามารถใช้ LangChain เพื่อสร้างการตอบสนองต่อพรอมต์ที่กำหนด โดยใช้ Upstash Redis เพื่อแคชผลลัพธ์:

// snip
 
import { Redis } from "@upstash/redis/cloudflare";
import { UpstashRedisCache } from "langchain/cache/upstash_redis";
import { OpenAI } from "langchain/llms/openai";
 
// snip
 
app.post("/api/announce", ratelimit, verify, async (ctx) => {
 const redis = new Redis({
 url: ctx.env.UPSTASH_REDIS_REST_URL,
 token: ctx.env.UPSTASH_REDIS_REST_TOKEN,
 });
 
 const cache = new UpstashRedisCache({ client: redis });
 const model = new OpenAI({
 cache,
 openAIApiKey: ctx.env.OPENAI_API_KEY,
 });
 
 const query = ctx.res.locals.query;
 const result = await model
 .call(query)
 .then((result) => {
 console.log(result);
 return result;
 })
 .catch((err) => console.error(err));
 
 return new Response(result ?? "", { status: 200 });
});
 
// snip

ที่นี่ เรานำเข้าคลาสที่จำเป็นในการตั้งค่าแคชสำหรับ LangChain จากนั้น เราสร้างเชนโดยใช้โมเดล OpenAI และแคช Upstash Redis โดยส่งอินสแตนซ์ Upstash Redis ไปยังแคช ขอย้ำอีกครั้ง เราจะต้องส่งคีย์ OpenAI API ไปยังโมเดลด้วยตนเอง เนื่องจากไม่สามารถอ่านได้โดยอัตโนมัติบน Cloudflare Workers

จากนั้น เราจะเข้าถึงคำค้นหาที่บันทึกไว้ก่อนหน้านี้ใน 486 ใช้เพื่อเรียกโมเดลและบันทึกผลลัพธ์ สุดท้ายนี้ เราจะส่งคืนผลลัพธ์เป็นการตอบกลับ หรือเป็นสตริงว่างหากมีข้อผิดพลาดเกิดขึ้น ในขั้นตอนนี้ เราสามารถทดสอบตำแหน่งข้อมูลของเราได้โดยการส่งคำขอ POST ไปที่จุดนั้นและตรวจสอบการตอบสนอง ขั้นแรก ให้รัน 498 อีกครั้ง สคริปต์ใน package.json ของคุณ:

bun run deploy

หลังจากที่คุณดึงข้อมูล URL ของอุปกรณ์ปลายทาง Cloudflare Workers แล้ว คุณสามารถส่งคำขอ POST ไปยังอุปกรณ์ดังกล่าวได้โดยใช้ 502 :

curl -XPOST \
 "https://qstash.upstash.io/v2/publish/https://<YOUR_API_URL>.workers.dev/api/announce" \
 -H "Authorization: Bearer <YOUR_QSTASH_TOKEN>" \
 -H "Content-Type: application/json" \
 -d "{ \"query\": \"What's the derivative of e^x?\" }"

Upstash มีคอนโซล QStash ที่คุณสามารถใช้เพื่อส่งคำขอเหล่านี้ได้ง่ายขึ้น มันง่ายมากที่จะให้ QStash ทำงาน cron เพื่อรันคำขอของเราซ้ำๆ QStash ส่งเนื้อหาของคำขอตามที่เป็นอยู่ไปยังตำแหน่งข้อมูลของเรา เรากำลังส่งเพย์โหลด JSON ต่อไปนี้ โดยที่ 515 คือพรอมต์สำหรับโมเดล:

{
 "query": "What's the derivative of e^x?"
}

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash

บทสรุป

SDK การจำกัดอัตราแคชจำนวนการโทรต่อตัวระบุสำเร็จแล้ว:

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash

ในทำนองเดียวกัน เนื้อหาที่สร้างขึ้นจาก LangChain ได้รับการแคชในฐานข้อมูล Upstash Redis ของเราสำเร็จแล้ว:

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash

และสุดท้าย การตอบสนองจะถูกบันทึกไว้ในบันทึกของ Cloudflare Worker ของเรา:

เพิ่มประสิทธิภาพ:การแคช LLM ตามความต้องการด้วย LangChain, Redis และ QStash