mgbam commited on
Commit
b37e303
Β·
verified Β·
1 Parent(s): d68e573

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +85 -96
agent.py CHANGED
@@ -2,78 +2,58 @@
2
  import os
3
  from langchain_google_genai import ChatGoogleGenerativeAI
4
  from langchain.agents import AgentExecutor, create_structured_chat_agent
 
 
5
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
6
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
7
 
8
  # --- Import your defined tools FROM THE 'tools' PACKAGE ---
9
- from tools import ( # This now relies on tools/__init__.py
 
10
  BioPortalLookupTool,
11
- # GeminiTool, # Uncomment if you decide to use it
12
  UMLSLookupTool,
13
  QuantumTreatmentOptimizerTool,
14
- # You might not need to import the Input schemas here if the tools use them internally
15
- # and the agent only needs the tool instances.
16
- # BioPortalInput, GeminiInput, UMLSInput, QuantumOptimizerInput
17
  )
18
- # If QuantumOptimizerInput is needed for type hinting or direct use in agent.py:
19
- from tools import QuantumOptimizerInput # Or from tools.quantum_treatment_optimizer_tool import QuantumOptimizerInput
20
 
21
  from config.settings import settings
22
  from services.logger import app_logger
23
 
24
- # ... (rest of your agent.py file) ...
25
-
26
  # --- Initialize LLM (Gemini) ---
27
  try:
28
- # Ensure GOOGLE_API_KEY is set in your environment (HuggingFace Secrets)
29
- # or settings.GEMINI_API_KEY correctly maps to it.
30
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
31
- raise ValueError("GOOGLE_API_KEY (for Gemini) not found in settings or environment.")
 
 
32
 
33
  llm = ChatGoogleGenerativeAI(
34
- model="gemini-1.5-pro-latest", # Using a more capable Gemini model if available
35
- # model="gemini-pro", # Fallback if 1.5-pro is not yet available or preferred
36
- temperature=0.3,
37
- # google_api_key=settings.GEMINI_API_KEY, # Explicitly pass if GOOGLE_API_KEY env var isn't set
38
- convert_system_message_to_human=True, # Can be helpful for some models
39
- # safety_settings={ # Example safety settings
40
- # HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
41
- # HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
42
- # }
43
  )
44
- app_logger.info(f"ChatGoogleGenerativeAI ({llm.model}) initialized successfully.")
45
  except Exception as e:
46
- app_logger.error(f"Failed to initialize ChatGoogleGenerativeAI: {e}", exc_info=True)
 
47
  raise ValueError(f"Gemini LLM initialization failed: {e}. Check API key and configurations in HF Secrets.")
48
 
49
 
50
- # --- Initialize Tools ---
51
- # Ensure each tool's description is clear and guides the LLM on when and how to use it.
52
- # Also, ensure their args_schema is correctly defined.
53
- tools = [
54
  UMLSLookupTool(),
55
  BioPortalLookupTool(),
56
  QuantumTreatmentOptimizerTool(),
57
- # GeminiTool(), # Consider if this is needed. The main LLM is already Gemini.
58
- # Useful if this tool performs a very specific, different task with Gemini,
59
- # or uses a different Gemini model (e.g., for vision if main is text).
60
- # If it's just for general queries, the main agent LLM can handle it.
61
  ]
62
- app_logger.info(f"Tools initialized: {[tool.name for tool in tools]}")
63
 
64
 
65
  # --- Agent Prompt (Adapted for Structured Chat with Gemini and your tools) ---
