mirror of
				https://github.com/microsoft/autogen.git
				synced 2025-10-30 17:29:47 +00:00 
			
		
		
		
	 2cc8c73d3b
			
		
	
	
		2cc8c73d3b
		
			
		
	
	
	
	
		
			
			<!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. --> <!-- Please add a reviewer to the assignee section when you create a PR. If you don't have the access to it, we will shortly find a reviewer and assign them to your PR. --> ## Why are these changes needed? Fix termination UI in AGS, ensure it can be edited correctly <img width="1269" alt="image" src="https://github.com/user-attachments/assets/eaa7a92f-a1ea-4ab4-a679-2894ac441311" /> <img width="1273" alt="image" src="https://github.com/user-attachments/assets/6db81068-932f-4d4e-9512-279770c02bf2" /> <img width="1270" alt="image" src="https://github.com/user-attachments/assets/5ca9df7d-b968-46c9-9d62-becd78809273" /> <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number <!-- For example: "Closes #1234" --> Closes #5872 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [ ] I've made sure all auto checks have passed.
		
			
				
	
	
		
			219 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import json
 | |
| import os
 | |
| from typing import Dict, List, Optional
 | |
| from urllib.parse import urljoin
 | |
| 
 | |
| import html2text
 | |
| import httpx
 | |
| from autogen_core.code_executor import ImportFromModule
 | |
| from autogen_core.tools import FunctionTool
 | |
| from bs4 import BeautifulSoup
 | |
| 
 | |
| 
 | |
| async def bing_search(
 | |
|     query: str,
 | |
|     num_results: int = 3,
 | |
|     include_snippets: bool = True,
 | |
|     include_content: bool = True,
 | |
|     content_max_length: Optional[int] = 10000,
 | |
|     language: str = "en",
 | |
|     country: Optional[str] = None,
 | |
|     safe_search: str = "moderate",
 | |
|     response_filter: str = "webpages",
 | |
| ) -> List[Dict[str, str]]:
 | |
|     """
 | |
|     Perform a Bing search using the Bing Web Search API.
 | |
| 
 | |
|     Args:
 | |
|         query: Search query string
 | |
|         num_results: Number of results to return (max 50)
 | |
|         include_snippets: Include result snippets in output
 | |
|         include_content: Include full webpage content in markdown format
 | |
|         content_max_length: Maximum length of webpage content (if included)
 | |
|         language: Language code for search results (e.g., 'en', 'es', 'fr')
 | |
|         country: Optional market code for search results (e.g., 'us', 'uk')
 | |
|         safe_search: SafeSearch setting ('off', 'moderate', or 'strict')
 | |
|         response_filter: Type of results ('webpages', 'news', 'images', or 'videos')
 | |
| 
 | |
|     Returns:
 | |
|         List[Dict[str, str]]: List of search results
 | |
| 
 | |
|     Raises:
 | |
|         ValueError: If API credentials are invalid or request fails
 | |
|     """
 | |
|     # Get and validate API key
 | |
|     api_key = os.getenv("BING_SEARCH_KEY", "").strip()
 | |
| 
 | |
|     if not api_key:
 | |
|         raise ValueError(
 | |
|             "BING_SEARCH_KEY environment variable is not set. " "Please obtain an API key from Azure Portal."
 | |
|         )
 | |
| 
 | |
|     # Validate safe_search parameter
 | |
|     valid_safe_search = ["off", "moderate", "strict"]
 | |
|     if safe_search.lower() not in valid_safe_search:
 | |
|         raise ValueError(f"Invalid safe_search value. Must be one of: {', '.join(valid_safe_search)}")
 | |
| 
 | |
|     # Validate response_filter parameter
 | |
|     valid_filters = ["webpages", "news", "images", "videos"]
 | |
|     if response_filter.lower() not in valid_filters:
 | |
|         raise ValueError(f"Invalid response_filter value. Must be one of: {', '.join(valid_filters)}")
 | |
| 
 | |
|     async def fetch_page_content(url: str, max_length: Optional[int] = 50000) -> str:
 | |
|         """Helper function to fetch and convert webpage content to markdown"""
 | |
|         headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
 | |
| 
 | |
|         try:
 | |
|             async with httpx.AsyncClient() as client:
 | |
|                 response = await client.get(url, headers=headers, timeout=10)
 | |
|                 response.raise_for_status()
 | |
| 
 | |
|                 soup = BeautifulSoup(response.text, "html.parser")
 | |
| 
 | |
|                 # Remove script and style elements
 | |
|                 for script in soup(["script", "style"]):
 | |
|                     script.decompose()
 | |
| 
 | |
|                 # Convert relative URLs to absolute
 | |
|                 for tag in soup.find_all(["a", "img"]):
 | |
|                     if tag.get("href"):
 | |
|                         tag["href"] = urljoin(url, tag["href"])
 | |
|                     if tag.get("src"):
 | |
|                         tag["src"] = urljoin(url, tag["src"])
 | |
| 
 | |
|                 h2t = html2text.HTML2Text()
 | |
|                 h2t.body_width = 0
 | |
|                 h2t.ignore_images = False
 | |
|                 h2t.ignore_emphasis = False
 | |
|                 h2t.ignore_links = False
 | |
|                 h2t.ignore_tables = False
 | |
| 
 | |
|                 markdown = h2t.handle(str(soup))
 | |
| 
 | |
|                 if max_length and len(markdown) > max_length:
 | |
|                     markdown = markdown[:max_length] + "\n...(truncated)"
 | |
| 
 | |
|                 return markdown.strip()
 | |
| 
 | |
|         except Exception as e:
 | |
|             return f"Error fetching content: {str(e)}"
 | |
