ํฐ์คํ ๋ฆฌ ๋ทฐ
2๏ธโฃ Lymo ๊ฐ๋ฐ์ผ์ง #2: https://parkcool.tistory.com/12
3๏ธโฃ Lymo ๊ฐ๋ฐ์ผ์ง #3: ํ์ฌ ๊ธ
4๏ธโฃ Lymo ๊ฐ๋ฐ์ผ์ง #4: https://parkcool.tistory.com/17
์๋ ํ์ธ์! ์ง๋ ๊ธ์์๋ SSE๋ฅผ ๋ฒ๋ฆฌ๊ณ RTDB๋ก ๊ฐ์ํ ์ฝ์ง๊ธฐ๋ฅผ ํตํด ์ค์๊ฐ ๋ฐ์ดํฐ ์คํธ๋ฆฌ๋ฐ์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์ด์ผ๊ธฐํ์์ฃ .
์ค๋์ ํผ๋์ AI ๊ฐ๋ฐ ๊ณผ์ ์ ๋ค๋ค๋ณด๋ ค ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ํผ๋ ์์์ ์ ๋ฅผ ๊ตฌ์ํด ์ค TypeScript์ Zod ์ด์ผ๊ธฐ๋์.
๐คฏ AI ๊ฐ๋ฐ์ ํ์ค

ํํ AI ๊ฐ๋ฐ์ด๋ผ๊ณ ํ๋ฉด, "ํ๋กฌํํธ๋ฅผ ์ ๋ ฅํ๋ AI๊ฐ ์ฒ์ฒ๋ฐ์ฌ์ฒ๋ผ ๋๋ตํด์ฃผ๋" ๋ชจ์ต์ ์์ํฉ๋๋ค. ์ ๋ ๊ทธ๋ฌ๊ณ ์. Genkit ์ฝ๋ ๋ช ์ค๋ก LLM์ ํธ์ถํ๊ณ , ๊ฐ์ฌ๋ฅผ ๋ฒ์ญํด๋ฌ๋ผ๊ณ ๋ช ๋ นํ๋ฉด ๋๋ ์ค ์์์ฃ . ์ ๋ ์ฌ์ง์ด ์ ์ฌ์ง์ฒ๋ผ ํ๋กฌํํธ๋ ์ ๋ง ์์ธํ๊ฒ ์์ฑํ์ต๋๋ค.
ํ์ง๋ง ํ์ค์ ์๊ถ์ฐฝ์ด์์ต๋๋ค. Lymo๋ ๋ฐ์ ์๋๋ฅผ ์ํด ๊ฐ๋ณ๊ณ ๋น ๋ฅธ gemini-2.5-flash-lite ๋ชจ๋ธ์ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ์๋ ๋น์ฉ์ด ์ ๋ ดํ๊ณ ๋ฐ์ ์๋๊ฐ ๋น ๋ฆฟ๋น ๋ฆฟํ ๋์ , ์ง์นจ์ ๋ฌด์ํ๊ฑฐ๋ ๋ด์๋ฆฌ, ํ์๋ฆฌ๋ฅผ ํ๋ ๋น๋๊ฐ ๊ฝค ๋์์ต๋๋ค. ์ฌ์ง์ด ์ผ๋ฐ ๋ชจ๋ธ์ธ gemini-2.5-flash ๋ชจ๋ธ์์๋ ์ด๋ฐ ํ์์ ์ ์ง ์๊ฒ ์ผ์ด๋ฌ์ต๋๋ค. ๋ํ์ ์ผ๋ก ๋ฐ์ํ ํ์๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๐คฏ 1. ์ง์นจ ๋ฌด์
์ ๋ ๊ฐ์ฌ ์ค๊ฐ์ ๋ค์ด๊ฐ๋ ์ถ์์(์: "Yeah", "Ooh", "Baby")์ฒ๋ผ ์๋ฏธ ์๋ ๋ง์ ๋ฒ์ญํ์ง ๋ง๊ณ null์ ๋ฐํํ๋ผ๊ณ ๋ช ์ํ์ต๋๋ค. ํ์ง๋ง AI๋ ๊ตณ์ด ์๋ฌธ์ ๊ทธ๋๋ก ๋ฑ์ด๋ด๊ฑฐ๋("Yeah"), ์ต์ง๋ก ๋ฒ์ญํ๋ ค๊ณ ์๋ํ์ต๋๋ค.
ํด์ ๋์ ์ธ์ด์ ์๋ฌธ ์ธ์ด๊ฐ ๋์ผํ ๊ฒฝ์ฐ์๋ null์ ๋ฐํํ๋ผ๊ณ ํ์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๊ฐ์ฌ๋ฅผ ํ๊ตญ์ด๋ก ๋ฒ์ญํ๊ธฐ๋ฅผ ์์ฒญํ๋๋ฐ, ๊ฐ์ฌ๊ฐ ์ด๋ฏธ ํ๊ตญ์ด์ธ ๊ฒฝ์ฐ์๋ null๋ฅผ ๋ฐํํด์ผ ํฉ๋๋ค. ํ์ง๋ง ์ด ๊ฒฝ์ฐ์๋ AI๋ ์๋ฌธ์ ๊ทธ๋๋ก ๋ฑ์ด๋ด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ต๋๋ค.
๐คฏ 2. ํ ๋ฃจ์๋ค์ด์

