Skip to content

CONCEPT Cited by 1 source

LLM conversion hallucination control

Definition

LLM conversion hallucination control is the structural problem class that arises when an LLM is asked to perform a deterministic code-transformation task (e.g. convert an Enzyme test to a React Testing Library test, migrate code from one API to another, port code between languages) where correctness is binary: the output either compiles / runs / passes the same tests as the input, or it does not.

LLMs trained to produce plausible-sounding code naturally hallucinate — they emit syntax that looks right but references nonexistent methods, mixes API versions, invents imports, drops tests, or silently changes behaviour. For code conversion, plausible-but-wrong is worse than obviously-wrong: it passes eyeball review, breaks invisibly in CI.

The control problem is: how do you structure the pipeline around the LLM so that hallucination is bounded, not an open-ended failure mode?

Why naive prompting fails

Slack's 2024-06 retrospective disclosed that pure prompting of Anthropic's Claude 2.1 for Enzyme-to-RTL conversion produced "significant variation and inconsistency. Conversion success rates fluctuated between 40-60%. The outcomes ranged from remarkably effective conversions to disappointingly inadequate ones, depending largely on the complexity of the task." Crucially: "our attempts to refine prompts had limited success. Our efforts to fine-tune prompts may have complicated matters, possibly perplexing the AI model rather than aiding it." (Source: sources/2024-06-19-slack-ai-powered-conversion-from-enzyme-to-react-testing-library)

The insight: prompt engineering alone cannot solve this class of problem. A model that gets find('ComponentName') right one call and wrong the next call isn't going to be fixed by adding a seventh bullet to the prompt. The control has to be structural.

Mitigation mechanisms (canonical list)

Known mechanisms, ordered roughly by structural effectiveness:

  1. AST pre-pass: take the deterministic cases off the LLM's table entirely. A rule-based codemod handles patterns you can encode rules for (e.g. wrapper.find('button').simulate('click')userEvent.click(screen.getByRole('button'))); the LLM only sees the residue. Slack's AST pass alone solved ~45% of the conversion cases.

  2. In-code annotations: for patterns the AST pass can't fully resolve, it writes in-code comments that tell the LLM precisely what to look for / what the likely RTL equivalent is / what doc to consult. The annotations constrain the LLM's attention-surface far more effectively than stuffing the same guidance into the system prompt.

  3. DOM context injection: for RTL specifically, the correct query (getByRole vs getByTestId vs getByText) depends on the rendered DOM, not the test source. Capturing the DOM at test-run time and feeding it to the LLM removes a whole class of hallucination (guessing about DOM structure).

  4. Structured prompt with self-evaluation: Slack's prompt had three parts — context-setting + 10 mandatory + 7 optional tasks + a final self-evaluation instruction ("evaluate your output and make sure your converted code is between <code></code> tags. If there are any deviations from the specified conditions, list them explicitly."). The self-evaluation step is a cheap forcing function that gets the model to catch its own mis-steps.

  5. LLM + validator: downstream of the LLM, run a deterministic validator (parse the output, check syntax, run the tests). Reject anything that doesn't parse or doesn't preserve the test count. Slack's pass-rate buckets (fully / 50-99% / 20-49% / <20%) are a validator reading test results.

  6. Treat LLM output as untrusted: human verification before merge is mandatory. Slack: "the generated code was manually verified by humans before merging into our main repository".

The load-bearing insight

Slack articulates the core mechanism this way:

"Instead of solely relying on prompt engineering, we integrated the partially converted code and suggestions generated by our initial AST-based codemod. The inclusion of AST-converted code in our requests yielded remarkable results. By automating the conversion of simpler cases and providing annotations for all other instances through comments in the converted file, we successfully minimized hallucinations and nonsensical conversions from the LLM." (Source: sources/2024-06-19-slack-ai-powered-conversion-from-enzyme-to-react-testing-library)

The AST pass isn't just a conversion layer — it's a hallucination-control layer. Every case it resolves is one less case the LLM can hallucinate on; every annotation it leaves behind is a structural constraint on the LLM's decoding path that's much harder to ignore than a bullet in a system prompt.

Generalisation

The problem class is not specific to test migration. It applies to any deterministic code-transformation task at production scale:

  • Language ports (Java → Kotlin, Python 2 → 3, Objective-C → Swift)
  • API migrations (v1 → v2 of an internal library, framework upgrades)
  • Cross-framework conversions (Redux → Zustand, styled- components → Tailwind)
  • SQL dialect translations
  • Config-format migrations

Anywhere an LLM can hallucinate plausible-but-wrong output, the AST-pre-pass + annotation-as-guidance + DOM/context- injection + validator pattern gives you structural knobs that prompt engineering alone cannot.

Seen in

Last updated · 470 distilled / 1,213 read