Skip to content

interleave metric and nonblock IO#76

Merged
raghavm243512 merged 6 commits intomainfrom
pr/rm/speedup
Apr 25, 2026
Merged

interleave metric and nonblock IO#76
raghavm243512 merged 6 commits intomainfrom
pr/rm/speedup

Conversation

@raghavm243512
Copy link
Copy Markdown
Collaborator

@raghavm243512 raghavm243512 commented Apr 23, 2026

Changes:

allow validation and metrics to run on successful conversations even if other conversations need rerun. These will be saved and used at the final pass once all retries are done or success is for all records

Much smaller changes:
Release semaphore before doing expensive writes for audio

Don't block main thread with IO. Files reads are large enough where this will help somewhat, Probably not a lot though.

@raghavm243512 raghavm243512 marked this pull request as ready for review April 23, 2026 22:01
Comment thread src/eva/orchestrator/runner.py Outdated
Comment on lines +263 to +264
*[_run_and_pipeline(output_id_to_record[oid], oid) for oid in pending_output_ids],
return_exceptions=True,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we except Exception inside _run_and_pipeline(), we don't expect _run_and_pipeline() to raise anything. Can you make that intentional by removing return_exceptions=True?

Also, it's a tiny detail, but since we're here, we don't need to instantiate a list; we can simply use a generator (in parentheses).

Suggested change
*[_run_and_pipeline(output_id_to_record[oid], oid) for oid in pending_output_ids],
return_exceptions=True,
*(_run_and_pipeline(output_id_to_record[oid], oid) for oid in pending_output_ids)

Comment thread src/eva/orchestrator/runner.py Outdated
Comment on lines +274 to +276
if isinstance(item, Exception):
logger.error(f"Unexpected pipeline coroutine exception: {item}")
continue
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, that shouldn't happen, so I think we can clean that up. Also, if that even happened, this wouldn't add the sample to either finished_ids or not_finished_ids, and it would disappear, which seems a bit broken. Does that make sense?

Comment thread src/eva/assistant/base_server.py Outdated
Comment on lines +122 to +137
if not self._audio_buffer and self.user_audio_buffer and self.assistant_audio_buffer:
diff_bytes = abs(len(self.user_audio_buffer) - len(self.assistant_audio_buffer))
diff_ms = diff_bytes / (2 * self._audio_sample_rate) * 1000
if diff_ms > 500:
logger.warning(
f"Audio buffer length mismatch: user={len(self.user_audio_buffer)} "
f"assistant={len(self.assistant_audio_buffer)} "
f"diff={diff_ms:.0f}ms — mixed recording may be temporally skewed"
)
from eva.assistant.audio_bridge import pcm16_mix # lazy: avoids circular import at module load

self._audio_buffer = bytearray(pcm16_mix(bytes(self.user_audio_buffer), bytes(self.assistant_audio_buffer)))
elif not self._audio_buffer and self.user_audio_buffer:
self._audio_buffer = bytearray(self.user_audio_buffer)
elif not self._audio_buffer and self.assistant_audio_buffer:
self._audio_buffer = bytearray(self.assistant_audio_buffer)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detail: I think a single if not self._audio_buffer would be clearer than repeating 3 times.

Suggested change
if not self._audio_buffer and self.user_audio_buffer and self.assistant_audio_buffer:
diff_bytes = abs(len(self.user_audio_buffer) - len(self.assistant_audio_buffer))
diff_ms = diff_bytes / (2 * self._audio_sample_rate) * 1000
if diff_ms > 500:
logger.warning(
f"Audio buffer length mismatch: user={len(self.user_audio_buffer)} "
f"assistant={len(self.assistant_audio_buffer)} "
f"diff={diff_ms:.0f}ms — mixed recording may be temporally skewed"
)
from eva.assistant.audio_bridge import pcm16_mix # lazy: avoids circular import at module load
self._audio_buffer = bytearray(pcm16_mix(bytes(self.user_audio_buffer), bytes(self.assistant_audio_buffer)))
elif not self._audio_buffer and self.user_audio_buffer:
self._audio_buffer = bytearray(self.user_audio_buffer)
elif not self._audio_buffer and self.assistant_audio_buffer:
self._audio_buffer = bytearray(self.assistant_audio_buffer)
if not self._audio_buffer:
if self.user_audio_buffer and self.assistant_audio_buffer:
diff_bytes = abs(len(self.user_audio_buffer) - len(self.assistant_audio_buffer))
diff_ms = diff_bytes / (2 * self._audio_sample_rate) * 1000
if diff_ms > 500:
logger.warning(
f"Audio buffer length mismatch: user={len(self.user_audio_buffer)} "
f"assistant={len(self.assistant_audio_buffer)} "
f"diff={diff_ms:.0f}ms — mixed recording may be temporally skewed"
)
from eva.assistant.audio_bridge import pcm16_mix # lazy: avoids circular import at module load
self._audio_buffer = bytearray(
pcm16_mix(bytes(self.user_audio_buffer), bytes(self.assistant_audio_buffer))
)
elif self.user_audio_buffer:
self._audio_buffer = bytearray(self.user_audio_buffer)
elif self.assistant_audio_buffer:
self._audio_buffer = bytearray(self.assistant_audio_buffer)

Comment thread src/eva/assistant/pipecat_server.py Outdated
Comment on lines +727 to +729
if isinstance(self.pipeline_config, SpeechToSpeechConfig):
self.audit_log.save_transcript_jsonl(transcript_path)
elif not transcript_path.exists():
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(self.pipeline_config, SpeechToSpeechConfig):
self.audit_log.save_transcript_jsonl(transcript_path)
elif not transcript_path.exists():
if isinstance(self.pipeline_config, SpeechToSpeechConfig) or not transcript_path.exists():

logger.error(f"Error saving agent perf stats: {e}", exc_info=True)

# Call base class to save audit_log, audio, scenario DBs, latencies
await super().save_outputs()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicating most of super().save_outputs(), instead of calling it, worries me about them going out of sync. Have you considered refactoring so that you can keep calling super().save_outputs()?

Comment thread src/eva/orchestrator/runner.py Outdated
# Phase 6: Fire metrics immediately if passed
if vr.passed and metrics_runner is not None:
rdir = self.output_dir / "records" / output_id
task = asyncio.create_task(metrics_runner._run_and_save_record(output_id, rdir))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we call _run_and_save_record() from two places outside of running.py, should we remove the "private" underscore prefix?

# STEP 7: Run full metrics on successful records
# STEP 7: Await background metrics, then run final aggregation pass.
# Background tasks already wrote metrics.json for records validated during the loop.
# The final run() skips already-computed records and only does summary aggregation.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're now mutating metrics_runner.record_ids, I think we need to be extra careful with the ordering of the steps. Would adding a comment like that make sense?

Suggested change
# The final run() skips already-computed records and only does summary aggregation.
# The final run() skips already-computed records and only does summary aggregation.
# IMPORTANT: Must await all background tasks BEFORE mutating metrics_runner.record_ids
# below — running tasks read record_ids to filter which records to process.

@raghavm243512 raghavm243512 added this pull request to the merge queue Apr 25, 2026
Merged via the queue into main with commit d4fabff Apr 25, 2026
1 check passed
@raghavm243512 raghavm243512 deleted the pr/rm/speedup branch April 25, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants