2026๋…„ 2์›” 27์ผ
Architecture

๐Ÿค– RAG ๊ธฐ๋ฐ˜ AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ โ€” ์„ค๊ณ„๋ถ€ํ„ฐ ์ž๋™ํ™”๊นŒ์ง€

Qdrant Hybrid Search + Cohere Rerank + GPT-5 ๊ธฐ๋ฐ˜ RAG ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ MR ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ํ”„๋กœ์ ํŠธ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฒฐํ•ฉํ•œ AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ๋ฅผ ์„ค๊ณ„ยท๊ตฌ์ถ•ํ•˜๊ณ , n8n์œผ๋กœ GitLab MR ์ž๋™ ๋ฆฌ๋ทฐ๊นŒ์ง€ ๊ตฌํ˜„ํ•œ ๊ณผ์ •์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

AI Agent
RAG
Code Review
Qdrant
Cohere Rerank
GPT-5
n8n
GitLab
TypeScript
Vector Search
๐Ÿค– RAG ๊ธฐ๋ฐ˜ AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ โ€” ์„ค๊ณ„๋ถ€ํ„ฐ ์ž๋™ํ™”๊นŒ์ง€

๐Ÿ“… ๊ธ€ ๊ฐœ์š”

์ฝ”๋“œ ๋ฆฌ๋ทฐ๋Š” ์†Œํ”„ํŠธ์›จ์–ด ํ’ˆ์งˆ์˜ ๋งˆ์ง€๋ง‰ ๊ด€๋ฌธ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ˜„์‹ค์—์„œ๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ตฌ์กฐ์ ์ธ ํ•œ๊ณ„๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

"์ด ์ปดํฌ๋„ŒํŠธ์—์„œ useCallback ๋น ์กŒ์–ด์š”", "z-index ํ† ํฐ ๊ทœ์น™ ํ™•์ธํ•ด์ฃผ์„ธ์š”", "์ ‘๊ทผ์„ฑ ์†์„ฑ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”"

๊ฐ™์€ ํ”ผ๋“œ๋ฐฑ์ด ๋งค MR๋งˆ๋‹ค ๋ฐ˜๋ณต๋ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด๋Š” ํ”ผ๋กœํ•ด์ง€๊ณ , ๋ฆฌ๋ทฐ์ด๋Š” ๊ฐ™์€ ์‹ค์ˆ˜๋ฅผ ๋˜ํ’€์ดํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์ฝ”๋”ฉ ๊ทœ์น™ ๋ฌธ์„œ๋Š” ๋ถ„๋ช…ํžˆ ์กด์žฌํ•˜์ง€๋งŒ, MR์„ ์˜ฌ๋ฆด ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ๋ฌธ์„œ๋ฅผ ์ผ์ผ์ด ๋Œ€์กฐํ•˜๋Š” ๊ฒƒ์€ ํ˜„์‹ค์ ์œผ๋กœ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

๋ฐ˜๋ณต ํ”ผ๋“œ๋ฐฑ ์™ธ์—๋„ ๋” ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด๋„ ์‚ฌ๋žŒ์ž…๋‹ˆ๋‹ค. ์—…๋ฌด๊ฐ€ ๋ชฐ๋ฆฌ๊ฑฐ๋‚˜ ์ผ์ •์ด ์ด‰๋ฐ•ํ•œ ์ƒํ™ฉ์—์„œ๋Š” ์•„๋ฌด๋ฆฌ ์ˆ™๋ จ๋œ ๋ฆฌ๋ทฐ์–ด๋ผ ํ•˜๋”๋ผ๋„ ๋†“์น˜๋Š” ๋ถ€๋ถ„์ด ์ƒ๊ธฐ๊ธฐ ๋งˆ๋ จ์ด๋ฉฐ, ์ด๋Ÿฌํ•œ ๋ˆ„๋ฝ์€ ๊ณง ์žฅ์•  ๋ฐœ์ƒ ํ™•๋ฅ ์˜ ์ฆ๊ฐ€๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค:

  • ๋ฆฌ๋ทฐ ํ’ˆ์งˆ์˜ ๊ท ์ผํ™” โ€” ์‚ฌ๋žŒ์˜ ์ปจ๋””์…˜์ด๋‚˜ ์—…๋ฌด ์ƒํ™ฉ์— ๊ด€๊ณ„์—†์ด, ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๊ธฐ๋ฐ˜์˜ ์ผ๊ด€๋œ ์ˆ˜์ค€์˜ ๋ฆฌ๋ทฐ๋ฅผ ํ•ญ์ƒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค
  • ์‚ฌ๊ฐ์ง€๋Œ€ ์‚ฌ์ „ ๋ฐœ๊ฒฌ โ€” ๋ฆฌ๋ทฐ์–ด๊ฐ€ ๋ฏธ์ฒ˜ ๋ณด์ง€ ๋ชปํ•œ ์ ‘๊ทผ์„ฑ ๋ˆ„๋ฝ, ๋ฉ”๋ชจ๋ฆฌ ๋ฆญ ํŒจํ„ด, ๋ณด์•ˆ ์ทจ์•ฝ์  ๊ฐ™์€ ๋œป๋ฐ–์˜ ์ด์Šˆ๋ฅผ ์‚ฌ์ „์— ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • ์ €์—ฐ์ฐจ ๊ฐœ๋ฐœ์ž์˜ ์„ฑ์žฅ ์ง€์› โ€” ๋‹จ์ˆœํžˆ "ํ‹€๋ ธ๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ, ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๋ฌธ์„œ๋ฅผ ๊ทผ๊ฑฐ๋กœ "์™œ ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ํ•˜๋Š”์ง€"๊นŒ์ง€ ์„ค๋ช…ํ•จ์œผ๋กœ์จ ์ฝ”๋“œ๋ฆฌ๋ทฐ์˜ ๋ฐฉํ–ฅ์„ฑ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค

๊ทธ๋ž˜์„œ ์ด ์—์ด์ „ํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ์ฝ”๋”ฉ ๊ทœ์น™๊ณผ ๊ฐ€์ด๋“œ ๋ฌธ์„œ๋ฅผ AI๊ฐ€ ํ•™์Šตํ•˜๊ณ , MR์ด ์˜ฌ๋ผ์˜ค๋ฉด ๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ์— ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฒ€์ƒ‰ํ•˜์—ฌ ์ž๋™์œผ๋กœ ๋ฆฌ๋ทฐ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” ํ•ด๋‹น ์—์ด์ „ํŠธ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ , ๊ตฌํ˜„ํ•˜๊ณ , ์ž๋™ํ™”ํ•œ ์ „์ฒด ๊ณผ์ •์„ ๊ณต์œ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฐ ๋‚ด์šฉ

  • ์ „์ฒด ์•„ํ‚คํ…์ฒ˜ ๋ฐ ๋ฐ์ดํ„ฐ ํ๋ฆ„
  • ์‹ค์ œ MR ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผ๊ฐ€๋ณด๊ธฐ
  • RAG ํŒŒ์ดํ”„๋ผ์ธ ์‹ฌ์ธต ๋ถ„์„: ์ ์‘ํ˜• ์ฒญํ‚น โ†’ Hybrid Search โ†’ RRF โ†’ Cohere Rerank
  • ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง: ํŽ˜๋ฅด์†Œ๋‚˜ ์„ค๊ณ„์™€ ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ
  • n8n ๊ธฐ๋ฐ˜ ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์ถ•
  • ์ง์ ‘ ๊ตฌํ˜„ํ•  ๋•Œ ์•Œ์•„์•ผ ํ•  ํ•ต์‹ฌ ํฌ์ธํŠธ

