Files
settled-reach/server/sr-voice/src/prompt.rs
T
jpmschweitzerandClaude Opus 4.6 1b58d8f949 feat(engine): add sr-voice LLM inference service for NPC voice pipeline
Standalone Rust crate wrapping llama-cpp-2 for GGUF model inference.
Persistent HTTP server architecture — model loaded once, requests
processed sequentially, zero CPU contention by construction.

Subcommands: serve (load model, listen), generate (single prompt),
batch (JSONL), benchmark (5-run average). Makefile targets for
build/serve/run/stop workflow.

Spike 1 validated: Gemma 2B Q4_K_M at ~16 t/s CPU, 4 cultures
tested (Krenn, Ireland, Shek'na, Aranthi), composition-engine
oath injection mechanism proven. GO for Spike 2.

Refs: D-138, #639

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 15:44:37 +01:00

42 lines
1.2 KiB
Rust

use serde::{Deserialize, Serialize};
use std::io::BufRead;
use crate::VoiceError;
/// Content types for voice generation.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContentType {
Behavior,
Dialogue,
Tell,
}
/// A single prompt payload, used in batch JSONL mode.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptPayload {
pub id: String,
pub content_type: ContentType,
pub prompt: String,
#[serde(default)]
pub base_text: Option<String>,
#[serde(default)]
pub semantic_core: Option<String>,
}
/// Parse a JSONL file into a list of prompt payloads.
pub fn parse_jsonl(reader: impl BufRead) -> Result<Vec<PromptPayload>, VoiceError> {
let mut payloads = Vec::new();
for (i, line) in reader.lines().enumerate() {
let line = line.map_err(|e| VoiceError::InvalidInput(format!("line {}: {}", i + 1, e)))?;
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let payload: PromptPayload = serde_json::from_str(trimmed)
.map_err(|e| VoiceError::InvalidInput(format!("line {}: {}", i + 1, e)))?;
payloads.push(payload);
}
Ok(payloads)
}