DuckDuckGo search node with duckduckgo-search
Every research pipeline starts with a query. We will use DuckDuckGo through the duckduckgo-search package. Note the two spellings: you install duckduckgo-search with pip, and you import it as duckduckgo_search with an underscore. It is free, it needs no API key, and it returns titles, URLs, and snippets in a clean shape we can feed straight into the next node.
# pip install duckduckgo-search
from duckduckgo_search import DDGS
# A raw call so you can see the shape of the data
ddgs = DDGS()
results = ddgs.text(
"What is the latest model from OpenAI?",
max_results=5,
)
for r in results:
print(r["title"])
print(r["href"])
print(r["body"][:120])
print("---")Each result is a dict with title, href, and body. The body is a snippet, not the full page. We scrape the page separately when wiring the fetch node.
def search_node(state: ResearchState) -> ResearchState:
"""Node for performing web search."""
print("Searching the web...")
state["current_step"] = "searching"
try:
ddgs = DDGS()
results = list(ddgs.text(
state["question"],
max_results=5,
region="wt-wt",
safesearch="moderate",
))
state["search_results"] = results
print(f"Found {len(results)} search results")
except Exception as e:
print(f"Search error: {e}")
state["search_results"] = []
return stateThe node signature is the LangGraph convention: take state, mutate it, return it. We wrap the DDGS call in a try block so a bad request does not kill the whole graph.
The "wt-wt" region code means worldwide with no localization. Without it, DuckDuckGo tries to guess your location and biases results toward that region. For a research tool you usually want the broadest possible result set, so worldwide is the safer default.
Quiz: Quiz
Loading practice…
AI prompt: Try it: pick good search queries
Loading practice…