Skip to content

Commit 5e1d8a3

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feat/atr-detection-rail
Signed-off-by: eeee2345 <217509886+eeee2345@users.noreply.github.com>
2 parents b3c07bb + 5bbd624 commit 5e1d8a3

21 files changed

Lines changed: 526 additions & 123 deletions

.github/workflows/_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ jobs:
9696
9797
- name: Run pytest with coverage
9898
if: inputs.with-coverage
99-
run: poetry run pytest --cov=nemoguardrails tests/ --cov-report=xml:coverage.xml -v
99+
run: make test-coverage
100100

101101
- name: Run pytest without coverage
102102
if: inputs.with-coverage == false
103-
run: poetry run pytest -v
103+
run: make test
104104

105105
- name: Upload coverage to Codecov
106106
if: inputs.with-coverage

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ jobs:
5555
run: poetry install --with dev
5656

5757
- name: Run pre-commit hooks
58-
run: poetry run make pre_commit
58+
run: poetry run make pre-commit

Makefile

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,48 @@
1-
.PHONY: all test tests test_watch test_coverage test_profile docs docs-strict docs-serve docs-update-cards docs-check-cards docs-watch-cards pre_commit help
1+
.PHONY: help
2+
.PHONY: test test-parallel test-serial test-benchmark test-watch test-coverage test-profile warm-fastembed-cache
3+
.PHONY: docs docs-strict docs-serve docs-update-cards docs-check-cards docs-watch-cards docs-check-redirects
4+
.PHONY: pre-commit
25

3-
# Default target executed when no specific target is provided to make.
4-
all: help
6+
.DEFAULT_GOAL := help
57

6-
# Define a variable for the test file path.
7-
TEST_FILE ?= tests/
8+
TEST ?=
9+
ARGS ?=
10+
WORKERS ?= auto
11+
# pytest-xdist --dist strategy for $(PYTEST) -n $(WORKERS) --dist $(DIST) $(ARGS) $(TEST).
12+
# worksteal dynamically rebalances queued tests; override DIST when debugging or grouping matters.
13+
DIST ?= worksteal
14+
15+
PYTEST ?= poetry run pytest
16+
# These targets assume a Unix-like shell for env -u; use bash, Git Bash, or WSL on Windows.
17+
UNIT_TEST_ENV ?= env -u OPENAI_API_KEY -u NVIDIA_API_KEY \
18+
-u LIVE_TEST -u LIVE_TEST_MODE -u TEST_LIVE_MODE
19+
20+
FASTEMBED_CACHE ?= .cache/fastembed
21+
FASTEMBED_MODEL ?= sentence-transformers/all-MiniLM-L6-v2
22+
FASTEMBED_ENV ?= env FASTEMBED_CACHE_PATH=$(FASTEMBED_CACHE)
823

924
test:
10-
poetry run pytest $(TEST_FILE)
25+
$(UNIT_TEST_ENV) $(PYTEST) -n $(WORKERS) --dist $(DIST) $(ARGS) $(TEST)
26+
27+
test-parallel: test
28+
29+
test-serial:
30+
$(PYTEST) $(ARGS) $(TEST)
1131

12-
tests:
13-
poetry run pytest $(TEST_FILE)
32+
test-benchmark:
33+
$(PYTEST) $(ARGS) benchmark/tests
1434

15-
test_watch:
16-
poetry run ptw --snapshot-update --now . -- -vv $(TEST_FILE)
35+
test-watch:
36+
poetry run ptw --snapshot-update --now . -- -vv $(ARGS) $(TEST)
1737

18-
test_coverage:
19-
poetry run pytest --cov=$(TEST_FILE) --cov-report=term-missing
38+
test-coverage:
39+
$(UNIT_TEST_ENV) $(PYTEST) -n $(WORKERS) --dist $(DIST) --cov=nemoguardrails --cov-report=xml:coverage.xml $(ARGS) $(TEST)
2040