๐Ÿ—๏ธ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜

๋จผ์ € ์•„ํ‚คํ…์ฒ˜ ๋ฐ ๋ฐ์ดํ„ฐ ํ๋ฆ„๋ถ€ํ„ฐ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. MR์ด ์˜ฌ๋ผ์˜ค๋ฉด ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ๊ฐ€ ์ž‘์„ฑ๋˜๊ธฐ๊นŒ์ง€, ํฌ๊ฒŒ ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ˆ˜์ง‘ โ†’ RAG ๊ฒ€์ƒ‰ โ†’ AI ๋ฆฌ๋ทฐ ์ƒ์„ฑ ์„ธ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์นฉ๋‹ˆ๋‹ค.

RAG ๊ธฐ๋ฐ˜ AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜ - GitLab MR์—์„œ Diff ํŒŒ์‹ฑ, RAG ๊ฒ€์ƒ‰, AI ๋ฆฌ๋ทฐ ์ƒ์„ฑ๊นŒ์ง€์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„

๋ณ€๊ฒฝ์‚ฌํ•ญ ์ˆ˜์ง‘

๋ชจ๋“ˆ ์—ญํ•  ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ
GitLab MR MR ์ •๋ณด(์ œ๋ชฉ, ๋ธŒ๋žœ์น˜, ์„ค๋ช…)์™€ ํŒŒ์ผ๋ณ„ Diff๋ฅผ REST API๋กœ ์ˆ˜์ง‘ MR ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ + ํŒŒ์ผ๋ณ„ Diff
Diff Parser Diff๋ฅผ ํŒŒ์‹ฑํ•˜๊ณ , ๋ณ€๊ฒฝ ๋ผ์ธ ยฑ50์ค„์˜ ์ฃผ๋ณ€ ์ฝ”๋“œ๊นŒ์ง€ ์ˆ˜์ง‘ ๊ตฌ์กฐํ™”๋œ Diff + ์ฃผ๋ณ€ ์ฝ”๋“œ ์ปจํ…์ŠคํŠธ
Keyword Extractor import๋ฌธ, React ํ›…, API๋ช… ๋“ฑ ๊ฒ€์ƒ‰์— ํ™œ์šฉํ•  ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœ ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ ๋ชฉ๋ก

RAG ๊ฒ€์ƒ‰

๋ชจ๋“ˆ ์—ญํ•  ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ
Qdrant Hybrid Search Dense(์˜๋ฏธ ๊ฒ€์ƒ‰) + Sparse(ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰)๋ฅผ ๋™์‹œ์— ์ˆ˜ํ–‰ ํ›„๋ณด ๋ฌธ์„œ 20๊ฐœ
RRF Fusion ๋‘ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ˆœ์œ„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ‘ํ•ฉ ์ˆœ์œ„ ํ†ตํ•ฉ๋œ 20๊ฐœ ๋ฌธ์„œ
Cohere Rerank ์ „์šฉ ๋ฆฌ๋žญํ‚น ๋ชจ๋ธ๋กœ ๊ด€๋ จ์„ฑ์„ ์žฌํ‰๊ฐ€ํ•˜์—ฌ ์ƒ์œ„ 5๊ฐœ ์„ ๋ณ„ ์ตœ์ข… RAG ๋ฌธ์„œ 5๊ฐœ

AI ๋ฆฌ๋ทฐ ์ƒ์„ฑ

๋ชจ๋“ˆ ์—ญํ•  ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ
Prompt Builder ํ”„๋กœ์ ํŠธ ๊ทœ์น™ + RAG ๋ฌธ์„œ + Diff + ์ฃผ๋ณ€ ์ฝ”๋“œ๋ฅผ ํ”„๋กฌํ”„ํŠธ๋กœ ์กฐ๋ฆฝ System + User ํ”„๋กฌํ”„ํŠธ
GPT-5 JSON Mode๋กœ ๊ตฌ์กฐํ™”๋œ ๋ฆฌ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  Zod ์Šคํ‚ค๋งˆ๋กœ ๊ฒ€์ฆ severity ร— category ๊ธฐ๋ฐ˜ ๋ฆฌ๋ทฐ JSON
GitLab Comments ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๋ฅผ MR์— ์š”์•ฝ ์ฝ”๋ฉ˜ํŠธ + ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ๋กœ ์ž๋™ ์ž‘์„ฑ ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ ๊ฒŒ์‹œ ์™„๋ฃŒ

๐Ÿข ๋น„๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ๋น„์œ  โ€” "AI ์›๊ณ  ๊ต์ • ์‹œ์Šคํ…œ"

์ถœํŒ์‚ฌ์—์„œ ์›๊ณ ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด, ๊ต์ •ํŒ€์ด (1) ์›๊ณ ์˜ ๋ณ€๊ฒฝ ๋ถ€๋ถ„์„ ํŒŒ์•…ํ•˜๊ณ , (2) ์‚ฌ๋‚ด ๊ต์ • ์ง€์นจ์„œ์—์„œ ๊ด€๋ จ ๊ทœ์น™์„ ์ฐพ์•„์˜ค๊ณ , (3) ๊ทœ์น™๊ณผ ์›๊ณ ๋ฅผ ํ•จ๊ป˜ ๋ณด๋ฉด์„œ ๊ต์ • ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋‹ฌ์•„์ฃผ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ๋„ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์›๊ณ  = ์ฝ”๋“œ ๋ณ€๊ฒฝ์‚ฌํ•ญ, ๊ต์ • ์ง€์นจ์„œ = ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๋ฌธ์„œ, ๊ต์ • ์ฝ”๋ฉ˜ํŠธ = ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ์ž…๋‹ˆ๋‹ค.


๐Ÿ” ์‹ค์ œ MR ๋ฆฌ๋ทฐ ๋”ฐ๋ผ๊ฐ€๋ณด๊ธฐ

์„ค๋ช…๋ณด๋‹ค ์ง์ ‘ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด ์ดํ•ด๊ฐ€ ๋น ๋ฆ…๋‹ˆ๋‹ค. ์‹ค์ œ MR์— AI๊ฐ€ ๋‚จ๊ธด ๋ฆฌ๋ทฐ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผ๊ฐ€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ „์ฒด ์š”์•ฝ ๋ฆฌ๋ทฐ

