mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
Second, deeper pass over tools/gateway/hermes_cli plus first pass over the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker, dashboard, conformance, monitoring, secret_sources, hermes_state, providers). Same rubric as wave 1 (AGENTS.md test policy); security, alternation/caching invariants, issue-number regressions, and E2E kept. Real test-quality fixes found and rooted out along the way: - tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls (DEFAULT_CONFIG smart-approval leaked in) — pinned approval mode=manual via autouse fixture: 17.4s → 0.4s. - test_model_switch_custom_providers.py / test_user_providers_model_switch.py silently probed live provider catalogs (~2s/test) — stubbed cached_provider_model_ids/provider_model_ids/fetch_api_models. - test_telegram_noise_filter.py: 15-platform copy-paste matrix over shared gateway.run logic → 3 representative platforms (55s → 3.9s). - test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on MagicMock agents — interrupt.side_effect now clears _running_agents (22s → 1.0s). - test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait 5s → 0.5s. - test_telegram_init_deadline.py: loop-block margin restored to 1.0s with rationale comment — the watchdog-dump assertion needs the loop blocked well past deadline+grace under parallel load (flaked once in the 40-worker verification run at a 0.2s margin). Verification: full hermetic suite via scripts/run_tests.sh — 2,438 files, 21,718 tests passed, 0 failed, 293.9s wall. Suite totals vs original baseline: 46,820 → 19,757 test functions (−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
110 lines
4.3 KiB
Python
110 lines
4.3 KiB
Python
"""Tests for the local faster-whisper silence-hallucination hardening.
|
|
|
|
One shared kwargs owner (`build_local_transcribe_kwargs`) must apply the
|
|
three-layer fix at every local whisper call site:
|
|
|
|
1. Silero VAD filter on by default (``stt.local.vad: false`` restores raw).
|
|
2. ``condition_on_previous_text=False`` always.
|
|
3. Segment confidence gate: drop segments only when the model BOTH thinks
|
|
the window is non-speech AND decoded it with low confidence — quiet but
|
|
real speech must survive.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
|
|
from tools.transcription_tools import (
|
|
_LOGPROB_THRESHOLD_DEFAULT,
|
|
_NO_SPEECH_PROB_THRESHOLD_DEFAULT,
|
|
_is_hallucinated_segment,
|
|
_join_confident_segments,
|
|
build_local_transcribe_kwargs,
|
|
)
|
|
|
|
|
|
def _seg(text, no_speech_prob=0.0, avg_logprob=-0.2):
|
|
return SimpleNamespace(text=text, no_speech_prob=no_speech_prob, avg_logprob=avg_logprob)
|
|
|
|
|
|
class TestBuildLocalTranscribeKwargs:
|
|
def test_vad_on_by_default(self):
|
|
kwargs = build_local_transcribe_kwargs({})
|
|
assert kwargs["vad_filter"] is True
|
|
assert kwargs["vad_parameters"] == {"min_silence_duration_ms": 500}
|
|
|
|
def test_conditioning_always_off(self):
|
|
assert build_local_transcribe_kwargs({})["condition_on_previous_text"] is False
|
|
assert (
|
|
build_local_transcribe_kwargs({"local": {"vad": False}})[
|
|
"condition_on_previous_text"
|
|
]
|
|
is False
|
|
)
|
|
|
|
|
|
def test_language_and_prompt_resolved(self, monkeypatch):
|
|
monkeypatch.delenv("HERMES_LOCAL_STT_LANGUAGE", raising=False)
|
|
cfg = {"language": "en", "local": {"initial_prompt": "Hermes glossary"}}
|
|
kwargs = build_local_transcribe_kwargs(cfg)
|
|
assert kwargs["language"] == "en"
|
|
assert kwargs["initial_prompt"] == "Hermes glossary"
|
|
|
|
|
|
class TestConfidenceGate:
|
|
def test_high_no_speech_and_low_logprob_dropped(self):
|
|
seg = _seg(" You", no_speech_prob=0.9, avg_logprob=-1.5)
|
|
assert _is_hallucinated_segment(
|
|
seg, _NO_SPEECH_PROB_THRESHOLD_DEFAULT, _LOGPROB_THRESHOLD_DEFAULT
|
|
)
|
|
|
|
def test_quiet_but_confident_speech_survives(self):
|
|
# High no_speech_prob alone must NOT drop a segment the model decoded
|
|
# confidently (quiet-but-real speech).
|
|
seg = _seg(" hello there", no_speech_prob=0.8, avg_logprob=-0.3)
|
|
assert not _is_hallucinated_segment(
|
|
seg, _NO_SPEECH_PROB_THRESHOLD_DEFAULT, _LOGPROB_THRESHOLD_DEFAULT
|
|
)
|
|
|
|
|
|
def test_garbage_thresholds_fall_back_to_defaults(self):
|
|
seg = _seg(" ok", no_speech_prob=0.1, avg_logprob=-0.1)
|
|
cfg = {"no_speech_prob_threshold": "high", "logprob_threshold": None}
|
|
assert _join_confident_segments([seg], cfg) == "ok"
|
|
|
|
|
|
class TestTranscribeLocalWiring:
|
|
"""_transcribe_local must pass the shared hardened kwargs to the model."""
|
|
|
|
def _run(self, monkeypatch, stt_config, segments=None):
|
|
import tools.transcription_tools as tt
|
|
|
|
captured = {}
|
|
|
|
class FakeModel:
|
|
def transcribe(self, path, **kwargs):
|
|
captured.update(kwargs)
|
|
info = SimpleNamespace(language="en", duration=1.0)
|
|
return iter(segments or [_seg(" hi")]), info
|
|
|
|
monkeypatch.setattr(tt, "_HAS_FASTER_WHISPER", True)
|
|
monkeypatch.setattr(tt, "_local_model", FakeModel())
|
|
monkeypatch.setattr(tt, "_local_model_name", "base")
|
|
monkeypatch.setattr(tt, "_load_stt_config", lambda: stt_config)
|
|
monkeypatch.delenv("HERMES_LOCAL_STT_LANGUAGE", raising=False)
|
|
result = tt._transcribe_local("/tmp/fake.wav", "base")
|
|
return captured, result
|
|
|
|
def test_hardened_kwargs_reach_model(self, monkeypatch):
|
|
captured, result = self._run(monkeypatch, {})
|
|
assert result["success"] is True
|
|
assert captured["vad_filter"] is True
|
|
assert captured["vad_parameters"] == {"min_silence_duration_ms": 500}
|
|
assert captured["condition_on_previous_text"] is False
|
|
|
|
|
|
def test_hallucinated_segments_filtered_from_transcript(self, monkeypatch):
|
|
segments = [
|
|
_seg(" real speech"),
|
|
_seg(" Дякую за перегляд!", no_speech_prob=0.97, avg_logprob=-1.6),
|
|
]
|
|
_, result = self._run(monkeypatch, {}, segments=segments)
|
|
assert result["transcript"] == "real speech"
|