fudii0921 commited on
Commit
a05159f
·
verified ·
1 Parent(s): 065d648

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +164 -0
app.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import cohere
4
+ import gradio as gr
5
+ from dotenv import load_dotenv
6
+ from concurrent.futures import ThreadPoolExecutor
7
+
8
+ # Docling: PDF構造解析
9
+ from docling.document_converter import DocumentConverter
10
+ # LangChain: チャンキング
11
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
12
+
13
+ # 環境変数の読み込み
14
+ load_dotenv()
15
+ COHERE_API_KEY = os.environ.get("COHERE_API_KEY")
16
+ co = cohere.ClientV2(api_key=COHERE_API_KEY)
17
+
18
+ # Docling Converterの初期化
19
+ converter = DocumentConverter()
20
+
21
+ def cleanse_text_with_cohere(raw_text):
22
+ """Cohereを使用してテキストをクレンジング(並列実行用)"""
23
+ system_message = """
24
+ あなたは高度なドキュメントエディターです。
25
+ 提供されたMarkdownテキストを、以下のルールに従ってクレンジングしてください:
26
+ 1. ページ番号、不自然なリピートヘッダー、システムログを削除。
27
+ 2. 文の途中で切れている不自然な改行を結合し、自然な文章にする。
28
+ 3. 表(|---|---|)の形式が崩れている場合は、正しいMarkdownテーブル形式に修正。
29
+ 4. 階層構造が不明瞭な場合は、適切な見出し(#、##、###)を付与。
30
+ 5. 出力は純粋なMarkdownテキストのみとし、説明は不要です。
31
+ """
32
+ try:
33
+ response = co.chat(
34
+ model="command-r-plus-08-2024",
35
+ messages=[
36
+ {"role": "system", "content": system_message},
37
+ {"role": "user", "content": f"以下のテキストをクレンジングしてください:\n\n{raw_text}"}
38
+ ]
39
+ )
40
+ return response.message.content[0].text
41
+ except Exception as e:
42
+ return f"Cleansing Error: {e}\nRaw Text: {raw_text}"
43
+
44
+ def process_pdf_for_rag(files, apply_cleansing):
45
+ """
46
+ PDFを解析し、並列クレンジングを実行して結果をストリーミングで返す
47
+ """
48
+ if not files:
49
+ yield "ファイルがアップロードされていません。", None, "エラー: ファイルがありません"
50
+ return
51
+
52
+ all_markdown_content = ""
53
+ excel_tables = []
54
+ accumulated_text = ""
55
+
56
+ # 1. Doclingによる解析工程
57
+ for idx, file_info in enumerate(files):
58
+ pdf_path = file_info.name
59
+ filename = os.path.basename(pdf_path)
60
+
61
+ status = f"【工程 1/4】解析中 ({idx+1}/{len(files)}): {filename}..."
62
+ yield accumulated_text, None, status
63
+
64
+ result = converter.convert(pdf_path)
65
+ markdown_text = result.document.export_to_markdown()
66
+ all_markdown_content += f"\n\n{markdown_text}"
67
+
68
+ for i, table in enumerate(result.document.tables):
69
+ try:
70
+ df = table.export_to_dataframe()
71
+ if not df.empty:
72
+ excel_tables.append({
73
+ "sheet_name": f"Tab_{i}_{filename[:15]}",
74
+ "df": df,
75
+ "filename": filename
76
+ })
77
+ except Exception as e:
78
+ print(f"Table export error: {e}")
79
+
80
+ # 2. チャンキング工程 (チャンクサイズ 2000)
81
+ yield accumulated_text, None, "【工程 2/4】セマンティック・チャンキング(2000文字)実行中..."
82
+ text_splitter = RecursiveCharacterTextSplitter(
83
+ chunk_size=2000,
84
+ chunk_overlap=200,
85
+ separators=["\n# ", "\n## ", "\n### ", "\n\n", "\n", " "]
86
+ )
87
+ raw_chunks = text_splitter.split_text(all_markdown_content)
88
+ total_chunks = len(raw_chunks)
89
+
90
+ # 3. 並列クレンジング工程
91
+ if apply_cleansing:
92
+ status = f"【工程 3/4】並列AIクレンジング実行中 (全 {total_chunks} チャンク)..."
93
+ yield accumulated_text, None, status
94
+
95
+ # 最大5スレッドで並列処理(APIのレート制限に応じて調整可)
96
+ with ThreadPoolExecutor(max_workers=5) as executor:
97
+ cleansed_results = list(executor.map(cleanse_text_with_cohere, raw_chunks))
98
+
99
+ for idx, processed_chunk in enumerate(cleansed_results):
100
+ accumulated_text += f"### CLEANSED CHUNK {idx+1} ###\n{processed_chunk}\n\n"
101
+ yield accumulated_text, None, f"クレンジング結果を表示中 ({idx+1}/{total_chunks})..."
102
+ else:
103
+ for idx, chunk in enumerate(raw_chunks):
104
+ accumulated_text += f"### RAW CHUNK {idx+1} ###\n{chunk}\n\n"
105
+ yield accumulated_text, None, f"RAWチャンクを表示中 ({idx+1}/{total_chunks})..."
106
+
107
+ # 4. Excel内容をテキストに結合 & ファイル生成
108
+ yield accumulated_text, None, "【工程 4/4】表データをテキストへ結合中..."
109
+
110
+ if excel_tables:
111
+ accumulated_text += "\n\n" + "="*50 + "\n"
112
+ accumulated_text += "📊 抽出された表データプレビュー (Excel出力内容)\n"
113
+ accumulated_text += "="*50 + "\n\n"
114
+
115
+ excel_path = "extracted_financial_data.xlsx"
116
+ with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
117
+ for item in excel_tables:
118
+ df = item["df"]
119
+ s_name = item["sheet_name"][:30].replace("[", "").replace("]", "")
120
+ df.to_excel(writer, sheet_name=s_name, index=False)
121
+
122
+ accumulated_text += f"📄 表: {item['sheet_name']} (from {item['filename']})\n"
123
+ accumulated_text += df.to_markdown(index=False) + "\n\n"
124
+ yield accumulated_text, None, status
125
+ else:
126
+ excel_path = None
127
+
128
+ yield accumulated_text, excel_path, f"処理完了: {total_chunks}チャンクを並列処理しました。"
129
+
130
+ # --- Gradio UI ---
131
+ with gr.Blocks(title="STRUCTURA ONE", theme=gr.themes.Ocean()) as demo:
132
+ gr.HTML("""
133
+ <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 20px; background-color: lightsteelblue; border-radius: 10px;">
134
+ <img src="https://www.ryhintl.com/images/structura_logo.png" alt="Structura ONE" width="100" height="100" />
135
+ <h1 style="color: #1a365d; margin-top: 10px; margin-bottom: 0;">🚀 Unleash your Documents</h1>
136
+ <p style="color: #4a5568; margin-top: 5px;">Parallelized RAG Parsing Engine | Docling × Cohere</p>
137
+ </div>
138
+ """)
139
+
140
+ with gr.Row():
141
+ with gr.Column(scale=1):
142
+ file_input = gr.File(label="PDFをアップロード", file_count="multiple", file_types=[".pdf"])
143
+ cleansing_switch = gr.Checkbox(label="高速並列AIクレンジングを適用", value=True)
144
+ run_btn = gr.Button("解析開始", variant="primary")
145
+ status_out = gr.Textbox(label="現在のステータス", interactive=False)
146
+ excel_out = gr.File(label="Excelダウンロード")
147
+
148
+ with gr.Column(scale=2):
149
+ text_out = gr.Textbox(
150
+ label="構造化Markdown & テーブルプレビュー",
151
+ lines=30,
152
+ max_lines=50
153
+ )
154
+
155
+ # 処理中のボタン無効化はGradioのデフォルト動作で制御
156
+ run_btn.click(
157
+ fn=process_pdf_for_rag,
158
+ inputs=[file_input, cleansing_switch],
159
+ outputs=[text_out, excel_out, status_out],
160
+ queue=True
161
+ )
162
+
163
+ if __name__ == "__main__":
164
+ demo.queue().launch()