MR์ด ์˜ฌ๋ผ์˜ค๋ฉด, ์—์ด์ „ํŠธ๊ฐ€ ๋จผ์ € ์ „์ฒด ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ถ„์„ํ•œ ์š”์•ฝ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋‚จ๊น๋‹ˆ๋‹ค.

AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ ์š”์•ฝ ๋ฆฌ๋ทฐ - Risk Level MEDIUM, ๊ธ์ • ํ”ผ๋“œ๋ฐฑ๊ณผ ๊ฐœ์„  ํฌ์ธํŠธ, RAG ์ฐธ์กฐ ๋ฌธ์„œ 5๊ฐœ ํฌํ•จ

๋ˆˆ์—ฌ๊ฒจ๋ณผ ์ ์ด ๋ช‡ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค:

  • Risk Level: ๐ŸŸก MEDIUM โ€” ๋ณ€๊ฒฝ์‚ฌํ•ญ์˜ ์œ„ํ—˜๋„๋ฅผ 3๋‹จ๊ณ„(LOW/MEDIUM/HIGH)๋กœ ์ž๋™ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค
  • ๊ธ์ •์  ํ”ผ๋“œ๋ฐฑ ๋จผ์ € โ€” "CSF3๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์‹  ์  ๋„ˆ๋ฌด ์ข‹์Šต๋‹ˆ๋‹ค! ๐ŸŽ‰" ์ฒ˜๋Ÿผ ์ž˜ํ•œ ์ ์„ ๋จผ์ € ์งš์–ด์ค๋‹ˆ๋‹ค
  • ๊ฐœ์„  ํฌ์ธํŠธ โ€” ๊ตฌ์ฒด์ ์ธ ๊ฐœ์„  ์ œ์•ˆ์„ ๋ฆฌ์ŠคํŠธ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • ๐Ÿ“š ์ฐธ์กฐ ๋ฌธ์„œ (5๊ฐœ) โ€” RAG๋กœ ๊ฒ€์ƒ‰ํ•œ ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ๋ฅผ ๊ทผ๊ฑฐ๋กœ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. "์™œ ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ํ•˜๋Š”์ง€"์˜ ์ถœ์ฒ˜๊ฐ€ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค

์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ

์š”์•ฝ๋ฟ ์•„๋‹ˆ๋ผ, ์ฝ”๋“œ์˜ ํŠน์ • ์ค„์— ์ง์ ‘ ์ฝ”๋ฉ˜ํŠธ๋„ ๋‹ฌ๋ฆฝ๋‹ˆ๋‹ค.

AI ์ฝ”๋“œ๋ฆฌ๋ทฐ ์—์ด์ „ํŠธ ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ - aria-haspopup ์šฉ๋„ ํ™•์ธ, z-index ํ† ํฐ ์ •์ฑ… ํ™•์ธ ๋“ฑ ์ฝ”๋“œ ์ค„ ๋‹จ์œ„ ํ”ผ๋“œ๋ฐฑ

์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ์—๋Š” ์‹ฌ๊ฐ๋„ ๋ผ๋ฒจ์ด ๋ถ™์Šต๋‹ˆ๋‹ค:

์‹ฌ๊ฐ๋„ ๋ผ๋ฒจ ์˜๋ฏธ
๐Ÿšจ critical ๊ผญ ์ˆ˜์ •์ด ํ•„์š”ํ•ด์š” ๋ฒ„๊ทธ, ๋ณด์•ˆ ์ด์Šˆ ๋“ฑ ๋ฐ˜๋“œ์‹œ ์ˆ˜์ •
โš ๏ธ warning ์ˆ˜์ •์„ ๊ถŒ์žฅ๋“œ๋ ค์š” ์„ฑ๋Šฅ, ์ ‘๊ทผ์„ฑ ๋“ฑ ๊ฐ•๋ ฅ ๊ถŒ์žฅ
๐Ÿ” check ํ•œ๋ฒˆ ํ™•์ธํ•ด๋ด์ฃผ์„ธ์š” ์˜๋„ ํ™•์ธ์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„
๐Ÿ’ก suggestion ์ด๋Ÿฐ ๋ฐฉ๋ฒ•๋„ ์žˆ์–ด์š” ๋” ๋‚˜์€ ๋Œ€์•ˆ ์ œ์‹œ
โœ๏ธ nitpick ์ฐธ๊ณ ๋งŒ ํ•ด์ฃผ์„ธ์š” ์‚ฌ์†Œํ•œ ์Šคํƒ€์ผ ๊ฐœ์„ 

์˜ˆ๋ฅผ ๋“ค์–ด ์œ„ ์ด๋ฏธ์ง€์—์„œ aria-haspopup="dialog" ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด, ์—์ด์ „ํŠธ๊ฐ€ "์‹ค์ œ๋กœ dialog ์—ญํ• ์„ ํ•˜๋Š”์ง€ ํ™•์ธ"์„ ์ œ์•ˆํ•˜๊ณ , z-index: 1020 ์ถ”๊ฐ€์— ๋Œ€ํ•ด "์ „์ฒด ๋ ˆ์ด์–ด๋ง ์ •์ฑ…๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€" ํ™•์ธ์„ ์š”์ฒญํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“š RAG ํŒŒ์ดํ”„๋ผ์ธ ์‹ฌ์ธต ๋ถ„์„

์ด๋ฒˆ์—๋Š” ํ•ต์‹ฌ์ธ RAG ํŒŒ์ดํ”„๋ผ์ธ์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. "ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๋ฌธ์„œ์—์„œ ๊ด€๋ จ ๋‚ด์šฉ์„ ์ฐพ์•„์˜จ๋‹ค"๋Š” ๊ฒƒ์ด ๊ตฌ์ฒด์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

1. ์ ์‘ํ˜• ์ฒญํ‚น โ€” ๋ฌธ์„œ๋ฅผ ๋˜‘๋˜‘ํ•˜๊ฒŒ ๋‚˜๋ˆ„๊ธฐ

ํ”„๋กœ์ ํŠธ์˜ CLAUDE.md, SKILL.md ๊ฐ™์€ ๊ทœ์น™ ๋ฌธ์„œ๋ฅผ ๋ฒกํ„ฐ DB์— ๋„ฃ์œผ๋ ค๋ฉด ๋จผ์ € ์ ์ ˆํ•œ ํฌ๊ธฐ๋กœ ๋‚˜๋ˆ ์•ผ(chunking) ํ•ฉ๋‹ˆ๋‹ค. ํ•ต์‹ฌ์€ ๋ฌธ์„œ ์œ ํ˜•์— ๋”ฐ๋ผ ๋‚˜๋ˆ„๋Š” ์ „๋žต์ด ๋‹ค๋ฅด๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

