Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs-website/docs/overview/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ You will want to ensure that a CUDA enabled GPU is being engaged when Haystack i

We only collect _anonymous_ usage statistics of Haystack pipeline components. Read more about telemetry in Haystack or how you can opt out on the [Telemetry](telemetry.mdx) page.

### How can I control progress bars?

Set the `HAYSTACK_PROGRESS_BARS` environment variable to override the `progress_bar` setting for `OpenAIDocumentEmbedder`, `AzureOpenAIDocumentEmbedder`, `MarkdownToDocument`, `LLMEvaluator`, `FaithfulnessEvaluator`, and `ContextRelevanceEvaluator`:

```python
import os

os.environ["HAYSTACK_PROGRESS_BARS"] = "false"
```

Use `0`, `false`, `no`, or `off` to hide progress bars, and `1`, `true`, `yes`, or `on` to show them. Values are case-insensitive and ignore surrounding whitespace. An unset, empty, or unrecognized value uses the component's configured `progress_bar` setting.

The variable is read when a component runs, so changes apply to subsequent runs, including asynchronous runs. It overrides the setting without changing the component's stored or serialized configuration.

### How can I ask my questions around Haystack?

For general questions, we recommend joining the [Haystack Discord ](https://discord.com/invite/xYvH6drSmA)or using [GitHub discussions](https://github.com/deepset-ai/haystack/discussions), where the community and maintainers can help. You can also explore [tutorials](https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling) and [examples](https://haystack.deepset.ai/cookbook/tools_support) on website to find more info.
Expand Down
14 changes: 14 additions & 0 deletions docs-website/versioned_docs/version-3.3/overview/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ You will want to ensure that a CUDA enabled GPU is being engaged when Haystack i

We only collect _anonymous_ usage statistics of Haystack pipeline components. Read more about telemetry in Haystack or how you can opt out on the [Telemetry](telemetry.mdx) page.

### How can I control progress bars?

Set the `HAYSTACK_PROGRESS_BARS` environment variable to override the `progress_bar` setting for `OpenAIDocumentEmbedder`, `AzureOpenAIDocumentEmbedder`, `MarkdownToDocument`, `LLMEvaluator`, `FaithfulnessEvaluator`, and `ContextRelevanceEvaluator`:

```python
import os

os.environ["HAYSTACK_PROGRESS_BARS"] = "false"
```

Use `0`, `false`, `no`, or `off` to hide progress bars, and `1`, `true`, `yes`, or `on` to show them. Values are case-insensitive and ignore surrounding whitespace. An unset, empty, or unrecognized value uses the component's configured `progress_bar` setting.

The variable is read when a component runs, so changes apply to subsequent runs, including asynchronous runs. It overrides the setting without changing the component's stored or serialized configuration.

### How can I ask my questions around Haystack?

For general questions, we recommend joining the [Haystack Discord ](https://discord.com/invite/xYvH6drSmA)or using [GitHub discussions](https://github.com/deepset-ai/haystack/discussions), where the community and maintainers can help. You can also explore [tutorials](https://haystack.deepset.ai/tutorials/40_building_chat_application_with_function_calling) and [examples](https://haystack.deepset.ai/cookbook/tools_support) on website to find more info.
Expand Down
4 changes: 3 additions & 1 deletion haystack/components/converters/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
from haystack.dataclasses import ByteStream
from haystack.lazy_imports import LazyImport
from haystack.utils.progress import _get_progress_bar_setting

with LazyImport("Run 'pip install markdown-it-py mdit_plain'") as markdown_conversion_imports:
from markdown_it import MarkdownIt
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(
If True converts table contents into a single line.
:param progress_bar:
If True shows a progress bar when running.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param store_full_path:
If True, the full path of the file is stored in the metadata of the document.
If False, only the file name is stored.
Expand Down Expand Up @@ -113,7 +115,7 @@ def run(
zip(sources, meta_list, strict=True),
total=len(sources),
desc="Converting markdown files to Documents",
disable=not self.progress_bar,
disable=not _get_progress_bar_setting(self.progress_bar),
):
try:
bytestream = get_bytestream_from_source(source)
Expand Down
1 change: 1 addition & 0 deletions haystack/components/embedders/azure_document_embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def __init__( # noqa: PLR0913, PLR0917 (too-many-arguments, too-many-positional
Number of documents to embed at once.
:param progress_bar:
If `True`, shows a progress bar when running.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param meta_fields_to_embed:
List of metadata fields to embed along with the document text.
:param embedding_separator:
Expand Down
8 changes: 6 additions & 2 deletions haystack/components/embedders/openai_document_embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from haystack import Document, component, default_from_dict, default_to_dict, logging
from haystack.utils import Secret
from haystack.utils.http_client import init_http_client
from haystack.utils.progress import _get_progress_bar_setting

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -89,6 +90,7 @@ def __init__( # noqa: PLR0913, PLR0917 (too-many-arguments, too-many-positional
Number of documents to embed at once.
:param progress_bar:
If `True`, shows a progress bar when running.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param meta_fields_to_embed:
List of metadata fields to embed along with the document text.
:param embedding_separator:
Expand Down Expand Up @@ -250,7 +252,9 @@ def _embed_batch(
doc_ids_to_embeddings: dict[str, list[float]] = {}
meta: dict[str, Any] = {}
for batch in tqdm(
batched(texts_to_embed.items(), batch_size), disable=not self.progress_bar, desc="Calculating embeddings"
batched(texts_to_embed.items(), batch_size),
disable=not _get_progress_bar_setting(self.progress_bar),
desc="Calculating embeddings",
):
args: dict[str, Any] = {"model": self.model, "input": [b[1] for b in batch], "encoding_format": "float"}

Expand Down Expand Up @@ -293,7 +297,7 @@ async def _embed_batch_async(
meta: dict[str, Any] = {}

batches = list(batched(texts_to_embed.items(), batch_size))
if self.progress_bar:
if _get_progress_bar_setting(self.progress_bar):
batches = async_tqdm(batches, desc="Calculating embeddings")

for batch in batches:
Expand Down
1 change: 1 addition & 0 deletions haystack/components/evaluators/context_relevance.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def __init__(
```
:param progress_bar:
Whether to show a progress bar during the evaluation.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param raise_on_failure:
Whether to raise an exception if the API call fails.
:param chat_generator:
Expand Down
1 change: 1 addition & 0 deletions haystack/components/evaluators/faithfulness.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def __init__(
```
:param progress_bar:
Whether to show a progress bar during the evaluation.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param raise_on_failure:
Whether to raise an exception if the API call fails.
:param chat_generator:
Expand Down
10 changes: 8 additions & 2 deletions haystack/components/evaluators/llm_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from haystack.dataclasses.chat_message import ChatMessage
from haystack.utils import deserialize_chatgenerator_inplace, deserialize_type, serialize_type
from haystack.utils.misc import _parse_dict_from_json
from haystack.utils.progress import _get_progress_bar_setting

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -87,6 +88,7 @@ def __init__(
If True, the component will raise an exception on an unsuccessful API call.
:param progress_bar:
Whether to show a progress bar during the evaluation.
The `HAYSTACK_PROGRESS_BARS` environment variable can override this setting at run time.
:param chat_generator:
a ChatGenerator instance which represents the LLM.
In order for the component to work, the LLM should be configured to return a JSON object. For example,
Expand Down Expand Up @@ -229,7 +231,9 @@ def run(self, **inputs: Any) -> dict[str, Any]:
results: list[dict[str, Any] | None] = []
metadata = []
errors = 0
for input_names_to_values in tqdm(list_of_input_names_to_values, disable=not self.progress_bar):
for input_names_to_values in tqdm(
list_of_input_names_to_values, disable=not _get_progress_bar_setting(self.progress_bar)
):
prompt = self.builder.run(**input_names_to_values)
messages = [ChatMessage.from_user(prompt["prompt"])]
try:
Expand Down Expand Up @@ -300,7 +304,9 @@ async def run_async(self, **inputs: Any) -> dict[str, Any]:
errors = 0

generator_has_async = hasattr(self._chat_generator, "run_async")
for input_names_to_values in async_tqdm(list_of_input_names_to_values, disable=not self.progress_bar):
for input_names_to_values in async_tqdm(
list_of_input_names_to_values, disable=not _get_progress_bar_setting(self.progress_bar)
):
prompt = self.builder.run(**input_names_to_values)
messages = [ChatMessage.from_user(prompt["prompt"])]
try:
Expand Down
15 changes: 15 additions & 0 deletions haystack/utils/progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0

import os


def _get_progress_bar_setting(default: bool) -> bool:
"""Resolve the global override at execution time without changing component configuration."""
value = os.getenv("HAYSTACK_PROGRESS_BARS", "").strip().lower()
if value in {"1", "true", "yes", "on"}:
return True
if value in {"0", "false", "no", "off"}:
return False
return default
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
enhancements:
- |
Set ``HAYSTACK_PROGRESS_BARS`` to control progress bars in
``OpenAIDocumentEmbedder``, ``AzureOpenAIDocumentEmbedder``,
``MarkdownToDocument``, ``LLMEvaluator``, ``FaithfulnessEvaluator``, and
``ContextRelevanceEvaluator``. Use ``0``, ``false``, ``no``, or ``off`` to
disable them, or ``1``, ``true``, ``yes``, or ``on`` to enable them.
Values are case-insensitive and ignore surrounding whitespace. An unset,
empty, or unrecognized value uses the component's ``progress_bar`` setting.
The override is read at execution time for synchronous and asynchronous
runs without changing the component's stored or serialized configuration.
21 changes: 21 additions & 0 deletions test/components/converters/test_markdown_to_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest

from haystack.components.converters.markdown import MarkdownToDocument
from haystack.core.serialization import component_from_dict, component_to_dict
from haystack.dataclasses import ByteStream


Expand All @@ -26,6 +27,26 @@ def test_init_params_custom(self):
assert converter.progress_bar is False
assert converter.store_full_path is False

@pytest.mark.parametrize("progress_bar", [True, False])
def test_progress_bar_override_is_resolved_for_each_run(self, monkeypatch, progress_bar):
converter = MarkdownToDocument(progress_bar=progress_bar)
source = ByteStream(data=b"# Example\nDocument text.")
with patch(
"haystack.components.converters.markdown.tqdm", side_effect=lambda iterable, **kwargs: iterable
) as bar:
for value, expected in [("0", False), ("1", True), (None, progress_bar)]:
if value is None:
monkeypatch.delenv("HAYSTACK_PROGRESS_BARS", raising=False)
else:
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", value)
result = converter.run(sources=[source])
assert "Document text." in result["documents"][0].content
assert bar.call_args.kwargs["disable"] is not expected
assert converter.progress_bar is progress_bar
data = component_to_dict(obj=converter, name="converter")
restored = component_from_dict(cls=MarkdownToDocument, data=data, name="converter")
assert restored.progress_bar is progress_bar

@pytest.mark.integration
def test_run(self, test_files_path):
converter = MarkdownToDocument()
Expand Down
27 changes: 27 additions & 0 deletions test/components/embedders/test_azure_document_embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,40 @@
from openai import APIError, OpenAIError

import haystack.components.embedders.azure_document_embedder as azure_document_embedder_module
import haystack.components.embedders.openai_document_embedder as openai_document_embedder_module
from haystack import Document
from haystack.components.embedders import AzureOpenAIDocumentEmbedder
from haystack.utils.auth import Secret
from haystack.utils.azure import default_azure_ad_token_provider


class TestAzureOpenAIDocumentEmbedder:
@pytest.mark.asyncio
@pytest.mark.parametrize("run_async", [False, True])
async def test_inherited_progress_bar_override(self, monkeypatch, mock_azure_clients, run_async):
embedder = AzureOpenAIDocumentEmbedder(azure_endpoint="https://example-resource.azure.openai.com/")
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", "false")
sync_cls, async_cls = mock_azure_clients
response = Mock(
data=[Mock(embedding=[0.1, 0.2, 0.3])],
model="text-embedding-3-small",
usage={"prompt_tokens": 1, "total_tokens": 1},
)
sync_cls.return_value.embeddings.create.return_value = response
async_cls.return_value.embeddings.create = AsyncMock(return_value=response)
progress = Mock(side_effect=lambda iterable, **kwargs: iterable)
monkeypatch.setattr(openai_document_embedder_module, "async_tqdm" if run_async else "tqdm", progress)

documents = [Document(content="test document")]
result = await embedder.run_async(documents=documents) if run_async else embedder.run(documents=documents)

assert result["documents"][0].embedding == [0.1, 0.2, 0.3]
if run_async:
progress.assert_not_called()
else:
assert progress.call_args.kwargs["disable"] is True
assert embedder.progress_bar is True

def test_init_default(self, monkeypatch):
monkeypatch.setenv("AZURE_OPENAI_API_KEY", "fake-api-key")
embedder = AzureOpenAIDocumentEmbedder(azure_endpoint="https://example-resource.azure.openai.com/")
Expand Down
40 changes: 40 additions & 0 deletions test/components/embedders/test_openai_document_embedder.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,46 @@ def test_run_on_empty_list(self):
assert result["documents"] is not None
assert not result["documents"] # empty list

@pytest.mark.asyncio
@pytest.mark.parametrize("progress_bar", [False, True])
@pytest.mark.parametrize("run_async", [False, True])
async def test_progress_bar_override_is_resolved_each_run(
self, monkeypatch, mock_openai_clients, progress_bar, run_async
):
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", "false")
embedder = OpenAIDocumentEmbedder(progress_bar=progress_bar)
sync_cls, async_cls = mock_openai_clients
response = Mock(
data=[Mock(embedding=[0.1, 0.2, 0.3])],
model="text-embedding-3-small",
usage={"prompt_tokens": 1, "total_tokens": 1},
)
sync_cls.return_value.embeddings.create.return_value = response
async_cls.return_value.embeddings.create = AsyncMock(return_value=response)
progress = Mock(side_effect=lambda iterable, **kwargs: iterable)
monkeypatch.setattr(openai_document_embedder_module, "async_tqdm" if run_async else "tqdm", progress)

expected_progress_calls = 0
for override, expected in [("false", False), ("true", True), (None, progress_bar)]:
if override is None:
monkeypatch.delenv("HAYSTACK_PROGRESS_BARS")
else:
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", override)
documents = [Document(content="test document")]
result = await embedder.run_async(documents=documents) if run_async else embedder.run(documents=documents)

assert result["documents"][0].embedding == [0.1, 0.2, 0.3]
if run_async:
expected_progress_calls += int(expected)
assert progress.call_count == expected_progress_calls
else:
assert progress.call_args.kwargs["disable"] is not expected
assert embedder.progress_bar is progress_bar

monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", "true" if not progress_bar else "false")
restored = OpenAIDocumentEmbedder.from_dict(embedder.to_dict())
assert restored.progress_bar is progress_bar

def test_embed_batch_handles_exceptions_gracefully(self, caplog):
embedder = OpenAIDocumentEmbedder(api_key=Secret.from_token("fake_api_key"))
embedder.warm_up()
Expand Down
36 changes: 36 additions & 0 deletions test/components/evaluators/test_llm_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@


class TestLLMEvaluator:
@pytest.mark.asyncio
@pytest.mark.parametrize("progress_bar", [False, True])
@pytest.mark.parametrize("run_async", [False, True])
async def test_progress_bar_override_is_resolved_each_run(self, monkeypatch, progress_bar, run_async):
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", "false")
evaluator = LLMEvaluator(
instructions="Score the answer.",
inputs=[("predicted_answers", list[str])],
outputs=["score"],
examples=[{"inputs": {"predicted_answers": "Answer"}, "outputs": {"score": 1}}],
chat_generator=MockChatGenerator('{"score": 1}'),
progress_bar=progress_bar,
)
progress = Mock(side_effect=lambda iterable, **kwargs: iterable)
monkeypatch.setattr(
f"haystack.components.evaluators.llm_evaluator.{'async_tqdm' if run_async else 'tqdm'}", progress
)

for override, expected in [("false", False), ("true", True), (None, progress_bar)]:
if override is None:
monkeypatch.delenv("HAYSTACK_PROGRESS_BARS")
else:
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", override)
if run_async:
result = await evaluator.run_async(predicted_answers=["Answer"])
else:
result = evaluator.run(predicted_answers=["Answer"])

assert result["results"] == [{"score": 1}]
assert progress.call_args.kwargs["disable"] is not expected
assert evaluator.progress_bar is progress_bar

monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", "true" if not progress_bar else "false")
restored = LLMEvaluator.from_dict(evaluator.to_dict())
assert restored.progress_bar is progress_bar

def test_init_default(self, monkeypatch):
monkeypatch.setenv("OPENAI_API_KEY", "test-api-key")
component = LLMEvaluator(
Expand Down
29 changes: 29 additions & 0 deletions test/utils/test_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0

import pytest

from haystack.utils.progress import _get_progress_bar_setting


@pytest.mark.parametrize("value", ["1", "true", "yes", "on", " TRUE ", "On"])
def test_environment_can_enable_progress_bars(monkeypatch, value):
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", value)
assert _get_progress_bar_setting(default=False) is True


@pytest.mark.parametrize("value", ["0", "false", "no", "off", " FALSE ", "Off"])
def test_environment_can_disable_progress_bars(monkeypatch, value):
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", value)
assert _get_progress_bar_setting(default=True) is False


@pytest.mark.parametrize("default", [True, False])
@pytest.mark.parametrize("value", [None, "", " ", "invalid"])
def test_missing_or_unrecognized_override_preserves_component_setting(monkeypatch, default, value):
if value is None:
monkeypatch.delenv("HAYSTACK_PROGRESS_BARS", raising=False)
else:
monkeypatch.setenv("HAYSTACK_PROGRESS_BARS", value)
assert _get_progress_bar_setting(default=default) is default