swarm-stt: the request subject, the service's NATS permissions and the --stt-client flag
This commit is contained in:
parent
40ec5ac9e0
commit
47bec6bef3
2 changed files with 171 additions and 1 deletions
|
|
@ -28,6 +28,7 @@ pub mod review_classifier;
|
|||
pub mod router_api;
|
||||
pub mod router_sink;
|
||||
pub mod router_todo;
|
||||
pub mod stt;
|
||||
pub mod subagent_term;
|
||||
pub mod wanted;
|
||||
|
||||
|
|
|
|||
84
swarm-wire/src/stt.rs
Normal file
84
swarm-wire/src/stt.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
//! The speech-to-text service's request: the subject an agent sends a voice
|
||||
//! memo on, and what comes back.
|
||||
//!
|
||||
//! Three crates name these: an agent's harness sends the audio, `swarm-stt`
|
||||
//! answers, and the auth-callout responder grants each its side. The request
|
||||
//! body is the raw bytes of the audio file, nothing wrapped around them, so
|
||||
//! the service hashes exactly what the sender held. The answer is a
|
||||
//! [`Response`] as JSON on the request's reply subject.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The subject an agent sends its audio on, `{prefix}.<agent>`. The agent's own
|
||||
/// name ends it, so an agent's token can publish only to its own subject, and
|
||||
/// the service learns from the subject who asked.
|
||||
pub const SUBJECT_PREFIX: &str = "$SWARM.stt.transcribe";
|
||||
|
||||
/// The subject agent `agent` sends audio on.
|
||||
#[must_use]
|
||||
pub fn request_subject(agent: &str) -> String {
|
||||
format!("{SUBJECT_PREFIX}.{agent}")
|
||||
}
|
||||
|
||||
/// What the service subscribes to: every agent's request subject.
|
||||
#[must_use]
|
||||
pub fn subject_wildcard() -> String {
|
||||
format!("{SUBJECT_PREFIX}.*")
|
||||
}
|
||||
|
||||
/// How long a sender waits for the answer, and the queue lets the service
|
||||
/// reply: the longest run, plus the time a request may wait for the engine's
|
||||
/// one slot.
|
||||
pub const REPLY_DEADLINE: Duration = Duration::from_mins(10);
|
||||
|
||||
/// The service's answer to one request.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "status", rename_all = "snake_case")]
|
||||
pub enum Response {
|
||||
/// The audio was transcribed, now or earlier.
|
||||
Ok {
|
||||
/// The text spoken in the audio (empty for silence).
|
||||
text: String,
|
||||
/// `true` when no engine run was started for this request.
|
||||
cached: bool,
|
||||
},
|
||||
/// The service could not transcribe it; the message says why and is safe
|
||||
/// to show to the sender.
|
||||
Failed {
|
||||
/// Why.
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Response, request_subject, subject_wildcard};
|
||||
|
||||
#[test]
|
||||
fn an_agent_sends_on_its_own_subject_and_the_service_hears_them_all() {
|
||||
assert_eq!(request_subject("iris"), "$SWARM.stt.transcribe.iris");
|
||||
assert_eq!(subject_wildcard(), "$SWARM.stt.transcribe.*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_response_travels_as_tagged_json() {
|
||||
let ok = Response::Ok {
|
||||
text: "hello".into(),
|
||||
cached: true,
|
||||
};
|
||||
let json = serde_json::to_string(&ok).unwrap();
|
||||
assert_eq!(json, r#"{"status":"ok","text":"hello","cached":true}"#);
|
||||
assert_eq!(serde_json::from_str::<Response>(&json).unwrap(), ok);
|
||||
|
||||
let failed: Response =
|
||||
serde_json::from_str(r#"{"status":"failed","message":"too large"}"#).unwrap();
|
||||
assert_eq!(
|
||||
failed,
|
||||
Response::Failed {
|
||||
message: "too large".into()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue