| import base64 |
| import os |
| import io |
| import contextlib |
| import requests |
| from typing import TypedDict, Annotated |
| from langchain_core.messages import AnyMessage |
| from langgraph.graph import START, StateGraph, add_messages |
| from langgraph.prebuilt import ToolNode, tools_condition |
| from langchain_community.tools import tool, DuckDuckGoSearchRun |
| from langchain_community.utilities import DuckDuckGoSearchAPIWrapper |
| from langchain_google_genai import ChatGoogleGenerativeAI |
| |
| |
| from dotenv import load_dotenv |
| import time |
| import random |
|
|
| |
| API_URL = "https://agents-course-unit4-scoring.hf.space" |
| QUESTIONS_URL = f"{API_URL}/questions" |
| FILES_URL = f"{API_URL}/files" |
| SUBMIT_URL = f"{API_URL}/submit" |
| load_dotenv() |
|
|
| class AgentState(TypedDict): |
| messages: Annotated[list[AnyMessage], add_messages] |
| |
| |
| |
|
|
| def build_gemini_llm(): |
| if not os.environ.get("GOOGLE_API_KEY"): |
| raise ValueError("GOOGLE_API_KEY environment variable is not set.") |
| return ChatGoogleGenerativeAI(model = "gemini-3.7-flash", temperature = 0, max_output_tokens = 1025, include_thoughts=True) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| @tool |
| def wikipedia_search(query: str) -> str: |
| """ |
| Search Wikipedia for factual and encyclopedic information. |
| |
| Use this tool FIRST for: |
| - people |
| - historical events |
| - countries and places |
| - musicians, artists, movies, books |
| - scientific concepts |
| - biographies |
| - general factual knowledge |
| |
| Wikipedia is preferred for stable, well-known topics. |
| Args: |
| query: Keywords to search on Wikipedia. |
| """ |
|
|
| url = "https://en.wikipedia.org/w/api.php" |
| params = { |
| "action": "query", |
| "list": "search", |
| "srsearch": query, |
| "format": "json", |
| "srlimit": 3, |
| "utf8": 1, |
| } |
| headers = { |
| "User-Agent": "MyLangGraphAgent/1.0" |
| } |
|
|
| max_retries = 3 |
| for attempt in range(max_retries): |
| try: |
| response = requests.get( |
| url, |
| params=params, |
| headers=headers, |
| timeout=10, |
| ) |
| print( |
| f"Wikipedia status={response.status_code}, " |
| f"content-type={response.headers.get('content-type')}" |
| ) |
|
|
| response.raise_for_status() |
| |
| data = response.json() |
| results = data.get("query", {}).get("search", []) |
| if not results: |
| return f"No Wikipedia results found for '{query}'." |
| MAX_CONTENT_LENGTH = 3000 |
| return "\n\n---\n\n".join( |
| f"Title: {item['title']}\n" |
| f"Snippet: {item.get('snippet', '')}" |
| for item in results |
| ) |
| except requests.exceptions.RequestException as e: |
| print( |
| f"Wikipedia request failed " |
| f"attempt {attempt + 1}/{max_retries}: {e}" |
| ) |
| except requests.exceptions.JSONDecodeError: |
| print( |
| f"Wikipedia returned non-JSON response. " |
| f"Status={response.status_code}" |
| ) |
| print("Response preview:") |
| print(response.text[:500]) |
|
|
| if attempt < max_retries - 1: |
| delay = 2 ** attempt + random.random() |
| print(f"Retrying in {delay:.2f}s") |
| time.sleep(delay) |
| return ( |
| f"Wikipedia search temporarily failed for '{query}'. " |
| "Please use another search source." |
| ) |
|
|
| |
| search_wrapper = DuckDuckGoSearchAPIWrapper( |
| region="us-en", |
| backend="duckduckgo", |
| ) |
| search_ddgs = DuckDuckGoSearchRun( |
| api_wrapper=search_wrapper |
| ) |
|
|
| @tool |
| def search_web(query: str) -> str: |
| """ |
| Search the public web for information not suitable for Wikipedia. |
| |
| Use this tool for: |
| - recent news |
| - current events |
| - latest information |
| - official websites |
| - product information |
| - information that may have changed recently |
| |
| Do NOT use this tool as the first choice for stable |
| encyclopedic information that can be found on Wikipedia. |
| Args: |
| query: Keywords to search, only keywords and spaces. |
| """ |
| try: |
| result = search_ddgs.invoke(query) |
| if not result: |
| return f"No web results found for: {query}" |
| print("ddgs result:") |
| print(result) |
| return result |
| except Exception as e: |
| print(f"DuckDuckGo search failed for {query}: {e}") |
| return ( |
| f"Web search failed for query: {query}\n" |
| f"Error: {type(e).__name__}: {e}\n" |
| "Please try a different search query." |
| ) |
|
|
| model = build_gemini_llm() |
| tools = [ |
| search_web, |
| wikipedia_search |
| ] |
| model_with_tools = model.bind_tools(tools) |
|
|
| def assistant(state: AgentState): |
| response = model_with_tools.invoke(state["messages"]) |
| print("\n===== Thinking Section =====") |
| print(response.content[0].get("thinking").strip()) |
| print("===============================\n") |
| print("\n===== ASSISTANT RESPONSE =====") |
| print("TYPE:", type(response)) |
| print("CONTENT:", response.content) |
| print("TOOL CALLS:", response.tool_calls) |
| print("===============================\n") |
| return { |
| "messages": [response], |
| |
| |
| |
| } |
|
|
| builder = StateGraph(AgentState) |
|
|
| builder.add_node("assistant", assistant) |
| builder.add_node("tools", ToolNode(tools)) |
|
|
| builder.add_edge(START, "assistant") |
| builder.add_conditional_edges("assistant", tools_condition) |
| builder.add_edge("tools", "assistant") |
|
|
| graph = builder.compile() |
|
|