Spaces:
Running
on
Zero
Running
on
Zero
Commit
Β·
8bafa0f
1
Parent(s):
0e45c9f
Upd agent mcp fallback
Browse files
app.py
CHANGED
|
@@ -277,25 +277,36 @@ async def get_mcp_session():
|
|
| 277 |
session = ClientSession(read, write)
|
| 278 |
await session.__aenter__()
|
| 279 |
|
| 280 |
-
# Wait
|
| 281 |
# The server needs time to start up and be ready
|
| 282 |
-
|
|
|
|
| 283 |
|
| 284 |
# Verify the session works by listing tools with retries
|
| 285 |
-
max_init_retries =
|
|
|
|
| 286 |
for init_attempt in range(max_init_retries):
|
| 287 |
try:
|
| 288 |
tools = await session.list_tools()
|
| 289 |
-
if tools and hasattr(tools, 'tools'):
|
| 290 |
-
logger.info(f"MCP server initialized with {len(tools.tools)} tools: {[t.name for t in tools.tools]}")
|
|
|
|
| 291 |
break
|
| 292 |
except Exception as e:
|
| 293 |
if init_attempt < max_init_retries - 1:
|
| 294 |
-
|
| 295 |
-
|
|
|
|
| 296 |
else:
|
| 297 |
-
logger.
|
| 298 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
# Store both the session and stdio context to keep them alive
|
| 301 |
global_mcp_session = session
|
|
@@ -323,18 +334,23 @@ async def call_agent(user_prompt: str, system_prompt: str = None, files: list =
|
|
| 323 |
return ""
|
| 324 |
|
| 325 |
# Retry listing tools if it fails the first time
|
| 326 |
-
|
|
|
|
| 327 |
tools = None
|
| 328 |
for attempt in range(max_retries):
|
| 329 |
try:
|
| 330 |
tools = await session.list_tools()
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
| 332 |
except Exception as e:
|
| 333 |
if attempt < max_retries - 1:
|
| 334 |
-
|
| 335 |
-
|
|
|
|
| 336 |
else:
|
| 337 |
-
logger.error(f"Failed to list MCP tools after {max_retries} attempts: {e}")
|
| 338 |
return ""
|
| 339 |
|
| 340 |
if not tools or not hasattr(tools, 'tools'):
|
|
@@ -366,7 +382,7 @@ async def call_agent(user_prompt: str, system_prompt: str = None, files: list =
|
|
| 366 |
if temperature is not None:
|
| 367 |
arguments["temperature"] = temperature
|
| 368 |
|
| 369 |
-
logger.
|
| 370 |
result = await session.call_tool(generate_tool.name, arguments=arguments)
|
| 371 |
|
| 372 |
# Parse result
|
|
@@ -793,11 +809,13 @@ async def search_web_mcp(query: str, max_results: int = 5) -> list:
|
|
| 793 |
# First try to use a dedicated web search MCP tool (like DuckDuckGo MCP server)
|
| 794 |
results = await search_web_mcp_tool(query, max_results)
|
| 795 |
if results:
|
|
|
|
| 796 |
return results
|
| 797 |
|
| 798 |
# If no web search MCP tool available, use direct search (ddgs)
|
| 799 |
-
#
|
| 800 |
-
|
|
|
|
| 801 |
return search_web_fallback(query, max_results)
|
| 802 |
|
| 803 |
def search_web_fallback(query: str, max_results: int = 5) -> list:
|
|
@@ -1034,9 +1052,10 @@ def autonomous_reasoning(query: str, history: list) -> dict:
|
|
| 1034 |
"""
|
| 1035 |
Autonomous reasoning: Analyze query complexity, intent, and information needs.
|
| 1036 |
Returns reasoning analysis with query type, complexity, and required information sources.
|
|
|
|
| 1037 |
"""
|
| 1038 |
if not MCP_AVAILABLE:
|
| 1039 |
-
logger.warning("Gemini MCP not available for reasoning, using fallback")
|
| 1040 |
# Fallback reasoning
|
| 1041 |
return {
|
| 1042 |
"query_type": "general_info",
|
|
@@ -1048,19 +1067,35 @@ def autonomous_reasoning(query: str, history: list) -> dict:
|
|
| 1048 |
}
|
| 1049 |
|
| 1050 |
try:
|
|
|
|
| 1051 |
loop = asyncio.get_event_loop()
|
| 1052 |
if loop.is_running():
|
| 1053 |
try:
|
| 1054 |
import nest_asyncio
|
| 1055 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1056 |
except Exception as e:
|
| 1057 |
-
logger.error(f"Error in nested async reasoning: {e}")
|
| 1058 |
else:
|
| 1059 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1060 |
except Exception as e:
|
| 1061 |
-
logger.error(f"Gemini MCP reasoning error: {e}")
|
|
|
|
|
|
|
| 1062 |
|
| 1063 |
-
# Fallback reasoning
|
|
|
|
| 1064 |
return {
|
| 1065 |
"query_type": "general_info",
|
| 1066 |
"complexity": "moderate",
|
|
@@ -1559,16 +1594,24 @@ def stream_chat(
|
|
| 1559 |
web_sources = []
|
| 1560 |
web_urls = [] # Store URLs for citations
|
| 1561 |
if final_use_web_search:
|
| 1562 |
-
logger.info("π Performing web search
|
| 1563 |
web_results = search_web(message, max_results=5)
|
| 1564 |
if web_results:
|
| 1565 |
-
logger.info(f"π
|
| 1566 |
web_summary = summarize_web_content(web_results, message)
|
| 1567 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1568 |
web_sources = [r['title'] for r in web_results[:3]]
|
| 1569 |
# Extract unique URLs for citations
|
| 1570 |
web_urls = [r.get('url', '') for r in web_results if r.get('url')]
|
| 1571 |
-
logger.info(f"Web search completed
|
|
|
|
|
|
|
| 1572 |
|
| 1573 |
# Build final context
|
| 1574 |
context_parts = []
|
|
|
|
| 277 |
session = ClientSession(read, write)
|
| 278 |
await session.__aenter__()
|
| 279 |
|
| 280 |
+
# Wait for the server to fully initialize
|
| 281 |
# The server needs time to start up and be ready
|
| 282 |
+
# Increased wait time and retries for better reliability
|
| 283 |
+
await asyncio.sleep(5.0)
|
| 284 |
|
| 285 |
# Verify the session works by listing tools with retries
|
| 286 |
+
max_init_retries = 10
|
| 287 |
+
tools_listed = False
|
| 288 |
for init_attempt in range(max_init_retries):
|
| 289 |
try:
|
| 290 |
tools = await session.list_tools()
|
| 291 |
+
if tools and hasattr(tools, 'tools') and len(tools.tools) > 0:
|
| 292 |
+
logger.info(f"β
MCP server initialized with {len(tools.tools)} tools: {[t.name for t in tools.tools]}")
|
| 293 |
+
tools_listed = True
|
| 294 |
break
|
| 295 |
except Exception as e:
|
| 296 |
if init_attempt < max_init_retries - 1:
|
| 297 |
+
wait_time = 1.0 * (init_attempt + 1) # Progressive wait: 1s, 2s, 3s...
|
| 298 |
+
logger.debug(f"Initialization attempt {init_attempt + 1}/{max_init_retries} failed, waiting {wait_time}s before retry...")
|
| 299 |
+
await asyncio.sleep(wait_time)
|
| 300 |
else:
|
| 301 |
+
logger.error(f"β Could not list tools after {max_init_retries} attempts: {e}")
|
| 302 |
+
# Don't continue - if we can't list tools, the session is not usable
|
| 303 |
+
await session.__aexit__(None, None, None)
|
| 304 |
+
await stdio_ctx.__aexit__(None, None, None)
|
| 305 |
+
return None
|
| 306 |
+
|
| 307 |
+
if not tools_listed:
|
| 308 |
+
logger.error("MCP server failed to initialize - tools could not be listed")
|
| 309 |
+
return None
|
| 310 |
|
| 311 |
# Store both the session and stdio context to keep them alive
|
| 312 |
global_mcp_session = session
|
|
|
|
| 334 |
return ""
|
| 335 |
|
| 336 |
# Retry listing tools if it fails the first time
|
| 337 |
+
# Use more retries and longer waits since MCP server might need time
|
| 338 |
+
max_retries = 5
|
| 339 |
tools = None
|
| 340 |
for attempt in range(max_retries):
|
| 341 |
try:
|
| 342 |
tools = await session.list_tools()
|
| 343 |
+
if tools and hasattr(tools, 'tools') and len(tools.tools) > 0:
|
| 344 |
+
break
|
| 345 |
+
else:
|
| 346 |
+
raise ValueError("Empty tools list")
|
| 347 |
except Exception as e:
|
| 348 |
if attempt < max_retries - 1:
|
| 349 |
+
wait_time = 1.0 * (attempt + 1) # Progressive wait
|
| 350 |
+
logger.debug(f"Failed to list tools (attempt {attempt + 1}/{max_retries}), waiting {wait_time}s...")
|
| 351 |
+
await asyncio.sleep(wait_time)
|
| 352 |
else:
|
| 353 |
+
logger.error(f"β Failed to list MCP tools after {max_retries} attempts: {e}")
|
| 354 |
return ""
|
| 355 |
|
| 356 |
if not tools or not hasattr(tools, 'tools'):
|
|
|
|
| 382 |
if temperature is not None:
|
| 383 |
arguments["temperature"] = temperature
|
| 384 |
|
| 385 |
+
logger.info(f"π§ Calling Gemini MCP tool '{generate_tool.name}' for: {user_prompt[:100]}...")
|
| 386 |
result = await session.call_tool(generate_tool.name, arguments=arguments)
|
| 387 |
|
| 388 |
# Parse result
|
|
|
|
| 809 |
# First try to use a dedicated web search MCP tool (like DuckDuckGo MCP server)
|
| 810 |
results = await search_web_mcp_tool(query, max_results)
|
| 811 |
if results:
|
| 812 |
+
logger.info(f"β
Web search via MCP tool: found {len(results)} results")
|
| 813 |
return results
|
| 814 |
|
| 815 |
# If no web search MCP tool available, use direct search (ddgs)
|
| 816 |
+
# Note: Gemini MCP doesn't have web search capability, so we use direct API
|
| 817 |
+
# The results will then be summarized using Gemini MCP
|
| 818 |
+
logger.info("βΉοΈ No web search MCP tool found, using direct DuckDuckGo search (results will be summarized with Gemini MCP)")
|
| 819 |
return search_web_fallback(query, max_results)
|
| 820 |
|
| 821 |
def search_web_fallback(query: str, max_results: int = 5) -> list:
|
|
|
|
| 1052 |
"""
|
| 1053 |
Autonomous reasoning: Analyze query complexity, intent, and information needs.
|
| 1054 |
Returns reasoning analysis with query type, complexity, and required information sources.
|
| 1055 |
+
Uses Gemini MCP for reasoning.
|
| 1056 |
"""
|
| 1057 |
if not MCP_AVAILABLE:
|
| 1058 |
+
logger.warning("β οΈ Gemini MCP not available for reasoning, using fallback")
|
| 1059 |
# Fallback reasoning
|
| 1060 |
return {
|
| 1061 |
"query_type": "general_info",
|
|
|
|
| 1067 |
}
|
| 1068 |
|
| 1069 |
try:
|
| 1070 |
+
logger.info("π€ Using Gemini MCP for autonomous reasoning...")
|
| 1071 |
loop = asyncio.get_event_loop()
|
| 1072 |
if loop.is_running():
|
| 1073 |
try:
|
| 1074 |
import nest_asyncio
|
| 1075 |
+
reasoning = nest_asyncio.run(autonomous_reasoning_gemini(query))
|
| 1076 |
+
if reasoning and reasoning.get("query_type") != "general_info": # Check if we got real reasoning
|
| 1077 |
+
logger.info(f"β
Gemini MCP reasoning successful: {reasoning.get('query_type')}, complexity: {reasoning.get('complexity')}")
|
| 1078 |
+
return reasoning
|
| 1079 |
+
else:
|
| 1080 |
+
logger.warning("β οΈ Gemini MCP returned fallback reasoning, using it anyway")
|
| 1081 |
+
return reasoning
|
| 1082 |
except Exception as e:
|
| 1083 |
+
logger.error(f"β Error in nested async reasoning: {e}")
|
| 1084 |
else:
|
| 1085 |
+
reasoning = loop.run_until_complete(autonomous_reasoning_gemini(query))
|
| 1086 |
+
if reasoning and reasoning.get("query_type") != "general_info":
|
| 1087 |
+
logger.info(f"β
Gemini MCP reasoning successful: {reasoning.get('query_type')}, complexity: {reasoning.get('complexity')}")
|
| 1088 |
+
return reasoning
|
| 1089 |
+
else:
|
| 1090 |
+
logger.warning("β οΈ Gemini MCP returned fallback reasoning, using it anyway")
|
| 1091 |
+
return reasoning
|
| 1092 |
except Exception as e:
|
| 1093 |
+
logger.error(f"β Gemini MCP reasoning error: {e}")
|
| 1094 |
+
import traceback
|
| 1095 |
+
logger.debug(traceback.format_exc())
|
| 1096 |
|
| 1097 |
+
# Fallback reasoning only if all attempts failed
|
| 1098 |
+
logger.warning("β οΈ Falling back to default reasoning")
|
| 1099 |
return {
|
| 1100 |
"query_type": "general_info",
|
| 1101 |
"complexity": "moderate",
|
|
|
|
| 1594 |
web_sources = []
|
| 1595 |
web_urls = [] # Store URLs for citations
|
| 1596 |
if final_use_web_search:
|
| 1597 |
+
logger.info("π Performing web search...")
|
| 1598 |
web_results = search_web(message, max_results=5)
|
| 1599 |
if web_results:
|
| 1600 |
+
logger.info(f"π Found {len(web_results)} web search results, now summarizing with Gemini MCP...")
|
| 1601 |
web_summary = summarize_web_content(web_results, message)
|
| 1602 |
+
if web_summary and len(web_summary) > 50: # Check if we got a real summary
|
| 1603 |
+
logger.info(f"β
Gemini MCP summarization successful ({len(web_summary)} chars)")
|
| 1604 |
+
web_context = f"\n\nAdditional Web Sources:\n{web_summary}"
|
| 1605 |
+
else:
|
| 1606 |
+
logger.warning("β οΈ Gemini MCP summarization failed or returned empty, using raw results")
|
| 1607 |
+
# Fallback: use first result's content
|
| 1608 |
+
web_context = f"\n\nAdditional Web Sources:\n{web_results[0].get('content', '')[:500]}"
|
| 1609 |
web_sources = [r['title'] for r in web_results[:3]]
|
| 1610 |
# Extract unique URLs for citations
|
| 1611 |
web_urls = [r.get('url', '') for r in web_results if r.get('url')]
|
| 1612 |
+
logger.info(f"β
Web search completed: {len(web_results)} results, summarized with Gemini MCP")
|
| 1613 |
+
else:
|
| 1614 |
+
logger.warning("β οΈ Web search returned no results")
|
| 1615 |
|
| 1616 |
# Build final context
|
| 1617 |
context_parts = []
|