Spaces:
Running
Running
| import json | |
| import time | |
| from typing import List, Optional | |
| from pydantic import BaseModel, Field | |
| from langchain.prompts import ChatPromptTemplate | |
| from langchain_core.output_parsers import PydanticOutputParser | |
| class QuestionParsedResult(BaseModel): | |
| stem: str = Field(description="๋ฌธ์ ๋ณธ๋ฌธ", default="") | |
| options: List[str] = Field(description="๋ณด๊ธฐ ๋ชฉ๋ก (๊ฐ๊ด์์ผ ๊ฒฝ์ฐ)", default_factory=list) | |
| answer: List[str] = Field(description="์ ๋ต ๋๋ ์ ํ๊ฒฐ๊ณผ", default_factory=list) | |
| explanation: str = Field(description="๊ฐ๋จํ ํด์ค", default="") | |
| question_type: str = Field(description="๋ฌธ์ ์ ํ (mcq, yes_no, sequence, code, scenario)") | |
| code: Optional[str] = Field(description="์ฝ๋๊ฐ ์๋ค๋ฉด ํฌํจ", default="") | |
| sequence: Optional[List[str]] = Field(description="์์ํ์ผ ๊ฒฝ์ฐ ํญ๋ชฉ", default_factory=list) | |
| parser = PydanticOutputParser(pydantic_object=QuestionParsedResult) | |
| QUESTION_PARSE_PROMPT = ChatPromptTemplate.from_template( | |
| """ | |
| ๋๋ OCR๋ก ์ธ์๋ ์ํ ๋ฌธ์ ๋ฅผ ๋ถ์ํ์ฌ ๊ตฌ์กฐํํ๋ ์ ๋ฌธ ํ์์ด๋ค. | |
| ๋ฌธ์ ์ ํ์ ๋ค์ ์ค ํ๋์ผ ์ ์๋ค: | |
| - mcq: ๊ฐ๊ด์ | |
| - yes_no: ์ฐธ/๊ฑฐ์ง ๋๋ ์/์๋์ค | |
| - sequence: ์์ํ ๋ฌธ์ | |
| - code: ์ฝ๋ ๊ธฐ๋ฐ ๋ฌธ์ | |
| - scenario: ์๋๋ฆฌ์ค ๊ธฐ๋ฐ ๋ฌธ์ | |
| ์ด ๋ฌธ์ ๋ ์ฃผ๋ก '{question_type}' ์ ํ์ผ๋ก ์ถ์ ๋๋ค. | |
| {format_instructions} | |
| ๋ฌธ์ ์๋ฌธ: | |
| {ocr_text} | |
| """ | |
| ) | |
| def detect_question_type(text: str) -> str: | |
| lowered = text.lower() | |
| if any(k in lowered for k in ["์", "์๋์ค", "yes", "no", "true", "false"]): | |
| return "yes_no" | |
| if any(k in lowered for k in ["์์", "์ ๋ ฌ", "drag", "drop", "์์๋๋ก"]): | |
| return "sequence" | |
| if any(k in lowered for k in ["json", "{", "}", "az ", "set-az", "cli", "powershell", "bash", "cmd", "์ฝ๋"]): | |
| return "code" | |
| if any(k in lowered for k in ["์๋๋ฆฌ์ค", "case", "contoso", "litware", "fabrikam", "์กฐ๊ฑด", "์ํฉ"]): | |
| return "scenario" | |
| return "mcq" | |
| def build_question_parser_chain(llm): | |
| # LCEL: Prompt -> LLM -> Pydantic Parser | |
| return QUESTION_PARSE_PROMPT | llm | parser | |
| def parse_question_with_chain(llm, page: int, text: str) -> dict: | |
| question_type = detect_question_type(text) | |
| chain = build_question_parser_chain(llm) | |
| try: | |
| started_at = time.time() | |
| # parser.get_format_instructions()๋ฅผ ํตํด ํ๋กฌํํธ์ JSON ์คํค๋ง๋ฅผ ์ฃผ์ ํฉ๋๋ค. | |
| parsed_result = chain.invoke( | |
| { | |
| "question_type": question_type, | |
| "ocr_text": text[:1800], | |
| "format_instructions": parser.get_format_instructions(), | |
| } | |
| ) | |
| # Pydantic ๋ชจ๋ธ์ dict๋ก ๋ณํ (Pydantic v1/v2 ํธํ์ฑ์ ์ํด dict() ์ฌ์ฉ) | |
| parsed = parsed_result.dict() | |
| parsed["page"] = page | |
| print(f"[LLM] p{page} ({question_type}) parsed in {time.time() - started_at:.1f}s") | |
| return parsed | |
| except Exception as exc: | |
| print(f"[WARN] LLM parse failed (p{page}): {exc}") | |
| return { | |
| "page": page, | |
| "stem": text[:400], | |
| "options": [], | |
| "answer": [], | |
| "explanation": f"์ค๋ฅ: {exc}", | |
| "question_type": question_type, | |
| "code": "", | |
| "sequence": [], | |
| } | |