Code Generation › Docstring-to-Code Generation

Michael BrenndoerferFebruary 3, 20267 min read
Language AI HandbookAll chapters
Part 47 of 67Code Generation
Chapter 360Code Generation › Docstring-to-Code Generation
Reading section 4 of 13 · Docstring-to-Code Generation · 7 minSection 4 of 13 · 7 min

Book contents

Section 4 of 13 · 7 min read

Code Generation › Docstring-to-Code GenerationOne section · Sections in this chapter4 / 13 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Docstring-to-code generation is the dominant evaluation setting for code generation models. The input is a function signature paired with a natural language description, and the model must produce the complete implementation. This section covers how the problem is set up, why ambiguity in specifications is the central challenge, and how bidirectional context from fill-in-the-middle training changes what the model can infer.

The Problem Setup

In docstring-to-code generation, the input to the model takes a form like:

In[3]:
Code
def find_median(numbers: list) -> float:
    """
    Find the median value of a list of numbers.
    The median is the middle value when the list is sorted.
    For even-length lists, return the average of the two middle values.
    """

The model must complete this with a correct implementation. This sounds simple, but the difficulty scales quickly. The docstring above is unusually clear. Real docstrings are often ambiguous, incomplete, or inconsistent with edge case behavior. The model must make choices about how to handle empty lists, lists with one element, lists with duplicate values, and so on, all from a specification that may not mention these cases.

The reason docstring-to-code is a productive research formulation is that it isolates the generation problem cleanly. The function signature provides type hints and naming conventions. The docstring provides intent. The rest is implementation. This structure makes it possible to evaluate generated code automatically by running test suites.

When we break down what a model must do to succeed at this task, the components are:

  • Intent parsing: Understanding what the function is supposed to compute, including which words in the docstring correspond to specific algorithmic operations
  • Edge case inference: Reasoning about what happens at the boundaries of the input space even when the docstring is silent
  • Algorithmic selection: Choosing an appropriate algorithm or data structure when multiple options are available
  • Syntax production: Generating code that is syntactically valid in the target language
  • Semantic consistency: Ensuring that all parts of the implementation are logically consistent with each other

Models fail on each of these components in different ways. Intent parsing fails when domain-specific terminology or ambiguous phrasing leads the model astray. Edge case inference fails when the training distribution does not include similar patterns. Algorithmic selection fails when the simplest approach the model would normally use is not appropriate for the given constraints. Syntax production failures are rare in modern large models but can occur for unusual language constructs.

Handling Ambiguity in Specifications

Natural language descriptions are inherently underspecified compared to formal specifications. When a docstring says "sort the list," it does not specify whether sorting should be in-place or return a new list. When it says "find the top-k items," it does not specify behavior when there are ties at the k-th position. When it says "validate the input," it leaves open what constitutes valid input and what should happen when validation fails.

Large language models handle this ambiguity in ways that reflect their training distribution. If the vast majority of Python code uses sorted() to return a new sorted list rather than list.sort() for in-place sorting, the model will tend to generate the former even when either would technically be acceptable. This statistical bias toward common patterns is generally helpful but creates subtle mismatches when the caller's intent diverges from the convention.

One practical response is prompt augmentation: adding explicit edge case descriptions to the docstring before passing it to the model. If you write "returns a new sorted list (does not modify the input)," the model is much more likely to generate the intended behavior. Research on instruction tuning has shown that models respond well to this kind of precision, provided the model has been trained on similar instruction styles.

A deeper issue is that docstring quality in real codebases varies enormously. Academic benchmarks like HumanEval use carefully written, relatively complete docstrings. Production code is messier. Studies examining code generation on real-world repository functions find that performance drops substantially when the docstrings are typical of what engineers write under time pressure: brief, sometimes incorrect, and often missing entire behavioral dimensions. This is the key reason why RAG-augmented code generation, where relevant context from the codebase is inserted into the prompt, has become a standard production technique. If the docstring is ambiguous but the surrounding code provides disambiguating context, the model can use that context.

Specification Completeness: A Closer Look

Consider the following two docstrings for the same function.

Version A (minimal):

Remove duplicates from a list.

Version B (complete):

Remove duplicate elements from a list, returning a new list with only the first occurrence of each element preserved. The relative order of elements in the original list must be maintained. If the input list is empty, return an empty list.

Both describe the same function, but Version A leaves open whether to sort the output, whether to preserve order, whether to modify in place, and how to handle empty input. A model generating from Version A might produce a sorted(set(items)) one-liner that is fast and concise but destroys ordering, while the caller expected order preservation. This is not a model failure in any deep sense; it is a specification failure that creates a mismatch.

The practical lesson is that engineers who rely on code generation tools consistently report that the quality of their prompts determines the quality of the output. The model is a highly capable engineer who needs complete requirements, not a mind reader.

Fill-in-the-Middle vs. Prefix-Only Generation

As we covered in Code LLM Training, fill-in-the-middle (FIM) training teaches the model to generate code that connects a given prefix to a given suffix. In the docstring-to-code setting, FIM enables a different kind of generation: the model can see both the function signature and any code that follows the function in the file, using context from both directions to constrain its output.

This bidirectional context is powerful. If the function being generated is later called with specific arguments, the model can infer usage patterns. If there is a test file that imports the function and checks its output, FIM-trained models can use those tests as additional specification signal. Prefix-only models see only the code before the cursor position and must extrapolate from partial context.

In practice, the difference between FIM and prefix-only generation is most pronounced when the function signature or docstring is ambiguous and the surrounding code provides disambiguating context. For clearly specified functions, both approaches tend to produce similar results. The advantage of FIM is especially visible in larger codebases where the calling code makes requirements explicit even when the docstring does not.

The mechanism by which FIM provides bidirectional signal is worth understanding. During FIM training, the model learns to predict the middle of a sequence given both its prefix and suffix. At inference time, when the model receives a prefix (the function signature and docstring) and a suffix (code following the function), it is in precisely the setting it trained on. The model has learned to generate code that is consistent with both boundaries simultaneously, which is fundamentally more constrained than extrapolating from the prefix alone.