Monday, August 25, 2025

Claude Code's 19 cent Parser

A brief prompt:

In authheader.go write a function to parse a SIP WWW-Authenticate header for Digest
authentication. It should return a map[string]string of key:value pairs which are
present. It should handle the case of valueless parameter with no "=" by populating
an empty string in the map.

Write unit tests, including these WWW-Authenticate headers:
1. WWW-Authenticate: Digest algorithm=MD5,realm="example.com",nonce="abcd="
2. WWW-Authenticate: Digest realm="example.com", nonce="efgh=", opaque="1234__", algorithm=MD5, qop="auth"

A classic hacker stock photo in a darkened room sitting in front of a laptop wearing a hoodie and mask, except the person typing is a robot

From this, Claude Code generated quite reasonable parsing code for a SIP WWW-Authenticate header. It did this in approximately one minute of wall-clock time at a cost of 19 cents. This is considerably more quickly and cheaply than I could have produced a similar function.

I made one manual fix: the string comparison for "Digest" and for parameter field names are supposed to be be case-insensitive, and I added unit tests for it. I hadn't specified this in the prompt, and Claude Code didn't figure that out from the mention of SIP.

I remain of the opinion that vibe coding can be a force multiplier for expertise, not a complete replacement for expertise.


 

Wisdom

Returning to an earlier topic: does the code which Claude Code generated exhibit wisdom? Did it have shortcomings which would be harmful? Claude Code came up with the following test cases, and wrote a Go table-driven test case for them.

  1. The two I explicitly gave it.
  2. Header with valueless parameter
  3. Header with unquoted values
  4. Empty header
  5. Header with comma in quoted value
  6. Header with extra spaces

I looked into the handling of unquoted values. The SIP standard says that fields like algorithm or qop which are enumerated in specifications can be left unquoted. What Claude Code generated would allow any field to be unquoted, including arbitrary text strings like realm.

The spec says these values must be quoted. Yet there is also the Robustness Principle, to be liberal in what you accept and strict in what you send.


 

Postel's Law Considered Harmful

Nowadays I think this principle has ultimately been more harmful than good. Over time we end up with a protocol which is only partially specified, where real implementations require a neverending series of quirks handling to work around the behaviors of widely deployed yet incorrect implementations which other implementations have liberally accepted. For new protocols I'm a fan of be strict in what you send and strict in what you accept, to not allow quirks to accumulate. Like barnacles, quirks slow the forward progress over time and tend to cause standrds to bog down and eventually stop even trying to evolve.

But SIP is ancient. In Internet Years it is a centennarian. What should one do about SIP? Being strict in what one accepts would lead to a series of relaxations being added during deployment when engineering philosophy meets harsh reality that there are a lot of barely-compliant production services run by vendors far too large to care what some Internet Rando thinks of their implementation.


 

Epilogue

I did consider whether to just leave it this way, and allow unquoted strings for all fields. Life is too short to fight the weight of Internet Protocol Inertia... but I couldn't do it. That would make my little corner of the SIP world be part of the problem. I made it only accept unquoted strings for algorithm and qop, the two enumerated fields which my system deals with.

In authheader.go:parseWWWAuthenticate() fields named “algorithm” or “qop” may be
quoted or unquoted. Any other field name must have its value quoted to be accepted.

In authheader_test.go add test cases:
1. fields named “algorithm” or “qop” may be quoted or unquoted.
2. Any other field name must have its value quoted to be accepted.

Monday, August 18, 2025

Training Gemma3-270m for German Q-and-A

Google recently introduced Gemma3-270M, a smaller Gemma3 model with "only" 270 million parameters instead of billions.

The most interesting aspect of this model to me is that it is explicitly intended to be able to run locally, without requiring highly specialized infrastructure — well within what is achievable outside of specialized datacenters. The potential to run the model with an air gap, isolating it from outside, would be interesting for some future stuff I'm working on.

The eventual uses would involve communication in the German language, so I decided to see about adding training to answer questions in German specifically. I referenced an existing colab notebook, which uses Gemma3-270M to predict chess moves. Chess as an application for LLMs isn't as interesting for me personally, we have better ways to use neural networks to play chess, but the training flow is the same.

We start by loading dependencies and instantiating the gemma-3-270m-it model.

%%capture
import os
if "COLAB_" not in "".join(os.environ.keys()):
    !pip install unsloth
else:
    # Do this only in Colab notebooks! Otherwise use pip install unsloth
    !pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft
    !pip install --no-deps trl triton cut_cross_entropy unsloth_zoo
    !pip install sentencepiece protobuf "datasets>=3.4.1,<4.0.0" "huggingface_hub>=0.34.0" hf_transfer
    !pip install --no-deps unsloth


