<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>rtb</title><description>Personal website of Reed Taylor Barnes</description><link>https://reedtaylorbarnes.com/</link><item><title>Coding against the beat</title><link>https://reedtaylorbarnes.com/blog/coding-against-the-beat/</link><guid isPermaLink="true">https://reedtaylorbarnes.com/blog/coding-against-the-beat/</guid><description>Play your heart out.</description><pubDate>Wed, 20 Aug 2025 00:00:00 GMT</pubDate><content:encoded>When I was learning to play the drums (and here I should mention that I&apos;m pretty bad at the drums), my instructor told me about how you could know you had *really* learned a song you were practicing. &quot;Take a different song, something loud and punchy,&quot; she said, &quot;and put it in your headphones. If you can play your song *while another beat is playing in your ears*, you can really play it.&quot;

I work very regularly with Cursor, which provides LLM-powered autocompletes. Very good ones!

{/* pic */}

I accept ~20% of the suggestions I get, which to me feels like quite a lot. It often writes big chunks, so I would say it&apos;s responsible for a solid 30% of the total code I write—more, in certain domains like throwaway shell scripts. 

These completions are a pretty big speed-up in my daily workflow, and I&apos;d miss them dearly if one day they turned off forever. However, I frequently take &quot;days off&quot; (particularly at the start of a project) to code unassisted in order to fight the core problem with incorporating LLMs into your workflow: even (especially?) if you&apos;re already good at the thing you ask them to do, the tendency is to turn off your critical thinking, accept whatever comes out, and cruise right along.

I think a lot of people assume, intuitively, that this is a bad thing. I think those people are correct—but it&apos;s worth being specific about why! Personally, it&apos;s bad for me to offload my thinking to an LLM because:
1. I don&apos;t build as sharp of a mental model of my solution. This makes it hard, at a certain point, to improve or debug the solution.
2. I don&apos;t think as critically about error cases and sharp edges, because I don&apos;t see them emerge from the structure of the code as I write it.
3. I lose fluency with the language/framework/codebase that I&apos;m working on—at the very least, I don&apos;t get *more* fluent.

These are all, y&apos;know, straightforwardly bad. But for one thing, they might be worth it anyway; for another, they might be easy to mitigate.

I&apos;ve got two practices that have helped me stay sharp with LLM-powered programming workflows. The first, like I mentioned, is frequently working unassisted. I highly recommend binding an easy keyboard command to whatever your &quot;enable/disable LLM autocomplete&quot; is. It&apos;s a really good habit to be frequently asking yourself whether the code you&apos;re about to write is something you should write alone.

The other practice is something I (admittedly cheesily) call &quot;coding against the beat&quot;: writing code with autocomplete enabled—ideally, a very fast and effective one—and refusing to accept any of the solutions, *even if* they&apos;re exactly what I want to write.

Programming this way forces me to have a crystal-clear vision of my intended solution. If I&apos;m being bombarded with ideas by an overeager intern who types at 150wpm, I have to constantly reaffirm to myself why what I&apos;m writing will solve the problem better. It&apos;s kind of a mental workout; it&apos;s actively draining, but I feel significantly more in-control of my solution after doing it.

Now, this isn&apos;t a perfect mitigation. In particular, I&apos;ve noticed that I get serious tunnel vision on whatever solution I&apos;ve decided to implement. I&apos;m so focused on writing a websocket event service that I forget to ask whether I even need real-time updates at all—which is likely a more important skill in the first place!

Despite the downsides, however, I&apos;m confident that LLM-generated code will continue to have a place in my workflow—likely, a larger and larger one—and so I&apos;m happy with anything that lets me mitigate the downsides so I can get back to adopting cool new tools.

If you have a practice you use to keep yourself sharp as you use AI tooling, I&apos;d love to hear it! Please feel free to drop me a line at reedbarnes98@gmail.com.</content:encoded></item><item><title>Refactoring for, and with, velocity</title><link>https://reedtaylorbarnes.com/blog/refactoring-for-and-with-speed/</link><guid isPermaLink="true">https://reedtaylorbarnes.com/blog/refactoring-for-and-with-speed/</guid><description>Going slow to go fast really does work!</description><pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate><content:encoded>When I joined [Mintlify](mintlify.com), my first real project was to help refactor one of the oldest areas of the codebase, the API reference pages. There were some concrete performance improvements we wanted to make, but more than any immediate customer-facing gains, the biggest goal was to make the code easier to maintain and extend.

