7. ๊ตฌ์กฐํ™”๋œ ์ถœ๋ ฅ - QuizGPT ๋งŒ๋“ค๊ธฐ

๐ŸŽฏ ์ด ์ฑ•ํ„ฐ์—์„œ ๋ฐฐ์šธ ๊ฒƒ

  • LLM์ด ์ƒ์„ฑํ•˜๋Š” ํ…์ŠคํŠธ๋ฅผ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์ •ํ™•ํ•œ ํ˜•์‹(JSON)์œผ๋กœ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•
  • WikipediaRetriever๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์™ธ๋ถ€ ์ง€์‹ ์†Œ์Šค์—์„œ ๋™์ ์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•
  • Pydantic/JSON Output Parser: ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง๊ณผ ํŒŒ์„œ๋ฅผ ํ†ตํ•ด LLM์˜ ์ถœ๋ ฅ์„ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๊ธฐ๋ฒ•
  • Function Calling: ๋ชจ๋ธ์ด ํŠน์ • ํ•จ์ˆ˜์˜ ์ธ์ž(argument) ํ˜•์‹์— ๋งž์ถฐ JSON์„ ์ƒ์„ฑํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋Š” ๋” ๊ฐ•๋ ฅํ•˜๊ณ  ์•ˆ์ •์ ์ธ ๊ธฐ๋ฒ•
  • ๋‘ ๊ฐœ์˜ ์ฒด์ธ(ํ€ด์ฆˆ ์ƒ์„ฑ, ์ฑ„์ )์„ ์—ฐ๊ฒฐํ•˜์—ฌ ๋ณตํ•ฉ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • Streamlit์˜ st.form์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์งˆ๋ฌธ์„ ํ•œ ๋ฒˆ์— ์ œ์ถœํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

๋ฐ์ดํ„ฐ ์†Œ์Šค ์—ฐ๊ฒฐ ๋ฐ ๋ชจ๋ธ ์„ค์ •

๐ŸŽฏ ์ด๋ฒˆ ๋‹จ๊ณ„์—์„œ ๋ฐฐ์šธ ๊ฒƒ

  • WikipediaRetriever๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ฃผ์ œ์— ๋Œ€ํ•œ Wikipedia ๋ฌธ์„œ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•
  • gpt-3.5-turbo-1106๊ณผ ๊ฐ™์€ ์ตœ์‹  ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ๊ณผ ๋น„์šฉ ํšจ์œจ์„ฑ์„ ๋†’์ด๋Š” ๋ฐฉ๋ฒ•
  • Streamlit์˜ st.sidebar์™€ st.selectbox๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐ์ดํ„ฐ ์†Œ์Šค(ํŒŒ์ผ ๋˜๋Š” Wikipedia) ์„ ํƒ ์˜ต์…˜์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ 1๋‹จ๊ณ„: Wikipedia Retriever ์—ฐ๋™

