Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
161433 stories
·
33 followers

Trump suggests rebranding AI with a new name, says he’s also creating an AI Force

1 Share
Trump claimed, without evidence, that the AI backlash is a Democratic hoax.
Read the whole story
alvinashcraft
59 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

AI Doom Backlash Arrives, Anthropic & OpenAI IPO Outlook, Frontier Business Momentum Slows

1 Share

Ranjan Roy from Margins is back for our weekly discussion of the latest tech news. We cover: 1) The WSJ says the Hugging Face might not be all that it was cracked up to be 2) The bots were told to hack? 3) Was this all a false flag for an effective altrust takeover? 4) Was this a smoke screen to conceal bad financial numbers from the AI labs? 5) Alex: Actually, maybe we should take these issues seriously? 6) Ranjan: They why aren't the labs being more transparent 7) Astra-6's crazy instructions to alter its personality in training 8) Anthropic hits $100 million in ARR 9) The IPOs will go on 10) The frontier AI business shows signs of slowing

---

Enjoying Big Technology Podcast? Please rate us five stars ⭐⭐⭐⭐⭐ in your podcast app of choice.

Want a discount for Big Technology on Substack + Discord? Here’s 25% off for the first year: https://www.bigtechnology.com/subscribe?coupon=0843016b

Learn more about your ad choices. Visit megaphone.fm/adchoices





Download audio: https://pdst.fm/e/tracking.swap.fm/track/t7yC0rGPUqahTF4et8YD/pscrb.fm/rss/p/traffic.megaphone.fm/AMPP8462448913.mp3
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

TypeSafe's Jev AI Model in .NET: A Community SDK for Structured AI Output in C#

1 Share

Most AI tooling I've used in the last couple of years follows the same pattern. You send a prompt, get back text, and then write brittle code to turn that text into something your app can actually use. JSON mode helps. Function calling helps more. Even then, you are still asking a model trained to write for people to produce machine-readable output, and hoping it doesn't wrap the JSON in a markdown fence, drop a field, or invent a category that is not in your enum.

Two days ago I found TypeSafe and its model, Jev. It takes a different approach. Instead of generating text, it evaluates typed questions against a piece of input and returns structured, typed decisions. No parsing. No "please respond only with valid JSON." I liked the idea enough that I had GitHub Copilot with HydraFusion port TypeSafe's official TypeScript SDK to .NET 11 and C# 15. This post is part introduction to the model and part walkthrough of the ported TypeSafe .NET SDK, so you can try it in C# yourself.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Finding Bugs

1 Share

Finding Bugs

Are generative (randomized) tests significantly more effective than example-based unit-tests at discovering bugs? There’s an interesting discussion about this on lobste.rs. One argument in favor of unit tests is, paraphrasing

My generic fuzzer wasn’t able to find this tricky bug in Rust regex crate.

To me, it seems that generative testing should shake out that particular creature, so I wrote a lil fuzzer of my own, and it indeed discovered another bug in that version of regex, and then the one I was after. I didn’t find anything in the latest version. I like to do a write up about the process, as it is a good case study for how one approaches a problem like this.

I want to be extra clear that my argument is very weak here, as I know exactly the bug I am after, and I even know that fuzzers can find it. My primary goal is to teach you the techniques, leaving it to your judgment just how effective they are. That being said, I think finding a second bug validates the approach somewhat.

I also want to emphasize that writing fuzzers to find known bugs is far from an idle amusement. While I believe that generative testing is very powerful, relative to its cost, it’s always a question whether a particular test is throughout enough. And it never is, you will find more bugs elsewhere (that’s why defense in depth and runtime mitigations are critical). And, whenever you have a pest that dodged your fuzzers, your first order of business is to treat this event as a bug in the fuzzer, and change it so that it can find this and related bugs. Only then you are allowed to add a fix and a unit test!

The Bug

For ".abb|b" regex and "zabb" input, an older version of regex crate returned b as the first match, which is incorrect, because the entire zabb matches:

use regex;

fn main() {
    let r = regex::Regex::new(".abb|b").unwrap();

    let m = r.find("zabb").unwrap();

    // Fails with regex-automata=0.4.15:
    assert_eq!(m.as_str(), "zabb")
}

How do we find this, or something like this?