66
- # This prompt guides the LLM to:
67
- # 1. Understand its role and capabilities.
68
- # 2. Know which tools are available and their purpose (from {tools}).
69
- # 3. Format tool invocations as a JSON blob with "action" and "action_input".
70
- # - "action_input" should be a string for simple tools (UMLSInput, GeminiInput).
71
- # - "action_input" should be a dictionary for tools with multiple args (BioPortalInput, QuantumOptimizerInput).
72
- # 4. Use the provided {patient_context}.
73
- # 5. Refer to {chat_history}.
74
- # 6. Process the new {input}.
75
- # 7. Use {agent_scratchpad} for its internal monologue/tool results.
76
-
77
  SYSTEM_PROMPT_TEMPLATE = (
78
  "You are 'Quantum Health Navigator', an advanced AI assistant for healthcare professionals. "
79
  "Your primary goal is to provide accurate information and insights based on user queries and available tools. "
@@ -83,43 +63,42 @@ SYSTEM_PROMPT_TEMPLATE = (
83
  "unless it's the direct output of a specialized tool like 'quantum_treatment_optimizer'.\n"
84
  "2. Patient Context: The user may provide patient context at the start of the session. This context is available as: {patient_context}. "
85
  "You MUST consider this context when it's relevant to the query, especially for the 'quantum_treatment_optimizer' tool.\n"
86
- "3. Tool Usage: You have access to the following tools:\n{tools}\n"
87
- " To use a tool, respond with a JSON markdown code block like this:\n"
88
- " ```json\n"
89
- " {{\n"
90
- ' "action": "tool_name",\n'
91
- ' "action_input": "query string for the tool" OR {{"arg1": "value1", "arg2": "value2", ...}} \n'
92
- " }}\n"
93
- " ```\n"
94
- " - For `umls_lookup` and `google_gemini_chat`, `action_input` is a single string (the 'term' or 'query').\n"
95
- " - For `bioportal_lookup`, `action_input` is a dictionary like `{{\"term\": \"search_term\", \"ontology\": \"ONTOLOGY_CODE\"}}`. If ontology is not specified by user, you can default to SNOMEDCT or ask.\n"
96
- " - For `quantum_treatment_optimizer`, `action_input` is a dictionary like `{{\"patient_data\": {{...patient details...}}, \"current_treatments\": [\"med1\"], \"conditions\": [\"cond1\"]}}`. You MUST populate 'patient_data' using the overall {patient_context} if available and relevant.\n"
97
  "4. Responding to User: After using a tool, you will receive an observation. Use this observation and your knowledge to formulate a comprehensive answer. Cite the tool if you used one (e.g., 'According to UMLS Lookup...').\n"
98
  "5. Specific Tool Guidance:\n"
99
- " - If asked about treatment optimization for a specific patient (especially if context is provided), you MUST use the `quantum_treatment_optimizer` tool.\n"
100
  " - For definitions, codes, or general medical concepts, `umls_lookup` or `bioportal_lookup` are appropriate.\n"
101
- " - If the query is very general, complex, or creative beyond simple lookups, you might consider using `google_gemini_chat` (if enabled) or answering directly if confident.\n"
102
  "6. Conversation Flow: Refer to the `Previous conversation history` to maintain context.\n\n"
103
  "Begin!\n\n"
104
  "Previous conversation history:\n"
105
  "{chat_history}\n\n"
106
  "New human question: {input}\n"
107
- "{agent_scratchpad}"
108
  )
109
 
 
 
 
 
110
  prompt = ChatPromptTemplate.from_messages([
111
  ("system", SYSTEM_PROMPT_TEMPLATE),
112
- # For structured chat agent, HumanMessage/AIMessage sequence is often handled by MessagesPlaceholder("agent_scratchpad")
113
- # or by how the agent formats history into the main prompt.
114
- # The key is that the {chat_history} and {input} placeholders are in the system prompt.
115
  MessagesPlaceholder(variable_name="agent_scratchpad"),
116
  ])
117
- app_logger.info("Agent prompt template created.")
118
 
119
  # --- Create Agent ---
120
  try:
121
- agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=prompt)
122
- app_logger.info("Structured chat agent created successfully with Gemini LLM.")
 
 
123
  except Exception as e:
124
  app_logger.error(f"Failed to create structured chat agent: {e}", exc_info=True)
125
  raise ValueError(f"Gemini agent creation failed: {e}")
@@ -128,70 +107,80 @@ except Exception as e:
128
  # --- Create Agent Executor ---
129
  agent_executor = AgentExecutor(
130
  agent=agent,
131
- tools=tools,
132
- verbose=True, # Set to True for debugging, False for production
133
- handle_parsing_errors=True, # Crucial for LLM-generated JSON for tool calls
134
- max_iterations=10, # Increased slightly for potentially complex tool interactions
135
- # return_intermediate_steps=True, # Enable if you need to see thoughts/tool calls in the response object
136
- early_stopping_method="generate", # Stop if LLM generates a stop token or a final answer
137
  )
138
  app_logger.info("AgentExecutor with Gemini agent created successfully.")
139
 
140
 
141
  # --- Getter Function for Streamlit App ---
142
  def get_agent_executor():
143
- """Returns the configured agent executor for Gemini."""
144
- # Initialization happens above when the module is loaded.
145
- # This function just returns the already created executor.
146
- # A check for API key is good practice, though it would have failed earlier if not set.
 
147
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
148
- # This log might be redundant if LLM init failed, but good as a sanity check here.
149
- app_logger.error("CRITICAL: GOOGLE_API_KEY (for Gemini) is not available at get_agent_executor call.")
150
  raise ValueError("Google API Key for Gemini not configured. Agent cannot function.")
151
  return agent_executor
152
 
153
  # --- Example Usage (for local testing of this agent.py file) ---
154
  if __name__ == "__main__":
155
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
156
- print("Please set your GOOGLE_API_KEY in .env file or as an environment variable.")
157
  else:
158
- print("\nQuantum Health Navigator (Gemini Agent Test Console)")
 
159
  print("Type 'exit' or 'quit' to stop.")
160
- print("Example queries:")
161
- print(" - What is hypertension?")
162
- print(" - Lookup 'myocardial infarction' in UMLS.")
163
- print(" - Search for 'diabetes mellitus type 2' in BioPortal using SNOMEDCT ontology.")
164
- print(" - Optimize treatment for a patient (context will be simulated).")
165
- print("-" * 30)
166
 
167
- executor = get_agent_executor()
168
- current_chat_history_for_test = [] # List of HumanMessage, AIMessage
169
 
170
  # Simulated patient context for testing the {patient_context} variable
171
- test_patient_context_summary = (
172
- "Age: 45; Gender: Male; Chief Complaint: Intermittent chest pain; "
173
- "Key Medical History: Hyperlipidemia; Current Medications: Atorvastatin 20mg."
 
174
  )
 
 
175
 
176
  while True:
177
- user_input_str = input("\nπŸ‘€ You: ")
178
  if user_input_str.lower() in ["exit", "quit"]:
179
- print("Exiting test console.")
180
  break
181
 
 
 
 
182
  try:
183
  app_logger.info(f"__main__ test: Invoking agent with input: '{user_input_str}'")
184
- response = executor.invoke({
 
 
185
  "input": user_input_str,
186
- "chat_history": current_chat_history_for_test,
187
- "patient_context": test_patient_context_summary # Passing the context
188
  })
189
 
190
- ai_output_str = response.get('output', "Agent did not produce an output.")
191
  print(f"πŸ€– Agent: {ai_output_str}")
192
 
193
- current_chat_history_for_test.append(HumanMessage(content=user_input_str))
194
- current_chat_history_for_test.append(AIMessage(content=ai_output_str))
 
 
 
 
 
195
 
196
  except Exception as e:
197
  print(f"⚠️ Error during agent invocation: {e}")
 
2
  import os
3
  from langchain_google_genai import ChatGoogleGenerativeAI
4
  from langchain.agents import AgentExecutor, create_structured_chat_agent
5
+ # from langchain_google_genai import HarmBlockThreshold, HarmCategory # Optional for safety
6
+
7
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
8
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
9
 
10
  # --- Import your defined tools FROM THE 'tools' PACKAGE ---
11
+ # This relies on tools/__init__.py correctly exporting these names.
12
+ from tools import (
13
  BioPortalLookupTool,
 
14
  UMLSLookupTool,
15
  QuantumTreatmentOptimizerTool,
16
+ # QuantumOptimizerInput, # Only if needed for type hints directly in this file
17
+ # GeminiTool, # Uncomment and add to __all__ in tools/__init__.py if you decide to use it
 
18
  )
 
 
19
 
20
  from config.settings import settings
21
  from services.logger import app_logger
22
 
 
 
23
  # --- Initialize LLM (Gemini) ---
24
  try:
 
 
25
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
26
+ # This check is crucial. If no key, LLM init will fail.
27
+ app_logger.error("CRITICAL: GOOGLE_API_KEY (for Gemini) not found in settings or environment. Agent cannot initialize.")
28
+ raise ValueError("GOOGLE_API_KEY (for Gemini) not configured.")
29
 
30
  llm = ChatGoogleGenerativeAI(
31
+ model="gemini-1.5-pro-latest", # Or "gemini-pro"
32
+ temperature=0.2, # Lower temperature for more deterministic tool use
33
+ # google_api_key=settings.GEMINI_API_KEY, # Explicitly pass if GOOGLE_API_KEY env var might not be picked up
34
+ convert_system_message_to_human=True, # Can help with models that don't strictly follow system role
35
+ # safety_settings={...} # Optional safety settings
 
 
 
 
36
  )
37
+ app_logger.info(f"ChatGoogleGenerativeAI ({llm.model}) initialized successfully for agent.")
38
  except Exception as e:
39
+ app_logger.error(f"Failed to initialize ChatGoogleGenerativeAI for agent: {e}", exc_info=True)
40
+ # This error needs to be propagated so get_agent_executor fails clearly
41
  raise ValueError(f"Gemini LLM initialization failed: {e}. Check API key and configurations in HF Secrets.")