However, we&apos;re a startup; we don&apos;t have endless time to write code purely for the sake of having beautiful, elegant artifacts. So how do you strike the balance between taking the time to Do It Right and actually getting something done?

I don&apos;t think there&apos;s a silver bullet answer, but I think there are a few things that helped guide us to a really great result.

### Unit tests are worth their weight in gold when refactoring

One _huge_ advantage we had going into this refactor was that while the old version was a rat&apos;s nest of layered-on complexity and hastily-appended edge cases, almost everything that had been added had _some_ kind of unit test to capture the bug which had originally prompted the change.

These changes were not always particularly well-documented, if at all, which meant that sometimes we had to accept that we didn&apos;t know exactly why some output needed to look a certain way—but we _did_ know what that certain way was.

We set a goal of paying this work forward, writing copious tests for our new functionality; not only will this be useful for future devs, it was hugely useful in the final days of the project as we were shipping a little more frantically trying to get things over the line!

### Write for your future self

When you&apos;re trawling through two-year-old code, wondering how the hell a thinking human being could have possibly written such insanity, it&apos;s helpful to remember all the times that _you_ have written ridiculous code that someone had to clean up in the past.

The times that we found components which left our jaws on the floor, or hacks that left us in disbelief—those are the times we put the _most_ effort into our refactors.

When you&apos;ve had to fix bugs in someone else&apos;s code at two in the morning, and you finally get a chance to set things right, you&apos;ve gotta take the opportunity to think of the next dev at 2am!

### Refactored software has the same quality bar as the old software

Regardless of any crowdthink about &quot;move fast and break things,&quot; I wouldn&apos;t feel right about shipping software that I didn&apos;t think worked properly.

However... if the old software was a little crunchy around the edges, you do get a little permission to accept that some things might not work perfectly in v2, either.

When you&apos;re drained and getting close to the end of a project, it&apos;s important not to let perfect be the enemy of the good; if there&apos;s one place you _know_ you can safely cut a few corners, it&apos;s the place where the corners have already been cut!

---

If you find yourself in a position to be refactoring code for the explicit benefit of future developers, I really do believe you should be grateful to have found yourself in such a position, and take every advantage to create something you&apos;ll be proud to look back on. Just remember that it doesn&apos;t matter to anybody until you actually get it shipped!</content:encoded></item><item><title>Streaming LLM responses is not a mystic art</title><link>https://reedtaylorbarnes.com/blog/streaming-and-ai-sdk/</link><guid isPermaLink="true">https://reedtaylorbarnes.com/blog/streaming-and-ai-sdk/</guid><description>Content-Type: application/x-ndjson is kind of a dream.</description><pubDate>Sun, 19 Oct 2025 00:00:00 GMT</pubDate><content:encoded>import Callout from &quot;../../components/Callout.astro&quot;;

Streaming stuff over HTTP is a lot easier than I thought! 

When experimenting with various LLM-enabled ideas recently, I&apos;ve stuck to a fairly simple backend API structure: HTTP server has controllers, those controllers await some asynchronous request(s) to a model API, and then I return the results. 

I knew that streaming responses existed, of course, and can see from daily use (in ChatGPT, in coding agents) how useful it can be for a nicer UX. In my head, though, streaming was this weird, arcane topic; I kept thinking &quot;eh, let&apos;s keep it simple&quot; and never really diving in.

During a recent hackathon project at [Mintlify](mintlify.com), streaming was too obvious of an idea to pass up, and so I had Claude write up a basic version of streaming to get me started. The code was *much* shorter and easier to read than I&apos;d anticipated! Here&apos;s a version I stripped down to be even simpler, no helper functions or anything:

```javascript
import { streamText } from &quot;ai&quot;;

// server is a simple Express app
app.post(&quot;/api/stream&quot;, async (_req, res) =&gt; {
  // Tell the client to accept event-stream
  res.writeHead(200, {
    // Also common to use &quot;text/event-stream&quot; for Server-Sent Events
    &quot;Content-Type&quot;: &quot;application/x-ndjson&quot;,
    &quot;Cache-Control&quot;: &quot;no-cache&quot;,
    Connection: &quot;keep-alive&quot;,
  });

  // ai-sdk method
  const result = streamText({
    model: openai(&quot;gpt-4o-mini&quot;),
    prompt: &quot;Tell me a short story about a robot learning to paint.&quot;,
  });

  // data type 1: text to append to the frontend 
  for await (const chunk of result.textStream) {
    res.write(JSON.stringify({ type: &quot;text&quot;, content: chunk }) + &quot;\n&quot;);
  }

  // data type 2: running count of token usage
  const usage = await result.usage;
  res.write(JSON.stringify({ type: &quot;usage&quot;, tokens: usage.totalTokens }) + &quot;\n&quot;);
  res.end();
});
```