Regular expression engines are one of the easiest things to apply generative testing to, they are pure algorithms. While few large systems are just an algorithm, algorithms are everywhere inside components of interesting systems, so this is a hands-on knowledge.

And by far the most important technique for testing algorithms is to compare with the known right answer, with an oracle. Implement both O(N log N) and O(N^2) versions of the algorithm, and match the answers.

To be fair, the original comment mentioned that the their fuzzer didn’t find the issue because they didn’t have access to an oracle. However, if you are designing a reliable system, it’s part of your job to ensure it has an oracle! One of the first things we did for our Jepsen test at TigerBeetle was to expose internal timestamps via API, to make it easier for Jepsen to find bugs (TigerBeetle is co-designed with its internal simulator VOPR which naturally has access to timestamps and anything else). And for, a regex engine, coming up with an oracle shouldn’t be hard, as they typically already come with multiple specialized implementations under a single facade, and the implementations can be cross-checked against each other.

But the regex case is even simpler (which makes it an excellent case study). There’s regex_lite crate that provides the same API.

So here’s a plan: generate a regular expression, an input text, and check that regex and regex_lite give identical answers.

Generating a String

I’ll start with code that generates a random string, as it is simpler, but still shows some non-trivial ideas. First, we’ll need a random number generator:

use fastrand::Rng;

There are fancier techniques, which can give you test-case minimization, exhaustive search, or coverage guided exploration, but the insight is that even a humble PRNG is brutally effective, if you put it to good use.

When you start with randomized testing, the instinct is to generate something big, no, HUGE! Surely regex will choke on 5 GiBs of input? This is usually a wrong call. Bugs usually involve small, but tricky examples, weaponizing interactions between a few features. A string where all characters are the same is more likely to trigger a bug than a purely random string where every character is unique.

So my default approach to generating strings is this. First, I fix the alphabet of possible characters. A nice way to get one is to sort | unique all the unit tests. Then, for each particular string, I pick a subset of that alphabet. I want strings that use all the characters, but I also want long strings with only a and b! Then I generate a string using the given subset of the alphabet, where the length of the string is also picked at random.

To make fuzzing efficient, I want to keep each iteration as fast as possible, so I make sure to re-use the memory across iterations, static allocation in the small:

use fastrand::Rng;

fn main() {
    let mut rng = Rng::new();

    // Re-use the same memory for all tests.
    let mut text_alphabet: Vec<u8> = vec![];
    let mut text: Vec<u8> = vec![];


    for _ in 0..1_000_000 {
        // It's unlikely that a counter example with
        // 7 different letters exists, while there
        // isn't one with just 6.
        alphabet_swarm(&mut rng, b"abcdef", &mut text_alphabet);
        let text =
            gen_string(&mut rng, &text_alphabet, &mut text);
    }
}

fn alphabet_swarm<'a>(
    rng: &mut Rng,
    all: &[u8],
    pick: &'a mut Vec<u8>,
) {
    pick.clear();
    pick.extend(all);
    rng.shuffle(pick);
    let count = rng.usize(1..=pick.len());
    pick.truncate(count);
}

fn gen_string<'a>(
    rng: &mut Rng,
    alphabet: &[u8],
    result: &'a mut Vec<u8>,
) -> &'a str {
    result.clear();
    // Again, this is a short string.
    // Longer failures are not likely.
    let count = rng.usize(0..8);
    for _ in 0..count {
        result.push(alphabet[rng.usize(0..alphabet.len())]);
    }
    str::from_utf8(result).unwrap()
}

There’s a nice way to think about this two step process, generating alphabet first, and then generating a string. To generate a string, you need a distribution of characters. You can use the same distribution for each of the million iterations. But an easy way to spice things up is to make the distribution itself random. I file this “randomize distributions themselves” idea under swarm testing.

Generating a Regex Distribution

Let’s apply the same tricks when generating a regex:

  • pick a subset of active regex features,
  • pick size at random,
  • re-use memory.

Let’s start with the first one:

#[derive(Default, Debug)]
struct ReOptions {
    alt: u16, // |
    rep: u16, // *
    any: u16, // .
    lit: u16, // 'a'
    sum: u16,
    alphabet: Vec<u8>,
}

Regexes have alternation r1|r2, repetition r*, wildcard ., and literals a. Rather then binary enabling or disabling a particular feature, I assign each feature a weight between 0 and 100, which is a bit more general. The sum is the total of all weights. To select a feature at random, we need to generate a number in 0..sum and find which segment it falls into.

