728x90
반응형
1. PDF 파일 업로드
- streamlit의 'file_uploader'를 통해 파일 업로드
- type 지정으로, pdf 파일만 업로드 할 수 있도록 제한
uploaded_file = st.file_uploader(label="PDF 업로드", type=["pdf"])
2. OpenAI API KEY 입력
- streamlit의 'text_input'으로 사용자의 OpenAI API Key 입력
- models.list()를 호출하여, 입력한 API Key의 유효성 검사
user_api_key = st.text_input(label="Groq API키 입력", type="password", placeholder="sk-******")
def api_key_check(self):
try:
OpenAI(api_key=self.api_key).models.list()
return True
except AuthenticationError as e:
return False
except OpenAIError as e:
return False
3. 퀴즈 생성
- pdf 텍스트 추출
- PyMuPdf 라이브러리를 통해 페이지 별 텍스트 추출 후, 문장으로 구분하여 리스트 생성
class PDFTextExtractor:
def extract_text(self, file_obj):
doc = fitz.open(stream=file_obj.read(), filetype="pdf")
return "\n".join(page.get_text() for page in doc)
def split_sentences(self, text):
return [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if s.strip()]
- 입력한 주제와 관련한 내용 추출
- pdf 텍스트를 임베딩 모델로 임베딩 생성
- tiktoken 라이브러리로 openai에서 사용하는 tokenizer로 token 계산
- max_token을 설정하고, 이 token의 크기를 넘어가는 경우 chunk로 구분
- 추후 faiss 활용을 위해, np.array 형태로 반환
- 입력한 주제가 pdf의 내용과 관련이 없는 경우 재입력 요구

class EmbeddingGenerator:
def __init__(self, api_key, model="text-embedding-3-small", max_tokens=8192):
self.client = OpenAI(api_key=api_key)
self.model = model
self.max_tokens = max_tokens
self.tokenizer = tiktoken.encoding_for_model(model)
def get_token_length(self, text):
return len(self.tokenizer.encode(text))
def chunk_text(self, text):
tokens = self.tokenizer.encode(text)
chunks = [tokens[i:i+self.max_tokens] for i in range(0, len(tokens), self.max_tokens)]
return [self.tokenizer.decode(chunk) for chunk in chunks]
def embed_text(self, text):
if self.get_token_length(text) <= self.max_tokens:
response = self.client.embeddings.create(input=text, model=self.model)
return np.array(response.data[0].embedding, dtype=np.float32)
else:
chunks = self.chunk_text(text)
embeddings = [np.array(self.client.embeddings.create(input=c, model=self.model).data[0].embedding, dtype=np.float32) for c in chunks]
return np.mean(embeddings, axis=0)
def embed_texts(self, texts):
embeddings = []
for text in texts:
try:
emb = self.embed_text(text)
embeddings.append(emb)
except Exception as e:
print(f"[오류] '{text[:30]}...': {e}")
embeddings.append(np.zeros(1536, dtype=np.float32))
return np.array(embeddings, dtype=np.float32)
- 퀴즈 생성
- 사용자가 입력한 주제를 pdf 텍스트와 동일하게 임베딩
- 임베딩 결과를 '퀴즈생성 프롬프트'에 넣어 퀴즈 생성
- 반환 결과를 json 형식으로 load하고, json 형식이 아닐 경우 에러 반환
try:
response = client.chat.completions.create(
model = MODEL_NAME,
messages = [{"role":"user", "content":prompt}],
max_tokens=2048,
)
content = response.choices[0].message.content
return json.loads(content)
except AuthenticationError:
return "유효하지 않은 API 키 입니다. API 키를 확인해주세요!"
except OpenAIError as e:
return {f'result':'실패 이유 {e}'}
4. 퀴즈 화면 구성 및 정답 확인
- 퀴즈 출력
- question, options, answer, explanation 으로 구분되어 반환한 결과를 radio 버튼과 함께 출력

st.session_state['user_selected'] = {}
st.session_state['answers'] = {}
print(st.session_state['quiz'])
for i, q in enumerate(st.session_state['quiz']):
st.subheader(f"{i+1}번. {q['question']}")
selected = st.radio(
label="보기",
options=q['options'],
label_visibility="collapsed",
key=f"q_{i}"
)
st.session_state['user_selected'][i] = selected
st.session_state['answers'][i] = [q['answer'], q['explanation']]
submit = st.button(label="정답제출")
if submit:
answer_check(st.session_state['user_selected'], st.session_state['answers'])
- 정답확인
- st.dialog로 정답 확인 화면 제공
- 세션에 저장해둔 answer와 입력한 radio 버튼의 값을 비교하여 정답률 계산 및 해설 제공

@st.dialog("정답확인", width="large")
def answer_check(user_answer, answer):
score = sum([u == a[0] for u, a in zip(user_answer.values(), answer.values())])
total = len(answer)
st.header(f"정답확인 - {score} / {total} 문제 정답 ({int(score / total * 100)}점)")
for i in range(len(answer)):
st.write("내 정답")
st.write(f"{user_answer[i]}")
st.write("정답")
st.write(f"{answer[i][0]}")
st.write("해설")
st.write(f"{answer[i][1]}")
5. 초기화
- 퀴즈 생성시 세션에 저장해준 퀴즈 내용, 주제 등 내용 삭제
def clear_session(keys):
for key in keys:
st.session_state.pop(key, None)
if clear_button:
clear_session(['quiz_make_button', 'quiz', 'user_selected', 'answers', 'context', 'category'])
추가 고민 사항
- 문제 수 증가
- LangChain을 활용해, 동일한 코드로 다양한 모델 지원 추가
- 현재 pdf 텍스트를 문장 부호 기반의 구분에서, 의미 기반의 문장 구분을 통한 RAG 성능 개선
'ML_DL > MUJAKJUNG (무작정 시리즈)' 카테고리의 다른 글
[PDF 기반 퀴즈 생성기] 개선하기 (시간 단축) (0) | 2025.05.10 |
---|---|
텍스트 분할 (Text Spliter) (0) | 2025.05.08 |
[PDF 기반 퀴즈 생성기] 구성하기 (0) | 2025.05.02 |
[LangChain] 모델 파라미터 설정 (0) | 2025.04.09 |
[LangChain] 챗봇 구성하기 (0) | 2025.04.03 |