starfriend commited on
Commit
0bc511e
·
verified ·
1 Parent(s): 4c153ee

Upload research_fallback.py

Browse files
Files changed (1) hide show
  1. backend/research_fallback.py +88 -0
backend/research_fallback.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bounded DeepSeek fallback for publication-grounded research answers.
2
+
3
+ Only called after eligible Llama infrastructure failures (or while a short
4
+ circuit breaker is active); never changes retrieval or media analysis.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import threading
10
+ import time
11
+
12
+ _LOCK = threading.Lock()
13
+ _UNAVAILABLE_UNTIL = 0.0
14
+
15
+
16
+ def is_infrastructure_error(exc: Exception) -> bool:
17
+ text = f"{type(exc).__name__}: {exc}".lower()
18
+ signals = (
19
+ "paused", "queue is full", "queueerror", "scheduling failure",
20
+ "unable to schedule", "space is unavailable", "invalid state",
21
+ "connection error", "connecterror", "connection refused", "timed out",
22
+ "timeout", "readtimeout", "service unavailable", "502 bad gateway",
23
+ "503 service", "504 gateway", "gradio", "network error",
24
+ )
25
+ return any(signal in text for signal in signals)
26
+
27
+
28
+ def llama_circuit_open() -> bool:
29
+ with _LOCK:
30
+ return time.monotonic() < _UNAVAILABLE_UNTIL
31
+
32
+
33
+ def mark_llama_unavailable() -> None:
34
+ global _UNAVAILABLE_UNTIL
35
+ cooldown = max(0, min(3600, int(os.getenv("WATERSCOPE_FALLBACK_COOLDOWN_SECONDS", "180"))))
36
+ with _LOCK:
37
+ _UNAVAILABLE_UNTIL = time.monotonic() + cooldown
38
+
39
+
40
+ def clear_llama_circuit() -> None:
41
+ global _UNAVAILABLE_UNTIL
42
+ with _LOCK:
43
+ _UNAVAILABLE_UNTIL = 0.0
44
+
45
+
46
+ def deepseek_research_answer(context: str, question: str, application: str) -> tuple[str, str]:
47
+ """Answer from supplied retrieved evidence, without independently browsing papers."""
48
+ from openai import OpenAI
49
+
50
+ key = os.getenv("DEEPSEEK_API_KEY", "").strip()
51
+ if not key:
52
+ raise RuntimeError("DeepSeek research fallback is not configured (missing DEEPSEEK_API_KEY)")
53
+ model = os.getenv("WATERSCOPE_RESEARCH_FALLBACK_MODEL", "deepseek-flash").strip()
54
+ if model not in {"deepseek-flash", "deepseek-v4-pro"}:
55
+ raise ValueError("Unsupported research fallback model")
56
+ max_context_chars = max(3000, min(150000, int(os.getenv("WATERSCOPE_FALLBACK_MAX_CONTEXT_CHARS", "60000"))))
57
+ max_tokens = max(256, min(8192, int(os.getenv("WATERSCOPE_FALLBACK_MAX_OUTPUT_TOKENS", "1600"))))
58
+ client = OpenAI(base_url="https://api.deepseek.com", api_key=key, timeout=90.0, max_retries=0)
59
+ instructions = (
60
+ "You are WaterScope-AI, a research assistant for environmental science and engineering. "
61
+ "Answer the user using ONLY the retrieved publication evidence provided. "
62
+ "Respect the task type. For summaries, synthesize supported findings; for research gaps, "
63
+ "distinguish gaps explicitly reported in the sources from possible directions inferred from them. "
64
+ "Do not invent observations, references, numerical values or citations. "
65
+ "Clearly say when evidence is insufficient. Refer to titles/authors present in the context. "
66
+ "Use concise Markdown formatting. Do not expose internal retrieval identifiers."
67
+ )
68
+ response = client.responses.create(
69
+ model=model,
70
+ instructions=instructions,
71
+ input=[{"role":"user","content":(
72
+ f"Research task: {application}\n\nRetrieved publication evidence:\n"
73
+ f"{context[:max_context_chars]}\n\nUser question:\n{question}"
74
+ )}],
75
+ max_output_tokens=max_tokens,
76
+ reasoning={"effort":"none"},
77
+ )
78
+ answer = (getattr(response, "output_text", None) or "").strip()
79
+ if not answer:
80
+ pieces = []
81
+ for item in getattr(response, "output", []) or []:
82
+ for segment in getattr(item, "content", []) or []:
83
+ if getattr(segment, "type", None) == "output_text":
84
+ pieces.append(getattr(segment, "text", ""))
85
+ answer = "\n".join(pieces).strip()
86
+ if not answer:
87
+ raise RuntimeError("DeepSeek research fallback returned an empty response")
88
+ return answer, model