21-
test_profile:
22-
poetry run pytest -vv tests/ --profile-svg
41+
test-profile:
42+
$(PYTEST) -vv --profile-svg $(ARGS) $(TEST)
43+
44+
warm-fastembed-cache:
45+
$(FASTEMBED_ENV) poetry run python -c 'from fastembed import TextEmbedding; model = TextEmbedding("$(FASTEMBED_MODEL)"); next(model.embed(["warmup"]))'
2346

2447
docs:
2548
poetry run sphinx-build -b html docs _build/docs
@@ -42,25 +65,38 @@ docs-watch-cards:
4265
docs-check-redirects:
4366
cd docs && poetry run python scripts/validate_redirects.py
4467

45-
pre_commit:
46-
pre-commit install
47-
pre-commit run --all-files
48-
49-
50-
# HELP
68+
pre-commit:
69+
poetry run pre-commit install
70+
poetry run pre-commit run --all-files
5171

5272
help:
53-
@echo '----'
54-
@echo 'test - run unit tests'
55-
@echo 'tests - run unit tests'
56-
@echo 'test TEST_FILE=<test_file> - run all tests in given file'
57-
@echo 'test_watch - run unit tests in watch mode'
58-
@echo 'test_coverage - run unit tests with coverage'
59-
@echo 'docs - build docs, if you installed the docs dependencies'
60-
@echo 'docs-strict - build docs with warnings as errors (used in CI)'
61-
@echo 'docs-serve - serve docs locally with auto-rebuild on changes'
62-
@echo 'docs-update-cards - update grid cards in index files from linked pages'
63-
@echo 'docs-check-cards - check if grid cards are up to date (dry run)'
64-
@echo 'docs-watch-cards - watch for file changes and auto-update cards'
65-
@echo 'docs-check-redirects - validate that all redirect targets exist'
66-
@echo 'pre_commit - run pre-commit hooks'
73+
@printf '%s\n' \
74+
'' \
75+
'Usage:' \
76+
' make test [TEST=path] [WORKERS=auto] [ARGS="-q --tb=short"]' \
77+
' make test-serial [TEST=path] [ARGS="-q"]' \
78+
' make test-benchmark [ARGS="-q"]' \
79+
' make test-parallel [TEST=path] [WORKERS=auto] [ARGS="-q --tb=short"]' \
80+
' make test-watch [TEST=path]' \
81+
'' \
82+
'Tests:' \
83+
' test Run pytest.ini testpaths with pytest-xdist' \
84+
' test-parallel Alias for test' \
85+
' test-serial Run pytest without xdist or env filtering' \
86+
' test-benchmark Run benchmark tooling tests' \
87+
' test-watch Run pytest in watch mode' \
88+
' test-coverage Run pytest with coverage' \
89+
' test-profile Run pytest with profiling' \
90+
' warm-fastembed-cache Prime the repo-local FastEmbed cache' \
91+
'' \
92+
'Docs:' \
93+
' docs Build docs' \
94+
' docs-strict Build docs with warnings as errors' \
95+
' docs-serve Serve docs locally' \
96+
' docs-update-cards Update generated docs cards' \
97+
' docs-check-cards Check generated docs cards' \
98+
' docs-watch-cards Watch and update generated docs cards' \
99+
' docs-check-redirects Validate docs redirects' \
100+
'' \
101+
'Maintenance:' \
102+
' pre-commit Install and run pre-commit hooks'

poetry.lock

Lines changed: 35 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ pytest = ">=7.2.2,<9.0.0"
164164
pytest-asyncio = ">=0.21.0, <1.0.0"
165165
pytest-cov = ">=4.1.0"
166166
pytest-httpx = ">=0.22.0"
167+
pytest-xdist = "^3.8.0"
167168
streamlit = ">=1.37.0"
168169
tox = "^4.23.2"
169170
pytest-profiling = "^1.7.0"

pytest.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ log_cli = False
1010

1111
asyncio_default_fixture_loop_scope = function
1212

