haystack/test/core/pipeline/test_tracing.py
mathislucka eec91824bc
fix: pipeline run bugs in cyclic and acyclic pipelines (#8707)
* add component checks

* pipeline should run deterministically

* add FIFOQueue

* add agent tests

* add order dependent tests

* run new tests

* remove code that is not needed

* test: intermediate from cycle outputs are available outside cycle

* add tests for component checks (Claude)

* adapt tests for component checks (o1 review)

* chore: format

* remove tests that aren't needed anymore

* add _calculate_priority tests

* revert accidental change in pyproject.toml

* test format conversion

* adapt to naming convention

* chore: proper docstrings and type hints for PQ

* format

* add more unit tests

* rm unneeded comments

* test input consumption

* lint

* fix: docstrings

* lint

* format

* format

* fix license header

* fix license header

* add component run tests

* fix: pass correct input format to tracing

* fix types

* format

* format

* types

* add defaults from Socket instead of signature

- otherwise components with dynamic inputs would fail

* fix test names

* still wait for optional inputs on greedy variadic sockets

- mirrors previous behavior

* fix format

* wip: warn for ambiguous running order

* wip: alternative warning

* fix license header

* make code more readable

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>

* Introduce content tracing to a behavioral test

* Fixing linting

* Remove debug print statements

* Fix tracer tests

* remove print

* test: test for component inputs

* test: remove testing for run order

* chore: update component checks from experimental

* chore: update pipeline and base from experimental

* refactor: remove unused method

* refactor: remove unused method

* refactor: outdated comment

* refactor: inputs state is updated as side effect

- to prepare for AsyncPipeline implementation

* format

* test: add file conversion test

* format

* fix: original implementation deepcopies outputs

* lint

* fix: from_dict was updated

* fix: format

* fix: test

* test: add test for thread safety

* remove unused imports

* format

* test: FIFOPriorityQueue

* chore: add release note

* fix: resolve merge conflict with mermaid changes

* fix: format

* fix: remove unused import

* refactor: rename to avoid accidental conflicts

* chore: remove unused inputs, add missing license header

* chore: extend release notes

* Update releasenotes/notes/fix-pipeline-run-2fefeafc705a6d91.yaml

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>

* fix: format

* fix: format

* Update release note

---------

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>
Co-authored-by: David S. Batista <dsbatista@gmail.com>
2025-02-06 14:19:47 +00:00

149 lines
6.2 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from test.tracing.utils import SpyingSpan, SpyingTracer
from typing import Optional
from unittest.mock import ANY
import pytest
from _pytest.monkeypatch import MonkeyPatch
from haystack import Pipeline, component
from haystack.tracing.tracer import tracer
@component
class Hello:
@component.output_types(output=str)
def run(self, word: Optional[str]): # use optional to spice up the typing tags
"""
Takes a string in input and returns "Hello, <string>!"
in output.
"""
return {"output": f"Hello, {word}!"}
@pytest.fixture()
def pipeline() -> Pipeline:
pipeline = Pipeline()
pipeline.add_component("hello", Hello())
pipeline.add_component("hello2", Hello())
pipeline.connect("hello.output", "hello2.word")
return pipeline
class TestTracing:
def test_with_enabled_tracing(self, pipeline: Pipeline, spying_tracer: SpyingTracer) -> None:
pipeline.run(data={"word": "world"})
assert len(spying_tracer.spans) == 3
assert spying_tracer.spans == [
pipeline_span := SpyingSpan(
operation_name="haystack.pipeline.run",
tags={
"haystack.pipeline.input_data": {"hello": {"word": "world"}},
"haystack.pipeline.output_data": {"hello2": {"output": "Hello, Hello, world!!"}},
"haystack.pipeline.metadata": {},
"haystack.pipeline.max_runs_per_component": 100,
},
parent_span=None,
trace_id=ANY,
span_id=ANY,
),
SpyingSpan(
operation_name="haystack.component.run",
tags={
"haystack.component.name": "hello",
"haystack.component.type": "Hello",
"haystack.component.input_types": {"word": "str"},
"haystack.component.input_spec": {"word": {"type": ANY, "senders": []}},
"haystack.component.input": {"word": "world"},
"haystack.component.output_spec": {"output": {"type": "str", "receivers": ["hello2"]}},
"haystack.component.output": {"output": "Hello, world!"},
"haystack.component.visits": 1,
},
parent_span=pipeline_span,
trace_id=ANY,
span_id=ANY,
),
SpyingSpan(
operation_name="haystack.component.run",
tags={
"haystack.component.name": "hello2",
"haystack.component.type": "Hello",
"haystack.component.input_types": {"word": "str"},
"haystack.component.input_spec": {"word": {"type": ANY, "senders": ["hello"]}},
"haystack.component.input": {"word": "Hello, world!"},
"haystack.component.output_spec": {"output": {"type": "str", "receivers": []}},
"haystack.component.output": {"output": "Hello, Hello, world!!"},
"haystack.component.visits": 1,
},
parent_span=pipeline_span,
trace_id=ANY,
span_id=ANY,
),
]
# We need to check the type of the input_spec because it can be rendered differently
# depending on the Python version 🫠
assert spying_tracer.spans[1].tags["haystack.component.input_spec"]["word"]["type"] in [
"typing.Union[str, NoneType]",
"typing.Optional[str]",
]
def test_with_enabled_content_tracing(
self, spying_tracer: SpyingTracer, monkeypatch: MonkeyPatch, pipeline: Pipeline
) -> None:
# Monkeypatch to avoid impact on other tests
monkeypatch.setattr(tracer, "is_content_tracing_enabled", True)
pipeline.run(data={"word": "world"})
assert len(spying_tracer.spans) == 3
assert spying_tracer.spans == [
pipeline_span := SpyingSpan(
operation_name="haystack.pipeline.run",
tags={
"haystack.pipeline.metadata": {},
"haystack.pipeline.max_runs_per_component": 100,
"haystack.pipeline.input_data": {"hello": {"word": "world"}},
"haystack.pipeline.output_data": {"hello2": {"output": "Hello, Hello, world!!"}},
},
trace_id=ANY,
span_id=ANY,
),
SpyingSpan(
operation_name="haystack.component.run",
tags={
"haystack.component.name": "hello",
"haystack.component.type": "Hello",
"haystack.component.input_types": {"word": "str"},
"haystack.component.input_spec": {"word": {"type": ANY, "senders": []}},
"haystack.component.output_spec": {"output": {"type": "str", "receivers": ["hello2"]}},
"haystack.component.input": {"word": "world"},
"haystack.component.visits": 1,
"haystack.component.output": {"output": "Hello, world!"},
},
parent_span=pipeline_span,
trace_id=ANY,
span_id=ANY,
),
SpyingSpan(
operation_name="haystack.component.run",
tags={
"haystack.component.name": "hello2",
"haystack.component.type": "Hello",
"haystack.component.input_types": {"word": "str"},
"haystack.component.input_spec": {"word": {"type": ANY, "senders": ["hello"]}},
"haystack.component.output_spec": {"output": {"type": "str", "receivers": []}},
"haystack.component.input": {"word": "Hello, world!"},
"haystack.component.visits": 1,
"haystack.component.output": {"output": "Hello, Hello, world!!"},
},
parent_span=pipeline_span,
trace_id=ANY,
span_id=ANY,
),
]