ginipick commited on
Commit
b2e08df
·
verified ·
1 Parent(s): d2a7d2b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -284
app.py CHANGED
@@ -7,9 +7,49 @@ import json
7
  import io
8
  import traceback
9
  import csv
10
- # HuggingFace 클라이언트 대신 OpenAI 클라이언트 사용
11
  from openai import OpenAI
12
- import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # 추론 API 클라이언트 설정
15
  hf_client = InferenceClient(
@@ -34,88 +74,24 @@ def load_parquet(filename: str) -> str:
34
  except Exception as e:
35
  return f"파일을 읽는 중 오류가 발생했습니다: {str(e)}"
36
 
37
-
38
- # OpenAI 클라이언트 설정
39
- client = OpenAI(api_key=os.getenv("OPEN_AI"))
40
-
41
- # respond 함수 수정
42
- def respond(message: str, history: List[Dict[str, str]], system_message: str = "", max_tokens: int = 4000, temperature: float = 0.5, top_p: float = 0.9, parquet_data: str = None) -> str:
43
- # 시스템 프롬프트 설정
44
- system_prefix = """반드시 한글로 답변할 것. 너는 업로드된 데이터를 기반으로 질문에 답변하는 역할을 한다.
45
-
46
- 주요 지침:
47
- 1. 질문과 직접 관련된 내용만 간단명료하게 답변할 것
48
- 2. 이전 답변과 중복되는 내용은 제외할 것
49
- 3. 불필요한 예시나 부연 설명은 하지 말 것
50
- 4. 동일한 내용을 다른 표현으로 반복하지 말 것
51
- 5. 핵심 정보만 전달할 것
52
- """
53
-
54
- if parquet_data:
55
- try:
56
- df = pd.read_json(io.StringIO(parquet_data))
57
- data_summary = df.describe(include='all').to_string()
58
- system_prefix += f"\n\n데이터 요약:\n{data_summary}"
59
- except Exception as e:
60
- print(f"데이터 로드 오류: {str(e)}")
61
-
62
- # 대화 히스토리 구성
63
- messages = [{"role": "system", "content": system_prefix}]
64
-
65
- # 최근 대화 컨텍스트만 유지
66
- recent_history = history[-3:] if history else []
67
- for chat in recent_history:
68
- messages.append({"role": chat["role"], "content": chat["content"]})
69
-
70
- messages.append({"role": "user", "content": message})
71
-
72
- try:
73
- # OpenAI API 호출
74
- response = client.chat.completions.create(
75
- model="gpt-4o-mini", # GPT-4-mini 모델 사용
76
- messages=messages,
77
- max_tokens=max_tokens,
78
- temperature=temperature,
79
- top_p=top_p,
80
- stream=True
81
- )
82
-
83
- full_response = ""
84
- for chunk in response:
85
- if chunk.choices[0].delta.content:
86
- full_response += chunk.choices[0].delta.content
87
- # 응답 정제
88
- cleaned_response = clean_response(full_response)
89
- yield cleaned_response
90
-
91
- except Exception as e:
92
- error_message = f"추론 오류: {str(e)}"
93
- print(error_message)
94
- yield error_message
95
-
96
  def clean_response(text: str) -> str:
97
  """응답 텍스트 정제 함수"""
98
- # 문장 단위로 분리
99
  sentences = [s.strip() for s in text.split('.') if s.strip()]
100
-
101
- # 중복 제거
102
  unique_sentences = []
103
  seen = set()
104
 
105
  for sentence in sentences:
106
- # 문장 정규화 (공백 제거, 소문자 변환)
107
  normalized = ' '.join(sentence.lower().split())
108
  if normalized not in seen:
109
  seen.add(normalized)
110
  unique_sentences.append(sentence)
111
 
112
- # 정제된 문장 결합
113
  cleaned_text = '. '.join(unique_sentences)
114
  if cleaned_text and not cleaned_text.endswith('.'):
115
  cleaned_text += '.'
116
 
117
  return cleaned_text
118
-
119
  def remove_duplicates(text: str) -> str:
120
  """중복 문장 제거 함수"""
121
  sentences = text.split('.')
@@ -132,20 +108,17 @@ def remove_duplicates(text: str) -> str:
132
 
133
  def upload_csv(file_path: str) -> Tuple[str, str]:
134
  try:
135
- # CSV 파일 읽기
136
  df = pd.read_csv(file_path, sep=',')
137
- # 필수 컬럼 확인
138
  required_columns = {'id', 'text', 'label', 'metadata'}
139
  available_columns = set(df.columns)
140
  missing_columns = required_columns - available_columns
141
  if missing_columns:
142
  return f"CSV 파일에 다음 필수 컬럼이 누락되었습니다: {', '.join(missing_columns)}", ""
143
- # 데이터 클렌징
144
  df.drop_duplicates(inplace=True)
145
  df.fillna('', inplace=True)
146
- # 데이터 유형 최적화
147
  df = df.astype({'id': 'int32', 'text': 'string', 'label': 'category', 'metadata': 'string'})
148
- # Parquet 파일로 변환
149
  parquet_filename = os.path.splitext(os.path.basename(file_path))[0] + '.parquet'
150
  df.to_parquet(parquet_filename, engine='pyarrow', compression='snappy')
151
  return f"{parquet_filename} 파일이 성공적으로 업로드되고 변환되었습니다.", parquet_filename
@@ -154,10 +127,8 @@ def upload_csv(file_path: str) -> Tuple[str, str]:
154
 
155
  def upload_parquet(file_path: str) -> Tuple[str, str, str]:
156
  try:
157
- # Parquet 파일 읽기
158
  df = pd.read_parquet(file_path, engine='pyarrow')
159
 
160
- # 데이터 기본 정보 수집
161
  data_info = {
162
  "총 레코드 수": len(df),
163
  "컬럼 목록": list(df.columns),
@@ -165,143 +136,53 @@ def upload_parquet(file_path: str) -> Tuple[str, str, str]:
165
  "결측치 정보": df.isnull().sum().to_dict()
166
  }
167
 
168
- # 데이터 요약 정보 생성
169
  summary = []
170
  summary.append(f"### 데이터셋 기본 정보:")
171
  summary.append(f"- 총 레코드 수: {data_info['총 레코드 수']}")
172
  summary.append(f"- 컬럼 목록: {', '.join(data_info['컬럼 목록'])}")
173
 
174
- # 각 컬럼별 통계 정보 생성
175
  summary.append("\n### 컬럼별 정보:")
176
  for col in df.columns:
177
  if df[col].dtype in ['int64', 'float64']:
178
- # 수치형 데이터
179
  stats = df[col].describe()
180
  summary.append(f"\n{col} (수치형):")
181
  summary.append(f"- 평균: {stats['mean']:.2f}")
182
  summary.append(f"- 최소: {stats['min']}")
183
  summary.append(f"- 최대: {stats['max']}")
184
  elif df[col].dtype == 'object' or df[col].dtype == 'string':
185
- # 문자열 데이터
186
  unique_count = df[col].nunique()
187
  summary.append(f"\n{col} (텍스트):")
188
  summary.append(f"- 고유값 수: {unique_count}")
189
- if unique_count < 10: # 고유값이 적은 경우만 표시
190
  value_counts = df[col].value_counts().head(5)
191
  summary.append("- 상위 5개 값:")
192
  for val, count in value_counts.items():
193
  summary.append(f" • {val}: {count}개")
194
 
195
- # 미리보기 생성
196
  preview = df.head(10).to_markdown(index=False)
197
  summary.append("\n### 데이터 미리보기:")
198
  summary.append(preview)
199
 
200
  parquet_content = "\n".join(summary)
201
-
202
- # DataFrame을 JSON 문자열로 변환 (Q&A에서 사용)
203
  parquet_json = df.to_json(orient='records', force_ascii=False)
204
 
205
  return "Parquet 파일이 성공적으로 업로드되었습니다.", parquet_content, parquet_json
206
  except Exception as e:
207
  return f"Parquet 파일 업로드 중 오류가 발생했습니다: {str(e)}", "", ""
208
 
209
-
210
- def respond(message: str, history: List[Dict[str, str]], system_message: str = "", max_tokens: int = 4000, temperature: float = 0.5, top_p: float = 0.9, parquet_data: str = None) -> str:
211
- try:
212
- if parquet_data:
213
- # JSON 문자열을 DataFrame으로 변환
214
- df = pd.read_json(io.StringIO(parquet_data))
215
-
216
- # 데이터셋 컨텍스트 생성
217
- columns_info = []
218
- for col in df.columns:
219
- if df[col].dtype in ['int64', 'float64']:
220
- col_type = "수치형"
221
- stats = df[col].describe()
222
- col_info = f"- {col} ({col_type}): 평균={stats['mean']:.2f}, 최소={stats['min']}, 최대={stats['max']}"
223
- else:
224
- col_type = "텍스트"
225
- unique_count = df[col].nunique()
226
- col_info = f"- {col} ({col_type}): 고유값 {unique_count}개"
227
- columns_info.append(col_info)
228
-
229
- data_context = f"""
230
- 현재 업로드된 데이터셋 정보:
231
- - 총 {len(df)} 개의 레코드
232
- - 컬럼 정보:
233
- {chr(10).join(columns_info)}
234
-
235
- 샘플 데이터:
236
- {df.head(20).to_string()}
237
- """
238
- system_prompt = f"""당신은 업로드된 데이터셋을 분석하고 질문에 답변하는 AI 어시스턴트입니다.
239
-
240
- 주요 지침:
241
- 1. 반드시 한글로 답변할 것
242
- 2. 데이터셋의 실제 내용을 기반으로 정확하게 답변할 것
243
- 3. 데이터에 없는 내용은 추측하지 말 것
244
- 4. 답변은 간단명료하게 할 것
245
- 5. 데이터 프라이버시를 고려하여 답변할 것
246
-
247
- 데이터셋 구조 설명:
248
- {chr(10).join(columns_info)}
249
-
250
- 참고할 데이터 샘플:
251
- {data_context}
252
- """
253
- else:
254
- system_prompt = system_message or "너는 AI 조언자 역할이다."
255
-
256
- # OpenAI API 호출
257
- messages = [{"role": "system", "content": system_prompt}]
258
-
259
- # 최근 대화 기록 추가
260
- recent_history = history[-3:] if history else []
261
- for chat in recent_history:
262
- messages.append({"role": chat["role"], "content": chat["content"]})
263
-
264
- messages.append({"role": "user", "content": message})
265
-
266
- response = client.chat.completions.create(
267
- model="gpt-4-0125-preview",
268
- messages=messages,
269
- max_tokens=max_tokens,
270
- temperature=temperature,
271
- top_p=top_p,
272
- stream=True
273
- )
274
-
275
- full_response = ""
276
- for chunk in response:
277
- if chunk.choices[0].delta.content:
278
- full_response += chunk.choices[0].delta.content
279
- yield clean_response(full_response)
280
-
281
- except Exception as e:
282
- error_message = f"응답 생성 중 오류 발생: {str(e)}"
283
- print(f"{error_message}\n{traceback.format_exc()}")
284
- yield error_message
285
-
286
  def text_to_parquet(text: str) -> Tuple[str, str, str]:
287
  try:
288
- # 입력 텍스트를 줄 단위로 분리
289
  lines = [line.strip() for line in text.split('\n') if line.strip()]
290
-
291
- # 데이터를 저장할 리스트
292
  data = []
293
 
294
  for line in lines:
295
  try:
296
- # 정규식을 사용하여 CSV 형식 파싱
297
  import re
298
  pattern = r'(\d+),([^,]+),([^,]+),(.+)'
299
  match = re.match(pattern, line)
300
 
301
  if match:
302
  id_val, text_val, label_val, metadata_val = match.groups()
303
-
304
- # 쌍따옴표 제거 및 정제
305
  text_val = text_val.strip().strip('"')
306
  label_val = label_val.strip().strip('"')
307
  metadata_val = metadata_val.strip().strip('"')
@@ -319,10 +200,7 @@ def text_to_parquet(text: str) -> Tuple[str, str, str]:
319
  if not data:
320
  return "변환할 데이터가 없습니다.", "", ""
321
 
322
- # DataFrame 생성
323
  df = pd.DataFrame(data)
324
-
325
- # 데이터 타입 설정
326
  df = df.astype({
327
  'id': 'int32',
328
  'text': 'string',
@@ -330,11 +208,8 @@ def text_to_parquet(text: str) -> Tuple[str, str, str]:
330
  'metadata': 'string'
331
  })
332
 
333
- # Parquet 파일로 변환
334
  parquet_filename = 'text_to_parquet.parquet'
335
  df.to_parquet(parquet_filename, engine='pyarrow', compression='snappy')
336
-
337
- # 미리보기 생성
338
  preview = df.to_markdown(index=False)
339
 
340
  return (
@@ -348,34 +223,46 @@ def text_to_parquet(text: str) -> Tuple[str, str, str]:
348
  print(f"{error_message}\n{traceback.format_exc()}")
349
  return error_message, "", ""
350
 
351
- # preprocess_text_with_llm 함수도 수정
352
- def preprocess_text_with_llm(input_text: str) -> str:
353
- if not input_text.strip():
354
- return "입력 텍스트가 비어있습니다."
355
-
356
- system_prompt = """반드시 한글(한국어)로 답변하시오. 당신은 데이터 전처리 전문가입니다. 입력된 텍스트를 CSV 데이터셋 형식으로 변환하세요.
357
 
358
- 규칙:
359
- 1. 출력 형식: id,text,label,metadata
360
- 2. id: 1부터 시작하는 순차적 번호
361
- 3. text: 의미 있는 단위로 분리된 텍스트
362
- 4. label: 텍스트의 주제나 카테고리를 아래 기준으로 정확하게 한 개만 선택
363
- - Historical_Figure (역사적 인물)
364
- - Military_History (군사 역사)
365
- - Technology (기술)
366
- - Politics (정치)
367
- - Culture (문화)
368
- 5. metadata: 날짜, 출처 등 추가 정보"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  try:
371
  response = client.chat.completions.create(
372
  model="gpt-4-0125-preview",
373
- messages=[
374
- {"role": "system", "content": system_prompt},
375
- {"role": "user", "content": input_text}
376
- ],
377
- max_tokens=4000,
378
- temperature=0.1,
379
  stream=True
380
  )
381
 
@@ -383,26 +270,19 @@ def preprocess_text_with_llm(input_text: str) -> str:
383
  for chunk in response:
384
  if chunk.choices[0].delta.content:
385
  full_response += chunk.choices[0].delta.content
 
386
 
387
- # 응답 정제
388
- processed_text = clean_response(full_response)
389
-
390
- # CSV 형식 검증
391
- try:
392
- from io import StringIO
393
- import csv
394
- csv.reader(StringIO(processed_text))
395
- return processed_text
396
- except csv.Error:
397
- return "LLM이 올바른 CSV 형식을 생성하지 못했습니다. 다시 시도해주세요."
398
-
399
  except Exception as e:
400
- error_message = f"전처리오류가 발생했습니다: {str(e)}"
401
- print(error_message)
402
- return error_message# preprocess_text_with_llm 함수도 수정
403
- def preprocess_text_with_llm(input_text: str) -> str:
404
- if not input_text.strip():
405
- return "입력 텍스트가 비어있습니다."
 
 
 
 
406
 
407
  system_prompt = """반드시 한글(한국어)로 답변하시오. 당신은 데이터 전처리 전문가입니다. 입력된 텍스트를 CSV 데이터셋 형식으로 변환하세요.
408
 
@@ -420,7 +300,7 @@ def preprocess_text_with_llm(input_text: str) -> str:
420
 
421
  try:
422
  response = client.chat.completions.create(
423
- model="gpt-4o-mini",
424
  messages=[
425
  {"role": "system", "content": system_prompt},
426
  {"role": "user", "content": input_text}
@@ -435,10 +315,8 @@ def preprocess_text_with_llm(input_text: str) -> str:
435
  if chunk.choices[0].delta.content:
436
  full_response += chunk.choices[0].delta.content
437
 
438
- # 응답 정제
439
  processed_text = clean_response(full_response)
440
 
441
- # CSV 형식 검증
442
  try:
443
  from io import StringIO
444
  import csv
@@ -452,46 +330,50 @@ def preprocess_text_with_llm(input_text: str) -> str:
452
  print(error_message)
453
  return error_message
454
 
455
- # CSS 설정
456
- css = """
457
- footer {
458
- visibility: hidden;
459
- }
460
- #chatbot-container, #chatbot-data-upload {
461
- height: 700px;
462
- overflow-y: scroll;
463
- }
464
- #chatbot-container .message, #chatbot-data-upload .message {
465
- font-size: 14px;
466
- }
467
- /* 입력창 배경색 및 글자색 변경 */
468
- textarea, input[type="text"] {
469
- background-color: #ffffff; /* 흰색 배경 */
470
- color: #000000; /* 검정색 글자 */
471
- }
472
- /* 파일 업로드 영역 높이 조절 */
473
- #parquet-upload-area {
474
- max-height: 150px;
475
- overflow-y: auto;
476
- }
477
- /* 초기 설명 글씨 크기 조절 */
478
- #initial-description {
479
- font-size: 14px;
480
- }
481
- """
482
 
483
  # Gradio Blocks 인터페이스 설정
484
  with gr.Blocks(css=css) as demo:
 
 
485
  gr.Markdown("# MyEzRAG: LLM이 나만의 데이터로 학습한 콘텐츠 생성/답변", elem_id="initial-description")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  gr.Markdown(
487
  "### '사용 방법' 탭을 통해 자세한 이용 방법을 참고하세요.\n"
488
  "### Tip) '예제'를 통해 다양한 활용 방법을 체험하고 응용해 보세요, 데이터셋 업로드시 미리보기는 10건만 출력",
489
  elem_id="initial-description"
490
  )
491
 
492
-
493
-
494
- # 첫 번째 탭: 챗봇 데이터 업로드 (탭 이름 변경: "My 데이터셋+LLM")
495
  with gr.Tab("My 데이터셋+LLM"):
496
  gr.Markdown("### LLM과 대화하기")
497
  chatbot_data_upload = gr.Chatbot(label="챗봇", type="messages", elem_id="chatbot-data-upload")
@@ -506,10 +388,14 @@ with gr.Blocks(css=css) as demo:
506
 
507
  parquet_data_state = gr.State()
508
 
509
- def handle_message_data_upload(message: str, history: List[Dict[str, str]], system_message: str, max_tokens: int, temperature: float, top_p: float, parquet_data: str):
 
 
 
 
 
 
510
  history = history or []
511
-
512
- # 중복 질문 검사
513
  recent_questions = [chat['content'].strip().lower() for chat in history[-3:] if chat['role'] == 'user']
514
  if message.strip().lower() in recent_questions:
515
  yield history + [{"role": "assistant", "content": "동일한 질문이 최근에 있었습니다. 다른 질문을 해주세요."}], ""
@@ -522,9 +408,10 @@ with gr.Blocks(css=css) as demo:
522
  history,
523
  system_message,
524
  max_tokens,
525
- temperature=0.3, # 낮은 temperature 사용
526
  top_p=top_p,
527
- parquet_data=parquet_data
 
528
  )
529
 
530
  partial_response = ""
@@ -539,9 +426,6 @@ with gr.Blocks(css=css) as demo:
539
  history.append({"role": "assistant", "content": response})
540
  yield history, ""
541
 
542
-
543
-
544
-
545
  send_data_upload.click(
546
  handle_message_data_upload,
547
  inputs=[
@@ -551,13 +435,14 @@ with gr.Blocks(css=css) as demo:
551
  max_tokens,
552
  temperature,
553
  top_p,
554
- parquet_data_state, # parquet_data_state를 사용하여 업로드된 데이터를 전달
 
555
  ],
556
  outputs=[chatbot_data_upload, msg_data_upload],
557
  queue=True
558
  )
559
 
560
- # 예제 추가
561
  with gr.Accordion("예제", open=False):
562
  gr.Examples(
563
  examples=[
@@ -572,7 +457,7 @@ with gr.Blocks(css=css) as demo:
572
  label="예제 선택",
573
  )
574
 
575
- # Parquet 파일 업로드를 화면 하단으로 이동
576
  gr.Markdown("### Parquet 파일 업로드")
577
  with gr.Row():
578
  with gr.Column():
@@ -596,7 +481,7 @@ with gr.Blocks(css=css) as demo:
596
  outputs=[parquet_upload_status, parquet_preview_chat, parquet_data_state]
597
  )
598
 
599
- # 두 번째 탭: 데이터 변환 (탭 이름 변경: "CSV to My 데이터셋")
600
  with gr.Tab("CSV to My 데이터셋"):
601
  gr.Markdown("### CSV 파일 업로드 및 Parquet 변환")
602
  with gr.Row():
@@ -621,7 +506,7 @@ with gr.Blocks(css=css) as demo:
621
  outputs=[upload_status, parquet_preview, download_button]
622
  )
623
 
624
- # 세 번째 탭: 텍스트 to csv to parquet 변환 (탭 이름 변경: "Text to My 데이터셋")
625
  with gr.Tab("Text to My 데이터셋"):
626
  gr.Markdown("### 텍스트를 입력하면 CSV로 변환 후 Parquet으로 자동 전환됩니다.")
627
  with gr.Row():
@@ -649,7 +534,7 @@ with gr.Blocks(css=css) as demo:
649
  outputs=[convert_status, parquet_preview_convert, download_parquet_convert]
650
  )
651
 
652
- # 네번째 탭의 UI 부분 수정
653
  with gr.Tab("Text Preprocessing with LLM"):
654
  gr.Markdown("### 텍스트를 입력하면 LLM이 데이터셋 형식에 맞게 전처리하여 출력합니다.")
655
  with gr.Row():
@@ -676,33 +561,29 @@ with gr.Blocks(css=css) as demo:
676
  interactive=False
677
  )
678
 
679
- # Parquet 변환 및 다운로드 섹션
680
  convert_to_parquet_button = gr.Button("Parquet으로 변환")
681
  download_parquet = gr.File(label="변환된 Parquet 파일 다운로드")
682
 
683
-
684
-
685
-
686
- def handle_text_preprocessing(input_text: str):
 
687
  if not input_text.strip():
688
- return "입력 텍스트가 없습니다.", ""
 
689
 
690
  try:
691
- preprocess_status_msg = "전처리를 시작합니다..."
692
- yield preprocess_status_msg, ""
693
-
694
- processed_text = preprocess_text_with_llm(input_text)
695
 
696
  if processed_text:
697
- preprocess_status_msg = "전처리가 완료되었습니다."
698
- yield preprocess_status_msg, processed_text
699
  else:
700
- preprocess_status_msg = "전처리 결과가 없습니다."
701
- yield preprocess_status_msg, ""
702
 
703
  except Exception as e:
704
- error_msg = f"처리 중 오류가 발생했습니다: {str(e)}"
705
- yield error_msg, ""
706
 
707
  def clear_inputs():
708
  return "", "대기 중...", ""
@@ -719,10 +600,9 @@ with gr.Blocks(css=css) as demo:
719
  except Exception as e:
720
  return f"Parquet 변환 중 오류 발생: {str(e)}", None
721
 
722
- # 이벤트 핸들러 연결
723
  preprocess_button.click(
724
  handle_text_preprocessing,
725
- inputs=[raw_text_input],
726
  outputs=[preprocess_status, processed_text_output],
727
  queue=True
728
  )
@@ -738,7 +618,6 @@ with gr.Blocks(css=css) as demo:
738
  outputs=[preprocess_status, download_parquet]
739
  )
740
 
741
- # 예제 텍스트 추가
742
  with gr.Accordion("예제 텍스트", open=False):
743
  gr.Examples(
744
  examples=[
@@ -749,12 +628,17 @@ with gr.Blocks(css=css) as demo:
749
  label="예제 선택"
750
  )
751
 
 
752
  with gr.Tab("📚 사용 방법"):
753
  gr.Markdown("""
754
  # MyEzRAG 사용 가이드
755
 
 
 
 
 
 
756
  ## 1️⃣ My 데이터셋+LLM 탭
757
- ![Tab1](https://your-image-url.com/tab1.png)
758
  ### 기능
759
  - 업로드된 Parquet 데이터셋을 기반으로 LLM과 대화
760
  - 데이터셋의 내용을 활용한 콘텐츠 생성
@@ -771,7 +655,6 @@ with gr.Blocks(css=css) as demo:
771
  ---
772
 
773
  ## 2️⃣ CSV to My 데이터셋 탭
774
- ![Tab2](https://your-image-url.com/tab2.png)
775
  ### 기능
776
  - CSV 파일을 Parquet 형식으로 변환
777
  - 데이터 최적화 및 정제
@@ -788,7 +671,6 @@ with gr.Blocks(css=css) as demo:
788
  ---
789
 
790
  ## 3️⃣ Text to My 데이터셋 탭
791
- ![Tab3](https://your-image-url.com/tab3.png)
792
  ### 기능
793
  - 텍스트 형식의 데이터를 Parquet으로 변환
794
  - 수동 데이터 입력 지원
@@ -811,7 +693,6 @@ with gr.Blocks(css=css) as demo:
811
  ---
812
 
813
  ## 4️⃣ Text Preprocessing with LLM 탭
814
- ![Tab4](https://your-image-url.com/tab4.png)
815
  ### 기능
816
  - LLM을 활용한 자동 텍스트 전처리
817
  - 구조화된 데이터셋 생성
@@ -828,26 +709,28 @@ with gr.Blocks(css=css) as demo:
828
  - 데이터 정규화
829
 
830
  ## 💡 일반적인 팁
 
831
  - 각 탭의 예제를 참고하여 사용법 ��히기
832
  - 데이터 품질이 좋을수록 더 나은 결과 제공
833
  - 오류 발생 시 입력 데이터 형식 확인
834
  - 대용량 처리 시 적절한 청크 크기로 분할 처리
835
 
836
  ## ⚠️ 주의사항
 
837
  - 민감한 개인정보 포함하지 않기
838
  - 데이터 백업 권장
839
  - 네트워크 상태 확인
840
  - 브라우저 캐시 주기적 정리
841
 
842
  ## 🔍 문제 해결
 
843
  - 오류 발생 시 입력 데이터 형식 확인
844
  - 파일 업로드 실패 시 파일 크기 및 형식 확인
845
  - 변환 실패 시 데이터 인코딩 확인
846
  - 응답이 느릴 경우 데이터 크기 조정
847
  """)
848
 
849
-
850
  gr.Markdown("### [email protected]", elem_id="initial-description")
851
 
852
  if __name__ == "__main__":
853
- demo.launch(share=True)
 
7
  import io
8
  import traceback
9
  import csv
 
10
  from openai import OpenAI
11
+ from functools import lru_cache
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ import math
14
+
15
+ # CSS 설정
16
+ css = """
17
+ footer {
18
+ visibility: hidden;
19
+ }
20
+ #chatbot-container, #chatbot-data-upload {
21
+ height: 700px;
22
+ overflow-y: scroll;
23
+ }
24
+ #chatbot-container .message, #chatbot-data-upload .message {
25
+ font-size: 14px;
26
+ }
27
+ /* 입력창 배경색 및 글자색 변경 */
28
+ textarea, input[type="text"] {
29
+ background-color: #ffffff;
30
+ color: #000000;
31
+ }
32
+ /* 파일 업로드 영역 높이 조절 */
33
+ #parquet-upload-area {
34
+ max-height: 150px;
35
+ overflow-y: auto;
36
+ }
37
+ /* 초기 설명 글씨 크기 조절 */
38
+ #initial-description {
39
+ font-size: 14px;
40
+ }
41
+ /* API Key 입력 섹션 스타일 */
42
+ .api-key-section {
43
+ margin: 10px 0;
44
+ padding: 10px;
45
+ border: 1px solid #ddd;
46
+ border-radius: 5px;
47
+ }
48
+ .api-key-status {
49
+ margin-top: 5px;
50
+ font-weight: bold;
51
+ }
52
+ """
53
 
54
  # 추론 API 클라이언트 설정
55
  hf_client = InferenceClient(
 
74
  except Exception as e:
75
  return f"파일을 읽는 중 오류가 발생했습니다: {str(e)}"
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def clean_response(text: str) -> str:
78
  """응답 텍스트 정제 함수"""
 
79
  sentences = [s.strip() for s in text.split('.') if s.strip()]
 
 
80
  unique_sentences = []
81
  seen = set()
82
 
83
  for sentence in sentences:
 
84
  normalized = ' '.join(sentence.lower().split())
85
  if normalized not in seen:
86
  seen.add(normalized)
87
  unique_sentences.append(sentence)
88
 
 
89
  cleaned_text = '. '.join(unique_sentences)
90
  if cleaned_text and not cleaned_text.endswith('.'):
91
  cleaned_text += '.'
92
 
93
  return cleaned_text
94
+
95
  def remove_duplicates(text: str) -> str:
96
  """중복 문장 제거 함수"""
97
  sentences = text.split('.')
 
108
 
109
  def upload_csv(file_path: str) -> Tuple[str, str]:
110
  try:
 
111
  df = pd.read_csv(file_path, sep=',')
 
112
  required_columns = {'id', 'text', 'label', 'metadata'}
113
  available_columns = set(df.columns)
114
  missing_columns = required_columns - available_columns
115
  if missing_columns:
116
  return f"CSV 파일에 다음 필수 컬럼이 누락되었습니다: {', '.join(missing_columns)}", ""
117
+
118
  df.drop_duplicates(inplace=True)
119
  df.fillna('', inplace=True)
 
120
  df = df.astype({'id': 'int32', 'text': 'string', 'label': 'category', 'metadata': 'string'})
121
+
122
  parquet_filename = os.path.splitext(os.path.basename(file_path))[0] + '.parquet'
123
  df.to_parquet(parquet_filename, engine='pyarrow', compression='snappy')
124
  return f"{parquet_filename} 파일이 성공적으로 업로드되고 변환되었습니다.", parquet_filename
 
127
 
128
  def upload_parquet(file_path: str) -> Tuple[str, str, str]:
129
  try:
 
130
  df = pd.read_parquet(file_path, engine='pyarrow')
131
 
 
132
  data_info = {
133
  "총 레코드 수": len(df),
134
  "컬럼 목록": list(df.columns),
 
136
  "결측치 정보": df.isnull().sum().to_dict()
137
  }
138
 
 
139
  summary = []
140
  summary.append(f"### 데이터셋 기본 정보:")
141
  summary.append(f"- 총 레코드 수: {data_info['총 레코드 수']}")
142
  summary.append(f"- 컬럼 목록: {', '.join(data_info['컬럼 목록'])}")
143
 
 
144
  summary.append("\n### 컬럼별 정보:")
145
  for col in df.columns:
146
  if df[col].dtype in ['int64', 'float64']:
 
147
  stats = df[col].describe()
148
  summary.append(f"\n{col} (수치형):")
149
  summary.append(f"- 평균: {stats['mean']:.2f}")
150
  summary.append(f"- 최소: {stats['min']}")
151
  summary.append(f"- 최대: {stats['max']}")
152
  elif df[col].dtype == 'object' or df[col].dtype == 'string':
 
153
  unique_count = df[col].nunique()
154
  summary.append(f"\n{col} (텍스트):")
155
  summary.append(f"- 고유값 수: {unique_count}")
156
+ if unique_count < 10:
157
  value_counts = df[col].value_counts().head(5)
158
  summary.append("- 상위 5개 값:")
159
  for val, count in value_counts.items():
160
  summary.append(f" • {val}: {count}개")
161
 
 
162
  preview = df.head(10).to_markdown(index=False)
163
  summary.append("\n### 데이터 미리보기:")
164
  summary.append(preview)
165
 
166
  parquet_content = "\n".join(summary)
 
 
167
  parquet_json = df.to_json(orient='records', force_ascii=False)
168
 
169
  return "Parquet 파일이 성공적으로 업로드되었습니다.", parquet_content, parquet_json
170
  except Exception as e:
171
  return f"Parquet 파일 업로드 중 오류가 발생했습니다: {str(e)}", "", ""
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  def text_to_parquet(text: str) -> Tuple[str, str, str]:
174
  try:
 
175
  lines = [line.strip() for line in text.split('\n') if line.strip()]
 
 
176
  data = []
177
 
178
  for line in lines:
179
  try:
 
180
  import re
181
  pattern = r'(\d+),([^,]+),([^,]+),(.+)'
182
  match = re.match(pattern, line)
183
 
184
  if match:
185
  id_val, text_val, label_val, metadata_val = match.groups()
 
 
186
  text_val = text_val.strip().strip('"')
187
  label_val = label_val.strip().strip('"')
188
  metadata_val = metadata_val.strip().strip('"')
 
200
  if not data:
201
  return "변환할 데이터가 없습니다.", "", ""
202
 
 
203
  df = pd.DataFrame(data)
 
 
204
  df = df.astype({
205
  'id': 'int32',
206
  'text': 'string',
 
208
  'metadata': 'string'
209
  })
210
 
 
211
  parquet_filename = 'text_to_parquet.parquet'
212
  df.to_parquet(parquet_filename, engine='pyarrow', compression='snappy')
 
 
213
  preview = df.to_markdown(index=False)
214
 
215
  return (
 
223
  print(f"{error_message}\n{traceback.format_exc()}")
224
  return error_message, "", ""
225
 
226
+ def respond(message: str, history: List[Dict[str, str]], system_message: str = "", max_tokens: int = 4000, temperature: float = 0.5, top_p: float = 0.9, parquet_data: str = None, api_key: str = None) -> str:
227
+ if not api_key:
228
+ yield "⚠️ API Key가 설정되지 않았습니다. 서비스 이용을 위해 API Key를 입력해주세요."
229
+ return
 
 
230
 
231
+ # OpenAI 클라이언트 초기화
232
+ client = OpenAI(api_key=api_key)
233
+
234
+ system_prefix = """반드시 한글로 답변할 것. 너는 업로드된 데이터를 기반으로 질문에 답변하는 역할을 한다.
235
+
236
+ 주요 지침:
237
+ 1. 질문과 직접 관련된 내용만 간단명료하게 답변할 것
238
+ 2. 이전 답변과 중복되는 내용은 제외할 것
239
+ 3. 불필요한 예시나 부연 설명은 하지 말 것
240
+ 4. 동일한 내용을 다른 표현으로 반복하지 말 것
241
+ 5. 핵심 정보만 전달할
242
+ """
243
+
244
+ if parquet_data:
245
+ try:
246
+ df = pd.read_json(io.StringIO(parquet_data))
247
+ data_summary = df.describe(include='all').to_string()
248
+ system_prefix += f"\n\n데이터 요약:\n{data_summary}"
249
+ except Exception as e:
250
+ print(f"데이터 로드 오류: {str(e)}")
251
+
252
+ messages = [{"role": "system", "content": system_prefix}]
253
+ recent_history = history[-3:] if history else []
254
+ for chat in recent_history:
255
+ messages.append({"role": chat["role"], "content": chat["content"]})
256
+
257
+ messages.append({"role": "user", "content": message})
258
 
259
  try:
260
  response = client.chat.completions.create(
261
  model="gpt-4-0125-preview",
262
+ messages=messages,
263
+ max_tokens=max_tokens,
264
+ temperature=temperature,
265
+ top_p=top_p,
 
 
266
  stream=True
267
  )
268
 
 
270
  for chunk in response:
271
  if chunk.choices[0].delta.content:
272
  full_response += chunk.choices[0].delta.content
273
+ yield clean_response(full_response)
274
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  except Exception as e:
276
+ error_message = f"응답 생성 오류 발생: {str(e)}"
277
+ print(f"{error_message}\n{traceback.format_exc()}")
278
+ yield error_message
279
+
280
+ def preprocess_text_with_llm(input_text: str, api_key: str = None) -> str:
281
+ if not api_key:
282
+ return "⚠️ API Key가 설정되지 않았습니다. 서비스 이용을 위해 API Key를 입력해주세요."
283
+
284
+ # OpenAI 클라이언트 초기화
285
+ client = OpenAI(api_key=api_key)
286
 
287
  system_prompt = """반드시 한글(한국어)로 답변하시오. 당신은 데이터 전처리 전문가입니다. 입력된 텍스트를 CSV 데이터셋 형식으로 변환하세요.
288
 
 
300
 
301
  try:
302
  response = client.chat.completions.create(
303
+ model="gpt-4-0125-preview",
304
  messages=[
305
  {"role": "system", "content": system_prompt},
306
  {"role": "user", "content": input_text}
 
315
  if chunk.choices[0].delta.content:
316
  full_response += chunk.choices[0].delta.content
317
 
 
318
  processed_text = clean_response(full_response)
319
 
 
320
  try:
321
  from io import StringIO
322
  import csv
 
330
  print(error_message)
331
  return error_message
332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  # Gradio Blocks 인터페이스 설정
335
  with gr.Blocks(css=css) as demo:
336
+ api_key_state = gr.State("") # API 키를 저장할 State 추가
337
+
338
  gr.Markdown("# MyEzRAG: LLM이 나만의 데이터로 학습한 콘텐츠 생성/답변", elem_id="initial-description")
339
+
340
+ # API 키 입력 섹션 추가
341
+ with gr.Row(elem_classes="api-key-section"):
342
+ with gr.Column(scale=3):
343
+ api_key_input = gr.Textbox(
344
+ label="OpenAI API Key",
345
+ placeholder="sk-...",
346
+ type="password",
347
+ show_label=True
348
+ )
349
+ with gr.Column(scale=1):
350
+ api_key_button = gr.Button("API Key 설정", variant="primary")
351
+
352
+ # API 키 상태 표시
353
+ api_key_status = gr.Markdown("⚠️ API Key가 설정되지 않았습니다. 서비스 이용을 위해 API Key를 입력해주세요.", elem_classes="api-key-status")
354
+
355
+ # API 키 설정 함수
356
+ def set_api_key(api_key: str):
357
+ if not api_key.strip():
358
+ return "⚠️ API Key가 설정되지 않았습니다. 서비스 이용을 위해 API Key를 입력해주세요.", ""
359
+ if not api_key.startswith("sk-"):
360
+ return "❌ 올바르지 않은 API Key 형���입니다. 다시 확인해주세요.", ""
361
+ return "✅ API Key가 성공적으로 설정되었습니다.", api_key
362
+
363
+ # API 키 설정 이벤트 연결
364
+ api_key_button.click(
365
+ set_api_key,
366
+ inputs=[api_key_input],
367
+ outputs=[api_key_status, api_key_state]
368
+ )
369
+
370
  gr.Markdown(
371
  "### '사용 방법' 탭을 통해 자세한 이용 방법을 참고하세요.\n"
372
  "### Tip) '예제'를 통해 다양한 활용 방법을 체험하고 응용해 보세요, 데이터셋 업로드시 미리보기는 10건만 출력",
373
  elem_id="initial-description"
374
  )
375
 
376
+ # 첫 번째 탭: My 데이터셋+LLM
 
 
377
  with gr.Tab("My 데이터셋+LLM"):
378
  gr.Markdown("### LLM과 대화하기")
379
  chatbot_data_upload = gr.Chatbot(label="챗봇", type="messages", elem_id="chatbot-data-upload")
 
388
 
389
  parquet_data_state = gr.State()
390
 
391
+ def handle_message_data_upload(message: str, history: List[Dict[str, str]], system_message: str, max_tokens: int, temperature: float, top_p: float, parquet_data: str, api_key: str):
392
+ if not api_key:
393
+ history = history or []
394
+ history.append({"role": "assistant", "content": "⚠️ API Key가 설정되지 않았습니다. 서비스 이용을 위해 API Key를 입력해주세요."})
395
+ yield history, ""
396
+ return
397
+
398
  history = history or []
 
 
399
  recent_questions = [chat['content'].strip().lower() for chat in history[-3:] if chat['role'] == 'user']
400
  if message.strip().lower() in recent_questions:
401
  yield history + [{"role": "assistant", "content": "동일한 질문이 최근에 있었습니다. 다른 질문을 해주세요."}], ""
 
408
  history,
409
  system_message,
410
  max_tokens,
411
+ temperature=0.3,
412
  top_p=top_p,
413
+ parquet_data=parquet_data,
414
+ api_key=api_key
415
  )
416
 
417
  partial_response = ""
 
426
  history.append({"role": "assistant", "content": response})
427
  yield history, ""
428
 
 
 
 
429
  send_data_upload.click(
430
  handle_message_data_upload,
431
  inputs=[
 
435
  max_tokens,
436
  temperature,
437
  top_p,
438
+ parquet_data_state,
439
+ api_key_state,
440
  ],
441
  outputs=[chatbot_data_upload, msg_data_upload],
442
  queue=True
443
  )
444
 
445
+ # 예제 추가
446
  with gr.Accordion("예제", open=False):
447
  gr.Examples(
448
  examples=[
 
457
  label="예제 선택",
458
  )
459
 
460
+ # Parquet 파일 업로드
461
  gr.Markdown("### Parquet 파일 업로드")
462
  with gr.Row():
463
  with gr.Column():
 
481
  outputs=[parquet_upload_status, parquet_preview_chat, parquet_data_state]
482
  )
483
 
484
+ # 두 번째 탭: CSV to My 데이터셋
485
  with gr.Tab("CSV to My 데이터셋"):
486
  gr.Markdown("### CSV 파일 업로드 및 Parquet 변환")
487
  with gr.Row():
 
506
  outputs=[upload_status, parquet_preview, download_button]
507
  )
508
 
509
+ # 세 번째 탭: Text to My 데이터셋
510
  with gr.Tab("Text to My 데이터셋"):
511
  gr.Markdown("### 텍스트를 입력하면 CSV로 변환 후 Parquet으로 자동 전환됩니다.")
512
  with gr.Row():
 
534
  outputs=[convert_status, parquet_preview_convert, download_parquet_convert]
535
  )
536
 
537
+ # 번째 탭: Text Preprocessing with LLM
538
  with gr.Tab("Text Preprocessing with LLM"):
539
  gr.Markdown("### 텍스트를 입력하면 LLM이 데이터셋 형식에 맞게 전처리하여 출력합니다.")
540
  with gr.Row():
 
561
  interactive=False
562
  )
563
 
 
564
  convert_to_parquet_button = gr.Button("Parquet으로 변환")
565
  download_parquet = gr.File(label="변환된 Parquet 파일 다운로드")
566
 
567
+ def handle_text_preprocessing(input_text: str, api_key: str):
568
+ if not api_key:
569
+ yield "⚠️ API Key가 설정되지 않았습니다.", ""
570
+ return
571
+
572
  if not input_text.strip():
573
+ yield "입력 텍스트가 없습니다.", ""
574
+ return
575
 
576
  try:
577
+ yield "전처리를 시작합니다...", ""
578
+ processed_text = preprocess_text_with_llm(input_text, api_key)
 
 
579
 
580
  if processed_text:
581
+ yield "전처리가 완료되었습니다.", processed_text
 
582
  else:
583
+ yield "전처리 결과가 없습니다.", ""
 
584
 
585
  except Exception as e:
586
+ yield f"처리 중 오류가 발생했습니다: {str(e)}", ""
 
587
 
588
  def clear_inputs():
589
  return "", "대기 중...", ""
 
600
  except Exception as e:
601
  return f"Parquet 변환 중 오류 발생: {str(e)}", None
602
 
 
603
  preprocess_button.click(
604
  handle_text_preprocessing,
605
+ inputs=[raw_text_input, api_key_state],
606
  outputs=[preprocess_status, processed_text_output],
607
  queue=True
608
  )
 
618
  outputs=[preprocess_status, download_parquet]
619
  )
620
 
 
621
  with gr.Accordion("예제 텍스트", open=False):
622
  gr.Examples(
623
  examples=[
 
628
  label="예제 선택"
629
  )
630
 
631
+ # 사용 방법 탭
632
  with gr.Tab("📚 사용 방법"):
633
  gr.Markdown("""
634
  # MyEzRAG 사용 가이드
635
 
636
+ ## 🔑 API Key 설정
637
+ 1. OpenAI API Key를 상단 입력창에 입력
638
+ 2. 'API Key 설정' 버튼 클릭
639
+ 3. 설정 성공 메시지 확인
640
+
641
  ## 1️⃣ My 데이터셋+LLM 탭
 
642
  ### 기능
643
  - 업로드된 Parquet 데이터셋을 기반으로 LLM과 대화
644
  - 데이터셋의 내용을 활용한 콘텐츠 생성
 
655
  ---
656
 
657
  ## 2️⃣ CSV to My 데이터셋 탭
 
658
  ### 기능
659
  - CSV 파일을 Parquet 형식으로 변환
660
  - 데이터 최적화 및 정제
 
671
  ---
672
 
673
  ## 3️⃣ Text to My 데이터셋 탭
 
674
  ### 기능
675
  - 텍스트 형식의 데이터를 Parquet으로 변환
676
  - 수동 데이터 입력 지원
 
693
  ---
694
 
695
  ## 4️⃣ Text Preprocessing with LLM 탭
 
696
  ### 기능
697
  - LLM을 활용한 자동 텍스트 전처리
698
  - 구조화된 데이터셋 생성
 
709
  - 데이터 정규화
710
 
711
  ## 💡 일반적인 팁
712
+ - API Key는 안전하게 보관하고 주기적으로 갱신
713
  - 각 탭의 예제를 참고하여 사용법 ��히기
714
  - 데이터 품질이 좋을수록 더 나은 결과 제공
715
  - 오류 발생 시 입력 데이터 형식 확인
716
  - 대용량 처리 시 적절한 청크 크기로 분할 처리
717
 
718
  ## ⚠️ 주의사항
719
+ - API Key를 타인과 공유하지 않기
720
  - 민감한 개인정보 포함하지 않기
721
  - 데이터 백업 권장
722
  - 네트워크 상태 확인
723
  - 브라우저 캐시 주기적 정리
724
 
725
  ## 🔍 문제 해결
726
+ - API Key 오류: 키 형식 및 유효성 확인
727
  - 오류 발생 시 입력 데이터 형식 확인
728
  - 파일 업로드 실패 시 파일 크기 및 형식 확인
729
  - 변환 실패 시 데이터 인코딩 확인
730
  - 응답이 느릴 경우 데이터 크기 조정
731
  """)
732
 
 
733
  gr.Markdown("### [email protected]", elem_id="initial-description")
734
 
735
  if __name__ == "__main__":
736
+ demo.launch(share=True)