Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

AI에 대한 접근성이 높아짐에 따라 Replicate와 같은 회사에서는 기계 학습 모델을 프로젝트에 원활하게 통합하는 것이 더 쉬워졌습니다.

이 기사에서는 사용자가 이미지를 업로드하고 AI 생성 텍스트 캡션을 받을 수 있는 웹 애플리케이션인 CaptionAI를 구축한 방법에 대해 설명하겠습니다. 저는 이 Vercel 템플릿을 사용하여 이 프로젝트를 구축했습니다. 이 프로젝트가 구축된 방법을 설명하는 이 비디오도 있습니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

우리가 사용할 것

  • Next.js 13(프런트엔드 및 백엔드)
  • Upstash Redis(속도 제한)
  • 복제(머신러닝 API)
  • Tailwind CSS(스타일링)
  • Vercel(배포)

필요한 것

  • 데이터베이스 생성을 위한 Upstash 계정
  • Machine Learning API에 액세스하기 위한 복제 계정

Upstash Redis 설정

Upstash 계정을 생성하고 로그인하면 Redis 탭으로 이동하여 데이터베이스를 생성하게 됩니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

데이터베이스를 생성한 후 세부정보 탭으로 이동합니다. REST API 섹션을 찾을 때까지 아래로 스크롤하고 .env 버튼을 선택합니다. 콘텐츠를 복사하여 안전한 곳에 저장하세요.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

복제 설정

복제 계정을 만들고 로그인하면 계정 탭으로 이동하여 API 토큰을 안전한 곳에 저장하게 됩니다.

*참고:Replicate를 무료로 사용할 수 있지만 잠시 후 신용 카드를 입력하라는 메시지가 표시됩니다. 가격은 사용하는 모델에 따라 다릅니다. 우리가 사용하고 있는 모델인 Salesforce/blip의 실행 비용은 약 $0.00042입니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

프로젝트 설정

프로젝트를 처음부터 만드는 대신 GitHub에서 저장소를 복제할 수 있습니다.

저장소를 복제한 후에는 .env 파일을 생성하게 됩니다. .example.env 파일의 정보를 .env 파일에 복사합니다. 복사한 후에는 위 섹션에서 저장한 항목을 추가하게 됩니다.

다음과 같아야 합니다:

// .env
 
REPLICATE_API_KEY="your_replicate_api_key_from_above"
 
// Optional, if you're doing rate limiting
UPSTASH_REDIS_REST_URL="your_upstash_redis_rest__url_from_above"
UPSTASH_REDIS_REST_TOKEN="your_upstash_redis_rest__token_from_above"

이 정보를 포함하고 나면 터미널에 다음 명령을 입력하여 프로젝트를 실행할 수 있습니다:

npm install
npm run dev

저장소 구조

이는 프로젝트의 기본 폴더 구조입니다. 이미지 업로드, 속도 제한 및 BLIP ML API 구현을 다루는 이 게시물에서 추가로 논의될 파일에 빨간색 원을 표시했습니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

대규모 데이터 흐름

이는 데이터 흐름 방식을 보여주는 상위 수준 다이어그램입니다. 사용자가 업로드한 이미지인 입력은 Upload Component를 거쳐 BLIP ML API 처리를 위한 백엔드로 이동한 다음 UI에 응답 텍스트를 표시합니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

Redis 인스턴스 생성

프로젝트 내에서 필요할 때 프로젝트 전체에서 참조할 수 있는 upstash Redis 클라이언트를 설정할 예정입니다.

// `/utils/redis.ts`
 
import { Redis } from "@upstash/redis";
 
const redis =
 !!process.env.UPSTASH_REDIS_REST_URL && !!process.env.UPSTASH_REDIS_REST_TOKEN
 ? new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL,
 token: process.env.UPSTASH_REDIS_REST_TOKEN,
 })
 : undefined;
 
export default redis;

이 코드 조각은 "@upstash/redis" 패키지에서 Redis 모듈을 가져오고 새 Redis 인스턴스를 생성합니다. 인스턴스는 UPSTASH_REDIS_REST_URL 및 UPSTASH_REDIS_REST_TOKEN이라는 두 가지 환경 변수가 있는지에 따라 조건부로 생성됩니다.

두 변수가 모두 정의되면 지정된 URL과 토큰을 사용하여 새 Redis 인스턴스가 생성됩니다. 변수 중 하나 또는 둘 다 정의되지 않은 경우 redis 변수는 정의되지 않음으로 설정됩니다. 마지막으로 redis 변수는 애플리케이션의 다른 부분에서 사용하기 위해 모듈에서 내보내집니다.