์ ์งค์ ๋ณด์ ์ ์์ผ์ ๊ฐ์? ChatGPT ์ถ์ ์ด๋ฐ ๋์ค๋ค์๊ฒ AI ํ๊ฐ์ด๋ผ๋ ๊ฐ๋ ์ ๋ํด ๊ฐ์ธ์์ผ์ค ์ฌ๋ก์์ต๋๋ค.
AI๊ฐ ์๋ ์ฌ์ค์ ์ง์ด๋ด๊ฑฐ๋, ๊ทธ๋ด ๋ฏํ๊ฒ ๊พธ๋ฉฐ๋ด์ด ์ง์ค์ธ ์ ๋ต๋ณํ๋ ํ๋ ๊ฒ์ ํ๊ฐ, ํ ๋ฃจ์๋ค์ด์ ์ด๋ผ๊ณ ํฉ๋๋ค. ์ ๊ฒฝ์ฐ์๋ ์ต์ ๋ ธ๋์ ๋ํด ํด์์ ์์ฒญํ์ ๋ ํนํ ๋ง์ด ๋ฐ์ํ๋๋ฐ์. AI๋ ํ์ต๋ ์์ ์ ์ดํ์ ์ฌ๊ฑด์ ๋ํด์๋ ์ ํ ์์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ ํ์์ด์์ต๋๋ค. (cutoff ๋ฌธ์ ) ์์ง ๋ชปํ๋ ์ ๋ณด์ ๋ํด ๋ฌผ์ด๋ณด๋ ์๋ ์ฌ์ค์ ์ต์ง๋ก ๋ง๋ค์ด ์ด์ํ ์๋ฆฌ๋ฅผ ํด๋๋ ๊ฒ์ด์ฃ .
๐คฏ 3. ๋ฐํ ํ์ ๋ถ์ผ์น
๊ฐ์ฅ ์น๋ช ์ ์ธ ๋ฌธ์ ๋ ๋ฐฐ์ด ๊ธธ์ด ๋ถ์ผ์น์์ต๋๋ค. ๊ฐ์ฌ 4์ค์ ๋ฐฐ์ด๋ก ๋ณด๋์ผ๋ฉด ๋ฒ์ญ๋ ์ ํํ 4์ค์ด ์์ผ, ์ฑ ํ๋ฉด์์ ์๋ฌธ๊ณผ ๋ฒ์ญ๋ฌธ์ 1:1๋ก ๋งค์นญํ ์ ์๊ฒ ์ฃ ? ๊ทธ๋ฐ๋ฐ AI๊ฐ ์๊ธฐ ๋ง์๋๋ก ๋ ๋ฌธ์ฅ์ ํฉ์น๊ฑฐ๋, ํ ๋ฌธ์ฅ์ ์ชผ๊ฐ์ 3์ค์ด๋ 5์ค์ ๋ฐํํด๋ฒ๋ฆฌ๋ ๊ฒ๋๋ค. ์ด๋ฌ๋ฉด ๋ค๋ฐ๋ผ์ค๋ ๋ชจ๋ ๊ฐ์ฌ ์ฑํฌ๊ฐ ๋ค ๋ฐ๋ ค๋ฒ๋ฆฌ๋ ๋์ฐธ์ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
๐ ๏ธ ํ๋กฌํํธ ๊น๊ธฐ

