mgbam commited on
Commit
be49f6d
·
verified ·
1 Parent(s): 7ae304b

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +120 -132
agent.py CHANGED
@@ -1,203 +1,191 @@
1
  # /home/user/app/agent.py
 
2
  from langchain_google_genai import ChatGoogleGenerativeAI
3
- from langchain.agents import AgentExecutor, create_structured_chat_agent # Using a more general agent
4
- # If you want to try Gemini's native function calling (experimental and might require specific model versions):
5
- # from langchain_google_genai.chat_models import GChatVertexAI # For Vertex AI
6
- # from langchain_google_genai import HarmBlockThreshold, HarmCategory # For safety settings
7
 
8
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
9
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
10
- # from langchain_community.chat_message_histories import ChatMessageHistory # Not used directly here
11
 
12
- from tools import (
13
- UMLSLookupTool, BioPortalLookupTool, QuantumTreatmentOptimizerTool
14
- # GeminiTool might be redundant if the main LLM is Gemini, unless it's for a different Gemini model/task.
15
- )
 
 
16
  from config.settings import settings
17
  from services.logger import app_logger
18
 
19
  # --- Initialize LLM (Gemini) ---
20
- # Ensure GOOGLE_API_KEY is set in your environment, or pass it directly:
21
- # api_key=settings.GEMINI_API_KEY (if settings.GEMINI_API_KEY maps to GOOGLE_API_KEY)
22
  try:
 
 
 
 
 
23
  llm = ChatGoogleGenerativeAI(
24
- model="gemini-pro", # Or "gemini-1.5-pro-latest" if available and preferred
 
25
  temperature=0.3,
26
- # google_api_key=settings.GEMINI_API_KEY, # Explicitly pass if needed
27
- # convert_system_message_to_human=True, # Sometimes helpful for models not strictly adhering to system role
28
- # safety_settings={ # Optional: configure safety settings
29
- # HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
30
- # HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
31
  # }
32
  )
33
- app_logger.info("ChatGoogleGenerativeAI (Gemini) initialized successfully.")
34
  except Exception as e:
35
  app_logger.error(f"Failed to initialize ChatGoogleGenerativeAI: {e}", exc_info=True)
36
- # Raise an error that can be caught by get_agent_executor to inform the user
37
- raise ValueError(f"Gemini LLM initialization failed: {e}. Check API key and configurations.")
38
 
39
 
40
  # --- Initialize Tools ---
41
- # Ensure your tools' descriptions are very clear, especially for non-OpenAI function calling agents.
 
42
  tools = [
43
  UMLSLookupTool(),
44
  BioPortalLookupTool(),
45
  QuantumTreatmentOptimizerTool(),
 
 
 
 
46
  ]
47
  app_logger.info(f"Tools initialized: {[tool.name for tool in tools]}")
48
 
49
 