In anything more serious, I’d introduce explicit types for probabilities and distributions, but just a two-digit number is perfectly serviceable in the small.

This is how I generate ReOptions, making sure that literals always have non-zero weight, and also selecting an alphabet for them:

impl ReOptions {
    fn swarm(&mut self, rng: &mut Rng, alphabet_full: &[u8]) {
        // We _still_ want to enable a few features at a time.
        self.alt = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.rep = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.any = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.lit = rng.u16(1..100);
        self.sum = self.alt + self.rep + self.any + self.lit;
        assert!(self.sum > 0);
        alphabet_swarm(rng, alphabet_full, &mut self.alphabet);
    }
}

Generating a Regex

So now we can generate a regular expression. This is convenient to do recursively. To avoid allocations, an output buffer is passed through. To control regex length, a size parameter is also threaded, and “branching” recursive invocations divide the size between the children:

fn gen_re(
    rng: &mut Rng,
    options: &ReOptions,
    result: &mut Vec<u8>,
) {
    result.clear();
    let size = rng.u8(0..8);
    gen_re_rec(rng, options, result, size);

}

fn gen_re_rec(
    rng: &mut Rng,
    options: &ReOptions,
    result: &mut Vec<u8>,
    size: u8,
) {
    if size == 0 {
        return; // Base case, empty regex.
    }

    // Pick one of the features, according to weights.
    let mut p = rng.u16(0..options.sum);
    if p < options.alt {
        // Alternation distributes the size
        // among the two children.
        let size_left = rng.u8(0..=size - 1);
        let size_right = size - size_left - 1;
        assert!(size == size_left + 1 + size_right);

        result.push(b'(');
        gen_re_rec(rng, options, result, size_left);
        result.extend(b")|(");
        gen_re_rec(rng, options, result, size_right);
        result.push(b')');
        return;
    }
    p -= options.alt;

    if p < options.rep {
        result.push(b'(');
        gen_re_rec(rng, options, result, size - 1);
        result.extend(b")*");
        return;
    }
    p -= options.rep;

    if p < options.any {
        gen_re_rec(rng, options, result, size - 1);
        result.push(b'.');
        return;
    }
    p -= options.any;

    if p < options.lit {
        gen_re_rec(rng, options, result, size - 1);
        let index = rng.usize(0..options.alphabet.len());
        let lit = options.alphabet[index];
        result.push(lit);
        return;
    }
    unreachable!();
}

Search Loop

Given that compiling regular expressions is somewhat slow, it seems like a good idea to try multiple strings for the same pair of regular expressions, which gives the following code:

fn main() {
    let mut rng = Rng::new();

    let mut options = ReOptions::default();
    let mut text_alphabet: Vec<u8> = vec![];
    let mut text: Vec<u8> = vec![];
    let mut re: Vec<u8> = vec![];

    let mut test_count: u32 = 0;
    for _ in 0..1_000_000 {
        options.swarm(&mut rng, b"abcdef");
        alphabet_swarm(&mut rng, b"abcdefx", &mut text_alphabet);

        gen_re(&mut rng, &options, &mut re);

        let re = str::from_utf8(&re).unwrap();
        let r1 = regex::Regex::new(re).unwrap();
        let r2 = regex_lite::Regex::new(re).unwrap();

        for _ in 0..1000 {
            test_count += 1;
            let text =
                gen_string(&mut rng, &text_alphabet, &mut text);

            let m1 = r1.find(text)
                .map_or("not found", |it| it.as_str());
            let m2 = r2.find(text)
                .map_or("not found", |it| it.as_str());

            if m1 != m2 {
                eprintln!("err re={re} text={text} m1={m1} m2={m2}");
                return;
            }

            if test_count % 500_000 == 0 {
                eprintln!("ok  re={re} text={text}");
            }
        }
    }
}

It produces examples similar to those in the issue, with a common suffix:

err re=(e)|(fee) text=xxfee

but also examples which somewhat different, without the shared suffix:

err re=(f..)*.d text=xfcbdd

All together:

use fastrand::Rng;

