๐ LLM ์ฑํ ์ RAG ๋ผ์น๊ธฐยถ
- ๋ณ๊ฒฝ ํ์ผ์ ํ ๋ฒ์ ๋ฎ์ด์ฐ๊ธฐ ํ์ค๋ ค๋ฉด, pyhub-git-commit-apply ์ ํธ๋ฆฌํฐ ์ค์นํ์ ํ์, ํ๋ก์ ํธ ๋ฃจํธ์์ ์๋ ๋ช ๋ น ์คํํ์๋ฉด ์ง์  ์ปค๋ฐ์ ๋ชจ๋ ํ์ผ์ ๋ค์ด๋ฐ์ ํ์ฌ ๊ฒฝ๋ก์ ๋ฎ์ด์ฐ๊ธฐํฉ๋๋ค. 
python -m pyhub_git_commit_apply https://github.com/pyhub-kr/django-webchat-rag-langcon2025/commit/960a43669b860a24a5b05607c880aa6553ac3532
uv๋ฅผ ์ฌ์ฉํ์ค ๊ฒฝ์ฐ
uv run pyhub-git-commit-apply https://github.com/pyhub-kr/django-webchat-rag-langcon2025/commit/960a43669b860a24a5b05607c880aa6553ac3532
AI ๋ฉ์์ง ์์ฑ ์์ ์ ์ฌ ๋ฌธ์๋ฅผ ์ง์์ผ๋ก ํ์ฉํ๊ธฐยถ
AI ๋ฉ์์ง ์์ฑ ์์ ํ๋กฌํํธ์ ์ ์ฌ ๋ฌธ์๋ฅผ ์ง์์ผ๋ก ํ์ฉํ๋ฉด RAG๊ฐ ๋ฉ๋๋ค.
Room ๋ชจ๋ธ์ create_ai_message ๋ฉ์๋ ๋ด์์ โ์ธ๋ฒ ํด์๋กโ ๋ฌธ์ ๊ฒ์์ด ํ์ํ  ๋,
TaxLawDocument ๋ชจ๋ธ์ ํตํด ์ ์ฌ ๋ฌธ์๋ฅผ ๊ฒ์ํ๊ณ , ๊ฒ์๋ ๋ฌธ์๋ฅผ ์ง์์ผ๋ก ํ์ฉํฉ๋๋ค.
chat/models.py ํ์ผ ๋ฎ์ด์ฐ๊ธฐยถ 1import json
 2
 3from django.db import models
 4from django.utils.functional import cached_property
 5from django_lifecycle import LifecycleModelMixin, hook, AFTER_UPDATE
 6from pyhub.rag.fields.sqlite import SQLiteVectorField
 7from pyhub.rag.models.sqlite import SQLiteVectorDocument
 8
 9from chat.llm import LLM