์ „์ฒด ์ฝ”๋“œ (pages/03_QuizGPT.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import streamlit as st
from langchain.retrievers import WikipediaRetriever
from langchain.chat_models import ChatOpenAI

# GPT-3.5-Turbo-1106 ๋ชจ๋ธ ์‚ฌ์šฉ
llm = ChatOpenAI(
temperature=0.1,
model="gpt-3.5-turbo-1106",
)

with st.sidebar:
docs = None
choice = st.selectbox(
"Choose what you want to use.",
("File", "Wikipedia Article"),
)
if choice == "File":
# ... ํŒŒ์ผ ์—…๋กœ๋“œ ๋กœ์ง ...
else:
topic = st.text_input("Search Wikipedia...")
if topic:
# Wikipedia์—์„œ ๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰
retriever = WikipediaRetriever(top_k_results=5)
with st.status("Searching Wikipedia..."):
docs = retriever.get_relevant_documents(topic)

if not docs:
st.markdown("Get started by uploading a file or searching on Wikipedia...")
else:
st.write(docs) # ๋กœ๋“œ๋œ ๋ฌธ์„œ ํ‘œ์‹œ

๐Ÿ” ์ฝ”๋“œ ์ƒ์„ธ ์„ค๋ช…

1. WikipediaRetriever

  • LangChain์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠน์ˆ˜ํ•œ ๊ฒ€์ƒ‰๊ธฐ(Retriever)๋กœ, ์‚ฌ์šฉ์ž์˜ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ›์•„ Wikipedia์—์„œ ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  Document ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • top_k_results: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ค‘์—์„œ ์ƒ์œ„ ๋ช‡ ๊ฐœ์˜ ๋ฌธ์„œ๋ฅผ ๊ฐ€์ ธ์˜ฌ์ง€ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ํŒŒ์ผ ์—…๋กœ๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ํŠน์ • ์ฃผ์ œ์— ๋Œ€ํ•ด ์ฆ‰์„์—์„œ ํ€ด์ฆˆ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

2. GPT-4 Turbo (gpt-3.5-turbo-1106)

  • ์ด ๋ชจ๋ธ์€ ๋” ๊ธด ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ๋ฅผ ์ง€์›ํ•˜๊ณ , JSON ๋ชจ๋“œ์™€ ๊ฐ™์€ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ด์ „ ๋ชจ๋ธ๋ณด๋‹ค ๋น„์šฉ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ์ด๋ฒˆ ์ฑ•ํ„ฐ์— ๋งค์šฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • wikipedia ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ–ˆ๋‚˜์š”?
  • WikipediaRetriever๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฃผ์ œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  Document๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์™”๋‚˜์š”?
  • st.selectbox๋ฅผ ํ†ตํ•ด ๋‘ ๊ฐ€์ง€ ๋ฐ์ดํ„ฐ ์†Œ์Šค(ํŒŒ์ผ, Wikipedia) ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•˜๋Š” UI๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‚˜์š”?

Output Parsers: LLM ๊ธธ๋“ค์ด๊ธฐ

๐ŸŽฏ ์ด๋ฒˆ ๋‹จ๊ณ„์—์„œ ๋ฐฐ์šธ ๊ฒƒ

  • LLM์ด ์ƒ์„ฑํ•œ ํ‰๋ฒ”ํ•œ ํ…์ŠคํŠธ๋ฅผ ์—„๊ฒฉํ•œ JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” 2๋‹จ๊ณ„ ์ฒด์ธ(์ƒ์„ฑ -> ํฌ๋งทํŒ…) ์„ค๊ณ„
  • questions_prompt: ์ฃผ์–ด์ง„ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŠน์ • ํ˜•์‹์˜ ํ…์ŠคํŠธ ํ€ด์ฆˆ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์‹œ
  • formatting_prompt: ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ ํ€ด์ฆˆ๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋„๋ก ์ง€์‹œ
  • JsonOutputParser: LLM์ด ์ƒ์„ฑํ•œ JSON ๋ฌธ์ž์—ด์„ Python ๋”•์…”๋„ˆ๋ฆฌ๋กœ ํŒŒ์‹ฑํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ 1๋‹จ๊ณ„: 2๋‹จ๊ณ„ ์ฒด์ธ๊ณผ JSON ํŒŒ์„œ ๊ตฌํ˜„

์ „์ฒด ์ฝ”๋“œ (pages/03_QuizGPT.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import json
from langchain.schema import BaseOutputParser

# 1. ์‚ฌ์šฉ์ž ์ •์˜ JSON ํŒŒ์„œ
class JsonOutputParser(BaseOutputParser):
def parse(self, text):
text = text.replace("```", "").replace("json", "")
return json.loads(text)

output_parser = JsonOutputParser()

# 2. ํ€ด์ฆˆ ์งˆ๋ฌธ ์ƒ์„ฑ์„ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ์™€ ์ฒด์ธ
questions_prompt = ChatPromptTemplate.from_messages([
("system", "... Based ONLY on the following context make 10 questions... Use (o) to signal the correct answer. ... Context: {context}")
])
questions_chain = {"context": format_docs} | questions_prompt | llm

# 3. ์ƒ์„ฑ๋œ ์งˆ๋ฌธ์„ JSON์œผ๋กœ ํฌ๋งทํŒ…ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กฌํ”„ํŠธ์™€ ์ฒด์ธ
formatting_prompt = ChatPromptTemplate.from_messages([
("system", "... You format exam questions into JSON format. ... Example Output: ... Questions: {context}")
])
formatting_chain = formatting_prompt | llm

# 4. ์ตœ์ข… ์ฒด์ธ: ์ƒ์„ฑ -> ํฌ๋งทํŒ… -> ํŒŒ์‹ฑ
chain = {"context": questions_chain} | formatting_chain | output_parser

# ... Streamlit ๋กœ์ง ...
if start:
response = chain.invoke(docs)
st.write(response) # Python ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ์ถœ๋ ฅ๋จ

๐Ÿ” ์ฝ”๋“œ ์ƒ์„ธ ์„ค๋ช…

1. ์™œ 2๋‹จ๊ณ„ ์ฒด์ธ์„ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€?
LLM์—๊ฒŒ ํ•œ ๋ฒˆ์˜ ํ”„๋กฌํ”„ํŠธ๋กœ ๋ณต์žกํ•œ ์ž‘์—…(๋‚ด์šฉ ๊ธฐ๋ฐ˜ ์งˆ๋ฌธ ์ƒ์„ฑ + ์™„๋ฒฝํ•œ JSON ํ˜•์‹ ์ค€์ˆ˜)์„ ๋ชจ๋‘ ์‹œํ‚ค๋ฉด ์‹คํŒจํ•  ํ™•๋ฅ ์ด ๋†’์Šต๋‹ˆ๋‹ค. ์ž‘์—…์„ ๋‘ ๋‹จ๊ณ„๋กœ ๋‚˜๋ˆ„๋ฉด ๊ฐ ๋‹จ๊ณ„์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•ด์ ธ ํ›จ์”ฌ ์•ˆ์ •์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • questions_chain: ๋‚ด์šฉ์— ์ง‘์ค‘ํ•˜์—ฌ ํ€ด์ฆˆ ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€์„ ํ…์ŠคํŠธ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • formatting_chain: ์•ž์„œ ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›์•„, ์˜ค์ง JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐ์—๋งŒ ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค.

2. JsonOutputParser

  • LLM์€ ์ข…์ข… JSON ์‘๋‹ต์„ ```json ... ``` ์ฝ”๋“œ ๋ธ”๋ก์œผ๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒŒ์„œ๋Š” parse ๋ฉ”์†Œ๋“œ์—์„œ ์ด๋Ÿฌํ•œ ๋ถˆํ•„์š”ํ•œ ๋ฌธ์ž์—ด์„ ์ œ๊ฑฐํ•˜๊ณ  json.loads()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆœ์ˆ˜ํ•œ JSON ๋ฌธ์ž์—ด์„ Python ๊ฐ์ฒด(๋”•์…”๋„ˆ๋ฆฌ/๋ฆฌ์ŠคํŠธ)๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ์งˆ๋ฌธ ์ƒ์„ฑ๊ณผ JSON ํฌ๋งทํŒ…์„ ์œ„ํ•œ ๋‘ ๊ฐœ์˜ ๋ถ„๋ฆฌ๋œ ChatPromptTemplate์„ ๋งŒ๋“ค์—ˆ๋‚˜์š”?
  • BaseOutputParser๋ฅผ ์ƒ์†๋ฐ›์•„ JsonOutputParser๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‚˜์š”?
  • LCEL์„ ์‚ฌ์šฉํ•˜์—ฌ questions_chain, formatting_chain, output_parser๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ์—ฐ๊ฒฐํ–ˆ๋‚˜์š”?

ํ€ด์ฆˆ UI ๋ฐ ์ฑ„์ 

๐ŸŽฏ ์ด๋ฒˆ ๋‹จ๊ณ„์—์„œ ๋ฐฐ์šธ ๊ฒƒ

  • @st.cache_data๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์ฒด์ธ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•˜๋Š” ๋ฐฉ๋ฒ•
  • st.form์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์งˆ๋ฌธ๊ณผ ๋ผ๋””์˜ค ๋ฒ„ํŠผ์„ ๊ทธ๋ฃนํ™”ํ•˜๊ณ  ํ•œ ๋ฒˆ์— ์ œ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœํ•œ ๋‹ต์•ˆ์„ ์ •๋‹ต๊ณผ ๋น„๊ตํ•˜์—ฌ ์ฆ‰์‹œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ 1๋‹จ๊ณ„: ํ€ด์ฆˆ ํผ(Form) ์ƒ์„ฑ ๋ฐ ์ฑ„์  ๋กœ์ง ๊ตฌํ˜„

์ „์ฒด ์ฝ”๋“œ (pages/03_QuizGPT.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ... (์ด์ „ ์ฝ”๋“œ)

@st.cache_data(show_spinner="Making quiz...")
def run_quiz_chain(_docs, topic):
chain = {"context": questions_chain} | formatting_chain | output_parser
return chain.invoke(_docs)

# ... (์‚ฌ์ด๋“œ๋ฐ” ๋กœ์ง)

if docs:
response = run_quiz_chain(docs, topic if topic else file.name)

with st.form("questions_form"):
for question in response["questions"]:
st.write(question["question"])
# ๋ผ๋””์˜ค ๋ฒ„ํŠผ์œผ๋กœ ์„ ํƒ์ง€ ํ‘œ์‹œ
value = st.radio(
"Select an option.",
[answer["answer"] for answer in question["answers"]],
index=None, # ๊ธฐ๋ณธ ์„ ํƒ ์—†์Œ
)
# ์ •๋‹ต ํ™•์ธ ๋กœ์ง
if {"answer": value, "correct": True} in question["answers"]:
st.success("Correct!")
elif value is not None:
st.error("Wrong!")

button = st.form_submit_button()

๐Ÿ” ์ฝ”๋“œ ์ƒ์„ธ ์„ค๋ช…

1. st.form

  • with st.form(...) ๋ธ”๋ก ์•ˆ์— ์žˆ๋Š” ๋ชจ๋“  ์œ„์ ฏ๋“ค์€ ํ•˜๋‚˜์˜ ํผ์œผ๋กœ ๊ทธ๋ฃนํ™”๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ผ๋””์˜ค ๋ฒ„ํŠผ์ด๋‚˜ ํ…์ŠคํŠธ ์ž…๋ ฅ์„ ๋ณ€๊ฒฝํ•ด๋„ ์•ฑ์€ ์žฌ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์˜ค์ง st.form_submit_button()์„ ํด๋ฆญํ–ˆ์„ ๋•Œ๋งŒ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์žฌ์‹คํ–‰๋˜๋ฉฐ, ๊ทธ ์‹œ์ ์˜ ๋ชจ๋“  ์œ„์ ฏ ๊ฐ’๋“ค์ด ํ•œ ๋ฒˆ์— ์ œ์ถœ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ€ด์ฆˆ๋‚˜ ์„ค๋ฌธ์กฐ์‚ฌ์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ์ž…๋ ฅ์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

2. ์ฑ„์  ๋กœ์ง

  • st.radio๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๋‹ต๋ณ€์˜ ๋ฌธ์ž์—ด์„ value์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • {"answer": value, "correct": True} in question["answers"] ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๋‹ต๋ณ€์ด ์ •๋‹ต ๋ฆฌ์ŠคํŠธ ์•ˆ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • run_quiz_chain ํ•จ์ˆ˜์— @st.cache_data๋ฅผ ์ ์šฉํ•˜์—ฌ ํ€ด์ฆˆ ์ƒ์„ฑ์„ ์บ์‹ฑํ–ˆ๋‚˜์š”?
  • st.form์„ ์‚ฌ์šฉํ•˜์—ฌ ํ€ด์ฆˆ ์ „์ฒด๋ฅผ ๊ฐ์ŒŒ๋‚˜์š”?
  • for ๋ฃจํ”„ ์•ˆ์—์„œ st.radio๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ์„ ํƒ์ง€๋ฅผ ํ‘œ์‹œํ–ˆ๋‚˜์š”?
  • ์‚ฌ์šฉ์ž์˜ ์„ ํƒ(value)์„ ์ •๋‹ต๊ณผ ๋น„๊ตํ•˜์—ฌ โ€œCorrect!โ€ ๋˜๋Š” โ€œWrong!โ€ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ–ˆ๋‚˜์š”?

Function Calling: ๋” ์•ˆ์ •์ ์ธ ๊ตฌ์กฐํ™”

๐ŸŽฏ ์ด๋ฒˆ ๋‹จ๊ณ„์—์„œ ๋ฐฐ์šธ ๊ฒƒ

  • OpenAI์˜ Function Calling ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ LLM์ด ํ•ญ์ƒ ์ง€์ •๋œ JSON ์Šคํ‚ค๋งˆ์— ๋งž์ถฐ ์‘๋‹ตํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿ“ 1๋‹จ๊ณ„: Function Calling์œผ๋กœ ํ€ด์ฆˆ ์ƒ์„ฑํ•˜๊ธฐ

์ „์ฒด ์ฝ”๋“œ (notebook.ipynb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate

# 1. ํ•จ์ˆ˜ ์Šคํ‚ค๋งˆ ์ •์˜ (JSON ์Šคํ‚ค๋งˆ ํ˜•์‹)
function = {
"name": "create_quiz",
"description": "...",
"parameters": {
"type": "object",
"properties": {
"questions": {
"type": "array",
"items": { # ... (์งˆ๋ฌธ, ๋‹ต๋ณ€, ์ •๋‹ต ์—ฌ๋ถ€์— ๋Œ€ํ•œ ์ƒ์„ธ ์Šคํ‚ค๋งˆ) ... }
}
},
"required": ["questions"],
},
}

# 2. LLM์— ํ•จ์ˆ˜๋ฅผ "bind(์—ฐ๊ฒฐ)"ํ•˜๊ธฐ
llm = ChatOpenAI(temperature=0.1).bind(
function_call={"name": "create_quiz"}, # ํ•ญ์ƒ ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๊ฐ•์ œ
functions=[function],
)

prompt = PromptTemplate.from_template("Make a quiz about {city}")
chain = prompt | llm

response = chain.invoke({"city": "rome"})

# 3. ๊ฒฐ๊ณผ ์ถ”์ถœ
arguments = response.additional_kwargs["function_call"]["arguments"]

๐Ÿ” ์ฝ”๋“œ ์ƒ์„ธ ์„ค๋ช…

1. Function Calling์ด๋ž€?
Function Calling์€ LLM์—๊ฒŒ ๋‹จ์ˆœํžˆ ํ…์ŠคํŠธ๋กœ ๋‹ต๋ณ€ํ•˜๋Š” ๋Œ€์‹ , ์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ ํŠน์ • ํ•จ์ˆ˜์˜ ์ธ์ž(arguments) ํ˜•์‹์— ๋งž๋Š” JSON ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ์š”์ฒญํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด๋Š” LLM์˜ ์ถœ๋ ฅ ํ˜•์‹์„ ๋งค์šฐ ์•ˆ์ •์ ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

  • ์žฅ์ : ๋ณต์žกํ•œ ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง(์˜ˆ: 2๋‹จ๊ณ„ ์ฒด์ธ)์ด๋‚˜ ์ถœ๋ ฅ ํŒŒ์„œ ์—†์ด๋„, ๋ชจ๋ธ์ด ๊ฑฐ์˜ ํ•ญ์ƒ ์™„๋ฒฝํ•œ JSON ์Šคํ‚ค๋งˆ๋ฅผ ์ค€์ˆ˜ํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. .bind(function_call=...)

  • LCEL์˜ .bind() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ LLM์„ ํ˜ธ์ถœํ•  ๋•Œ ํŠน์ • ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • function_call={"name": "create_quiz"}: LLM์—๊ฒŒ create_quiz ํ•จ์ˆ˜์˜ ์ธ์ž ํ˜•์‹์— ๋งž์ถฐ ์‘๋‹ตํ•˜๋ผ๊ณ  ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.
  • functions=[function]: LLM์—๊ฒŒ create_quiz ํ•จ์ˆ˜์˜ ์Šคํ‚ค๋งˆ(๊ตฌ์กฐ)๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

3. ๊ฒฐ๊ณผ

  • ์ฒด์ธ์„ ์‹คํ–‰ํ•˜๋ฉด, response์˜ content๋Š” ๋น„์–ด์žˆ๊ณ , additional_kwargs["function_call"]["arguments"] ์•ˆ์— ์›ํ•˜๋Š” JSON ํ˜•์‹์˜ ๋ฌธ์ž์—ด์ด ๋‹ด๊ฒจ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • JSON ์Šคํ‚ค๋งˆ ํ˜•์‹์œผ๋กœ ํ•จ์ˆ˜์˜ ์ด๋ฆ„, ์„ค๋ช…, ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ƒ์„ธํžˆ ์ •์˜ํ–ˆ๋‚˜์š”?
  • .bind()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ LLM์— ํ•จ์ˆ˜ ์Šคํ‚ค๋งˆ์™€ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ๊ฐ•์ œํ–ˆ๋‚˜์š”?
  • response.additional_kwargs์—์„œ ์ƒ์„ฑ๋œ JSON ๊ฒฐ๊ณผ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”์ถœํ–ˆ๋‚˜์š”?

์ถœ์ฒ˜ : https://nomadcoders.co/fullstack-gpt