๐ 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 ๊ฒฐ๊ณผ๋ฅผ ํฌํจํ ๋ต๋ณ์ ์ป์ ์ ์์ต๋๋ค.
