Scott Cogan
commited on
Commit
·
8cfdb1c
1
Parent(s):
6373484
requirements update for llm compat
Browse files
app.py
CHANGED
|
@@ -209,23 +209,24 @@ def log_message(message: BaseMessage, prefix: str = ""):
|
|
| 209 |
|
| 210 |
class BasicAgent:
|
| 211 |
def __init__(self):
|
| 212 |
-
# Initialize primary LLM (
|
| 213 |
-
self.primary_llm = ChatGoogleGenerativeAI(
|
| 214 |
-
model="gemini-2.5-flash-preview-05-20",
|
| 215 |
-
max_tokens=8192,
|
| 216 |
-
temperature=0,
|
| 217 |
-
convert_system_message_to_human=True # Enable system message conversion
|
| 218 |
-
)
|
| 219 |
-
|
| 220 |
-
# Initialize fallback LLM (if available)
|
| 221 |
-
self.fallback_llm = None
|
| 222 |
if os.getenv("OPENAI_API_KEY"):
|
| 223 |
from langchain_openai import ChatOpenAI
|
| 224 |
-
self.
|
| 225 |
model="gpt-3.5-turbo",
|
| 226 |
temperature=0,
|
| 227 |
max_tokens=4096
|
| 228 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
# Create tool executor
|
| 231 |
self.tools = {
|
|
@@ -301,42 +302,82 @@ class BasicAgent:
|
|
| 301 |
for msg in messages:
|
| 302 |
log_message(msg, " ")
|
| 303 |
|
| 304 |
-
# Try primary LLM first
|
| 305 |
try:
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
|
|
|
| 327 |
}
|
| 328 |
}]
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
-
|
| 332 |
try:
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
-
response = self.
|
| 339 |
-
|
| 340 |
tools=[genai_tool]
|
| 341 |
)
|
| 342 |
|
|
@@ -344,99 +385,28 @@ class BasicAgent:
|
|
| 344 |
raise ValueError("Invalid response format from Gemini")
|
| 345 |
|
| 346 |
# Check if response contains tool call
|
| 347 |
-
if
|
| 348 |
-
|
| 349 |
-
response
|
| 350 |
-
if not response or not hasattr(response, 'content'):
|
| 351 |
-
raise ValueError("Invalid response format from Gemini")
|
| 352 |
-
logger.info("Successfully used primary LLM without tools")
|
| 353 |
else:
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
except Exception as e:
|
| 357 |
-
error_str = str(e)
|
| 358 |
-
if "429" in error_str:
|
| 359 |
-
# Handle rate limit
|
| 360 |
-
logger.warning("Rate limit hit for Gemini, waiting before retry...")
|
| 361 |
-
time.sleep(60) # Wait 60 seconds before retry
|
| 362 |
-
raise
|
| 363 |
-
elif "list index out of range" in error_str:
|
| 364 |
-
# Try without tools if tool configuration fails
|
| 365 |
-
response = self.primary_llm.invoke(messages_with_system)
|
| 366 |
if not response or not hasattr(response, 'content'):
|
| 367 |
raise ValueError("Invalid response format from Gemini")
|
| 368 |
-
logger.info("Successfully used
|
| 369 |
-
else:
|
| 370 |
-
raise
|
| 371 |
-
|
| 372 |
-
except Exception as e:
|
| 373 |
-
error_str = str(e)
|
| 374 |
-
logger.error(f"Primary LLM error: {error_str}")
|
| 375 |
-
|
| 376 |
-
# Check if we should try fallback
|
| 377 |
-
if hasattr(self, 'fallback_llm') and self.fallback_llm is not None:
|
| 378 |
-
try:
|
| 379 |
-
logger.info("Attempting to use fallback LLM (OpenAI)")
|
| 380 |
-
# Add explicit tool usage prompt
|
| 381 |
-
messages_with_tool_prompt = [self.sys_msg] + messages + [
|
| 382 |
-
HumanMessage(content="Please use the google_search tool to find information about Mercedes Sosa's studio albums between 2000 and 2009.")
|
| 383 |
-
]
|
| 384 |
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
"description": "Search for information on the web",
|
| 393 |
-
"parameters": {
|
| 394 |
-
"type": "object",
|
| 395 |
-
"properties": {
|
| 396 |
-
"query": {
|
| 397 |
-
"type": "string",
|
| 398 |
-
"description": "The search query"
|
| 399 |
-
}
|
| 400 |
-
},
|
| 401 |
-
"required": ["query"]
|
| 402 |
-
}
|
| 403 |
-
}
|
| 404 |
-
}]
|
| 405 |
-
)
|
| 406 |
-
|
| 407 |
-
if not response or not hasattr(response, 'content'):
|
| 408 |
-
raise ValueError("Invalid response format from fallback LLM")
|
| 409 |
-
|
| 410 |
-
# Check if response contains tool call
|
| 411 |
-
if not hasattr(response, 'tool_calls') or not response.tool_calls:
|
| 412 |
-
# If no tool call, try without tools
|
| 413 |
-
response = self.fallback_llm.invoke(messages_with_tool_prompt)
|
| 414 |
-
if not response or not hasattr(response, 'content'):
|
| 415 |
-
raise ValueError("Invalid response format from fallback LLM")
|
| 416 |
-
logger.info("Successfully used fallback LLM without tools")
|
| 417 |
-
else:
|
| 418 |
-
logger.info("Successfully used fallback LLM with tools")
|
| 419 |
-
except Exception as fallback_error:
|
| 420 |
-
logger.error(f"Fallback LLM error: {str(fallback_error)}")
|
| 421 |
-
if "429" in str(fallback_error):
|
| 422 |
-
return {
|
| 423 |
-
"messages": [AIMessage(content="All LLM services are currently rate limited. Please try again later.")],
|
| 424 |
-
"next": END
|
| 425 |
-
}
|
| 426 |
-
else:
|
| 427 |
-
return {
|
| 428 |
-
"messages": [AIMessage(content="All LLM services are currently unavailable. Please try again later.")],
|
| 429 |
-
"next": END
|
| 430 |
-
}
|
| 431 |
-
else:
|
| 432 |
-
# If no fallback available or error not related to rate limits
|
| 433 |
-
if "429" in error_str:
|
| 434 |
-
wait_time = 60 * (retry_count + 1) # Exponential backoff
|
| 435 |
-
logger.warning(f"Rate limit hit, waiting {wait_time} seconds before retry...")
|
| 436 |
-
time.sleep(wait_time)
|
| 437 |
-
raise # Re-raise to trigger retry
|
| 438 |
else:
|
| 439 |
-
|
|
|
|
|
|
|
|
|
|
| 440 |
|
| 441 |
logger.info("\n=== Model Output ===")
|
| 442 |
log_message(response, " ")
|
|
@@ -445,41 +415,37 @@ class BasicAgent:
|
|
| 445 |
logger.error("Empty response from model")
|
| 446 |
raise ValueError("Empty response from model")
|
| 447 |
|
| 448 |
-
#
|
| 449 |
-
|
| 450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
else:
|
| 452 |
-
# If
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
# If the model is just acknowledging or explaining, prompt it to use the tool
|
| 456 |
-
if any(phrase in content.lower() for phrase in ["let me", "i'll", "i will", "sure", "okay", "alright"]):
|
| 457 |
-
logger.info("Model provided acknowledgment instead of tool call, prompting for search")
|
| 458 |
-
return {
|
| 459 |
-
"messages": messages + [
|
| 460 |
-
AIMessage(content="Please use the google_search tool to find the information."),
|
| 461 |
-
HumanMessage(content="Please search for the information using the google_search tool.")
|
| 462 |
-
],
|
| 463 |
-
"next": "agent"
|
| 464 |
-
}
|
| 465 |
|
| 466 |
-
# Clean up the content to ensure it's in the correct format
|
| 467 |
-
if content.startswith("**Final Answer**: "):
|
| 468 |
-
content = content.replace("**Final Answer**: ", "").strip()
|
| 469 |
-
|
| 470 |
-
# For numbers, ensure they're in the correct format
|
| 471 |
-
if content.replace(".", "").isdigit():
|
| 472 |
-
# Remove any decimal places for whole numbers
|
| 473 |
-
if float(content).is_integer():
|
| 474 |
-
content = str(int(float(content)))
|
| 475 |
-
|
| 476 |
-
# Check if the content is a valid final answer
|
| 477 |
-
if content.isdigit() or (content.startswith('[') and content.endswith(']')):
|
| 478 |
-
return {"messages": [AIMessage(content=content)], "next": END}
|
| 479 |
-
else:
|
| 480 |
-
# If not a final answer, continue the conversation
|
| 481 |
-
return {"messages": [response], "next": "agent"}
|
| 482 |
-
|
| 483 |
except Exception as e:
|
| 484 |
last_error = e
|
| 485 |
retry_count += 1
|
|
|
|
| 209 |
|
| 210 |
class BasicAgent:
|
| 211 |
def __init__(self):
|
| 212 |
+
# Initialize primary LLM (OpenAI)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
if os.getenv("OPENAI_API_KEY"):
|
| 214 |
from langchain_openai import ChatOpenAI
|
| 215 |
+
self.primary_llm = ChatOpenAI(
|
| 216 |
model="gpt-3.5-turbo",
|
| 217 |
temperature=0,
|
| 218 |
max_tokens=4096
|
| 219 |
)
|
| 220 |
+
else:
|
| 221 |
+
self.primary_llm = None
|
| 222 |
+
|
| 223 |
+
# Initialize fallback LLM (Gemini)
|
| 224 |
+
self.fallback_llm = ChatGoogleGenerativeAI(
|
| 225 |
+
model="gemini-2.5-flash-preview-05-20",
|
| 226 |
+
max_tokens=8192,
|
| 227 |
+
temperature=0,
|
| 228 |
+
convert_system_message_to_human=True # Enable system message conversion
|
| 229 |
+
)
|
| 230 |
|
| 231 |
# Create tool executor
|
| 232 |
self.tools = {
|
|
|
|
| 302 |
for msg in messages:
|
| 303 |
log_message(msg, " ")
|
| 304 |
|
| 305 |
+
# Try primary LLM first (OpenAI)
|
| 306 |
try:
|
| 307 |
+
if self.primary_llm is None:
|
| 308 |
+
raise ValueError("Primary LLM not initialized")
|
| 309 |
+
|
| 310 |
+
logger.info("Attempting to use primary LLM (OpenAI)")
|
| 311 |
+
# For OpenAI, we can use the system message directly
|
| 312 |
+
response = self.primary_llm.invoke(
|
| 313 |
+
[self.sys_msg] + messages,
|
| 314 |
+
tools=[{
|
| 315 |
+
"type": "function",
|
| 316 |
+
"function": {
|
| 317 |
+
"name": "google_search",
|
| 318 |
+
"description": "Search for information on the web",
|
| 319 |
+
"parameters": {
|
| 320 |
+
"type": "object",
|
| 321 |
+
"properties": {
|
| 322 |
+
"query": {
|
| 323 |
+
"type": "string",
|
| 324 |
+
"description": "The search query"
|
| 325 |
+
}
|
| 326 |
+
},
|
| 327 |
+
"required": ["query"]
|
| 328 |
+
}
|
| 329 |
}
|
| 330 |
}]
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
if not response or not hasattr(response, 'content'):
|
| 334 |
+
raise ValueError("Invalid response format from OpenAI")
|
| 335 |
+
|
| 336 |
+
# Check if response contains tool call
|
| 337 |
+
if hasattr(response, 'tool_calls') and response.tool_calls:
|
| 338 |
+
logger.info("Successfully used primary LLM with tools")
|
| 339 |
+
return {"messages": [response], "next": "tools"}
|
| 340 |
+
else:
|
| 341 |
+
# If no tool call, try without tools
|
| 342 |
+
response = self.primary_llm.invoke([self.sys_msg] + messages)
|
| 343 |
+
if not response or not hasattr(response, 'content'):
|
| 344 |
+
raise ValueError("Invalid response format from OpenAI")
|
| 345 |
+
logger.info("Successfully used primary LLM without tools")
|
| 346 |
+
|
| 347 |
+
except Exception as e:
|
| 348 |
+
error_str = str(e)
|
| 349 |
+
logger.error(f"Primary LLM error: {error_str}")
|
| 350 |
|
| 351 |
+
# Try fallback LLM (Gemini)
|
| 352 |
try:
|
| 353 |
+
logger.info("Attempting to use fallback LLM (Gemini)")
|
| 354 |
+
# Convert system message to human message for Gemini
|
| 355 |
+
if isinstance(self.sys_msg, SystemMessage):
|
| 356 |
+
system_content = f"System Instructions: {self.sys_msg.content}"
|
| 357 |
+
messages_with_system = [HumanMessage(content=system_content)] + messages
|
| 358 |
+
else:
|
| 359 |
+
messages_with_system = [self.sys_msg] + messages
|
| 360 |
+
|
| 361 |
+
# Create tool configuration for Gemini
|
| 362 |
+
genai_tool = {
|
| 363 |
+
"function_declarations": [{
|
| 364 |
+
"name": "google_search",
|
| 365 |
+
"description": "Search for information on the web",
|
| 366 |
+
"parameters": {
|
| 367 |
+
"type": "object",
|
| 368 |
+
"properties": {
|
| 369 |
+
"query": {
|
| 370 |
+
"type": "string",
|
| 371 |
+
"description": "The search query"
|
| 372 |
+
}
|
| 373 |
+
},
|
| 374 |
+
"required": ["query"]
|
| 375 |
+
}
|
| 376 |
+
}]
|
| 377 |
+
}
|
| 378 |
|
| 379 |
+
response = self.fallback_llm.invoke(
|
| 380 |
+
messages_with_system,
|
| 381 |
tools=[genai_tool]
|
| 382 |
)
|
| 383 |
|
|
|
|
| 385 |
raise ValueError("Invalid response format from Gemini")
|
| 386 |
|
| 387 |
# Check if response contains tool call
|
| 388 |
+
if hasattr(response, 'tool_calls') and response.tool_calls:
|
| 389 |
+
logger.info("Successfully used fallback LLM with tools")
|
| 390 |
+
return {"messages": [response], "next": "tools"}
|
|
|
|
|
|
|
|
|
|
| 391 |
else:
|
| 392 |
+
# If no tool call, try without tools
|
| 393 |
+
response = self.fallback_llm.invoke(messages_with_system)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
if not response or not hasattr(response, 'content'):
|
| 395 |
raise ValueError("Invalid response format from Gemini")
|
| 396 |
+
logger.info("Successfully used fallback LLM without tools")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
+
except Exception as fallback_error:
|
| 399 |
+
logger.error(f"Fallback LLM error: {str(fallback_error)}")
|
| 400 |
+
if "429" in str(fallback_error):
|
| 401 |
+
return {
|
| 402 |
+
"messages": [AIMessage(content="All LLM services are currently rate limited. Please try again later.")],
|
| 403 |
+
"next": END
|
| 404 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
else:
|
| 406 |
+
return {
|
| 407 |
+
"messages": [AIMessage(content="All LLM services are currently unavailable. Please try again later.")],
|
| 408 |
+
"next": END
|
| 409 |
+
}
|
| 410 |
|
| 411 |
logger.info("\n=== Model Output ===")
|
| 412 |
log_message(response, " ")
|
|
|
|
| 415 |
logger.error("Empty response from model")
|
| 416 |
raise ValueError("Empty response from model")
|
| 417 |
|
| 418 |
+
# Process the response content
|
| 419 |
+
content = response.content.strip()
|
| 420 |
+
|
| 421 |
+
# If the model is just acknowledging or explaining, prompt it to use the tool
|
| 422 |
+
if any(phrase in content.lower() for phrase in ["let me", "i'll", "i will", "sure", "okay", "alright"]):
|
| 423 |
+
logger.info("Model provided acknowledgment instead of tool call, prompting for search")
|
| 424 |
+
return {
|
| 425 |
+
"messages": messages + [
|
| 426 |
+
AIMessage(content="Please use the google_search tool to find the information."),
|
| 427 |
+
HumanMessage(content="Please search for the information using the google_search tool.")
|
| 428 |
+
],
|
| 429 |
+
"next": "agent"
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
# Clean up the content to ensure it's in the correct format
|
| 433 |
+
if content.startswith("**Final Answer**: "):
|
| 434 |
+
content = content.replace("**Final Answer**: ", "").strip()
|
| 435 |
+
|
| 436 |
+
# For numbers, ensure they're in the correct format
|
| 437 |
+
if content.replace(".", "").isdigit():
|
| 438 |
+
# Remove any decimal places for whole numbers
|
| 439 |
+
if float(content).is_integer():
|
| 440 |
+
content = str(int(float(content)))
|
| 441 |
+
|
| 442 |
+
# Check if the content is a valid final answer
|
| 443 |
+
if content.isdigit() or (content.startswith('[') and content.endswith(']')):
|
| 444 |
+
return {"messages": [AIMessage(content=content)], "next": END}
|
| 445 |
else:
|
| 446 |
+
# If not a final answer, continue the conversation
|
| 447 |
+
return {"messages": [response], "next": "agent"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
except Exception as e:
|
| 450 |
last_error = e
|
| 451 |
retry_count += 1
|