function detectDocType(source: string): 'markdown' | 'code' | 'html' | 'text' {
  if (source.endsWith('.md')) return 'markdown';
  if (source.endsWith('.ts') || source.endsWith('.tsx')) return 'code';
  if (source.endsWith('.html')) return 'html';
  return 'text';
}
๋ฌธ์„œ ์œ ํ˜• ๋ถ„ํ•  ์ „๋žต ์™œ?
Markdown ํ—ค๋”ฉ(#, ##, ###) ๊ธฐ์ค€ ํ•˜๋‚˜์˜ ์„น์…˜ = ํ•˜๋‚˜์˜ ๊ฐœ๋… ๋‹จ์œ„
Code export, function, class ๋‹จ์œ„ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜ = ํ•˜๋‚˜์˜ ๊ฒ€์ƒ‰ ๋‹จ์œ„
HTML ๊ตฌ์กฐ ํƒœ๊ทธ ๊ธฐ์ค€ DOM ๊ตฌ์กฐ๋ฅผ ์กด์ค‘
๊ธฐํƒ€ ํฌ๊ธฐ ๊ธฐ๋ฐ˜ (์ฝ”๋“œ ๋ธ”๋ก ๋ณด์กด) ์•ˆ์ „ํ•œ ํด๋ฐฑ

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ทœ์น™์ด ์žˆ์Šต๋‹ˆ๋‹ค: ์ฝ”๋“œ ๋ธ”๋ก ์•ˆ์—์„œ๋Š” ์ ˆ๋Œ€ ์ž๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

let inCodeBlock = false;
for (const line of lines) {
  if (line.startsWith('```')) {
    inCodeBlock = !inCodeBlock;
  }
  if (currentTokens > targetSize && !inCodeBlock) {
    // ์—ฌ๊ธฐ์„œ๋งŒ ์ž๋ฆ„ โ€” ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ–์—์„œ๋งŒ!
    chunks.push(current.join('\n'));
  }
}

Markdown ๋ฌธ์„œ ์•ˆ์— ์ฝ”๋“œ ์˜ˆ์‹œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์„ ๋•Œ, ์ฝ”๋“œ ๋ธ”๋ก์ด ์ค‘๊ฐ„์— ์ž˜๋ฆฌ๋ฉด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์˜๋ฏธ ์—†์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

2. Hybrid Search โ€” ๋†“์น˜์ง€ ์•Š๋Š” ๊ฒ€์ƒ‰

์ฒญํ‚น๋œ ๋ฌธ์„œ๊ฐ€ Qdrant์— ์ €์žฅ๋˜๋ฉด, MR์ด ์˜ฌ๋ผ์˜ฌ ๋•Œ ๋ณ€๊ฒฝ์‚ฌํ•ญ๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๊ฒ€์ƒ‰์„ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

๐Ÿ“š "๋‘ ๋ช…์˜ ์‚ฌ์„œ" ๋น„์œ 

๋„์„œ๊ด€์—์„œ "useCallback ๋ฉ”๋ชจ์ด์ œ์ด์…˜" ๊ด€๋ จ ์ž๋ฃŒ๋ฅผ ์ฐพ๋Š”๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์‚ฌ์„œ A (Dense Search) โ€” ์˜๋ฏธ๋กœ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. "ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ์žฌ์ƒ์„ฑ์„ ๋ฐฉ์ง€ํ•˜๋Š” ํŒจํ„ด"์ด๋ผ๋Š” ์˜๋ฏธ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋น„์Šทํ•œ ๊ฐœ๋…์˜ ๋ฌธ์„œ๋ฅผ ์ฐพ์•„์˜ต๋‹ˆ๋‹ค. "useCallback"์ด๋ž€ ๋‹จ์–ด๊ฐ€ ์—†์–ด๋„ "๋ฉ”๋ชจ์ด์ œ์ด์…˜ ํŒจํ„ด" ๋ฌธ์„œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์„œ B (Sparse Search) โ€” ๋‹จ์–ด๋กœ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. "useCallback"์ด๋ผ๋Š” ์ •ํ™•ํ•œ ๊ธ€์ž๊ฐ€ ๋“ค์–ด๊ฐ„ ๋ฌธ์„œ๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. ๊ณ ์œ  ํ•จ์ˆ˜๋ช…์ด๋‚˜ API๋ช…์„ ์ •ํ™•ํžˆ ๋งค์นญํ•ฉ๋‹ˆ๋‹ค.

Hybrid Search โ€” ๋‘ ์‚ฌ์„œ์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์นฉ๋‹ˆ๋‹ค. ์˜๋ฏธ๋„ ๋งž๊ณ , ์ •ํ™•ํ•œ ํ‚ค์›Œ๋“œ๋„ ํฌํ•จ๋œ ๋ฌธ์„œ๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

์‹ค์ œ ์ฝ”๋“œ์—์„œ๋Š” Qdrant์˜ prefetch + fusion: 'rrf'๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค:

const results = await qdrant.client.query(collection, {
  prefetch: [
    { query: queryVector, using: 'dense', limit: 20 },
    { query: { indices: sparseIndices, values: sparseValues }, using: 'sparse', limit: 20 },
  ],
  query: { fusion: 'rrf' },
  limit: 20,
  with_payload: true,
});

3. RRF ํ“จ์ „ โ€” ์ˆœ์œ„๋กœ ํ•ฉ์น˜๊ธฐ

Dense ๊ฒ€์ƒ‰๊ณผ Sparse ๊ฒ€์ƒ‰์€ ์ ์ˆ˜ ์ฒด๊ณ„๊ฐ€ ์„œ๋กœ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. Dense๋Š” 0~1 ์‚ฌ์ด์˜ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„, Sparse๋Š” TF-IDF ์ ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ์ ์ˆ˜๋ฅผ ํ•ฉ์‚ฐํ•˜๋ฉด ๋ถˆ๊ณต์ •ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค.

RRF(Reciprocal Rank Fusion) ๋Š” ์ ์ˆ˜ ๋Œ€์‹  ์ˆœ์œ„๋งŒ ๋ณด๊ณ  ํ•ฉ์นฉ๋‹ˆ๋‹ค:

๐Ÿ—ณ๏ธ "์ˆœ์œ„ ํˆฌํ‘œ" ๋น„์œ 

๋‘ ๋ช…์˜ ์‹ฌ์‚ฌ์œ„์›์ด ๊ฐ๊ฐ "์ถ”์ฒœ ๋ฌธ์„œ Top 10"์„ ์คฌ์Šต๋‹ˆ๋‹ค.

  • A ์‹ฌ์‚ฌ์œ„์›: 1์œ„ ๋ฌธ์„œX, 2์œ„ ๋ฌธ์„œY, 3์œ„ ๋ฌธ์„œZ...
  • B ์‹ฌ์‚ฌ์œ„์›: 1์œ„ ๋ฌธ์„œZ, 2์œ„ ๋ฌธ์„œX, 3์œ„ ๋ฌธ์„œW...

RRF๋Š” ์–‘์ชฝ์—์„œ ๋ชจ๋‘ ๋†’์€ ์ˆœ์œ„๋ฅผ ๋ฐ›์€ ๋ฌธ์„œ๋ฅผ ์ตœ์ข… ์ƒ์œ„๋กœ ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค. ๋ฌธ์„œX๋Š” A์—์„œ 1์œ„ + B์—์„œ 2์œ„์ด๋‹ˆ ์ตœ์ข… 1์œ„๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

Qdrant๊ฐ€ RRF๋ฅผ ๋‚ด์žฅ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ณ„๋„์˜ ๋ณ‘ํ•ฉ ์ฝ”๋“œ ์—†์ด fusion: 'rrf' ํ•œ ์ค„๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. Cohere Rerank โ€” ์ตœ์ข… ๋ฉด์ ‘๊ด€

RRF๋กœ 20๊ฐœ์˜ ํ›„๋ณด๋ฅผ ์„ ์ •ํ–ˆ์ง€๋งŒ, ์‹ค์ œ๋กœ ํ”„๋กฌํ”„ํŠธ์— ํฌํ•จํ•  ๋ฌธ์„œ๋Š” 5๊ฐœ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ Cohere Rerank ์ „์šฉ ๋ชจ๋ธ์ด ๊ฐ ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์ฝ๊ณ  ๊ด€๋ จ์„ฑ ์ ์ˆ˜๋ฅผ ๋งค๊น๋‹ˆ๋‹ค.

async function rerankWithCohere(documents, diffSummary, finalTopK) {
  const docTexts = documents.map(
    doc => `[${doc.source}] ${doc.heading}\n${doc.content}`
  );
  const results = await cohereClient.rerank(
    diffSummary.slice(0, 4000),  // ์ฟผ๋ฆฌ: Diff ์š”์•ฝ
    docTexts,                     // ํ›„๋ณด ๋ฌธ์„œ ์ „์ฒด
    finalTopK,                    // ์ƒ์œ„ 5๊ฐœ๋งŒ
  );
  return results.map(r => documents[r.index]);
}

๋งŒ์•ฝ Cohere API๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? LLM Rerank๋กœ ์ž๋™ ํด๋ฐฑ๋ฉ๋‹ˆ๋‹ค:

try {
  return await rerankWithCohere(documents, diffSummary, finalTopK);
} catch (error) {
  logger.warn('Cohere Rerank ์‹คํŒจ, LLM Rerank๋กœ ํด๋ฐฑ');
  return await rerankWithLLM(chatClient, documents, diffSummary, finalTopK);
}

LLM Rerank์€ GPT-5์—๊ฒŒ "์ด ๋ฌธ์„œ๋“ค ์ค‘ Diff์™€ ๊ฐ€์žฅ ๊ด€๋ จ ๋†’์€ 5๊ฐœ๋ฅผ ์„ ๋ณ„ํ•ด ์ฃผ์„ธ์š”"๋ผ๊ณ  ์š”์ฒญํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. Cohere ๋Œ€๋น„ ์†๋„์™€ ๋น„์šฉ ๋ฉด์—์„œ ๋ถˆ๋ฆฌํ•˜์ง€๋งŒ, ์„œ๋น„์Šค๊ฐ€ ์ค‘๋‹จ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.


๐ŸŽฏ ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง

RAG๋กœ ์ฐพ์•„์˜จ ๋ฌธ์„œ์™€ Diff๋ฅผ ์–ด๋–ป๊ฒŒ ํ”„๋กฌํ”„ํŠธ๋กœ ์กฐ๋ฆฝํ•˜๋А๋ƒ์— ๋”ฐ๋ผ ๋ฆฌ๋ทฐ ํ’ˆ์งˆ์ด ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.

์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ โ€” "๋”ฐ๋œปํ•œ ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž"

๋‹น์‹ ์€ ํŒ€์›์˜ ์„ฑ์žฅ์„ ์ง„์‹ฌ์œผ๋กœ ์‘์›ํ•˜๋Š” ๋”ฐ๋œปํ•˜๊ณ  ์‹ค๋ ฅ ์žˆ๋Š” ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

์ด ํ•œ ์ค„์˜ ํŽ˜๋ฅด์†Œ๋‚˜ ์„ค์ •์ด ๋ฆฌ๋ทฐ ํ†ค์„ ์™„์ „ํžˆ ๋ฐ”๊ฟ”์ค๋‹ˆ๋‹ค. "์ด๊ฑฐ ํ‹€๋ ธ์–ด์š”"๊ฐ€ ์•„๋‹Œ "์ด๋Ÿฐ ๋ฐฉ๋ฒ•๋„ ์žˆ์–ด์š” ๐Ÿ˜Š" ๊ฐ™์€ ๊ฑด์„ค์ ์ธ ํ”ผ๋“œ๋ฐฑ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋Š” ๋‹ค์Œ ์„ธ ํŒŒํŠธ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค:

ํŒŒํŠธ ๋‚ด์šฉ ์—ญํ• 
ํŽ˜๋ฅด์†Œ๋‚˜ ๋”ฐ๋œปํ•œ ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž ๋ฆฌ๋ทฐ ํ†ค ๊ฒฐ์ •
ํ”„๋กœ์ ํŠธ ๊ทœ์น™ CLAUDE.md, SKILL.md ๋“ฑ ํ”„๋กœ์ ํŠธ ๊ณ ์œ  ๊ธฐ์ค€
RAG ๋ฌธ์„œ Hybrid Search + Rerank ๊ฒฐ๊ณผ (์ƒ์œ„ 5๊ฐœ) ๋ณ€๊ฒฝ์‚ฌํ•ญ ๊ด€๋ จ ์ปจํ…์ŠคํŠธ

JSON Mode + Zod ๊ฒ€์ฆ โ€” ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ

AI์˜ ์‘๋‹ต์„ ํŒŒ์‹ฑ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๊ฐ•์ œํ•˜๊ธฐ ์œ„ํ•ด JSON Mode๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

const response = await chatClient.chatCompletion({
  messages,
  response_format: { type: 'json_object' },
});

์‘๋‹ต์€ Zod ์Šคํ‚ค๋งˆ๋กœ ์ฆ‰์‹œ ๊ฒ€์ฆ๋ฉ๋‹ˆ๋‹ค:

const aiResponseSchema = z.object({
  summary: z.string(),
  riskLevel: z.enum(['low', 'medium', 'high']),
  comments: z.array(z.object({
    filePath: z.string().optional(),
    line: z.number().int().positive().optional(),
    title: z.string(),
    body: z.string(),
    severity: z.enum(['critical', 'warning', 'check', 'suggestion', 'nitpick']),
    category: z.string(),
    suggestion: z.string().optional(),
  })),
});

const parsed = aiResponseSchema.safeParse(JSON.parse(response.content));

๊ฒ€์ฆ์„ ํ†ต๊ณผํ•œ ์ฝ”๋ฉ˜ํŠธ๋Š” ์‹ฌ๊ฐ๋„ ์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜์–ด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ด์Šˆ๋ถ€ํ„ฐ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ตœ๋Œ€ 15๊ฐœ๊นŒ์ง€ ์ œํ•œํ•˜์—ฌ ๋ฆฌ๋ทฐ๊ฐ€ ๋ถ€๋‹ด์Šค๋Ÿฝ์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Diff ยฑ50์ค„ โ€” ๋งฅ๋ฝ์„ ์ดํ•ด์‹œํ‚ค๊ธฐ

AI๊ฐ€ ๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ๋งŒ ๋ณด๋ฉด ๋งฅ๋ฝ์„ ๋†“์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ณ€๊ฒฝ ๋ผ์ธ ๊ธฐ์ค€ ์œ„์•„๋ž˜ 50์ค„์˜ ์ฃผ๋ณ€ ์ฝ”๋“œ๋ฅผ ํ•จ๊ป˜ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค:

const CONTEXT_LINES = 50;
const minLine = Math.max(1, Math.min(...changedLineNumbers) - CONTEXT_LINES);
const maxLine = Math.min(lines.length, Math.max(...changedLineNumbers) + CONTEXT_LINES);

const surroundingCode = lines
  .slice(minLine - 1, maxLine)
  .map((line, i) => `${minLine + i}| ${line}`)
  .join('\n');

๋ผ์ธ ๋ฒˆํ˜ธ๋ฅผ ํ•จ๊ป˜ ํฌํ•จํ•˜๊ธฐ ๋•Œ๋ฌธ์—, AI๊ฐ€ "78๋ฒˆ ์ค„์—์„œ..." ๋ผ๊ณ  ์ •ํ™•ํ•œ ์œ„์น˜๋ฅผ ์ง€๋ชฉํ•˜๋Š” ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


โšก n8n ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ์šฐ

์—์ด์ „ํŠธ์˜ ์„ฑ๋Šฅ์ด ์•„๋ฌด๋ฆฌ ์ข‹์•„๋„, MR๋งˆ๋‹ค ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์‹ค์šฉ์„ฑ์ด ๋–จ์–ด์ง‘๋‹ˆ๋‹ค. n8n์„ ํ™œ์šฉํ•˜์—ฌ ์™„์ „ ์ž๋™ํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

n8n ์›Œํฌํ”Œ๋กœ์šฐ - GitLab Webhook์—์„œ Event Filter, AI Code Review, Respond๊นŒ์ง€์˜ ์ž๋™ํ™” ํ๋ฆ„

๋™์ž‘ ํ๋ฆ„

โ‘  GitLab Webhook โ€” MR์ด ์ƒ์„ฑ/์—…๋ฐ์ดํŠธ๋˜๋ฉด GitLab์ด n8n ์›นํ›…(POST /webhook/gitlab-mr-review)์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

โ‘ก Event Filter โ€” ๋ถˆํ•„์š”ํ•œ ์‹คํ–‰์„ ๊ฑธ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค:

// Draft MR์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
if (attrs.work_in_progress || attrs.draft) return skip;

// ๋ด‡์ด ๋งŒ๋“  MR์€ ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€)
if (String(attrs.author_id) === botUserId) return skip;

// opened, update, reopen ์ด๋ฒคํŠธ๋งŒ ์ฒ˜๋ฆฌ
if (!['open', 'update', 'reopen'].includes(attrs.action)) return skip;

ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ๋‘ ๊ฐˆ๋ž˜๋กœ ๋ณ‘๋ ฌ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค:

โ‘ข-A Teams ์•Œ๋ฆผ (์ƒ๋‹จ ๋ถ„๊ธฐ) โ€” MR ์ •๋ณด(title, author, branch, labels)๋ฅผ Adaptive Card JSON์œผ๋กœ ๊ฐ€๊ณตํ•˜์—ฌ Teams Webhook์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด์—๊ฒŒ "MR ๋ฆฌ๋ทฐ ์š”์ฒญ" ์•Œ๋ฆผ์ด ์ฆ‰์‹œ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค.

โ‘ข-B AI Code Review (ํ•˜๋‹จ ๋ถ„๊ธฐ) โ€” @support/code-review-agent ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์—์„œ Azure OpenAI(GPT-5), Qdrant(Hybrid Search), Cohere(Rerank v4.0) ์„ธ ๊ฐ€์ง€ ์™ธ๋ถ€ ์„œ๋น„์Šค์™€ ํ†ต์‹ ํ•˜๋ฉฐ RAG ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๋Š” GitLab API๋กœ MR์— ์š”์•ฝ + ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

โ‘ฃ Respond โ€” ์–‘์ชฝ ๋ถ„๊ธฐ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์›นํ›…์— 200 OK๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

Docker Compose ๊ตฌ์„ฑ

n8n, Qdrant, ์—์ด์ „ํŠธ๋ฅผ ํ•จ๊ป˜ ์‹คํ–‰ํ•˜๋Š” Docker Compose ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

services:
  n8n:
    build:
      context: .
      dockerfile: Dockerfile  # @support/code-review-agent ํฌํ•จ ์ปค์Šคํ…€ ๋นŒ๋“œ
    environment:
      - NODE_FUNCTION_ALLOW_EXTERNAL=@support/code-review-agent
      - GITLAB_TOKEN=${GITLAB_TOKEN}
      - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY}
    ports:
      - "5678:5678"

  qdrant:
    image: qdrant/qdrant:latest
    ports:
      - "6333:6333"