10
11
12class TaxLawDocument(SQLiteVectorDocument):
13    embedding = SQLiteVectorField(
14        dimensions=3072,
15        editable=False,
16        embedding_model="text-embedding-3-large",
17    )
18
19    @cached_property
20    def page_content_obj(self):
21        return json.loads(self.page_content)
22
23
24class Room(LifecycleModelMixin, models.Model):
25    name = models.CharField(max_length=255)
26    system_prompt = models.TextField(blank=True)
27    created_at = models.DateTimeField(auto_now_add=True)
28    updated_at = models.DateTimeField(auto_now=True)
29
30    def __str__(self):
31        return self.name
32
33    @hook(AFTER_UPDATE, when="system_prompt", has_changed=True)
34    def on_after_update(self):
35        self.message_set.all().delete()
36
37    def create_ai_message(self):
38        # ํ์ฌ ๋ฐฉ์ ์ด์  ๋ฉ์์ง๋ค์ ์์ง
39        message_qs = self.message_set.all()
40        messages = [{"role": msg.role, "content": msg.content} for msg in message_qs]
41
42        # ์ธ๋ฒ ํด์๋ก ๋ฌธ์ ๊ฒ์์ด ํ์ํ  ๋
43        user_message = messages[-1]["content"].strip()
44        if user_message.startswith("!"):
45            user_message = user_message[1:]
46            # RAG๋ฅผ ์ํ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ์ ์ฌ ๋ฌธ์ ๊ฒ์
47            doc_list = TaxLawDocument.objects.similarity_search(user_message)
48            ์ง์ = str(doc_list)
49            system_prompt = self.system_prompt + "\n\n" + f"์ฐธ๊ณ ๋ฌธ์ : {์ง์}"
50        else:
51            system_prompt = self.system_prompt
52
53        # AI ์๋ต ์์ฑ
54        llm = LLM(
55            model="gpt-4o-mini",
56            temperature=1,
57            system_prompt=system_prompt,
58            initial_messages=messages,
59        )
60        ai_message = llm.make_reply()
61
62        # AI ์๋ต์ ์ ๋ฉ์์ง๋ก ์ ์ฅ
63        return self.message_set.create(
64            role=Message.Role.ASSISTANT,
65            content=ai_message,
66        )
67
68    class Meta:
69        ordering = ["-pk"]
70
71
72class Message(models.Model):
73    class Role(models.TextChoices):
74        USER = "user"
75        ASSISTANT = "assistant"
76
77    room = models.ForeignKey(Room, on_delete=models.CASCADE)
78    role = models.CharField(max_length=255, choices=Role.choices, default=Role.USER)
79    content = models.TextField()
80    created_at = models.DateTimeField(auto_now_add=True)
81    updated_at = models.DateTimeField(auto_now=True)
82
83    def __str__(self):
84        return self.content
85
86    class Meta:
87        ordering = ["pk"]
์ฐธ๊ณ
์ง๊ธ์ ๋ชจ๋ ๋ฉ์์ง๋ง๋ค ์ ์ฌ ๋ฌธ์๋ฅผ ๊ฒ์ํ์ง๋ง, ์ด๋ฅผ ๊ณ ๋ํํ์ฌ ์ ์ฌ ๋ฌธ์ ๊ฒ์์ด ํ์ํ์ง ์ฌ๋ถ๋ฅผ LLM์ ํตํด ํ๋จํ๊ณ , ์ ์ฌ ๋ฌธ์ ๊ฒ์์ด ํ์ํ ๊ฒฝ์ฐ์๋ง ์ ์ฌ ๋ฌธ์๋ฅผ ๊ฒ์ํ๋๋ก ํ ์ ์์ต๋๋ค.
๋์ ํ๋ฉดยถ
http://localhost:8000/chat/new/ ํ์ด์ง์์ ์๋ก์ด ์ธ๋ฌด/ํ๊ณ ์ฑ๋ด ์ฑํ ๋ฐฉ์ ์์ฑํฉ๋๋ค.
์์คํ  ํ๋กฌํํธ ์์
๋ํ๋ฏผ๊ตญ ์ธ๋ฌด/ํ๊ณ ์ ๋ณด ์ฑ๋ด์ผ๋ก์, ์ฃผ์ด์ง ์ง๋ต ์ง์์์ ์ฌ์ค๊ณผ ์๊ฒฌ์ ๊ตฌ๋ณํ์ฌ ์ฌ์ค ์ ๋ณด๋ง์ ์ ๋ฆฌํ๊ณ ,
๊ฐ ๋ต๋ณ์ ํด๋น ์ ๋ณด์ ์ถ์ฒ๊น์ง ํจ๊ป ๊ธฐ์
ํ์ฌ ๋ต๋ณํ์ธ์.
# Steps
1. ์ดํดํ๊ธฐ: ์ง๋ฌธ๊ณผ ์ ๊ณต๋ ์ง์์ ์ฃผ์ ๊น๊ฒ ์ฝ๊ณ  ์ ํํ ์ดํดํฉ๋๋ค.
2. ์ ๋ณด ๊ตฌ๋ถํ๊ธฐ: ์ง๋ต ์ง์์์ ์ฌ์ค๊ณผ ์๊ฒฌ์ ์๋ณํฉ๋๋ค.
- ์ฌ์ค: ๊ฒ์ฆ ๊ฐ๋ฅํ ๋ฐ์ดํฐ, ๋ฒ๋ฅ , ๊ท์  ๋ฐ ์์น ๋ฑ
- ์๊ฒฌ: ๊ฐ์ธ์ ๊ฒฌํด, ํด์, ์ถ์ฒ ๋ฑ
3. ์ฌ์ค ์ ๋ฆฌํ๊ธฐ: ์๋ณ๋ ์ฌ์ค ์ ๋ณด๋ฅผ ๋
ผ๋ฆฌ์ ์ด๊ณ  ๋ช
ํํ๊ฒ ์ ๋ฆฌํ๋ฉฐ, ๋ถํ์ํ ๋ถ๋ถ์ ์ ๊ฑฐํฉ๋๋ค.
4. ๋ต๋ณ ์์ฑํ๊ธฐ: ์ ๋ฆฌ๋ ์ฌ์ค ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ช
๋ฃํ๊ณ  ๊ฐ๊ฒฐํ ๋ฌธ์ฅ์ผ๋ก ๊ตฌ์ฑ๋ ๋จ๋ฝ ํํ์ ๋ต๋ณ์ ์์ฑํฉ๋๋ค. ๋ฐ๋์ ํด๋น ์ฌ์ค ์ ๋ณด์ ์ถ์ฒ๋ฅผ ํจ๊ป ๋ช
์ํฉ๋๋ค.
- ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์ ๋ขฐํ  ์ ์๋ ์ถ์ฒ(์: ์ ๋ถ ๊ธฐ๊ด, ๊ณต์ ๋ฌธ์, ํ์ ์๋ฃ ๋ฑ)๋ฅผ ํฌํจํฉ๋๋ค.
- ์ถ์ฒ๊ฐ ํ์ธ๋์ง ์๊ฑฐ๋ ์๋ ๊ฒฝ์ฐ, โ์ถ์ฒ๋ฅผ ์ฐพ์ ์ ์์ต๋๋คโ๋ผ๊ณ  ๋ช
์ํฉ๋๋ค.
- ์ถ์ฒ์ ๋ฌธ์ID๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ, ๋ฐ๋์ ๋ฌธ์ID๋ฅผ ๊ธฐ์
ํ๊ณ  ์๋ URL ํ์์ ์ฐธ๊ณ ํ์ฌ ํด๋น URL๋ ํจ๊ป ํฌํจํฉ๋๋ค.
# Output Format
- ๋ช
๋ฃํ๊ณ  ๊ฐ๊ฒฐํ ๋ฌธ์ฅ์ผ๋ก ๊ตฌ์ฑ๋ ๋จ๋ฝ ํํ์ ๋ต๋ณ
- ๋ต๋ณ ๋ด์ ์ฌ์ฉํ ์ ๋ณด์ ์ถ์ฒ๋ฅผ ๋ฐ๋์ ํฌํจํ์ฌ ์์ฑ
# Notes
- ๊ฐ ์ธ๋ฌด/ํ๊ณ ์ ๋ณด๋ฅผ ๊ฐ๊ด์ ์ผ๋ก ํ๊ฐํ์ฌ ๋ต๋ณ์ ์์ฑํฉ๋๋ค.
- ๋ชจํธํ๊ฑฐ๋ ๋ถํ์คํ ์ ๋ณด๋ ์ ์ธํฉ๋๋ค.
- ๋ต๋ณ์ ๋ฐ๋์ ๊ด๋ จ ์ฌ์ค ์ ๋ณด์ ์ถ์ฒ๋ฅผ ํจ๊ป ๊ธฐ์
ํ์ฌ ๊ฐ๊ด์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ๋์
๋๋ค.
 
์ฑํ ๋ฐฉ์ด ์์ฑ๋์๊ตฌ์.
 
์ธ๋ฌด/ํ๊ณ ์ฑ๋ด์๊ฒ ๋๋ํ๋ก ์์ํ๋ ! ์ฌํ ์์ถํ๋ ๊ฒฝ์ฐ ์์ธ์จ ์ฒจ๋ถ ์๋ฅ๋ก ์์ถ์ค์ ๋ช
์ธ์๊ฐ ์๋ ๊ฒฝ์ฐ ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ฉด,
์๋์ ๊ฐ์ด ์ญ์ฌ์ด ํ์ RAG ๊ฒฐ๊ณผ๋ฅผ ํฌํจํ ๋ต๋ณ์ ์ป์ ์ ์์ต๋๋ค.