์ฒ์์ AI๋ฅผ ์ค๋ํ๋ ค๊ณ ๋ ธ๋ ฅํ์ต๋๋ค. ํ๋กฌํํธ๋ฅผ ์์ ํ๊ณ ๋ ์์ ํ์ฃ .
- ํ๋กฌํํธ ๊น๊ธฐ: "๋ชจ๋ฅด๋ ๊ฒ์ ๋ต๋ณํ์ง ๋ง ๊ฒ.", "์ ๋๋ก ~~ํ์ง ๋ง ๊ฒ.", ...์ฒ๋ผ ํด์๋ ์ ๋๋ ๊ฒ์ ๋ช ํํ ๋ช ์ํ๊ณ , ์ /์ถ๋ ฅ ํ์๊ณผ ๊ทธ ์์๋ฅผ ์ ์ํ๊ณ , "๋๋ ๊ฐ์ฌ ๋ถ์ ์ ๋ฌธ๊ฐ์ผ.", "๋๋ ๊ฐ์ฌ ํด์์ ํ๊ณ ๋ ๋ฒ์ญ๊ฐ์ผ.", ...์ฒ๋ผ AI์ ์ญํ ์ ์ ํด์ฃผ๋ ๋ฑ AI์ ๋ต๋ณ ํ์ง์ ์ฌ๋ฆฌ๋ ๋ฐ ํจ๊ณผ์ ์ธ ๊ฒ์ผ๋ก ์๋ ค์ง ๋ฌธ๊ตฌ๋ค์ ๋ค์ ํฌํจ์์ผฐ์ต๋๋ค.
- ๋ฒํธํ ๋ถ์ฌ์ฃผ๊ธฐ: ์ ๋ ฅ ๋ฐ์ดํฐ (JSON ๋ฐฐ์ด)๋ฅผ ๊ทธ๋ฅ ์ฃผ์ง ์๊ณ , ๊ฐ ๋ฌธ์ฅ ์์ 1, 2, 3 ๋ฒํธ๋ฅผ ๋ถ์ฌ์ ์คฌ์ต๋๋ค. "์, 1๋ฒ ์ค ๋ฒ์ญํด. 2๋ฒ ์ค ๋ฒ์ญํด."๋ผ๊ณ ๋ช ํํ๊ฒ ๋ฒํธ๋ฅผ ๋ช ์ํ ๊ฑฐ์ฃ .
- ์๊ฐํ ์๊ฐ ์ฃผ๊ธฐ: ๋ฐ๋ก ๋ต์ ๋ด๋์ผ๋ผ๊ณ ํ๋ฉด ํ๋ฆฌ๋๊น, ์ถ๋ ฅ ์คํค๋ง ๋งจ ์์ reasoning ํ๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค. "๋ต๋ณํ๊ธฐ ์ ์ ์ด ๋ฌธ์ฅ์ ์ด๋ป๊ฒ ๋ฒ์ญํ ์ง, ํน์ ํฉ์ณ์ผ ํ ๋ฌธ์ฅ์ ์๋์ง ๋จผ์ ์๊ฐํด ๋ด" ๋ผ๊ณ ๋์์ฅ์ ์ฅ์ฌ์ค ์ ์ ๋๋ค.
๊ทธ ๊ฒฐ๊ณผ ์ ๋ต๋ฅ ์ด (์ฒด๊ฐ์) 70%์์ 80%๊น์ง ์ฌ๋ผ๊ฐ์ต๋๋ค. ๋ถ์กฑํ์ต๋๋ค. ์ฌ์ฉ์๊ฐ 20%์ ํ๋ฅ ๋ก ์๋ง์ง์ฐฝ์ธ ํด์์ ๋ณด๊ฒ ํ ์๋ ์์์ต๋๋ค. ๋ํ ์ด๋ฌํ ๋ฐฉ๋ฒ์ผ๋ก๋ ํ ๋ฃจ์๋ค์ด์ ๋ฌธ์ ๋ฅผ ๊ทผ๋ณธ์ ์ผ๋ก ํด๊ฒฐํ ์ ์์์ต๋๋ค.
๐ ์น ๊ฒ์ ๋ฅ๋ ฅ ์ฅ์ฌ์ฃผ๊ธฐ
ํ ๋ฃจ์๋ค์ด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ํ์ ์ธ ๋ฐฉ๋ฒ ์ค ํ๋๋ ๊ฒ์์ฆ๊ฐ์์ฑ, RAG์ ๋๋ค. ์ฝ๊ฒ ์ค๋ช ํ๋ฉด AI์๊ฒ ์ธ๋ถ ์ง์์ ์ ๊ณตํ์ฌ ์ ํํ๊ณ ์ต์ ์ ์ ๋ณด๋ฅผ ๋ฐํํ ์ ์๋๋ก ํ๋ ๊ธฐ์ ์ ๋๋ค. ์ด ๊ธฐ์ ์ ์ด์ฉํ๋ฉด AI๊ฐ ๊ฒ์ ์์ง์ ํตํด ์ค์ค๋ก ํ์ํ ์ ๋ณด๋ฅผ ๊ฒ์ํ๊ณ , ์ด ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์๋ต์ ์์ฑํ๋๋ก ํ ์ ์์ต๋๋ค.
import { genkit } from 'genkit';
import { vertexAI } from '@genkit-ai/google-genai';
const ai = genkit({
plugins: [ vertexAI({ location: 'us-central1' }) ]
});
const response = await ai.generate({
model: vertexAI.model('gemini-2.5-pro'),
prompt: 'Explain Vertex AI in simple terms.',
});
console.log(response.text());
์ ๊ฐ ์ฌ์ฉ ์ค์ธ AI ๊ฐ๋ฐ ํ๋ ์์ํฌ์ธ Genkit์์๋ ์ด ๊ธฐ๋ฅ์ ์ ๋ง ์ฝ๊ฒ ๊ฒฐํฉํ ์ ์์ต๋๋ค. Genkit์ด ์ ๊ณตํ๋ ํ๋ฌ๊ทธ์ธ ์ค ํ๋์ธ Vertex AI๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. Vertex AI๋ Google Cloud์์ ์ ๊ณตํ๋ AI ํ๋ซํผ์ธ๋ฐ, ์ฌ๊ธฐ์ ์ ๊ณตํ๋ ์น ๊ฒ์ + ์คํฌ๋ํ + ๋ต๋ณ ๊ธฐ๋ฅ์ "์ค์ ๋ธ๊น" ํ ๋ฒ์ผ๋ก ๊ฐ๊ณ ์ฌ ์ ์์ต๋๋ค.
์ค์ ๋ก ์ ๊ฐ ์น ๊ฒ์ RAG์ ๊ฒฐํฉํ๊ธฐ ์ํด ์์ฑํ๋ ์ฝ๋์ ์์ด 10์ค์ด ์ฑ ์ ๋ผ์. Genkit์ ์ฌ์ฉํ์๋ ๋ถ๋ค ์ค์ ์์ ์ ์๋น์ค์ ์น ๊ฒ์ RAG๋ฅผ ๊ฒฐํฉํด๋ณด๊ณ ์ถ์ผ์ ๋ถ๋ค์ ๋ฌด์กฐ๊ฑด Vertex AI ์ฐพ์๋ณด์๊ธฐ ๋ฐ๋๋๋ค!! ๐ฅน๐ฅน ์์ธํ ์ ๋ณด๋ ๋ค์ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด๋ณด์ธ์.
Vertex AI plugin
Learn how to use Google Cloud Vertex AI with Genkit across JavaScript, Go, and Python, including enterprise features like grounding, Vector Search, Model Garden, and evaluation metrics.
genkit.dev
ํ๋กฌํํธ๋ฅผ ๊ฐ์ ํ๊ณ , ์น ๊ฒ์ RAG๊น์ง ๋ถ์ด๋ ์ ๋ต๋ฅ ์ด 80%์์ 90%๊น์ง๋ ์ฌ๋ผ๊ฐ ๊ฒ ๊ฐ์์ต๋๋ค. ํนํ ์ต์ ๋ ธ๋์ ๋ํด ๋ง์ ์ง์ด๋ด๋ ํ์์ ์ ๋ง ๋ง์ด ๊ฐ์ ๋์์ต๋๋ค. ํ์ง๋ง ์๋น์ค์ ํ์ํ ๊ฑด 90%๊ฐ ์๋๋ผ 99%์ ์์ ์ฑ์ด์์ต๋๋ค. 10%์ ํ๋ฅ ๋ก ์ฌ์ฉ์๋ ์ฌ์ ํ ์๋ง์ง์ฐฝ์ธ ํด์์ ๋ด์ผ ํ์ผ๋๊น์.
๐ก๏ธ ๊ฒฐ๊ตญ ๋ฏฟ์ ๊ฑด '์ฝ๋'๋ฟ์ด๋ค

์ ๋ AI๋ฅผ 100% ์ ๋ขฐํ๋ ๊ฒ์ ํฌ๊ธฐํ๊ณ , ๊ธฐ๊ณ์ ์ธ ๊ฒ์ฆ ๋ก์ง์ผ๋ก AI๋ฅผ ๊ฒ์ฆํ๊ธฐ๋ก ํ์ต๋๋ค. AI๊ฐ ๋ฑ์ ๊ฒฐ๊ณผ๋ฌผ์ ์ฝ๋๊ฐ ์ฑ์ ํ๋ ์ฝ๋๋ฅผ ์ง๋ ๊ฒ์ด์ฃ .
1. ๐ ํ ์คํธ ์ ์ฌ๋ ๊ฒ์ฌ
const similarity = calculateSimilarity(lyrics[index].text, translation);
if (similarity > 0.8) return null;
AIํํ "Yeah"๋ฅผ ๋ฒ์ญํ๋ผ๊ณ ํ๋๋ "Yeah"๋ผ๊ณ ๊ทธ๋๋ก ๋ตํ๋ค๊ณ ๊ฐ์ ํด๋ด์. ํ๋กฌํํธ๋ก๋ ์ด๊ฑธ ๋ง๊ธฐ๊ฐ ๋๋ฌด ํ๋ญ๋๋ค. ๊ทธ๋์ ์ฝ๋๋ก ์ ๋ ฅ๊ฐ๊ณผ ์ถ๋ ฅ๊ฐ์ ํ ์คํธ ์ ์ฌ๋ (Levenshtein ๊ฑฐ๋ฆฌ, ๋ ๋ฒค์ํ์ธ ๊ฑฐ๋ฆฌ)๋ฅผ ๊ณ์ฐํ์ต๋๋ค. ์ ์ฌ๋๊ฐ ์ง๋์น๊ฒ ๋์ผ๋ฉด(๊ฑฐ์ ๋ฒ ๊ปด ์ผ์ผ๋ฉด) ์ฝ๋๊ฐ ๊ฐ์ ๋ก ๊ทธ ๊ฐ์ null๋ก ๋ฎ์ด์์ ๋ฒ๋ฆฝ๋๋ค.
2. ๐ช๏ธ ์ ๋ ฅ๊ฐ ํํฐ๋ง
const filteredLyrics = lyrics.filter((lyric) => {
const detectedLang = detectLanguage(lyric.text);
return detectedLang !== targetLanguage;
});
๋ฒ์ญ ๋์ ์ธ์ด๊ฐ ํ๊ตญ์ด์ด๊ณ ๋ฒ์ญํ ๊ฐ์ฌ๊ฐ "์๋ ํ์ธ์"์ธ๋ฐ, AI๊ฐ ๋ง์ฐฌ๊ฐ์ง๋ก "์๋ ํ์ธ์"๋ฅผ ๊ทธ๋๋ก ๋ฐํํ๋ ๊ฒฝ์ฐ๋ ๋ง์์ต๋๋ค. ๊ฐ์ฌ๊ฐ ์ด๋ฏธ ๋ฒ์ญ ๋์ ์ธ์ด์ธ ๊ฒฝ์ฐ์๋ null์ ๋ฐํํ๋ผ๊ณ ๋ช ๋ นํ์์๋ ๋ถ๊ตฌํ๊ณ ์. ๊ทธ๋์ ์ ๋ ์ด๋ฏธ ๋ฒ์ญ ๋์ ์ธ์ด์ธ ๋ฌธ์ฅ์ ์ ๋ ฅ๊ฐ์์ ์ ์ธํ๋ ํํฐ๋ง ๋ก์ง์ ์ถ๊ฐํ์์ต๋๋ค. 1๋ฒ ๋ฐฉ๋ฒ์ธ ํ ์คํธ ์ ์ฌ๋ ๊ฒ์ฌ ๋ฐฉ๋ฒ๊ณผ๋ ๋ฌ๋ฆฌ, AI์๊ฒ ๊ฐ์ ์ ๋ฌํ๊ธฐ ์ ์ ํํฐ๋งํด๋ฒ๋ฆฌ๋ ๋ฐฉ๋ฒ์ด๊ธฐ ๋๋ฌธ์, ์ ์ถ๋ ฅ ํ ํฐ์ ์ ๊ฐํ ์ ์๋ค๋ ์ถ๊ฐ์ ์ธ ์ด์ ๋ ์์์ต๋๋ค.
import { franc } from "franc-min";
const language = franc(text)
๊ฐ์ฌ๊ฐ ์ด๋ค ์ธ์ด์ธ์ง ๊ตฌ๋ณํ๊ธฐ ์ํด์๋ franc ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ต๋๋ค. ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ franc ํจ์๋ฅผ ์ด์ฉํ๋ฉด ์ ์ฝ๋์ฒ๋ผ ๋งค์ฐ ์์ฝ๊ฒ ๋ฌธ์ฅ์ ์ธ์ด๋ฅผ ํ๋ณํ ์ ์์ต๋๋ค!
3. ๐ ์ฌ์๋
config: { temperature: Math.max(0.1, 0.4 - (retry * 0.3) / MAX_RETRIES) }
๋ฐฐ์ด ๊ธธ์ด๊ฐ ์ ๋ง์ผ๋ฉด ์ด๋ป๊ฒ ํ ๊น์? ๋ต์ด ์์ต๋๋ค. ๋ง์ ๋๊น์ง ๋ค์ ์ํค๋ ์๋ฐ์์. ๋จ, ๊ทธ๋ฅ ๋ค์ ์ํค๋ฉด ๋ ํ๋ฆด ์ ์์ผ๋, ์ฌ์๋ํ ๋๋ง๋ค temperature (์ฐฝ์์ฑ ์์น)๋ฅผ ์กฐ๊ธ์ฉ ๋ฎ์ท์ต๋๋ค. "๋ค์ ํด. ์ด๋ฒ์ ์ข ๋ ์ฐฝ์์ ์ผ๋ก, ๋ ๊ธฐ๊ณ์ ์ผ๋ก ๋ฒ์ญํด" ๋ผ๊ณ ๋ค๊ทธ์น๋ ๋ก์ง์ ๊ตฌํํ ๊ฑฐ์ฃ . (๋ฌผ๋ก ์ด ๋ฐฉ๋ฒ ์ญ์ ๋ณด์กฐ์ ์ธ ์๋จ์ผ ๋ฟ, ์๋ฒฝํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฅํ๋ ์๋จ์ ์๋๋๋ค.)
์ด ๊ณผ์ ์์ ๋ฐฐ์ด ๊ตํ์ ๋ช ํํฉ๋๋ค. "AI ๊ธฐ๋ฅ ๊ตฌํ์์ AI ๋ชจ๋ธ์ ๋ถํ์ผ ๋ฟ์ด๋ค. ๊ทธ ๋ถํ์ ์๋ฒฝํ๊ฒ ๋์ํ๊ฒ ๋ง๋๋ ๊ฑด ๊ฒฐ๊ตญ ๊ฐ๋ฐ์์ ์ง์ ์์ฑํ ์ฝ๋์ด๋ค."
๐ค Typescript + Zod: ํผ๋์ค๋ฌ์ด ๋ฐฑ์๋์ ํํ๋ฅผ

AI์ ์จ๋ฆํ๋ฉฐ ๋ฉํ์ด ๋๊ฐ ๋์ฏค, ์ ๋ฅผ ์น์ ํด ์ค ๊ฑด TypeScript์ Zod์์ต๋๋ค. 1์ธ ๊ฐ๋ฐ์ ์ต๋ ์ฅ์ ์ ํ๋ก ํธ์๋์ ๋ฐฑ์๋๋ฅผ ํผ์ ๋ค ์ง ๋ค๋ ์ ์ด๊ณ , ์ด ์ฅ์ ์ ๊ทน๋ํํ๋ ค๋ฉด ํ์ ๊ณผ ์คํค๋ง๋ฅผ ๊ณต์ ํด์ผ ํฉ๋๋ค.
์ ๋ packages/schemas๋ผ๋ ํด๋๋ฅผ ๋ง๋ค๊ณ , ๊ฑฐ๊ธฐ์ DB ์คํค๋ง์ API์ ์ ์ถ๋ ฅ ์คํค๋ง๋ฅผ Zod๋ก ์ ์ํด์ ์์ชฝ์์ ๊ฐ์ด ์ผ์ต๋๋ค. ํนํ RTDB ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์์์ Discriminated Union ํจํด์ด ๋น์ ๋ฐํ์ต๋๋ค.
export const StoryRequestSchema = z.union([
// status๊ฐ "PENDING"์ผ ๋
z.object({
status: z.literal("PENDING"),
trackId: z.string(),
}),
// ...
// status๊ฐ "COMPLETED"์ผ ๋
z.object({
status: z.literal("COMPLETED"),
generatedStory: StorySchema,
}),
]);
Discriminated Union์ด๋, ์ฌ๋ฌ ๊ฐ์ฒด ํ์ ์ด ๊ณตํต๋ ๋ฆฌํฐ๋ด ํ๋(์ฌ๊ธฐ์๋ status)๋ฅผ ๊ฐ์ง์ผ๋ก์จ, ๊ทธ ํ๋์ ๊ฐ๋ง์ผ๋ก ๊ฐ์ฒด์ ๊ตฌ์ฒด์ ์ธ ํ์ ์ ์๋ณํ ์ ์๊ฒ ํ๋ ํ์ ์คํฌ๋ฆฝํธ์ ํจํด์ ๋๋ค. ์ด๋ ๊ฒ ์ ์ํด๋๋ฉด, ์ฝ๋๋ฅผ ์งค ๋ if (request.status === "PENDING") ์กฐ๊ฑด๋ฌธ ์์ผ๋ก ๋ค์ด๊ฐ๋ ์๊ฐ, ํ์ ์คํฌ๋ฆฝํธ๊ฐ "์ฌ๊ธฐ์ ์์ง generatedStory๋ ์๊ณ trackId๋ ๋ฌด์กฐ๊ฑด ์๋ค" ๋ผ๊ณ ๋๋ํ๊ฒ ์ถ๋ก ํด๋ฒ๋ฆฝ๋๋ค. ์์ ์ฝ๋๋ฅผ ์ดํด๋ณผ๊น์?
๐ Discriminated Union ์ ์ฉ ์ฌ๋ก
const reducer = (_acc, chunk) => {
if (chunk.status === "IN_PROGRESS" || chunk.status === "COMPLETED") {
return {
id: chunk.storyId,
data: chunk,
};
}
}
์ ์ฝ๋๋ ํ๋ก ํธ์๋์์ RTDB์ ์์ฑ๋ storyRequest ๊ฐ์ ์ฒ๋ฆฌํ๋ ํจ์์ ๋๋ค. chunk๋ StoryRequestSchema์ ์คํค๋ง๋ฅผ ๋ฐ๋ฅด๋ ๊ฐ์ผ๋ก, status ๊ฐ์ด "IN_PROGRESS"์ด๊ฑฐ๋ "COMPLETED"์ด๋ฉด overview, lyricsTranslations ๋ฑ ํด์ ์ ๋ณด๊ฐ ์ ํ์ ์ผ๋ก ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ๊ฐ๋ค์ ๊ทธ๋๋ก ๋ฐํํ์ฌ ์ฌ์ฉ์ ํ๋ฉด์ ๋ฟ๋ ค์ฃผ๋ ๊ตฌ์กฐ์ธ ๊ฒ์ด์ฃ .
์ด๊ฒ์ Type Narrowing์ด๋ผ๊ณ ํ๋๋ฐ์, ๋๋ถ์ ์ต์ ๋ ์ฒด์ด๋(?.)์ ๋จ๋ฐํ ํ์๊ฐ ์ฌ๋ผ์ง๋๋ค. AI๊ฐ ๋ฑ์ด๋ด๋ ๋ฐ์ดํฐ๋ ๋ถํ์คํ์ง๋ง, Zod์ด ์ง์ผ์ฃผ๋ ํ์ ์์คํ ๋๋ถ์, ์ปดํ์ผ ์๋ฌ๊ฐ ์ ๋๋ฉด ๋ฐํ์์์๋ ์์ ํ๋ค๋ ํ์ ์ ๊ฐ์ง ์ ์์์ต๋๋ค. ๊ทธ ํ์ ์ ๊ฐ๋ฐ ์์ฐ์ฑ์ ์์ฒญ๋๊ฒ ๋์ฌ์ฃผ์์ต๋๋ค.
โก๏ธ ์ ๋ฆฌ ๋ฐ ๋ค์ ๋จ๊ณ
์ด๋ ๊ฒ ์ฐ์ฌ๊ณก์ ๋์ Lymo์ ๋ฐฑ์๋๊ฐ ์ผ์ถ ์์ฑ๋์์ต๋๋ค.
- Firebase + Genkit์ผ๋ก ์๋ฒ๋ฆฌ์ค AI ํ๊ฒฝ์ ๊ตฌ์ถํ๊ณ ,
- RTDB๋ก ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ์ ๊ตฌํํด๋์ผ๋ฉฐ,
- ํ๋กฌํํธ ์์ง๋์ด๋ง + ๊ฒฌ๊ณ ํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก AI๋ฅผ ์ฐธ๊ต์กํ๊ณ ,
- Typescript + Zod๋ก ๊ฒฌ๊ณ ํ ํ์ ์์ ์ฑ๋ ํ๋ณดํ์ต๋๋ค.
ํผ์์ ์ด ๋ชจ๋ ์์ฌ๊ฒฐ์ ์ ํ๊ณ , ๋๋ฒ๊น ํ๊ณ , ์ฝ์งํ๋ ๊ณผ์ ์ด ์ฐธ ๊ณ ๋๋ฉด์๋ ์ฆ๊ฑฐ์ ์ต๋๋ค. "์ด๊ฒ ๋๋ค?" ํ๋ ์๊ฐ์ ์ง๋ฆฟํจ์ด ๊ฐ๋ฐ์ ๋ฉ์ถ ์ ์๊ฒ ๋ง๋๋ ๊ฒ ๊ฐ๋ค์.
๋ค์ ํธ๋ถํฐ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ๊ธฐ๋ก ๋์ด๊ฐ๊ฒ ์ต๋๋ค. ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!!!