""" Web Search Specialist Agent Node ================================= Performs free external web search using DuckDuckGo search integration. Summarizes search results with clickable markdown URLs. """ from __future__ import annotations import structlog from agents.state import CopilotState logger = structlog.get_logger(__name__) def duckduckgo_search(query: str, max_results: int = 5) -> list[dict]: """Execute free DuckDuckGo search using duckduckgo_search library.""" try: from duckduckgo_search import DDGS with DDGS() as ddgs: results = list(ddgs.text(query, max_results=max_results)) return [ { "title": r.get("title", "Web Result"), "link": r.get("href", ""), "snippet": r.get("body", "") } for r in results ] except Exception as e: logger.warning("DuckDuckGo search failed or rate-limited", error=str(e)) return [] async def web_node(state: CopilotState) -> CopilotState: """ Web Search Specialist Node. Searches the public web for real-time information. """ query = state.get("query", "") logger.info("Web Agent executing search", query=query) try: results = duckduckgo_search(query, max_results=4) chunks = [] citations = [] if results: context_text = "### Web Search Results:\n" for r in results: context_text += f"- [{r['title']}]({r['link']}): {r['snippet']}\n" citations.append({ "document_id": "web_search", "document_name": r["title"], "chunk_text": r["snippet"], "score": 0.9, "doc_type": "web" }) chunks.append({ "document_id": "web_search_summary", "document_name": "DuckDuckGo Search", "text": context_text, "score": 1.0, "doc_type": "web" }) else: chunks.append({ "document_id": "web_search_none", "document_name": "DuckDuckGo Search", "text": "Web search returned no external results for this query.", "score": 0.0, "doc_type": "web" }) state["retrieved_chunks"] = chunks state["citations"] = citations state["active_agent"] = "web" outputs = state.get("agent_outputs", []) outputs.append({ "agent_name": "web", "content": f"Performed web search. Found {len(results)} results.", "sources": citations }) state["agent_outputs"] = outputs except Exception as e: logger.error("Web Agent error", error=str(e)) state["error"] = f"Web Search Error: {str(e)}" state["retrieved_chunks"] = [{ "document_name": "Web Search Error", "text": f"Web search encountered an issue: {str(e)}" }] return state