---
title: "Data Extractor"
---

# Project: Data Extractor

**Source:** `2-extract/`  
**Model:** `gemma4:26b`  
**Type:** TypeScript (Bun runtime)

## What It Does

The data extractor parses Ollama's output format. Given a directory containing `ollama-output.md`, it produces:

- `reasoning.md` — the model's internal thinking
- `response.md` — the model's actual answer
- `code` — the first code block extracted, made executable

This is the tool that turns raw model output into runnable, inspectable artifacts.

## The Prompt

The original prompt was a one-shot specifying the Ollama output format:

````
write a standalone typescript script that has a shebang header using bun

it accepts a directory and an optional filename

by default it loads directory/ollama-output.md

and it extracts it to directory/reasoning.md, directory/response.md and directory/code

below is a sample file

The section seperators are

Thinking...
...done thinking.

and when you get the code out dont include the ```

make the code exectuable once you have it

output 2 sections

# New Prompt

An updated version of this prompt with your assumptions and choices filled out

# Source Code

The source code

no other commentary
````

### Prompt Evolution

This prompt went through multiple refinements. The author iterated:

1. Initial prompt → worked but output was garbled
2. Updated to store reasoning, response, AND code properly
3. Further refined to ensure code blocks only use backticks from the start of a line
4. Final `prompt2.md` version consistently produces clean output

The iteration process itself used the self-improving loop:

```bash
# Test the extractor
./2-extract/test 2>&1 | tee ./2-extract/test-output

# Feed results back for improvement
./1-combind/code -m "update the prompt to make sure all of the
reasoning gets stored, all of the response gets stored, the code
stuff was great" \
  2-extract/prompt.md 2-extract/code 2-extract/test-output \
  0-weather/code 0-weather/reasoning.md 0-weather/response.md \
  | ollama run gemma4:26b --nowordwrap
```

## The Code

````typescript
#!/usr/bin/env bun
import { writeFileSync, chmodSync } from "node:fs";
import path from "node:path";

async function main() {
  const args = process.argv.slice(2);
  if (args.length < 1) {
    console.error("Usage: ./extract.ts <directory> [filename]");
    process.exit(1);
  }

  const dir = args[0];
  const filename = args[1] || "ollama-output.md";
  const inputPath = path.join(dir, filename);

  try {
    const content = await Bun.file(inputPath).text();
    const lines = content.split(/\r?\n/);

    const reasoning: string[] = [];
    const response: string[] = [];
    const code: string[] = [];

    let state: "searching" | "reasoning" | "response" = "searching";
    let isInsideCodeBlock = false;
    let codeExtracted = false;

    for (const line of lines) {
      // Separator detection (must be only content on line)
      if (state === "searching" && line === "Thinking...") {
        state = "reasoning";
        continue;
      }
      if (state === "reasoning" && line === "...done thinking.") {
        state = "response";
        continue;
      }

      if (state === "reasoning") {
        reasoning.push(line);
      } else if (state === "response") {
        response.push(line);

        // Code block extraction logic
        if (!codeExtracted && line.startsWith("```")) {
          if (!isInsideCodeBlock) {
            isInsideCodeBlock = true;
          } else {
            isInsideCodeBlock = false;
            codeExtracted = true;
          }
          continue; // Skip the fence line
        }

        if (isInsideCodeBlock) {
          code.push(line);
        }
      }
    }

    // Write outputs
    writeFileSync(path.join(dir, "reasoning.md"), reasoning.join("\n"));
    writeFileSync(path.join(dir, "response.md"), response.join("\n"));

    const codePath = path.join(dir, "code");
    writeFileSync(codePath, code.join("\n"));
    chmodSync(codePath, 0o755);

    console.log("Extraction complete.");
  } catch (error) {
    console.error(`Error: ${error}`);
    process.exit(1);
  }
}

main();
````

## The Test

```bash
#!/bin/bash
set -e
./2-extract/code 0-weather
```

This tests the extractor against the 0-weather exercise output. If `0-weather/ollama-output.md` exists and is valid, the test should produce `0-weather/reasoning.md`, `0-weather/response.md`, and `0-weather/code`.

## Key Lessons

- **State machines** are a natural fit for parsing structured text. Three states: searching, reasoning, response.
- **The thinking/response split** is specific to Gemini-family models — other model families may have different output formats.
- **Prompt refinement works.** The initial extractor worked but was fragile. Feeding specific failure cases back to the model produced a robust version.
- **This is a building block.** The extractor + combiner + prompt runner together form the complete agentic loop.

**Next: [Prompt Improver →](/run-ai-locally/projects/prompt-improver/)**