fn main() {
    let mut rng = Rng::new();

    let mut options = ReOptions::default();
    let mut text_alphabet: Vec<u8> = vec![];
    let mut text: Vec<u8> = vec![];
    let mut re: Vec<u8> = vec![];

    let mut test_count: u32 = 0;
    for _ in 0..1_000_000 {
        options.swarm(&mut rng, b"abcdef");
        alphabet_swarm(&mut rng, b"abcdefx", &mut text_alphabet);

        gen_re(&mut rng, &options, &mut re);

        let re = str::from_utf8(&re).unwrap();
        let r1 = regex::Regex::new(re).unwrap();
        let r2 = regex_lite::Regex::new(re).unwrap();

        for _ in 0..1000 {
            test_count += 1;
            let text =
                gen_string(&mut rng, &text_alphabet, &mut text);

            let m1 = r1.find(text)
                .map_or("not found", |it| it.as_str());
            let m2 = r2.find(text)
                .map_or("not found", |it| it.as_str());

            if m1 != m2 {
                eprintln!("err re={re} text={text} m1={m1} m2={m2}");
                return;
            }

            if test_count % 500_000 == 0 {
                eprintln!("ok  re={re} text={text}");
            }
        }
    }
}

fn alphabet_swarm<'a>(
    rng: &mut Rng,
    all: &[u8],
    pick: &'a mut Vec<u8>,
) {
    pick.clear();
    pick.extend(all);
    rng.shuffle(pick);
    let count = rng.usize(1..=pick.len());
    pick.truncate(count);
}

fn gen_string<'a>(
    rng: &mut Rng,
    alphabet: &[u8],
    result: &'a mut Vec<u8>,
) -> &'a str {
    result.clear();
    let count = rng.usize(0..8);
    for _ in 0..count {
        result.push(alphabet[rng.usize(0..alphabet.len())]);
    }
    str::from_utf8(result).unwrap()
}

#[derive(Default, Debug)]
struct ReOptions {
    alt: u16, // |
    rep: u16, // *
    any: u16, // .
    lit: u16, // 'a'
    sum: u16,
    alphabet: Vec<u8>,
}

impl ReOptions {
    fn swarm(&mut self, rng: &mut Rng, alphabet_full: &[u8]) {
        self.alt = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.rep = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.any = if rng.bool() { 0 } else { rng.u16(0..100) };
        self.lit = rng.u16(1..100);
        self.sum = self.alt + self.rep + self.any + self.lit;
        assert!(self.sum > 0);
        alphabet_swarm(rng, alphabet_full, &mut self.alphabet);

    }
}

fn gen_re(
    rng: &mut Rng,
    options: &ReOptions,
    result: &mut Vec<u8>,
) {
    result.clear();
    let size = rng.u8(0..8);
    gen_re_rec(rng, options, result, size);

}

fn gen_re_rec(
    rng: &mut Rng,
    options: &ReOptions,
    result: &mut Vec<u8>,
    size: u8,
) {
    if size == 0 {
        return; // Base case, empty regex.
    }

    // Pick one of the features, according to weights.
    let mut p = rng.u16(0..options.sum);
    if p < options.alt {
        // Alternation distributes the size
        // among the two children.
        let size_left = rng.u8(0..=size - 1);
        let size_right = size - size_left - 1;
        assert!(size == size_left + 1 + size_right);

        result.push(b'(');
        gen_re_rec(rng, options, result, size_left);
        result.extend(b")|(");
        gen_re_rec(rng, options, result, size_right);
        result.push(b')');
        return;
    }
    p -= options.alt;

    if p < options.rep {
        result.push(b'(');
        gen_re_rec(rng, options, result, size - 1);
        result.extend(b")*");
        return;
    }
    p -= options.rep;

    if p < options.any {
        gen_re_rec(rng, options, result, size - 1);
        result.push(b'.');
        return;
    }
    p -= options.any;

    if p < options.lit {
        gen_re_rec(rng, options, result, size - 1);
        let index = rng.usize(0..options.alphabet.len());
        let lit = options.alphabet[index];
        result.push(lit);
        return;
    }
    unreachable!();
}

https://github.com/matklad/regex-fuzz

Takeaways:

  • Fuzzing against an oracle is effective, which is a strong motivation to build an oracle!
  • Go for small, tricky examples, rather than large uniform ones.
  • Real fuzzers are cool, but, if you know something, even xoroshiro can be dangerous.
  • Black box testing is cool, but co-designing system and its testing harness is a point of leverage (build an oracle!).
  • This stuff is not rocket science, you don’t need a Haskell PhD to apply these ideas.
Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Grit your teeth and ship it

1 Share

Being good at building and being good at shipping are two separate skills. In the short term, they’re actually countervailing: if you have a gift for building, you’re likely to be worse at shipping. Ira Glass has a classic quote about this.

All of us who do creative work, we get into it because we have good taste. But there is this gap. For the first couple years you make stuff, it’s just not that good. It’s trying to be good, it has potential, but it’s not. But your taste, the thing that got you into the game, is still killer. And your taste is why your work disappoints you.

The only way around this is to grit your teeth and ship it. You have to force yourself to publish things you’ve made even when you think they’re crap.

Programming

Gifted programmers have a nearly pathological desire to build elegant, correct, neat systems. That’s what motivates them to learn the arcane details of their languages, or to spend time polishing and refactoring over and over again. But it’s also what makes them reluctant to ship. Any flaws in the software bother them on an emotional level. If they ship with those flaws, they feel like people will think they weren’t paying enough attention to notice them, or that they weren’t good enough to fix them.

This is annoying when you’re writing software on your own, but it’s completely fatal when you’re working in a tech company. Any large software system is covered in flaws, whether due to time pressure, relative inexperience, wicked features, or a hundred other reasons. Working with it is a process of compromise: of finding the best possible solution given the quirks and foibles of the codebase. In fact, since the most important thing in large codebases is consistency, the right thing to do is sometimes to duplicate flaws, assuming they’re not catastrophic.

Gifted programmers often freeze up. I’ve often seen them retreat to smaller domains where they can safely make the code “correct”: tweaking dev-environment setup, or refactoring tests. Sometimes they just do nothing, and spin in shame and guilt (plus the compounding shame of not achieving anything) until they implode and quit. If they had worse taste, they wouldn’t be as good at programming, but they’d be a lot more useful. You can typically improve a bad diff with time and effort. You can’t improve no diff.

Writing

I have a sensitive eye for awkward sentences and uneven prose. That can make writing an unpleasant process: I know what I’m trying to say, but I can’t seem to say it in a way that’s as clear and as elegant as I know is possible. More than half the time I finish drafting a blog post, I look at the post and don’t think it’s very good. But I (mostly) grit my teeth and publish it anyway, because you have to bias towards shipping.

Like any skill, shipping gets easier the more you practice it. If I don’t publish a blog post for a month, I always feel like the next draft is too poorly-written or uninteresting to put out there. But when I’m publishing a post per day, I typically feel great about each draft. When I go back and read my old posts, I can’t tell which ones I felt good about and which ones I felt bad about. There’s no correlation between that and the posts that become popular. Here are some posts I didn’t like as I was writing them but that resonated with my audience:

Here are some posts I thought were pretty good but that didn’t find popularity:

You just can’t predict what people will find interesting or useful. Producing a high volume of work thus gives much better yield than a small amount of highly-polished work.

It can be disheartening to realize that some of your most casual, throwaway work will be more successful than the work you slaved over1. Specifically, it’s disheartening because it means realizing you don’t have control over your own success. You can’t produce something successful by focusing on a single piece until you’re satisfied it’s great. Instead, you just have to do a lot of things and see what sticks. You have to be momentum-based, not outcome-based. In other words, you have to grit your teeth and ship it.

One common reason to write less is getting overly precious about your ideas. If you think you’ve got a really compelling concept, you don’t want to “waste it” on a poorly-written story. But in fact you can just write about the same thing over and over until you get it right! I have written like thirty blog posts about shipping (this is one of them), or about how tech companies work, or about how internal emotional regulation is as important as technical ability. I expect to continue writing and thinking about these ideas for as long as I find them interesting.


  1. Anthony Burgess famously claimed to have “knocked off” A Clockwork Orange in three weeks, and Arthur Conan Doyle considered his largely-forgotten historical novel Sir Nigel to be far better than his Sherlock Holmes stories.

Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete

Build a TPL Dataflow Pipeline in Modern .NET

1 Share

Build a bounded TPL Dataflow pipeline in .NET 10 with explicit capacity, concurrency, ordering, cancellation, coordinated faults, and terminal completion.



Read the whole story
alvinashcraft
1 hour ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories