Skip to content

fix: parse Google docstrings missing the blank line before a section#3832

Merged
seratch merged 2 commits into
openai:mainfrom
sohumt123:fix/google-docstring-missing-blank-line-before-args
Jul 14, 2026
Merged

fix: parse Google docstrings missing the blank line before a section#3832
seratch merged 2 commits into
openai:mainfrom
sohumt123:fix/google-docstring-missing-blank-line-before-args

Conversation

@sohumt123

Copy link
Copy Markdown
Contributor

Summary

A Google-style docstring whose summary line is immediately followed by an Args: section with no blank line in between silently loses every parameter description, and the raw Args: block leaks into the function/tool description.

@function_tool
def get_weather(city: str, units: str) -> str:
    """Get the weather for a city.
    Args:
        city: The city to get weather for.
        units: Temperature units to use.
    """
    ...

Before this change:

  • params_json_schema["properties"]["city"] has no description
  • tool.description becomes "Get the weather for a city.\nArgs:\n city: ...\n units: ..." (the raw block)

So the model never sees any parameter description and gets a malformed tool description. Adding the (PEP 257 / Google-style) blank line fixes it, but the failure is completely silent, which makes it easy to ship a degraded tool without noticing.

Root cause. generate_func_documentation in src/agents/function_schema.py passes inspect.getdoc(func) to griffe's Google parser. griffe skips a section header when there is no blank line above it and the next line is indented, logging Possible section skipped, reasons: Missing blank line above section; the header and its body are appended to the summary text instead of becoming a parameters section. The numpy and sphinx parsers already tolerate the missing blank line, so this is a Google-only gap.

Fix. When the resolved style is google, normalize the docstring before building the griffe Docstring: insert a single blank line before a recognized section header (Args:/Returns:/Raises:/… as an exact Header: line) that directly follows non-indented top-level text (the summary) and is itself followed by an indented block. The anchor mirrors griffe's own recognition — column-0 header, case-insensitive section keys, indented line below — so inline mentions (see Args: below) and headers already preceded by a blank line or an indented body are left untouched. The helper returns the original string object unchanged when no insertion is needed, so well-formed docstrings stay byte-identical.

This is not the same as the closed PR #3795. That PR addressed a different variant (a docstring with no summary that opens directly with Args:), and its approach (prepending "\n") was correctly closed by @seratch as a no-op because griffe applies inspect.cleandoc() internally, which strips the leading newline. This PR targets the distinct Missing blank line above section skip path (a header mid-docstring, after a non-empty summary) and demonstrates a real base failure. Out of scope and deliberately left unchanged: admonitions (Note:/Warning:), headers with trailing title text (Examples: foo), and griffe's separate Extraneous blank line below section title skip reason.

Test plan

Added five tests to tests/test_function_schema.py covering function_schema, generate_func_documentation, and the end-to-end @function_tool path, plus a multi-section case and a byte-identity control for the already-well-formed docstring.

Running the new tests on the base (before the src/agents/function_schema.py change) reproduces the bug (griffe logs DEBUG:griffe:<module>: Possible section skipped, reasons: Missing blank line above section):

$ uv run pytest tests/test_function_schema.py -k "google_docstring" -q
>       assert properties["city"]["description"] == "The city to get weather for."
E       KeyError: 'description'
...
4 failed, 1 passed, 26 deselected in 0.13s

(The one passing test is the byte-identity control on the well-formed docstring, which correctly parses on base too.)

After the fix:

$ uv run pytest tests/test_function_schema.py -k "google_docstring" -q
5 passed, 26 deselected in 0.05s

$ uv run pytest tests/test_function_schema.py tests/test_doc_parsing.py \
      tests/test_function_tool.py tests/test_function_tool_decorator.py -q
99 passed in 0.84s

make format-check and make lint are clean; mypy and pyright pass on the changed source file. .agents/skills/code-change-verification/scripts/run.sh runs make format (clean), make lint (pass), and the full make tests suite (pass). make typecheck reports 6 errors, but all of them are pre-existing on the base branch in files this PR does not touch (src/agents/sandbox/session/archive_ops.py and two test modules that depend on an optional any_llm stub / a newer-Python eager_task_factory); I verified they are unchanged from base and unrelated to this change.

Validated against the locked griffelib==2.0.1 (griffelib>=2, <3).

Disclosure: I used an AI coding assistant while investigating and preparing this change; I reviewed the diff, ran the tests, and stand behind the result.

Issue number

No linked issue — self-discovered while reading function_schema.py (same discovery context as #3795, but a different, actually-failing variant).

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass (except pre-existing, unrelated make typecheck errors documented above)
  • If using Codex, I've run /review before submitting this PR

generate_func_documentation feeds inspect.getdoc(func) to griffe's Google
parser. When a summary line is immediately followed by an Args:/Returns:/...
section with no blank line in between, griffe skips the section ("Missing
blank line above section"), so every parameter description is dropped and the
raw Args: block leaks into the function description. This is a Google-only gap;
the numpy and sphinx parsers already tolerate the missing blank line.

Normalize the docstring before parsing: when the resolved style is google,
insert a single blank line before a recognized section header that directly
follows non-indented text and is itself followed by an indented block. The
anchor mirrors griffe's own recognition (column-0 header, case-insensitive
section keys, indented line below), and the helper returns the original string
unchanged when no insertion is needed, so well-formed docstrings stay
byte-identical.

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underlying issue is valid: on the released path, a summary immediately followed by Args: silently drops parameter descriptions and leaves the raw block in the tool description.

Please narrow the normalization to the parameter-section aliases consumed by generate_func_documentation (Args, Arguments, Params, and Parameters), and update the generalized comments and tests accordingly. The current header list is only a partial duplicate of Griffe's section registry, while the multi-section test does not actually prove that the later Returns: section was parsed because later text sections are ignored.

Please retain the end-to-end base-failing regression and the well-formed-docstring control. After that focused change and green CI, this should be ready for another review.

Per review feedback, I narrowed the blank-line normalization to the four
parameter-section aliases that generate_func_documentation actually
consumes (Args, Arguments, Params, Parameters) instead of mirroring the
full griffe section registry, and rescoped the comments and helper
docstring accordingly. I also removed the multi-section test: its
Returns: assertion passed vacuously because the helper never fires after
an indented line and later text sections are ignored anyway. The
base-failing regression test, the equivalence test, the well-formed
control test, and the end-to-end function_tool test are retained
unchanged.
@sohumt123

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @seratch , all three addressed in 9654627:

  • Narrowed _GOOGLE_SECTION_HEADER_RE to only the parameter-section aliases generate_func_documentation consumes, Args, Arguments, Params, Parameters , dropping the broader Griffe-registry headers.

  • Rescoped the comment and the helper's docstring to say it normalizes just the parameter section this function parses.

  • Removed the misleading multi-section test (its Returns: assertion passed vacuously, since later text sections aren't read here) and kept the end-to-end base-failing regression plus the well-formed control.

Locally, pytest tests/test_function_schema.py tests/test_doc_parsing.py tests/test_function_tool.py passes (83) and ruff is clean. CI looks like it's waiting on workflow approval for this first PR, happy to address anything once it runs. Ready for another look.

@seratch seratch added this to the 0.18.x milestone Jul 14, 2026
@seratch seratch enabled auto-merge (squash) July 14, 2026 20:26
@seratch seratch merged commit df20e83 into openai:main Jul 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants