ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฐ˜์‘ํ˜•

๐Ÿ’ธ RAG ๋น„์šฉ ์ตœ์ ํ™” ์‹ค์ „ํŽธ

– “AI ์“ฐ๋‹ค ๋งํ•œ๋‹ค” ์†Œ๋ฆฌ ์•ˆ ๋“ฃ๊ณ , ๋ˆ ์•ˆ ์ƒˆ๊ฒŒ ์„œ๋น„์Šค ์šด์˜ํ•˜๋Š” ๋ฒ•
(OpenAI · Vector DB · NestJS ๊ธฐ์ค€, ์‹ค์ œ ์šด์˜์—์„œ ํšจ๊ณผ ์žˆ์—ˆ๋˜ ๊ฒƒ๋งŒ)


์šด์˜ ๋“ค์–ด๊ฐ€๋ฉด ์ด๋Ÿฐ ์ˆœ๊ฐ„ ์˜ต๋‹ˆ๋‹ค.

“์‚ฌ์šฉ์ž๋Š” ๋Š˜์—ˆ๋Š”๋ฐ…
AI ๋น„์šฉ์ด ์ด์ƒํ•˜๊ฒŒ ๋” ๋นจ๋ฆฌ ๋Š˜์–ด๋‚œ๋‹ค?”

์ด๋•Œ ๋Œ€๋ถ€๋ถ„ ์ด๋ ‡๊ฒŒ ๋Œ€์‘ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ชจ๋ธ์„ ๋ฐ”๊พผ๋‹ค โŒ
  • ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ๋ง‰๋Š”๋‹ค โŒ
  • ๊ทธ๋ƒฅ ๋น„์šฉ ๊ฐ์ˆ˜ํ•œ๋‹ค โŒ

๊ทผ๋ฐ์š”,
RAG ๋น„์šฉ ๋ฌธ์ œ๋Š” ๋Œ€๋ถ€๋ถ„ ๊ตฌ์กฐ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์€ ์ œ๊ฐ€ ์‹ค์ œ๋กœ ๋น„์šฉ์„ ํ™• ์ค„์˜€๋˜ ๋ฐฉ๋ฒ•๋“ค๋งŒ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
(์ด๋ก  ๋ง๊ณ , ์šด์˜์—์„œ ๋จนํžŒ ๊ฒƒ๋งŒ)


๐Ÿงญ ์ด ๊ธ€์˜ ๋ชฉํ‘œ

  • RAG ๋น„์šฉ์ด ์–ด๋””์„œ ์ƒˆ๋Š”์ง€ ์ •ํ™•ํžˆ ๋ถ„ํ•ด
  • “์•ˆ ์จ๋„ ๋˜๋Š” ํ† ํฐ” ์—†์• ๋Š” ๋ฐฉ๋ฒ•
  • ์บ์‹œ/์š”๊ธˆ์ œ/์ฟผํ„ฐ๋ฅผ ๊ตฌ์กฐ๋กœ ์„ค๊ณ„ํ•˜๋Š” ๋ฒ•
  • PM·๋Œ€ํ‘œ·ํŒ€์› ์•ž์—์„œ ์ˆซ์ž๋กœ ์„ค๋ช… ๊ฐ€๋Šฅํ•œ ์šด์˜ ๋งŒ๋“ค๊ธฐ

1๏ธโƒฃ RAG ๋น„์šฉ์€ ์–ด๋””์„œ ํ„ฐ์งˆ๊นŒ? (์ •ํ™•ํžˆ ๋ณด์ž)

RAG ๋น„์šฉ์€ ๋ณดํ†ต ์ด 3๊ณณ์—์„œ ํ„ฐ์ง‘๋‹ˆ๋‹ค.

1. ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ ๋น„์šฉ
2. LLM ์‘๋‹ต ํ† ํฐ ๋น„์šฉ
3. ์ค‘๋ณต·๋ถˆํ•„์š” ์š”์ฒญ

๐Ÿ‘‰ ์ด ์ค‘ 1๋ฒˆ๊ณผ 3๋ฒˆ์€
๊ตฌ์กฐ๋งŒ ์ž˜ ์žก์•„๋„ 50% ์ด์ƒ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


2๏ธโƒฃ ์ฒซ ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “์ž„๋ฒ ๋”ฉ์„ ๋งค๋ฒˆ ๋งŒ๋“ค์ง€ ๋ง ๊ฒƒ”

์ด๊ฑด ์ง„์งœ ๋งŽ์ด๋“ค ๋†“์นฉ๋‹ˆ๋‹ค.

๊ฐ™์€ ์งˆ๋ฌธ
๊ฐ™์€ ๋ฌธ์žฅ
๊ฐ™์€ ์‚ฌ์šฉ์ž

๊ทผ๋ฐ ๋งค๋ฒˆ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ?

์ด๊ฑด ๊ทธ๋ƒฅ ๋ˆ ํƒœ์šฐ๋Š” ๊ตฌ์กฐ์˜ˆ์š”.


โŒ ๋น„์šฉ ์ƒˆ๋Š” ๊ตฌ์กฐ

const embedding = await openai.embeddings.create({
  model: "text-embedding-3-small",
  input: query,
});

→ ์ด ์ฝ”๋“œ,
์š”์ฒญ ์˜ฌ ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋˜๊ณ  ์žˆ๋‹ค๋ฉด ์ด๋ฏธ ๊ณผ๊ธˆ ์ง€์˜ฅ์ž…๋‹ˆ๋‹ค.


โœ… ํ•ด๊ฒฐ: ์ž„๋ฒ ๋”ฉ ์บ์‹œ (Redis ๊ธฐ์ค€)

const key = `embed:${query}`;

const cached = await redis.get(key);
if (cached) {
  return JSON.parse(cached);
}

const embedding = await openai.embeddings.create({
  model: "text-embedding-3-small",
  input: query,
});

await redis.set(key, JSON.stringify(embedding), 'EX', 60 * 60 * 24);
return embedding;

๐Ÿ“Œ ์ฒด๊ฐ ๊ฒฐ๊ณผ

  • ์ž„๋ฒ ๋”ฉ ๋น„์šฉ 60~70% ๊ฐ์†Œ
  • ์‘๋‹ต ์†๋„๋„ ๊ฐ™์ด ๊ฐœ์„ 

๐Ÿ‘‰ ์ž„๋ฒ ๋”ฉ ์บ์‹œ๋Š” ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ํ•„์ˆ˜


3๏ธโƒฃ ๋‘ ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “LLM์— ๋ณด๋‚ด๋Š” ์ปจํ…์ŠคํŠธ ์ค„์ด๊ธฐ”

๋ฐ˜์‘ํ˜•

RAG ๋น„์šฉ์˜ ์ ˆ๋ฐ˜์€ ์—ฌ๊ธฐ์„œ ๋‚˜๊ฐ‘๋‹ˆ๋‹ค.

“์ •ํ™•ํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด
๋ฌธ๋งฅ์„ ๋งŽ์ด ์ค˜์•ผ์ง€!”

โŒ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.
๋งŽ์ด ์ฃผ๋ฉด ๋น„์‹ธ๊ณ , ์˜คํžˆ๋ ค ํ’ˆ์งˆ๋„ ํ”๋“ค๋ฆฝ๋‹ˆ๋‹ค.


โŒ ํ”ํ•œ ์‹ค์ˆ˜

const context = docs.map(d => d.content).join("\n");
  • ์›๋ฌธ ํ†ต์งธ๋กœ
  • ๊ธธ๊ณ 
  • ์ค‘๋ณต ๋งŽ์Œ

โœ… ์‹ค์ „ ๊ธฐ์ค€

  • summary๋งŒ ์‚ฌ์šฉ
  • ๊ธธ์ด ์ œํ•œ
  • 3~5๊ฐœ ๋ฌธ์„œ๊นŒ์ง€๋งŒ
const context = docs
  .slice(0, 4)
  .map(d => `- ${d.summary}`)
  .join("\n");

๐Ÿ“Œ ์ฒด๊ฐ ๊ฒฐ๊ณผ

  • ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ 30~40% ๊ฐ์†Œ
  • hallucination๋„ ๊ฐ™์ด ์ค„์–ด๋“ฆ

4๏ธโƒฃ ์„ธ ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “๋ชจ๋“  ์งˆ๋ฌธ์— RAG ์“ฐ์ง€ ๋ง ๊ฒƒ”

์ด๊ฑด ์ •๋ง ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  ์งˆ๋ฌธ์ด RAG์ผ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.


์‹ค์ œ ์šด์˜์—์„œ ๋‚˜๋ˆˆ ์งˆ๋ฌธ ์œ ํ˜•

์œ ํ˜•์ฒ˜๋ฆฌ ๋ฐฉ์‹

๋‹จ์ˆœ ์„ค๋ช… LLM only
์ •์˜ ์งˆ๋ฌธ ์บ์‹œ ์‘๋‹ต
FAQ DB ์กฐํšŒ
๋ฌธ์„œ ๊ธฐ๋ฐ˜ ์งˆ๋ฌธ RAG
๋ฐ˜๋ณต ์งˆ๋ฌธ ์บ์‹œ

๊ฐ„๋‹จํ•œ ๋ถ„๊ธฐ ์˜ˆ์‹œ

function routeQuery(query: string) {
  if (isSimple(query)) return 'LLM_ONLY';
  if (isFaq(query)) return 'FAQ';
  return 'RAG';
}

๐Ÿ‘‰ ์ด ๋ถ„๊ธฐ ํ•˜๋‚˜๋กœ
RAG ํ˜ธ์ถœ ์ž์ฒด๋ฅผ 20~30% ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


5๏ธโƒฃ ๋„ค ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “์š”๊ธˆ์ œ๋ณ„ ๋ชจ๋ธ ์ „๋žต”

์ด๊ฑด ๋น„์šฉ ๊ตฌ์กฐ๋ฅผ ์™„์ „ํžˆ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

โŒ ์ดˆ๋ฐ˜ ๊ตฌ์กฐ

model: "gpt-4"

→ ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋™์ผ
→ ๋น„์šฉ ์˜ˆ์ธก ๋ถˆ๊ฐ€


โœ… ์‹ค์ „ ์š”๊ธˆ์ œ ์ „๋žต

ํ”Œ๋žœ๋ชจ๋ธ

Free gpt-3.5
Pro gpt-4o-mini
Enterprise ์ƒ์œ„ ๋ชจ๋ธ
const model =
  user.plan === 'FREE'
    ? 'gpt-3.5-turbo'
    : 'gpt-4o-mini';

๐Ÿ“Œ ํฌ์ธํŠธ

  • ์‚ฌ์šฉ์ž๋Š” ์ฒด๊ฐ ๊ฑฐ์˜ ์—†์Œ
  • ๋น„์šฉ ์ฐจ์ด๋Š” ํผ

6๏ธโƒฃ ๋‹ค์„ฏ ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “์‘๋‹ต ์บ์‹œ (์ด๊ฑฐ ์•ˆ ํ•˜๋ฉด ์†ํ•ด)”

๋†€๋ž๊ฒŒ๋„,
๊ฐ™์€ ์งˆ๋ฌธ์„ ํ•˜๋Š” ์‚ฌ์šฉ์ž๋Š” ์ •๋ง ๋งŽ์Šต๋‹ˆ๋‹ค.


์‘๋‹ต ์บ์‹œ ๊ตฌ์กฐ

const key = `answer:${hash(query)}`;

const cached = await redis.get(key);
if (cached) return cached;

const answer = await rag(query);
await redis.set(key, answer, 'EX', 60 * 10);
return answer;

๐Ÿ“Œ ์ ์šฉ ํ›„

  • AI ํ˜ธ์ถœ ํšŸ์ˆ˜ ๋ˆˆ์— ๋„๊ฒŒ ๊ฐ์†Œ
  • ํŠนํžˆ FAQ ์˜์—ญ์—์„œ ํšจ๊ณผ ํผ

7๏ธโƒฃ ์—ฌ์„ฏ ๋ฒˆ์งธ ์ ˆ๊ฐ ํฌ์ธํŠธ: “์‚ฌ์šฉ๋Ÿ‰์„ ๋ˆˆ์— ๋ณด์ด๊ฒŒ ๋งŒ๋“ค ๊ฒƒ”

๋น„์šฉ ์ตœ์ ํ™”์˜ ์‹œ์ž‘์€
“๋ณด๋Š” ๊ฒƒ” ์ž…๋‹ˆ๋‹ค.


์ตœ์†Œํ•œ ์ด๊ฑด ๊ผญ ๊ธฐ๋กํ•˜์„ธ์š”

{
  userId,
  model,
  tokenUsage,
  responseTime,
  costEstimate
}

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ์งˆ๋ฌธ์— ๋‹ตํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์–ด๋–ค ํ”Œ๋žœ์ด ๊ฐ€์žฅ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š”๊ฐ€?
  • ์–ด๋–ค ์งˆ๋ฌธ ์œ ํ˜•์ด ๋ˆ์„ ํƒœ์šฐ๋Š”๊ฐ€?
  • ์บ์‹œ๋กœ ๋ง‰์„ ์ˆ˜ ์žˆ๋Š” ์š”์ฒญ์€?

๐Ÿ‘‰ ์ด๊ฑธ ๋ชจ๋ฅด๋ฉด, ์ตœ์ ํ™”๋Š” ๊ฐ์œผ๋กœ ํ•  ์ˆ˜๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.


8๏ธโƒฃ ์‹ค์ œ๋กœ ์ค„์–ด๋“  ๋น„์šฉ (์šด์˜ ๊ธฐ์ค€)

ํ•ญ๋ชฉ์ „ํ›„

์ž„๋ฒ ๋”ฉ ๋น„์šฉ ๊ธฐ์ค€ -65%
LLM ํ† ํฐ ๋น„์šฉ ๊ธฐ์ค€ -35%
RAG ํ˜ธ์ถœ ์ˆ˜ ๊ธฐ์ค€ -28%
์ด AI ๋น„์šฉ ๊ธฐ์ค€ -42%

๐Ÿ‘‰ ๋ชจ๋ธ ๋ฐ”๊พผ ๊ฑฐ ์—†์Œ
๐Ÿ‘‰ ๊ธฐ๋Šฅ ์ค„์ธ ๊ฑฐ ์—†์Œ
๐Ÿ‘‰ ๊ตฌ์กฐ๋งŒ ๋ฐ”๊ฟˆ


9๏ธโƒฃ ์ง„์งœ ์ค‘์š”ํ•œ ํ•œ ๋ฌธ์žฅ

์ด๊ฑด ๊ผญ ๊ธฐ์–ตํ•˜์„ธ์š”.

AI ๋น„์šฉ์€
“์–ผ๋งˆ ์“ฐ๋А๋ƒ”๊ฐ€ ์•„๋‹ˆ๋ผ
“์–ด๋””์„œ ์“ฐ๋А๋ƒ”์˜ ๋ฌธ์ œ๋‹ค.


 

RAG๋น„์šฉ์ตœ์ ํ™”, AI๋น„์šฉ๊ด€๋ฆฌ, OpenAI์šด์˜, AI์„œ๋น„์Šค์šด์˜, NestJS, RAG๊ตฌ์กฐ, ๋ฒกํ„ฐDB, SaaS์šด์˜, AI๋ฐฑ์—”๋“œ

โ€ป ์ด ํฌ์ŠคํŒ…์€ ์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค ํ™œ๋™์˜ ์ผํ™˜์œผ๋กœ, ์ด์— ๋”ฐ๋ฅธ ์ผ์ •์•ก์˜ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ œ๊ณต๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
ยซ   2026/01   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ
๋ฐ˜์‘ํ˜•