이미지 업로드

// `/pages/captions.tsx`
 
const uploader = Uploader({
 apiKey: !!process.env.NEXT_PUBLIC_UPLOAD_API_KEY
 ? process.env.NEXT_PUBLIC_UPLOAD_API_KEY
 : "free",
});
 
const options = {
 maxFileCount: 1,
 mimeTypes: ["image/jpeg", "image/png", "image/jpg"],
 editor: { images: { crop: false } },
 styles: {
 colors: {
 primary: "#5a5cd1", // Primary buttons & links
 error: "#d23f4d", // Error messages
 shade100: "#fff", // Standard text
 shade200: "#fffe", // Secondary button text
 shade300: "#fffd", // Secondary button text (hover)
 shade400: "#fffc", // Welcome text
 shade500: "#fff9", // Modal close button
 shade600: "#fff7", // Border
 shade700: "#fff2", // Progress indicator background
 shade800: "#fff1", // File item background
 shade900: "#ffff", // Various (draggable crop buttons, etc.)
 },
 },
 onValidate: async (file: File): Promise<undefined | string> => {
 let isSafe = false;
 try {
 isSafe = await NSFWPredictor.isSafeImg(file);
 if (!isSafe) va.track("NSFW Image blocked");
 } catch (error) {
 console.error("NSFW predictor threw an error", error);
 }
 return isSafe
 ? undefined
 : "Detected a NSFW image which is not allowed. If this was a mistake, please contact me at hosna.qasmei@gmail.com";
 },
};

이 코드는 업로더 구성요소에 대한 구성 옵션을 설정합니다. 업로더는 Uploader() 함수를 사용하여 생성되며 옵션은 개체로 전달됩니다.

첫 번째 구성 옵션은 업로더 서비스를 인증하는 데 사용되는 apiKey입니다. apiKey 값은 환경 변수 NEXT_PUBLIC_UPLOAD_API_KEY 설정 여부에 따라 결정됩니다. 설정되면 환경변수 값이 사용되고, 설정되지 않으면 "free" 값이 사용됩니다.

옵션 개체에는 업로더에 대한 다양한 옵션이 포함되어 있습니다. 여기에는 다음이 포함됩니다:

  • maxFileCount:한 번에 업로드할 수 있는 최대 파일 수를 1로 설정합니다.
  • mimeTypes:업로드된 파일에 허용되는 MIME 유형을 'image/jpeg', 'image/png' 및 'image/jpg'로 설정합니다.
  • editor:이미지 편집기에 대한 옵션을 구성합니다. 이 경우 자르기를 false로 설정하면 비활성화됩니다.
  • 스타일:업로더 UI에 대한 맞춤 스타일을 정의합니다.
  • onValidate:각 파일을 업로드하기 전에 유효성을 검사하기 위해 호출되는 함수를 정의합니다. 이 경우 함수는 NSFWPredictor를 사용하여 이미지가 작업에 안전한지 확인합니다. 이미지가 안전하지 않으면 이미지가 허용되지 않는다는 오류 메시지가 반환됩니다.
// `/pages/captions.tsx` continued
 
