๐˜š๐˜ญ๐˜ฐ๐˜ธ ๐˜ฃ๐˜ถ๐˜ต ๐˜ด๐˜ต๐˜ฆ๐˜ข๐˜ฅ๐˜บ

[LLM] LangChain๊ณผ RAG์„ ํ™œ์šฉํ•œ ๊ฐ„๋‹จํ•œ LLM ๊ธฐ๋ฐ˜ ์ฑ—๋ด‡ ๊ตฌํ˜„ํ•˜๊ธฐ ๋ณธ๋ฌธ

machine learning

[LLM] LangChain๊ณผ RAG์„ ํ™œ์šฉํ•œ ๊ฐ„๋‹จํ•œ LLM ๊ธฐ๋ฐ˜ ์ฑ—๋ด‡ ๊ตฌํ˜„ํ•˜๊ธฐ

.23 2025. 3. 26. 00:55

LangChain?

LangChain์€ ๋Œ€ํ˜• ์–ธ์–ด ๋ชจ๋ธ(LLM)์„ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, AI ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์„ ๋ณด๋‹ค ์ฒด๊ณ„์ ์ด๊ณ  ์œ ์—ฐํ•˜๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š”๋‹ค. LangChain์€ ๋‹ค์–‘ํ•œ LLM๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด AI ๋ชจ๋ธ์„ ์‰ฝ๊ฒŒ ํ˜ธ์ถœํ•˜๊ณ , ๋Œ€ํ™”ํ˜• ์‘๋‹ต์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋ฐ ์ž๋™ํ™” ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
- ChatGPT

 

LangChain์€ ๋‹ค์–‘ํ•œ LLM ๋ชจ๋ธ์„ ์†์‰ฝ๊ฒŒ ํ˜ธ์ถœํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•œ๋‹ค. ํŠนํžˆ OpenAI API๋ฅผ ์ง์ ‘ ํ™œ์šฉํ•˜์—ฌ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹๋ณด๋‹ค LangChain์„ ํ™œ์šฉํ•˜๋ฉด LLM์„ ํ™œ์šฉํ•˜๋Š” ์ธก๋ฉด์—์„œ ํ™•์žฅ์„ฑ๋„ ์ข‹๊ณ  ์ฝ”๋“œ ํ™œ์šฉ๋„ ํ›จ์”ฌ ๊ฐ„ํŽธํ•˜๋‹ค.

๊ณต๋ถ€ํ•˜๋‹ค ๊ถ๊ธˆํ•ด์„œ ๊ฒ€์ƒ‰ํ•ด๋ด„

 

์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋†“๊ณ  ๋น„๊ตํ•ด๋„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋” ์ง๊ด€์ ์œผ๋กœ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ์ด๊ณ , LLM ๋ชจ๋ธ์ด ๊ฐ€์ ธ์˜ค๋Š” ์‘๋‹ต ์—ญ์‹œ ๋ณ„๋‹ค๋ฅธ ํŒŒ์‹ฑ์ด ํ•„์š”์—†๊ธฐ ๋•Œ๋ฌธ์— ์‘๋‹ต ํ™œ์šฉ์ด ํ›จ์”ฌ ์ž์œ ๋กญ๋‹ค.

 

API ์ง์ ‘ ํ™œ์šฉ ๋ฐฉ์‹์˜ result๋Š” response๋กœ๋ถ€ํ„ฐ ๊ตฌ๊ตฌ์ ˆ์ ˆ(์ง€๊ธˆ์€ deprecated๋œ ํ˜•์‹) ํŒŒ์‹ฑํ•ด์™€์•ผ ํ•˜๋Š” ๋ฐ˜๋ฉด,

LangChain์˜ result๋Š” invoke ํ•œ๋ฒˆ์ด๋ฉด ๋๋‚œ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ, ๋ณ€์ˆ˜ ํ™œ์šฉ ์ธก๋ฉด์—์„œ๋„ ์ž์œ ๋กญ๊ธฐ ๋•Œ๋ฌธ์— ๋ณธ ์ฑ—๋ด‡ ๊ตฌํ˜„์—๋„ LangChain์„ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๊ณ ์ž ํ–ˆ๋‹ค.

 

์ฑ—๋ด‡ ์„ค๊ณ„ ๋ชฉ์ 

