TheFocus.AI TheFocus.AI

Code Extraction

TUTOR WITH THEFOCUS.AI

Agent Integration

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.

Please tutor me in this lesson using the following context. First, read the instructions at: https://courses.thefocus.ai/llms.txt My Student ID is: <none> The lesson markdown source is at: https://courses.thefocus.ai/run-ai-locally/03-automation/08-code-extraction.md

You are not enrolled yet. Enroll to generate a Student ID to track lesson completions and store learning notes.

08 — Code Extraction

The Problem

When Ollama generates code, it wraps it in markdown code blocks:

Here's the script:

```python
print("Hello, world!")
```

Save this as hello.py and run it.

If we want to run that code automatically, we need to pull it out of the markdown — strip the backticks, get the raw code, and save it as a runnable file.

The Extractor

The 2-extract tool does exactly this. It reads ollama-output.md and splits it into:

  • reasoning.md — the model’s internal thinking (between Thinking... and ...done thinking.)
  • response.md — the model’s actual response (everything after ...done thinking.)
  • code — the first extracted code block, made executable

The Script

Here’s the extractor generated by our gemma4:26b model:

#!/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
      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();

How It Works

The extractor uses a simple state machine:

                    "Thinking..."
SEARCHING ─────────────────────────► REASONING

                              "...done thinking."


                                      RESPONSE

                              sees ``` (open)


                                   INSIDE CODE BLOCK

                              sees ``` (close)


                                  CODE EXTRACTED
  1. SEARCHING — scanning for the start of reasoning
  2. REASONING — collecting lines between Thinking... and ...done thinking.
  3. RESPONSE — collecting everything after, while watching for code blocks
  4. INSIDE CODE BLOCK — collecting code lines (strips the ``` markers)

Why Separate Reasoning and Response?

Ollama models (like Gemini-family models) produce two sections:

  • Thinking/reasoning — the model’s internal monologue. This is not meant for the user. It’s where the model plans, considers options, and works through the problem.
  • Response — the actual answer. This is what you’d show a user.

Separating them lets you:

  • Store reasoning separately for debugging (why did the model make that choice?)
  • Feed only the response to downstream tools
  • Compare reasoning across runs to see how the model’s thinking changes

Usage

# Run a prompt
cat my-prompt/prompt.md | ollama run gemma4:26b --nowordwrap | tee my-prompt/ollama-output.md

# Extract the code
./2-extract/code my-prompt

# See what we got
ls my-prompt/
# reasoning.md  response.md  code  prompt.md  ollama-output.md

# The code is already executable!
./my-prompt/code

The Test Pattern

The extractor also supports automatic testing. If there’s a test file in the directory, the prompt runner will:

  1. Make it executable (chmod +x)
  2. Run it
  3. Capture output to test-output.md

This is how we close the automation loop: prompt → generate → extract → test → capture results → feed back.

What You’ve Learned

  • The extractor parses Ollama’s output format (thinking → response → code blocks)
  • A state machine is a simple, reliable way to parse structured text
  • Separating reasoning from response is useful for debugging
  • Making extracted code executable lets us run it immediately
  • The test pattern captures results for the self-improving loop

Next: Projects → — five real tools the AI built for you.