const Home: NextPage = () => {
 const [originalPhoto, setOriginalPhoto] = useState<string | null>(null);
 const [caption, setCaption] = useState<string | null>(null);
 const [buttonText, setButtonText] = useState("Copy");
 const [loading, setLoading] = useState<boolean>(false);
 const [error, setError] = useState<string | null>(null);
 
 const copyToClipboard = () => {
 navigator.clipboard.writeText(caption!);
 
 setButtonText("Copied!"); // set the button text to "Copied!" when text is copied
 setTimeout(() => {
 setButtonText("Copy"); // set the button text back to "Copy" after 2 seconds
 }, 2000);
 };
 
 const UploadDropZone = () => (
 <UploadDropzone
 uploader={uploader}
 options={options}
 onUpdate={(file) => {
 if (file.length !== 0) {
 setOriginalPhoto(file[0].fileUrl.replace("raw", "thumbnail"));
 generateCaption(file[0].fileUrl.replace("raw", "thumbnail"));
 }
 }}
 width="670px"
 height="250px"
 />
 );
 
 async function generateCaption( fileUrl: string )
 {
 await new Promise((resolve) => setTimeout(resolve, 500));
 setLoading(true);
 const res = await fetch("/api/generate", {
 method: "POST",
 headers: {
 "Content-Type": "application/json",
 },
 body: JSON.stringify({ imageUrl: fileUrl }),
 });
 
 let newCaption = await res.json();
 if (res.status !== 200) {
 setError(newCaption);
 } else {
 setCaption(newCaption);
 }
 setLoading(false);
 }
 
 ...
 

useState 후크로 정의된 여러 상태 변수가 있습니다.

  • originalPhoto 업로드된 이미지의 URL을 나타내는 문자열입니다.
  • caption 업로드된 이미지에 대해 생성된 캡션이 포함된 문자열입니다.
  • buttonText 복사 버튼의 텍스트를 나타내는 문자열입니다.
  • loading 구성요소가 현재 데이터를 가져오고 있는지 여부를 나타내는 부울입니다.
  • error 캡션 생성 과정에서 오류가 발생한 경우 오류 메시지가 포함된 문자열입니다.

구성 요소에는 navigator.clipboard.writeText 메서드를 사용하여 캡션 변수를 클립보드에 복사하는 copyToClipboard라는 함수가 있습니다. 텍스트가 복사되면 ButtonText 변수가 "Copied!"로 변경됩니다. 2초 동안 기다린 후 "복사"로 재설정하세요.

지정된 업로더 및 옵션을 사용하여 UploadDropzone 구성 요소의 인스턴스를 렌더링하는 UploadDropZone이라는 하위 구성 요소도 있습니다. onUpdate 콜백은 업로드된 이미지의 URL과 생성된 캡션으로 OriginalPhoto 및 캡션 변수를 업데이트하는 데 사용됩니다.

마지막으로, 업로드된 이미지의 URL인 fileUrl 매개변수를 사용하는 generateCaption이라는 비동기 함수가 있습니다. 가져오기를 사용하여 POST 요청으로 /api/generate 엔드포인트를 호출하고 fileUrl을 JSON 페이로드로 전달합니다. 그런 다음 응답은 JSON으로 구문 분석되고 응답 성공 여부에 따라 캡션 또는 오류 변수를 설정합니다. 요청이 아직 진행 중인지 여부를 나타내기 위해 로딩 변수도 업데이트됩니다. 이 함수에는 API 속도 제한에 도달하지 않도록 setTimeout 함수를 사용하는 500ms 지연도 포함되어 있습니다.

속도 제한

// `/pages/api/generate.ts`
 
import redis from "../../utils/redis";
import requestIp from "request-ip";
 
import { Ratelimit } from "@upstash/ratelimit";
import type { NextApiRequest, NextApiResponse } from "next";
 
type Data = string;
interface ExtendedNextApiRequest extends NextApiRequest {
 body: {
 imageUrl: string;
 };
}
 
// Create a new ratelimiter, that allows 3 requests every 15 minutes
const ratelimit = redis
 ? new Ratelimit({
 redis: redis,
 limiter: Ratelimit.fixedWindow(5, "1440 m"),
 analytics: true,
 })
 : undefined;
 
 ...

이 코드는 Next.js에서 API 엔드포인트를 생성하는 데 필요한 모듈과 유형은 물론 Upstash Redis 데이터베이스 클라이언트와 "@upstash/ratelimit"라는 속도 제한기 라이브러리를 가져옵니다.

ratelimit 상수는 1440분(24시간)마다 5개의 요청을 허용하는 고정 창 ratelimiter를 생성하는 Ratelimit 클래스의 새 인스턴스를 생성합니다. redis 속성은 애플리케이션의 여러 인스턴스에 걸쳐 속도 제한을 활성화하기 위해 Ratelimit 생성자에 매개 변수로 전달됩니다. redis가 정의되지 않은 경우(예:Redis 데이터베이스가 구성되지 않은 경우) ratelimit도 정의되지 않음으로 설정됩니다. 즉, Redis를 사용할 수 없으면 속도 제한이 적용되지 않습니다.

// `/pages/api/generate.ts` continued
 
export default async function handler(
 req: ExtendedNextApiRequest,
 res: NextApiResponse<Data>
) {
 // Rate Limiter Code
 if (ratelimit) {
 const identifier = requestIp.getClientIp(req);
 const result = await ratelimit.limit(identifier!);
 res.setHeader("X-RateLimit-Limit", result.limit);
 res.setHeader("X-RateLimit-Remaining", result.remaining);
 
 if (!result.success) {
 res
 .status(429)
 .json("Too many uploads in 1 day. Please try again after 24 hours.");
 return;
 }
 }
 
 ...

이 코드 블록은 클라이언트가 API에 요청할 수 있는 속도를 제한하는 API 핸들러 함수의 일부입니다. 먼저 속도 제한기 인스턴스를 사용할 수 있는지 확인하고, 그렇다면 request-ip 패키지를 사용하여 클라이언트의 IP 주소를 추출하여 ratelimit.limit 메서드에 전달합니다. 이 메소드는 지정된 기간 내에 남아 있는 요청 수와 요청 성공 여부를 포함하는 개체를 반환합니다.

요청이 성공하면 X-RateLimit-Limit 및 X-RateLimit-Remaining 헤더가 응답에 설정됩니다. 요청 제한을 초과한 경우 429 상태 코드와 오류 메시지가 응답으로 전송되며 함수는 더 이상 실행되지 않도록 조기에 반환됩니다.

BLIP ML API

// `/pages/api/generate.ts` continued
 
 const imageUrl = req.body.imageUrl;
 let startResponse = await fetch("https://api.replicate.com/v1/predictions", {
 method: "POST",
 headers: {
 "Content-Type": "application/json",
 Authorization: "Token " + process.env.REPLICATE_API_KEY,
 },
 body: JSON.stringify({
 version:
 "2e1dddc8621f72155f24cf2e0adbde548458d3cab9f00c0139eea840d0ac4746",
 input: {
 image: imageUrl,
 task: "image_captioning",
 },
 }),
 });
 
 ...
 

코드의 이 부분은 요청 본문에서 imageUrl을 가져와 "https://api.replicate.com/v1/predictions" 엔드포인트에 POST 요청을 보내 image_captioning 작업을 사용하여 이미지 캡션을 가져옵니다. 요청에는 인증을 위한 Replicate API 키가 포함된 Authorization 헤더가 포함되어 있으며 Content-Type 헤더는 "application/json"으로 설정됩니다. API의 응답은 JSON으로 구문 분석되고, jsonStartResponse 객체에서 엔드포인트Url이 추출됩니다.

사용하려는 모델을 선택하면 모델의 버전 번호를 확인할 수 있습니다.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

API 탭을 선택하세요.

Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

버전 번호가 나타날 때까지 아래로 스크롤하십시오(빨간색 윤곽선). 파란색 윤곽선은 사용할 수 있는 입력 매개변수입니다. Next.js, Replicate 및 Redis를 사용하여 AI 기반 이미지 캡션 앱 만들기

// `/pages/api/generate.ts` continued
 
 let jsonStartResponse = await startResponse.json();
 let endpointUrl = jsonStartResponse.urls.get;
 
 // GET request to get the status of the image restoration process & return the result when it's ready
 let caption: string | null = null;
 while (!caption) {
 // Loop in 1s intervals until the alt text is ready
 console.log("polling for result...");
 let finalResponse = await fetch(endpointUrl, {
 method: "GET",
 headers: {
 "Content-Type": "application/json",
 Authorization: "Token " + process.env.REPLICATE_API_KEY,
 },
 });
 let jsonFinalResponse = await finalResponse.json();
 
 if (jsonFinalResponse.status === "succeeded") {
 caption = jsonFinalResponse.output;
 } else if (jsonFinalResponse.status === "failed") {
 break;
 } else {
 await new Promise((resolve) => setTimeout(resolve, 1000));
 }
 }
 res.status(200).json(caption ? caption : "Failed to generate caption");
}

다음으로 캡션이 준비될 때까지 while 루프를 사용하여 1초 간격으로 엔드포인트Url을 폴링합니다. 루프는 동일한 Authorization 및 Content-Type 헤더를 사용하여 EndpointUrl에 GET 요청을 보내고 응답도 JSON으로 구문 분석됩니다. jsonFinalResponse 개체의 상태가 "성공"인 경우 출력 속성에서 캡션이 추출됩니다. 상태가 "실패"이면 루프가 종료됩니다. 상태가 "성공" 또는 "실패"가 아닌 경우 루프는 다시 폴링하기 전에 setTimeout 메소드를 사용하여 1초 동안 기다립니다.

마지막으로 캡션은 null이 아닌 경우 상태 코드 200과 함께 JSON 응답으로 반환됩니다. 그렇지 않으면 "캡션 생성 실패" 메시지가 포함된 응답이 상태 코드 200과 함께 반환됩니다.

결론

결론적으로, 이 프로젝트는 이미지 업로드 구현, 속도 제한 및 기계 학습 API 통합에 대한 귀중한 경험을 제공했습니다. 이 프로젝트를 성공적으로 완료함으로써 우리는 이러한 기술에 대해 더 잘 이해하고 향후 더 발전된 프로젝트를 만드는 데 이러한 기술을 활용할 수 있는 방법을 얻게 되었습니다.