์ฒญ์†Œ๋…„ ๋Œ€์ƒ ๊ธˆ์œต ๊ต์œก์šฉ ์ฑ—๋ด‡์„ ์„ค๊ณ„ํ•˜๊ธฐ ์œ„ํ•ด ๊ต์œก์ž๋ฃŒ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š” ์ตœ์‹  ๋‰ด์Šค/๊ต์œก์ž๋ฃŒ ๋“ฑ์„ ๋ฒกํ„ฐDB(chroma db ์‚ฌ์šฉ)์— ์šฐ์„  ์ €์žฅํ•ด๋†“๊ณ , ์ฑ—๋ด‡์—์„œ ์งˆ์˜๋ฌธ์„ ๋˜์ง€๋ฉด

 

1. ์ฟผ๋ฆฌ์™€ ์ €์žฅ๋œ ์ปจํ…์ธ ๋“ค ๋ณ„ ์œ ํ˜•๊ณผ์˜ ์ ํ•ฉ์„ฑ์„ ํŒŒ์•…ํ•œ๋‹ค.

2. ๊ฐ€์žฅ ์ ํ•ฉํ•œ DB์—์„œ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์™€ ๋‹ต๋ณ€ํ•œ๋‹ค.

 

์š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ•˜์˜€๋‹ค.

 

์ž๋ฃŒ๋ฅผ ํฌ๋กค๋งํ•˜๋Š” ์ฝ”๋“œ๋Š” ๋‰ด์Šค/์ฆ๊ถŒ๋ณด๊ณ ์„œ/๊ต์œก์ž๋ฃŒ ๋ณ„ ๊ฐ๊ฐ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์ž‘์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํŽธ์˜์ƒ ๊ฐœ๋ณ„์ ์ธ DB๋ฅผ ๊ตฌ์ถ•ํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , ํ†ตํ•ฉํ•ด์„œ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์—์„œ ์ฒ˜์Œ์—๋Š” ์ฟผ๋ฆฌ์™€ ์—ฐ๊ด€์„ฑ์žˆ๋Š” ๋ชจ๋“  ์ž๋ฃŒ๋ฅผ ๊ฒ€์ƒ‰ํ•ด์„œ ๊ฐ€์ ธ์˜ค๊ฒŒ ํ•ด์•ผ๊ฒ ๋‹ค! ํ–ˆ๋Š”๋ฐ ํ† ํฐ ๊ธธ์ด๊ฐ€ ํญ๋ฐœํ•ด๋ฒ„๋ฆฌ๋Š” ์ด์Šˆ๋กœ ์ธํ•ด.. ใ… ใ… 

 

๋ถˆ๊ฐ€ํ”ผํ•˜๊ฒŒ ์ž…๋ ฅ์ฟผ๋ฆฌ๋กœ๋ถ€ํ„ฐ LLM ๋ชจ๋ธ์ด ์—ฐ๊ด€์„ฑ์„ ํŒŒ์•…ํ•˜๊ฒŒ ์„ค๊ณ„ํ•ด์„œ ์–ด๋Š DB๋ฅผ ์ฐพ์„์ง€ ๋ถ„๋ฅ˜๋ถ€ํ„ฐ ํ•˜๊ณ ,

๋ถ„๋ฅ˜๋œ ๊ฒฐ๊ณผ์˜ DB๋กœ๋ถ€ํ„ฐ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์™€ ๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉํ–ฅ์„ฑ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์„ค๊ณ„ํ–ˆ๋‹ค.

 

UI ์—ฐ๊ฒฐํ•˜๊ธฐ ์ด์ „๋‹จ๊ณ„๊นŒ์ง€ ๊ตฌํ˜„์„ ๋งก์•„์„œ, ๋‚˜๋Š” ์ฝ˜์†”๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค.

 

์ฝ”๋“œ

๋ชจ๋“ˆํ™” ํ•ด๋‹ฌ๋ผ๊ณ  ๋ถ€ํƒ์„ ๋ฐ›์•„์„œ ChatBot class๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— ๋ชจ๋“  ์ž‘๋™ ํ•จ์ˆ˜๋ฅผ ๋•Œ๋ ค๋„ฃ์—ˆ๋‹ค.

 

1. query๋กœ๋ถ€ํ„ฐ ์—ฐ๊ด€๋œ DB์œ ํ˜• ๋ถ„๋ฅ˜

from langchain_core.prompts import ChatPromptTemplate

