Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
c816ffa
1
Parent(s):
b843268
Upd gemini mcp server fallback
Browse files
app.py
CHANGED
|
@@ -277,6 +277,17 @@ async def get_mcp_session():
|
|
| 277 |
session = ClientSession(read, write)
|
| 278 |
await session.__aenter__()
|
| 279 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
# Store both the session and stdio context to keep them alive
|
| 281 |
global_mcp_session = session
|
| 282 |
global_mcp_stdio_ctx = stdio_ctx
|
|
@@ -293,24 +304,44 @@ async def get_mcp_session():
|
|
| 293 |
async def call_gemini_mcp(user_prompt: str, system_prompt: str = None, files: list = None, model: str = None, temperature: float = 0.2) -> str:
|
| 294 |
"""Call Gemini MCP generate_content tool"""
|
| 295 |
if not MCP_AVAILABLE:
|
|
|
|
| 296 |
return ""
|
| 297 |
|
| 298 |
try:
|
| 299 |
session = await get_mcp_session()
|
| 300 |
if session is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
return ""
|
| 302 |
|
| 303 |
# Find generate_content tool
|
| 304 |
-
tools = await session.list_tools()
|
| 305 |
generate_tool = None
|
| 306 |
for tool in tools.tools:
|
| 307 |
-
if "generate_content"
|
| 308 |
generate_tool = tool
|
| 309 |
logger.info(f"Found Gemini MCP tool: {tool.name}")
|
| 310 |
break
|
| 311 |
|
| 312 |
if not generate_tool:
|
| 313 |
-
logger.warning("Gemini MCP generate_content tool not found")
|
| 314 |
return ""
|
| 315 |
|
| 316 |
# Prepare arguments
|
|
@@ -326,6 +357,7 @@ async def call_gemini_mcp(user_prompt: str, system_prompt: str = None, files: li
|
|
| 326 |
if temperature is not None:
|
| 327 |
arguments["temperature"] = temperature
|
| 328 |
|
|
|
|
| 329 |
result = await session.call_tool(generate_tool.name, arguments=arguments)
|
| 330 |
|
| 331 |
# Parse result
|
|
@@ -333,6 +365,7 @@ async def call_gemini_mcp(user_prompt: str, system_prompt: str = None, files: li
|
|
| 333 |
for item in result.content:
|
| 334 |
if hasattr(item, 'text'):
|
| 335 |
return item.text.strip()
|
|
|
|
| 336 |
return ""
|
| 337 |
except Exception as e:
|
| 338 |
logger.error(f"Gemini MCP call error: {e}")
|
|
@@ -655,120 +688,95 @@ def translate_text(text: str, target_lang: str = "en", source_lang: str = None)
|
|
| 655 |
# Return original text if translation fails
|
| 656 |
return text
|
| 657 |
|
| 658 |
-
async def
|
| 659 |
-
"""Search web using MCP
|
| 660 |
if not MCP_AVAILABLE:
|
| 661 |
-
logger.warning("MCP not available
|
| 662 |
-
return
|
| 663 |
|
| 664 |
try:
|
| 665 |
-
#
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
|
| 671 |
-
#
|
| 672 |
-
|
| 673 |
-
tools = await session.list_tools()
|
| 674 |
-
except Exception as e:
|
| 675 |
-
logger.error(f"Failed to list MCP tools: {e}")
|
| 676 |
-
return search_web_fallback(query, max_results)
|
| 677 |
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
|
| 685 |
-
if
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
if web_content:
|
| 728 |
-
# Try to fetch full content for each result using MCP fetch tool
|
| 729 |
-
fetch_tool = None
|
| 730 |
-
for tool in tools.tools:
|
| 731 |
-
if "fetch" in tool.name.lower() or "scrape" in tool.name.lower() or "get" in tool.name.lower():
|
| 732 |
-
fetch_tool = tool
|
| 733 |
-
logger.info(f"Found MCP fetch tool: {tool.name}")
|
| 734 |
-
break
|
| 735 |
-
|
| 736 |
-
if fetch_tool:
|
| 737 |
-
for item in web_content[:3]: # Fetch content for top 3 results
|
| 738 |
-
if not item.get('url'):
|
| 739 |
-
continue
|
| 740 |
-
try:
|
| 741 |
-
fetch_result = await session.call_tool(
|
| 742 |
-
fetch_tool.name,
|
| 743 |
-
arguments={"url": item['url']}
|
| 744 |
-
)
|
| 745 |
-
if hasattr(fetch_result, 'content') and fetch_result.content:
|
| 746 |
-
for content_item in fetch_result.content:
|
| 747 |
-
if hasattr(content_item, 'text'):
|
| 748 |
-
# Extract text content
|
| 749 |
-
full_text = content_item.text
|
| 750 |
-
if len(full_text) > 1000:
|
| 751 |
-
full_text = full_text[:1000] + "..."
|
| 752 |
-
item['content'] = item.get('content', '') + "\n" + full_text[:500]
|
| 753 |
-
except Exception as e:
|
| 754 |
-
logger.debug(f"Could not fetch content for {item['url']}: {e}")
|
| 755 |
-
continue
|
| 756 |
-
|
| 757 |
-
if web_content:
|
| 758 |
-
logger.info(f"MCP search returned {len(web_content)} results")
|
| 759 |
-
return web_content
|
| 760 |
-
else:
|
| 761 |
-
logger.warning("MCP search returned no results, falling back to direct search")
|
| 762 |
-
return search_web_fallback(query, max_results)
|
| 763 |
-
else:
|
| 764 |
-
logger.warning("MCP search tool not found, falling back to direct search")
|
| 765 |
-
return search_web_fallback(query, max_results)
|
| 766 |
-
|
| 767 |
except Exception as e:
|
| 768 |
-
logger.error(f"MCP web search error: {e}
|
| 769 |
import traceback
|
| 770 |
logger.debug(traceback.format_exc())
|
| 771 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
|
| 773 |
def search_web_fallback(query: str, max_results: int = 5) -> list:
|
| 774 |
"""Fallback web search using DuckDuckGo directly (when MCP is not available)"""
|
|
|
|
| 277 |
session = ClientSession(read, write)
|
| 278 |
await session.__aenter__()
|
| 279 |
|
| 280 |
+
# Wait a bit for the server to fully initialize
|
| 281 |
+
await asyncio.sleep(0.5)
|
| 282 |
+
|
| 283 |
+
# Verify the session works by listing tools
|
| 284 |
+
try:
|
| 285 |
+
tools = await session.list_tools()
|
| 286 |
+
logger.info(f"MCP server initialized with {len(tools.tools)} tools")
|
| 287 |
+
except Exception as e:
|
| 288 |
+
logger.warning(f"Could not list tools immediately after session creation: {e}")
|
| 289 |
+
# Continue anyway, might work on first actual call
|
| 290 |
+
|
| 291 |
# Store both the session and stdio context to keep them alive
|
| 292 |
global_mcp_session = session
|
| 293 |
global_mcp_stdio_ctx = stdio_ctx
|
|
|
|
| 304 |
async def call_gemini_mcp(user_prompt: str, system_prompt: str = None, files: list = None, model: str = None, temperature: float = 0.2) -> str:
|
| 305 |
"""Call Gemini MCP generate_content tool"""
|
| 306 |
if not MCP_AVAILABLE:
|
| 307 |
+
logger.warning("MCP not available for Gemini call")
|
| 308 |
return ""
|
| 309 |
|
| 310 |
try:
|
| 311 |
session = await get_mcp_session()
|
| 312 |
if session is None:
|
| 313 |
+
logger.warning("Failed to get MCP session for Gemini call")
|
| 314 |
+
return ""
|
| 315 |
+
|
| 316 |
+
# Retry listing tools if it fails the first time
|
| 317 |
+
max_retries = 3
|
| 318 |
+
tools = None
|
| 319 |
+
for attempt in range(max_retries):
|
| 320 |
+
try:
|
| 321 |
+
tools = await session.list_tools()
|
| 322 |
+
break
|
| 323 |
+
except Exception as e:
|
| 324 |
+
if attempt < max_retries - 1:
|
| 325 |
+
logger.debug(f"Failed to list tools (attempt {attempt + 1}/{max_retries}), retrying...")
|
| 326 |
+
await asyncio.sleep(0.5 * (attempt + 1)) # Exponential backoff
|
| 327 |
+
else:
|
| 328 |
+
logger.error(f"Failed to list MCP tools after {max_retries} attempts: {e}")
|
| 329 |
+
return ""
|
| 330 |
+
|
| 331 |
+
if not tools or not hasattr(tools, 'tools'):
|
| 332 |
+
logger.error("Invalid tools response from MCP server")
|
| 333 |
return ""
|
| 334 |
|
| 335 |
# Find generate_content tool
|
|
|
|
| 336 |
generate_tool = None
|
| 337 |
for tool in tools.tools:
|
| 338 |
+
if tool.name == "generate_content" or "generate_content" in tool.name.lower():
|
| 339 |
generate_tool = tool
|
| 340 |
logger.info(f"Found Gemini MCP tool: {tool.name}")
|
| 341 |
break
|
| 342 |
|
| 343 |
if not generate_tool:
|
| 344 |
+
logger.warning(f"Gemini MCP generate_content tool not found. Available tools: {[t.name for t in tools.tools]}")
|
| 345 |
return ""
|
| 346 |
|
| 347 |
# Prepare arguments
|
|
|
|
| 357 |
if temperature is not None:
|
| 358 |
arguments["temperature"] = temperature
|
| 359 |
|
| 360 |
+
logger.debug(f"Calling Gemini MCP tool '{generate_tool.name}' with arguments: {list(arguments.keys())}")
|
| 361 |
result = await session.call_tool(generate_tool.name, arguments=arguments)
|
| 362 |
|
| 363 |
# Parse result
|
|
|
|
| 365 |
for item in result.content:
|
| 366 |
if hasattr(item, 'text'):
|
| 367 |
return item.text.strip()
|
| 368 |
+
logger.warning("Gemini MCP returned empty or invalid result")
|
| 369 |
return ""
|
| 370 |
except Exception as e:
|
| 371 |
logger.error(f"Gemini MCP call error: {e}")
|
|
|
|
| 688 |
# Return original text if translation fails
|
| 689 |
return text
|
| 690 |
|
| 691 |
+
async def search_web_gemini(query: str, max_results: int = 5) -> list:
|
| 692 |
+
"""Search web using Gemini MCP generate_content tool"""
|
| 693 |
if not MCP_AVAILABLE:
|
| 694 |
+
logger.warning("Gemini MCP not available for web search")
|
| 695 |
+
return []
|
| 696 |
|
| 697 |
try:
|
| 698 |
+
# Use Gemini MCP to search the web and get structured results
|
| 699 |
+
user_prompt = f"""Search the web for: "{query}"
|
| 700 |
+
|
| 701 |
+
Return the search results in JSON format with the following structure:
|
| 702 |
+
{{
|
| 703 |
+
"results": [
|
| 704 |
+
{{
|
| 705 |
+
"title": "Result title",
|
| 706 |
+
"url": "Result URL",
|
| 707 |
+
"content": "Brief summary or snippet of the content"
|
| 708 |
+
}}
|
| 709 |
+
]
|
| 710 |
+
}}
|
| 711 |
+
|
| 712 |
+
Return up to {max_results} most relevant results. Focus on medical/health information if applicable."""
|
| 713 |
|
| 714 |
+
# Use concise system prompt
|
| 715 |
+
system_prompt = "You are a web search assistant. Search the web and return structured JSON results with titles, URLs, and content summaries."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 716 |
|
| 717 |
+
result = await call_gemini_mcp(
|
| 718 |
+
user_prompt=user_prompt,
|
| 719 |
+
system_prompt=system_prompt,
|
| 720 |
+
model=GEMINI_MODEL, # Use full model for web search
|
| 721 |
+
temperature=0.3
|
| 722 |
+
)
|
| 723 |
|
| 724 |
+
if not result:
|
| 725 |
+
logger.warning("Gemini MCP returned empty search results")
|
| 726 |
+
return []
|
| 727 |
+
|
| 728 |
+
# Parse JSON response
|
| 729 |
+
try:
|
| 730 |
+
# Extract JSON from response
|
| 731 |
+
json_start = result.find('{')
|
| 732 |
+
json_end = result.rfind('}') + 1
|
| 733 |
+
if json_start >= 0 and json_end > json_start:
|
| 734 |
+
data = json.loads(result[json_start:json_end])
|
| 735 |
+
if isinstance(data, dict) and "results" in data:
|
| 736 |
+
web_content = []
|
| 737 |
+
for entry in data["results"][:max_results]:
|
| 738 |
+
web_content.append({
|
| 739 |
+
'title': entry.get('title', ''),
|
| 740 |
+
'url': entry.get('url', ''),
|
| 741 |
+
'content': entry.get('content', '')
|
| 742 |
+
})
|
| 743 |
+
logger.info(f"Gemini MCP search returned {len(web_content)} results")
|
| 744 |
+
return web_content
|
| 745 |
+
elif isinstance(data, list):
|
| 746 |
+
# Handle case where results are directly in a list
|
| 747 |
+
web_content = []
|
| 748 |
+
for entry in data[:max_results]:
|
| 749 |
+
web_content.append({
|
| 750 |
+
'title': entry.get('title', ''),
|
| 751 |
+
'url': entry.get('url', entry.get('href', '')),
|
| 752 |
+
'content': entry.get('content', entry.get('body', entry.get('snippet', '')))
|
| 753 |
+
})
|
| 754 |
+
logger.info(f"Gemini MCP search returned {len(web_content)} results")
|
| 755 |
+
return web_content
|
| 756 |
+
except json.JSONDecodeError as e:
|
| 757 |
+
logger.warning(f"Failed to parse Gemini search results as JSON: {e}")
|
| 758 |
+
# Fallback: treat as plain text result
|
| 759 |
+
return [{
|
| 760 |
+
'title': 'Web Search Result',
|
| 761 |
+
'url': '',
|
| 762 |
+
'content': result[:1000] # Limit content length
|
| 763 |
+
}]
|
| 764 |
+
|
| 765 |
+
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
except Exception as e:
|
| 767 |
+
logger.error(f"Gemini MCP web search error: {e}")
|
| 768 |
import traceback
|
| 769 |
logger.debug(traceback.format_exc())
|
| 770 |
+
return []
|
| 771 |
+
|
| 772 |
+
async def search_web_mcp(query: str, max_results: int = 5) -> list:
|
| 773 |
+
"""Search web using Gemini MCP (wrapper for compatibility)"""
|
| 774 |
+
results = await search_web_gemini(query, max_results)
|
| 775 |
+
if results:
|
| 776 |
+
return results
|
| 777 |
+
# Fallback to direct search only if Gemini MCP fails
|
| 778 |
+
logger.warning("Gemini MCP web search failed, falling back to direct search")
|
| 779 |
+
return search_web_fallback(query, max_results)
|
| 780 |
|
| 781 |
def search_web_fallback(query: str, max_results: int = 5) -> list:
|
| 782 |
"""Fallback web search using DuckDuckGo directly (when MCP is not available)"""
|