Simplify editor cleanup and keep live ASR metadata
Some checks are pending
ci / test-and-build (push) Waiting to run

Keep the daemon path on the full ASR result so word timings and detected language survive into the editor pipeline instead of falling back to a plain transcript string.

Add PipelineEngine.run_asr_result(), have aman call it when live ASR data is available, and cover the word-aware alignment behavior in the daemon tests.

Collapse the llama cleanup flow to a single JSON-shaped completion while leaving the legacy pass1/pass2 parameters in place as compatibility no-ops.

Validated with PYTHONPATH=src python3 -m unittest tests.test_aiprocess tests.test_aman.
This commit is contained in:
Thales Maciel 2026-03-12 13:24:36 -03:00
parent 8c1f7c1e13
commit fa91f313c4
No known key found for this signature in database
GPG key ID: 33112E6833C34679
5 changed files with 166 additions and 84 deletions

View file

@ -186,6 +186,29 @@ class LlamaWarmupTests(unittest.TestCase):
with self.assertRaisesRegex(RuntimeError, "expected JSON"):
processor.warmup(profile="default")
def test_process_with_metrics_uses_single_completion_timing_shape(self):
processor = object.__new__(LlamaProcessor)
client = _WarmupClient(
{"choices": [{"message": {"content": '{"cleaned_text":"friday"}'}}]}
)
processor.client = client
cleaned_text, timings = processor.process_with_metrics(
"thursday, I mean friday",
lang="en",
dictionary_context="",
profile="default",
)
self.assertEqual(cleaned_text, "friday")
self.assertEqual(len(client.calls), 1)
call = client.calls[0]
self.assertEqual(call["messages"][0]["content"], aiprocess.SYSTEM_PROMPT)
self.assertIn('{"cleaned_text":"..."}', call["messages"][1]["content"])
self.assertEqual(timings.pass1_ms, 0.0)
self.assertGreater(timings.pass2_ms, 0.0)
self.assertEqual(timings.pass2_ms, timings.total_ms)
class ModelChecksumTests(unittest.TestCase):
def test_accepts_expected_checksum_case_insensitive(self):

View file

@ -12,6 +12,7 @@ if str(SRC) not in sys.path:
import aman
from config import Config, VocabularyReplacement
from stages.asr_whisper import AsrResult, AsrSegment, AsrWord
class FakeDesktop:
@ -144,6 +145,21 @@ class FakeStream:
self.close_calls += 1
def _asr_result(text: str, words: list[str], *, language: str = "auto") -> AsrResult:
asr_words: list[AsrWord] = []
start = 0.0
for token in words:
asr_words.append(AsrWord(text=token, start_s=start, end_s=start + 0.1, prob=0.9))
start += 0.2
return AsrResult(
raw_text=text,
language=language,
latency_ms=5.0,
words=asr_words,
segments=[AsrSegment(text=text, start_s=0.0, end_s=max(start, 0.1))],
)
class DaemonTests(unittest.TestCase):
def _config(self) -> Config:
cfg = Config()
@ -248,6 +264,53 @@ class DaemonTests(unittest.TestCase):
self.assertEqual(desktop.inject_calls, [])
self.assertEqual(daemon.get_state(), aman.State.IDLE)
@patch("aman.stop_audio_recording", return_value=FakeAudio(8))
@patch("aman.start_audio_recording", return_value=(object(), object()))
def test_live_path_uses_asr_words_for_alignment_correction(self, _start_mock, _stop_mock):
desktop = FakeDesktop()
ai_processor = FakeAIProcessor()
daemon = self._build_daemon(desktop, FakeModel(), verbose=False, ai_processor=ai_processor)
daemon.asr_stage.transcribe = lambda _audio: _asr_result(
"set alarm for 6 i mean 7",
["set", "alarm", "for", "6", "i", "mean", "7"],
language="en",
)
daemon._start_stop_worker = (
lambda stream, record, trigger, process_audio: daemon._stop_and_process(
stream, record, trigger, process_audio
)
)
daemon.toggle()
daemon.toggle()
self.assertEqual(desktop.inject_calls, [("set alarm for 7", "clipboard", False)])
self.assertEqual(ai_processor.last_kwargs.get("lang"), "en")
@patch("aman.stop_audio_recording", return_value=FakeAudio(8))
@patch("aman.start_audio_recording", return_value=(object(), object()))
def test_live_path_calls_word_aware_pipeline_entrypoint(self, _start_mock, _stop_mock):
desktop = FakeDesktop()
daemon = self._build_daemon(desktop, FakeModel(), verbose=False)
asr_result = _asr_result(
"set alarm for 6 i mean 7",
["set", "alarm", "for", "6", "i", "mean", "7"],
language="en",
)
daemon.asr_stage.transcribe = lambda _audio: asr_result
daemon._start_stop_worker = (
lambda stream, record, trigger, process_audio: daemon._stop_and_process(
stream, record, trigger, process_audio
)
)
with patch.object(daemon.pipeline, "run_asr_result", wraps=daemon.pipeline.run_asr_result) as run_asr:
daemon.toggle()
daemon.toggle()
run_asr.assert_called_once()
self.assertIs(run_asr.call_args.args[0], asr_result)
def test_transcribe_skips_hints_when_model_does_not_support_them(self):
desktop = FakeDesktop()
model = FakeModel(text="hello")