seawolf2357 commited on
Commit
ea7bf73
Β·
verified Β·
1 Parent(s): e0af97d

Update routes.py

Browse files
Files changed (1) hide show
  1. routes.py +130 -1
routes.py CHANGED
@@ -3,6 +3,9 @@ from flask import Blueprint, render_template, request, jsonify, send_from_direct
3
  from pathlib import Path
4
  import shutil # For zipping the cache directory
5
  import json # For parsing streamed JSON data
 
 
 
6
 
7
  import os
8
  import config
@@ -16,6 +19,12 @@ logger = logging.getLogger(__name__)
16
 
17
  main_bp = Blueprint('main', __name__)
18
 
 
 
 
 
 
 
19
  # LLM client is initialized in app.py create_app()
20
 
21
  # --- Serve the cache directory as a zip file ---
@@ -229,4 +238,124 @@ def explain_sentence():
229
  logger.error(f"LLM API(REST) 호좜 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
230
  user_error_message = ("μ„€λͺ… 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. μ„œλΉ„μŠ€κ°€ μΌμ‹œμ μœΌλ‘œ μ‚¬μš©ν•  수 μ—†μœΌλ©° "
231
  "ν˜„μž¬ μ‹œμž‘ 쀑일 수 μžˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.")
232
- return jsonify({"error": user_error_message}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  from pathlib import Path
4
  import shutil # For zipping the cache directory
5
  import json # For parsing streamed JSON data
6
+ from werkzeug.utils import secure_filename
7
+ import uuid
8
+ import tempfile
9
 
10
  import os
11
  import config
 
19
 
20
  main_bp = Blueprint('main', __name__)
21
 
22
+ # μ—…λ‘œλ“œ ν—ˆμš© ν™•μž₯자
23
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'dcm', 'dicom'}
24
+
25
+ def allowed_file(filename):
26
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
27
+
28
  # LLM client is initialized in app.py create_app()
29
 
30
  # --- Serve the cache directory as a zip file ---
 
238
  logger.error(f"LLM API(REST) 호좜 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
239
  user_error_message = ("μ„€λͺ… 생성에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€. μ„œλΉ„μŠ€κ°€ μΌμ‹œμ μœΌλ‘œ μ‚¬μš©ν•  수 μ—†μœΌλ©° "
240
  "ν˜„μž¬ μ‹œμž‘ 쀑일 수 μžˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.")
241
+ return jsonify({"error": user_error_message}), 500
242
+
243
+ @main_bp.route('/upload_and_analyze', methods=['POST'])
244
+ def upload_and_analyze():
245
+ """이미지λ₯Ό μ—…λ‘œλ“œν•˜κ³  λΆ„μ„ν•©λ‹ˆλ‹€."""
246
+ if not llm_is_initialized():
247
+ logger.error("LLM ν΄λΌμ΄μ–ΈνŠΈ(REST API)κ°€ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μš”μ²­μ„ μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
248
+ return jsonify({"error": "LLM ν΄λΌμ΄μ–ΈνŠΈκ°€ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. API 킀와 베이슀 URL을 ν™•μΈν•˜μ„Έμš”."}), 500
249
+
250
+ # 파일 확인
251
+ if 'image' not in request.files:
252
+ return jsonify({"error": "이미지 파일이 μš”μ²­μ— ν¬ν•¨λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."}), 400
253
+
254
+ file = request.files['image']
255
+ if file.filename == '':
256
+ return jsonify({"error": "파일이 μ„ νƒλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."}), 400
257
+
258
+ if not allowed_file(file.filename):
259
+ return jsonify({"error": "ν—ˆμš©λ˜μ§€ μ•ŠλŠ” 파일 ν˜•μ‹μž…λ‹ˆλ‹€. PNG, JPG, JPEG, GIF, BMP, DICOM 파일만 μ—…λ‘œλ“œ κ°€λŠ₯ν•©λ‹ˆλ‹€."}), 400
260
+
261
+ # 이미지 νƒ€μž… κ°€μ Έμ˜€κΈ° (κΈ°λ³Έκ°’: CXR)
262
+ image_type = request.form.get('image_type', 'CXR')
263
+ image_type_korean = '흉뢀 X-레이' if image_type == 'CXR' else ('CT' if image_type == 'CT' else '의료 μ˜μƒ')
264
+
265
+ # μΆ”κ°€ μ»¨ν…μŠ€νŠΈ κ°€μ Έμ˜€κΈ° (선택사항)
266
+ additional_context = request.form.get('context', '')
267
+
268
+ try:
269
+ # μž„μ‹œ 파일둜 μ €μž₯
270
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp_file:
271
+ file.save(tmp_file.name)
272
+ temp_filepath = tmp_file.name
273
+
274
+ # Base64둜 인코딩
275
+ base64_image_data_url = utils.image_to_base64_data_url(temp_filepath)
276
+
277
+ # μž„μ‹œ 파일 μ‚­μ œ
278
+ os.unlink(temp_filepath)
279
+
280
+ if not base64_image_data_url:
281
+ logger.error("이미지λ₯Ό base64둜 μΈμ½”λ”©ν•˜λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.")
282
+ return jsonify({"error": "이미지 인코딩에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."}), 500
283
+
284
+ # μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ ꡬ성
285
+ system_prompt = (
286
+ "당신은 κ²½ν—˜μ΄ ν’λΆ€ν•œ 방사선과 μ „λ¬Έμ˜μž…λ‹ˆλ‹€. "
287
+ "λ°˜λ“œμ‹œ ν•œκ΅­μ–΄λ‘œ μ‘λ‹΅ν•˜μ„Έμš”. "
288
+ f"제곡된 {image_type_korean} μ˜μƒμ„ λΆ„μ„ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. "
289
+ "μ˜μƒμ—μ„œ κ΄€μ°°λ˜λŠ” μ£Όμš” μ†Œκ²¬μ„ μ„€λͺ…ν•˜κ³ , κ°€λŠ₯ν•œ 진단을 μ œμ‹œν•˜μ„Έμš”. "
290
+ "일반인이 이해할 수 μžˆλ„λ‘ μ‰¬μš΄ μš©μ–΄λ‘œ μ„€λͺ…ν•˜λ˜, μ˜ν•™μ  정확성을 μœ μ§€ν•˜μ„Έμš”. "
291
+ "λ‹€μŒ ꡬ쑰둜 μ‘λ‹΅ν•˜μ„Έμš”:\n"
292
+ "1. μ£Όμš” μ†Œκ²¬: μ˜μƒμ—μ„œ κ΄€μ°°λ˜λŠ” μ£Όμš” νŠΉμ§•λ“€\n"
293
+ "2. κ°€λŠ₯ν•œ 진단: κ΄€μ°°λœ μ†Œκ²¬μ„ λ°”νƒ•μœΌλ‘œ ν•œ κ°€λŠ₯ν•œ 진단듀\n"
294
+ "3. μΆ”κ°€ κΆŒκ³ μ‚¬ν•­: ν•„μš”ν•œ μΆ”κ°€ κ²€μ‚¬λ‚˜ μ‘°μΉ˜μ‚¬ν•­\n"
295
+ "λͺ¨λ“  μ„€λͺ…은 λ°˜λ“œμ‹œ ν•œκ΅­μ–΄λ‘œ μž‘μ„±ν•˜μ„Έμš”."
296
+ )
297
+
298
+ user_prompt_text = f"이 {image_type_korean} μ˜μƒμ„ λΆ„μ„ν•΄μ£Όμ„Έμš”."
299
+ if additional_context:
300
+ user_prompt_text += f"\nμΆ”κ°€ 정보: {additional_context}"
301
+
302
+ messages_for_api = [
303
+ {"role": "system", "content": system_prompt},
304
+ {
305
+ "role": "user",
306
+ "content": [
307
+ {"type": "text", "text": user_prompt_text},
308
+ {"type": "image_url", "image_url": {"url": base64_image_data_url}}
309
+ ]
310
+ }
311
+ ]
312
+
313
+ # μΊμ‹œ ν‚€ 생성 (파일λͺ…κ³Ό μ»¨ν…μŠ€νŠΈ 기반)
314
+ cache_key = f"analyze::{secure_filename(file.filename)}::{image_type}::{additional_context[:50]}"
315
+ cached_result = cache.get(cache_key)
316
+ if cached_result:
317
+ logger.info("μΊμ‹œλœ 뢄석 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.")
318
+ return jsonify({"analysis": cached_result})
319
+
320
+ logger.info("이미지와 ν•¨κ»˜ LLM API(REST)둜 뢄석 μš”μ²­μ„ 전솑 쀑...")
321
+ response = make_chat_completion_request(
322
+ model="tgi",
323
+ messages=messages_for_api,
324
+ top_p=None,
325
+ temperature=0.1,
326
+ max_tokens=500,
327
+ stream=True,
328
+ seed=None,
329
+ stop=None,
330
+ frequency_penalty=None,
331
+ presence_penalty=None
332
+ )
333
+ logger.info("LLM API(REST)λ‘œλΆ€ν„° 응닡 μŠ€νŠΈλ¦Όμ„ μˆ˜μ‹ ν–ˆμŠ΅λ‹ˆλ‹€.")
334
+
335
+ analysis_parts = []
336
+ for line in response.iter_lines():
337
+ if line:
338
+ decoded_line = line.decode('utf-8')
339
+ if decoded_line.startswith('data: '):
340
+ json_data_str = decoded_line[len('data: '):].strip()
341
+ if json_data_str == "[DONE]":
342
+ break
343
+ try:
344
+ chunk = json.loads(json_data_str)
345
+ if chunk.get("choices") and chunk["choices"][0].get("delta") and chunk["choices"][0]["delta"].get("content"):
346
+ analysis_parts.append(chunk["choices"][0]["delta"]["content"])
347
+ except json.JSONDecodeError:
348
+ logger.warning(f"슀트림 μ²­ν¬μ—μ„œ JSON을 λ””μ½”λ”©ν•  수 μ—†μŠ΅λ‹ˆλ‹€: {json_data_str}")
349
+ elif decoded_line.strip() == "[DONE]":
350
+ break
351
+
352
+ analysis = "".join(analysis_parts).strip()
353
+ if analysis:
354
+ cache.set(cache_key, analysis, expire=None)
355
+
356
+ logger.info("뢄석이 μ„±κ³΅μ μœΌλ‘œ μ™„μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€." if analysis else "APIλ‘œλΆ€ν„° 빈 뢄석 κ²°κ³Όλ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.")
357
+ return jsonify({"analysis": analysis or "APIλ‘œλΆ€ν„° 뢄석 κ²°κ³Όλ₯Ό λ°›μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."})
358
+
359
+ except Exception as e:
360
+ logger.error(f"이미지 μ—…λ‘œλ“œ 및 뢄석 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
361
+ return jsonify({"error": "이미지 뢄석 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”."}), 500