intermediate10 min· python · langchain · langgraph · anthropic

SiftQ as a LangChain agent tool

Wrap SiftQ in @tool, hand it to a ReAct agent, watch it search autonomously.

Prerequisites

  • $pip install langchain langchain-anthropic langgraph requests
  • $export SIFTQ_API_KEY=mk-...
  • $export ANTHROPIC_API_KEY=sk-ant-...

How it works

When the user query is open-ended ("research X and write a summary"), you don't want to hard-code retrieval — you want the agent to decide when, how, and which scope to search. The pattern below uses LangGraph's prebuilt ReAct agent with SiftQ wrapped as a @tool. The model picks scope=scholar for paper questions, scope=news for fresh-event questions, scope=webpage for everything else.

The recipe

langchain_tool.py
import os
import requests
from langchain_core.tools import tool
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent

SIFTQ_KEY = os.environ["SIFTQ_API_KEY"]


@tool
def search_web(query: str, scope: str = "webpage") -> str:
    """Search the live web. Use scope=scholar for academic papers,
    scope=news for time-sensitive events, scope=webpage for general,
    scope=image for visual, scope=video for video, scope=podcast for audio.
    Returns top 5 results with title, URL, and snippet.
    """
    r = requests.post(
        "https://api.siftq.com/v1/search",
        headers={"Authorization": f"Bearer {SIFTQ_KEY}"},
        json={"q": query, "scope": scope, "size": "5", "conciseSnippet": True},
        timeout=15,
    )
    data = r.json()
    items = (
        data.get("webpages")
        or data.get("scholars")
        or data.get("podcasts")
        or data.get("videos")
        or []
    )
    return "\n\n".join(
        f"[{i + 1}] {h.get('title', '')}\n{h.get('snippet', '')}\nURL: {h.get('link', '')}"
        for i, h in enumerate(items)
    )


llm = ChatAnthropic(model="claude-sonnet-4-6")
agent = create_react_agent(llm, tools=[search_web])

result = agent.invoke({
    "messages": [(
        "user",
        "Find 3 recent (2024-2025) papers on RLHF reward hacking, "
        "then check if any news outlets covered them in the last month.",
    )],
})

for msg in result["messages"]:
    print(f"--- {msg.type} ---")
    print(msg.content)

Variations

Add a budget cap

Wrap the agent in a loop with a max_steps counter; abort if the agent calls search_web more than N times.

Memory across calls

Use LangGraph's MemorySaver to persist conversation state, letting the agent build on prior searches in a multi-turn session.

LlamaIndex equivalent

Replace with llama_index.retrievers.siftq.SiftQRetriever inside a RetrieverQueryEngine for the LlamaIndex stack.

Keep cooking

RAG with Claude in 30 lines
Web-grounded Q&A using SiftQ retrieval + Claude Sonnet, with inline citations.
Give Claude Desktop live web search via MCP
Zero code — drop a JSON config and Claude can search the web in every chat.
Test this query in the SiftQ API playground →