feat(flow): add whole-utterance batch ASR fallback (P1)
- Accumulate utterance PCM in FlowContinuousCapture for batch retry - Run full-utterance transcribeChunk when stitched final lags partial - Refactor Mac MLX tail drain to shared FlowUtteranceEndCoordinator - Add FlowUtterancePCMStore, UtteranceBatchFallbackPolicy, and tests Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
@@ -25,6 +25,18 @@ public enum FlowPipelineDiagnostics {
|
||||
OSGLog.asr.info("finalChunkRecovery \(action) chunk=\(chunkIndex)")
|
||||
}
|
||||
|
||||
public static func logBatchFallback(
|
||||
sampleCount: Int,
|
||||
stitchedLength: Int,
|
||||
partialLength: Int,
|
||||
batchLength: Int
|
||||
) {
|
||||
OSGLog.flow.info(
|
||||
"batchFallback samples=\(sampleCount) stitchedLen=\(stitchedLength) " +
|
||||
"partialLen=\(partialLength) batchLen=\(batchLength)"
|
||||
)
|
||||
}
|
||||
|
||||
public static func logChunkFinalize(
|
||||
chunkCount: Int,
|
||||
lastChunkSamples: Int,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// FlowUtterancePCMStore.swift
|
||||
// OSGKeyboard · Shared
|
||||
//
|
||||
// Thread-safe rolling buffer of 16 kHz mono utterance PCM for whole-utterance
|
||||
// batch ASR fallback when pipelined chunking drops weak tail segments.
|
||||
|
||||
import Foundation
|
||||
|
||||
public final class FlowUtterancePCMStore: @unchecked Sendable {
|
||||
private let lock = OSAllocatedUnfairLock()
|
||||
private var samples: [Float] = []
|
||||
private let maxSampleCount: Int
|
||||
|
||||
public init(maxSampleCount: Int) {
|
||||
self.maxSampleCount = max(1, maxSampleCount)
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
lock.withLock {
|
||||
samples.removeAll(keepingCapacity: false)
|
||||
}
|
||||
}
|
||||
|
||||
public func append(_ chunk: [Float]) {
|
||||
guard !chunk.isEmpty else { return }
|
||||
lock.withLock {
|
||||
samples.append(contentsOf: chunk)
|
||||
if samples.count > maxSampleCount {
|
||||
samples.removeFirst(samples.count - maxSampleCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var sampleCount: Int {
|
||||
lock.withLock { samples.count }
|
||||
}
|
||||
|
||||
/// Returns accumulated samples and clears the store.
|
||||
public func consume() -> [Float] {
|
||||
lock.withLock {
|
||||
let out = samples
|
||||
samples.removeAll(keepingCapacity: false)
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// UtteranceBatchFallbackPolicy.swift
|
||||
// OSGKeyboard · Shared
|
||||
//
|
||||
// Decides when to re-run ASR on the full utterance PCM after pipelined
|
||||
// chunking, and how to pick the best transcript among candidates.
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum UtteranceBatchFallbackPolicy {
|
||||
public static let defaultCharacterAdvantage = UtteranceTranscriptGuard.defaultPartialAdvantage
|
||||
|
||||
/// True when chunked output likely lost trailing content vs the live partial.
|
||||
public static func shouldRunBatchFallback(
|
||||
stitchedFinal: String,
|
||||
partialSnapshot: String,
|
||||
minimumCharacterAdvantage: Int = defaultCharacterAdvantage
|
||||
) -> Bool {
|
||||
let final = stitchedFinal.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let partial = partialSnapshot.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if final.isEmpty, !partial.isEmpty { return true }
|
||||
if partial.isEmpty { return false }
|
||||
return partial.count >= final.count + minimumCharacterAdvantage
|
||||
}
|
||||
|
||||
/// Prefer the longest non-empty transcript after batch ASR completes.
|
||||
public static func preferredTranscript(
|
||||
batch: String,
|
||||
stitchedFinal: String,
|
||||
partialSnapshot: String,
|
||||
current: String
|
||||
) -> String {
|
||||
let candidates = [batch, current, stitchedFinal, partialSnapshot]
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
guard let best = candidates.max(by: { $0.count < $1.count }) else {
|
||||
return current.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
return best
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user