Cookbook

CrewAI tool

Wrap Krawl as a CrewAI tool — let an in-Crew analyst delegate deep, cited research without paying token costs in-loop.

A CrewAI agent can delegate deep-research work out of the main loop by calling a KrawlResearchTool that streams /research SSE and returns the final cited markdown.

Working file: docs/cookbook/crewai-tool.py.

Install

pip install crewai httpx
export KRAWL_API_KEY=krawl_demo_xxx
export OPENAI_API_KEY=sk-...        # CrewAI's default LLM

curl

The raw SSE stream the tool consumes:

curl -N -X POST https://api.krawl.sh/research \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $KRAWL_API_KEY" \
  -d '{"query": "Compare EigenLayer and Symbiotic", "mode": "deep"}'

The event: result SSE frame carries the final markdown field.

Python

import json, os, httpx
from crewai import Agent, Crew, Task
from crewai.tools import BaseTool

class KrawlResearchTool(BaseTool):
    name: str = "krawl_research"
    description: str = "Deep, cited research. Input: a single question."

    def _run(self, query: str) -> str:
        with httpx.stream(
            "POST",
            "https://api.krawl.sh/research",
            headers={
                "X-API-Key": os.environ["KRAWL_API_KEY"],
                "Content-Type": "application/json",
                "Accept": "text/event-stream",
            },
            json={"query": query, "mode": "deep"},
            timeout=600,
        ) as resp:
            event = None
            for line in resp.iter_lines():
                if not line:
                    continue
                if line.startswith("event: "):
                    event = line.removeprefix("event: ").strip()
                elif line.startswith("data: ") and event == "result":
                    return json.loads(line.removeprefix("data: ")).get("markdown", "")
        return ""

analyst = Agent(
    role="Senior research analyst",
    goal="Answer with cited primary sources.",
    backstory="You delegate factual lookups to krawl_research.",
    tools=[KrawlResearchTool()],
)
task = Task(
    description="Compare EigenLayer and Symbiotic in 2026.",
    expected_output="200-400 words with at least 5 [n] citations.",
    agent=analyst,
)
Crew(agents=[analyst], tasks=[task]).kickoff()

Node

CrewAI is Python-only. For Node, wrap the same /research SSE call in a LangChain Tool or a plain async function, and call it from any Node agent runtime:

async function krawlResearch(query: string): Promise<string> {
  const r = await fetch("https://api.krawl.sh/research", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.KRAWL_API_KEY!,
      "Content-Type": "application/json",
      Accept: "text/event-stream",
    },
    body: JSON.stringify({ query, mode: "deep" }),
  });
  const reader = r.body!.getReader();
  const dec = new TextDecoder();
  let buf = "", evt = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    for (const line of buf.split("\n")) {
      if (line.startsWith("event: ")) evt = line.slice(7).trim();
      if (line.startsWith("data: ") && evt === "result") {
        return JSON.parse(line.slice(6)).markdown ?? "";
      }
    }
    buf = buf.endsWith("\n") ? "" : buf.slice(buf.lastIndexOf("\n") + 1);
  }
  return "";
}

Notes

  • The tool blocks the calling agent for the entire run. For long jobs use POST /research/async instead and poll /tasks/{id}.
  • Set mode: "quick" (~10s) for cheap pre-flight checks and mode: "deep" (~90s) for the cited synthesis.
  • Source filtering, cost ceilings, output schemas, and webhooks are all supported on the same request body — see API Reference.

On this page