mgbam commited on
Commit
a6d04e1
·
verified ·
1 Parent(s): 0c0ddb5

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +166 -57
agent.py CHANGED
@@ -1,94 +1,203 @@
1
- from langchain_openai import ChatOpenAI
2
- from langchain.agents import AgentExecutor, create_openai_functions_agent
 
 
 
 
 
3
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
5
- from langchain_community.chat_message_histories import ChatMessageHistory # For in-memory history if not using DB for agent turn
6
 
7
  from tools import (
8
- GeminiTool, UMLSLookupTool, BioPortalLookupTool, QuantumTreatmentOptimizerTool
 
9
  )
10
  from config.settings import settings
11
  from services.logger import app_logger
12
 
13
- # Initialize LLM
14
- llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0.2, openai_api_key=settings.OPENAI_API_KEY)
15
- # If you have gpt-4 access and budget, it's generally better for agentic tasks:
16
- # llm = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0.2, openai_api_key=settings.OPENAI_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
 
19
- # Initialize Tools
 
20
  tools = [
21
  UMLSLookupTool(),
22
  BioPortalLookupTool(),
23
  QuantumTreatmentOptimizerTool(),
24
- # GeminiTool(), # Add if you want the agent to be able to call Gemini as a sub-LLM.
25
- # Be mindful of costs and latency.
26
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- # Agent Prompt
29
- # You can pull a prompt from Langchain Hub or define your own
30
- # e.g., prompt = hub.pull("hwchase17/openai-functions-agent")
31
  prompt = ChatPromptTemplate.from_messages([
32
- ("system", (
33
- "You are a helpful AI assistant for healthcare professionals, named 'Quantum Health Navigator'. "
34
- "Your goal is to assist with medical information lookup, treatment optimization queries, and general medical Q&A. "
35
- "When using tools, be precise with your inputs. "
36
- "Always cite the tool you used if its output is part of your response. "
37
- "If asked about treatment for a specific patient, you MUST use the 'quantum_treatment_optimizer' tool. "
38
- "Do not provide medical advice directly without tool usage for specific patient cases. "
39
- "For general medical knowledge, you can answer directly or use UMLS/BioPortal for definitions and codes."
40
- )),
41
- MessagesPlaceholder(variable_name="chat_history"),
42
- ("human", "{input}"),
43
- MessagesPlaceholder(variable_name="agent_scratchpad"),
44
  ])
 
 
 
 
 
 
 
 
 
 
45
 
46
- # Create Agent
47
- # This agent is optimized for OpenAI function calling.
48
- agent = create_openai_functions_agent(llm, tools, prompt)
49
 
50
- # Create Agent Executor
 
 
 
 
 
 
 
 
 
 
 
51
  agent_executor = AgentExecutor(
52
  agent=agent,
53
  tools=tools,
54
- verbose=True, # Set to False in production if too noisy
55
- handle_parsing_errors=True, # Gracefully handle errors if LLM output is not parsable
56
- # max_iterations=5, # Prevent runaway agents
 
 
57
  )
 
58
 
 
 
59
  def get_agent_executor():
60
- """Returns the configured agent executor."""
61
- if not settings.OPENAI_API_KEY:
62
- app_logger.error("OPENAI_API_KEY not set. Agent will not function.")
63
- raise ValueError("OpenAI API Key not configured. Agent cannot be initialized.")
 
 
64
  return agent_executor
65
 
66
- # Example usage (for testing, not part of Streamlit app directly here)
 
 
67
  if __name__ == "__main__":
68
- if not settings.OPENAI_API_KEY:
69
- print("Please set your OPENAI_API_KEY in .env file.")
70
  else:
 
71
  executor = get_agent_executor()
72
- chat_history = [] # In a real app, this comes from DB or session state
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  while True:
75
  user_input = input("You: ")
76
  if user_input.lower() in ["exit", "quit"]:
77
  break
78
 
79
- # Convert simple list history to LangChain Message objects for the agent
80
- langchain_chat_history = []
81
- for role, content in chat_history:
82
- if role == "user":
83
- langchain_chat_history.append(HumanMessage(content=content))
84
- elif role == "assistant":
85
- langchain_chat_history.append(AIMessage(content=content))
86
-
87
- response = executor.invoke({
88
- "input": user_input,
89
- "chat_history": langchain_chat_history
90
- })
91
-
92
- print(f"Agent: {response['output']}")
93
- chat_history.append(("user", user_input))
94
- chat_history.append(("assistant", response['output']))
 
 
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)