13+
markers =
14+
serial: documentation-only marker for tests that may need serial scheduling
15+
slow: high-runtime tests that may need separate scheduling
16+
live: tests that require real provider credentials or external services
17+
real_embeddings: tests that intentionally exercise configured real embedding providers
18+
1319
testpaths =
1420
tests
1521
docs/colang-2/examples

tests/conftest.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,28 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
import os
1617
from unittest.mock import patch
1718

1819
import pytest
1920

21+
from tests.testing.embeddings import (
22+
DeterministicEmbeddingSearchProvider,
23+
)
24+
2025
try:
2126
import langchain_core # noqa: F401
2227
except ImportError:
2328
collect_ignore_glob = ["integrations/langchain/*.py", "integrations/langchain/**/*.py"]
2429

2530
REASONING_TRACE_MOCK_PATH = "nemoguardrails.actions.llm.generation.get_and_clear_reasoning_trace_contextvar"
31+
_FASTEMBED_ENGINES = {"FastEmbed"}
32+
_FASTEMBED_MODELS = {"all-MiniLM-L6-v2", "sentence-transformers/all-MiniLM-L6-v2"}
33+
_REAL_EMBEDDING_ENV_VARS = (
34+
"LIVE_TEST",
35+
"LIVE_TEST_MODE",
36+
"TEST_LIVE_MODE",
37+
)
2638

2739

2840
@pytest.fixture(autouse=True)
@@ -35,6 +47,53 @@ def reset_reasoning_trace_var():
3547
reasoning_trace_var.set(None)
3648

3749

50+
@pytest.fixture(autouse=True)
51+
def reset_tool_calls_var():
52+
from nemoguardrails.context import tool_calls_var
53+
54+
tool_calls_var.set(None)
55+
yield
56+
tool_calls_var.set(None)
57+
58+
59+
@pytest.fixture(autouse=True)
60+
def reset_explain_info_var():
61+
from nemoguardrails.context import explain_info_var
62+
63+
explain_info_var.set(None)
64+
yield
65+
explain_info_var.set(None)
66+
67+
68+
@pytest.fixture(autouse=True)
69+
def use_deterministic_embeddings_for_default_fastembed(monkeypatch, request):
70+
if request.node.get_closest_marker("real_embeddings") or any(
71+
os.environ.get(name) for name in _REAL_EMBEDDING_ENV_VARS
72+
):
73+
return
74+
75+
from nemoguardrails.rails.llm.llmrails import LLMRails
76+
77+
original_get_embedding_search_provider = LLMRails._get_embeddings_search_provider_instance
78+
79+
def patched_get_embedding_search_provider(self, esp_config=None):
80+
if esp_config is None or _uses_default_fastembed(
81+
esp_config,
82+
self.default_embedding_engine,
83+
self.default_embedding_model,
84+
):
85+
parameters = getattr(esp_config, "parameters", {}) if esp_config is not None else {}
86+
return DeterministicEmbeddingSearchProvider(**parameters)
87+
88+
return original_get_embedding_search_provider(self, esp_config)
89+
90+
monkeypatch.setattr(
91+
LLMRails,
92+
"_get_embeddings_search_provider_instance",
93+
patched_get_embedding_search_provider,
94+
)
95+
96+
3897
@pytest.fixture
3998
def langchain_framework():
4099
from nemoguardrails.llm.frameworks import _reset_frameworks, set_default_framework
@@ -47,3 +106,13 @@ def langchain_framework():
47106

48107
def pytest_configure(config):
49108
patch("prompt_toolkit.PromptSession", autospec=True).start()
109+
110+
111+
def _uses_default_fastembed(provider_config, default_engine, default_model):
112+
if provider_config.name != "default":
113+
return False
114+
115+
parameters = provider_config.parameters
116+
engine = parameters.get("embedding_engine", default_engine)
117+
model = parameters.get("embedding_model", default_model)
118+
return engine in _FASTEMBED_ENGINES and model in _FASTEMBED_MODELS

0 commit comments

Comments
 (0)