NODE_FUNCTION_ALLOW_EXTERNAL๋กœ n8n Code ๋…ธ๋“œ์—์„œ ์™ธ๋ถ€ ํŒจํ‚ค์ง€ ์‚ฌ์šฉ์„ ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.


๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

์—ญํ•  ๊ธฐ์ˆ  ์„ ํƒ ์ด์œ 
AI ๋ฆฌ๋ทฐ ์ƒ์„ฑ Azure OpenAI GPT-5 (JSON Mode) ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ + ํ•œ๊ตญ์–ด ํ’ˆ์งˆ
๋ฌธ์„œ ์ž„๋ฒ ๋”ฉ text-embedding-3-large (3,072์ฐจ์›) ๊ณ ์ฐจ์›์œผ๋กœ ์˜๋ฏธ ๊ตฌ๋ถ„๋ ฅ ์šฐ์ˆ˜
๋ฒกํ„ฐ DB Qdrant (Dense + Sparse) Hybrid Search + RRF ๋‚ด์žฅ ์ง€์›
๋ฆฌ๋žญํ‚น Cohere Rerank v4.0-fast ์ „์šฉ ๋ชจ๋ธ๋กœ ๋น ๋ฅด๊ณ  ์ •ํ™•
Git ํ†ตํ•ฉ GitLab REST API MR/Diff ์กฐํšŒ, ์ฝ”๋ฉ˜ํŠธ ์ž‘์„ฑ
์ž๋™ํ™” n8n + Docker Compose ๋…ธ์ฝ”๋“œ ์›Œํฌํ”Œ๋กœ์šฐ + ์ปจํ…Œ์ด๋„ˆ
์Šคํ‚ค๋งˆ ๊ฒ€์ฆ Zod ๋Ÿฐํƒ€์ž„ ํƒ€์ž… ์•ˆ์ „์„ฑ
๋กœ๊น… pino ๊ตฌ์กฐํ™”๋œ JSON ๋กœ๊น…
๋นŒ๋“œ Vite (๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋“œ) ESM/CJS ๋“€์–ผ ๋นŒ๋“œ
CLI Commander.js ์„œ๋ธŒ์ปค๋งจ๋“œ ๊ธฐ๋ฐ˜ CLI
์–ธ์–ด TypeScript ํƒ€์ž… ์•ˆ์ „์„ฑ + DX

