bb584b6ea0
This commit removes the `whisper_variant` and `whisper_hotwords_variant` fields from the `run_id` generation and the `print_meta_summary` function. These variants are no longer used as the project is shifting focus to LLM-based generation. The `run_full_case.rs` example has also been updated to reflect this change.
99 lines
3.3 KiB
Rust
99 lines
3.3 KiB
Rust
//! Helpers shared by the experiment binaries.
|
|
//!
|
|
//! Sandbox tooling for iterating on Whisper / LLM prompts against a real case
|
|
//! without modifying production state. All outputs land under `case_<id>/runs/`
|
|
//! and `case_<id>/compare/` — never in `tmpdata/`.
|
|
//!
|
|
//! **Security note**: The Ionos bearer key flows through this code as a
|
|
//! `String` inside `Settings.llm.api_key`. Helpers here MUST NOT persist or
|
|
//! print the key. `RunMeta` deliberately stores only the URL/model/timestamp —
|
|
//! never the key — so on-disk audit logs are safe to share.
|
|
|
|
use std::path::Path;
|
|
|
|
use anyhow::Context;
|
|
use serde::Serialize;
|
|
use time::OffsetDateTime;
|
|
use time::format_description::well_known::Iso8601;
|
|
|
|
/// Generate a run identifier in the form
|
|
/// `<UTC-ISO>_l-<lid>_run<n>`. Used as the directory name under
|
|
/// `case_<id>/runs/`.
|
|
pub fn run_id(llm_variant: &str, run_index: u32) -> String {
|
|
let now = OffsetDateTime::now_utc();
|
|
let stamp = now
|
|
.format(&Iso8601::DEFAULT)
|
|
.unwrap_or_else(|_| "unknown".into());
|
|
let safe_stamp = stamp.replace(':', "-").replace('.', "-");
|
|
format!("{safe_stamp}_l-{llm_variant}_run{run_index}")
|
|
}
|
|
|
|
/// Read a prompt file. Returns empty string for an empty file (used as
|
|
/// "no prompt" signal by `run_full_case`'s settings override).
|
|
pub fn load_prompt(path: &Path) -> anyhow::Result<String> {
|
|
let content = std::fs::read_to_string(path)
|
|
.with_context(|| format!("reading prompt file {}", path.display()))?;
|
|
Ok(content.trim_end_matches('\n').to_string())
|
|
}
|
|
|
|
/// Atomically write content to `path` via a sibling tempfile + rename.
|
|
/// Same pattern the server uses for `document.md` — ensures readers never
|
|
/// observe a partial file.
|
|
pub fn atomic_write(path: &Path, content: &str) -> anyhow::Result<()> {
|
|
let parent = path
|
|
.parent()
|
|
.with_context(|| format!("path {} has no parent", path.display()))?;
|
|
std::fs::create_dir_all(parent)?;
|
|
let mut tmp = tempfile::Builder::new()
|
|
.prefix(".tmp-")
|
|
.tempfile_in(parent)?;
|
|
use std::io::Write;
|
|
tmp.write_all(content.as_bytes())?;
|
|
tmp.flush()?;
|
|
tmp.persist(path)
|
|
.map_err(|e| anyhow::anyhow!("persist failed: {}", e.error))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Snapshot of variant identities + provenance for one run. Serialized
|
|
/// as `meta.json` next to the produced `document.md`. **Never contains
|
|
/// the API key** — only the LLM endpoint URL and model name.
|
|
#[derive(Debug, Serialize)]
|
|
pub struct RunMeta {
|
|
pub run_id: String,
|
|
pub created_at_utc: String,
|
|
pub case: String,
|
|
pub llm_variant: String,
|
|
pub whisper_url: String,
|
|
pub llm_url: String,
|
|
pub llm_model: String,
|
|
pub llm_temperature: f32,
|
|
pub vocab_dir: String,
|
|
pub vocab_loaded: usize,
|
|
pub gazetteer_dict: GazetteerDictMode,
|
|
pub timings_ms: RunTimings,
|
|
pub recordings: Vec<RecordingMeta>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub enum GazetteerDictMode {
|
|
NoDict,
|
|
SpellbookHunspell { stem: String },
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub struct RunTimings {
|
|
pub whisper_total_ms: u64,
|
|
pub gazetteer_total_ms: u64,
|
|
pub llm_ms: u64,
|
|
pub wallclock_ms: u64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct RecordingMeta {
|
|
pub recorded_at: String,
|
|
pub audio_path: String,
|
|
pub whisper_chars_raw: usize,
|
|
pub whisper_chars_after_gazetteer: usize,
|
|
}
|