50
- # --- Agent Prompt (Adapted for a general structured chat agent) ---
51
- # This prompt needs to guide the LLM to decide on tool use and format tool calls.
52
- # It's more complex than relying on native function calling.
53
- # We might need to instruct it to output a specific JSON structure for tool calls.
54
- # For now, let's try a ReAct-style approach with create_structured_chat_agent.
55
- # LangChain Hub has prompts for this: hub.pull("hwchase17/structured-chat-agent")
56
- # Or we define a custom one:
57
-
58
- # This is a simplified prompt. For robust tool use with Gemini (without native function calling),
59
- # you'd often use a ReAct prompt or a prompt that guides it to output JSON for tool calls.
60
- # `create_structured_chat_agent` is designed to work with models that can follow instructions
61
- # to produce a structured output, often for tool usage.
62
-
63
- # For Gemini, which now supports tool calling via its API directly (though LangChain integration might vary),
64
- # we *could* try to structure the prompt for that if `ChatGoogleGenerativeAI` has good support.
65
- # Let's assume a more general structured chat approach first if direct tool calling isn't as smooth
66
- # as with OpenAI's function calling agents.
67
-
68
- # If trying Gemini's newer tool calling features with LangChain (ensure your langchain-google-genai is up to date):
69
- # You might be able to bind tools directly to the LLM and use a simpler agent structure.
70
- # llm_with_tools = llm.bind_tools(tools) # This is the newer LangChain syntax
71
- # Then create an agent that leverages this.
72
-
73
- # For now, let's use `create_structured_chat_agent` which is more general.
74
- # This prompt is similar to hwchase17/structured-chat-agent on Langsmith Hub
75
- SYSTEM_PROMPT_TEXT = (
76
- "You are 'Quantum Health Navigator', a helpful AI assistant for healthcare professionals. "
77
- "Respond to the human as helpfully and accurately as possible. "
78
- "You have access to the following tools:\n\n"
79
- "{tools}\n\n" # This will be filled by the agent with tool names and descriptions
80
- "To use a tool, you can use the following format:\n\n"
81
- "```json\n"
82
- "{{\n"
83
- ' "action": "tool_name",\n'
84
- ' "action_input": "input_to_tool"\n' # For tools with single string input
85
- # Or for tools with structured input (like QuantumTreatmentOptimizerTool):
86
- # ' "action_input": {{"arg1": "value1", "arg2": "value2"}}\n'
87
- "}}\n"
88
- "```\n\n"
89
- "If you use a tool, the system will give you the observation from the tool. "
90
- "Then you must respond to the human based on this observation and your knowledge. "
91
- "If the human asks a question that doesn't require a tool, answer directly. "
92
- "When asked about treatment optimization for a specific patient based on provided context, "
93
- "you MUST use the 'quantum_treatment_optimizer' tool. "
94
- "For general medical knowledge, you can answer directly or use UMLS/BioPortal. "
95
- "Always cite the tool you used if its output is part of your final response. "
96
- "Do not provide medical advice directly for specific patient cases without using the 'quantum_treatment_optimizer' tool. "
97
- "Patient Context for this session (if provided by the user earlier): {patient_context}\n" # Added patient_context
98
  "Begin!\n\n"
99
  "Previous conversation history:\n"
100
  "{chat_history}\n\n"
101
  "New human question: {input}\n"
102
- "{agent_scratchpad}" # Placeholder for agent's thoughts and tool outputs
103
  )
104
 
105
  prompt = ChatPromptTemplate.from_messages([
106
- ("system", SYSTEM_PROMPT_TEXT),
107
- # MessagesPlaceholder(variable_name="chat_history"), # chat_history is now part of the system prompt
108
- # ("human", "{input}"), # input is now part of the system prompt
109
- MessagesPlaceholder(variable_name="agent_scratchpad"), # For structured chat agent, this is important
 
110
  ])
111
- # Note: The `create_structured_chat_agent` expects `input` and `chat_history` to be implicitly handled
112
- # or passed through `agent_scratchpad` based on how it formats things.
113
- # The prompt structure might need adjustment based on the exact agent behavior.
114
- # Often, for these agents, you pass "input" and "chat_history" to invoke, and the prompt template variables
115
- # are {input}, {chat_history}, {agent_scratchpad}, {tools}, {tool_names}.
116
-
117
- # For create_structured_chat_agent, the prompt should guide the LLM to produce
118
- # either a final answer or a JSON blob for a tool call.
119
- # The input variables for the prompt are typically 'input', 'chat_history', 'agent_scratchpad', 'tools', 'tool_names'.
120
- # Our SYSTEM_PROMPT_TEXT includes these implicitly or explicitly.
121
-
122
 
123
  # --- Create Agent ---
124
  try:
125
- # `create_structured_chat_agent` is designed for LLMs that can follow complex instructions
126
- # and output structured data (like JSON for tool calls) when prompted to do so.
127
  agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=prompt)
128
- app_logger.info("Structured chat agent created successfully with Gemini.")
129
  except Exception as e:
130
  app_logger.error(f"Failed to create structured chat agent: {e}", exc_info=True)
131
- raise ValueError(f"Agent creation failed: {e}")
132
 
133
 
134
  # --- Create Agent Executor ---
135
  agent_executor = AgentExecutor(
136
  agent=agent,
137
  tools=tools,
138
- verbose=True,
139
- handle_parsing_errors=True, # Important for agents that parse LLM output for tool calls
140
- # Example: "Could not parse LLM output: `...`" - then it can retry or return this error.
141
- # max_iterations=7, # Good to prevent overly long chains
142
- # return_intermediate_steps=True # Useful for debugging to see thought process
143
  )
144
- app_logger.info("AgentExecutor created successfully.")
145
 
146
 
147
  # --- Getter Function for Streamlit App ---
148
  def get_agent_executor():
149
  """Returns the configured agent executor for Gemini."""
150
- # The llm and agent_executor are already initialized.
151
- # We check for the API key here as a safeguard, though initialization would have failed earlier.
152
- if not (settings.GEMINI_API_KEY or os.environ.get("GOOGLE_API_KEY")): # Check both setting and env var
153
- app_logger.error("GOOGLE_API_KEY (for Gemini) not set. Agent will not function.")
154
- raise ValueError("Google API Key for Gemini not configured. Agent cannot be initialized.")
 
 
155
  return agent_executor
156
 
157
- # --- Example Usage (for local testing) ---
158
- import os # For checking GOOGLE_API_KEY from environment
159
-
160
  if __name__ == "__main__":
161
- if not (settings.GEMINI_API_KEY or os.environ.get("GOOGLE_API_KEY")):
162
- print("Please set your GOOGLE_API_KEY (for Gemini) in .env file or environment.")
163
  else:
164
- print("Gemini Agent Test Console (type 'exit' or 'quit' to stop)")
 
 
 
 
 
 
 
 
165
  executor = get_agent_executor()
166
-
167
- # For structured chat agents, chat_history is often passed in the invoke call.
168
- # The agent prompt includes {chat_history}.
169
- current_chat_history = [] # List of HumanMessage, AIMessage
170
-
171
- # Initial patient context (simulated for testing)
172
- patient_context_for_test = {
173
- "age": 35,
174
- "gender": "Male",
175
- "key_medical_history": "Type 2 Diabetes, Hypertension",
176
- "current_medications": "Metformin, Lisinopril"
177
- }
178
- context_summary_parts_test = [f"{k.replace('_', ' ').title()}: {v}" for k, v in patient_context_for_test.items() if v]
179
- patient_context_str_test = "; ".join(context_summary_parts_test) if context_summary_parts_test else "None provided."
180
 
 
 
 
 
 
181
 
182
  while True:
183
- user_input = input("You: ")
184
- if user_input.lower() in ["exit", "quit"]:
 
185
  break
186
 
187
  try:
 
188
  response = executor.invoke({
189
- "input": user_input,
190
- "chat_history": current_chat_history,
191
- "patient_context": patient_context_str_test # Passing patient context
192
- # `tools` and `tool_names` are usually handled by the agent constructor
193
  })
194
 
195
- ai_output = response.get('output', "No output from agent.")
196
- print(f"Agent: {ai_output}")
197
 
198
- current_chat_history.append(HumanMessage(content=user_input))
199
- current_chat_history.append(AIMessage(content=ai_output))
200
 
201
  except Exception as e:
202
- print(f"Error invoking agent: {e}")
203
- app_logger.error(f"Error in __main__ agent test: {e}", exc_info=True)
 
1
  # /home/user/app/agent.py
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 ---
11
+ from tools.bioportal_tool import BioPortalLookupTool, BioPortalInput
12
+ from tools.gemini_tool import GeminiTool, GeminiInput # For using Gemini as a specific sub-task tool
13
+ from tools.umls_tool import UMLSLookupTool, UMLSInput
14
+ from tools.quantum_treatment_optimizer_tool import QuantumTreatmentOptimizerTool, QuantumOptimizerInput # Assuming this path and model name
15
+
16
  from config.settings import settings
17
  from services.logger import app_logger
18
 
19
  # --- Initialize LLM (Gemini) ---
 
 
20
  try:
21
+ # Ensure GOOGLE_API_KEY is set in your environment (HuggingFace Secrets)
22
+ # or settings.GEMINI_API_KEY correctly maps to it.
23
+ if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
24
+ raise ValueError("GOOGLE_API_KEY (for Gemini) not found in settings or environment.")
25
+
26
  llm = ChatGoogleGenerativeAI(
27
+ model="gemini-1.5-pro-latest", # Using a more capable Gemini model if available
28
+ # model="gemini-pro", # Fallback if 1.5-pro is not yet available or preferred
29
  temperature=0.3,
30
+ # google_api_key=settings.GEMINI_API_KEY, # Explicitly pass if GOOGLE_API_KEY env var isn't set
31
+ convert_system_message_to_human=True, # Can be helpful for some models
32
+ # safety_settings={ # Example safety settings
33
+ # HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
34
+ # HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
35
  # }
36
  )
37
+ app_logger.info(f"ChatGoogleGenerativeAI ({llm.model}) initialized successfully.")
38
  except Exception as e:
39
  app_logger.error(f"Failed to initialize ChatGoogleGenerativeAI: {e}", exc_info=True)
40
+ raise ValueError(f"Gemini LLM initialization failed: {e}. Check API key and configurations in HF Secrets.")
 
41
 
42
 
43
  # --- Initialize Tools ---
44
+ # Ensure each tool's description is clear and guides the LLM on when and how to use it.
45
+ # Also, ensure their args_schema is correctly defined.
46
  tools = [
47
  UMLSLookupTool(),
48
  BioPortalLookupTool(),
49
  QuantumTreatmentOptimizerTool(),
50
+ # GeminiTool(), # Consider if this is needed. The main LLM is already Gemini.
51
+ # Useful if this tool performs a very specific, different task with Gemini,
52
+ # or uses a different Gemini model (e.g., for vision if main is text).
53
+ # If it's just for general queries, the main agent LLM can handle it.
54
  ]
55
  app_logger.info(f"Tools initialized: {[tool.name for tool in tools]}")
56
 
57
 
58
+ # --- Agent Prompt (Adapted for Structured Chat with Gemini and your tools) ---
59
+ # This prompt guides the LLM to:
60
+ # 1. Understand its role and capabilities.
61
+ # 2. Know which tools are available and their purpose (from {tools}).
62
+ # 3. Format tool invocations as a JSON blob with "action" and "action_input".
63
+ # - "action_input" should be a string for simple tools (UMLSInput, GeminiInput).
64
+ # - "action_input" should be a dictionary for tools with multiple args (BioPortalInput, QuantumOptimizerInput).
65
+ # 4. Use the provided {patient_context}.
66
+ # 5. Refer to {chat_history}.
67
+ # 6. Process the new {input}.
68
+ # 7. Use {agent_scratchpad} for its internal monologue/tool results.
69
+
70
+ SYSTEM_PROMPT_TEMPLATE = (
71
+ "You are 'Quantum Health Navigator', an advanced AI assistant for healthcare professionals. "
72
+ "Your primary goal is to provide accurate information and insights based on user queries and available tools. "
73
+ "You must adhere to the following guidelines:\n"
74
+ "1. Disclaimers: Always remind the user that you are an AI, not a human medical professional, and your information "
75
+ "is for support, not a substitute for clinical judgment. Do not provide direct medical advice for specific patient cases "
76
+ "unless it's the direct output of a specialized tool like 'quantum_treatment_optimizer'.\n"
77
+ "2. Patient Context: The user may provide patient context at the start of the session. This context is available as: {patient_context}. "
78
+ "You MUST consider this context when it's relevant to the query, especially for the 'quantum_treatment_optimizer' tool.\n"
79
+ "3. Tool Usage: You have access to the following tools:\n{tools}\n"
80
+ " To use a tool, respond with a JSON markdown code block like this:\n"
81
+ " ```json\n"
82
+ " {{\n"
83
+ ' "action": "tool_name",\n'
84
+ ' "action_input": "query string for the tool" OR {{"arg1": "value1", "arg2": "value2", ...}} \n'
85
+ " }}\n"
86
+ " ```\n"
87
+ " - For `umls_lookup` and `google_gemini_chat`, `action_input` is a single string (the 'term' or 'query').\n"
88
+ " - 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"
89
+ " - 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"
90
+ "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"
91
+ "5. Specific Tool Guidance:\n"
92
+ " - If asked about treatment optimization for a specific patient (especially if context is provided), you MUST use the `quantum_treatment_optimizer` tool.\n"
93
+ " - For definitions, codes, or general medical concepts, `umls_lookup` or `bioportal_lookup` are appropriate.\n"
94
+ " - 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"
95
+ "6. Conversation Flow: Refer to the `Previous conversation history` to maintain context.\n\n"
 
 
 
 
 
 
 
 
 
 
96
  "Begin!\n\n"
97
  "Previous conversation history:\n"
98
  "{chat_history}\n\n"
99
  "New human question: {input}\n"
100
+ "{agent_scratchpad}"
101
  )
102
 
103
  prompt = ChatPromptTemplate.from_messages([
104
+ ("system", SYSTEM_PROMPT_TEMPLATE),
105
+ # For structured chat agent, HumanMessage/AIMessage sequence is often handled by MessagesPlaceholder("agent_scratchpad")
106
+ # or by how the agent formats history into the main prompt.
107
+ # The key is that the {chat_history} and {input} placeholders are in the system prompt.
108
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
109
  ])