๐Ÿš€ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด?

์ด ์‹œ์Šคํ…œ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์‹ ๋‹ค๋ฉด, ๋‹ค์Œ 5๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ผ๊ฐ€์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Step 1. ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๋ฌธ์„œ ์ •๋ฆฌ

AI๊ฐ€ ์ฐธ๊ณ ํ•  ๋ฌธ์„œ๋ฅผ ๋จผ์ € ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. CLAUDE.md, CODING_GUIDE.md, SKILL.md ๋“ฑ ํŒ€์—์„œ ์ด๋ฏธ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ทœ์น™ ๋ฌธ์„œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

docs/
โ”œโ”€โ”€ CLAUDE.md           # ํ”„๋กœ์ ํŠธ ์ฝ”๋”ฉ ๊ทœ์น™
โ”œโ”€โ”€ SKILL.md            # ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๊ฐ€์ด๋“œ
โ””โ”€โ”€ best-practices/
    โ”œโ”€โ”€ react-hooks.md  # React ํ›… ์‚ฌ์šฉ ํŒจํ„ด
    โ””โ”€โ”€ accessibility.md # ์ ‘๊ทผ์„ฑ ๊ฐ€์ด๋“œ

๐Ÿ’ก ์œ ์˜์‚ฌํ•ญ: ๋ฌธ์„œ๊ฐ€ ์ตœ์‹  ์ƒํƒœ์ธ์ง€๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋ž˜๋œ ๊ทœ์น™์„ AI๊ฐ€ ์ฐธ์กฐํ•˜๋ฉด ์˜คํžˆ๋ ค ํ˜ผ๋ž€์„ ์ค๋‹ˆ๋‹ค.

