LangGraph ๊ธฐ๋ฐ์ Agentic ํ๋ก์ฐ๋ฅผ ์ด์ฉํด ํ๊ตญ์ด ํ ์ด๋ฏธ์ง๋ฅผ ํฉ์ฑ ๋ฐ์ดํฐ๋ก ๋ณํํฉ๋๋ค. ์ ๋ ฅ ์ด๋ฏธ์ง๋ฅผ HTML ํ๋ก ๋ณํํ ๋ค, ๋ด์ฉ์ ๋ถ์ํ์ฌ ๋์ผํ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ๋ผ์ด์ ์ค ํ๋ฆฌ ํฉ์ฑ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ JSON์ผ๋ก ํ์ฑํฉ๋๋ค.
ํ๋ก์ฐ๋ ๋ค์ 7๋จ๊ณ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
- Image2HTML โ ํ ์ด๋ฏธ์ง๋ฅผ HTML
<table>๊ตฌ์กฐ๋ก ๋ณต์ํฉ๋๋ค. - Validate PyMuPDF โ PyMuPDF๋ก ํ์ฑ๋ ๊ฒฐ๊ณผ๊ฐ ์ ํจํ์ง ๊ฒ์ฆํฉ๋๋ค.
- Analyze Table โ ์ถ์ถ๋ HTML ํ์ ๊ตฌ์กฐ์ ๋ฐ์ดํฐ ํจํด์ ๋ถ์ํ์ฌ ์์ฝํฉ๋๋ค.
- Generate Synthetic Dataset โ ๋ถ์๋ ์์ฝ์ ๋ฐํ์ผ๋ก ๋์ผํ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋ฉด์ ํฉ์ฑ ๋ฐ์ดํฐ๋ฅผ ์ฑ์ด HTML ํ๋ฅผ ์์ฑํฉ๋๋ค.
- Self-Reflection โ ์์ฑ๋ ํ๊ฐ ๋ผ์ด์ ์ค/๊ฐ์ธ์ ๋ณด ์ด์๊ฐ ์๋์ง ์ ๊ฒํ๊ณ , ํ์์ ์ฌ์์ฑ์ ์์ฒญํฉ๋๋ค.
- Parse Synthetic Table โ ์ต์ข ์์ฑ๋ ํฉ์ฑ HTML ํ๋ฅผ ๊ตฌ์กฐํ๋ JSON ํฌ๋งท์ผ๋ก ๋ณํํฉ๋๋ค.
- Generate QA โ ํฉ์ฑ๋ ํ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์ง๋ฌธ-๋ต๋ณ(QA) ์์ ์์ฑํฉ๋๋ค.
graph TD
START[Start] -->|Provider: OpenAI/Gemini| generate_synthetic_table_from_image[Generate Synthetic Table From Image]
START[Start] -->|Other Providers| pymupdf_parse[PyMuPDF Parse]
generate_synthetic_table_from_image --> self_reflection[Self Reflection]
pymupdf_parse --> validate_parsed_table[Validate PyMuPDF]
validate_parsed_table -->|Valid| analyze_table[Analyze Table]
validate_parsed_table -->|Invalid| image_to_html[Image to HTML]
image_to_html --> analyze_table
analyze_table --> generate_synthetic_table[Generate Synthetic Table]
generate_synthetic_table --> self_reflection
self_reflection -->|Passed| parse_synthetic_table[Parse Synthetic Table]
self_reflection -->|Failed| revise_synthetic_table[Revise Synthetic Table]
revise_synthetic_table --> self_reflection
parse_synthetic_table --> generate_qa[Generate QA]
generate_qa --> END[End]
์ฝ๋ ๋ฆฌ๋ทฐ ์ ์ฐธ๊ณ ํ ์ฃผ์ ํ์ผ๊ณผ ํต์ฌ ๋ก์ง์ ๋ํ ์ค๋ช ์ ๋๋ค.
ํต์ฌ ๋ก์ง์ธ LangGraph ํ๋ก์ฐ๊ฐ ์ ์๋ ํ์ผ์ ๋๋ค.
-
TableState(TypedDict):- ํ๋ก์ฐ ์ ์ฒด์์ ๊ณต์ ๋๋ ์ํ ๊ฐ์ฒด์ ๋๋ค.
image_path: ์ ๋ ฅ ์ด๋ฏธ์ง ๊ฒฝ๋กhtml_table: ์ด๋ฏธ์ง์์ ์ถ์ถ๋ ์๋ณธ HTMLtable_summary: ํ ๊ตฌ์กฐ ๋ฐ ๋ฐ์ดํฐ ํจํด ์์ฝsynthetic_table: ์์ฑ๋ ํฉ์ฑ ๋ฐ์ดํฐ HTMLsynthetic_json: ์ต์ข ํ์ฑ๋ JSON ๋ฐ์ดํฐreflection,passed,attempts: ์๊ธฐ ์ ๊ฒ ๋ฐ ์ฌ์๋ ๋ก์ง์ ์ํ ํ๋๋ค
-
build_synthetic_table_graph:- LangGraph์ ๋ ธ๋์ ์ฃ์ง๋ฅผ ์ฐ๊ฒฐํ์ฌ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํฉ๋๋ค.
image_to_html->parse_contents->generate_synthetic_table->self_reflection์์ผ๋ก ์งํ๋ฉ๋๋ค.self_reflection๊ฒฐ๊ณผ์ ๋ฐ๋ผrevise_synthetic_table๋ก ์ด๋ํ์ฌ ์ฌ์๋ํ๊ฑฐ๋, ์ฑ๊ณต ์parse_synthetic_table๋ก ์ด๋ํ์ฌ ์ข ๋ฃํฉ๋๋ค.
-
Nodes:
image_to_html_node: VLM์ ์ฌ์ฉํด ์ด๋ฏธ์ง๋ฅผ HTML๋ก ๋ณํํฉ๋๋ค.parse_synthetic_table_node: ํฉ์ฑ๋ HTML์ ์ต์ข ์ ์ผ๋ก JSON์ผ๋ก ํ์ฑํ์ฌ ํ์ฉํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
CLI ์คํ ๋ฐ ํ์ผ ์ ์ถ๋ ฅ์ ๋ด๋นํฉ๋๋ค.
run_with_args:argparse๋ก ๋ฐ์ ์ธ์๋ฅผ ์ฒ๋ฆฌํ๊ณ ํ๋ก์ฐ๋ฅผ ์คํํฉ๋๋ค.- ์คํ ๊ฒฐ๊ณผ(
html_table,synthetic_table,synthetic_json)๋ฅผ ๊ฐ๊ฐ ํ์ผ๋ก ์ ์ฅํ๋ ๋ก์ง์ด ํฌํจ๋์ด ์์ต๋๋ค.
LLM์๊ฒ ์ ๋ฌ๋๋ ์ง์์ฌํญ๋ค์ ๋๋ค. ์๋ฌธ์ผ๋ก ์์ฑ๋์ด ์ฑ๋ฅ์ ์ต์ ํํ์ต๋๋ค.
image_to_html.txt: ์ด๋ฏธ์ง์์ ํ ๊ตฌ์กฐ(rowspan, colspan ํฌํจ)๋ฅผ ์ ํํ ์ถ์ถํ๋๋ก ์ง์ํฉ๋๋ค.generate_synthetic_table.txt: ์๋ณธ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๋, ๋ด์ฉ์ ํฉ์ฑ ๋ฐ์ดํฐ๋ก ์์ ํ ๋์ฒดํ๋๋ก ์ง์ํฉ๋๋ค.self_reflection.txt: ์์ฑ๋ ๋ฐ์ดํฐ์ ํ์ง๊ณผ ๊ตฌ์กฐ์ ์ ํ์ฑ์ ๊ฒ์ฆํ๋ QA ํ๋กฌํํธ์ ๋๋ค.parse_synthetic_table.txt: HTML์ JSON์ผ๋ก ๋ณํํ๋ ๊ท์น์ ์ ์ํฉ๋๋ค.
ํ๋ก์ ํธ ๋ฃจํธ์ .env ํ์ผ์ ๋ง๋ค๊ณ OpenAI ํค๋ฅผ ์ค์ ํฉ๋๋ค.
echo "OPENAI_API_KEY=sk-..." > .env์์กด์ฑ์ pyproject.toml์ ํตํด ๊ด๋ฆฌ๋๋ฏ๋ก ์ํ๋ ํจํค์ง ๋งค๋์ ๋ก ์ค์นํฉ๋๋ค. uv๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ:
uv sync๋๋ ์ผ๋ฐ pip๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ:
pip install .๋ช ๋ นํ ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํด ํ๋ก์ฐ๋ฅผ ์คํํ ์ ์์ต๋๋ค.
# ๊ธฐ๋ณธ ์คํ (๊ฒฐ๊ณผ๋ฅผ result.json ๋ฐ ๊ด๋ จ ํ์ผ๋ก ์ ์ฅ)
python main.py I_table_78.png --save-json result.json
# ๋ชจ๋ธ ๋ฐ ์ต์
์ง์
python main.py I_table_78.png --model gpt-4o --temperature 0.1 --save-json output.jsonimage_path: (ํ์) ๋ณํํ ํ ์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก--model: ์ฌ์ฉํ OpenAI ๋ชจ๋ธ ์ด๋ฆ (๊ธฐ๋ณธ๊ฐ:gpt-4.1-mini)--temperature: ๋ชจ๋ธ ์จ๋ (๊ธฐ๋ณธ๊ฐ:0.2)--save-json: ์ต์ข ์ํ๋ฅผ JSON ํ์ผ๋ก ์ ์ฅํฉ๋๋ค. (ํ์๋ HTML ๋ฐ JSON ํ์ผ๋ค๋ ํจ๊ป ์ ์ฅ๋ฉ๋๋ค)
์คํ ๊ฒฐ๊ณผ์๋ HTML ํ, ๋ด์ฉ ์์ฝ, ํฉ์ฑ ํ, ์๊ธฐ ์ ๊ฒ ๊ฒฐ๊ณผ, ๊ทธ๋ฆฌ๊ณ ํ์ฑ๋ JSON ๋ฐ์ดํฐ๊ฐ ํฌํจ๋ฉ๋๋ค.