Building Isolyne (Part 4): Building a Typed LLM Extraction Layer for a Deterministic CQRS Kernel

Wait 5 sec.

One of the biggest mistakes in modern AI engineering is building an "LLM wrapper that does everything":It chats with the user.It parses the data.It makes the business decisions.It writes to the database.When an LLM controls your business logic, your app inherits the LLM's flaws: non-determinism, hallucinations, unpredictable schema changes, and high latency.When we designed Isolyne for Shipaton 2026, we established a strict architectural rule: The LLM is a sensory organ, not the brain.We chose Gemini 1.5 Flash for its speed (< 400ms latency) and native structured output capabilities, but we restricted its responsibility to exactly one task: converting messy, human developer chat into a typed { topic, choice } JSON object.Here is how we engineered the boundary to reduce hallucinations and constrain the output format.#svg1. The Single Responsibility BoundaryIn Isolyne, developers don't fill out structured dropdowns. They chat naturally:"Let's just use Postgres for the database.""I'm going to set up Zustand for global client state.""We should probably deploy on Vercel."The LLM never decides if these statements conflict with other teammates. Its sole job is extraction:[ Human Chat String ] │ ▼"Let's just use Postgres for the database" │ ▼┌───────────────────────────────────────────────────────────┐│ GEMINI 1.5 FLASH (REST API) ││ ││ Temperature: 0.1 ││ Response Mime: application/json ││ Strict Schema: { topic: string, choice: string } │└─────────────────────────────┬─────────────────────────────┘ │ ▼[ Structured Signal ]{ topic: "Database", choice: "PostgreSQL" } │ ▼[ Pure CQRS Kernel ](Calculates Consensus & Ownership mathematically)#svg2. Enforcing Native JSON Schemas at the Engine LevelInstead of writing prompt instructions like "Please only return valid JSON", we leverage Gemini's native responseSchema configuration. This constrains the model's output to our JSON schema definition:const model = 'gemini-1.5-flash';const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;const res = await fetchWithRetry(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey }, body: JSON.stringify({ systemInstruction: { parts: [ { text: "You are a precise data extraction engine. Extract the core domain/category of the decision (e.g., Database, Architecture, Frontend, Scope, CI/CD) and the specific choice made. If the statement is casual banter or not a decision, return 'UNKNOWN' for both." } ] }, contents: [ { parts: [ { text: `Statement: "${sanitizedInput}"` } ] } ], generationConfig: { temperature: 0.1, // Near-deterministic extraction responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { topic: { type: "STRING", description: "The category or domain of the decision (e.g. Architecture, Database)." }, choice: { type: "STRING", description: "The specific option chosen by the user (e.g. PostgreSQL, React Native)." } }, required: ["topic", "choice"] } } })});Because temperature is pinned at 0.1 and responseSchema is enforced by the Gemini runtime, the output is constrained to syntactically valid JSON that follows the supplied schema. This does not guarantee semantically correct values, so we still validate the output inside the application and handle incorrect extractions. Google’s structured-output documentation3. Solving Topic FragmentationA classic problem with NLP extraction in collaborative apps is Topic Fragmentation:Alice says: "We are using Postgres for the DB." →→ Model outputs topic: "DB".Bob says: "I want MongoDB for the database." →→ Model outputs topic: "Database".If the model assigns different topic strings ("DB" vs "Database"), our deterministic set-theory kernel won't know they are conflicting over the same architectural layer!To solve this, we pass the active team's recent topics directly into the prompt context:const prompt = ` Extract the decision. Existing topics to reuse if applicable: [${recentTopics.join(', ')}] Statement: "${sanitizedInput}"`;If the team has already established a "Database" topic, Gemini reuses the exact same topic key, enabling our downstream set-theory detector to catch the disagreement instantly.4. Handling Non-Decisions and BanterWhat happens if a developer types "Good morning team, let's crush this hackathon!"?A naive extractor might hallucinate a decision like { topic: "Work Ethic", choice: "Crush Hackathon" }.We instructed the model to emit "UNKNOWN" whenever an input lacks a concrete technical commitment. Our parser detects this and returns null:if ( parsed.topic.toUpperCase() === 'UNKNOWN' || parsed.choice.toUpperCase() === 'UNKNOWN') { return null; // Gracefully ignored by the UI without adding noise to the timeline}5. Resilient Networking: fetchWithRetryIn mobile apps, network requests on mobile data or conference Wi-Fi can drop packets. Rather than importing a heavy SDK, we implemented a lightweight fetchWithRetry wrapper with exponential backoff and timeout abortion:async function fetchWithRetry( url: string, options: RequestInit, retries = 2, delayMs = 500): Promise { const controller = new AbortController(); const timeoutId = setTimeout( () => controller.abort(), 8000 ); // 8-second circuit breaker try { const res = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); // If rate-limited (429), respect Retry-After or backoff if (res.status === 429 && retries > 0) { const retryAfter = res.headers.get('Retry-After'); const wait = retryAfter ? parseInt(retryAfter, 10) * 1000 : delayMs; await new Promise(r => setTimeout(r, wait)); return fetchWithRetry( url, options, retries - 1, delayMs * 2 ); } return res; } catch (err: any) { clearTimeout(timeoutId); if (retries > 0 && err.name !== 'AbortError') { await new Promise(r => setTimeout(r, delayMs)); return fetchWithRetry( url, options, retries - 1, delayMs * 2 ); } throw err; }}The RevenueCat Connection: High-Value Signals Fueling Pro FeaturesHow does clean NLP extraction support our RevenueCat monetization strategy?Every extracted { topic, choice } is paired with its raw verbatim user input and written to our immutable event log.When users upgrade to Isolyne Pro via RevenueCat, they unlock:Verbatim Signal Tracing: Expanding any radar card reveals the exact conversation snippets that produced the decision.Structured Exporting: Downloading team history as clean Markdown Architectural Decision Records (ADRs) ready for GitHub or Jira.By scoping Gemini to clean extraction, we turn messy conversation into high-value, structured data that users gladly pay to preserve.In Part 5...What happens when there is no internet connection at all, or the user's API quota is exhausted?In Part 5, we’ll explore The Graceful Fallback—how we built a local keyword-matching engine that ensures Isolyne remains 100% functional offline.