Step 2. ๋ฌธ์„œ๋ฅผ ๋ฒกํ„ฐ DB์— ์ธ๋ฑ์‹ฑ

๊ทœ์น™ ๋ฌธ์„œ๋ฅผ ์ฒญํ‚น โ†’ ์ž„๋ฒ ๋”ฉ โ†’ Qdrant์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. CLI๋กœ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค:

code-review-agent build-index

๋‚ด๋ถ€์ ์œผ๋กœ Dense ๋ฒกํ„ฐ(Azure OpenAI ์ž„๋ฒ ๋”ฉ) ์™€ Sparse ๋ฒกํ„ฐ(TF-IDF) ๋ฅผ ๋™์‹œ์— ์ƒ์„ฑํ•˜์—ฌ, ์ดํ›„ Hybrid Search๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด ์ธ๋ฑ์Šค๋ฅผ ๋‹ค์‹œ ๋นŒ๋“œํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Step 3. GitLab API๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ˆ˜์ง‘

MR์˜ Diff๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ๋ณ„๋กœ ์ฃผ๋ณ€ ์ฝ”๋“œ(ยฑ50์ค„)๋ฅผ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ํŒŒ์ผ์ด๋‚˜ ์„ค์ • ํŒŒ์ผ์€ ํ•„ํ„ฐ๋ง์„ ํ†ตํ•ด ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค.

const filePatterns = ['**/*.ts', '**/*.tsx', '**/*.js'];
const ignorePatterns = ['**/*.test.*', '**/node_modules/**', '**/*.config.*'];

Step 4. RAG ๊ฒ€์ƒ‰ + Rerank

Diff์—์„œ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”์ถœํ•˜๊ณ , Qdrant Hybrid Search โ†’ Cohere Rerank ์ˆœ์„œ๋กœ ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ข… ์ƒ์œ„ 5๊ฐœ ๋ฌธ์„œ๋งŒ ํ”„๋กฌํ”„ํŠธ์— ํฌํ•จํ•˜์—ฌ ํ† ํฐ์„ ์ ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์œ ์˜์‚ฌํ•ญ: Rerank์— ํด๋ฐฑ ์ „๋žต์„ ๋ฐ˜๋“œ์‹œ ๊ตฌํ˜„ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์™ธ๋ถ€ API๋Š” ์–ธ์ œ๋“  ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Cohere๊ฐ€ ์‹คํŒจํ•˜๋ฉด LLM์œผ๋กœ, ๊ทธ๋งˆ์ €๋„ ์‹คํŒจํ•˜๋ฉด RAG ์—†์ด ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๋‹จ๊ณ„๋ณ„ ํด๋ฐฑ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

Step 5. ํ”„๋กฌํ”„ํŠธ ์กฐ๋ฆฝ + ๋ฆฌ๋ทฐ ์ƒ์„ฑ + ์ฝ”๋ฉ˜ํŠธ ์ž‘์„ฑ

๋ชจ๋“  ์žฌ๋ฃŒ๊ฐ€ ๋ชจ์ด๋ฉด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์กฐ๋ฆฝํ•˜๊ณ , GPT-5์—๊ฒŒ JSON Mode๋กœ ๋ฆฌ๋ทฐ๋ฅผ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ์‘๋‹ต์„ Zod๋กœ ๊ฒ€์ฆํ•œ ๋’ค, GitLab API๋ฅผ ํ†ตํ•ด ์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ์˜ ์œ„์น˜๋ฅผ ์ •ํ™•ํžˆ ๋งž์ถ”๋Š” ๊ฒƒ์ด ๊นŒ๋‹ค๋กœ์šด ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. AI๊ฐ€ ์ง€๋ชฉํ•œ ๋ผ์ธ ๋ฒˆํ˜ธ๊ฐ€ Diff์— ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์œ ํšจ ๋ผ์ธ์„ ์ฐพ์•„ ๋งคํ•‘ํ•˜๋Š” ๋กœ์ง์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

function findClosestValidLine(targetLine: number, validLines: Set<number>): number | null {
  if (validLines.has(targetLine)) return targetLine;
  let closest = null;
  let minDist = Infinity;
  for (const line of validLines) {
    const dist = Math.abs(line - targetLine);
    if (dist < minDist) { minDist = dist; closest = line; }
  }
  return minDist <= 20 ? closest : null;
}

