diff --git a/integrations/README.md b/integrations/README.md index d8ed601..ada514d 100644 --- a/integrations/README.md +++ b/integrations/README.md @@ -5,4 +5,5 @@ Examples for adding Opik to a specific framework or library. Each folder covers | Integration | Description | |---|---| | [google_adk/](./google_adk/) | Google ADK — Trace an Agentic RAG router with Opik | +| [haystack/](./haystack/) | Haystack — Trace a multi-agent web-search pipeline with Opik | | [otel/](./otel/) | OpenTelemetry — send OTel spans to Opik via OTLP | diff --git a/integrations/haystack/README.md b/integrations/haystack/README.md new file mode 100644 index 0000000..37ce101 --- /dev/null +++ b/integrations/haystack/README.md @@ -0,0 +1,7 @@ +# Haystack + Opik + +Examples for tracing [Haystack](https://haystack.deepset.ai/) pipelines and Agents with Opik. + +| Example | Description | +|---|---| +| [multi_agent_web_search](./multi_agent_web_search/) | Trace a coordinator -> scout multi-agent web-search chain with Opik and Haystack pipeline | diff --git a/integrations/haystack/multi_agent_web_search/.env.example b/integrations/haystack/multi_agent_web_search/.env.example new file mode 100644 index 0000000..8064dee --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/.env.example @@ -0,0 +1,6 @@ +OPENAI_API_KEY=your_openai_api_key_here +SERPERDEV_API_KEY=your_serperdev_api_key_here + +OPIK_API_KEY=your_opik_api_key_here +OPIK_WORKSPACE=your_workspace +OPIK_PROJECT_NAME=haystack-multi-agent-scout diff --git a/integrations/haystack/multi_agent_web_search/.gitignore b/integrations/haystack/multi_agent_web_search/.gitignore new file mode 100644 index 0000000..cc6a33b --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/.gitignore @@ -0,0 +1,4 @@ +.env +.venv/ +__pycache__/ +.ruff_cache/ diff --git a/integrations/haystack/multi_agent_web_search/README.md b/integrations/haystack/multi_agent_web_search/README.md new file mode 100644 index 0000000..a0eb491 --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/README.md @@ -0,0 +1,49 @@ +# Haystack Multi-Agent Web Search with Comet Opik + +Trace a Haystack multi-agent pipeline with Comet Opik. + +## What this does + +This example runs a two-agent Haystack chain: a coordinator agent that delegates research +questions to a scout agent, which searches the web via SerperDev to answer them. `OpikConnector` +traces the coordinator, the scout, and every tool call into a single Opik trace. + +## Prerequisites + +This is a `uv` project — dependencies live in `pyproject.toml`. + +```bash +uv sync +``` + +Copy `.env.example` to `.env` (or `export` the variables). With `OPENAI_API_KEY` / +`SERPERDEV_API_KEY` / Opik credentials unset, the example runs in **DRY_RUN** and prints what it +would do instead of calling OpenAI/SerperDev/Opik. + +| Variable | Required | Description | +|---|---|---| +| `OPENAI_API_KEY` | for a live run | OpenAI API key used by both agents' chat generators. Unset → DRY_RUN. | +| `SERPERDEV_API_KEY` | for a live run | SerperDev API key for the web-search tool. Unset → DRY_RUN. | +| `OPIK_API_KEY` | for a live run | Opik API key from [comet.com/opik](https://www.comet.com/opik). Unset → DRY_RUN. | +| `OPIK_WORKSPACE` | for a live run | Your Opik workspace. Unset → DRY_RUN. | +| `OPIK_PROJECT_NAME` | no | Project traces are logged to (default `haystack-multi-agent-scout`). | +| `HAYSTACK_OPENAI_MODEL` | no | OpenAI model both agents run on (default `gpt-5-mini`). | + +## Running it + +```bash +uv run python agent.py + +# or, the way CI does: +bash run.sh +``` + +## How it works + +1. **Define the tool** — `tool.py` wraps `SerperDevWebSearch` as a `ComponentTool` named `web_search`. +2. **Enable tracing** — `agent.py` sets `HAYSTACK_CONTENT_TRACING_ENABLED=true` and constructs + `OpikConnector`, which activates Opik tracing for every Haystack component run in the process. +3. **Build the agents** — `agent.py` creates a `scout` agent that calls `web_search`, wraps it as a + `scout` tool, and gives that tool to a `coordinator` agent. +4. **Run and trace** — `run_agent` sends a query to the coordinator, which delegates to the scout as + needed; the full call chain is logged to Opik under `OPIK_PROJECT_NAME`. diff --git a/integrations/haystack/multi_agent_web_search/agent.py b/integrations/haystack/multi_agent_web_search/agent.py new file mode 100644 index 0000000..da52276 --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/agent.py @@ -0,0 +1,77 @@ +""" +Haystack multi-agent web-search example, traced with Opik. + +A coordinator agent delegates research questions to a scout agent, which in turn +calls a SerperDev web-search tool. Opik traces the whole coordinator -> scout -> +tool chain via `OpikConnector`. +""" + +import os +from typing import Annotated + +os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true" + +from haystack.components.agents import Agent +from haystack.components.generators.chat import OpenAIChatGenerator +from haystack.dataclasses import ChatMessage +from haystack.tools import tool +from opik.integrations.haystack import OpikConnector + +import config +from tool import build_web_search_tool + +QUERY = "What was the final score and who won the FIFA World Cup 2026 championship?" + + +def build_coordinator() -> Agent: + # WHY: constructing OpikConnector registers Opik as Haystack's global tracer for the whole + # process; the instance is intentionally unused because we run Agents directly, not via a Pipeline. + OpikConnector(name="haystack-multi-agent-scout", project_name=config.OPIK_PROJECT_NAME) + + scout_agent = Agent( + chat_generator=OpenAIChatGenerator(model=config.OPENAI_MODEL), + tools=[build_web_search_tool()], + system_prompt=( + "You are a football scouting specialist covering the FIFA World Cup 2026. " + "Search the web to find up-to-date information on teams, fixtures, venues, and knockout news" + ), + ) + + @tool + def scout(query: Annotated[str, "The World Cup 2026 research question to investigate"]) -> str: + """Research a FIFA World Cup 2026 topic and return a summary of findings.""" + try: + result = scout_agent.run(messages=[ChatMessage.from_user(query)]) + return result["last_message"].text + except Exception as e: + return f"Scouting research failed: {e}" + + return Agent( + chat_generator=OpenAIChatGenerator(model=config.OPENAI_MODEL), + tools=[scout], + system_prompt=( + "You are a World Cup 2026 coverage coordinator. Delegate research questions " + "about teams, matches, venues, and players to the scout tool, then summarize " + "the findings for a fan who wants the latest updates." + ), + ) + + +def run_agent(query: str) -> str: + coordinator = build_coordinator() + result = coordinator.run(messages=[ChatMessage.from_user(query)]) + return result["last_message"].text + + +def main() -> None: + if config.DRY_RUN: + print( + "[DRY RUN] OpenAI / SerperDev / Opik credentials not set — would delegate this " + f"query through the coordinator -> scout agent chain and trace it to Opik:\n {QUERY}" + ) + return + print(run_agent(QUERY)) + + +if __name__ == "__main__": + main() diff --git a/integrations/haystack/multi_agent_web_search/config.py b/integrations/haystack/multi_agent_web_search/config.py new file mode 100644 index 0000000..d4bb306 --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/config.py @@ -0,0 +1,11 @@ +import os + +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") +SERPERDEV_API_KEY = os.environ.get("SERPERDEV_API_KEY") +OPIK_API_KEY = os.environ.get("OPIK_API_KEY") +OPIK_WORKSPACE = os.environ.get("OPIK_WORKSPACE") + +OPIK_PROJECT_NAME = os.environ.get("OPIK_PROJECT_NAME", "haystack-multi-agent-scout") +OPENAI_MODEL = os.environ.get("HAYSTACK_OPENAI_MODEL", "gpt-5-mini") + +DRY_RUN = not (OPENAI_API_KEY and SERPERDEV_API_KEY and OPIK_API_KEY and OPIK_WORKSPACE) diff --git a/integrations/haystack/multi_agent_web_search/pyproject.toml b/integrations/haystack/multi_agent_web_search/pyproject.toml new file mode 100644 index 0000000..b36c917 --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "haystack-multi-agent-web-search" +version = "0.1.0" +description = "Trace a Haystack coordinator -> scout multi-agent web-search chain with Opik." +readme = "README.md" +requires-python = ">=3.10,<3.14" +dependencies = [ + "haystack-ai>=3.0.0", + "serperdev-haystack>=1.0.0", + "opik>=2.2.0", +] + +[dependency-groups] +dev = ["ruff"] + +# WHY: a loose runnable script, not an installable package — uv manages the env but builds nothing. +[tool.uv] +package = false + +[tool.ruff] +line-length = 110 +target-version = "py310" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] diff --git a/integrations/haystack/multi_agent_web_search/run.sh b/integrations/haystack/multi_agent_web_search/run.sh new file mode 100755 index 0000000..fffed7e --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/run.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +export OPIK_PROJECT_NAME="haystack-multi-agent-scout" + +uv sync + +# With no OpenAI / SerperDev / Opik credentials this falls back to DRY_RUN and +uv run python agent.py diff --git a/integrations/haystack/multi_agent_web_search/tool.py b/integrations/haystack/multi_agent_web_search/tool.py new file mode 100644 index 0000000..23fd4e7 --- /dev/null +++ b/integrations/haystack/multi_agent_web_search/tool.py @@ -0,0 +1,14 @@ +from haystack.tools import ComponentTool +from haystack.utils import Secret +from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch + + +def build_web_search_tool(top_k: int = 4) -> ComponentTool: + return ComponentTool( + component=SerperDevWebSearch( + api_key=Secret.from_env_var("SERPERDEV_API_KEY"), + top_k=top_k, + ), + name="web_search", + description="Search the web for current information on any topic", + )