# 필요한 라이브러리 불러오기 import gradio as gr # Gradio: 웹 인터페이스 구성을 위한 라이브러리 import requests # requests: HTTP 요청을 보내기 위한 라이브러리 from openai import OpenAI # OpenAI: Upstage Solar API와 호환되는 클라이언트 # ------------------------------ # 🔍 문서 파싱 함수 정의 # ------------------------------ def parse_document(file, api_key): """ 업로드된 PDF 문서를 HTML로 변환하는 함수 (Upstage Document Parse API 사용) """ url = "https://api.upstage.ai/v1/document-ai/document-parse" # API 요청 URL headers = {'Authorization': f'Bearer {api_key}'} # 인증 헤더 설정 files = {"document": open(file.name, "rb")} # 파일 읽기 data = { "base64_encoding": "['table']", # 테이블 데이터는 base64로 인코딩 "model": "document-parse" # 사용 모델 명시 } response = requests.post(url, headers=headers, files=files, data=data) # POST 요청 result = response.json() # 응답 결과 파싱 html_text = result.get("content", {}).get("html", "") # HTML 추출 return html_text # ------------------------------ # 💬 문서 기반 Q&A 함수 정의 # ------------------------------ def chat_with_document(history, html_text, user_question, api_key): """ 문서 내용을 기반으로 사용자 질문에 답변하는 Solar LLM 함수 """ if not html_text.strip(): return history, history, "⚠️ 먼저 문서를 변환해주세요." # 문서가 없는 경우 안내 # OpenAI 클라이언트 초기화 (Upstage Solar LLM) client = OpenAI( api_key=api_key, base_url="https://api.upstage.ai/v1" ) # 이전 대화 기록 초기화 history = history or [] # 시스템 프롬프트: HTML 문서 내용을 기반으로 답변 요청 system_prompt = f"""The following is a financial statement document extracted in HTML format. Please answer user questions accurately and concisely in Korean, based on the text within HTML tags. Document: {html_text} """ # 메시지 구성 (시스템 → 사용자/봇 대화 → 현재 질문) messages = [{"role": "system", "content": system_prompt}] for user, bot in history: messages.append({"role": "user", "content": user}) messages.append({"role": "assistant", "content": bot}) messages.append({"role": "user", "content": user_question}) # Solar LLM 호출 try: response = client.chat.completions.create( model="solar-pro", # 사용할 모델 이름 messages=messages, # 전체 메시지 전달 temperature=0, # 창의성 최소화 max_tokens=1024 # 최대 응답 길이 ) bot_reply = response.choices[0].message.content # 응답 메시지 추출 except Exception as e: bot_reply = f"⚠️ 오류가 발생했습니다: {str(e)}" # 에러 처리 # 대화 이력 업데이트 후 반환 history.append((user_question, bot_reply)) return history, history, "" # ------------------------------ # 🔁 HTML 보기 토글 함수 # ------------------------------ def toggle_html_view(current_html, is_visible): """ HTML 보기/숨기기 상태를 토글하는 함수 """ return ( gr.update(value=current_html, visible=not is_visible), # 텍스트박스 숨기기/보이기 gr.update(value=current_html, visible=is_visible), # HTML 렌더링 반대 동작 not is_visible # 상태 반전 ) # ------------------------------ # 📦 Gradio UI 구성 # ------------------------------ with gr.Blocks() as demo: # 제목 및 설명 표시 gr.Markdown("# 📄 재무제표 분석 챗봇") gr.Markdown("1. Document Parse API로 PDF 문서를 HTML로 변환합니다.\n" "2. Solar LLM을 통해 문서 기반 질문에 답변합니다.") # 🔑 API Key 입력창 (사용자가 직접 입력) api_key_input = gr.Textbox(label="🔑 Upstage API Key", type="password", placeholder="Paste your API key here") # 📎 파일 업로드 + 문서 변환 버튼 with gr.Row(): file_input = gr.File(label="📎 재무제표 업로드") parse_btn = gr.Button("문서 HTML 변환") # 📘 HTML 출력 영역 (텍스트 + HTML 토글 뷰) html_output = gr.Textbox(label="📘 문서 내용", lines=10, visible=True, elem_id="scrollable-html") html_display = gr.HTML(visible=False, elem_id="scrollable-html-display") toggle_html_btn = gr.Button("🔁 HTML 보기 전환") html_visible_state = gr.State(False) # 보기 상태 저장 # 문서 변환 버튼 클릭 시 → HTML 생성 parse_btn.click( fn=parse_document, inputs=[file_input, api_key_input], outputs=html_output ) # HTML 보기 전환 버튼 클릭 시 → 토글 동작 실행 toggle_html_btn.click( fn=toggle_html_view, inputs=[html_output, html_visible_state], outputs=[html_output, html_display, html_visible_state] ) # 💬 챗봇 인터페이스 chatbot = gr.Chatbot(label="💬 문서 기반 Q&A", height=400) user_question = gr.Textbox(label="❓ 질문을 입력하세요", lines=2) answer_btn = gr.Button("답변 생성") chat_state = gr.State([]) # 대화 상태 저장 # 💡 예제 질문 버튼 구성 with gr.Row(): gr.Markdown("💡 예제 질문:") ex1 = gr.Button("어떤 기업의 재무제표인가요?") ex2 = gr.Button("3분기 총 순매출은 얼마인가요?") # 예제 질문 버튼 클릭 시 → 질문 + 응답 실행 for btn, question in [(ex1, "어떤 기업의 재무제표인가요?"), (ex2, "1분기 총 순매출은 얼마인가요?")]: btn.click( fn=lambda q=question: q, # 질문 텍스트 전달 inputs=[], outputs=user_question ).then( fn=chat_with_document, inputs=[chat_state, html_output, user_question, api_key_input], outputs=[chatbot, chat_state, user_question], show_progress=True ) # 사용자 질문 제출 → Solar LLM 답변 answer_btn.click( fn=chat_with_document, inputs=[chat_state, html_output, user_question, api_key_input], outputs=[chatbot, chat_state, user_question], show_progress=True ) # ------------------------------ # 🎨 스크롤 가능한 HTML 박스 스타일 지정 # ------------------------------ demo.css = """ #scrollable-html, #scrollable-html-display { max-height: 400px; overflow: auto; border: 1px solid #ccc; padding: 10px; } """ # 🚀 앱 실행 if __name__ == "__main__": demo.launch()