20์ค„ ์ด๋‚ด์— ์œ ํšจ ๋ผ์ธ์ด ์—†์œผ๋ฉด ์ธ๋ผ์ธ ๋Œ€์‹  ์ „์ฒด ์ฝ”๋ฉ˜ํŠธ๋กœ ์ „ํ™˜๋˜๋„๋ก ์„ค๊ณ„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


๐ŸŽ‰ ๋งˆ๋ฌด๋ฆฌ

์ฝ”๋“œ ๋ฆฌ๋ทฐ์˜ ๋ฐ˜๋ณต์ ์ธ ํŒจํ„ด์„ AI์—๊ฒŒ ๋งก๊ธฐ๊ณ , ์‚ฌ๋žŒ์€ ์•„ํ‚คํ…์ฒ˜ ํŒ๋‹จ๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ฒ€ํ†  ๊ฐ™์€ ๊ณ ์ฐจ์› ๋ฆฌ๋ทฐ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ์„ ์„ธ ๊ฐ€์ง€๋กœ ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค:

  1. RAG ํŒŒ์ดํ”„๋ผ์ธ: Hybrid Search + RRF + Cohere Rerank๋ฅผ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ ๊ทœ์น™ ๋ฌธ์„œ์—์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ๊ณผ ๊ฐ€์žฅ ๊ด€๋ จ ๋†’์€ ์ปจํ…์ŠคํŠธ๋ฅผ ์ •๋ฐ€ํ•˜๊ฒŒ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ: JSON Mode + Zod ๊ฒ€์ฆ์œผ๋กœ severity ร— category ๊ธฐ๋ฐ˜์˜ ์ผ๊ด€๋œ ๋ฆฌ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , GitLab์— ์š”์•ฝ + ์ธ๋ผ์ธ ์ฝ”๋ฉ˜ํŠธ๋กœ ์ž๋™ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  3. n8n ์ž๋™ํ™”: MR์ด ์˜ฌ๋ผ์˜ค๋ฉด ์ž๋™์œผ๋กœ ๋ฆฌ๋ทฐ๊ฐ€ ์‹คํ–‰๋˜๋ฏ€๋กœ, ๊ฐœ๋ฐœ์ž๋Š” ์ฝ”๋“œ๋ฅผ ์˜ฌ๋ฆฌ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

ํ–ฅํ›„ ๊ฐœ์„  ๋ฐฉํ–ฅ

ํ˜„์žฌ ์‹œ์Šคํ…œ๋„ ์ถฉ๋ถ„ํžˆ ์‹ค์šฉ์ ์ด์ง€๋งŒ, RAG ํŒŒ์ดํ”„๋ผ์ธ์˜ ๊ฒ€์ƒ‰ ์ •ํ™•๋„๋ฅผ ํ•œ ๋‹จ๊ณ„ ๋” ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฐœ์„  ํฌ์ธํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Hybrid Search ๊ณ ๋„ํ™” โ€” ํ˜„์žฌ๋Š” Dense์™€ Sparse ๊ฒ€์ƒ‰์„ ๋™์ผํ•œ ๊ฐ€์ค‘์น˜๋กœ RRF ๋ณ‘ํ•ฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ–ฅํ›„์—๋Š” ์ฟผ๋ฆฌ ํŠน์„ฑ์— ๋”ฐ๋ผ ๊ฐ€์ค‘์น˜๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ ˆํ•˜๋Š” Adaptive Fusion์„ ์ ์šฉํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด useCallback์ฒ˜๋Ÿผ ์ •ํ™•ํ•œ API๋ช…์ด ํฌํ•จ๋œ ์ฟผ๋ฆฌ๋Š” Sparse ๋น„์ค‘์„ ๋†’์ด๊ณ , "์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒจํ„ด"์ฒ˜๋Ÿผ ์˜๋ฏธ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ๋Š” Dense ๋น„์ค‘์„ ๋†’์ด๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ Qdrant์˜ Multi-Vector ๊ฒ€์ƒ‰์„ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ ์ž„๋ฒ ๋”ฉ๊ณผ ์ž์—ฐ์–ด ์ž„๋ฒ ๋”ฉ์„ ๋ณ„๋„ ๋ฒกํ„ฐ๋กœ ๋ถ„๋ฆฌ ์ €์žฅํ•˜๋ฉด, ์ฝ”๋“œ ํŒจํ„ด๊ณผ ๊ทœ์น™ ๋ฌธ์„œ ๊ฐ„์˜ ๋งค์นญ ์ •๋ฐ€๋„๋ฅผ ๋”์šฑ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Agentic Chunking ๋„์ž… โ€” ํ˜„์žฌ์˜ ์ ์‘ํ˜• ์ฒญํ‚น์€ ํ—ค๋”ฉยทํ•จ์ˆ˜ ๋‹จ์œ„๋กœ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๋ถ„ํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ตœ๊ทผ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ๋Š” Agentic Chunking์€ LLM์ด ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์ฝ๊ณ  "์ด ๋ถ€๋ถ„์€ ํ•˜๋‚˜์˜ ๊ฐœ๋… ๋‹จ์œ„๋กœ ๋ฌถ์–ด์•ผ ํ•œ๋‹ค"๊ณ  ํŒ๋‹จํ•˜์—ฌ ์˜๋ฏธ ๊ฒฝ๊ณ„๋ฅผ ์Šค์Šค๋กœ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์ ์šฉํ•˜๋ฉด ๊ทœ์น™ ๋ฌธ์„œ ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ํ—ค๋”ฉ์— ๊ฑธ์ณ ์„ค๋ช…๋˜๋Š” ํ•˜๋‚˜์˜ ๊ฐœ๋…(์˜ˆ: "์ปดํฌ๋„ŒํŠธ ์ ‘๊ทผ์„ฑ ๊ฐ€์ด๋“œ ์ „์ฒด")์ด ํ•˜๋‚˜์˜ ์ฒญํฌ๋กœ ์œ ์ง€๋˜์–ด ๊ฒ€์ƒ‰ ์‹œ ์™„์ „ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Late Chunking(์ž„๋ฒ ๋”ฉ ํ›„ ์ฒญํ‚น)์ด๋‚˜ Contextual Retrieval(๊ฐ ์ฒญํฌ์— ๋ฌธ์„œ ์ „์ฒด ๋งฅ๋ฝ ์š”์•ฝ์„ ์ฃผ์ž…)๊ณผ ๊ฐ™์€ ์ตœ์‹  ๊ธฐ๋ฒ•๋„ ํ•จ๊ป˜ ๊ฒ€ํ† ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์ฒญํฌ ๋‹จ์œ„์˜ ์˜๋ฏธ ์†์‹ค์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ๋ฆฌ๋ทฐ ํ’ˆ์งˆ์„ ๋”์šฑ ๋†’์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”— ๊ด€๋ จ ๋งํฌ

๐Ÿท๏ธ ํƒœ๊ทธ

#ai_agent #rag #code_review #qdrant #cohere_rerank #gpt5 #n8n #gitlab #typescript #vector_search #hybrid_search