Data Extractor
TUTOR WITH THEFOCUS.AI
Copy this prompt into Claude, ChatGPT, or any external AI assistant. It points the assistant to the course instructions and links it to your student profile to track your progress and customize observations.
You are not enrolled yet. Enroll to generate a Student ID to track lesson completions and store learning notes.
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 thinkingresponse.md— the model’s actual answercode— 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:
- Initial prompt → worked but output was garbled
- Updated to store reasoning, response, AND code properly
- Further refined to ensure code blocks only use backticks from the start of a line
- Final
prompt2.mdversion consistently produces clean output
The iteration process itself used the self-improving loop:
# 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
#!/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
#!/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 →