def classify_query(self, query: str) -> str:
    prompt = ChatPromptTemplate.from_messages([
        ('system', 
        """
        ๋‹ค์Œ ์งˆ์˜๋ฅผ ์ฝ๊ณ  ๊ด€๋ จ๋œ ์ •๋ณด ์œ ํ˜•์„ ํ•œ ๋‹จ์–ด๋กœ ์ถœ๋ ฅํ•˜์„ธ์š”.
        ๊ฐ€๋Šฅํ•œ ๊ฐ’: "edu", "news", "report", "all", "nothing".
        
        - edu: ๊ฒฝ์ œ ์šฉ์–ด๋‚˜ ๊ฐœ๋… ๋“ฑ ๋ฐฐ๊ฒฝ์ง€์‹
        - news: ์ตœ์‹  ์‚ฐ์—… ๋™ํ–ฅ
        - report: ๊ธฐ์—… ๋ถ„์„, ์žฌ๋ฌด ์ •๋ณด ๋“ฑ
        - all/nothing: ๋ฒ”์ฃผ ๊ตฌ๋ถ„์ด ๋ถˆ๋ช…ํ™•ํ•  ๋•Œ

        "nothing"๊ณผ "all"์€ ์ตœ์†Œํ•œ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ ,
        ๊ฐ€๋Šฅํ•˜๋ฉด edu, news, report ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”.
        """),
        ('user', f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
    ])

    chain = prompt | self.llm
    classification = chain.invoke({"query": query}).content.strip().lower()
    return classification

์ž‘๋™ ๋ฐฉ์‹

๊ฐ๊ฐ ๋ชจ์œผ๋Š” ์ž๋ฃŒ๋กœ๋ถ€ํ„ฐ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋“ค์˜ ์„ฑ๊ฒฉ์ด ๋‹ค ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ์œ ํ˜•์„ ๋ถ„๋ฅ˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ–ˆ๋‹ค.

๋ฌผ๋ก  OpenAI API๋ฅผ ์ตœ๋Œ€ํ•œ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„ ํŒ๋ณ„๋ฒ•์ด๋‚˜ BERT๊ฐ™์€ classification ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ์งˆ์˜์˜ ๊ฐ€๋ณ€์„ฑ์„ ์ƒ๊ฐํ•˜์—ฌ ๊ฐ€์žฅ ํ™•์‹คํ•œ LLM ๋ชจ๋ธ์—๊ฒŒ ๋งก๊ฒผ๋‹ค.

 

์šฉ์–ด ์„ค๋ช… / ๋ฐฐ๊ฒฝ์ง€์‹๊ณผ ๊ฐ™์ด ๊ฐœ๋…์ ์ธ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ์„ฑ๊ฒฉ์ด ๊ฐ•ํ•˜๋ฉด ๊ต์œก์ž๋ฃŒ์—์„œ,

์‚ฐ์—… / ๊ธฐ์—…๋“ค์˜ '์ตœ์‹ ' ๋™ํ–ฅ์„ ํŒŒ์•…ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒฝ์šฐ ๋‰ด์Šค๊ธฐ์‚ฌ์—์„œ,

๊ทธ ์™ธ ๊ธฐ์—…๋“ค์˜ ๋Œ€๋žต์ ์ธ ํ˜„์žฌ ์ •๋ณด๋ฅผ ํŒŒ์•…ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฒฝ์šฐ ์ฆ๊ถŒ๋ณด๊ณ ์„œ์—์„œ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€๋‹ค.

 

๊ฒฝ์ œ๊ด€๋ จ์ด๋‚˜ ๋”ฑ ํ•˜๋‚˜๋กœ ๋‹ตํ•˜๊ธฐ ๋ชจํ˜ธํ•œ ์ฟผ๋ฆฌ๊ฐ€ ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ all, ๊ทธ ์™ธ ์„ฑ๊ฒฉ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ

1. ์ผ๋‹จ ๊ฒฝ์ œ ๊ด€๋ จ ์šฉ์–ด์ธ์ง€ ์ƒ๊ฐํ•ด๋ณด๊ณ 

2. ์˜ ์•„๋‹ˆ๋‹ค ์‹ถ์œผ๋ฉด ๋Œ€๋‹ต์„ ํ•˜์ง€ ์•Š๊ฒŒ

ํ•˜๋Š” ๊ทœ์น™์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด nothing๋„ ํ•จ๊ป˜ ์„ค๊ณ„ํ•ด์คฌ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ตœ๋Œ€ํ•œ ๋‘๊ฐœ์˜ label๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ์ผ์ด ์—†๋„๋ก ํ”„๋กฌํ”„ํŠธ์— ๋ช…์‹œํ–ˆ๋‹ค.

 

'SK', '์‚ผ์ „' ์ฒ˜๋Ÿผ ๊ธฐ์—…๋ช…๋งŒ ์น˜๋ฉด ์ฆ๊ถŒ๋ณด๊ณ ์„œ์—์„œ ์ •๋ณด๋ฅผ ์ฐพ์•„์ฃผ๊ณ ,

'SK ์ตœ๊ทผ ํ˜ธ์žฌ', 'ํ™˜์œจ ๋ณ€๋™ ์ถ”์ด' ์ด๋Ÿฐ '์ตœ์‹ /ํ˜„ํ™ฉ'๊ณผ ๊ฐ™์€ ํ๋ฆ„์ ์ธ ํ‚ค์›Œ๋“œ๋ฅผ ๊ฐ•์กฐํ•œ ์ฟผ๋ฆฌ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ๋‰ด์Šค๊ธฐ์‚ฌ๋กœ ์ž˜ ๋ถ„๋ฅ˜ํ•ด์ค€๋‹ค.

 

์ž‘๋™ ์›๋ฆฌ

prompt = ChatPromptTemplate.from_messages([
        ('system', 
        """
        ~~~~
        """),
        ('user', f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
    ])

 

ChatPromptTemplate.from_messages : ์ผ๋ฐ˜์ ์œผ๋กœ LangChain์„ ํ™œ์šฉํ•œ ๋Œ€ํ™”ํ˜• prompt๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๋งŽ์ด ์“ฐ๋Š” ํ•จ์ˆ˜ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. ๋ฐฐ์—ดํ˜•์œผ๋กœ message๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐ ๋ฉ”์‹œ์ง€๋Š” ์—ญํ• (role)๊ณผ ๋‚ด์šฉ(content)์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค. ๊ตฌ์„ฑ๋งŒ ์ง€ํ‚จ๋‹ค๋ฉด ์•ˆ์— ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ ๋ฐฉ์‹๋„ ๋น„๊ต์  ๋‹ค์–‘ํ•œ ํŽธ์ด๋‹ค.

 

์œ„์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ํŠœํ”Œ๋“ค์˜ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ "system", "user" ๋ฉ”์‹œ์ง€๋ฅผ ๋”ฐ๋กœ๋”ฐ๋กœ ๋‚˜๋ˆ  ์ž‘์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๊ณ , 

from langchain.schema.messages import SystemMessage, HumanMessage, AIMessage

prompt_messages = [
    SystemMessage(content=
        f"""
        ์‹œ์Šคํ…œ๋ฉ”์‹œ์ง€~~~
        """),
    HumanMessage(content=
        f"""
        ์ธ๊ฐ„๋ฉ”์‹œ์ง€~~~~
        """)
    ]

SystemMessage, HumanMessage๋ฅผ ๋ถˆ๋Ÿฌ์™€ ๊ฐ ์—ญํ•  ๋ณ„ ๋ฉ”์‹œ์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์กด์žฌํ•œ๋‹ค. ๋ชฉ์ ์— ๋งž๊ฒŒ, ํŽธํ•œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

๋˜ํ•œ f-string์„ ํ™œ์šฉํ•˜์—ฌ ํ”„๋กฌํ”„ํŠธ ์ž์ฒด์— ๋ณ€์ˆ˜ ์„ ์–ธ์„ ํ•ด์ฃผ๊ณ , ๋‚˜์ค‘์— invoke๋ฅผ ํ†ตํ•ด chain์„ ์‹คํ–‰ํ•  ๋•Œ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๊ฐ’์„ ๋ฐ”์ธ๋”ฉ์ด ๊ฐ€๋Šฅํ•œ๋ฐ, ์ด๋ฅผ ํ†ตํ•ด ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ๋ณ€ํ˜•ํ•˜๋ฉฐ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŠนํžˆ ์ฑ—๋ด‡๊ฐ™์€ ๋™์  ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

 

chain = prompt | self.llm
classification = chain.invoke({"query": query}).content.strip().lower()

์ดํ›„ chain์ด๋ผ๋Š” ๋ณ€์ˆ˜์— ํ”„๋กฌํ”„ํŠธ์™€ ๋ฏธ๋ฆฌ ์„ ์–ธํ–ˆ๋˜ llm ๋ชจ๋ธ(gpt-4o ์‚ฌ์šฉ)์„ ๋น„ํŠธ์—ฐ์‚ฐ์ž '|'๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•œ ์ฒด์ด๋‹ ์—ฐ์‚ฐ์ž๋ฅผ ํ™œ์šฉํ•˜์—ฌ LangChain ๊ตฌ์กฐ๋ฅผ ํ• ๋‹นํ•ด์ค€๋‹ค.

 

์—ฌ๊ธฐ์„œ ๋“ฑ์žฅํ•˜๋Š” ์ฒด์ด๋‹ ์—ฐ์‚ฐ์ž '|'๋Š” LCEL(LangChain Expression Language)์—์„œ ๋“ฑ์žฅํ•˜๋Š” ๊ฐœ๋…์œผ๋กœ, ์„œ๋กœ ๋‹ค๋ฅธ ๊ตฌ์„ฑ์š”์†Œ์ธ ํ”„๋กฌํ”„ํŠธ์™€ LLM ๋ชจ๋ธ๊ณผ Parser(์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•˜์ง„ ์•Š์•˜์ง€๋งŒ ์ž์ฃผ ์‚ฌ์šฉํ•จ), ์•„๋‹ˆ๋ฉด ๋˜๋‹ค๋ฅธ LLM ๋ชจ๋ธ ๋“ฑ๋“ฑ์„ ์—ฎ์–ด ๊ตฌ์„ฑ ์š”์†Œ๋“ค์„ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ํ”„๋กฌํ”„ํŠธ | ๋ชจ๋ธ | ํŒŒ์„œ ์™€ ๊ฐ™์ด ์„ ์–ธํ•˜๋Š”๋ฐ, ์ •ํ•ด์ง„ ๊ทœ์น™์€ ์•„๋‹ˆ์ง€๋งŒ ๋ฐ์ดํ„ฐ์˜ ์ „๋‹ฌ ํ๋ฆ„์„ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณดํ†ต ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ•œ๋‹ค.

chain1 = prompt1 | llm | JsonOutputParser()
chain2 =(
     chain1 | prompt2 | llm | StrOutputParser()
)

๋ฐ˜๋“œ์‹œ ์ € ํ˜•์‹์„ ์ง€์ผœ์•ผ๋œ๋‹ค๋Š” ๊ฒƒ์ด ์•„๋‹ ๋ฟ, ์œ„์™€ ๊ฐ™์ด ๋” ๋ณตํ•ฉ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ •์˜ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์ธ๊ณต์ง€๋Šฅ ๋ชจ๋ธ๋“ค์„ ํ™•์žฅ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค!

 

prompt ๋‚ด์— {query} ๋ผ๋Š” ๋ณ€์ˆ˜๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, invokeํ• ๋•Œ ์•ˆ์— dictionaryํ˜• ์ธ์ž๋กœ ๋„˜๊ฒจ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ฒŒ ๋‹ค์–‘ํ•œ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ๋ถ„๋ฅ˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

2. ๋ถ„๋ฅ˜๋œ ์งˆ์˜๋ฌธ๊ณผ DB ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๋ณ€ ์ƒ์„ฑ

def generate_answer(self, query: str, classification: str, context: str) -> str:
    # ๋ถ„๋ฅ˜๊ฐ€ nothing์ผ ๊ฒฝ์šฐ: ๊ฒฝ์ œ ์—ฌ๋ถ€ ํŒ๋‹จ
    if classification == "nothing":
        prompt = ChatPromptTemplate.from_messages([
            SystemMessage(
                """
                ๋„ˆ๋Š” ์ฃผ์‹ ๊ต์œก ๋ฐ ์ข…๋ชฉ ์ถ”์ฒœ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ฒญ์†Œ๋…„ ๋งž์ถค ๊ฒฝ์ œ ๊ต์œก ์ฑ—๋ด‡์ด์•ผ.
                ์‚ฌ์šฉ์ž์˜ ์งˆ์˜๊ฐ€ ๊ฒฝ์ œ ๊ด€๋ จ์ด๋ฉด ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ๊ณ , ์•„๋‹ˆ๋ผ๋ฉด '๊ฒฝ์ œ์™€ ๊ด€๋ จ๋œ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!' ๋ผ๊ณ  ๋‹ต๋ณ€ํ•ด.
                ๋งํˆฌ๋Š” ์กด๋Œ“๋ง์„ ์œ ์ง€ํ•ด์ค˜.
                """
            ),
            HumanMessage(f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
        ])
    else:
    prompt = ChatPromptTemplate.from_messages([
            SystemMessage(
                f"""
                ๋„ˆ๋Š” ์ฃผ์‹ ๊ต์œก ๋ฐ ์ถ”์ฒœ ์ข…๋ชฉ์— ๊ด€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ฒญ์†Œ๋…„ ๋งž์ถค ๊ฒฝ์ œ ๊ต์œก ์ฑ—๋ด‡์ด์•ผ.
                ์•„๋ž˜๋Š” ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์„œ ์ •๋ณด์•ผ:\n\n{context}
                
                ์œ„ ์ •๋ณด๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์งˆ์˜์— ๋Œ€ํ•ด ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ค˜.
                ํŠนํžˆ ๋‰ด์Šค ๊ธฐ๋ฐ˜์ด๋ผ๋ฉด ์ตœ๊ทผ ๋™ํ–ฅ์„ ํฌํ•จํ•ด์ค˜.
                ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”์ฒœ ์ข…๋ชฉ๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ค˜.
                ๋งํˆฌ๋Š” ์กด๋Œ“๋ง์„ ์œ ์ง€ํ•ด์ค˜.
                """
            ),
            HumanMessage(f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
        ])

    chain = prompt | self.llm
    return chain.invoke({"query": query, "combined_info": context}).content

์ž‘๋™ ๋ฐฉ์‹

์ฟผ๋ฆฌ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๊ฐ€ nothing์ผ ๊ฒฝ์šฐ, ๊ฒฝ์ œ ๊ด€๋ จ์œผ๋กœ ๋‹ตํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒ€ํ† ํ•˜๊ณ  ์•Œ์•„์„œ ์ปทํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋จผ์ € ๋ถ„๊ธฐํ–ˆ๋‹ค.

๊ทธ ์™ธ์˜ ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด์„œ๋Š”, prompt์— ๋ฒกํ„ฐDB์—์„œ ์ฐพ์€ ๊ฒฐ๊ณผ๋ฅผ {context} ๋ณ€์ˆ˜์— ๋‹ด์•„ ์ฐธ๊ณ  ์ž๋ฃŒ๋กœ ํ™œ์šฉํ•˜์—ฌ RAG(๊ฒ€์ƒ‰ ์ฆ๊ฐ• ์ƒ์„ฑ)๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์„ค๊ณ„ํ–ˆ๋‹ค.

 

์‚ฌ์‹ค ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ LangChain์— ์ข€ ์ต์ˆ™ํ•ด์ง€๊ณ  ์‹ถ์–ด์„œ ์ด์ „์— ์ฟผ๋ฆฌ ๋ถ„๋ฅ˜๊ธฐ์—์„œ๋Š” ์‹œ์Šคํ…œ๊ณผ ์œ ์ € ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํŠœํ”Œ๋กœ ์ •์˜ํ–ˆ๊ณ , ์—ฌ๊ธฐ์„œ๋Š” SystemMessage์™€ HumanMessage๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.

๋ญ˜ ์จ๋„ ๋‘˜๋‹ค ๋˜‘๊ฐ™์ด ์ž‘๋™ํ•˜๋‹ˆ๊นŒ ํŽธํ•˜๊ฒŒ ์“ฐ๋ฉด ๋œ๋‹ค.

 

์ด์ „๊ณผ ๋˜‘๊ฐ™์ด chain์œผ๋กœ ์—ฎ์–ด๋‚ด invokeํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ ์ž‘๋™ ์›๋ฆฌ๋Š” ํŒจ์Šคํ•œ๋‹ค.

 

์‹คํ–‰ ๊ฒฐ๊ณผ

์ฑ—๋ด‡์˜ ๋ชฉ์ ์— ์ ํ•ฉํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ–ˆ๋‹ค๋ฉด, ์œ„์™€ ๊ฐ™์ด ์ƒ๊ฐ๋ณด๋‹ค ๋” ๊ดœ์ฐฎ์€ ๋‹ต๋ณ€์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ตœ์‹  ๋‰ด์Šค๋ฅผ ํฌ๋กค๋งํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ฑ—์ง€ํ”ผํ‹ฐ์— ๋ฐ”๋กœ ์งˆ๋ฌธํ•˜๋ฉด ๋‚˜์˜ฌ ์ˆ˜ ์—†๋Š” ๋‚ด์šฉ๊นŒ์ง€ ๋‹ต์œผ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.

์ˆ˜์ง‘ํ•œ ๋ฐ์ดํ„ฐ์˜ ํ€„๋ฆฌํ‹ฐ๊ฐ€ ๋‹ต์•ˆ์— ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์˜ํ–ฅ์„ ๋ผ์น˜๊ธฐ ๋•Œ๋ฌธ์—, ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์ž˜ ์ •์ œํ•˜๋Š” ๊ฒƒ์ด ์ œ์ผ ์ค‘์š”ํ•˜๋‹ค.

 

๋˜ํ•œ, ์•ž์„œ ๋ฏธ๋ฆฌ ์ฒ˜๋ฆฌํ•ด์ค€ ์ฝ”๋“œ ๋•๋ถ„์— ์ด์ƒํ•œ๊ฑธ ์งˆ๋ฌธํ•ด๋„ ์ด๋ ‡๊ฒŒ ๋จน๊ธˆ๋„ ์ž˜ํ•œ๋‹ค.

๋งŒ์กฑ์Šค๋Ÿฝ๋‹ค. ๐Ÿ‘

 

์ „์ฒด ์ฝ”๋“œ

์ฝ”๋“œ ๋ณด๊ธฐ
import os
import openai
from dotenv import load_dotenv

from langchain_chroma import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain_openai import ChatOpenAI
from langchain.schema.messages import SystemMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate


class ChatBot:
    def __init__(self):
        load_dotenv()
        openai.api_key = os.getenv("OPENAI_API_KEY")
        os.environ["OPEN_API_KEY"] = openai.api_key  # ํ˜น์‹œ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋Œ€๋น„

        self.llm = ChatOpenAI(model_name="gpt-4o", temperature=0.2, max_tokens=1024)

        # ๊ฐ Chroma DB ๋กœ๋“œ
        self.edu_db = self.load_vector_store("chroma_edu_db")
        self.news_db = self.load_vector_store("chroma_news_db")
        self.report_db = self.load_vector_store("chroma_report_db")

    def load_vector_store(self, persist_directory: str) -> Chroma:
        """์ง€์ •๋œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ Chroma ๋ฒกํ„ฐ์Šคํ† ์–ด๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค."""
        return Chroma(
            persist_directory=persist_directory,
            embedding_function=OpenAIEmbeddings()
        )

    def classify_query(self, query: str) -> str:
        """์‚ฌ์šฉ์ž ์งˆ์˜๋ฅผ edu, news, report ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค."""

        prompt = ChatPromptTemplate.from_messages([
            ('system', 
            """
            ๋‹ค์Œ ์งˆ์˜๋ฅผ ์ฝ๊ณ  ๊ด€๋ จ๋œ ์ •๋ณด ์œ ํ˜•์„ ํ•œ ๋‹จ์–ด๋กœ ์ถœ๋ ฅํ•˜์„ธ์š”.
            ๊ฐ€๋Šฅํ•œ ๊ฐ’: "edu", "news", "report", "all", "nothing".
            
            - edu: ๊ฒฝ์ œ ์šฉ์–ด๋‚˜ ๊ฐœ๋… ๋“ฑ ๋ฐฐ๊ฒฝ์ง€์‹
            - news: ์ตœ์‹  ์‚ฐ์—… ๋™ํ–ฅ
            - report: ๊ธฐ์—… ๋ถ„์„, ์žฌ๋ฌด ์ •๋ณด ๋“ฑ
            - all/nothing: ๋ฒ”์ฃผ ๊ตฌ๋ถ„์ด ๋ถˆ๋ช…ํ™•ํ•  ๋•Œ

            "nothing"๊ณผ "all"์€ ์ตœ์†Œํ•œ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ ,
            ๊ฐ€๋Šฅํ•˜๋ฉด edu, news, report ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜์„ธ์š”.
            """),
            ('user', f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
        ])

        chain = prompt | self.llm
        classification = chain.invoke({"query": query}).content.strip().lower()
        return classification

    def search_documents(self, query: str, classification: str) -> str:
        """๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ DB์—์„œ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•ด ๋‚ด์šฉ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค."""

        combined_info = ""

        if classification in ["edu", "all"]:
            edu_docs = self.edu_db.similarity_search(query, k=3)
            if edu_docs:
                combined_info += "ใ€์œ ํŠœ๋ธŒ ๊ฒฝ์ œ ๊ต์œก ์ž๋ฃŒ (์ฒญ์†Œ๋…„์šฉ)ใ€‘\n"
                combined_info += "\n".join(doc.page_content for doc in edu_docs) + "\n\n"

        if classification in ["news", "all"]:
            news_docs = self.news_db.similarity_search(query, k=3)
            if news_docs:
                combined_info += "ใ€๊ฒฝ์ œ ๊ด€๋ จ ๋‰ด์Šคใ€‘\n"
                combined_info += "\n".join(doc.page_content for doc in news_docs) + "\n\n"

        if classification in ["report", "all"]:
            report_docs = self.report_db.similarity_search(query, k=3)
            if report_docs:
                combined_info += "ใ€์ฆ๊ถŒ ๋ณด๊ณ ์„œใ€‘\n"
                combined_info += "\n".join(doc.page_content for doc in report_docs) + "\n\n"

        return combined_info

    def generate_answer(self, query: str, classification: str, context: str) -> str:
        """๋ถ„๋ฅ˜๋œ ์งˆ์˜์™€ ๊ด€๋ จ ๋ฌธ์„œ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ GPT๋กœ ๋‹ต๋ณ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."""

        # ๋ถ„๋ฅ˜๊ฐ€ nothing์ผ ๊ฒฝ์šฐ: ๊ฒฝ์ œ ์—ฌ๋ถ€ ํŒ๋‹จ
        if classification == "nothing":
            prompt = ChatPromptTemplate.from_messages([
                SystemMessage(
                    """
                    ๋„ˆ๋Š” ์ฃผ์‹ ๊ต์œก ๋ฐ ์ข…๋ชฉ ์ถ”์ฒœ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ฒญ์†Œ๋…„ ๋งž์ถค ๊ฒฝ์ œ ๊ต์œก ์ฑ—๋ด‡์ด์•ผ.
                    ์‚ฌ์šฉ์ž์˜ ์งˆ์˜๊ฐ€ ๊ฒฝ์ œ ๊ด€๋ จ์ด๋ฉด ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ๊ณ , ์•„๋‹ˆ๋ผ๋ฉด '๊ฒฝ์ œ์™€ ๊ด€๋ จ๋œ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!' ๋ผ๊ณ  ๋‹ต๋ณ€ํ•ด.
                    ๋งํˆฌ๋Š” ์กด๋Œ“๋ง์„ ์œ ์ง€ํ•ด์ค˜.
                    """
                ),
                HumanMessage(f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
            ])
        else:
            prompt = ChatPromptTemplate.from_messages([
                SystemMessage(
                    f"""
                    ๋„ˆ๋Š” ์ฃผ์‹ ๊ต์œก ๋ฐ ์ถ”์ฒœ ์ข…๋ชฉ์— ๊ด€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ฒญ์†Œ๋…„ ๋งž์ถค ๊ฒฝ์ œ ๊ต์œก ์ฑ—๋ด‡์ด์•ผ.
                    ์•„๋ž˜๋Š” ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์„œ ์ •๋ณด์•ผ:\n\n{context}
                    
                    ์œ„ ์ •๋ณด๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์งˆ์˜์— ๋Œ€ํ•ด ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ค˜.
                    ํŠนํžˆ ๋‰ด์Šค ๊ธฐ๋ฐ˜์ด๋ผ๋ฉด ์ตœ๊ทผ ๋™ํ–ฅ์„ ํฌํ•จํ•ด์ค˜.
                    ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”์ฒœ ์ข…๋ชฉ๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ค˜.
                    ๋งํˆฌ๋Š” ์กด๋Œ“๋ง์„ ์œ ์ง€ํ•ด์ค˜.
                    """
                ),
                HumanMessage(f"์งˆ์˜: {query}\n๋‹ต๋ณ€:")
            ])

        chain = prompt | self.llm
        return chain.invoke({"query": query, "combined_info": context}).content

    def run_query(self, query: str) -> str:
        """์ „์ฒด ์งˆ์˜ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค"""
        classification = self.classify_query(query)
        print(f"\n[์งˆ์˜ ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ]: {classification}")

        context = self.search_documents(query, classification)
        return self.generate_answer(query, classification, context)

    def input_query(self, user_query: str = ""):
        print("\n[์‚ฌ์šฉ์ž ์งˆ์˜]:", user_query)
        response = self.run_query(user_query)
        print("\n[์ฑ—๋ด‡ ์‘๋‹ต]:\n", response)


if __name__ == "__main__":
    chatbot = ChatBot()
    while True:
        user_input = input("\n์งˆ์˜๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์ข…๋ฃŒํ•˜๋ ค๋ฉด 'quit' ์ž…๋ ฅ): ")
        if user_input.strip().lower() == 'quit':
            print("์ฑ—๋ด‡์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.")
            break
        chatbot.input_query(user_input)

 

Comments