[\@ai-sdk](https://ai-sdk.dev/) is an unsung hero here. Being able to swap in different models with a single line change is obviously huge, but the `streamText` method is a simple abstraction while being flexible enough to do pretty much whatever you want it to. `streamObject` is also great for structured output via OpenAPI spec or (my preference) Zod schema. Generally, I think the DX is super nice; highly recommend dropping it in instead of (my previous default) the OpenAI SDK. I haven&apos;t even gotten into tool-definition/calling or agent-loop control stuff, but it feels really easy to pick up.

&lt;Callout type=&quot;info&quot; title=&quot;Aside: What&apos;s the difference between `text/event-stream` and `application/x-ndjson` content types?&quot;&gt;
Both are ways to send streams of data over HTTP, but have distinct formats and typical uses. `text/event-stream` is used for [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), where the server sends specially-formatted text messages with `data:` prefixes and double newlines—this is one of the default options for [MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) servers, incidentally. It&apos;s natively supported by browsers via the `EventSource` API. 

Personally, I kinda like NDJSON (Newline-Delimited JSON) better: each line is a valid JSON object, and that&apos;s it, no special prefixing. There might be some reason to do SSE instead, but I haven&apos;t hit it yet!
&lt;/Callout&gt;  

The code to receive the stream on the frontend is very reasonable, too:

```javascript
const [text, setText] = useState(&apos;&apos;)
const [tokens, setTokens] = useState&lt;number | null&gt;(null)
const [streaming, setStreaming] = useState(false)

const startStream = async () =&gt; {
  setText(&apos;&apos;)
  setTokens(null)
  setStreaming(true)

  const response = await fetch(&apos;http://localhost:3001/api/stream&apos;, {
    method: &apos;POST&apos;,
  })

  const reader = response.body?.getReader()
  const decoder = new TextDecoder()

  while (reader) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    const lines = chunk.split(&apos;\n&apos;).filter(line =&gt; line.startsWith(&apos;data: &apos;))

    for (const line of lines) {
      const data = JSON.parse(line.slice(6))
      
      if (data.type === &apos;text&apos;) {
        setText(prev =&gt; prev + data.content)
      } else if (data.type === &apos;usage&apos;) {
        setTokens(data.tokens)
      }
    }
  }

  setStreaming(false)
}
```

This is without any kind of helper functions or type-safety! A couple super quick wrappers make this even nicer:

```js del={46, 51} ins={1-38, 47, 52}
import { z } from &quot;zod&quot;;

// Event schemas for type safety
const TextEventSchema = z.object({
  type: z.literal(&quot;text&quot;),
  content: z.string(),
});

const UsageEventSchema = z.object({
  type: z.literal(&quot;usage&quot;),
  tokens: z.number(),
});

const StreamEventSchema = z.discriminatedUnion(&quot;type&quot;, [
  TextEventSchema,
  UsageEventSchema,
]);

// Helper functions for cleaner streaming
function withNDJSONHeaders(res: Response) {
  res.writeHead(200, {
    &quot;Content-Type&quot;: &quot;application/x-ndjson&quot;,
    &quot;Cache-Control&quot;: &quot;no-cache&quot;,
    Connection: &quot;keep-alive&quot;,
  });
}

const writeEvent = (res: Response, data: z.infer&lt;typeof StreamEventSchema&gt;) =&gt; {
  res.write(JSON.stringify(data) + &quot;\n&quot;);
};

const writeText = (res: Response, content: string) =&gt; {
  writeEvent(res, { type: &quot;text&quot;, content });
};

const writeUsage = (res: Response, tokens: number) =&gt; {
  writeEvent(res, { type: &quot;usage&quot;, tokens });
};

// Updated controller:
app.post(&quot;/api/stream&quot;, async (_req, res) =&gt; {

  // ...

  for await (const chunk of result.textStream) {
    res.write(`data: ${JSON.stringify({ type: &quot;text&quot;, content: chunk })}\n\n`);
    writeText(res, chunk);
  }

  const usage = await result.usage;
  res.write(`data: ${JSON.stringify({ type: &quot;usage&quot;, tokens: usage.totalTokens })}\n\n`);
  writeUsage(res, usage.totalTokens);
  res.end();
});
```

Now we&apos;ve got type-safe events—and you can see how easy this would be to extend to a ton of different events, all being sent back from the same response.

And on the frontend:

```js del={36-41} ins={1-13, 41-51}
import { StreamEventSchema } from &quot;./schemas&quot;;

const parseStreamData = (line: string) =&gt; {
  if (!line.startsWith(&apos;data: &apos;)) return null;

  try {
    const rawData = JSON.parse(line.slice(6));
    return StreamEventSchema.parse(rawData);
  } catch (error) {
    console.warn(&apos;Invalid stream data:&apos;, error);
    return null;
  }
};

// Updated streaming logic
const startStream = async () =&gt; {
  setText(&apos;&apos;)
  setTokens(null)
  setStreaming(true)

  const response = await fetch(&apos;http://localhost:3001/api/stream&apos;, {
    method: &apos;POST&apos;,
  })

  const reader = response.body?.getReader()
  const decoder = new TextDecoder()

  while (reader) {
    const { done, value } = await reader.read()
    if (done) break

    const chunk = decoder.decode(value)
    const lines = chunk.split(&apos;\n&apos;).filter(line =&gt; line.startsWith(&apos;data: &apos;))

    for (const line of lines) {
      const data = JSON.parse(line.slice(6))
      if (data.type === &apos;text&apos;) {
        setText(prev =&gt; prev + data.content)
      } else if (data.type === &apos;usage&apos;) {
        setTokens(data.tokens)
      }
      const data = parseStreamData(line)
      switch (data.type) {
        case &apos;text&apos;:
          setText(prev =&gt; prev + data.content);
          break;
        case &apos;usage&apos;:
          setTokens(data.tokens);
          break;
        default:
          console.warn(&apos;Unknown event type:&apos;, data.type);
      }
    }

    setStreaming(false)
  }
}
```

So ends another tale of &quot;code pattern I thought was hard/scary for no reason is actually simple and good.&quot; You&apos;d think I would learn at some point!</content:encoded></item><item><title>Writing is only getting more important</title><link>https://reedtaylorbarnes.com/blog/writing-and-thinking/</link><guid isPermaLink="true">https://reedtaylorbarnes.com/blog/writing-and-thinking/</guid><description>The right words have never been more valuable than they are right now.</description><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>Writing and speaking, for me, _are_ thinking.

I learned early in school that I had a talent for winging a presentation or bashing out a paper hours before the deadline. In hindsight, this had less to do with procrastination than it did with my own brain: I wasn&apos;t skipping the work to make something up at the last minute; I was _doing the work by making something up_. I wish I&apos;d known earlier that what I thought was a cheap hack was actually just how I processed information!

This distinction—between &quot;making something up&quot; and &quot;thinking through an idea&quot;—has gotten much more interesting to me over the last few years, for probably obvious reasons. The concept of a computer &quot;making something up&quot; was essentially science fiction half a decade ago; now we talk about LLM hallucinations as a matter of daily pragmatism.

---

It&apos;s common knowledge, these days, that we&apos;re all thinking less: LLMs are stealing our cognitive abilities, we&apos;re just operators for our soon-to-be robot overlords, and so on.

Except I don&apos;t actually find this to be true! Even as an enthusiastic early adopter of a ton of different AI tools, I&apos;ve never had to think harder about the words I put into a computer.

Oddly, I&apos;ve always found it easy to write code _without_ thinking—to jump straight to the friendly shapes of loops and logs without actually working through the problem front to back.

It&apos;s pretty funny to see this exact tendency manifest in coding agents. Writing more code is easy! It&apos;s quite a bit harder to write _less_ code, and harder still to know when you&apos;re going the wrong way entirely.

Now, I spend ~20% of my day explaining things to LLMs! As forgiving as coding agents might seem to be at first, anybody who&apos;s let Claude loose in a codebase knows it&apos;s incredibly easy to blast out 1000s of lines of code over which you have almost no understanding.

The wonderful side effect of this is that when I take prompting and explaining really seriously, it makes my work _much better_. Software engineers have long been familiar with the idea of rubber-duck programming. Just taking the time to explain your thoughts to someone, or something, can reveal things in minutes that you hadn&apos;t realized in hours of your own attempts to understand. Now, that&apos;s the start of nearly every software task I undertake.

While many AI tools make it absurdly easy to accomplish 80% of a task, I&apos;ve yet to find any tool that can nail the final 20% that always seems to take 80% of the effort. In fact, I&apos;m beginning to suspect that&apos;s just the nature of building. The more quickly you can get work done, the faster you get to the edge cases, the iterative improvement—the genuinely valuable stuff, as a matter of fact.

---

The nascent profession of &quot;prompt engineer&quot; received a great deal of mockery in 2023. &quot;Imagine,&quot; we laughed, &quot;a whole job title for somebody who tweaks the input to LLMs doing the actual work!&quot;

Except I don&apos;t know _anyone_ today who denies the importance of putting the right information into an LLM. Prompt engineering isn&apos;t an individual job, it&apos;s a table-stakes skill for an increasingly-large share of all knowledge work.</content:encoded></item><item><title>Building software on top of an LLM is hard, but not that hard</title><link>https://reedtaylorbarnes.com/blog/building-with-llms/</link><guid isPermaLink="true">https://reedtaylorbarnes.com/blog/building-with-llms/</guid><description>Concrete advice for the proper care and feeding of your stochastic parrot.</description><pubDate>Sat, 16 Aug 2025 00:00:00 GMT</pubDate><content:encoded>There&apos;s so much noisy discourse about AI chatbots, image generator slop, trillion-dollar server farms, and the impending dawn of superintelligence that I think it&apos;s easy to forget how just how _neat_ LLMs already are—at least, to me. We taught a long chain of matrix multiplications to talk like people! Not only can they hold a half-decent conversation, but we can actually use that simulated thought to solve certain classes of simple problems that were previously just &quot;not something computers can do&quot;! That&apos;s completely nuts!

Because LLMs are so fascinating to me as a technology, and because they seem so naturally suited to the problem space of product copy (which is what I work on at [Ditto](https://dittowords.com)), I had been itching for a reason to build something on top of a language model. Okay, yes, this is the classic software engineer trap of &quot;if only I could find a problem for this solution,&quot; but I&apos;ll spoil the ending by saying this was a really good idea, and we built a pretty cool feature. However, we didn&apos;t get to a feature without learning some meaningful lessons that weren&apos;t totally intuitive to me up front; hopefully, I can save someone else some time.

## You need to be doing more manual testing than you think

Actually, more than that. Nope, sorry—even more!

Working with truly non-deterministic outputs is _extremely_ foreign to a traditional software development workflow. Sure, certain tech stacks can make you feel like you&apos;re playing a cursed slot machine, but I promise you no race condition or CSS stacking issue compares to the latent possibilities of human language! Even if you think you know exactly how inconsistent and strange LLM output can be, you&apos;re likely underestimating **with respect to your specific problem space**.

I went into this project knowing ahead of time that models be crazy, preparing to do a lot of evals; what I didn&apos;t realize was how important it would be to develop an **extremely-well honed sense of taste** for your system&apos;s outputs across **across many, many possible inputs**. Testing for early feasibility and getting a preliminary idea of good vs. bad was pretty quick, and gave me a false sense of confidence. We ended up needing to do a ton of iteration on our system prompt + default examples in the final week leading up to shipping our product because we&apos;d overestimated based on our very limited set of early test inputs.

### Building internal test tools pays huge dividends

One of the best decisions I made was to spin up a little web app very early on that we could use to test the feasibility of our idea. This let our designer, and anyone else who&apos;s not always in the codebase, iterate on things like system prompts, possible inputs, and simply try the same thing over and over to see how much variation we were getting. Throughout the project, I added new options and tools to this app as we needed them, and it was critical particularly for building the sense of taste I describe above; generating a massive grid of 50 responses and quickly scanning them was dramatically quicker than testing from within the product.

Bonus note: you know who&apos;s reeeeally good at building one-off / non-customer-facing software tools astonishingly quickly? I&apos;ll give you one guess!

### Early prototypes are even more important than usual

You could write this about any form of software development, I think, but it&apos;s true ten times over with a product that has non-deterministic foundations. Test harnesses and manual evals can take you a long way, but there are myriad edge cases and weird hangups that you simply will not encounter until you&apos;re actually using your text generation service in something that at least comes _close_ to a real product.

## LLM APIs are really, really cheap.

Two months ago, I would&apos;ve thought this was an actively controversial take; it might still be, honestly. At some point, I&apos;d gotten the idea in my head that running _any_ kind of inference-based product was going to be really, really expensive. You hear about OpenAI melting GPUs and vibe-coders getting surprise $100k invoices, and if you&apos;re me, you start to get a little nervous about the feasibility of your project. I am very unused to having to think about _truly marginal_ costs in software, and the safe engineer tendency is to plan for the worst: what happens if we get 100x—or 10,000x—the usage we&apos;re expecting?

We use Gemini 2.5 Flash Lite, which currently (8/16/25) costs $0.10/1M input tokens and $0.40/1M output tokens. In two months developing and testing our feature, plus a month of the feature being widely available, our GCP bill came to a grand total of **$25.46.**

I don&apos;t wanna be totally careless with money, but let&apos;s be honest: that is **literally nothing** relative to the costs of running even a small software company. Frankly it is probably too _little_ spend; as I mentioned above, I wish we had been running _way_ more, and larger, tests earlier in the development process.

Now, there are some caveats. Our use case means sending fairly small payloads—p95 less than 10,000 tokens, p50 under 1000. It&apos;s also important to note that we&apos;re a company with a _relatively_ small userbase, compared to large B2C products, and there&apos;s less of a possibility of a viral moment; if I _were_ truly anticipating the possibility of 100x users tomorrow, I would probably have had to rethink the foundations here, or at least sit my CEO down for a quick chat about finances.

## LLM-as-judge is a quick win in many cases

Influenced by both fears of exploding costs and worries about multiplying the non-deterministic nature of our feature, I initially dismissed the idea of submitting the LLM-generated output back to another LLM to assess it for quality.

This was dumb! As it turns out, if you have the prompt + contextual data required to get anything close to good output from your generations, you almost certainly have all the context you need to have an LLM constructively critique those generations.

The route I found most productive was prompting the judge LLM with a long and explicit prompt about how to judge properly, which included an explicit scoring guide plus instructions to return a &quot;quality&quot; score between 0 and 100.

```
rest of prompt
...
QUALITY SCORING GUIDANCE:
- 90-100: Essential correction that clearly improves the text
- 70-89: Helpful improvement that aligns well with rules
- 50-69: Minor improvement with questionable necessity
- 30-49: Unnecessary change that doesn&apos;t add value
- 0-29: Harmful change that actively worsens the text
```

Then, we just reject any suggestions with a quality below 50. This is not a perfect filter by any means; it probably removes some outputs we&apos;d actually like to see. But for our particular product needs, it&apos;s worse to return a suggestion that sucks than to return nothing at all.

Many people will tell you—or at least, various blog posts told me—not to do this, because &quot;LLMs don&apos;t know math&quot; or something like that. For one, that&apos;s not nearly as true these days as it once was; ask a frontier model what 22+53 is and you&apos;ll get the right answer. For another, language models are **great** with associative relationships.

I _didn&apos;t_ give any explicit examples of judgement. I tried a few, but found that early results were too easily polluted by specific details from the examples I provided. That leads me to...

## Examples are footguns

One of the earliest pieces of conventional LLM-prompting wisdom I remember seeing was &quot;give examples.&quot; This is, in fact, good advice, but it&apos;s important to keep in mind the _generality_ of both your examples and your overall prompt. LLMs will index heavily on any examples you provide them, and it&apos;s easy to poison your results with a single bad example. In our case, we found that about half of our examples had typographic quotes (&apos;) and half had standard quotes (&apos;)—leading to constant suggestions flip-flopping back and forth between the two.

## Epilogue

One of the infuriating things about building software is that by the end of a project, you wish you could do the whole thing over again with all the new stuff you learned. One of the amazing things about building software is that it&apos;s malleable enough to _actually do that_.

We&apos;ve already iterated on our system prompt and default context a number of times since launch, and we&apos;re gathering tons of great data on usage which should let us refine the product as we go—and that&apos;s before we get into anything like finetuning a model for our own usecases.

It&apos;s a very exciting time to be building software that has anything to do with words—while LLMs are not a cure-all, they&apos;re pretty damn neat. Get out there and play around!

P.S.: if you&apos;re struggling to manage product copy at scale, I highly recommend you check out our product at [Ditto](https://www.dittowords.com/)!

P.P.S.: if the process of building this sounds interesting to you, you might be interested in working with me—we&apos;re [hiring](https://www.dittowords.com/careers/software-engineer))!</content:encoded></item></channel></rss>