Skip to content

feat(haystack): migrate openinference-instrumentation-haystack - #318

Open
srinjoy356 wants to merge 18 commits into
open-telemetry:mainfrom
srinjoy356:migrate-openinference-haystack
Open

feat(haystack): migrate openinference-instrumentation-haystack#318
srinjoy356 wants to merge 18 commits into
open-telemetry:mainfrom
srinjoy356:migrate-openinference-haystack

Conversation

@srinjoy356

@srinjoy356 srinjoy356 commented Jul 27, 2026

Copy link
Copy Markdown

Description

Migrates openinference-instrumentation-haystack onto opentelemetry-util-genai's typed invocations, per the process in .github/skills/migrate-from-openinference/SKILL.md.

Instruments:

  • Pipeline.run / run_async / run_async_generatorinvoke_workflow (never double-counted when the generator form is driven internally by run_async)
  • Classified components (by class name + run type hints) → chat/text_completion (generators), embeddings (embedders), retrieval (retrievers/rankers), invoke_agent (Agent)
  • Tool.invoke / invoke_asyncexecute_tool

Targets haystack-ai >= 3.0.0 only, not OpenInference's >=2.18.0 range — Haystack 3.0 merged AsyncPipeline into Pipeline and dropped the plain-text Generator/websearch components the 2.x-era OpenInference tests used, so supporting both API shapes wasn't worth it for a first migration PR (see repo's "support only latest major versions" guidance).

Also fixes a correctness bug found while testing: components defined after instrument() runs were only wrapped if later invoked through a Pipeline. Since Agent calls its chat_generator directly (never through a Pipeline), this produced zero telemetry for that path — and would affect anyone who instruments at app startup before importing their pipeline components. Fixed by hooking _Component._component (the @component decorator's actual registration point) instead of Pipeline._run_component.

Full gap/coverage analysis is in the comment below (from MIGRATION_REPORT.md), not here, per this repo's guidance to keep AI-assisted analysis out of the PR description.

Towards #141

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

  • uv run tox -e py312-test-instrumentation-genai-haystack-latest — 22 passed
  • uv run tox -e py310-test-instrumentation-genai-haystack-oldest — 22 passed (UV_RESOLUTION=lowest-direct, floors to haystack-ai==3.0.0)
  • uv run tox -e py314-test-instrumentation-genai-haystack-conformance — 6 passed, validated against real weaver semconv checks (not just the auto-skip path)
  • uv run tox -e precommit — clean
  • uv run tox -e typecheck — 0 errors

Manual verification against a local SigNoz instance — real OpenAIChatGenerator + a real tool call through an Agent:

Trace list — 4 spans under haystack-agent-tool-smoketest: invoke_agent Agent, chat ×2, execute_tool get_weather.

01-trace-list-4-spans

Waterfall view — invoke_agent Agent correctly parenting all three child spans, with attributes panel showing gen_ai.agent.name and gen_ai.input.messages.

02-waterfall-agent-nesting

invoke_agent span's gen_ai.output.messages — correctly sliced to just the 3 new messages (tool call → tool result → final answer), not the echoed input.

03-agent-output-messages

execute_tool span attributes — gen_ai.tool.call.arguments, gen_ai.tool.call.result, gen_ai.tool.name, gen_ai.tool.type.

04-execute-tool-attributes

execute_tool span's raw metadata — parent_span_id / references: CHILD_OF confirming the nesting at the data level.

05-execute-tool-raw-metadata

Checklist

  • Followed the style guidelines of this project
    • Changelog updated if the change requires an entry — added .changelog/318.added
  • Unit tests added
  • Documentation updated

@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Jul 27, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on the author · refreshed 2026-08-06 09:10 UTC

Respond to 1 review item (e.g. link a commit, explain why not, ask a follow-up):

  • Top-level threads: 1
Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Should this be with reviewers? Comment /dashboard route:reviewers to route it to them.
  • Anything wrong — including the routing? Report it with what you expected; it helps us improve the dashboard.

srinjoy356 added a commit to srinjoy356/opentelemetry-python-genai that referenced this pull request Jul 27, 2026
@srinjoy356
srinjoy356 marked this pull request as ready for review July 27, 2026 07:55
@srinjoy356
srinjoy356 requested a review from a team as a code owner July 27, 2026 07:55
@srinjoy356

Copy link
Copy Markdown
Author

Migration review: opentelemetry-instrumentation-genai-haystack

Mode: greenfield migration

Compared against:

1. Instrumented API surface

OpenInference wraps every registered Haystack component generically (via the
haystack.core.component.component.component registry) plus
Pipeline.run/AsyncPipeline.run/AsyncPipeline.run_async/AsyncPipeline.run_async_generator,
and internally classifies each component into GENERATOR / EMBEDDER /
RANKER / RETRIEVER / PROMPT_BUILDER / UNKNOWN to decide which
attributes to attach — but every component gets a span (generic
input.value/output.value for unclassified ones). opentelemetry-util-genai
has no invocation type for a generic, unclassified pipeline step, so this
migration only emits spans for methods with a semconv-defined operation:

API method OpenInference This package Notes
Unclassified component run/run_async (prompt builders, routers, converters, joiners, samplers, ...) ✅ (generic span only) no semconv operation defined for a generic component step
Pipeline.run (sync) invoke_workflow
Pipeline.run_async (async) invoke_workflow
Pipeline.run_async_generator (async, called directly) invoke_workflow; suppressed when driven internally by run_async to avoid double-counting one execution — see §3
Generator / ChatGenerator run/run_async chat or text_completion
Embedder run/run_async embeddings
Retriever/Ranker run/run_async retrieval
haystack.components.agents.agent.Agent run/run_async ✅ (generic span only) invoke_agent; the Agent's own chat_generator.run() calls and any Tool.invoke() calls it drives are separately captured as nested chat / execute_tool spans (see next two rows) — not something this row's wrapper does itself
haystack.tools.tool.Tool.invoke / invoke_async — (OpenInference doesn't instrument tool execution) execute_tool; wrapped directly on Tool, not via the component registry — a Tool is not a Haystack Component
haystack.core.component.component._Component._component (the @component decorator's registration point) — (not applicable to OpenInference's design) ✅ (plumbing only, no span) see §3 — classifies and wraps every component class the instant it's registered, regardless of import order relative to instrument() or whether it's ever run through a Pipeline

2. Gaps and open issues

Gap File / test Upstream issue Notes
Unclassified components emit no span component_types.py (ComponentType.UNKNOWN) none filed No util-genai invocation type for a generic pipeline step. Matches how opentelemetry-instrumentation-genai-langchain suppresses unclassified on_chain_start callbacks rather than emitting a generic span.
EmbeddingInvocation has no per-document text/vector fields message_utils.py, test_embedding.py none filed OpenInference's embedding.embeddings.<i>.{text,vector} attributes have no util-genai equivalent; only aggregate dimension_count/input_tokens/response_model_name are recorded. This is a util-genai gap, not fixable in this package.
RetrievalInvocation has no data_source_id populated patch.py::_start_retrieval_invocation none filed Haystack retrievers/rankers don't expose a normalized index/collection identifier generically (e.g. InMemoryDocumentStore has no such concept); left unset rather than guessing. Vendor document stores (Elasticsearch, Pinecone, ...) that do have one are out of scope (separate integration packages).
tool_call_id not populated on execute_tool spans patch.py::_start_tool_invocation none filed The model's tool_call.id is only available one layer up, in the private haystack.components.agents.tool_calling._make_context_bound_invoke, not in Tool.invoke itself. Correlating it would mean hooking that private function (confirmed feasible — it snapshots contextvars.copy_context() right before invocation, so a contextvar set there would propagate into Tool.invoke even across its ThreadPoolExecutor boundary) but was deliberately not done to avoid depending on Haystack internals beyond the public Tool/Agent/Pipeline surface. Declared as an ExpectedViolation in tests/conformance/invoke_agent.py. See §5.
gen_ai.response.id not populated for real Haystack generators patch.py::_finish_generator_invocation none filed Haystack's own _convert_chat_completion_to_chat_message (in haystack/components/generators/chat/openai.py) never copies the OpenAI response's id into the ChatMessage.meta it builds, and run() returns no other place to find it — genuinely unrecoverable from this instrumentation for real OpenAI-backed generators. The extraction code (reply.meta.get("id")) is implemented and does populate response_id when a generator's meta does carry one (verified with the fake generator in tests/conformance/invoke_agent.py), so this is a Haystack-upstream gap, not missing code. Declared as an ExpectedViolation in the scenarios that use a real OpenAIChatGenerator (inference.py, tool_calling.py, invoke_workflow.py).
server.address/server.port unavailable on a fresh generator/embedder's first standalone call patch.py::_server_address_and_port none filed Haystack's SDK-backed generators/embedders build their client lazily via warm_up(). Pipeline.run() calls warm_up() on its components automatically, so this is populated for Pipeline-driven calls (confirmed: invoke_workflow.py's scenario needs no violation declared for it) — the gap is specific to a component called standalone, whose first-ever call has nothing to read yet (populated from the second call onward on a reused instance). Declared as an ExpectedViolation in the standalone-call scenarios (inference.py, embedding.py, tool_calling.py) and in invoke_agent.py (whose fake generator has no SDK client concept at all). Not fixed by calling component.warm_up() ourselves — see the note in _known_gaps.py about Tool/Toolset warm-up side effects.
Generator/embedder provider inference is a closed class-name map provider.py none filed Covers OpenAI, Azure OpenAI, Cohere, Amazon Bedrock, and Google Vertex AI generator/embedder class names (verified against each integration package's real source). Hugging Face API generators still resolve to no provider — there is no matching gen_ai.provider.name enum value yet (a semconv gap, not a util-genai or instrumentation gap).
tools_to_definitions only handles typed haystack.tools.Tool/Toolset objects message_utils.py none filed Raw provider-format tool dicts passed through generation_kwargs={"tools": [...]}} (as in test_tool_calling_captures_tool_call_on_output_message, mirroring the OpenInference test) aren't typed Haystack objects and have no reliable common shape across generators, so gen_ai.tool.definitions isn't populated for that path — the resulting tool call on the output message is still captured correctly.

3. Significant behavioral changes

Aspect Upstream This package Notes
Telemetry model Generic OpenInference span kinds (CHAIN/LLM/EMBEDDING/RETRIEVER/RERANKER) with flat input.value/output.value/llm.* attributes on every wrapped component Typed opentelemetry-util-genai invocations (WorkflowInvocation/InferenceInvocation/EmbeddingInvocation/RetrievalInvocation/AgentInvocation/ToolInvocation) emitting gen_ai.* semconv attributes only for components with a defined operation Required by this repo's AGENTS.md/migration skill — not a regression, the intended target shape.
Component discovery Walks the component registry once at instrument() time, plus hooks Pipeline._run_component[_async] to catch late registrations used inside a Pipeline Hooks _Component._component — the @component decorator's actual registration call — so every component class is classified and wrapped the instant it's defined, regardless of import order relative to instrument() and regardless of whether it's ever run through a Pipeline Found and fixed during this migration: the Pipeline-hook-only approach silently produced zero telemetry for any component imported/defined after instrument() that isn't invoked via a Pipeline — e.g. a haystack.components.agents.agent.Agent's own chat_generator, which it calls directly (self.chat_generator.run(...)), never through Pipeline._run_component. This affects the common "instrument at app startup, then import/build pipelines" ordering, not just Agent-specific code paths.
Pipeline entry points wrapped Pipeline.run, AsyncPipeline.run, AsyncPipeline.run_async, AsyncPipeline.run_async_generator (4 methods on 2 classes; calling AsyncPipeline.run() produced 3 nested CHAIN spans for one logical execution: runrun_asyncrun_async_generator) Pipeline.run, Pipeline.run_async, Pipeline.run_async_generator (3 methods on 1 class — Haystack 3.x merged AsyncPipeline into Pipeline) Haystack 3.0 removed the separate AsyncPipeline class. run_async_generator is wrapped too, but a contextvars.ContextVar set for the duration of run_async's call suppresses the inner span when run_async_generator is driven internally by an already-wrapped run_async() — so a caller using either entry point gets exactly one WorkflowInvocation, never zero or two.
Supported haystack-ai version >= 2.18.0 (test suite pinned to ==2.18.0) >= 3.0.0 Per this repo's migration guidance ("support only latest major versions"), and because Haystack 3.0 is a breaking change from 2.x: AsyncPipeline was removed/merged into Pipeline, and the plain-text OpenAIGenerator/Generator components (text_completion operation) were removed from haystack-ai core along with the websearch component category (SerperDevWebSearch). The text_completion code path in patch.py/component_types.py is kept for forward-compat with any generator that still returns List[str], but has no live test target on current haystack-ai.
Cohere reranker coverage test_cohere_reranker_span_has_expected_attributes (needs the cohere-haystack integration package + a live/cassette Cohere key) Not ported; RANKER classification is instead covered by MetaFieldRanker (built into haystack-ai core, fully local/deterministic, no cassette or API key needed) Same semconv operation (retrieval) and code path as the Cohere reranker; substituting a dependency-free component avoids adding cohere-haystack as a test-only dependency for a code path already exercised.
error.type recording span.set_status(ERROR, str(exception)), no dedicated error.type attribute error.type attribute (exception class name) + ERROR span status, via invocation.fail(exc) Matches this repo's other packages; not a regression.
Context-propagated session/user/tags attributes using_attributes(session_id=..., user_id=..., tags=..., metadata=...) context manager sets attributes on every span in scope Not ported No OTel GenAI semconv equivalent exists (per AGENTS.md's migration guidance) — dropped along with the tests that exercise it (test_pipeline_and_component_spans_contain_context_attributes).
suppress_tracing() openinference.instrumentation.suppress_tracing() context manager checked explicitly in each wrapper (context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY)) to skip wrapping entirely Not ported No other package in this repo (openai/anthropic/langchain) checks this key; this migration follows the established local convention rather than introducing a new one. test_pipelines_and_components_produce_no_tracing_with_suppress_tracing was not ported.
gen_ai.request.top_k type on retrieval spans n/a (OpenInference doesn't map to this attribute) int, matching the semconv registry Found via uv run tox -e py314-test-instrumentation-genai-haystack-conformance with weaver installed (not just the auto-skip path — see §4b): the code originally cast top_k to float to match RetrievalInvocation.top_k's declared type (float | None in opentelemetry-util-genai), but weaver flagged the registry expects int. Fixed in patch.py::_start_retrieval_invocation by casting to int instead — Haystack's own top_k parameters are always plain integer counts, so this is strictly more correct, not a workaround.

4. Test coverage

4a. Unit-test matrix per wrapped method

Wrapped method Missing variants Notes
Pipeline.run / Pipeline.run_async / Pipeline.run_async_generator sync, async, and direct-generator-call (incl. the no-double-count case) covered in test_workflow.py
Generator/ChatGenerator run sync happy, sync error, tool-calling covered in test_inference.py
Generator/ChatGenerator run_async async error test_chat_generator_async covers the happy path only; no async-error variant ported
Embedder run run_async variant, error variant only sync happy path covered (test_document_embedder)
Retriever run run_async variant, error variant only sync happy path covered (test_bm25_retriever)
Ranker run run_async variant, error variant only sync happy path covered (test_meta_field_ranker)
Agent.run run_async variant, error variant happy path (tool-calling turn + final answer) and no-content-capture covered in test_agent.py; also covers the late-component-registration fix (§3)
Tool.invoke sync happy, async happy, no-content-capture, error covered in test_tool.py

4b. Conformance scenarios

Operation Scenario file Status
chat tests/conformance/inference.py implemented, passes with weaver
chat + tool call tests/conformance/tool_calling.py implemented, passes with weaver
embeddings tests/conformance/embedding.py implemented, passes with weaver
retrieval tests/conformance/retrieval.py implemented (no cassette — local component), passes with weaver
invoke_workflow tests/conformance/invoke_workflow.py implemented, passes with weaver
invoke_agent (+ nested chat, execute_tool) tests/conformance/invoke_agent.py implemented (no cassette — fake local generator + real Tool), passes with weaver

Actually run against weaver_live_checkweaver isn't on PATH by
default in this environment (the fixture auto-skips when it's absent), so a
pinned copy (v0.24.2, matching versions.env) was downloaded and put on
PATH for verification: uv run tox -e py314-test-instrumentation-genai-haystack-conformance6 passed, all
via real weaver semconv validation, not the skip path. This surfaced one
real bug (gen_ai.request.top_k typed as float instead of int — see
§3) which is now fixed; the three remaining gaps it flagged
(gen_ai.response.id, server.address, gen_ai.tool.call.id) are declared
as ExpectedViolations per-scenario in tests/conformance/_known_gaps.py
with the reasoning documented in §2, per this repo's rule that a conformance
gap must be declared (and fail loudly if weaver stops reporting it — i.e. it
gets fixed upstream), never silently skipped.

4c. Docstring / README coverage

noneREADME.rst and the __init__.py module docstring both point at
opentelemetry.instrumentation.genai.haystack, document
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT and the completion-hook
env vars, and point at tests/conformance/ (no examples/).

5. Follow-up work

  • Correlate tool_call_id on execute_tool spans — hook the private
    haystack.components.agents.tool_calling._make_context_bound_invoke to
    stash the ToolCall.id in a contextvar Tool.invoke's wrapper can read
    (confirmed technically feasible; not done to avoid depending on Haystack
    internals beyond Tool/Agent/Pipeline). Needs a decision on whether
    that tradeoff is worth it.
  • Add run_async/error-path unit test variants for the embedder and
    retriever/ranker wrappers, and an error-path variant for Agent.run
    (§4a) — the code paths are shared with the already-tested sync/happy
    variants (component_run_async in patch.py is the same dispatcher as
    component_run) but aren't independently exercised.
  • Cohere reranker parity test — if the maintainers want the exact
    upstream scenario ported (rather than the dependency-free MetaFieldRanker
    substitute), add cohere-haystack as a test-only dependency and an
    AI-synthesized or live-recorded cassette.
  • Hugging Face provider mapping (provider.py) — blocked on a semconv
    gap (no gen_ai.provider.name enum value for Hugging Face yet), not
    something to fix here.

@AgentGymLeader

Copy link
Copy Markdown
Contributor

@srinjoy356 the README.rst and several modules point readers at MIGRATION_REPORT.md for the gap list, but that filename is excluded by the root .gitignore ("Migration review reports generated by the review-migration skill"), and it isn't in this PR's head or anywhere else in the tree.

Since README.rst becomes the PyPI description, that leaves a dangling pointer for anyone installing the package. Dropping the references, or folding the parts readers need into the README itself, would probably be cleaner than pointing at a file that by design never gets committed.

@srinjoy356

Copy link
Copy Markdown
Author

@srinjoy356 the README.rst and several modules point readers at MIGRATION_REPORT.md for the gap list, but that filename is excluded by the root .gitignore ("Migration review reports generated by the review-migration skill"), and it isn't in this PR's head or anywhere else in the tree.

Since README.rst becomes the PyPI description, that leaves a dangling pointer for anyone installing the package. Dropping the references, or folding the parts readers need into the README itself, would probably be cleaner than pointing at a file that by design never gets committed.

Thanks for catching this! I wasn't sure whether it made sense to add a new committed doc file for the gap list, so I'd left the details in MIGRATION_REPORT.md (gitignored, generated by the migration skill) and just pointed to it from README.rst and a few docstrings — not realizing that left a dangling reference for anyone reading the package off PyPI.

Per your suggestion, I've folded the gap list directly into README.rst instead:

  • Added a "Known limitations" section to README.rst covering all the gaps that were previously only in MIGRATION_REPORT.md: missing gen_ai.response.id for real OpenAI-backed generators, server.address/server.port timing on standalone (non-Pipeline) calls, missing gen_ai.tool.call.id correlation, no gen_ai.provider.name mapping for Hugging Face components, unwrapped component types (prompt builders, routers, etc.), untyped tool dicts, and per-document embedding data.
  • Dropped the MIGRATION_REPORT.md references from the source and test docstrings (__init__.py, patch.py, provider.py, component_types.py, message_utils.py, and the test files) — most already carried the rationale inline, so it was just the trailing pointer that needed to go. A couple now point to the real in-tree location instead (e.g. patch.py's Tool.invoke wrapper comment, __init__.py's _Component._component registration hook).

Pushed in 647446e. Please take another look whenever you get a chance.

@AgentGymLeader

Copy link
Copy Markdown
Contributor

@srinjoy356 checked 647446e — the references are gone from the package and the Known limitations section reads well on its own. Thanks for turning it around so fast.

@srinjoy356

Copy link
Copy Markdown
Author

@srinjoy356 checked 647446e — the references are gone from the package and the Known limitations section reads well on its own. Thanks for turning it around so fast.

Thanks! I actually had kept a track of where I put the references. It was a known thing. I just forgot to remove it beforehand.

@eternalcuriouslearner eternalcuriouslearner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your contribution @srinjoy356. Couple of things:

  1. Can you please check if haystack has native instrumentation?
  2. If yes, can you see if they're emitting genai spans or not.
  3. After this analysis can you please cut down the verbosity of this pr? You can space it like: skeleton, inference spans, agent spans etc. For now if you don't mind can you please close this pr and first let me know if 1 & 2 are not in place before we proceed adding the telemetry support.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new Haystack GenAI instrumentation package (opentelemetry-instrumentation-genai-haystack) migrated from OpenInference, built on opentelemetry-util-genai, and wires it into the repo’s tox test matrix with unit + conformance coverage and VCR cassettes.

Changes:

  • Introduces Haystack instrumentation that wraps Pipeline.run*, classified component run/run_async, and Tool.invoke* into util-genai invocations.
  • Adds unit tests, conformance scenarios, and VCR cassettes for chat/tool-calling, embeddings, retrieval, workflows, and agents.
  • Updates repo-level tox envs and gitignore to include the new package and workflows.

Reviewed changes

Copilot reviewed 44 out of 48 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tox.ini Adds haystack test/lint/conformance envs and deps.
.gitignore Ignores local .env* files and preserves existing MIGRATION_REPORT ignore.
instrumentation/opentelemetry-instrumentation-genai-haystack/pyproject.toml New package metadata, deps, entry point, towncrier config.
instrumentation/opentelemetry-instrumentation-genai-haystack/README.rst End-user docs: what’s instrumented, limitations, content capture + completion hook.
instrumentation/opentelemetry-instrumentation-genai-haystack/LICENSE Package license file.
instrumentation/opentelemetry-instrumentation-genai-haystack/.changelog/318.added Towncrier fragment for new instrumentation package.
instrumentation/opentelemetry-instrumentation-genai-haystack/.changelog/.gitignore Keeps .changelog/ tracked structure.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/init.py Instrumentor implementation + module docstring.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/version.py Package version definition.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/package.py Declares instrumentation_dependencies().
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/patch.py Core wrappers for pipeline, components, and tools.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/component_types.py Classifies Haystack components into GenAI operation types.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/message_utils.py Maps Haystack message/tool/document shapes into util-genai typed message models.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/provider.py Heuristic provider inference from component class name.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conftest.py Shared fixtures, VCR config, and env setup for tests.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/requirements.oldest.txt Oldest-factor test requirements (currently empty aside from comments).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/requirements.latest.txt Latest-factor test requirements (installs haystack-ai + local editable deps).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_instrumentor.py Verifies entry point + wrap/unwrap behavior.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_workflow.py Unit tests for invoke_workflow spans + double-count prevention.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_inference.py Unit tests for chat inference spans, content capture, errors, and tool-call parts.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_embedding.py Unit tests for embeddings spans and dimension count.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_retrieval.py Unit tests for retrieval spans, query/doc capture, and top_k handling.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_tool.py Unit tests for execute_tool spans (sync/async/error/no-content).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_agent.py Unit tests for invoke_agent span nesting + late component registration regression.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_utils.py Shared assertion helpers for spans and message JSON parsing.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_conformance.py Conformance runner executing all scenarios.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/init.py Conformance package marker.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/_known_gaps.py Declares reusable ExpectedViolation constants.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/inference.py Conformance scenario for chat inference.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/tool_calling.py Conformance scenario for tool-call parts in output messages.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/embedding.py Conformance scenario for embeddings.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/retrieval.py Conformance scenario for retrieval.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/invoke_workflow.py Conformance scenario for invoke_workflow + nested chat.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/invoke_agent.py Conformance scenario for invoke_agent + nested chat/execute_tool.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/inference_conformance.yaml VCR cassette for inference conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/tool_calling_conformance.yaml VCR cassette for tool-calling conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/workflow_conformance.yaml VCR cassette for workflow conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/embedding_conformance.yaml VCR cassette for embedding conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_sync.yaml VCR cassette for sync chat generator unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_async.yaml VCR cassette for async chat generator unit test (includes PostHog batch interaction).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_no_content_capture.yaml VCR cassette for no-content chat test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_error.yaml VCR cassette for auth error path test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_tool_calling_captures_tool_call_on_output_message.yaml VCR cassette for tool-call output-message unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_pipeline_run_produces_workflow_and_chat_spans.yaml VCR cassette for sync pipeline workflow test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_pipeline_run_async_produces_workflow_and_chat_spans.yaml VCR cassette for async pipeline workflow test (includes PostHog batch interaction).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_document_embedder.yaml VCR cassette for embedder unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/init.py Test package marker.
Comments suppressed due to low confidence (1)

instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/patch.py:295

  • Same issue as the generator wrapper: passing provider="" will emit an empty gen_ai.provider.name attribute when the provider can’t be inferred. Use a non-empty fallback (or adjust util-genai to make provider truly optional for framework instrumentations).
    request_model = getattr(component, "model", None)
    server_address, server_port = _server_address_and_port(component)
    return handler.embedding(
        provider=infer_provider(component) or "",
        request_model=request_model,
        server_address=server_address,
        server_port=server_port,
    )

# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

_instruments = ("haystack-ai >= 2.18.0",)
Comment on lines +50 to +52
Message content capture can be enabled by setting the environment variable:
``OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true``

Comment on lines +204 to +210
invocation = handler.inference(
provider=infer_provider(component) or "",
request_model=request_model,
operation_name=operation_name,
server_address=server_address,
server_port=server_port,
)
Comment on lines +13 to +18
"distinct_id": "0fbae5f1-414c-4dde-a02e-459ab7e75ae0", "event": "Pipeline run
(2.x)", "uuid": "4d1cce43-d74b-47c6-ba1a-10cdc05d8b90"}], "historical_migration":
false, "sentAt": "2025-10-14T03:56:18.209957+00:00", "api_key": "phc_C44vUK9R1J6HYVdfJarTEPqVAoRPJzMXzFcj8PIrJgP"}'
headers: {}
method: POST
uri: https://eu.i.posthog.com/batch/
srinjoy356 added a commit to srinjoy356/opentelemetry-python-genai that referenced this pull request Jul 29, 2026
- Fix instrumentation_dependencies() version floor to match pyproject.toml
  (haystack-ai >= 3.0.0, was stale at >= 2.18.0).
- Fix __init__.py docstring documenting a non-existent
  OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true value; document the
  real NO_CONTENT/SPAN_ONLY/EVENT_ONLY/SPAN_AND_EVENT modes, matching README.
- Stop emitting gen_ai.provider.name="" for unmapped providers; fall back to
  "unknown" instead, matching the precedent in the already-merged
  genai-langchain package. Corrected README wording to match.
- Strip a leftover Haystack/PostHog telemetry interaction (with a real
  project API key) from two VCR cassettes recorded before
  HAYSTACK_TELEMETRY_ENABLED=False was added to conftest.py; the interaction
  is unused dead weight now that telemetry is disabled in tests.

Assisted-by: Claude Sonnet 5
srinjoy356 added a commit to srinjoy356/opentelemetry-python-genai that referenced this pull request Jul 29, 2026
…ce-haystack

Resolves the merge-conflict indicator GitHub was showing on PR open-telemetry#318
(.gitignore, tox.ini envlist, uv.lock all had additive conflicts against
main's newer state -- agno/llama-index instrumentation, langchain updates,
etc. -- with no actual overlap in intent). uv.lock was regenerated via
`uv lock` rather than hand-merged.
@srinjoy356

Copy link
Copy Markdown
Author

Pull request overview

This PR adds a new Haystack GenAI instrumentation package (opentelemetry-instrumentation-genai-haystack) migrated from OpenInference, built on opentelemetry-util-genai, and wires it into the repo’s tox test matrix with unit + conformance coverage and VCR cassettes.

Changes:

  • Introduces Haystack instrumentation that wraps Pipeline.run*, classified component run/run_async, and Tool.invoke* into util-genai invocations.
  • Adds unit tests, conformance scenarios, and VCR cassettes for chat/tool-calling, embeddings, retrieval, workflows, and agents.
  • Updates repo-level tox envs and gitignore to include the new package and workflows.

Reviewed changes

Copilot reviewed 44 out of 48 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tox.ini Adds haystack test/lint/conformance envs and deps.
.gitignore Ignores local .env* files and preserves existing MIGRATION_REPORT ignore.
instrumentation/opentelemetry-instrumentation-genai-haystack/pyproject.toml New package metadata, deps, entry point, towncrier config.
instrumentation/opentelemetry-instrumentation-genai-haystack/README.rst End-user docs: what’s instrumented, limitations, content capture + completion hook.
instrumentation/opentelemetry-instrumentation-genai-haystack/LICENSE Package license file.
instrumentation/opentelemetry-instrumentation-genai-haystack/.changelog/318.added Towncrier fragment for new instrumentation package.
instrumentation/opentelemetry-instrumentation-genai-haystack/.changelog/.gitignore Keeps .changelog/ tracked structure.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/init.py Instrumentor implementation + module docstring.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/version.py Package version definition.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/package.py Declares instrumentation_dependencies().
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/patch.py Core wrappers for pipeline, components, and tools.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/component_types.py Classifies Haystack components into GenAI operation types.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/message_utils.py Maps Haystack message/tool/document shapes into util-genai typed message models.
instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/provider.py Heuristic provider inference from component class name.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conftest.py Shared fixtures, VCR config, and env setup for tests.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/requirements.oldest.txt Oldest-factor test requirements (currently empty aside from comments).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/requirements.latest.txt Latest-factor test requirements (installs haystack-ai + local editable deps).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_instrumentor.py Verifies entry point + wrap/unwrap behavior.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_workflow.py Unit tests for invoke_workflow spans + double-count prevention.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_inference.py Unit tests for chat inference spans, content capture, errors, and tool-call parts.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_embedding.py Unit tests for embeddings spans and dimension count.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_retrieval.py Unit tests for retrieval spans, query/doc capture, and top_k handling.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_tool.py Unit tests for execute_tool spans (sync/async/error/no-content).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_agent.py Unit tests for invoke_agent span nesting + late component registration regression.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_utils.py Shared assertion helpers for spans and message JSON parsing.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/test_conformance.py Conformance runner executing all scenarios.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/init.py Conformance package marker.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/_known_gaps.py Declares reusable ExpectedViolation constants.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/inference.py Conformance scenario for chat inference.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/tool_calling.py Conformance scenario for tool-call parts in output messages.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/embedding.py Conformance scenario for embeddings.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/retrieval.py Conformance scenario for retrieval.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/invoke_workflow.py Conformance scenario for invoke_workflow + nested chat.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/conformance/invoke_agent.py Conformance scenario for invoke_agent + nested chat/execute_tool.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/inference_conformance.yaml VCR cassette for inference conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/tool_calling_conformance.yaml VCR cassette for tool-calling conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/workflow_conformance.yaml VCR cassette for workflow conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/embedding_conformance.yaml VCR cassette for embedding conformance scenario.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_sync.yaml VCR cassette for sync chat generator unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_async.yaml VCR cassette for async chat generator unit test (includes PostHog batch interaction).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_no_content_capture.yaml VCR cassette for no-content chat test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_chat_generator_error.yaml VCR cassette for auth error path test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_tool_calling_captures_tool_call_on_output_message.yaml VCR cassette for tool-call output-message unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_pipeline_run_produces_workflow_and_chat_spans.yaml VCR cassette for sync pipeline workflow test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_pipeline_run_async_produces_workflow_and_chat_spans.yaml VCR cassette for async pipeline workflow test (includes PostHog batch interaction).
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/cassettes/test_document_embedder.yaml VCR cassette for embedder unit test.
instrumentation/opentelemetry-instrumentation-genai-haystack/tests/init.py Test package marker.
Comments suppressed due to low confidence (1)

Thanks Copilot — all four points were real, addressed in 4499fe9:

  • package.py's _instruments said haystack-ai >= 2.18.0 while pyproject.toml actually requires >= 3.0.0 — fixed the drift.
  • Fixed the __init__.py docstring: it documented OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true, which isn't a real accepted value and contradicted the README. Now documents the actual NO_CONTENT/SPAN_ONLY/EVENT_ONLY/SPAN_AND_EVENT modes.
  • infer_provider(component) or "" was emitting gen_ai.provider.name="" for unmapped providers (util-genai only skips None, not empty string, when populating attributes). Switched to or "unknown" to match the same pattern already used in the merged genai-langchain package, and fixed the README wording to match.
  • Confirmed the PostHog interaction in the cassettes was a real leak — leftover from before HAYSTACK_TELEMETRY_ENABLED=False was added to conftest.py. Stripped it from both affected cassettes (it's unused now that telemetry is disabled in tests); grepped all cassettes to confirm there's no other instance of it.

Also merged main in (b9fe4af) to resolve the conflict indicator on the PR — additive conflicts in .gitignore, tox.ini's envlist, and uv.lock (regenerated via uv lock rather than hand-merged) against main's newer state, no real overlap in intent.

All 22 unit tests still pass and ruff (including the version bump from #332) is clean. @lmolkova

@srinjoy356

Copy link
Copy Markdown
Author

@eternalcuriouslearner Thanks for the questions — here's what I found:

1. Does Haystack have native instrumentation?
Yes. OpenTelemetryTracer used to live in Haystack core but was deprecated and moved out to a separate package, opentelemetry-haystack (docs: https://docs.haystack.deepset.ai/docs/2.31/tracing-opentelemetry). It's enabled either directly (tracing.enable_tracing(OpenTelemetryTracer(...))) or via an OpenTelemetryConnector pipeline component, and plugs into Haystack's own internal generic tracing abstraction (haystack.tracing.Tracer/Span, which is a no-op NullTracer unless a backend is enabled).

2. Does it emit GenAI semantic-convention spans?
No. I verified this directly against the installed haystack-ai==3.0.0 source rather than relying on docs alone — grepping haystack.core.pipeline.base/pipeline and haystack.components.agents.agent for every tag Haystack's own tracer emits:

  • haystack.component.name, haystack.component.type, haystack.component.input, haystack.component.output, haystack.component.visits
  • haystack.agent.input, haystack.agent.output, haystack.agent.steps_taken, haystack.agent.step.llm.input/.output

None of these are gen_ai.* attributes — no gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.operation.name, gen_ai.provider.name, etc. A full grep for "gen_ai" and "opentelemetry" across the entire haystack-ai core package returns zero hits. The opentelemetry-haystack integration package itself is pure plumbing — it implements Haystack's abstract Tracer/Span interface over the OTel SDK and just relays whatever proprietary tag name Haystack core passes in; it doesn't translate anything into GenAI semconv.

So there's no overlap: nothing in Haystack, native or otherwise, emits gen_ai.* telemetry today. This PR is additive, not duplicative.

3. Splitting the PR / closing it for now
Given 1 & 2 are confirmed clear, could we keep this as a single PR instead of closing and re-splitting it? It's already reviewed and the pieces are interdependent. Happy to stage future work if you'd prefer that going forward.

@srinjoy356

Copy link
Copy Markdown
Author

I ran tox -e generate to update instrumentation/README.md with the new Haystack package, which should resolve the failing generate and check CI jobs. Let me know if there's anything else needed! @lmolkova @eternalcuriouslearner

@eternalcuriouslearner

eternalcuriouslearner commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

I ran tox -e generate to update instrumentation/README.md with the new Haystack package, which should resolve the failing generate and check CI jobs. Let me know if there's anything else needed! @lmolkova @eternalcuriouslearner

Yes can you please raise an issue on the parent repository of haystack to see if they're interested in adding native instrumentation while we are pushing through this.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 45 out of 49 changed files in this pull request and generated no new comments.

Suppressed comments (3)

instrumentation/opentelemetry-instrumentation-genai-haystack/pyproject.toml:66

  • This package enables towncrier with filename = "CHANGELOG.md", but the PR doesn't add CHANGELOG.md in the package root. That will typically break tox -e changelog-preview / release-time changelog compilation for this package. Add a minimal CHANGELOG.md (with the <!-- changelog start --> marker) matching the pattern used by other instrumentation packages.
[tool.towncrier]
directory = ".changelog"
filename = "CHANGELOG.md"
start_string = "<!-- changelog start -->\n"
template = "../../scripts/changelog_template.j2"
issue_format = "[#{issue}](https://github.com/open-telemetry/opentelemetry-python-genai/pull/{issue})"
wrap = true

instrumentation/opentelemetry-instrumentation-genai-haystack/README.rst:30

  • New instrumentation packages in this repo are expected to ship minimal runnable examples under examples/ (both manual/ and zero-code/ variants). This PR adds the package but no examples directory, which makes it harder for users to validate the instrumentation quickly and is inconsistent with the repo’s instrumentation contribution guidelines.
Usage
-----

.. code-block:: python

    from opentelemetry.instrumentation.genai.haystack import HaystackInstrumentor

    # Instrument Haystack
    HaystackInstrumentor().instrument()

instrumentation/opentelemetry-instrumentation-genai-haystack/src/opentelemetry/instrumentation/genai/haystack/patch.py:151

  • Pipeline.run_async_generator() wrapper only calls invocation.stop() in the else: branch, so if the caller stops consuming the async generator early (e.g., breaks out of the loop), the span never gets finalized. Using a finally: to stop() ensures the workflow span closes on normal exhaustion, errors, and early close (with stop() being a no-op if fail() already ended it).
        else:
            invocation.stop()

@eternalcuriouslearner eternalcuriouslearner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please fix the failing gh actions?

@srinjoy356

Copy link
Copy Markdown
Author

can you please fix the failing gh actions?

yes I am on it. There are some naming convention issues. I will fix them right up.

@srinjoy356

Copy link
Copy Markdown
Author

can you please fix the failing gh actions?

I've pushed a fix for the failing CI checks. The test assertions for ErrorAttributes.ERROR_TYPE were previously checking for the short exception name (e.g., AuthenticationError), but the semantic conventions dictate using the fully qualified module name (openai.AuthenticationError).

I've updated the tests to dynamically resolve the fully qualified exception name (f"{type(excinfo.value).module}.{type(excinfo.value).name}"). I ran the specific instrumentation-genai-haystack test matrix locally to verify the fix, and the assertions now pass correctly. Let's see if the CI goes fully green now!

@eternalcuriouslearner

Copy link
Copy Markdown
Contributor

can you please fix the failing gh actions?

I've pushed a fix for the failing CI checks. The test assertions for ErrorAttributes.ERROR_TYPE were previously checking for the short exception name (e.g., AuthenticationError), but the semantic conventions dictate using the fully qualified module name (openai.AuthenticationError).

I've updated the tests to dynamically resolve the fully qualified exception name (f"{type(excinfo.value).module}.{type(excinfo.value).name}"). I ran the specific instrumentation-genai-haystack test matrix locally to verify the fix, and the assertions now pass correctly. Let's see if the CI goes fully green now!

Can you please fix the gh actions pipeline.

Add opentelemetry-instrumentation-genai-haystack, porting the OpenInference
Haystack instrumentation onto opentelemetry-util-genai's typed invocations
(WorkflowInvocation for Pipeline.run/run_async, InferenceInvocation for
classified generators, EmbeddingInvocation for embedders, RetrievalInvocation
for retrievers/rankers) per .github/skills/migrate-from-openinference.

Targets haystack-ai >= 3.0.0 only (Haystack 3.x merged AsyncPipeline into
Pipeline and dropped the plain-text Generator/websearch components that
2.x-era OpenInference tests targeted). See MIGRATION_REPORT.md (gitignored,
local review artifact) for the full gap list and follow-up items.

Assisted-by: Claude Sonnet 5
Closes several gaps identified in the initial migration:

- Classify haystack.components.agents.agent.Agent -> AgentInvocation
  (invoke_agent). Its own chat_generator/tool calls are already captured
  as nested chat/execute_tool spans via existing wrapping.
- Wrap haystack.tools.tool.Tool.invoke/invoke_async -> ToolInvocation
  (execute_tool), independent of the component registry (a Tool is not a
  Haystack Component).
- Wrap Pipeline.run_async_generator directly, using a contextvar to skip
  the span when it's driven internally by an already-wrapped run_async()
  call, so direct callers of the generator form now get a workflow span
  without double-counting.
- Broaden provider.py's class-name map to cover Cohere, Amazon Bedrock,
  and Google Vertex AI generators/embedders (verified against each
  integration package's real source), not just OpenAI/Azure OpenAI.

Also fixes a real correctness bug found while testing Agent support:
component classes defined *after* instrument() runs were only ever wrapped
if later invoked through a Pipeline (via the _run_component hook). Agent
calls its chat_generator directly, never through a Pipeline, so this
silently produced zero telemetry for that common ordering. Replaced the
Pipeline-hook-based lazy discovery with a hook on
_Component._component (the @component decorator's actual registration
point), which catches every component the instant it's defined regardless
of import order or how it's later invoked.

MIGRATION_REPORT.md updated accordingly (gitignored, local review artifact).

Assisted-by: Claude Sonnet 5
…er weaver

Actually ran the tox test matrix (uv run tox -e ...-latest/-oldest/-conformance)
instead of just pytest directly, and got a real weaver binary on PATH instead
of relying on the auto-skip path. That surfaced genuine bugs, now fixed:

- gen_ai.request.top_k was cast to float; the semconv registry requires int.
- gen_ai.response.id and server.address/port were never populated for
  generator/embedder spans. response.id now extracts from reply.meta when a
  generator provides one (verified with the fake conformance generator);
  real Haystack OpenAIChatGenerator drops it entirely, which is a genuine
  upstream limitation, not a code gap. server.address/port now reads the
  SDK client's base_url when available -- which, it turns out, is every
  Pipeline-driven call (Pipeline.run() calls warm_up() first) and every
  call after a standalone component's first one.

Declared the remaining permanent gaps (response.id, server.address on a
component's first standalone call, tool.call.id) as ExpectedViolation in
tests/conformance/_known_gaps.py per-scenario, rather than leaving them as
undeclared failures or skipping the scenarios.

All 6 conformance scenarios now pass against real weaver validation
(previously only verified via the auto-skip path with no weaver binary
installed). -latest and -oldest tox envs re-verified (22 passed each),
plus tox -e precommit and tox -e typecheck, matching the CI gates in
AGENTS.md.

MIGRATION_REPORT.md updated accordingly (gitignored, local review artifact).

Assisted-by: Claude Sonnet 5
…ngling refs

MIGRATION_REPORT.md is gitignored by design and never ships in the repo, so
pointers to it from README.rst (which becomes the PyPI long description) and
various source/test docstrings were dangling. Moves the actual gap list into
README.rst's new "Known limitations" section and drops the now-redundant
references elsewhere, since most already carried the rationale inline.

Assisted-by: Claude Sonnet 5
- Fix instrumentation_dependencies() version floor to match pyproject.toml
  (haystack-ai >= 3.0.0, was stale at >= 2.18.0).
- Fix __init__.py docstring documenting a non-existent
  OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true value; document the
  real NO_CONTENT/SPAN_ONLY/EVENT_ONLY/SPAN_AND_EVENT modes, matching README.
- Stop emitting gen_ai.provider.name="" for unmapped providers; fall back to
  "unknown" instead, matching the precedent in the already-merged
  genai-langchain package. Corrected README wording to match.
- Strip a leftover Haystack/PostHog telemetry interaction (with a real
  project API key) from two VCR cassettes recorded before
  HAYSTACK_TELEMETRY_ENABLED=False was added to conftest.py; the interaction
  is unused dead weight now that telemetry is disabled in tests.

Assisted-by: Claude Sonnet 5
@srinjoy356
srinjoy356 force-pushed the migrate-openinference-haystack branch from 6fd033c to 1973aa3 Compare August 4, 2026 08:37
@srinjoy356

Copy link
Copy Markdown
Author

Just force-pushed a rebase on top of the latest main to resolve the tox.ini conflicts from the new agents.

I also bundled in a few fixes for the CI edge cases that were failing earlier: fixed the shellcheck path resolution issues in tox.ini, forced LF line endings on the shell scripts to fix Windows checkouts, and downgraded Sphinx to <9.0.0 so docs/linting pass again on Python 3.10. Verified everything passes locally!

image

@eternalcuriouslearner

@eternalcuriouslearner

Copy link
Copy Markdown
Contributor

Just force-pushed a rebase on top of the latest main to resolve the tox.ini conflicts from the new agents.

I also bundled in a few fixes for the CI edge cases that were failing earlier: fixed the shellcheck path resolution issues in tox.ini, forced LF line endings on the shell scripts to fix Windows checkouts, and downgraded Sphinx to <9.0.0 so docs/linting pass again on Python 3.10. Verified everything passes locally!

image

@eternalcuriouslearner

@srinjoy356 can you please break the pr for quicker review. This is verbose and review time will be higher for this pr.

@srinjoy356

Copy link
Copy Markdown
Author

Just force-pushed a rebase on top of the latest main to resolve the tox.ini conflicts from the new agents.

I also bundled in a few fixes for the CI edge cases that were failing earlier: fixed the shellcheck path resolution issues in tox.ini, forced LF line endings on the shell scripts to fix Windows checkouts, and downgraded Sphinx to <9.0.0 so docs/linting pass again on Python 3.10. Verified everything passes locally!

image

@eternalcuriouslearner

@srinjoy356 can you please break the pr for quicker review. This is verbose and review time will be higher for this pr.

I'm on it. Help me with one thing how should I do it? As the tests seems to be passing for all the ones? Also I'm opening a pr on haystack today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants