AI Chatbot (ALPACO) API ๋ช ์ธ์
1. ๊ฐ์
๋ณธ ๋ฌธ์๋ ALPACO ์ฝ๋ฉ ํ ์คํธ ํ๋ซํผ ๋ด AI Chatbot ๊ธฐ๋ฅ์ MSA(Microservice Architecture)์ ๋ํ API ๋ช ์ธ ๋ฐ ๊ด๋ จ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ฑ๋ด์ ์ฌ์ฉ์์๊ฒ ํ๋ก๊ทธ๋๋ฐ ๋ฌธ์ ํด๊ฒฐ์ ๋ํ ํํธ, ๊ฐ๋ ์ค๋ช , ๋๋ฒ๊น ์ ๋ต ์ ์ ๋ฑ์ ์ํํ๋ฉฐ, ์ง์ ์ ์ธ ์ ๋ต์ด๋ ์ ์ฒด ์ฝ๋๋ ์ ๊ณตํ์ง ์์ต๋๋ค. ์๋ต์ Server-Sent Events (SSE)๋ฅผ ํตํด ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์์ผ๋ก ์ ๊ณต๋ฉ๋๋ค.
2. ์ํคํ ์ฒ
+-----------------+ +----------------------+ +-------------------------+ +-----------------------+ +------------------------+
| Frontend |----->| AWS CloudFront |----->| AWS Lambda Function URL |----->| AWS Lambda |----->| Google Generative AI |
| (Next.js/React) | | (OAC, Header Fwd) | | (IAM Auth, Streaming) | | (Node.js, Langchain) | | (Gemini Model) |
+-----------------+ +----------------------+ +-------------------------+ +-----------------------+ +------------------------+
^ |
| | (JWT Validation)
+------------------------------------------------------------------------------------------+
Amazon Cognito (User Pool)
- Frontend (ํด๋ผ์ด์ธํธ): ์ฌ์ฉ์์์ ์ธํฐ๋์ ์ ๋ด๋นํ๋ฉฐ, AWS Amplify๋ฅผ ํตํด Cognito๋ก๋ถํฐ JWT (ID Token)๋ฅผ ํ๋ํฉ๋๋ค.
- AWS CloudFront: ๊ณต๊ฐ ์๋ํฌ์ธํธ ์ญํ ์ ํ๋ฉฐ, Lambda ํจ์ URL์ ๋ณดํธํ๊ธฐ ์ํด OAC (Origin Access Control)๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์์ฒญ ์ SigV4 ์๋ช
์ ํตํด Lambda ํจ์ URL์ ํธ์ถํ๋ฉฐ, ํ์ํ ํค๋(
X-Custom-Auth-Token
,x-amz-content-sha256
๋ฑ)๋ฅผ ์ ๋ฌํฉ๋๋ค. - AWS Lambda Function URL: Lambda ํจ์๋ฅผ ์ํ HTTPS ์๋ํฌ์ธํธ๋ก,
AWS_IAM
์ธ์ฆ ๋ฐฉ์๊ณผRESPONSE_STREAM
ํธ์ถ ๋ชจ๋๋ก ์ค์ ๋์ด CloudFront OAC๋ฅผ ํตํ ์์ ํ ์คํธ๋ฆฌ๋ฐ ํธ์ถ์ ์ง์ํฉ๋๋ค. - AWS Lambda (
chatbot-query
):- Node.js ๋ฐํ์์์ ์คํ๋ฉ๋๋ค.
- CloudFront๋ก๋ถํฐ ์ ๋ฌ๋ฐ์
X-Custom-Auth-Token
ํค๋์ JWT๋ฅผ Cognito JWKS๋ฅผ ํตํด ๊ฒ์ฆํฉ๋๋ค. - Langchain.js์
@langchain/google-genai
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ Google Generative AI (Gemini ๋ชจ๋ธ)์ ์ํธ์์ฉํฉ๋๋ค. - LLM์ผ๋ก๋ถํฐ ๋ฐ์ ์๋ต์ SSE ์คํธ๋ฆผ ํํ๋ก CloudFront๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์ ์ ๋ฌํฉ๋๋ค.
- ํ์ํ ์์กด์ฑ์ AWS Lambda Layer๋ฅผ ํตํด ๊ด๋ฆฌ๋ฉ๋๋ค.
- Google Generative AI: ์ค์ ์์ฐ์ด ์ฒ๋ฆฌ ๋ฐ ์๋ต ์์ฑ์ ๋ด๋นํ๋ LLM ์๋น์ค์ ๋๋ค.
- Amazon Cognito: ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ JWT ๋ฐ๊ธ์ ๋ด๋นํฉ๋๋ค.
3. ์ฃผ์ ๊ธฐ์ ์คํ
- Backend:
- AWS Lambda (Runtime: Node.js 20.x)
- Langchain.js (
@langchain/core
,@langchain/google-genai
) - Google Generative AI (e.g., Gemini 2.0 Flash)
- Server-Sent Events (SSE)
jose
(JWT ๊ฒ์ฆ)
- Frontend (์ฐธ๊ณ ):
- TypeScript, Next.js/React
- AWS Amplify (Cognito ์ธ์ฆ ํ ํฐ ํ๋)
- Infrastructure & Deployment:
- Terraform
- AWS CloudFront (OAC ํฌํจ)
- AWS Lambda Function URL, Layers
- AWS IAM (Identity and Access Management)
- Authentication:
- Amazon Cognito (JWT ID Token)
4. API ์๋ํฌ์ธํธ ๋ช ์ธ
4.1. ์ฑ๋ด ์ง์ ๋ฐ ์คํธ๋ฆฌ๋ฐ ์๋ต
- Endpoint:
/
(CloudFront ๋ฐฐํฌ ๋๋ฉ์ธ ๋ฃจํธ) - Method:
POST
- URL:
https://{cloudfront_distribution_domain_name}
{cloudfront_distribution_domain_name}
์ Terraform ๋ฐฐํฌ ํ ์ถ๋ ฅ๋๋ ๊ฐ์ ๋๋ค. (์:d123abcdef890.cloudfront.net
)
- ์ธ์ฆ (Authentication):
- ์์ฒญ ํค๋์ Cognito ID Token์ ํฌํจํด์ผ ํฉ๋๋ค. (์์ธํ ๋ด์ฉ์ 5. ์ธ์ฆ ์ฐธ์กฐ)
- CloudFront OAC๋ฅผ ํตํด Lambda ํจ์ URL์ ํธ์ถํ๋ฏ๋ก, IAM SigV4 ์๋ช
์ ์ํด
x-amz-content-sha256
ํค๋๊ฐ ํ์ํฉ๋๋ค.
-
์์ฒญ ํค๋ (Request Headers):
ํค๋ ํ์ ํ์ ์ค๋ช Content-Type
string O application/json
X-Custom-Auth-Token
string O Bearer <COGNITO_ID_TOKEN>
(Cognito ์ฌ์ฉ์ ํ์์ ๋ฐ๊ธ๋ฐ์ ID ํ ํฐ)x-amz-content-sha256
string O ์์ฒญ ๋ณธ๋ฌธ(payload)์ SHA256 ํด์๊ฐ. (AWS SigV4 ์๋ช ์๊ตฌ์ฌํญ) -
์์ฒญ ๋ณธ๋ฌธ (Request Body - JSON):
chatbotApi.ts
์ChatContext
๋ฐnewMessage
๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค.{ "problemDetails": { // ๋ฌธ์ ์์ธ ์ ๋ณด (์ ํ์ , ์์ผ๋ฉด null) "id": "string | number", // ๋ฌธ์ ID "title": "string", // ๋ฌธ์ ์ ๋ชฉ "description": "string" // ๋ฌธ์ ์ค๋ช (์ ํ์ ) }, "userCode": "string", // ์ฌ์ฉ์๊ฐ ์์ฑํ ํ์ฌ ์ฝ๋ "history": [ // ์ด์ ๋ํ ๊ธฐ๋ก (๋ฐฐ์ด, ์ ํ์ ) { "role": "user", // "user" ๋๋ "assistant" ("model"๋ ๊ฐ๋ฅ) "content": "string" // ๋ฉ์์ง ๋ด์ฉ } ], "newMessage": "string" // ์ฌ์ฉ์์ ์ ๋ฉ์์ง (ํ์) }
- ์๋ต (Response - Server-Sent Events Stream):
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
SSE ์ด๋ฒคํธ ํ์:
- ํ ํฐ (๋ฐ์ดํฐ ์กฐ๊ฐ):
data: {"token": "์๋ต ๋ฉ์์ง์ ์ผ๋ถ"}\n\n
token
: LLM์ด ์์ฑํ ํ ์คํธ์ ์คํธ๋ฆฌ๋ฐ ์กฐ๊ฐ์ ๋๋ค.
- ์คํธ๋ฆผ ์ข
๋ฃ ์ ํธ (์ ํ์ ):
data: [DONE]\n\n
- ํด๋ผ์ด์ธํธ์์ ์คํธ๋ฆผ ์ข ๋ฃ๋ฅผ ๋ช ์์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ ํธ์ ๋๋ค.
- ์ค๋ฅ ๋ฐ์ ์ (์คํธ๋ฆผ ์ค):
data: {"error": "์๋ฌ ๋ฉ์์ง ์์ฝ", "details": "์์ธ ์๋ฌ ๋ด์ฉ"}\n\n
- ์คํธ๋ฆผ์ด ์์๋ ํ ๋ฐฑ์๋์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์ด ํ์์ผ๋ก ์ ์ก๋ ์ ์์ต๋๋ค.
-
HTTP ์ํ ์ฝ๋ (Status Codes):
์ฝ๋ ์ค๋ช ์๋ต ๋ณธ๋ฌธ 200 OK
์์ฒญ ์ฑ๊ณต ๋ฐ SSE ์คํธ๋ฆผ ์์ text/event-stream
ํ์์ SSE ๋ฐ์ดํฐ400 Bad Request
์์ฒญ ๋ณธ๋ฌธ ํ์ฑ ์คํจ ๋๋ ํ์ ํ๋ ๋๋ฝ (Lambda ๋ด๋ถ์์ ์ฒ๋ฆฌ, SSE ํค๋ ์ ์ก ํ ๋ฐ์ ์ SSE ์ค๋ฅ๋ก ์ ๋ฌ) JSON: {"error": "Invalid request body.", "details": "..."}
๋๋ SSE ์ค๋ฅ401 Unauthorized
X-Custom-Auth-Token
ํค๋ ๋๋ฝ ๋๋ JWT ๊ฒ์ฆ ์คํจJSON: {"error": "Unauthorized", "details": "..."}
500 Internal Server Error
LLM ํธ์ถ ์คํจ ๋ฑ ์๋ฒ ๋ด๋ถ ์ค๋ฅ (Lambda ๋ด๋ถ์์ ์ฒ๋ฆฌ, SSE ํค๋ ์ ์ก ํ ๋ฐ์ ์ SSE ์ค๋ฅ๋ก ์ ๋ฌ) JSON: {"error": "Failed to get response from LLM", "details": "..."}
๋๋ SSE ์ค๋ฅ
5. ์ธ์ฆ (Authentication)
- ํด๋ผ์ด์ธํธ๋ AWS Amplify์
fetchAuthSession()
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ Amazon Cognito๋ก๋ถํฐ ์ฌ์ฉ์์ ID ํ ํฐ (JWT)์ ๊ฐ์ ธ์ต๋๋ค. - ํ๋ํ ID ํ ํฐ์
X-Custom-Auth-Token
HTTP ํค๋์Bearer
์ ๋์ฌ์ ํจ๊ป ๋ด๊ฒจ API๋ก ์ ์ก๋ฉ๋๋ค.- ์:
X-Custom-Auth-Token: Bearer eyJraWQiOiJ...
- ์:
- ๋ฐฑ์๋ Lambda ํจ์๋ ์์ ๋ JWT๋ฅผ ๋ค์ ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์ฆํฉ๋๋ค:
COGNITO_JWKS_URL
: Cognito User Pool์ JWKS (JSON Web Key Set) URICOGNITO_ISSUER_URL
: Cognito User Pool์ ๋ฐ๊ธ์(Issuer) URLCOGNITO_APP_CLIENT_ID
: Cognito User Pool ์ฑ ํด๋ผ์ด์ธํธ ID (Audience ๊ฒ์ฆ์ฉ)
- JWT ๊ฒ์ฆ์ด ์ฑ๊ณตํ๋ฉด ์์ฒญ ์ฒ๋ฆฌ๊ฐ ๊ณ์๋๊ณ , ์คํจํ๋ฉด
401 Unauthorized
์ค๋ฅ๊ฐ ๋ฐํ๋ฉ๋๋ค.
6. ๋ฐ์ดํฐ ๋ชจ๋ธ (์์ธ)
ChatMessage
(๋ํ ๊ธฐ๋ก ๋ด ๋ฉ์์ง ๊ฐ์ฒด)
ํ๋ | ํ์ | ์ค๋ช |
---|---|---|
role | "user" | "assistant" | "model" | ๋ฉ์์ง ๋ฐํ์ ์ญํ |
content | string | ๋ฉ์์ง ๋ด์ฉ |
ProblemDetailPlaceholder
(๋ฌธ์ ์์ธ ์ ๋ณด ๊ฐ์ฒด)
ํ๋ | ํ์ | ์ค๋ช |
---|---|---|
id | string | number | ๋ฌธ์ ID |
title | string | ๋ฌธ์ ์ ๋ชฉ |
description | string (์ ํ์ ) | ๋ฌธ์ ์์ธ ์ค๋ช |
ChatStreamPayload
(SSE ์คํธ๋ฆผ ๋ฐ์ดํฐ ํ์ด๋ก๋)
ํ๋ | ํ์ | ์ค๋ช |
---|---|---|
token | string (์ ํ์ ) | LLM ์๋ต ํ ์คํธ ์กฐ๊ฐ |
error | string (์ ํ์ ) | ์ค๋ฅ ๋ฐ์ ์ ์ค๋ฅ ๋ฉ์์ง ์์ฝ |
details | string (์ ํ์ ) | ์ค๋ฅ ๋ฐ์ ์ ์์ธ ๋ด์ฉ |
7. ์๋ฌ ์ฒ๋ฆฌ
- ์ธ์ฆ ์ค๋ฅ:
X-Custom-Auth-Token
ํค๋์ JWT๊ฐ ์ ํจํ์ง ์๊ฑฐ๋ ๋๋ฝ๋ ๊ฒฝ์ฐ, Lambda ํจ์๋ HTTP401 Unauthorized
์๋ต๊ณผ ํจ๊ป JSON ํ์์ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค. - ์์ฒญ ์ ํจ์ฑ ๊ฒ์ฌ ์ค๋ฅ: ์์ฒญ ๋ณธ๋ฌธ์ด ์๋ชป๋์๊ฑฐ๋ ํ์ ํ๋๊ฐ ๋๋ฝ๋ ๊ฒฝ์ฐ, Lambda ํจ์๋ ์ํฉ์ ๋ฐ๋ผ HTTP
400 Bad Request
๋๋ SSE ์คํธ๋ฆผ ๋ด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค. (SSE ํค๋๊ฐ ์ด๋ฏธ ์ ์ก๋ ๊ฒฝ์ฐ ์คํธ๋ฆผ ๋ด ์ค๋ฅ๋ก ์ฒ๋ฆฌ) - LLM ๋ฐ ๋ด๋ถ ์๋ฒ ์ค๋ฅ: Google AI ๋ชจ๋ธ ํธ์ถ ์คํจ ๋ฑ ๋ฐฑ์๋ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด, Lambda ํจ์๋ ์ํฉ์ ๋ฐ๋ผ HTTP
500 Internal Server Error
๋๋ SSE ์คํธ๋ฆผ ๋ด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
8. ๋ฐฐํฌ (Deployment)
Terraform์ ์ฌ์ฉํ์ฌ AWS ์ธํ๋ผ๋ฅผ ๋ฐฐํฌํฉ๋๋ค. (infrastructure/chatbot/README.md
๋ฐ README.ko.md
์ฐธ์กฐ)
์ฃผ์ ๋จ๊ณ:
- ์ฌ์ ์ค๋น:
- AWS ๊ณ์ ๋ฐ AWS CLI ์ค์ (
aws configure
) - Terraform CLI ์ค์น
- Node.js ๋ฐ npm ์ค์น (Lambda Layer ๋น๋์ฉ)
- Cognito ์ธํ๋ผ๊ฐ ์ด๋ฏธ ๋ฐฐํฌ๋์ด ์๊ณ , ํด๋น Terraform ์ํ ํ์ผ(
cognito/terraform.tfstate
)์ ์ ๊ทผ ๊ฐ๋ฅํด์ผ ํจ. - Google AI API Key (
GOOGLE_AI_API_KEY
) ์ค๋น.
- AWS ๊ณ์ ๋ฐ AWS CLI ์ค์ (
- ํ๊ฒฝ ๋ณ์ ์ค์ :
- Terraform ๋ณ์
google_ai_api_key
์ ์ค๋น๋ Google AI API ํค ๊ฐ์ ์ค์ ํฉ๋๋ค. (์:terraform.tfvars
ํ์ผ ์์ฑ ๋๋ CI/CD ํ๊ฒฝ ๋ณ์TF_VAR_google_ai_api_key
์ค์ )
- Terraform ๋ณ์
- ์ ์ฅ์ ํด๋ก ๋ฐ ์ด๋:
git clone <repository_url> cd <repository_name>/capstone-2025-04
- Lambda Layer ์์กด์ฑ ์ค์น: Lambda Layer์ ํฌํจ๋ Node.js ํจํค์ง๋ค์ ์ง์ ๋ ๋๋ ํ ๋ฆฌ(
infrastructure/chatbot/layers/chatbot_deps/nodejs
)์ ์ค์นํฉ๋๋ค.npm install --prefix ./infrastructure/chatbot/layers/chatbot_deps/nodejs ./backend/lambdas/chatbot-query
- ์ ๋ช
๋ น์ด๋
backend/lambdas/chatbot-query/package.json
์ ์ ์๋ ์์กด์ฑ์infrastructure/chatbot/layers/chatbot_deps/nodejs/node_modules
๊ฒฝ๋ก ์๋์ ์ค์นํฉ๋๋ค.
- ์ ๋ช
๋ น์ด๋
- Terraform ๋ฐฐํฌ:
infrastructure/chatbot
๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ์ฌ Terraform ๋ช ๋ น์ ์คํํฉ๋๋ค.cd infrastructure/chatbot terraform init \ -backend-config="bucket=alpaco-tfstate-bucket-kmu" \ -backend-config="key=chatbot/terraform.tfstate" \ -backend-config="region=ap-northeast-2" \ -backend-config="dynamodb_table=alpaco-tfstate-lock-table" # ๋๋ backend.tf ํ์ผ์ด ์ด๋ฏธ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ๋์ด ์์ผ๋ฉด terraform init ๋ง ์คํ terraform plan # (์ ํ ์ฌํญ) ๋ณ๊ฒฝ๋ ๋ด์ฉ ํ์ธ terraform apply # ์ธํ๋ผ ๋ฐฐํฌ
apply
์๋ฃ ํ,cloudfront_distribution_domain_name
์ถ๋ ฅ์ ํ์ธํฉ๋๋ค.
- ํ๋ก ํธ์๋ ์ค์ ์
๋ฐ์ดํธ:
- Terraform ์ถ๋ ฅ๊ฐ์ธ
cloudfront_distribution_domain_name
์ ํ๋ก ํธ์๋ ํ๋ก์ ํธ์ ํ๊ฒฝ ๋ณ์NEXT_PUBLIC_CHATBOT_API_ENDPOINT
์ ์ค์ ํฉ๋๋ค. (์:https://{์ถ๋ ฅ๋_๋๋ฉ์ธ_์ด๋ฆ}
)
- Terraform ์ถ๋ ฅ๊ฐ์ธ
9. ์ฌ์ฉ ์์ (ํด๋ผ์ด์ธํธ ์ธก - chatbotApi.ts
์ฐธ๊ณ )
ํ๋ก ํธ์๋์ src/api/chatbotApi.ts
ํ์ผ์ ์๋ streamChatbotResponse
ํจ์๊ฐ ์ด API๋ฅผ ํธ์ถํ๋ ์ฃผ์ ๋ก์ง์ ๋ด๊ณ ์์ต๋๋ค.
// chatbotApi.ts (์ผ๋ถ ๋ฐ์ท)
import { fetchAuthSession } from "aws-amplify/auth";
// ... (์ธํฐํ์ด์ค ์ ์: ChatContext, ChatMessage, ChatStreamPayload, Callbacks)
const API_ENDPOINT = process.env.NEXT_PUBLIC_CHATBOT_API_ENDPOINT;
export const streamChatbotResponse = async (
context: ChatContext,
message: string,
callbacks: { /* onData, onError, onComplete */ }
): Promise<void> => {
const { onData, onError, onComplete } = callbacks;
try {
// 1. Cognito ID Token ํ๋
const session = await fetchAuthSession();
const idToken = session.tokens?.idToken?.toString();
if (!idToken) throw new Error("User not authenticated.");
// 2. ์์ฒญ ๋ณธ๋ฌธ ๊ตฌ์ฑ
const payload = { ...context, newMessage: message };
const payloadString = JSON.stringify(payload);
// 3. SHA256 ํด์ ๊ณ์ฐ (x-amz-content-sha256 ํค๋์ฉ)
const sha256Hash = await calculateSHA256(payloadString); // (calculateSHA256 ํจ์๋ crypto.subtle.digest ์ฌ์ฉ)
// 4. Fetch API๋ก SSE ์คํธ๋ฆฌ๋ฐ ์์ฒญ
const response = await fetch(API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Custom-Auth-Token": `Bearer ${idToken}`,
"x-amz-content-sha256": sha256Hash,
},
body: payloadString,
});
// ... (์๋ต ์ํ ์ฝ๋ ๋ฐ SSE ์คํธ๋ฆผ ์ฒ๋ฆฌ ๋ก์ง) ...
// 5. SSE ์คํธ๋ฆผ ํ์ฑ ๋ฐ ์ฝ๋ฐฑ ํธ์ถ
// reader.read() ๋ก ๋ฐ์ดํฐ ์์
// decoder.decode() ๋ก ํ
์คํธ ๋ณํ
// "data: " ๋ก ์์ํ๋ ๋ฉ์์ง ํ์ฑ
// JSON.parse() ๋ก ๊ฐ์ฒด ๋ณํ ํ onData, onError ์ฝ๋ฐฑ ํธ์ถ
// ์คํธ๋ฆผ ์ข
๋ฃ ์ onComplete ํธ์ถ
} catch (error) {
// ... (์ ์ฒด์ ์ธ ์๋ฌ ์ฒ๋ฆฌ ๋ฐ onError, onComplete ์ฝ๋ฐฑ ํธ์ถ) ...
}
};
์ด ๋ช ์ธ์๊ฐ ALPACO AI Chatbot API์ ์ดํด์ ์ฌ์ฉ์ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.