Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -56,16 +56,73 @@ print("✅ BioGPT ready!\n")
|
|
| 56 |
|
| 57 |
|
| 58 |
# ── 2. BioGPT LLM — subclasses BaseLLM, bypasses LiteLLM ──────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
class BioGPTLLM(BaseLLM):
|
| 60 |
"""
|
| 61 |
Subclassing BaseLLM routes CrewAI agent calls directly to the local
|
| 62 |
-
BioGPT HuggingFace pipeline. LiteLLM is never invoked
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
| 64 |
"""
|
| 65 |
|
| 66 |
def __init__(self):
|
| 67 |
super().__init__(model="biogpt-local", temperature=0.65)
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
def call(
|
| 70 |
self,
|
| 71 |
messages: Union[str, List[Dict[str, str]]],
|
|
@@ -74,31 +131,42 @@ class BioGPTLLM(BaseLLM):
|
|
| 74 |
available_functions: Optional[Dict[str, Any]] = None,
|
| 75 |
**kwargs,
|
| 76 |
) -> str:
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
else:
|
| 81 |
-
|
| 82 |
-
for m in messages:
|
| 83 |
-
role = m.get("role", "")
|
| 84 |
-
content = m.get("content", "")
|
| 85 |
-
if role == "system":
|
| 86 |
-
parts.append(f"[System] {content}")
|
| 87 |
-
elif role in ("user", "human"):
|
| 88 |
-
parts.append(content)
|
| 89 |
-
prompt = "\n".join(parts).strip()
|
| 90 |
-
|
| 91 |
-
if not prompt:
|
| 92 |
-
return "No prompt received."
|
| 93 |
-
|
| 94 |
-
raw = _pipe(prompt)[0]["generated_text"]
|
| 95 |
-
generated = raw[len(prompt):].strip()
|
| 96 |
-
|
| 97 |
-
# Trim to last complete sentence
|
| 98 |
-
if "." in generated:
|
| 99 |
-
generated = generated[: generated.rfind(".") + 1]
|
| 100 |
|
| 101 |
-
return
|
| 102 |
|
| 103 |
def supports_function_calling(self) -> bool:
|
| 104 |
return False
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
# ── 2. BioGPT LLM — subclasses BaseLLM, bypasses LiteLLM ──────────
|
| 59 |
+
# BioGPT is a causal LM trained on PubMed abstracts — it is NOT
|
| 60 |
+
# instruction-tuned. It works by completing a biomedical sentence/phrase.
|
| 61 |
+
# CrewAI passes a large system+user message block; we extract the clinical
|
| 62 |
+
# case from it and build a tight completion-style prompt BioGPT can handle.
|
| 63 |
+
|
| 64 |
+
def _extract_case(messages) -> str:
|
| 65 |
+
"""Pull the raw clinical case text out of CrewAI message list."""
|
| 66 |
+
full = ""
|
| 67 |
+
if isinstance(messages, str):
|
| 68 |
+
full = messages
|
| 69 |
+
else:
|
| 70 |
+
for m in messages:
|
| 71 |
+
full += " " + m.get("content", "")
|
| 72 |
+
# Look for our case marker
|
| 73 |
+
m = re.search(r"(\d+-year-old .+?\.)", full, re.S)
|
| 74 |
+
if m:
|
| 75 |
+
return m.group(1).strip()
|
| 76 |
+
# Fallback: last non-empty content block
|
| 77 |
+
if isinstance(messages, list):
|
| 78 |
+
for m in reversed(messages):
|
| 79 |
+
c = m.get("content", "").strip()
|
| 80 |
+
if c:
|
| 81 |
+
return c[:300]
|
| 82 |
+
return full.strip()[:300]
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _biogpt_complete(prompt: str, max_new: int = 200) -> str:
|
| 86 |
+
"""Run BioGPT completion and return only the generated portion."""
|
| 87 |
+
raw = _pipe(prompt, max_new_tokens=max_new)[0]["generated_text"]
|
| 88 |
+
generated = raw[len(prompt):].strip()
|
| 89 |
+
# Trim to last complete sentence
|
| 90 |
+
if "." in generated:
|
| 91 |
+
generated = generated[: generated.rfind(".") + 1]
|
| 92 |
+
return generated or "Insufficient data generated."
|
| 93 |
+
|
| 94 |
+
|
| 95 |
class BioGPTLLM(BaseLLM):
|
| 96 |
"""
|
| 97 |
Subclassing BaseLLM routes CrewAI agent calls directly to the local
|
| 98 |
+
BioGPT HuggingFace pipeline. LiteLLM is never invoked.
|
| 99 |
+
|
| 100 |
+
Key insight: BioGPT is a PubMed completion model, not an instruction
|
| 101 |
+
follower. We detect which agent role is calling from the system message
|
| 102 |
+
and craft a tight domain-specific completion prompt accordingly.
|
| 103 |
"""
|
| 104 |
|
| 105 |
def __init__(self):
|
| 106 |
super().__init__(model="biogpt-local", temperature=0.65)
|
| 107 |
|
| 108 |
+
def _detect_role(self, messages) -> str:
|
| 109 |
+
"""Detect which agent is calling by scanning the system message."""
|
| 110 |
+
system_text = ""
|
| 111 |
+
if isinstance(messages, list):
|
| 112 |
+
for m in messages:
|
| 113 |
+
if m.get("role") == "system":
|
| 114 |
+
system_text = m.get("content", "").lower()
|
| 115 |
+
break
|
| 116 |
+
if "diagnos" in system_text:
|
| 117 |
+
return "diagnosis"
|
| 118 |
+
if "treatment" in system_text or "pharmacol" in system_text:
|
| 119 |
+
return "treatment"
|
| 120 |
+
if "precaution" in system_text or "lifestyle" in system_text:
|
| 121 |
+
return "precaution"
|
| 122 |
+
if "coordinat" in system_text or "synthesise" in system_text or "summary" in system_text:
|
| 123 |
+
return "summary"
|
| 124 |
+
return "general"
|
| 125 |
+
|
| 126 |
def call(
|
| 127 |
self,
|
| 128 |
messages: Union[str, List[Dict[str, str]]],
|
|
|
|
| 131 |
available_functions: Optional[Dict[str, Any]] = None,
|
| 132 |
**kwargs,
|
| 133 |
) -> str:
|
| 134 |
+
role = self._detect_role(messages)
|
| 135 |
+
case = _extract_case(messages)
|
| 136 |
+
|
| 137 |
+
# Build tight PubMed-style completion prompts per agent role
|
| 138 |
+
if role == "diagnosis":
|
| 139 |
+
prompt = (
|
| 140 |
+
f"A {case} "
|
| 141 |
+
f"The differential diagnosis includes:"
|
| 142 |
+
)
|
| 143 |
+
elif role == "treatment":
|
| 144 |
+
prompt = (
|
| 145 |
+
f"For a patient with {case} "
|
| 146 |
+
f"The recommended treatment includes:"
|
| 147 |
+
)
|
| 148 |
+
elif role == "precaution":
|
| 149 |
+
prompt = (
|
| 150 |
+
f"For a patient with {case} "
|
| 151 |
+
f"Important precautions and follow-up recommendations include:"
|
| 152 |
+
)
|
| 153 |
+
elif role == "summary":
|
| 154 |
+
# For coordinator: re-run all three and concatenate
|
| 155 |
+
diag = _biogpt_complete(
|
| 156 |
+
f"A {case} The differential diagnosis includes:", 150)
|
| 157 |
+
treat = _biogpt_complete(
|
| 158 |
+
f"For a patient with {case} The recommended treatment includes:", 150)
|
| 159 |
+
prec = _biogpt_complete(
|
| 160 |
+
f"For a patient with {case} Important precautions include:", 150)
|
| 161 |
+
return (
|
| 162 |
+
f"## Diagnosis\n{diag}\n\n"
|
| 163 |
+
f"## Treatment\n{treat}\n\n"
|
| 164 |
+
f"## Precautions\n{prec}"
|
| 165 |
+
)
|
| 166 |
else:
|
| 167 |
+
prompt = f"The clinical findings for {case} suggest:"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
+
return _biogpt_complete(prompt)
|
| 170 |
|
| 171 |
def supports_function_calling(self) -> bool:
|
| 172 |
return False
|