This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Deterministiska röstformer med Blazor och lokala LLM
Monday, 05 January 2026
Ett tio budord –Compliant Architecture
I vilket vi bygger en röst- -av - till -- -som inte- -handlar nyckeln till AI: s rike.
Här är det som händer med ljudgränssnitt. Alla vill ha dem. Ingen litar på dem. Och de har rätt att inte göra det.
De flesta röstformer misslyckas så fort modellen hör fel på ett datum eller bestämmer sig för att " hjälpa till " hoppa framåt . Du säger ♫ " ♫ Maj ♫12 ♫ ♫ och LLM extraherar | " ♫ May ♫ МSK7 ♫, och går vidare till nästa fält innan du kan korrigera det ♫
I samma stund som du låter en LLM " kontrollera" ditt formflödeM SK2 så har du introducerat ett icke-deterministiskt svart låda i vad som borde vara en förutsägbar användarupplevelse
Det är inte hypotetiskt McDonalds körde AI, -, motorisk drivkraft och - på plats. över 100+ amerikanska platser . Den rutinmässigt misslyckades med att förstå grundläggande modifieringar . De avslutade samarbetet i | 2024. Lektionen | : | ordning | МSK5 | antagande är ett strukturerat dataproblem |- | en ändlig meny |, | kända modifierare |
Men tänk om vi kunde ha vår kaka och äta den också. deterministisk kod för allt som faktiskt betyder något
Det är vad vi bygger idag.'-"Ten budöverdrag som följer ", Lokala viskning för tal--Text, och lokala Ollama för fältutvinning. Inga molnhängningar. Inga oförutsägbara AI överlor. Bara ren arkitektur.
Att tillämpa de tio budorden
Det här projektet följer De tio budorden som LLM använder. Om du inte har läst detta än, ' inte läst det ännu, ♫ , den korta versionen ♫ ♫ LLMs är sannolikhetsmotorer ♫
För röstformulären, är de relevanta kommandorna
- I. Don' lät inte LLM ha egen stat - Stadsmaskinens spår formar framsteg , inte modellen
- II. Don ' låter LLM orsaka biverkningar - Den rekommenderar extraktioner ; koden förbinder dem
- III. Separerad orsakssamband från berättelser - LLM förklarar vad den hörde ; Validatorn bestämmer om det är korrekt
- IV. Använd LLM där sannolikheten är accepterad - Utvinning | " | Maj | МSK2 | sd | " | från tal är fuzzy |; | fältordning är inte
Designmål för röstform
Förutom budorden, så följer detta system specifika principer.
- Rödsel är förlorande inmatning - Ett sätt att befolka fält
- Bekräftelse är policydriven - tröskeln i konfigurationen , inte LLM bedömande
- Användaren kan alltid överrida - Röst är bekvämlighet , inte tvång
- Allt kör lokalt - Whisper och Ollama
- Misslyckanden är graciös. - När rösten misslyckas , så fungerar skrivandet fortfarande
Här är vad det ser ut i praktiken.
flowchart LR
A[User Speaks] --> B[Whisper STT]
B --> C[Raw Transcript]
C --> D[Ollama LLM]
D --> E[Extracted Value]
E --> F{Validation}
F -->|Pass| G{Policy Check}
F -->|Fail| H[Show Error]
G -->|High Confidence| I[Auto-Confirm]
G -->|Low Confidence| J[Ask User]
J --> K{User Confirms?}
K -->|Yes| L[State Machine: Next Field]
K -->|No| M[Retry Recording]
I --> L
H --> M
style D stroke:#f96,stroke-width:2px
style F stroke:#6f6,stroke-width:2px
style G stroke:#ff9,stroke-width:2px
Vad gör detta?
Låt oss klargöra om scopen.
- Ingen naturlig konversation - Det här är ingen chatbot
- Inga anpassningsmässiga frågeställningar - Formschematen är statisk . LLM: n följer inte improvisationer
- Nej " assistent " personlighet - LLM extraherar värder , det gör inga små samtal
- Ingen kontext över fält - Varje extraktion är oberoende
Dessa begränsningar är egenskaper, inte bugs. De gör systemet förutsägbart och tillförlitligt.
Om du vill ha en konversationsagent, Build a conversational agent.
Projektstrukturen
Vi bygger en självständig Blazor Server app. Här är architecturen.
Mostlylucid.VoiceForm/
├── Config/
│ └── VoiceFormConfig.cs # Configuration binding
├── Models/
│ ├── FormSchema/ # Form definitions
│ ├── State/ # Session state
│ └── Extraction/ # LLM input/output
├── Services/
│ ├── Stt/ # Speech-to-text (Whisper)
│ ├── Extraction/ # Field extraction (Ollama)
│ ├── Validation/ # Type-based validators
│ ├── StateMachine/ # Form flow control
│ └── Orchestration/ # Coordinates everything
├── Components/
│ └── Pages/ # Blazor UI
└── wwwroot/js/
└── audio-recorder.js # Web Audio API capture
beroende regel: tjänster beror enbart på abstraktioner under dem. Inget uppstream vet vad som händer nedstream
Att bygga upp infrastrukturen
Först, du ' behöver Whisper och Ollama som kör lokalt devdeps-docker-compose.yml:
services:
whisper:
image: onerahmet/openai-whisper-asr-webservice:latest
container_name: whisper-stt
ports:
- "9000:9000"
environment:
- ASR_MODEL=base.en
- ASR_ENGINE=faster_whisper
volumes:
- whisper-models:/root/.cache/huggingface
restart: unless-stopped
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
restart: unless-stopped
volumes:
whisper-models:
ollama-models:
Efter att ha snurrat upp en modell för Ollama:
docker exec -it ollama ollama pull llama3.2:3b
Varför lokalt? Testbarhet + bestämningskraft + datalokalitet . CI snurrar upp samma behållare ♫ . ♫ Testen når samma slutpunkter ♫ . ♫ Inga hastighetsgränser ♫, ♫ ingen nätverksvariabilitet ♫ .
Nota: Den base.en Diktatormodellen är snabb och korrekt för engelska. För produktionen , överväg small.en eller medium.en för bättre precision.
Formularsschema: Definitioner för deklarativa former
Former definieras i JSON. Detta är den enda källan till sanning för vilka fält som finns och hur de beter sig
{
"id": "customer-intake",
"name": "Customer Intake Form",
"fields": [
{
"id": "fullName",
"label": "Full Name",
"type": "Text",
"prompt": "Please say your full name.",
"required": true
},
{
"id": "dateOfBirth",
"label": "Date of Birth",
"type": "Date",
"prompt": "What is your date of birth?",
"required": true,
"confirmationPolicy": {
"alwaysConfirm": true
}
},
{
"id": "email",
"label": "Email Address",
"type": "Email",
"prompt": "What is your email address?",
"required": true
},
{
"id": "phone",
"label": "Phone Number",
"type": "Phone",
"prompt": "What is your phone number?",
"required": false
},
{
"id": "notes",
"label": "Additional Notes",
"type": "Text",
"prompt": "Any additional notes?",
"required": false
}
]
}
De C#-modeller som representerar denna
public record FormDefinition(
string Id,
string Name,
List<FieldDefinition> Fields);
public record FieldDefinition(
string Id,
string Label,
FieldType Type,
string Prompt,
bool Required = true,
ConfirmationPolicy? ConfirmationPolicy = null);
public enum FieldType
{
Text,
Date,
Email,
Phone,
Choice // Constrained vocabulary - especially important for voice
}
public record ConfirmationPolicy(
bool AlwaysConfirm = false,
double ConfidenceThreshold = 0.85);
Eftersom schemat är deklarativt att lägga till fält kräver aldrig att röra vid statemaskinen. Släng in en ny definition i JSON , och Formularet inkluderar den automatiskt med rätt flödeskontroll
State Machine: Deterministisk Form Flow
Det motverkande mönstret vi avoiderar är ",LLM,-,driven navigation, ":, framsteg i formens tillstånd baserat på modellens utgång.
Statmaskinen använder inte AI Det är den reina C-logiken som bestämmer övergångar baserat på explicita regler
public class FormStateMachine : IFormStateMachine
{
private FormSession _session = null!;
private int _currentFieldIndex;
public FormSession StartSession(FormDefinition form)
{
_session = new FormSession
{
Id = Guid.NewGuid().ToString(),
Form = form,
Status = FormStatus.InProgress,
StartedAt = DateTime.UtcNow,
FieldStates = form.Fields.ToDictionary(
f => f.Id,
f => new FieldState { FieldId = f.Id, Status = FieldStatus.Pending })
};
// First field starts in progress
if (form.Fields.Count > 0)
{
_session.FieldStates[form.Fields[0].Id].Status = FieldStatus.InProgress;
}
return _session;
}
public FieldDefinition? GetCurrentField()
{
if (_currentFieldIndex >= _session.Form.Fields.Count)
return null;
return _session.Form.Fields[_currentFieldIndex];
}
}
Statetransformationerna är uppenbara och testbara:
public StateTransitionResult ProcessExtraction(
ExtractionResponse extraction,
ValidationResult validation)
{
var currentField = GetCurrentField();
if (currentField == null)
return new StateTransitionResult(false, "No current field");
var fieldState = _session.FieldStates[currentField.Id];
fieldState.AttemptCount++;
// Validation failed? Stay on current field
if (!validation.IsValid)
{
return new StateTransitionResult(
false,
$"Validation failed: {validation.ErrorMessage}");
}
// Store the pending value
fieldState.PendingValue = extraction.Value;
fieldState.PendingConfidence = extraction.Confidence;
// Check confirmation policy - this is rules, not AI
var policy = currentField.ConfirmationPolicy
?? new ConfirmationPolicy();
var needsConfirmation = policy.AlwaysConfirm
|| extraction.Confidence < policy.ConfidenceThreshold;
if (needsConfirmation)
{
fieldState.Status = FieldStatus.AwaitingConfirmation;
return new StateTransitionResult(
true,
"Please confirm this value",
RequiresConfirmation: true);
}
// Auto-confirm high confidence values
return ConfirmValue();
}
Nyckelpunkt: Den _currentFieldIndex bara framsteg på bekräftandet, aldrig på extraktionen. Det betyder att en misslyckad extraktion eller avslagna bekräftande sätter dig på samma plats . Användaren stannar i kontroll
Lägg märke till förklaringslogiken: det ' är en enkel policycheck , inte LLM resonemang alwaysConfirm = automatiskt -bekräftelse. Mindre tillit eller känsliga fält ♫ = ♫ fråga användaren ♫
Only the LLM's contract
Den Ollama-extraktorn har ett jobb, att omvandla stökiga ljudspråk till strukturerade fältvärden.
Om du inte kan skriva den här gränssnitten utan skam, så gör LLM för mycket.
public interface IFieldExtractor
{
Task<ExtractionResponse> ExtractAsync(
ExtractionContext context,
CancellationToken ct = default);
}
public record ExtractionContext(
FieldDefinition Field,
string Prompt,
string Transcript);
public record ExtractionResponse(
string FieldId,
string? Value,
double Confidence,
bool NeedsConfirmation, // Suggestion only - policy has final say
string? Reason);
viktigt: Den extraktorn kan antyda NeedsConfirmation: true, men bekräftande policy har alltid det slutgiltiga ordet
Och implementeringen
public class OllamaFieldExtractor : IFieldExtractor
{
private readonly HttpClient _httpClient;
private readonly string _model;
public async Task<ExtractionResponse> ExtractAsync(
ExtractionContext context,
CancellationToken ct = default)
{
var systemPrompt = BuildSystemPrompt(context.Field);
var userPrompt = $"User said: \"{context.Transcript}\"";
var request = new
{
model = _model,
messages = new[]
{
new { role = "system", content = systemPrompt },
new { role = "user", content = userPrompt }
},
format = "json",
stream = false,
options = new { temperature = 0.1 } // Low = deterministic
};
var response = await _httpClient.PostAsJsonAsync(
"/api/chat", request, ct);
return ParseResponse(response, context.Field.Id);
}
}
Varför temperaturen 0.1? Vi vill att LLM är tråkigt och förutsägbart.
Systemmönstret handlar explicit om LLMs begränsade roll
private string BuildSystemPrompt(FieldDefinition field)
{
return $"""
You are a data extraction assistant. Extract the {field.Label}
from the user's speech.
Field type: {field.Type}
Return JSON only:
{{
"fieldId": "{field.Id}",
"value": "<extracted value or null>",
"confidence": <0.0-1.0>,
"needsConfirmation": <true/false>,
"reason": "<brief explanation>"
}}
Rules:
- For dates, output ISO format (YYYY-MM-DD)
- For emails, output lowercase
- For phones, output digits only
- If you can't extract, set value to null
- Be conservative with confidence scores
DO NOT:
- Ask follow-up questions
- Suggest next steps
- Make assumptions beyond the transcript
""";
}
LLM extraherar, validerar typformatet, , och rapporterar självförtroende. . Det bestämmer inte vad som händer härnäst.
Audio Recorder: Browser till WAV
JavaScript: s sida fångar mikrofonljud och konverterar det till 16kHz mono WAV
window.voiceFormAudio = (function () {
let mediaRecorder = null;
let audioChunks = [];
let audioContext = null;
let dotNetRef = null;
async function startRecording() {
const stream = await navigator.mediaDevices
.getUserMedia({ audio: true });
audioContext = new AudioContext();
audioChunks = [];
const options = { mimeType: 'audio/webm;codecs=opus' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
audioChunks.push(event.data);
}
};
mediaRecorder.onstop = async () => {
stream.getTracks().forEach(track => track.stop());
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
const wavBlob = await convertToWav(audioBlob);
const wavBytes = await wavBlob.arrayBuffer();
// Send to Blazor
const uint8Array = new Uint8Array(wavBytes);
await dotNetRef.invokeMethodAsync(
'OnRecordingComplete',
Array.from(uint8Array));
};
mediaRecorder.start(100);
}
return { initialize, startRecording, stopRecording };
})();
Varför WAV? Skryft är tränad på 16kHz mono WAV. Samma ljud i → samma byter ut ♫ → ♫ Samma transkription ♫
Den convertToWav funktionen hanterar resampling.
Blazors gränssnitt
Interfejsen visar det nuvarande fältet tydligt till vänster, med alla fält synliga i en sidabar till höger. Seitenbalken är inte kosmetisk. Användare kan se framsteg
graph LR
subgraph "Left Panel"
A[Current Field Prompt]
B[Record Button]
C[Transcript Display]
D[Confirmation Dialog]
end
subgraph "Right Sidebar"
E[Field 1: Full Name ✓]
F[Field 2: DOB - Active]
G[Field 3: Email - Pending]
H[Field 4: Phone - Pending]
end
style F stroke:#36f,stroke-width:2px
style E stroke:#6f6,stroke-width:2px
Här' är Blazor-sidans struktur
@page "/voiceform/{FormId}"
@inject IFormOrchestrator Orchestrator
@rendermode InteractiveServer
<div class="top-bar">
<h1>Voice Form</h1>
<button class="theme-toggle" onclick="voiceFormTheme.toggle()">
Dark Mode
</button>
</div>
<main>
<div class="voice-form-layout">
<!-- Left: Active Field -->
<div class="active-field-panel">
@if (_currentField != null)
{
<div class="current-prompt">
<h2>@_currentField.Label</h2>
<p class="prompt-text">@_message</p>
</div>
<AudioRecorder OnAudioCaptured="HandleAudioCaptured"
IsRecording="_isRecording"
IsProcessing="_isProcessing" />
@if (!string.IsNullOrEmpty(_transcript))
{
<TranscriptDisplay Transcript="_transcript"
Confidence="_transcriptConfidence" />
}
@if (_showConfirmation)
{
<ConfirmationDialog ExtractedValue="_pendingValue"
OnConfirm="HandleConfirm"
OnReject="HandleReject" />
}
}
</div>
<!-- Right: Form Overview -->
<div class="form-sidebar">
<h3>@_session.Form.Name</h3>
<div class="form-fields-list">
@foreach (var field in _session.Form.Fields)
{
var state = _session.GetFieldState(field.Id);
<div class="form-field-item @GetFieldClass(field, state)">
<div class="field-status-icon">
@GetStatusIcon(state.Status)
</div>
<div class="field-info">
<div class="field-name">@field.Label</div>
<div class="field-value">
@(state.Value ?? "Waiting")
</div>
</div>
</div>
}
</div>
</div>
</div>
</main>
Orchestern, :, tar allt samman
Orchestern koordinerar tjänsteren ingen egen sorts logik- så ropar den bara på tjänster i sekvens och skickar ut resultat längs med
public class FormOrchestrator : IFormOrchestrator
{
private readonly ISttService _sttService;
private readonly IFieldExtractor _extractor;
private readonly IFormValidator _validator;
private readonly IFormStateMachine _stateMachine;
private readonly IFormEventLog _eventLog;
public async Task<ProcessingResult> ProcessAudioAsync(byte[] audioData)
{
var currentField = _stateMachine.GetCurrentField();
if (currentField == null)
return ProcessingResult.Error("No current field");
// Step 1: Speech to text
var sttResult = await _sttService.TranscribeAsync(audioData);
await _eventLog.LogAsync(new TranscriptReceivedEvent(
currentField.Id, sttResult.Transcript, sttResult.Confidence));
// Step 2: Extract structured value
var context = new ExtractionContext(
currentField, currentField.Prompt, sttResult.Transcript);
var extraction = await _extractor.ExtractAsync(context);
await _eventLog.LogAsync(new ExtractionAttemptEvent(
currentField.Id, extraction));
// Step 3: Validate
var validation = _validator.Validate(currentField, extraction.Value);
// Step 4: State machine decides next step
var transition = _stateMachine.ProcessExtraction(extraction, validation);
return new ProcessingResult(
Success: transition.Success,
Message: transition.Message,
Session: _stateMachine.GetSession(),
RequiresConfirmation: transition.RequiresConfirmation,
PendingValue: extraction.Value);
}
}
Varje steg är oberoende och testbar. Stansmaskin aldrig rör vid nätverket. LLM aldrig rör staten.
Dark Mode: CSS-variabler för att vinna
Dark mode implementeras med custom properties i CSS:
:root {
--bg-primary: #f8fafc;
--bg-secondary: #ffffff;
--text-primary: #1e293b;
--accent-blue: #3b82f6;
--accent-green: #22c55e;
}
[data-theme="dark"] {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--text-primary: #f1f5f9;
--accent-blue: #60a5fa;
--accent-green: #4ade80;
}
body {
background: var(--bg-primary);
color: var(--text-primary);
}
Theme-togglen stannar kvar via localStorage:
window.voiceFormTheme = (function () {
const THEME_KEY = 'voiceform-theme';
function toggle() {
const current = document.documentElement
.getAttribute('data-theme') || 'light';
const newTheme = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem(THEME_KEY, newTheme);
}
// Initialize from saved preference or system preference
function init() {
const saved = localStorage.getItem(THEME_KEY);
const systemDark = window.matchMedia(
'(prefers-color-scheme: dark)').matches;
const theme = saved || (systemDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
}
return { toggle, init };
})();
Testing: Unittests + Browserintegration
Den deterministiska arkitekturen lönar sig i tester.
[Fact]
public void ProcessExtraction_HighConfidence_AutoConfirms()
{
// Arrange
var form = CreateTestForm();
var stateMachine = new FormStateMachine();
stateMachine.StartSession(form);
var extraction = new ExtractionResponse(
FieldId: "fullName",
Value: "John Smith",
Confidence: 0.95, // High confidence
NeedsConfirmation: false,
Reason: "Clear speech");
var validation = ValidationResult.Success();
// Act
var result = stateMachine.ProcessExtraction(extraction, validation);
// Assert
result.Success.Should().BeTrue();
result.RequiresConfirmation.Should().BeFalse();
var fieldState = stateMachine.GetSession()
.GetFieldState("fullName");
fieldState.Status.Should().Be(FieldStatus.Confirmed);
fieldState.Value.Should().Be("John Smith");
}
Och här är ett negativt test, lågt självförtroende. krafter bekräftande oberoende av vad LLM antyder:
[Fact]
public void ProcessExtraction_LowConfidence_RequiresConfirmation()
{
// Arrange
var form = CreateTestForm();
var stateMachine = new FormStateMachine();
stateMachine.StartSession(form);
var extraction = new ExtractionResponse(
FieldId: "fullName",
Value: "John Smith",
Confidence: 0.65, // Below threshold
NeedsConfirmation: false, // LLM says no, but policy overrides
Reason: "Noisy audio");
var validation = ValidationResult.Success();
// Act
var result = stateMachine.ProcessExtraction(extraction, validation);
// Assert
result.RequiresConfirmation.Should().BeTrue(
"Policy threshold (0.85) should override LLM suggestion");
var fieldState = stateMachine.GetSession()
.GetFieldState("fullName");
fieldState.Status.Should().Be(FieldStatus.AwaitingConfirmation);
}
Och en PuppeteerSharp-browsertest som faktiskt klickar på saker
[Fact]
public async Task HomePage_ThemeToggle_ShouldSwitchTheme()
{
await _page!.GoToAsync(BaseUrl);
await _page.WaitForSelectorAsync(".theme-toggle");
var initialTheme = await _page.EvaluateFunctionAsync<string?>(
"() => document.documentElement.getAttribute('data-theme')");
// Click toggle
await _page.ClickAsync(".theme-toggle");
await Task.Delay(100);
var newTheme = await _page.EvaluateFunctionAsync<string?>(
"() => document.documentElement.getAttribute('data-theme')");
newTheme.Should().NotBe(initialTheme);
}
[Fact]
public async Task VoiceFormPage_Sidebar_ShouldShowAllFields()
{
await _page!.GoToAsync($"{BaseUrl}/voiceform/customer-intake");
await _page.WaitForSelectorAsync(".form-fields-list");
var fieldItems = await _page.QuerySelectorAllAsync(".form-field-item");
fieldItems.Should().HaveCount(5, "Customer intake form has 5 fields");
}
Den fulla testpaketet kör 98 tester -76 enhetenstest för affärslogiken , 22 webbläsarens integreringstest för gränssnittet
Det viktiga är inte att testerna själva är möjliga av arkitekturen
Varför är denna arkitektur viktig
Låt oss återvända till varför vi byggde den på det här sättet.
graph TD
subgraph "Traditional Voice Form"
A1[User Speaks] --> B1[LLM]
B1 --> C1[LLM decides field]
C1 --> D1[LLM validates]
D1 --> E1[LLM confirms]
E1 --> F1[LLM advances form]
end
subgraph "Ten Commandments Approach"
A2[User Speaks] --> B2[Whisper STT]
B2 --> C2[Ollama Extract]
C2 --> D2[C# Validator]
D2 --> E2[Policy Check]
E2 --> F2[State Machine]
end
style B1 stroke:#f96,stroke-width:2px
style C1 stroke:#f96,stroke-width:2px
style D1 stroke:#f96,stroke-width:2px
style E1 stroke:#f96,stroke-width:2px
style F1 stroke:#f96,stroke-width:2px
style C2 stroke:#f96,stroke-width:2px
style D2 stroke:#6f6,stroke-width:2px
style E2 stroke:#6f6,stroke-width:2px
style F2 stroke:#6f6,stroke-width:2px
I den traditionella metoden, allt är LLM. Varje beslut är icke
Varje bug är " det gör bara ibland så.
I vår tillvägagångspunkt, ,, så gör LLM en sak: översätta tal till strukturerade värden . Allt annat är deterministiskt C
Det här är budord II, III , och IV i aktionM SK2 LLM översätter ( ingen sida, - effekter,), tillstånd maskinen kontrollerar flödet, ( orsak i koden, ), policy bestämmer bekräftelse, ♫ ( ♫ sannolikhet där det är accepterat ♫
Att köra på egen hand
- Starta beroendena:
docker compose -f devdeps-docker-compose.yml up -d
docker exec -it ollama ollama pull llama3.2:3b
- Runna appn:
cd Mostlylucid.VoiceForm
dotnet run
Navigera till
http://localhost:5000Titta på "Kundeinkomsten " och börja prata
Slutsatsen
Rödslinterfacer behöver inte vara svarta lådor.
- Förutsägbart beteende - Samma ingångar , samma utgångar
- Testbar logik - Unittest för företagsregler
- Rättsliga beslut - Varje statövergång är registrerad
- Glädjande nedbrytning - Röst misslyckas ? Typ istället
- Lokalisering - Inga molnhängningar för kärnans funktionalitet
De "" tio budorden "", """, handlar inte om att vara "" anti "". att placera AI på rätt plats: är ett kraftfullt verktyg som översätter människans rörlighet med datorpräcision. , är inte en orakel som fattar beslut för oss.
AI tjänar sin plats när det minskar otydighet, -, inte när det ersätter ansvar.
Nu bygger du något där du - inte modellen - bestämmer vad som händer -
Kom i del 2: Röntgen-Feedback (IVR)
Just nu, ,, läser användaren instruktioner och talar om svar. tala tillbaka? Del | 2 | kommer att lägga till text |- | till - | talar återkoppling | МSK4 | och göra detta till en full IVR |
- Lees instruktioner högt - "Snälla berätta hela ditt namn
- Bevestig extraktioner - " Jag hörde ' John Smith '. Är det korrekt?
- Audiosignaler - Klank för inspelning av start
- Hands- fri aktivitet - Complete forms without looking at the screen
Architekturen är redan redo.:, statmaskinen utsätter händelser,, orkestraren koordinerar tjänster., att lägga till ljudutsläpp är bara en annan tjänst som lyssnar på de händelserna
Kom i del 3: Politisk implementation
Den nuvarande bekräftelset är typ-baserade : mejl ser ut som e-poster, datum analysera som datumM SK3 Men vad sägs om företagsreglernMska4 Mske5 födseldatet måste vara minst ♫ Mska6 ♫ för flera år sedan ♫Mska7 ♫ | Mska8 ♫ Telefonnummern måste matcha regionen som har valts ♫
Del 3 kommer att lägga till :
- Regler-baserad validering - Deklarerande policyr i schemat
- Cross-fältbegränsningar - "Om landet är USA, måste telefonen vara
- LLM- hjälpsam policykontroll - användandet av modellen för att flagera potentiella problem , med deterministisk kod som gör det slutgiltiga samtalet
- Revideringsspår - Vilket policy utlöste , varför det misslyckades
Samma princip.
Fortsättning avläsning
- De tio budorden som LLM använder - Filosofin bakom den här arkitekturen
- Att använda Docker Compose för utvecklings beroenden - Lokalisering av tjänster
- HTMX med ASP.NET Core - Server-driven UI-mönster
Den fulla källkoden finns i Mostlylucid.VoiceForm projekt i repo.