from unsloth import FastModel
import torch
max_seq_length = 2048
model, tokenizer = FastModel.from_pretrained(
    model_name = "unsloth/gemma-3-270m-it",
    max_seq_length = max_seq_length, # Choose any for long context!
    load_in_4bit = False,  # 4 bit quantization to reduce memory
    load_in_8bit = False, # [NEW!] A bit more accurate, uses 2x memory
    full_finetuning = False, # [NEW!] We have full finetuning now!
    # token = "hf_...", # use one if using gated models
)

We set it up to accept training data in a chat format using the Huggingface deepset/germanquad dataset, a curated set of training data from the Deutsch Wikipedia and various academic sources.

model = FastModel.get_peft_model(
    model, r = 128,
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj",],
    lora_alpha = 128, lora_dropout = 0, bias = "none",
    use_gradient_checkpointing = "unsloth",
    random_state = 3407, # Seems pretty random
    use_rslora = False, loftq_config = None,
)

from unsloth.chat_templates import get_chat_template
tokenizer = get_chat_template(tokenizer, chat_template = "gemma3")

from datasets import load_dataset
dataset = load_dataset("deepset/germanquad", split = "train[:10000]")

def convert_to_chatml(example):
    return {
        "conversations": [
            {"role": "system", "content": example["context"]},
            {"role": "user", "content": example["question"]},
            {"role": "assistant", "content": example["answers"]["text"][0]}
        ]
    }
dataset = dataset.map(convert_to_chatml)

def formatting_prompts_func(examples):
   convos = examples["conversations"]
   texts = [tokenizer.apply_chat_template(convo,tokenize = False,
       add_generation_prompt = False).removeprefix('<bos>') for convo in convos]
   return { "text" : texts, }
dataset = dataset.map(formatting_prompts_func, batched = True)

from trl import SFTTrainer, SFTConfig
trainer = SFTTrainer(
    model = model, tokenizer = tokenizer,
    train_dataset = dataset, eval_dataset = None,
    args = SFTConfig(
        dataset_text_field = "text",
        per_device_train_batch_size = 8,
        gradient_accumulation_steps = 1,
        warmup_steps = 5, num_train_epochs = 1,
        max_steps = 100, learning_rate = 5e-5,
        logging_steps = 1, optim = "adamw_8bit",
        weight_decay = 0.01, lr_scheduler_type = "linear",
        seed = 3407, output_dir="outputs",
        report_to = "none",
    ),
)

from unsloth.chat_templates import train_on_responses_only
trainer = train_on_responses_only(
    trainer,
    instruction_part = "<start_of_turn>user\n",
    response_part = "<start_of_turn>model\n",
)

We then train the model. This took about three minutes on Google Colab using a Tensor T4 system.

trainer_stats = trainer.train()

Now, the real test: can it give good answers to questions not in its training data?

messages = [
    {'role': 'system','content': 'Bielefeld'},
    {"role" : 'user', 'content' : 'Gibt es Bielefeld?'}
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize = False,
    add_generation_prompt = True, # Must add for generation
).removeprefix('<bos>')

from transformers import TextStreamer
_ = model.generate(
    **tokenizer(text, return_tensors = "pt").to("cuda"),
    max_new_tokens = 125,
    temperature = 1, top_p = 0.95, top_k = 64,
    streamer = TextStreamer(tokenizer, skip_prompt = True),
)

<bos><start_of_turn>user
Gibt es Bielefeld?
<end_of_turn>

<start_of_turn>model
Ja
<end_of_turn>

Indeed yes, it can!

If that interaction doesn't make much sense: it is a German joke, alleging that the city of Bielefeld doesn't actually exist. Wikipedia has an explanation in English.

The trained model says that Bielefeld does exist. Clearly it has no sense of humor.

Sunday, August 17, 2025

Iceland Carbfix Tour

In July 2025 we took a tour of the Geothermal Exhibition at Hellisheiðarvirkjun in Iceland, all about Geothermal power. My spouse is a Professional Geologist, for whom this was an especially interesting tour.

We took a slightly more extensive version of the tour which included the CarbFix plant, a carbon capture and sequestration project where carbon dioxide is injected deep underground to mineralize.

Near the Carbfix injection site is the Climeworks Mammoth plant, a direct air carbon capture facility. We didn't get to go inside, we could only see it from a distance.

large steam pipes running over a low hill and across the field
Steam pipes from the geothermal vents back to the power plant.
steam pipes within the power plant
Steam pipes within the power plant.
turbine within the power plant
Turbine within the power plant.
Building with a very large number of fans to pull air through
Climeworks Direct Air Capture facility.
Carbfix piping driving H2S and CO2 deep underground
Carbfix H2S+CO2 pumping facility.
Carbfix H2S and CO2 meters
Carbfix H2S+CO2 meters.

Saturday, August 16, 2025

Survey of Germany-related blog posts

