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)
  1. Frontend (ํด๋ผ์ด์–ธํŠธ): ์‚ฌ์šฉ์ž์™€์˜ ์ธํ„ฐ๋ž™์…˜์„ ๋‹ด๋‹นํ•˜๋ฉฐ, AWS Amplify๋ฅผ ํ†ตํ•ด Cognito๋กœ๋ถ€ํ„ฐ JWT (ID Token)๋ฅผ ํš๋“ํ•ฉ๋‹ˆ๋‹ค.
  2. AWS CloudFront: ๊ณต๊ฐœ ์—”๋“œํฌ์ธํŠธ ์—ญํ• ์„ ํ•˜๋ฉฐ, Lambda ํ•จ์ˆ˜ URL์„ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด OAC (Origin Access Control)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ ์‹œ SigV4 ์„œ๋ช…์„ ํ†ตํ•ด Lambda ํ•จ์ˆ˜ URL์„ ํ˜ธ์ถœํ•˜๋ฉฐ, ํ•„์š”ํ•œ ํ—ค๋”(X-Custom-Auth-Token, x-amz-content-sha256 ๋“ฑ)๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  3. AWS Lambda Function URL: Lambda ํ•จ์ˆ˜๋ฅผ ์œ„ํ•œ HTTPS ์—”๋“œํฌ์ธํŠธ๋กœ, AWS_IAM ์ธ์ฆ ๋ฐฉ์‹๊ณผ RESPONSE_STREAM ํ˜ธ์ถœ ๋ชจ๋“œ๋กœ ์„ค์ •๋˜์–ด CloudFront OAC๋ฅผ ํ†ตํ•œ ์•ˆ์ „ํ•œ ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜ธ์ถœ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  4. 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๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  5. Google Generative AI: ์‹ค์ œ ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ ๋ฐ ์‘๋‹ต ์ƒ์„ฑ์„ ๋‹ด๋‹นํ•˜๋Š” LLM ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
  6. 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 ์ด๋ฒคํŠธ ํ˜•์‹:

    1. ํ† ํฐ (๋ฐ์ดํ„ฐ ์กฐ๊ฐ):
      data: {"token": "์‘๋‹ต ๋ฉ”์‹œ์ง€์˜ ์ผ๋ถ€"}\n\n
      
      • token: LLM์ด ์ƒ์„ฑํ•œ ํ…์ŠคํŠธ์˜ ์ŠคํŠธ๋ฆฌ๋ฐ ์กฐ๊ฐ์ž…๋‹ˆ๋‹ค.
    2. ์ŠคํŠธ๋ฆผ ์ข…๋ฃŒ ์‹ ํ˜ธ (์„ ํƒ์ ):
      data: [DONE]\n\n
      
      • ํด๋ผ์ด์–ธํŠธ์—์„œ ์ŠคํŠธ๋ฆผ ์ข…๋ฃŒ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค.
    3. ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ (์ŠคํŠธ๋ฆผ ์ค‘):
      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)

  1. ํด๋ผ์ด์–ธํŠธ๋Š” AWS Amplify์˜ fetchAuthSession() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Amazon Cognito๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž์˜ ID ํ† ํฐ (JWT)์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  2. ํš๋“ํ•œ ID ํ† ํฐ์€ X-Custom-Auth-Token HTTP ํ—ค๋”์— Bearer ์ ‘๋‘์‚ฌ์™€ ํ•จ๊ป˜ ๋‹ด๊ฒจ API๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ: X-Custom-Auth-Token: Bearer eyJraWQiOiJ...
  3. ๋ฐฑ์—”๋“œ Lambda ํ•จ์ˆ˜๋Š” ์ˆ˜์‹ ๋œ JWT๋ฅผ ๋‹ค์Œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค:
    • COGNITO_JWKS_URL: Cognito User Pool์˜ JWKS (JSON Web Key Set) URI
    • COGNITO_ISSUER_URL: Cognito User Pool์˜ ๋ฐœ๊ธ‰์ž(Issuer) URL
    • COGNITO_APP_CLIENT_ID: Cognito User Pool ์•ฑ ํด๋ผ์ด์–ธํŠธ ID (Audience ๊ฒ€์ฆ์šฉ)
  4. 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 ํ•จ์ˆ˜๋Š” HTTP 401 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 ์ฐธ์กฐ)

์ฃผ์š” ๋‹จ๊ณ„:

  1. ์‚ฌ์ „ ์ค€๋น„:
    • 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) ์ค€๋น„.
  2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •:
    • Terraform ๋ณ€์ˆ˜ google_ai_api_key์— ์ค€๋น„๋œ Google AI API ํ‚ค ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: terraform.tfvars ํŒŒ์ผ ์ƒ์„ฑ ๋˜๋Š” CI/CD ํ™˜๊ฒฝ ๋ณ€์ˆ˜ TF_VAR_google_ai_api_key ์„ค์ •)
  3. ์ €์žฅ์†Œ ํด๋ก  ๋ฐ ์ด๋™:
    git clone <repository_url>
    cd <repository_name>/capstone-2025-04
    
  4. 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 ๊ฒฝ๋กœ ์•„๋ž˜์— ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
  5. 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 ์ถœ๋ ฅ์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  6. ํ”„๋ก ํŠธ์—”๋“œ ์„ค์ • ์—…๋ฐ์ดํŠธ:
    • Terraform ์ถœ๋ ฅ๊ฐ’์ธ cloudfront_distribution_domain_name์„ ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ NEXT_PUBLIC_CHATBOT_API_ENDPOINT์— ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: https://{์ถœ๋ ฅ๋œ_๋„๋ฉ”์ธ_์ด๋ฆ„})

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์˜ ์ดํ•ด์™€ ์‚ฌ์šฉ์— ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.