Contracts (the shared language): - percept / state / context / utterance / render / audio / capabilities - Capability model instead of vendor schemas (see below) Providers (capability-based abstraction): - LLM + Embedding: OpenAI-compatible shape IS the standard, adopted directly - ASR: internal contract is an event stream (partial -> final), because /v1/audio/transcriptions has no partial and dialogue needs it for endpointing. OpenAI adapter degrades to supports_partial=False. - TTS: internal contract carries tags/emotion/style, because /v1/audio/speech has no paralinguistics and [laughter]/[breath] are the core of feeling alive. Unsupported providers are stripped AND reported via Degradation -- silent degradation is forbidden. - Avatar: no de-facto standard, custom RenderCmd protocol driven by State. Core: - mood: dynamical system, slow variables, negativity bias (grudge), numbing after prolonged negativity - bazi: four pillars -> numeric vector; 'ming' is data, not prose - projection: phi, system event -> in-world statement; uncovered events are counted as projection gaps Storage: - SQLite schema, three memory layers, dyad ledger - accessibility floor 0.03 (not 0): forgettable but still there Config split: - models.yaml is the ONLY place model versions live - character.yaml holds world rules / emotion params, no version numbers 58 tests pass; ruff clean.
50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
"""能量 VAD。离线与兜底用。
|
|
|
|
**不是生产方案。** 生产要用 Silero v6 + 语义端点。
|
|
它的价值是把"VAD 接口长什么样"固定下来,让上层可以先跑通。
|
|
|
|
同时它是一个反面教材:纯能量判定会抢话。
|
|
规格里明确要求:能量 VAD 只做门控,不做端点判定。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import struct
|
|
|
|
from ...contracts import AudioChunk, AudioFrameStats, Capabilities, HealthStatus, Modality
|
|
from .base import VADProvider
|
|
|
|
|
|
class EnergyVAD(VADProvider):
|
|
name = "energy"
|
|
|
|
def __init__(self, *, rms_threshold: float = 0.012) -> None:
|
|
self._thr = rms_threshold
|
|
|
|
@property
|
|
def capabilities(self) -> Capabilities:
|
|
return Capabilities(modality=Modality.AUDIO)
|
|
|
|
async def health(self) -> HealthStatus:
|
|
return HealthStatus(ok=True, detail="energy-vad (fallback,非生产用)")
|
|
|
|
async def process(self, frame: AudioChunk) -> AudioFrameStats:
|
|
pcm = frame.data
|
|
n = len(pcm) // 2
|
|
if n == 0:
|
|
return AudioFrameStats(ts=frame.ts, rms=0.0, peak=0.0)
|
|
samples = struct.unpack(f"<{n}h", pcm[: n * 2])
|
|
peak = max(abs(s) for s in samples) / 32768.0
|
|
rms = math.sqrt(sum(s * s for s in samples) / n) / 32768.0
|
|
return AudioFrameStats(
|
|
ts=frame.ts, rms=rms, peak=peak, is_speech=rms > self._thr
|
|
)
|
|
|
|
async def stream(self, audio): # type: ignore[no-untyped-def]
|
|
async for frame in audio:
|
|
yield await self.process(frame)
|
|
|
|
async def reset(self) -> None:
|
|
return None
|