File size: 10,767 Bytes
a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 f194991 a6d04e1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# /home/user/app/agent.py
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.agents import AgentExecutor, create_structured_chat_agent # Using a more general agent
# If you want to try Gemini's native function calling (experimental and might require specific model versions):
# from langchain_google_genai.chat_models import GChatVertexAI # For Vertex AI
# from langchain_google_genai import HarmBlockThreshold, HarmCategory # For safety settings
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
# from langchain_community.chat_message_histories import ChatMessageHistory # Not used directly here
from tools import (
UMLSLookupTool, BioPortalLookupTool, QuantumTreatmentOptimizerTool
# GeminiTool might be redundant if the main LLM is Gemini, unless it's for a different Gemini model/task.
)
from config.settings import settings
from services.logger import app_logger
# --- Initialize LLM (Gemini) ---
# Ensure GOOGLE_API_KEY is set in your environment, or pass it directly:
# api_key=settings.GEMINI_API_KEY (if settings.GEMINI_API_KEY maps to GOOGLE_API_KEY)
try:
llm = ChatGoogleGenerativeAI(
model="gemini-pro", # Or "gemini-1.5-pro-latest" if available and preferred
temperature=0.3,
# google_api_key=settings.GEMINI_API_KEY, # Explicitly pass if needed
# convert_system_message_to_human=True, # Sometimes helpful for models not strictly adhering to system role
# safety_settings={ # Optional: configure safety settings
# HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
# HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
# }
)
app_logger.info("ChatGoogleGenerativeAI (Gemini) initialized successfully.")
except Exception as e:
app_logger.error(f"Failed to initialize ChatGoogleGenerativeAI: {e}", exc_info=True)
# Raise an error that can be caught by get_agent_executor to inform the user
raise ValueError(f"Gemini LLM initialization failed: {e}. Check API key and configurations.")
# --- Initialize Tools ---
# Ensure your tools' descriptions are very clear, especially for non-OpenAI function calling agents.
tools = [
UMLSLookupTool(),
BioPortalLookupTool(),
QuantumTreatmentOptimizerTool(),
]
app_logger.info(f"Tools initialized: {[tool.name for tool in tools]}")
# --- Agent Prompt (Adapted for a general structured chat agent) ---
# This prompt needs to guide the LLM to decide on tool use and format tool calls.
# It's more complex than relying on native function calling.
# We might need to instruct it to output a specific JSON structure for tool calls.
# For now, let's try a ReAct-style approach with create_structured_chat_agent.
# LangChain Hub has prompts for this: hub.pull("hwchase17/structured-chat-agent")
# Or we define a custom one:
# This is a simplified prompt. For robust tool use with Gemini (without native function calling),
# you'd often use a ReAct prompt or a prompt that guides it to output JSON for tool calls.
# `create_structured_chat_agent` is designed to work with models that can follow instructions
# to produce a structured output, often for tool usage.
# For Gemini, which now supports tool calling via its API directly (though LangChain integration might vary),
# we *could* try to structure the prompt for that if `ChatGoogleGenerativeAI` has good support.
# Let's assume a more general structured chat approach first if direct tool calling isn't as smooth
# as with OpenAI's function calling agents.
# If trying Gemini's newer tool calling features with LangChain (ensure your langchain-google-genai is up to date):
# You might be able to bind tools directly to the LLM and use a simpler agent structure.
# llm_with_tools = llm.bind_tools(tools) # This is the newer LangChain syntax
# Then create an agent that leverages this.
# For now, let's use `create_structured_chat_agent` which is more general.
# This prompt is similar to hwchase17/structured-chat-agent on Langsmith Hub
SYSTEM_PROMPT_TEXT = (
"You are 'Quantum Health Navigator', a helpful AI assistant for healthcare professionals. "
"Respond to the human as helpfully and accurately as possible. "
"You have access to the following tools:\n\n"
"{tools}\n\n" # This will be filled by the agent with tool names and descriptions
"To use a tool, you can use the following format:\n\n"
"```json\n"
"{{\n"
' "action": "tool_name",\n'
' "action_input": "input_to_tool"\n' # For tools with single string input
# Or for tools with structured input (like QuantumTreatmentOptimizerTool):
# ' "action_input": {{"arg1": "value1", "arg2": "value2"}}\n'
"}}\n"
"```\n\n"
"If you use a tool, the system will give you the observation from the tool. "
"Then you must respond to the human based on this observation and your knowledge. "
"If the human asks a question that doesn't require a tool, answer directly. "
"When asked about treatment optimization for a specific patient based on provided context, "
"you MUST use the 'quantum_treatment_optimizer' tool. "
"For general medical knowledge, you can answer directly or use UMLS/BioPortal. "
"Always cite the tool you used if its output is part of your final response. "
"Do not provide medical advice directly for specific patient cases without using the 'quantum_treatment_optimizer' tool. "
"Patient Context for this session (if provided by the user earlier): {patient_context}\n" # Added patient_context
"Begin!\n\n"
"Previous conversation history:\n"
"{chat_history}\n\n"
"New human question: {input}\n"
"{agent_scratchpad}" # Placeholder for agent's thoughts and tool outputs
)
prompt = ChatPromptTemplate.from_messages([
("system", SYSTEM_PROMPT_TEXT),
# MessagesPlaceholder(variable_name="chat_history"), # chat_history is now part of the system prompt
# ("human", "{input}"), # input is now part of the system prompt
MessagesPlaceholder(variable_name="agent_scratchpad"), # For structured chat agent, this is important
])
# Note: The `create_structured_chat_agent` expects `input` and `chat_history` to be implicitly handled
# or passed through `agent_scratchpad` based on how it formats things.
# The prompt structure might need adjustment based on the exact agent behavior.
# Often, for these agents, you pass "input" and "chat_history" to invoke, and the prompt template variables
# are {input}, {chat_history}, {agent_scratchpad}, {tools}, {tool_names}.
# For create_structured_chat_agent, the prompt should guide the LLM to produce
# either a final answer or a JSON blob for a tool call.
# The input variables for the prompt are typically 'input', 'chat_history', 'agent_scratchpad', 'tools', 'tool_names'.
# Our SYSTEM_PROMPT_TEXT includes these implicitly or explicitly.
# --- Create Agent ---
try:
# `create_structured_chat_agent` is designed for LLMs that can follow complex instructions
# and output structured data (like JSON for tool calls) when prompted to do so.
agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=prompt)
app_logger.info("Structured chat agent created successfully with Gemini.")
except Exception as e:
app_logger.error(f"Failed to create structured chat agent: {e}", exc_info=True)
raise ValueError(f"Agent creation failed: {e}")
# --- Create Agent Executor ---
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True, # Important for agents that parse LLM output for tool calls
# Example: "Could not parse LLM output: `...`" - then it can retry or return this error.
# max_iterations=7, # Good to prevent overly long chains
# return_intermediate_steps=True # Useful for debugging to see thought process
)
app_logger.info("AgentExecutor created successfully.")
# --- Getter Function for Streamlit App ---
def get_agent_executor():
"""Returns the configured agent executor for Gemini."""
# The llm and agent_executor are already initialized.
# We check for the API key here as a safeguard, though initialization would have failed earlier.
if not (settings.GEMINI_API_KEY or os.environ.get("GOOGLE_API_KEY")): # Check both setting and env var
app_logger.error("GOOGLE_API_KEY (for Gemini) not set. Agent will not function.")
raise ValueError("Google API Key for Gemini not configured. Agent cannot be initialized.")
return agent_executor
# --- Example Usage (for local testing) ---
import os # For checking GOOGLE_API_KEY from environment
if __name__ == "__main__":
if not (settings.GEMINI_API_KEY or os.environ.get("GOOGLE_API_KEY")):
print("Please set your GOOGLE_API_KEY (for Gemini) in .env file or environment.")
else:
print("Gemini Agent Test Console (type 'exit' or 'quit' to stop)")
executor = get_agent_executor()
# For structured chat agents, chat_history is often passed in the invoke call.
# The agent prompt includes {chat_history}.
current_chat_history = [] # List of HumanMessage, AIMessage
# Initial patient context (simulated for testing)
patient_context_for_test = {
"age": 35,
"gender": "Male",
"key_medical_history": "Type 2 Diabetes, Hypertension",
"current_medications": "Metformin, Lisinopril"
}
context_summary_parts_test = [f"{k.replace('_', ' ').title()}: {v}" for k, v in patient_context_for_test.items() if v]
patient_context_str_test = "; ".join(context_summary_parts_test) if context_summary_parts_test else "None provided."
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit"]:
break
try:
response = executor.invoke({
"input": user_input,
"chat_history": current_chat_history,
"patient_context": patient_context_str_test # Passing patient context
# `tools` and `tool_names` are usually handled by the agent constructor
})
ai_output = response.get('output', "No output from agent.")
print(f"Agent: {ai_output}")
current_chat_history.append(HumanMessage(content=user_input))
current_chat_history.append(AIMessage(content=ai_output))
except Exception as e:
print(f"Error invoking agent: {e}")
app_logger.error(f"Error in __main__ agent test: {e}", exc_info=True) |