42
 
43
 
44
+ # --- Initialize Tools List ---
45
+ # The tool instances are created here. Their internal logic (like API calls)
46
+ # will be executed when the agent calls their .run() or ._run() method.
47
+ tools_list = [
48
  UMLSLookupTool(),
49
  BioPortalLookupTool(),
50
  QuantumTreatmentOptimizerTool(),
51
+ # GeminiTool(), # Add if using
 
 
 
52
  ]
53
+ app_logger.info(f"Agent tools initialized: {[tool.name for tool in tools_list]}")
54
 
55
 
56
  # --- Agent Prompt (Adapted for Structured Chat with Gemini and your tools) ---
 
 
 
 
 
 
 
 
 
 
 
57
  SYSTEM_PROMPT_TEMPLATE = (
58
  "You are 'Quantum Health Navigator', an advanced AI assistant for healthcare professionals. "
59
  "Your primary goal is to provide accurate information and insights based on user queries and available tools. "
 
63
  "unless it's the direct output of a specialized tool like 'quantum_treatment_optimizer'.\n"
64
  "2. Patient Context: The user may provide patient context at the start of the session. This context is available as: {patient_context}. "
65
  "You MUST consider this context when it's relevant to the query, especially for the 'quantum_treatment_optimizer' tool.\n"
66
+ "3. Tool Usage: You have access to the following tools:\n{tools}\n" # {tools} is filled by the agent with tool names and descriptions
67
+ " To use a tool, respond with a JSON markdown code block with the 'action' and 'action_input' keys. "
68
+ " The 'action_input' should match the schema for the specified tool. Examples:\n"
69
+ " For `umls_lookup`: ```json\n{{\"action\": \"umls_lookup\", \"action_input\": \"myocardial infarction\"}}\n```\n"
70
+ " For `bioportal_lookup`: ```json\n{{\"action\": \"bioportal_lookup\", \"action_input\": {{\"term\": \"diabetes mellitus\", \"ontology\": \"SNOMEDCT\"}}}}\n```\n"
71
+ " For `quantum_treatment_optimizer`: ```json\n{{\"action\": \"quantum_treatment_optimizer\", \"action_input\": {{\"patient_data\": {{\"age\": 55, \"gender\": \"Male\"}}, \"current_treatments\": [\"metformin\"], \"conditions\": [\"Type 2 Diabetes\"]}}}}\n```\n"
72
+ " Ensure the `action_input` for `quantum_treatment_optimizer` includes a `patient_data` dictionary populated from the overall {patient_context}.\n"
 
 
 
 
73
  "4. Responding to User: After using a tool, you will receive an observation. Use this observation and your knowledge to formulate a comprehensive answer. Cite the tool if you used one (e.g., 'According to UMLS Lookup...').\n"
74
  "5. Specific Tool Guidance:\n"
75
+ " - If asked about treatment optimization for a specific patient (especially if patient context is provided), you MUST use the `quantum_treatment_optimizer` tool.\n"
76
  " - For definitions, codes, or general medical concepts, `umls_lookup` or `bioportal_lookup` are appropriate.\n"
77
+ # " - If the query is very general, complex, or creative beyond simple lookups, you might consider using `google_gemini_chat` (if enabled as a tool) or answering directly if confident.\n" # If GeminiTool is used
78
  "6. Conversation Flow: Refer to the `Previous conversation history` to maintain context.\n\n"
79
  "Begin!\n\n"
80
  "Previous conversation history:\n"
81
  "{chat_history}\n\n"
82
  "New human question: {input}\n"
83
+ "{agent_scratchpad}" # Placeholder for agent's thoughts and tool outputs
84
  )
85
 
86
+ # Create the prompt template
87
+ # The input_variables are what agent_executor.invoke expects, plus what create_structured_chat_agent adds.
88
+ # create_structured_chat_agent uses 'tools' and 'tool_names' internally when formatting the prompt for the LLM.
89
+ # The primary inputs we pass to invoke are 'input', 'chat_history', and 'patient_context'.
90
  prompt = ChatPromptTemplate.from_messages([
91
  ("system", SYSTEM_PROMPT_TEMPLATE),
 
 
 
92
  MessagesPlaceholder(variable_name="agent_scratchpad"),
93
  ])
94
+ app_logger.info("Agent prompt template created for Gemini structured chat agent.")
95
 
96
  # --- Create Agent ---
97
  try:
98
+ # create_structured_chat_agent is suitable for LLMs that can follow instructions
99
+ # to produce structured output (like JSON for tool calls) when prompted.
100
+ agent = create_structured_chat_agent(llm=llm, tools=tools_list, prompt=prompt)
101
+ app_logger.info("Structured chat agent created successfully with Gemini LLM and tools.")
102
  except Exception as e:
103
  app_logger.error(f"Failed to create structured chat agent: {e}", exc_info=True)
104
  raise ValueError(f"Gemini agent creation failed: {e}")
 
107
  # --- Create Agent Executor ---
108
  agent_executor = AgentExecutor(
109
  agent=agent,
110
+ tools=tools_list,
111
+ verbose=True, # Essential for debugging tool usage
112
+ handle_parsing_errors=True, # Gracefully handle if LLM output for tool call isn't perfect JSON
113
+ max_iterations=10, # Prevents overly long or runaway chains
114
+ # return_intermediate_steps=True, # Set to True to get thoughts/actions in the response dict
115
+ early_stopping_method="generate", # Sensible default
116
  )
117
  app_logger.info("AgentExecutor with Gemini agent created successfully.")
118
 
119
 
120
  # --- Getter Function for Streamlit App ---
121
  def get_agent_executor():
122
+ """
123
+ Returns the configured agent executor for Gemini.
124
+ Initialization of LLM, tools, agent, and executor happens when this module is imported.
125
+ """
126
+ # A final check for API key availability, though LLM initialization should have caught it.
127
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
128
+ app_logger.critical("CRITICAL: GOOGLE_API_KEY (for Gemini) is not available when get_agent_executor is called. This indicates an earlier init failure or misconfiguration.")
 
129
  raise ValueError("Google API Key for Gemini not configured. Agent cannot function.")
130
  return agent_executor
131
 
132
  # --- Example Usage (for local testing of this agent.py file) ---
133
  if __name__ == "__main__":
134
  if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
135
+ print("🚨 Please set your GOOGLE_API_KEY in .env file or as an environment variable to run the test.")
136
  else:
137
+ print("\nπŸš€ Quantum Health Navigator (Gemini Agent Test Console) πŸš€")
138
+ print("-----------------------------------------------------------")
139
  print("Type 'exit' or 'quit' to stop.")
140
+ print("Example topics: medical definitions, treatment optimization (will use simulated patient context).")
141
+ print("-" * 59)
 
 
 
 
142
 
143
+ test_executor = get_agent_executor() # Get the globally defined executor
144
+ current_chat_history_for_test_run = [] # List of HumanMessage, AIMessage
145
 
146
  # Simulated patient context for testing the {patient_context} variable
147
+ test_patient_context_summary_str = (
148
+ "Age: 62; Gender: Female; Chief Complaint: Fatigue and increased thirst; "
149
+ "Key Medical History: Obesity, family history of diabetes; "
150
+ "Current Medications: None reported; Allergies: Sulfa drugs."
151
  )
152
+ print(f"ℹ️ Simulated Patient Context for this test run: {test_patient_context_summary_str}\n")
153
+
154
 
155
  while True:
156
+ user_input_str = input("πŸ‘€ You: ")
157
  if user_input_str.lower() in ["exit", "quit"]:
158
+ print("πŸ‘‹ Exiting test console.")
159
  break
160
 
161
+ if not user_input_str.strip():
162
+ continue
163
+
164
  try:
165
  app_logger.info(f"__main__ test: Invoking agent with input: '{user_input_str}'")
166
+ # These are the keys expected by the prompt template
167
+ # and processed by create_structured_chat_agent
168
+ response_dict = test_executor.invoke({
169
  "input": user_input_str,
170
+ "chat_history": current_chat_history_for_test_run,
171
+ "patient_context": test_patient_context_summary_str
172
  })
173
 
174
+ ai_output_str = response_dict.get('output', "Agent did not produce an 'output' key.")
175
  print(f"πŸ€– Agent: {ai_output_str}")
176
 
177
+ # Update history for the next turn
178
+ current_chat_history_for_test_run.append(HumanMessage(content=user_input_str))
179
+ current_chat_history_for_test_run.append(AIMessage(content=ai_output_str))
180
+
181
+ # Optional: Limit history length
182
+ if len(current_chat_history_for_test_run) > 10: # Keep last 5 pairs
183
+ current_chat_history_for_test_run = current_chat_history_for_test_run[-10:]
184
 
185
  except Exception as e:
186
  print(f"⚠️ Error during agent invocation: {e}")