Implemented the spoken_word LLM review module

This commit is contained in:
2026-04-25 08:12:36 -05:00
parent 92c8c371a6
commit 52d29f7228
13 changed files with 484 additions and 13 deletions

View File

@@ -6,10 +6,11 @@ from audita.core.errors import AuditaLLMError
from audita.core.schemas import parse_glossary_yaml, parse_transcript_json
from audita.framework.models import CorrectionProposal, ModuleRunSpec
from audita.validators.base import ValidationContext
from audita.validators.llm import MeaningReversalValidator, SpokenFormPlausibilityValidator
from audita.validators.llm import MeaningReversalValidator, SpokenFormPlausibilityValidator, SpokenWordValidator
from audita.validators.prompts import (
build_meaning_reversal_messages,
build_spoken_form_plausibility_messages,
build_spoken_word_messages,
)
import audita.validators.llm as llm_module
@@ -204,6 +205,110 @@ def test_meaning_reversal_validator_rejects_reversal_and_approves_nonreversal(tm
assert "The figure became invisible in the doorway." in prompt_text
def test_spoken_word_validator_approves_cleanup_and_rejects_rewrite(tmp_path):
transcript = parse_transcript_json(
"""
[
{"id": 1, "speaker": "A", "start": 0.0, "end": 1.0, "text": "I, uh, I think we should go."},
{"id": 2, "speaker": "A", "start": 1.0, "end": 2.0, "text": "We should maybe proceed carefully."}
]
"""
)
proposals = [
CorrectionProposal(
proposal_index=0,
module_instance="spoken_word",
module_key="spoken_word",
id=1,
original_text="I, uh, I think",
corrected_text="I think",
confidence=0.95,
),
CorrectionProposal(
proposal_index=1,
module_instance="spoken_word",
module_key="spoken_word",
id=2,
original_text="maybe proceed carefully",
corrected_text="go now",
confidence=0.95,
),
]
client = FakeStructuredLLMClient(
[
{
"validations": [
{
"correction_index": 0,
"approved": True,
"confidence": 0.97,
"reason": "Reasonable dysfluency cleanup that preserves meaning.",
},
{
"correction_index": 1,
"approved": False,
"confidence": 0.99,
"reason": "This changes the substance of the segment rather than cleaning a dysfluency.",
},
]
}
]
)
result = SpokenWordValidator("spoken_word_review").validate(
_context(proposals=proposals, transcript=transcript, llm_client=client, tmp_path=tmp_path)
)
assert [(decision.proposal_index, decision.approved) for decision in result.decisions] == [
(0, True),
(1, False),
]
prompt_text = client.calls[0]["messages"][1]["content"]
assert "I think we should go." in prompt_text
assert "go now" in prompt_text
def test_spoken_word_validator_allows_punctuation_cleanup_tied_to_dysfluency(tmp_path):
transcript = parse_transcript_json(
"""
[
{"id": 1, "speaker": "A", "start": 0.0, "end": 1.0, "text": "Well ... I think we should go."}
]
"""
)
proposals = [
CorrectionProposal(
proposal_index=0,
module_instance="spoken_word",
module_key="spoken_word",
id=1,
original_text="Well ... ",
corrected_text="",
confidence=0.95,
)
]
client = FakeStructuredLLMClient(
[
{
"validations": [
{
"correction_index": 0,
"approved": True,
"confidence": 0.95,
"reason": "Removes a hesitation artifact without changing substantive meaning.",
}
]
}
]
)
result = SpokenWordValidator("spoken_word_review").validate(
_context(proposals=proposals, transcript=transcript, llm_client=client, tmp_path=tmp_path)
)
assert [(decision.proposal_index, decision.approved) for decision in result.decisions] == [(0, True)]
@pytest.mark.parametrize(
("validator", "payload", "message_fragment"),
[
@@ -246,6 +351,20 @@ def test_meaning_reversal_validator_rejects_reversal_and_approves_nonreversal(tm
},
"unknown correction_index",
),
(
SpokenWordValidator("spoken_word_review"),
{
"validations": [
{
"correction_index": 99,
"approved": True,
"confidence": 0.9,
"reason": "unknown",
}
]
},
"unknown correction_index",
),
],
)
def test_llm_validators_reject_bad_correction_indexes(tmp_path, validator, payload, message_fragment):
@@ -356,6 +475,27 @@ def test_meaning_reversal_prompt_emphasizes_antonyms_and_segment_context():
assert "original_segment_text" in messages[1]["content"]
def test_spoken_word_prompt_emphasizes_dysfluency_cleanup():
messages = build_spoken_word_messages(
[
{
"correction_index": 0,
"id": 1,
"original_segment_text": "I, uh, I think we should go.",
"corrected_segment_text": "I think we should go.",
"original_text": "I, uh, I think",
"corrected_text": "I think",
}
]
)
combined = messages[0]["content"] + messages[1]["content"]
assert "dysfluencies" in combined
assert "punctuation" in combined
assert "substantive meaning" in combined
assert "original_segment_text" in messages[1]["content"]
def test_llm_validators_use_shared_token_batching_helper(monkeypatch, tmp_path):
transcript = parse_transcript_json(
"""