110
+ app_logger.info("Agent prompt template created.")
 
 
 
 
 
 
 
 
 
 
111
 
112
  # --- Create Agent ---
113
  try:
 
 
114
  agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=prompt)
115
+ app_logger.info("Structured chat agent created successfully with Gemini LLM.")
116
  except Exception as e:
117
  app_logger.error(f"Failed to create structured chat agent: {e}", exc_info=True)
118
+ raise ValueError(f"Gemini agent creation failed: {e}")
119
 
120
 
121
  # --- Create Agent Executor ---
122
  agent_executor = AgentExecutor(
123
  agent=agent,
124
  tools=tools,
125
+ verbose=True, # Set to True for debugging, False for production
126
+ handle_parsing_errors=True, # Crucial for LLM-generated JSON for tool calls
127
+ max_iterations=10, # Increased slightly for potentially complex tool interactions
128
+ # return_intermediate_steps=True, # Enable if you need to see thoughts/tool calls in the response object
129
+ early_stopping_method="generate", # Stop if LLM generates a stop token or a final answer
130
  )
131
+ app_logger.info("AgentExecutor with Gemini agent created successfully.")
132
 
133
 
134
  # --- Getter Function for Streamlit App ---
135
  def get_agent_executor():
136
  """Returns the configured agent executor for Gemini."""
137
+ # Initialization happens above when the module is loaded.
138
+ # This function just returns the already created executor.
139
+ # A check for API key is good practice, though it would have failed earlier if not set.
140
+ if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
141
+ # This log might be redundant if LLM init failed, but good as a sanity check here.
142
+ app_logger.error("CRITICAL: GOOGLE_API_KEY (for Gemini) is not available at get_agent_executor call.")
143
+ raise ValueError("Google API Key for Gemini not configured. Agent cannot function.")
144
  return agent_executor
145
 
146
+ # --- Example Usage (for local testing of this agent.py file) ---
 
 
147
  if __name__ == "__main__":
148
+ if not (settings.GEMINI_API_KEY or os.getenv("GOOGLE_API_KEY")):
149
+ print("Please set your GOOGLE_API_KEY in .env file or as an environment variable.")
150
  else:
151
+ print("\nQuantum Health Navigator (Gemini Agent Test Console)")
152
+ print("Type 'exit' or 'quit' to stop.")
153
+ print("Example queries:")
154
+ print(" - What is hypertension?")
155
+ print(" - Lookup 'myocardial infarction' in UMLS.")
156
+ print(" - Search for 'diabetes mellitus type 2' in BioPortal using SNOMEDCT ontology.")
157
+ print(" - Optimize treatment for a patient (context will be simulated).")
158
+ print("-" * 30)
159
+
160
  executor = get_agent_executor()
161
+ current_chat_history_for_test = [] # List of HumanMessage, AIMessage
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
+ # Simulated patient context for testing the {patient_context} variable
164
+ test_patient_context_summary = (
165
+ "Age: 45; Gender: Male; Chief Complaint: Intermittent chest pain; "
166
+ "Key Medical History: Hyperlipidemia; Current Medications: Atorvastatin 20mg."
167
+ )
168
 
169
  while True:
170
+ user_input_str = input("\n👤 You: ")
171
+ if user_input_str.lower() in ["exit", "quit"]:
172
+ print("Exiting test console.")
173
  break
174
 
175
  try:
176
+ app_logger.info(f"__main__ test: Invoking agent with input: '{user_input_str}'")
177
  response = executor.invoke({
178
+ "input": user_input_str,
179
+ "chat_history": current_chat_history_for_test,
180
+ "patient_context": test_patient_context_summary # Passing the context
 
181
  })
182
 
183
+ ai_output_str = response.get('output', "Agent did not produce an output.")
184
+ print(f"🤖 Agent: {ai_output_str}")
185
 
186
+ current_chat_history_for_test.append(HumanMessage(content=user_input_str))
187
+ current_chat_history_for_test.append(AIMessage(content=ai_output_str))
188
 
189
  except Exception as e:
190
+ print(f"⚠️ Error during agent invocation: {e}")
191
+ app_logger.error(f"Error in __main__ agent test invocation: {e}", exc_info=True)