Scott Cogan
commited on
Commit
·
d541b87
1
Parent(s):
9aa8d3a
requirements update for llm compat
Browse files
app.py
CHANGED
|
@@ -286,148 +286,138 @@ class BasicAgent:
|
|
| 286 |
|
| 287 |
logger.info("BasicAgent initialized with fallback LLM support.")
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
for msg in messages:
|
| 297 |
-
log_message(msg, " ")
|
| 298 |
-
|
| 299 |
-
# Try primary LLM first
|
| 300 |
try:
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 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 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
"
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
return {
|
| 346 |
-
"messages": [AIMessage(content="
|
| 347 |
"next": END
|
| 348 |
}
|
| 349 |
else:
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
else:
|
| 356 |
-
|
| 357 |
-
wait_time = 60
|
| 358 |
-
logger.warning(f"Rate limit hit, waiting {wait_time} seconds before retry...")
|
| 359 |
-
time.sleep(wait_time)
|
| 360 |
-
raise # Re-raise to trigger retry
|
| 361 |
-
else:
|
| 362 |
-
raise
|
| 363 |
-
|
| 364 |
-
logger.info("\n=== Model Output ===")
|
| 365 |
-
log_message(response, " ")
|
| 366 |
-
|
| 367 |
-
if not response or not response.content:
|
| 368 |
-
logger.error("Empty response from model")
|
| 369 |
-
raise ValueError("Empty response from model")
|
| 370 |
-
|
| 371 |
-
# Check if the response contains a tool call
|
| 372 |
-
if hasattr(response, 'tool_calls') and response.tool_calls:
|
| 373 |
-
return {"messages": [response], "next": "tools"}
|
| 374 |
-
else:
|
| 375 |
-
# If no tool call, check if it's a final answer
|
| 376 |
-
content = response.content.strip()
|
| 377 |
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
content = content.replace("**Final Answer**: ", "").strip()
|
| 381 |
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
if float(content).is_integer():
|
| 386 |
-
content = str(int(float(content)))
|
| 387 |
|
| 388 |
-
# Check if the
|
| 389 |
-
if
|
| 390 |
-
return {"messages": [
|
| 391 |
else:
|
| 392 |
-
# If
|
| 393 |
-
|
| 394 |
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
"parameters": {
|
| 409 |
-
"type": "object",
|
| 410 |
-
"properties": {
|
| 411 |
-
"query": {
|
| 412 |
-
"type": "string",
|
| 413 |
-
"description": "The search query"
|
| 414 |
-
}
|
| 415 |
-
},
|
| 416 |
-
"required": ["query"]
|
| 417 |
-
}
|
| 418 |
-
}}]
|
| 419 |
-
)
|
| 420 |
-
return {"messages": [response], "next": "tools"}
|
| 421 |
-
except Exception as fallback_error:
|
| 422 |
-
logger.error(f"Fallback LLM also failed: {str(fallback_error)}")
|
| 423 |
-
return {"messages": [AIMessage(content="All LLM services are currently unavailable. Please try again later.")], "next": END}
|
| 424 |
else:
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
else:
|
| 427 |
-
logger.
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
|
| 432 |
def call_tools(self, state: AgentState) -> AgentState:
|
| 433 |
"""Call the tools based on the model's response."""
|
|
@@ -476,38 +466,17 @@ class BasicAgent:
|
|
| 476 |
}
|
| 477 |
|
| 478 |
# Process through the graph with retry logic
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
last_error = None
|
| 482 |
-
|
| 483 |
-
while retry_count < max_retries:
|
| 484 |
-
try:
|
| 485 |
-
logger.info(f"\n=== Attempt {retry_count + 1}/{max_retries} ===")
|
| 486 |
-
result = self.app.invoke(initial_state)
|
| 487 |
-
final_message = result["messages"][-1]
|
| 488 |
-
|
| 489 |
-
if isinstance(final_message, AIMessage) and final_message.content:
|
| 490 |
-
logger.info(f"\n=== Final Answer ===")
|
| 491 |
-
logger.info(f"Answer: {final_message.content}")
|
| 492 |
-
return final_message.content
|
| 493 |
-
else:
|
| 494 |
-
logger.error("Empty or invalid response")
|
| 495 |
-
raise ValueError("Empty or invalid response")
|
| 496 |
-
|
| 497 |
-
except Exception as e:
|
| 498 |
-
last_error = e
|
| 499 |
-
retry_count += 1
|
| 500 |
-
if "429" in str(e):
|
| 501 |
-
wait_time = 60 * retry_count
|
| 502 |
-
logger.warning(f"Rate limit hit, waiting {wait_time} seconds before retry {retry_count}/{max_retries}")
|
| 503 |
-
await asyncio.sleep(wait_time)
|
| 504 |
-
else:
|
| 505 |
-
logger.error(f"Error in processing, retry {retry_count}/{max_retries}: {str(e)}")
|
| 506 |
-
await asyncio.sleep(5)
|
| 507 |
-
|
| 508 |
-
logger.error(f"All retries failed. Last error: {str(last_error)}")
|
| 509 |
-
return "Unable to generate answer after multiple attempts"
|
| 510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
except Exception as e:
|
| 512 |
logger.error(f"Fatal error in agent: {str(e)}")
|
| 513 |
return f"Error: {str(e)}"
|
|
|
|
| 286 |
|
| 287 |
logger.info("BasicAgent initialized with fallback LLM support.")
|
| 288 |
|
| 289 |
+
def _call_model_with_retry(self, state: AgentState) -> AgentState:
|
| 290 |
+
"""Internal method to handle retries for model calls."""
|
| 291 |
+
max_retries = 3
|
| 292 |
+
retry_count = 0
|
| 293 |
+
last_error = None
|
| 294 |
+
|
| 295 |
+
while retry_count < max_retries:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
try:
|
| 297 |
+
messages = state["messages"]
|
| 298 |
+
logger.info("\n=== Model Input ===")
|
| 299 |
+
log_message(self.sys_msg, " ")
|
| 300 |
+
for msg in messages:
|
| 301 |
+
log_message(msg, " ")
|
| 302 |
+
|
| 303 |
+
# Try primary LLM first
|
| 304 |
+
try:
|
| 305 |
+
response = self.primary_llm.invoke(
|
| 306 |
+
[self.sys_msg] + messages,
|
| 307 |
+
tools=[{"type": "function", "function": {
|
| 308 |
+
"name": "google_search",
|
| 309 |
+
"description": "Search for information on the web",
|
| 310 |
+
"parameters": {
|
| 311 |
+
"type": "object",
|
| 312 |
+
"properties": {
|
| 313 |
+
"query": {
|
| 314 |
+
"type": "string",
|
| 315 |
+
"description": "The search query"
|
| 316 |
+
}
|
| 317 |
+
},
|
| 318 |
+
"required": ["query"]
|
| 319 |
+
}
|
| 320 |
+
}}]
|
| 321 |
+
)
|
| 322 |
+
except Exception as e:
|
| 323 |
+
error_str = str(e)
|
| 324 |
+
if "429" in error_str:
|
| 325 |
+
if "GenerateRequestsPerDayPerProjectPerModel-FreeTier" in error_str:
|
| 326 |
+
logger.warning("Daily quota limit reached for primary LLM, trying fallback")
|
| 327 |
+
if hasattr(self, 'fallback_llm') and self.fallback_llm is not None:
|
| 328 |
+
try:
|
| 329 |
+
response = self.fallback_llm.invoke(
|
| 330 |
+
[self.sys_msg] + messages,
|
| 331 |
+
tools=[{"type": "function", "function": {
|
| 332 |
+
"name": "google_search",
|
| 333 |
+
"description": "Search for information on the web",
|
| 334 |
+
"parameters": {
|
| 335 |
+
"type": "object",
|
| 336 |
+
"properties": {
|
| 337 |
+
"query": {
|
| 338 |
+
"type": "string",
|
| 339 |
+
"description": "The search query"
|
| 340 |
+
}
|
| 341 |
+
},
|
| 342 |
+
"required": ["query"]
|
| 343 |
+
}
|
| 344 |
+
}}]
|
| 345 |
+
)
|
| 346 |
+
logger.info("Successfully used fallback LLM")
|
| 347 |
+
except Exception as fallback_error:
|
| 348 |
+
logger.error(f"Fallback LLM also failed: {str(fallback_error)}")
|
| 349 |
+
return {
|
| 350 |
+
"messages": [AIMessage(content="All LLM services are currently unavailable. Please try again later.")],
|
| 351 |
+
"next": END
|
| 352 |
+
}
|
| 353 |
+
else:
|
| 354 |
+
logger.warning("No fallback LLM available")
|
| 355 |
return {
|
| 356 |
+
"messages": [AIMessage(content="I've reached my daily limit for processing requests. Please try again tomorrow or contact support for assistance.")],
|
| 357 |
"next": END
|
| 358 |
}
|
| 359 |
else:
|
| 360 |
+
# For other rate limits, wait and retry
|
| 361 |
+
wait_time = 60 * (retry_count + 1) # Exponential backoff
|
| 362 |
+
logger.warning(f"Rate limit hit, waiting {wait_time} seconds before retry...")
|
| 363 |
+
time.sleep(wait_time)
|
| 364 |
+
raise # Re-raise to trigger retry
|
| 365 |
else:
|
| 366 |
+
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
+
logger.info("\n=== Model Output ===")
|
| 369 |
+
log_message(response, " ")
|
|
|
|
| 370 |
|
| 371 |
+
if not response or not response.content:
|
| 372 |
+
logger.error("Empty response from model")
|
| 373 |
+
raise ValueError("Empty response from model")
|
|
|
|
|
|
|
| 374 |
|
| 375 |
+
# Check if the response contains a tool call
|
| 376 |
+
if hasattr(response, 'tool_calls') and response.tool_calls:
|
| 377 |
+
return {"messages": [response], "next": "tools"}
|
| 378 |
else:
|
| 379 |
+
# If no tool call, check if it's a final answer
|
| 380 |
+
content = response.content.strip()
|
| 381 |
|
| 382 |
+
# Clean up the content to ensure it's in the correct format
|
| 383 |
+
if content.startswith("**Final Answer**: "):
|
| 384 |
+
content = content.replace("**Final Answer**: ", "").strip()
|
| 385 |
+
|
| 386 |
+
# For numbers, ensure they're in the correct format
|
| 387 |
+
if content.replace(".", "").isdigit():
|
| 388 |
+
# Remove any decimal places for whole numbers
|
| 389 |
+
if float(content).is_integer():
|
| 390 |
+
content = str(int(float(content)))
|
| 391 |
+
|
| 392 |
+
# Check if the content is a valid final answer
|
| 393 |
+
if content.isdigit() or (content.startswith('[') and content.endswith(']')):
|
| 394 |
+
return {"messages": [AIMessage(content=content)], "next": END}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
else:
|
| 396 |
+
# If not a final answer, continue the conversation
|
| 397 |
+
return {"messages": [response], "next": "agent"}
|
| 398 |
+
|
| 399 |
+
except Exception as e:
|
| 400 |
+
last_error = e
|
| 401 |
+
retry_count += 1
|
| 402 |
+
logger.error(f"Error in processing, retry {retry_count}/{max_retries}: {str(e)}")
|
| 403 |
+
if retry_count < max_retries:
|
| 404 |
+
wait_time = 5 * retry_count # Simple backoff
|
| 405 |
+
time.sleep(wait_time)
|
| 406 |
else:
|
| 407 |
+
logger.error(f"All retries failed. Last error: {str(last_error)}")
|
| 408 |
+
return {
|
| 409 |
+
"messages": [AIMessage(content="Unable to generate answer after multiple attempts. Please try again later.")],
|
| 410 |
+
"next": END
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
return {
|
| 414 |
+
"messages": [AIMessage(content="Unable to generate answer after multiple attempts. Please try again later.")],
|
| 415 |
+
"next": END
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
def call_model(self, state: AgentState) -> AgentState:
|
| 419 |
+
"""Call the model to generate a response with retry logic and fallback support."""
|
| 420 |
+
return self._call_model_with_retry(state)
|
| 421 |
|
| 422 |
def call_tools(self, state: AgentState) -> AgentState:
|
| 423 |
"""Call the tools based on the model's response."""
|
|
|
|
| 466 |
}
|
| 467 |
|
| 468 |
# Process through the graph with retry logic
|
| 469 |
+
result = self.app.invoke(initial_state)
|
| 470 |
+
final_message = result["messages"][-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
|
| 472 |
+
if isinstance(final_message, AIMessage) and final_message.content:
|
| 473 |
+
logger.info(f"\n=== Final Answer ===")
|
| 474 |
+
logger.info(f"Answer: {final_message.content}")
|
| 475 |
+
return final_message.content
|
| 476 |
+
else:
|
| 477 |
+
logger.error("Empty or invalid response")
|
| 478 |
+
raise ValueError("Empty or invalid response")
|
| 479 |
+
|
| 480 |
except Exception as e:
|
| 481 |
logger.error(f"Fatal error in agent: {str(e)}")
|
| 482 |
return f"Error: {str(e)}"
|