| 
 | |
|     # Build request headers and parameters
 | |
|     headers = {"Ocp-Apim-Subscription-Key": api_key, "Accept": "application/json"}
 | |
| 
 | |
|     params = {
 | |
|         "q": query,
 | |
|         "count": min(max(1, num_results), 50),
 | |
|         "mkt": f"{language}-{country.upper()}" if country else language,
 | |
|         "safeSearch": safe_search.capitalize(),
 | |
|         "responseFilter": response_filter,
 | |
|         "textFormat": "raw",
 | |
|     }
 | |
| 
 | |
|     # Make the request
 | |
|     try:
 | |
|         async with httpx.AsyncClient() as client:
 | |
|             response = await client.get(
 | |
|                 "https://api.bing.microsoft.com/v7.0/search", headers=headers, params=params, timeout=10
 | |
|             )
 | |
| 
 | |
|             # Handle common error cases
 | |
|             if response.status_code == 401:
 | |
|                 raise ValueError("Authentication failed. Please verify your Bing Search API key.")
 | |
|             elif response.status_code == 403:
 | |
|                 raise ValueError(
 | |
|                     "Access forbidden. This could mean:\n"
 | |
|                     "1. The API key is invalid\n"
 | |
|                     "2. The API key has expired\n"
 | |
|                     "3. You've exceeded your API quota"
 | |
|                 )
 | |
|             elif response.status_code == 429:
 | |
|                 raise ValueError("API quota exceeded. Please try again later.")
 | |
| 
 | |
|             response.raise_for_status()
 | |
|             data = response.json()
 | |
| 
 | |
|         # Process results based on response_filter
 | |
|         results = []
 | |
|         if response_filter == "webpages" and "webPages" in data:
 | |
|             items = data["webPages"]["value"]
 | |
|         elif response_filter == "news" and "news" in data:
 | |
|             items = data["news"]["value"]
 | |
|         elif response_filter == "images" and "images" in data:
 | |
|             items = data["images"]["value"]
 | |
|         elif response_filter == "videos" and "videos" in data:
 | |
|             items = data["videos"]["value"]
 | |
|         else:
 | |
|             if not any(key in data for key in ["webPages", "news", "images", "videos"]):
 | |
|                 return []  # No results found
 | |
|             raise ValueError(f"No {response_filter} results found in API response")
 | |
| 
 | |
|         # Extract relevant information based on result type
 | |
|         for item in items:
 | |
|             result = {"title": item.get("name", "")}
 | |
| 
 | |
|             if response_filter == "webpages":
 | |
|                 result["link"] = item.get("url", "")
 | |
|                 if include_snippets:
 | |
|                     result["snippet"] = item.get("snippet", "")
 | |
|                 if include_content:
 | |
|                     result["content"] = await fetch_page_content(result["link"], max_length=content_max_length)
 | |
| 
 | |
|             elif response_filter == "news":
 | |
|                 result["link"] = item.get("url", "")
 | |
|                 if include_snippets:
 | |
|                     result["snippet"] = item.get("description", "")
 | |
|                 result["date"] = item.get("datePublished", "")
 | |
|                 if include_content:
 | |
|                     result["content"] = await fetch_page_content(result["link"], max_length=content_max_length)
 | |
| 
 | |
|             elif response_filter == "images":
 | |
|                 result["link"] = item.get("contentUrl", "")
 | |
|                 result["thumbnail"] = item.get("thumbnailUrl", "")
 | |
|                 if include_snippets:
 | |
|                     result["snippet"] = item.get("description", "")
 | |
| 
 | |
|             elif response_filter == "videos":
 | |
|                 result["link"] = item.get("contentUrl", "")
 | |
|                 result["thumbnail"] = item.get("thumbnailUrl", "")
 | |
|                 if include_snippets:
 | |
|                     result["snippet"] = item.get("description", "")
 | |
|                 result["duration"] = item.get("duration", "")
 | |
| 
 | |
|             results.append(result)
 | |
| 
 | |
|         return results[:num_results]
 | |
| 
 | |
|     except httpx.HTTPError as e:
 | |
|         error_msg = str(e)
 | |
|         if "InvalidApiKey" in error_msg:
 | |
|             raise ValueError("Invalid API key. Please check your BING_SEARCH_KEY environment variable.") from e
 | |
|         elif "KeyExpired" in error_msg:
 | |
|             raise ValueError("API key has expired. Please generate a new key.") from e
 | |
|         else:
 | |
|             raise ValueError(f"Search request failed: {error_msg}") from e
 | |
|     except json.JSONDecodeError:
 | |
|         raise ValueError("Failed to parse API response. " "Please verify your API credentials and try again.") from None
 | |
|     except Exception as e:
 | |
|         raise ValueError(f"Unexpected error during search: {str(e)}") from e
 | |
| 
 | |
| 
 | |
| # Create the Bing search tool
 | |
| bing_search_tool = FunctionTool(
 | |
|     func=bing_search,
 | |
|     description="""
 | |
|     Perform Bing searches using the Bing Web Search API. Requires BING_SEARCH_KEY environment variable.
 | |
|     Supports web, news, image, and video searches.
 | |
|     See function documentation for detailed setup instructions.
 | |
|     """,
 | |
|     global_imports=[
 | |
|         ImportFromModule("typing", ("List", "Dict", "Optional")),
 | |
|         "os",
 | |
|         "httpx",
 | |
|         "json",
 | |
|         "html2text",
 | |
|         ImportFromModule("bs4", ("BeautifulSoup",)),
 | |
|         ImportFromModule("urllib.parse", ("urljoin",)),
 | |
|     ],
 | |
| )
 |