๐ค RAG ๊ธฐ๋ฐ AI ์ฝ๋๋ฆฌ๋ทฐ ์์ด์ ํธ โ ์ค๊ณ๋ถํฐ ์๋ํ๊น์ง
Qdrant Hybrid Search + Cohere Rerank + GPT-5 ๊ธฐ๋ฐ RAG ํ์ดํ๋ผ์ธ์ผ๋ก MR ๋ณ๊ฒฝ์ฌํญ์ ํ๋ก์ ํธ ์ปจํ ์คํธ๋ฅผ ๊ฒฐํฉํ AI ์ฝ๋๋ฆฌ๋ทฐ ์์ด์ ํธ๋ฅผ ์ค๊ณยท๊ตฌ์ถํ๊ณ , n8n์ผ๋ก GitLab MR ์๋ ๋ฆฌ๋ทฐ๊น์ง ๊ตฌํํ ๊ณผ์ ์ ๋ค๋ฃน๋๋ค.

๐ ๊ธ ๊ฐ์
์ฝ๋ ๋ฆฌ๋ทฐ๋ ์ํํธ์จ์ด ํ์ง์ ๋ง์ง๋ง ๊ด๋ฌธ์ ๋๋ค. ํ์ง๋ง ํ์ค์์๋ ๋ช ๊ฐ์ง ๊ตฌ์กฐ์ ์ธ ํ๊ณ๊ฐ ์กด์ฌํฉ๋๋ค.
"์ด ์ปดํฌ๋ํธ์์
useCallback๋น ์ก์ด์", "z-index ํ ํฐ ๊ท์น ํ์ธํด์ฃผ์ธ์", "์ ๊ทผ์ฑ ์์ฑ ์ถ๊ฐํด์ฃผ์ธ์"
๊ฐ์ ํผ๋๋ฐฑ์ด ๋งค MR๋ง๋ค ๋ฐ๋ณต๋ฉ๋๋ค. ๋ฆฌ๋ทฐ์ด๋ ํผ๋กํด์ง๊ณ , ๋ฆฌ๋ทฐ์ด๋ ๊ฐ์ ์ค์๋ฅผ ๋ํ์ดํฉ๋๋ค. ํ๋ก์ ํธ ์ฝ๋ฉ ๊ท์น ๋ฌธ์๋ ๋ถ๋ช ํ ์กด์ฌํ์ง๋ง, MR์ ์ฌ๋ฆด ๋๋ง๋ค ํด๋น ๋ฌธ์๋ฅผ ์ผ์ผ์ด ๋์กฐํ๋ ๊ฒ์ ํ์ค์ ์ผ๋ก ์ด๋ ต์ต๋๋ค.
๋ฐ๋ณต ํผ๋๋ฐฑ ์ธ์๋ ๋ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ฆฌ๋ทฐ์ด๋ ์ฌ๋์ ๋๋ค. ์ ๋ฌด๊ฐ ๋ชฐ๋ฆฌ๊ฑฐ๋ ์ผ์ ์ด ์ด๋ฐํ ์ํฉ์์๋ ์๋ฌด๋ฆฌ ์๋ จ๋ ๋ฆฌ๋ทฐ์ด๋ผ ํ๋๋ผ๋ ๋์น๋ ๋ถ๋ถ์ด ์๊ธฐ๊ธฐ ๋ง๋ จ์ด๋ฉฐ, ์ด๋ฌํ ๋๋ฝ์ ๊ณง ์ฅ์ ๋ฐ์ ํ๋ฅ ์ ์ฆ๊ฐ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
AI ์ฝ๋๋ฆฌ๋ทฐ ์์ด์ ํธ๋ ์ด ๋ฌธ์ ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ํด๊ฒฐํฉ๋๋ค:
- ๋ฆฌ๋ทฐ ํ์ง์ ๊ท ์ผํ โ ์ฌ๋์ ์ปจ๋์ ์ด๋ ์ ๋ฌด ์ํฉ์ ๊ด๊ณ์์ด, ํ๋ก์ ํธ ๊ท์น ๊ธฐ๋ฐ์ ์ผ๊ด๋ ์์ค์ ๋ฆฌ๋ทฐ๋ฅผ ํญ์ ์ ๊ณตํฉ๋๋ค
- ์ฌ๊ฐ์ง๋ ์ฌ์ ๋ฐ๊ฒฌ โ ๋ฆฌ๋ทฐ์ด๊ฐ ๋ฏธ์ฒ ๋ณด์ง ๋ชปํ ์ ๊ทผ์ฑ ๋๋ฝ, ๋ฉ๋ชจ๋ฆฌ ๋ฆญ ํจํด, ๋ณด์ ์ทจ์ฝ์ ๊ฐ์ ๋ป๋ฐ์ ์ด์๋ฅผ ์ฌ์ ์ ๊ฐ์งํฉ๋๋ค
- ์ ์ฐ์ฐจ ๊ฐ๋ฐ์์ ์ฑ์ฅ ์ง์ โ ๋จ์ํ "ํ๋ ธ๋ค"๊ฐ ์๋๋ผ, ํ๋ก์ ํธ ๊ท์น ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก "์ ์ด๋ ๊ฒ ํด์ผ ํ๋์ง"๊น์ง ์ค๋ช ํจ์ผ๋ก์จ ์ฝ๋๋ฆฌ๋ทฐ์ ๋ฐฉํฅ์ฑ์ ์ ์ํฉ๋๋ค
๊ทธ๋์ ์ด ์์ด์ ํธ๋ฅผ ๋ง๋ค์์ต๋๋ค. ํ๋ก์ ํธ์ ์ฝ๋ฉ ๊ท์น๊ณผ ๊ฐ์ด๋ ๋ฌธ์๋ฅผ AI๊ฐ ํ์ตํ๊ณ , MR์ด ์ฌ๋ผ์ค๋ฉด ๋ณ๊ฒฝ๋ ์ฝ๋์ ๊ฐ์ฅ ์ ํฉํ ์ปจํ ์คํธ๋ฅผ ๊ฒ์ํ์ฌ ์๋์ผ๋ก ๋ฆฌ๋ทฐ๋ฅผ ์ํํ๋ ์์คํ ์ ๋๋ค.
์ด ๊ธ์์๋ ํด๋น ์์ด์ ํธ๋ฅผ ์ค๊ณํ๊ณ , ๊ตฌํํ๊ณ , ์๋ํํ ์ ์ฒด ๊ณผ์ ์ ๊ณต์ ํ๊ฒ ์ต๋๋ค.
๐ก ์ด ๊ธ์์ ๋ค๋ฃฐ ๋ด์ฉ
- ์ ์ฒด ์ํคํ ์ฒ ๋ฐ ๋ฐ์ดํฐ ํ๋ฆ
- ์ค์ MR ๋ฆฌ๋ทฐ ๊ฒฐ๊ณผ๋ฅผ ๋จ๊ณ๋ณ๋ก ๋ฐ๋ผ๊ฐ๋ณด๊ธฐ
- RAG ํ์ดํ๋ผ์ธ ์ฌ์ธต ๋ถ์: ์ ์ํ ์ฒญํน โ Hybrid Search โ RRF โ Cohere Rerank
- ํ๋กฌํํธ ์์ง๋์ด๋ง: ํ๋ฅด์๋ ์ค๊ณ์ ๊ตฌ์กฐํ๋ ์ถ๋ ฅ
- n8n ๊ธฐ๋ฐ ์๋ํ ์ํฌํ๋ก์ฐ ๊ตฌ์ถ
- ์ง์ ๊ตฌํํ ๋ ์์์ผ ํ ํต์ฌ ํฌ์ธํธ
๐๏ธ ์ ์ฒด ์ํคํ ์ฒ
๋จผ์ ์ํคํ ์ฒ ๋ฐ ๋ฐ์ดํฐ ํ๋ฆ๋ถํฐ ์ดํด๋ณด๊ฒ ์ต๋๋ค. MR์ด ์ฌ๋ผ์ค๋ฉด ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ๊ฐ ์์ฑ๋๊ธฐ๊น์ง, ํฌ๊ฒ ๋ณ๊ฒฝ์ฌํญ ์์ง โ 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์ด ์ฌ๋ผ์ค๋ฉด, ์์ด์ ํธ๊ฐ ๋จผ์ ์ ์ฒด ๋ณ๊ฒฝ์ฌํญ์ ๋ถ์ํ ์์ฝ ์ฝ๋ฉํธ๋ฅผ ๋จ๊น๋๋ค.
๋์ฌ๊ฒจ๋ณผ ์ ์ด ๋ช ๊ฐ์ง ์์ต๋๋ค:
- Risk Level: ๐ก MEDIUM โ ๋ณ๊ฒฝ์ฌํญ์ ์ํ๋๋ฅผ 3๋จ๊ณ(LOW/MEDIUM/HIGH)๋ก ์๋ ํ๊ฐํฉ๋๋ค
- ๊ธ์ ์ ํผ๋๋ฐฑ ๋จผ์ โ "CSF3๋ก ๊น๋ํ๊ฒ ์ ๋ฆฌํ์ ์ ๋๋ฌด ์ข์ต๋๋ค! ๐" ์ฒ๋ผ ์ํ ์ ์ ๋จผ์ ์ง์ด์ค๋๋ค
- ๊ฐ์ ํฌ์ธํธ โ ๊ตฌ์ฒด์ ์ธ ๊ฐ์ ์ ์์ ๋ฆฌ์คํธ๋ก ์ ๋ฆฌํฉ๋๋ค
- ๐ ์ฐธ์กฐ ๋ฌธ์ (5๊ฐ) โ RAG๋ก ๊ฒ์ํ ํ๋ก์ ํธ ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก ์ ์ํฉ๋๋ค. "์ ์ด๋ ๊ฒ ํด์ผ ํ๋์ง"์ ์ถ์ฒ๊ฐ ๋ช ํํฉ๋๋ค
์ธ๋ผ์ธ ์ฝ๋ฉํธ
์์ฝ๋ฟ ์๋๋ผ, ์ฝ๋์ ํน์ ์ค์ ์ง์ ์ฝ๋ฉํธ๋ ๋ฌ๋ฆฝ๋๋ค.
์ธ๋ผ์ธ ์ฝ๋ฉํธ์๋ ์ฌ๊ฐ๋ ๋ผ๋ฒจ์ด ๋ถ์ต๋๋ค:
| ์ฌ๊ฐ๋ | ๋ผ๋ฒจ | ์๋ฏธ |
|---|---|---|
| ๐จ 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์ ํ์ฉํ์ฌ ์์ ์๋ํํ์์ต๋๋ค.
๋์ ํ๋ฆ
โ 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์๊ฒ ๋งก๊ธฐ๊ณ , ์ฌ๋์ ์ํคํ ์ฒ ํ๋จ๊ณผ ๋น์ฆ๋์ค ๋ก์ง ๊ฒํ ๊ฐ์ ๊ณ ์ฐจ์ ๋ฆฌ๋ทฐ์ ์ง์คํ ์ ์๊ฒ ๋์์ต๋๋ค. ํต์ฌ์ ์ธ ๊ฐ์ง๋ก ์ ๋ฆฌํ๊ฒ ์ต๋๋ค:
- RAG ํ์ดํ๋ผ์ธ: Hybrid Search + RRF + Cohere Rerank๋ฅผ ํตํด ํ๋ก์ ํธ ๊ท์น ๋ฌธ์์์ ๋ณ๊ฒฝ์ฌํญ๊ณผ ๊ฐ์ฅ ๊ด๋ จ ๋์ ์ปจํ ์คํธ๋ฅผ ์ ๋ฐํ๊ฒ ๊ฒ์ํฉ๋๋ค.
- ๊ตฌ์กฐํ๋ ์ถ๋ ฅ: JSON Mode + Zod ๊ฒ์ฆ์ผ๋ก severity ร category ๊ธฐ๋ฐ์ ์ผ๊ด๋ ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํ๊ณ , GitLab์ ์์ฝ + ์ธ๋ผ์ธ ์ฝ๋ฉํธ๋ก ์๋ ์์ฑํฉ๋๋ค.
- 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(๊ฐ ์ฒญํฌ์ ๋ฌธ์ ์ ์ฒด ๋งฅ๋ฝ ์์ฝ์ ์ฃผ์ )๊ณผ ๊ฐ์ ์ต์ ๊ธฐ๋ฒ๋ ํจ๊ป ๊ฒํ ํ๊ณ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ฒญํฌ ๋จ์์ ์๋ฏธ ์์ค์ ์ต์ํํ์ฌ ๋ฆฌ๋ทฐ ํ์ง์ ๋์ฑ ๋์ผ ์ ์์ ๊ฒ์ผ๋ก ๊ธฐ๋ํฉ๋๋ค.
๐ ๊ด๋ จ ๋งํฌ
OpenPersona v2.0 RAG ๊ตฌ์ถ๊ธฐ
Hybrid Search + RRF + LLM Reranking ์์ธ ์ค๋ช
Qdrant Hybrid Search Docs
Dense + Sparse + RRF Fusion ๊ณต์ ๋ฌธ์
n8n Documentation
์ํฌํ๋ก์ฐ ์๋ํ ํ๋ซํผ ๊ณต์ ๋ฌธ์
๐ท๏ธ ํ๊ทธ
#ai_agent #rag #code_review #qdrant #cohere_rerank #gpt5 #n8n #gitlab #typescript #vector_search #hybrid_search