Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files
backend/media_analysis/figure_analysis.py
CHANGED
|
@@ -117,7 +117,7 @@ def prepare_media_evidence(
|
|
| 117 |
|
| 118 |
|
| 119 |
def analyze_media_question(question: str, selected_papers: list[dict], *, enabled: bool | None = None,
|
| 120 |
-
figures_lookup=None, asset_resolver=None, vision_client=None) -> dict:
|
| 121 |
"""Explicit opt-in entry point. Never silently substitutes a text summary answer."""
|
| 122 |
if enabled is None:
|
| 123 |
enabled = os.getenv("WATERSCOPE_ENABLE_MEDIA_ANALYSIS", "0").lower() in ("1", "true", "yes")
|
|
@@ -128,5 +128,5 @@ def analyze_media_question(question: str, selected_papers: list[dict], *, enable
|
|
| 128 |
if vision_client is None:
|
| 129 |
from .vision_client import analyze_images
|
| 130 |
vision_client = analyze_images
|
| 131 |
-
result = vision_client(question, items)
|
| 132 |
return {"status": "ok", "vision_inspected": True, **result}
|
|
|
|
| 117 |
|
| 118 |
|
| 119 |
def analyze_media_question(question: str, selected_papers: list[dict], *, enabled: bool | None = None,
|
| 120 |
+
figures_lookup=None, asset_resolver=None, vision_client=None, provider: str = "azure", model: str | None = None) -> dict:
|
| 121 |
"""Explicit opt-in entry point. Never silently substitutes a text summary answer."""
|
| 122 |
if enabled is None:
|
| 123 |
enabled = os.getenv("WATERSCOPE_ENABLE_MEDIA_ANALYSIS", "0").lower() in ("1", "true", "yes")
|
|
|
|
| 128 |
if vision_client is None:
|
| 129 |
from .vision_client import analyze_images
|
| 130 |
vision_client = analyze_images
|
| 131 |
+
result = vision_client(question, items, provider=provider, model=model)
|
| 132 |
return {"status": "ok", "vision_inspected": True, **result}
|
backend/media_analysis/test_routes.py
CHANGED
|
@@ -33,6 +33,14 @@ def media_analyze_test():
|
|
| 33 |
body = request.get_json(silent=True)
|
| 34 |
if not isinstance(body, dict):
|
| 35 |
return jsonify({"error": "JSON object required"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
question = body.get("question")
|
| 37 |
papers = body.get("selected_papers")
|
| 38 |
if not isinstance(question, str) or not question.strip() or len(question) > 4000:
|
|
@@ -51,7 +59,7 @@ def media_analyze_test():
|
|
| 51 |
return jsonify({"error": "Paper summary exceeds limit"}), 400
|
| 52 |
sanitized.append(clean)
|
| 53 |
try:
|
| 54 |
-
result = analyze_media_question(question, sanitized, enabled=True)
|
| 55 |
return jsonify(result)
|
| 56 |
except (MediaSelectionError, VisionConfigurationError, ValueError, FileNotFoundError) as exc:
|
| 57 |
return jsonify({"error": str(exc), "vision_inspected": False}), 400
|
|
|
|
| 33 |
body = request.get_json(silent=True)
|
| 34 |
if not isinstance(body, dict):
|
| 35 |
return jsonify({"error": "JSON object required"}), 400
|
| 36 |
+
provider = str(body.get("provider", "azure")).strip().lower()
|
| 37 |
+
if provider not in ("azure", "deepseek"):
|
| 38 |
+
return jsonify({"error": "provider must be azure or deepseek"}), 400
|
| 39 |
+
model = body.get("model")
|
| 40 |
+
if model is not None:
|
| 41 |
+
if not isinstance(model, str) or not (1 <= len(model.strip()) <= 100):
|
| 42 |
+
return jsonify({"error": "model must be a nonempty string of at most 100 characters"}), 400
|
| 43 |
+
model = model.strip()
|
| 44 |
question = body.get("question")
|
| 45 |
papers = body.get("selected_papers")
|
| 46 |
if not isinstance(question, str) or not question.strip() or len(question) > 4000:
|
|
|
|
| 59 |
return jsonify({"error": "Paper summary exceeds limit"}), 400
|
| 60 |
sanitized.append(clean)
|
| 61 |
try:
|
| 62 |
+
result = analyze_media_question(question, sanitized, enabled=True, provider=provider, model=model)
|
| 63 |
return jsonify(result)
|
| 64 |
except (MediaSelectionError, VisionConfigurationError, ValueError, FileNotFoundError) as exc:
|
| 65 |
return jsonify({"error": str(exc), "vision_inspected": False}), 400
|
backend/media_analysis/vision_client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Opt-in Azure
|
| 2 |
|
| 3 |
Dependencies: openai>=1.99 (a version with responses.create).
|
| 4 |
Environment: existing AZURE_OPENAI_ENDPOINT, AZURE_LLM_DEPLOYMENT,
|
|
@@ -64,6 +64,7 @@ def _image_part(path: Path, detail: str = "high", max_bytes: int = 15_000_000):
|
|
| 64 |
|
| 65 |
|
| 66 |
def analyze_images(question: str, evidence: list[dict], *, model: str | None = None,
|
|
|
|
| 67 |
detail: str = "high", max_output_tokens: int = 1000,
|
| 68 |
client=None) -> dict:
|
| 69 |
"""Analyze pre-resolved evidence records containing image_path/caption/paper identifiers.
|
|
@@ -79,12 +80,25 @@ def analyze_images(question: str, evidence: list[dict], *, model: str | None = N
|
|
| 79 |
raise ValueError("POC permits at most four images per request")
|
| 80 |
if detail not in ("auto", "low", "high"):
|
| 81 |
raise ValueError("detail must be auto, low or high")
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
if client is None:
|
| 84 |
from openai import OpenAI
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
instructions = (
|
| 90 |
"You analyze scientific publication figures and tables. Use only supplied images, "
|
|
@@ -122,7 +136,8 @@ def analyze_images(question: str, evidence: list[dict], *, model: str | None = N
|
|
| 122 |
usage = getattr(response, "usage", None)
|
| 123 |
return {
|
| 124 |
"answer": getattr(response, "output_text", "") or "",
|
| 125 |
-
"strategy": "media_analysis:
|
|
|
|
| 126 |
"model": model,
|
| 127 |
"sources": used,
|
| 128 |
"usage": {
|
|
|
|
| 1 |
+
"""Opt-in Azure or DeepSeek Responses API client for scientific publication media.
|
| 2 |
|
| 3 |
Dependencies: openai>=1.99 (a version with responses.create).
|
| 4 |
Environment: existing AZURE_OPENAI_ENDPOINT, AZURE_LLM_DEPLOYMENT,
|
|
|
|
| 64 |
|
| 65 |
|
| 66 |
def analyze_images(question: str, evidence: list[dict], *, model: str | None = None,
|
| 67 |
+
provider: str = "azure",
|
| 68 |
detail: str = "high", max_output_tokens: int = 1000,
|
| 69 |
client=None) -> dict:
|
| 70 |
"""Analyze pre-resolved evidence records containing image_path/caption/paper identifiers.
|
|
|
|
| 80 |
raise ValueError("POC permits at most four images per request")
|
| 81 |
if detail not in ("auto", "low", "high"):
|
| 82 |
raise ValueError("detail must be auto, low or high")
|
| 83 |
+
provider = str(provider).strip().lower()
|
| 84 |
+
if provider not in ("azure", "deepseek"):
|
| 85 |
+
raise ValueError("provider must be azure or deepseek")
|
| 86 |
+
if provider == "azure":
|
| 87 |
+
model = model or os.getenv("AZURE_OPENAI_VISION_DEPLOYMENT") or _env_required("AZURE_LLM_DEPLOYMENT")
|
| 88 |
+
else:
|
| 89 |
+
model = model or "deepseek-flash"
|
| 90 |
+
if model not in {"deepseek-flash", "deepseek-v4-pro"}:
|
| 91 |
+
raise ValueError("DeepSeek model must be deepseek-flash or deepseek-v4-pro")
|
| 92 |
if client is None:
|
| 93 |
from openai import OpenAI
|
| 94 |
+
if provider == "azure":
|
| 95 |
+
client = OpenAI(base_url=_azure_responses_base_url(),
|
| 96 |
+
api_key=_env_required("AZURE_OPENAI_API_KEY"),
|
| 97 |
+
timeout=180.0, max_retries=1)
|
| 98 |
+
else:
|
| 99 |
+
client = OpenAI(base_url="https://api.deepseek.com",
|
| 100 |
+
api_key=_env_required("DEEPSEEK_API_KEY"),
|
| 101 |
+
timeout=180.0, max_retries=1)
|
| 102 |
|
| 103 |
instructions = (
|
| 104 |
"You analyze scientific publication figures and tables. Use only supplied images, "
|
|
|
|
| 136 |
usage = getattr(response, "usage", None)
|
| 137 |
return {
|
| 138 |
"answer": getattr(response, "output_text", "") or "",
|
| 139 |
+
"strategy": f"media_analysis:{provider}_responses",
|
| 140 |
+
"provider": provider,
|
| 141 |
"model": model,
|
| 142 |
"sources": used,
|
| 143 |
"usage": {
|