A gothic building with a huge animatronic clockMy spouse's German mother emigrated to the United States in 1958. Until 1975, German mothers did not pass on citizenship to children born in wedlock. My spouse was not born a German citizen for this reason. The modern state of Germany has decided that this gender discriminatory policy was unconstitutional, and defined a declaration process called Staatsangehörigkeit § 5 (StAG5) by which descendants of such persons can declare their German citizenship.

Hand holding four German Reispässe

Our journey in this area began in 2020 with genealogical research, then filing a declaration of citizenship for spouse and our children, and finally taking trips to Germany as new German citizens. I've written a number of blog posts on this topic, roughly categorized below.


 

German Genealogy




German Citizenship




Other Topics of Interest to Americans, Concerning Germany




Our European Experiences

Monday, August 11, 2025

High School German with UCScout On Demand

I've written about our journey to German citizenship for my wife and our children. Yet merely having a German passport, Passdeutsche, isn't our goal: we want our children to be able to function comfortably in Europe if they choose to do so at any point in their lives. That means learning to speak German conversationally, if not fluently.

UCScout logo

Two of our kids are in High School. My school in Missouri lo these many years ago offered French, Spanish, and German, but times and school funding levels were different back then. Our High School now offers Spanish — the most widely used language in California after English, but we'd prefer they use this time to learn German instead.

Last year we started taking an online German course from UCScout, which is run by the University of California. The UCScout On Demand courses are self-paced but have an instructor available to assist, grade assignments, and conduct sessions in German. The On Demand courses cost $399 per semester, are accredited high school courses, and meet California's A-G requirements.

We have opted out of the Spanish class offered at school and instead enrolled in the UCScout German course, for 10th and 11th grade so far. At the end of each two semester course UCScout sends a report which our school incorporates into their regular transcript. There won't be a separate report card for the German classes when they apply for admission to college, it will all be part of their High School transcript.

A gothic building with a huge animatronic clockThis has worked out quite well for us. During the school year they use the hour which would have otherwise been the Spanish class to work on their German. They've also taken a class over each of the last two summers while we were in Germany. Being able to work on their own schedule lets them do the classwork in the evenings after we're done for the day.

If you choose to do something like this, start early. It took the entire first year of high school to get agreement that the kids would be allowed to drop Spanish and take German instead. It helped that the school had used UCScout during the pandemic to offer their Spanish course, they already had a way to incorporate the grades into their system.

Monday, August 4, 2025

Germany trip 7.2025

Reprising last year's trip, we spent another July in Europe this year.

One somewhat less pleasant aspect of last year's trip was the flights, particularly the return from Frankfurt to San Francisco where we spent 13 hours in the air. This year we broke up the time in the air:

  1. San Francisco -> Pittsburgh, to visit family
  2. Pittburgh -> Iceland
  3. Iceland -> Munich, Germany
  4. Munich -> Potsdam, near Berlin
  5. Potsdam -> Hannover
  6. Hannover -> Hamburg
  7. Hamburg -> Reykjavik, Iceland
  8. Iceland -> New York City
  9. New York -> San Francisco

 

Pittsburgh

We mainly visited family in Pittsburgh, but saw a few sights like the Duquesne Incline.

Duquesne Incline in Pittsburgh, a furnicular railway with a rail car climbing a steep slope
Duquesne Incline

 

Iceland Geothermal Exhibit

We rented a car in Iceland and went to the Geothermal Exhibit, all about Geothermal power. My spouse is a Professional Geologist, for whom this was an especially interesting tour.

Turbines and steam pipes
Geothermal power plant at Hellisheiðarvirkjun
Carbon capture system

 

Munich

We spent four days in Munich, a highlight was watching a performance of the Glockenspiel.

A gothic building with a huge animatronic clock
Munich Rathaus Glockenspiel

 

Potsdam

Last year we stayed in Berlin, and didn't find time to make it down to Potsdam but wanted to. So this year, we spent four days in Potsdam. We toured the Sanssouci Palace.

Sanssouci Palace

 

Hannover

My wife's family is from Hannover, we visit each time we are there. This year we went to Lake Maschee and the Herrenhäuser Garten.

Panoramic shot of a lake
Lake Maschee
Statue of a man laying in the lap of a woman
Herrenhäuser Garten

 

Hamburg

We loved Hamburg. Hamburg and Potsdam were our favorite cities on this trip, mainly because of the water. It reminded us of the San Francisco Bay.

Miniature replica of a city
Miniatur Wunderland

 

Reykjavik

We went back to Iceland on the return trip, staying in Reykjavik. We visited the Hallgrímskirkja church.

Hallgrímskirkja

 

New York City

I've been to New York a number of times but the rest of the family had not been, so this was a special treat. We took tours of the United Nations and of the Empire State Building.

View of a large room with two concentric seating areas, the UN Security Council chamber
United Nations
View of Manhattan from high above
View from the Empire State Building