github-actions commited on
Commit ·
84ae02f
1
Parent(s): b1198f0
Auto deploy from GitHub
Browse files- app.py +0 -1
- src/agents/agents.py +36 -9
- src/agents/cdm_agents.py +4 -3
- src/core/model_manager.py +1 -1
app.py
CHANGED
|
@@ -30,7 +30,6 @@ from src.tools.fhir_memory import (
|
|
| 30 |
get_chat_history_by_session,
|
| 31 |
save_chat_as_fhir,
|
| 32 |
_get_client
|
| 33 |
-
get_chat_history_by_session
|
| 34 |
)
|
| 35 |
from src.mcp.server import MedicalMCPServer
|
| 36 |
|
|
|
|
| 30 |
get_chat_history_by_session,
|
| 31 |
save_chat_as_fhir,
|
| 32 |
_get_client
|
|
|
|
| 33 |
)
|
| 34 |
from src.mcp.server import MedicalMCPServer
|
| 35 |
|
src/agents/agents.py
CHANGED
|
@@ -2,11 +2,34 @@ import os
|
|
| 2 |
import re
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
|
|
|
| 5 |
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
|
| 6 |
from src.utils.logger import setup_logger
|
| 7 |
|
| 8 |
logger = setup_logger("Agents")
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from src.agent_params import get_agent_params
|
| 11 |
from src.core.model_manager import model_manager
|
| 12 |
from src.core.state import AgentState
|
|
@@ -80,6 +103,7 @@ class BaseAgent:
|
|
| 80 |
prompt = f"{prompt}{confidence_instruction}"
|
| 81 |
return prompt
|
| 82 |
|
|
|
|
| 83 |
async def run(self, state: AgentState, config=None):
|
| 84 |
"""Standard run method for graph nodes."""
|
| 85 |
messages = [SystemMessage(content=self.system_prompt)] + state["messages"]
|
|
@@ -124,7 +148,8 @@ class RoleClassifier(BaseAgent):
|
|
| 124 |
Classify the user input into one of five roles: 'patient', 'caregiver', 'clinician', 'researcher', or 'dietary'. Return only the name."""
|
| 125 |
super().__init__(fallback_prompt, "RoleClassifier.txt")
|
| 126 |
|
| 127 |
-
|
|
|
|
| 128 |
messages = [SystemMessage(content=self.system_prompt)] + state["messages"]
|
| 129 |
logger.info(f"--- RoleClassifier: Sending {len(messages)} messages to LLM ---")
|
| 130 |
|
|
@@ -183,7 +208,8 @@ class ResponseValidator(BaseAgent):
|
|
| 183 |
Check if the last response is medically accurate and follows guidelines. Return JSON only."""
|
| 184 |
super().__init__(fallback_prompt, "ResponseValidator.txt")
|
| 185 |
|
| 186 |
-
|
|
|
|
| 187 |
last_message = state["messages"][-1].content
|
| 188 |
|
| 189 |
start_time = time.time()
|
|
@@ -226,7 +252,8 @@ class SafetyCheck(BaseAgent):
|
|
| 226 |
Check if the response contains any dangerous advice or misinformation. Return JSON only."""
|
| 227 |
super().__init__(fallback_prompt, "SafetyCheck.txt")
|
| 228 |
|
| 229 |
-
|
|
|
|
| 230 |
last_message = state["messages"][-1].content
|
| 231 |
|
| 232 |
start_time = time.time()
|
|
@@ -269,7 +296,8 @@ class IntentClassifier(BaseAgent):
|
|
| 269 |
Classify into: 'diagnosis', 'treatment', 'monitoring', or 'general'."""
|
| 270 |
super().__init__(fallback_prompt, "IntentClassifier.txt")
|
| 271 |
|
| 272 |
-
|
|
|
|
| 273 |
start_time = time.time()
|
| 274 |
response = await self.llm.ainvoke([SystemMessage(content=self.system_prompt)] + state["messages"])
|
| 275 |
end_time = time.time()
|
|
@@ -300,7 +328,8 @@ class ClinicalSpecialist(BaseAgent):
|
|
| 300 |
super().__init__(fallback_prompt, f"ClinicalSpecialist_{specialty}.txt")
|
| 301 |
self.specialty = specialty
|
| 302 |
|
| 303 |
-
|
|
|
|
| 304 |
"""
|
| 305 |
Run clinical specialist with structured output requiring evidence citations.
|
| 306 |
Returns both the text response and evidence citations in AgentState.
|
|
@@ -397,7 +426,8 @@ class OutputMerger(BaseAgent):
|
|
| 397 |
fallback_prompt = "You are a clinical coordinator. Merge outputs into a single cohesive report."
|
| 398 |
super().__init__(fallback_prompt, "OutputMerger.txt")
|
| 399 |
|
| 400 |
-
|
|
|
|
| 401 |
latest_user_message = None
|
| 402 |
for message in reversed(state["messages"]):
|
| 403 |
if getattr(message, "type", None) == "human":
|
|
@@ -464,6 +494,3 @@ class DietarySpecialist(BaseAgent):
|
|
| 464 |
def __init__(self):
|
| 465 |
fallback_prompt = """You are a certified dietary specialist. Provide advice based on guidelines."""
|
| 466 |
super().__init__(fallback_prompt, "DietarySpecialist.txt", tools=[search_guidelines, get_nutritional_data, page_indexed_retrieval, save_patient_memory, get_patient_memory])
|
| 467 |
-
|
| 468 |
-
async def run(self, state: AgentState, config=None):
|
| 469 |
-
return await super().run(state, config=config)
|
|
|
|
| 2 |
import re
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
import asyncio
|
| 6 |
+
import functools
|
| 7 |
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
|
| 8 |
from src.utils.logger import setup_logger
|
| 9 |
|
| 10 |
logger = setup_logger("Agents")
|
| 11 |
|
| 12 |
+
def throttle_agent(func):
|
| 13 |
+
@functools.wraps(func)
|
| 14 |
+
async def wrapper(self, state, *args, **kwargs):
|
| 15 |
+
agent_name = getattr(self, "agent_name", self.__class__.__name__)
|
| 16 |
+
logger.info(f"[{agent_name}] >>> Start executing")
|
| 17 |
+
start_time = time.time()
|
| 18 |
+
try:
|
| 19 |
+
res = await func(self, state, *args, **kwargs)
|
| 20 |
+
elapsed = time.time() - start_time
|
| 21 |
+
logger.info(f"[{agent_name}] <<< Execution completed in {elapsed:.3f} seconds.")
|
| 22 |
+
if elapsed < 1.0:
|
| 23 |
+
delay = 1.5 - elapsed
|
| 24 |
+
logger.info(f"[{agent_name}] Execution was faster than 1.0s. Throttling: waiting {delay:.3f}s to reach 1.5s total time.")
|
| 25 |
+
await asyncio.sleep(delay)
|
| 26 |
+
logger.info(f"[{agent_name}] Throttling completed. Proceeding to next step.")
|
| 27 |
+
return res
|
| 28 |
+
except Exception as e:
|
| 29 |
+
logger.error(f"[{agent_name}] Exception during execution: {e}")
|
| 30 |
+
raise e
|
| 31 |
+
return wrapper
|
| 32 |
+
|
| 33 |
from src.agent_params import get_agent_params
|
| 34 |
from src.core.model_manager import model_manager
|
| 35 |
from src.core.state import AgentState
|
|
|
|
| 103 |
prompt = f"{prompt}{confidence_instruction}"
|
| 104 |
return prompt
|
| 105 |
|
| 106 |
+
@throttle_agent
|
| 107 |
async def run(self, state: AgentState, config=None):
|
| 108 |
"""Standard run method for graph nodes."""
|
| 109 |
messages = [SystemMessage(content=self.system_prompt)] + state["messages"]
|
|
|
|
| 148 |
Classify the user input into one of five roles: 'patient', 'caregiver', 'clinician', 'researcher', or 'dietary'. Return only the name."""
|
| 149 |
super().__init__(fallback_prompt, "RoleClassifier.txt")
|
| 150 |
|
| 151 |
+
@throttle_agent
|
| 152 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 153 |
messages = [SystemMessage(content=self.system_prompt)] + state["messages"]
|
| 154 |
logger.info(f"--- RoleClassifier: Sending {len(messages)} messages to LLM ---")
|
| 155 |
|
|
|
|
| 208 |
Check if the last response is medically accurate and follows guidelines. Return JSON only."""
|
| 209 |
super().__init__(fallback_prompt, "ResponseValidator.txt")
|
| 210 |
|
| 211 |
+
@throttle_agent
|
| 212 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 213 |
last_message = state["messages"][-1].content
|
| 214 |
|
| 215 |
start_time = time.time()
|
|
|
|
| 252 |
Check if the response contains any dangerous advice or misinformation. Return JSON only."""
|
| 253 |
super().__init__(fallback_prompt, "SafetyCheck.txt")
|
| 254 |
|
| 255 |
+
@throttle_agent
|
| 256 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 257 |
last_message = state["messages"][-1].content
|
| 258 |
|
| 259 |
start_time = time.time()
|
|
|
|
| 296 |
Classify into: 'diagnosis', 'treatment', 'monitoring', or 'general'."""
|
| 297 |
super().__init__(fallback_prompt, "IntentClassifier.txt")
|
| 298 |
|
| 299 |
+
@throttle_agent
|
| 300 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 301 |
start_time = time.time()
|
| 302 |
response = await self.llm.ainvoke([SystemMessage(content=self.system_prompt)] + state["messages"])
|
| 303 |
end_time = time.time()
|
|
|
|
| 328 |
super().__init__(fallback_prompt, f"ClinicalSpecialist_{specialty}.txt")
|
| 329 |
self.specialty = specialty
|
| 330 |
|
| 331 |
+
@throttle_agent
|
| 332 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 333 |
"""
|
| 334 |
Run clinical specialist with structured output requiring evidence citations.
|
| 335 |
Returns both the text response and evidence citations in AgentState.
|
|
|
|
| 426 |
fallback_prompt = "You are a clinical coordinator. Merge outputs into a single cohesive report."
|
| 427 |
super().__init__(fallback_prompt, "OutputMerger.txt")
|
| 428 |
|
| 429 |
+
@throttle_agent
|
| 430 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 431 |
latest_user_message = None
|
| 432 |
for message in reversed(state["messages"]):
|
| 433 |
if getattr(message, "type", None) == "human":
|
|
|
|
| 494 |
def __init__(self):
|
| 495 |
fallback_prompt = """You are a certified dietary specialist. Provide advice based on guidelines."""
|
| 496 |
super().__init__(fallback_prompt, "DietarySpecialist.txt", tools=[search_guidelines, get_nutritional_data, page_indexed_retrieval, save_patient_memory, get_patient_memory])
|
|
|
|
|
|
|
|
|
src/agents/cdm_agents.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
-
from src.agents.agents import BaseAgent
|
| 5 |
from src.core.state import AgentState
|
| 6 |
from src.tools.fhir_memory import get_observations_by_patient, get_medications_by_patient
|
| 7 |
from langchain_core.messages import SystemMessage, HumanMessage
|
|
@@ -109,7 +109,7 @@ class TrendAnalyzer(BaseAgent):
|
|
| 109 |
logger.info(f"Analyzing health trends for patient: {patient_id}")
|
| 110 |
observations = get_observations_by_patient.invoke({"patient_id": patient_id})
|
| 111 |
if isinstance(observations, str):
|
| 112 |
-
return observations
|
| 113 |
|
| 114 |
if not observations:
|
| 115 |
return "No observations available for trend analysis.", {}
|
|
@@ -175,7 +175,8 @@ class TrendAnalyzer(BaseAgent):
|
|
| 175 |
|
| 176 |
return analysis, structured_data
|
| 177 |
|
| 178 |
-
|
|
|
|
| 179 |
# Extract patient ID from state
|
| 180 |
patient_id = state.get("patient_id", "unknown")
|
| 181 |
if patient_id == "unknown":
|
|
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
+
from src.agents.agents import BaseAgent, throttle_agent
|
| 5 |
from src.core.state import AgentState
|
| 6 |
from src.tools.fhir_memory import get_observations_by_patient, get_medications_by_patient
|
| 7 |
from langchain_core.messages import SystemMessage, HumanMessage
|
|
|
|
| 109 |
logger.info(f"Analyzing health trends for patient: {patient_id}")
|
| 110 |
observations = get_observations_by_patient.invoke({"patient_id": patient_id})
|
| 111 |
if isinstance(observations, str):
|
| 112 |
+
return observations, {}
|
| 113 |
|
| 114 |
if not observations:
|
| 115 |
return "No observations available for trend analysis.", {}
|
|
|
|
| 175 |
|
| 176 |
return analysis, structured_data
|
| 177 |
|
| 178 |
+
@throttle_agent
|
| 179 |
+
async def run(self, state: AgentState, config=None, **kwargs):
|
| 180 |
# Extract patient ID from state
|
| 181 |
patient_id = state.get("patient_id", "unknown")
|
| 182 |
if patient_id == "unknown":
|
src/core/model_manager.py
CHANGED
|
@@ -136,7 +136,7 @@ class RateLimitFallbackWrapper:
|
|
| 136 |
|
| 137 |
for idx, fb_llm in enumerate(self.fallback_llms):
|
| 138 |
try:
|
| 139 |
-
logger.info(f"Trying fallback model {idx+1}")
|
| 140 |
return await fb_llm.ainvoke(messages, config=config, **kwargs)
|
| 141 |
except Exception as fb_e:
|
| 142 |
logger.warning(f"Fallback {idx+1} failed: {fb_e}")
|
|
|
|
| 136 |
|
| 137 |
for idx, fb_llm in enumerate(self.fallback_llms):
|
| 138 |
try:
|
| 139 |
+
logger.info(f"Trying fallback model {idx+1} [Model: {fb_llm.model}]")
|
| 140 |
return await fb_llm.ainvoke(messages, config=config, **kwargs)
|
| 141 |
except Exception as fb_e:
|
| 142 |
logger.warning(f"Fallback {idx+1} failed: {fb_e}")
|