Repository navigation
Add the verbatim and select features of the Python protocol, so code is not - #790
Merged
Merged
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
copybara-service
Bot
force-pushed
the
test_1008623926
branch
2 times, most recently
from
October 30, 2026 18:33
559bb84 to
e737205
Compare
…ode is not
escape-decoded.
PROBLEM
With protocol='python' a code field (shell script, Python program, file body)
is an inner Python string literal, and `structure_from_python` parses the
response with `exec`. Every `\n`, `\"` or `\r` the LM did not double-escape is
decoded before the code runs: `print("\n=== x ===")` reaches the shell with a
real line break and fails with `SyntaxError: unterminated string literal`. The
outer literal still parses, so autofix never sees the damage.
FIX
Two switches, both required: a `str` field opts in with
`dict(verbatim=True)`, and a query opts in with `protocol='python+verbatim'`.
The agents also ask for `select`: `protocol='python+verbatim+select'`, recorded
as `python:2.0+verbatim:1.0+select:1.0`. The marked fields then travel in
out-of-band blocks after the ```python fence. A block is copied byte for byte
and never unescaped:
output = NextStep(next_step=BashCode(bash_code=BASH_CODE))
<<<LF:BLOCK BASH_CODE 7f3a>>>
python3 -c 'print("a\nb")'
<<<LF:END BASH_CODE 7f3a>>>
* `verbatim:1.0` supports the base `python:2.0` only, whose example its prompt
replaces. `python:1.0+verbatim` and `json+verbatim` raise `ValueError`. It
does not imply `select:1.0`. Both are the default versions of their
features, so `python+verbatim+select` keeps its meaning when a later version
of either is added.
* For a schema with a verbatim field, the prompt replaces the `1 + 1` example
of `python:2.0` with block instructions and a worked example, and adds a hint
comment above each verbatim field. A schema without one gets exactly the
`python:2.0` prompt and parse (plus `select`, if the name asks for it).
* `select:1.0` adds no prompt text: it parses the ```python fence that builds
the output object when the LM also echoes the class definitions. A ``` line
inside a string literal of the code does not end a fence, and if the fence
that builds the object does not parse, the whole response is parsed as
without `select`.
* Block names are bound as globals of the existing `exec`, so nothing
downstream changes. Inline string literals still parse. A block never
shadows a class of the schema, a name bound for every response (`UNKNOWN`,
...) or a builtin; a field named like one of them, e.g. `db` when the
schema has a class `DB` anywhere, gets the block name `VERBATIM_<NAME>` in
the hint and in few-shot examples.
* The checks after `exec` also cover outputs that are plain lists or dicts.
* When verbatim applies, `lf.parse` adds the block instructions to its
preamble. A caller's `preamble=` still replaces the default preamble of
`lf.query` or `lf.parse`, and gets the block instructions appended.
* When the features of a query's protocol that apply to its schema are not
empty, its autofix correction query uses the protocol with just those
features: under `python+verbatim+select`,
`python:2.0+verbatim:1.0+select:1.0` for a schema with a verbatim field,
where `CorrectedCode.corrected_code` is a verbatim field, so a correction
travels as a block as well, and `python:2.0+select:1.0` for a schema without
one. A correction reply that
misuses the blocks counts as a failed attempt. Correction queries of other
protocols are unchanged.
* Only `VerbatimBlockError` is exported from `lf.structured`.
* `PythonPromptingProtocol` runs the features by phase: cut the blocks, pick
the fence, check the block references, `exec`, check the value. Within a
phase, features run by rank: `verbatim` (1) cuts the blocks before `select`
(2) picks the fence. The feature hooks are internal.
PARSE RULES of `verbatim:1.0` (frozen; a change needs a new version)
* A block's text is the bytes strictly between the newline that ends the
opening fence and the newline that starts the closing fence. If the opening
fence ends with a CRLF, that newline is a CRLF too.
* Fence variants LMs emit are accepted: the keyword fused into the name, a
closing fence without a name, indented fences, a quoted block name, and fence
lines that end with `\r`. Any line that starts with `<<<LF:END` or `<<LF:END`
is a closing fence, whatever follows its `>` (`<<<LF:END NAME token>>`,
`<<LF:END NAME token>>>`, `<<<LF:END NAME token>>>.`); the text after `END`,
up to a `>`, may name the block, repeat the token, both, or neither. One that
names another block, or no block and another token (`<<<LF:END OTHER t1>>>`,
`<<<LF:END t2>>>`, `<<<LF:ENDPOINT x>>>`), does not end the block; one that
names the block with another token (`<<<LF:END BASH_CODE t2>>>`) counts as a
closing fence without the token.
* A line inside a string literal of the Python code is never a fence, so an
inline literal that quotes block syntax parses as under `python:2.0`. The
code is in the ```, ```py and ```python fences, or, in a response without a
fence, everywhere outside the blocks. A literal that is never closed does
not count.
* The closing-fence search stops at the next opener that carries the block's
token, whatever its name (without a token: the next opener without one),
since every block of a response carries the same token. An opener with
another token is payload, so a block can quote fences of another token;
past one of its own name, only a closing fence that repeats the token ends
the block.
* Before that point, the block ends at the first closing fence with its token
(that names it or nothing) or its closing fence (same name and token) fused
to the end of a line, keeping the text before the fence; only if there is
none, at the first closing fence without the token. Closing fences without
the token before that one are payload. The parser never guesses: it is
unclear where the block ends if another closing fence, with or without the
token, follows that one with text between them (a payload that quotes its
own closing fence looks like prose that repeats it; between two closing
fences with the token, one without it is text too), or follows it at all
when that one is fused to a line of text, or if that one is fused to a line
of text after a closing fence without the token that names the block, or if
the block's only closing fences name another block or token; a later closing
fence that names another block or token does not count. A used unclear block
raises `VerbatimBlockError`; an unused one is ignored. Closing fences with
only blank lines between them are a stutter: the first one ends the block.
* An indented opening fence: its indentation is removed only if every
non-blank body line starts with it; otherwise the body is kept as written.
* A block with no closing fence runs to the next opening fence. A used block
that the response ends inside raises `VerbatimBlockError`.
* A block whose own closing fence comes only after the next opener with its
token quotes that block: its text would end at that opener, so a used one
raises `VerbatimBlockError`; an unused one is ignored.
* A name defined twice with different text, a referenced block that is not
defined, a reference to a block named like a schema class, constant or
builtin, and misnamed blocks that match neither 1:1 nor by ordinal or name
stem raise `VerbatimBlockError` before `exec`, so autofix is never asked to
invent a payload. A lone unused block is not renamed to a lone undefined
reference if its name agrees with a block that was already used, or only
shares that block's name stem: the blocks are numbered off by one
(references `CODE_1`, `CODE_2`; blocks `CODE_2`, `CODE_3`).
* After an autofix rewrite, every verbatim value must be the text of a block,
a string literal of the response, a `+` chain of those that the response
itself writes (evaluated with the blocks' texts), or the field's default.
Other expressions, f-strings included, are not evaluated.
* A string literal that the executed code passes for a verbatim field and that
names a block of that field (named after the field, or claimed by no other
field) is replaced by the block's text; text that came from a block is never
expanded. A quoted value that equals a block name derived from its field
(`BASH_CODE` or `BASH_CODE_2` for `bash_code`) raises `VerbatimBlockError` if
no block has that name; other literals are kept.
* An inline string literal in a verbatim field is accepted, unless the
response also defines blocks named after the field (`BASH_CODE`,
`BASH_CODE_2` for `bash_code`) that the object does not use and none of them
holds the literal's text: that raises `VerbatimBlockError`. A literal that
repeats such a block is accepted.
Opt-in: no default changes; existing protocols and callers behave as before.
PiperOrigin-RevId: 1010674607
copybara-service
Bot
force-pushed
the
test_1008623926
branch
from
October 30, 2026 18:41
e737205 to
dbcb928
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add the
verbatimandselectfeatures of the Python protocol, so code is notescape-decoded.
PROBLEM
With protocol='python' a code field (shell script, Python program, file body)
is an inner Python string literal, and
structure_from_pythonparses theresponse with
exec. Every\n,\"or\rthe LM did not double-escape isdecoded before the code runs:
print("\n=== x ===")reaches the shell with areal line break and fails with
SyntaxError: unterminated string literal. Theouter literal still parses, so autofix never sees the damage.
FIX
Two switches, both required: a
strfield opts in withdict(verbatim=True), and a query opts in withprotocol='python+verbatim'.The agents also ask for
select:protocol='python+verbatim+select', recordedas
python:2.0+verbatim:1.0+select:1.0. The marked fields then travel inout-of-band blocks after the ```python fence. A block is copied byte for byte
and never unescaped:
output = NextStep(next_step=BashCode(bash_code=BASH_CODE))
<<<LF:BLOCK BASH_CODE 7f3a>>>
python3 -c 'print("a\nb")'
<<<LF:END BASH_CODE 7f3a>>>
verbatim:1.0supports the basepython:2.0only, whose example its promptreplaces.
python:1.0+verbatimandjson+verbatimraiseValueError. Itdoes not imply
select:1.0. Both are the default versions of theirfeatures, so
python+verbatim+selectkeeps its meaning when a later versionof either is added.
1 + 1exampleof
python:2.0with block instructions and a worked example, and adds a hintcomment above each verbatim field. A schema without one gets exactly the
python:2.0prompt and parse (plusselect, if the name asks for it).select:1.0adds no prompt text: it parses thepython fence that builds the output object when the LM also echoes the class definitions. Alineinside a string literal of the code does not end a fence, and if the fence
that builds the object does not parse, the whole response is parsed as
without
select.exec, so nothingdownstream changes. Inline string literals still parse. A block never
shadows a class of the schema, a name bound for every response (
UNKNOWN,...) or a builtin; a field named like one of them, e.g.
dbwhen theschema has a class
DBanywhere, gets the block nameVERBATIM_<NAME>inthe hint and in few-shot examples.
execalso cover outputs that are plain lists or dicts.lf.parseadds the block instructions to itspreamble. A caller's
preamble=still replaces the default preamble oflf.queryorlf.parse, and gets the block instructions appended.empty, its autofix correction query uses the protocol with just those
features: under
python+verbatim+select,python:2.0+verbatim:1.0+select:1.0for a schema with a verbatim field,where
CorrectedCode.corrected_codeis a verbatim field, so a correctiontravels as a block as well, and
python:2.0+select:1.0for a schema withoutone. A correction reply that
misuses the blocks counts as a failed attempt. Correction queries of other
protocols are unchanged.
VerbatimBlockErroris exported fromlf.structured.PythonPromptingProtocolruns the features by phase: cut the blocks, pickthe fence, check the block references,
exec, check the value. Within aphase, features run by rank:
verbatim(1) cuts the blocks beforeselect(2) picks the fence. The feature hooks are internal.
PARSE RULES of
verbatim:1.0(frozen; a change needs a new version)opening fence and the newline that starts the closing fence. If the opening
fence ends with a CRLF, that newline is a CRLF too.
closing fence without a name, indented fences, a quoted block name, and fence
lines that end with
\r. Any line that starts with<<<LF:ENDor<<LF:ENDis a closing fence, whatever follows its
>(<<<LF:END NAME token>>,<<LF:END NAME token>>>,<<<LF:END NAME token>>>.); the text afterEND,up to a
>, may name the block, repeat the token, both, or neither. One thatnames another block, or no block and another token (
<<<LF:END OTHER t1>>>,<<<LF:END t2>>>,<<<LF:ENDPOINT x>>>), does not end the block; one thatnames the block with another token (
<<<LF:END BASH_CODE t2>>>) counts as aclosing fence without the token.
inline literal that quotes block syntax parses as under
python:2.0. Thecode is in the
,py and ```python fences, or, in a response without afence, everywhere outside the blocks. A literal that is never closed does
not count.
token, whatever its name (without a token: the next opener without one),
since every block of a response carries the same token. An opener with
another token is payload, so a block can quote fences of another token;
past one of its own name, only a closing fence that repeats the token ends
the block.
(that names it or nothing) or its closing fence (same name and token) fused
to the end of a line, keeping the text before the fence; only if there is
none, at the first closing fence without the token. Closing fences without
the token before that one are payload. The parser never guesses: it is
unclear where the block ends if another closing fence, with or without the
token, follows that one with text between them (a payload that quotes its
own closing fence looks like prose that repeats it; between two closing
fences with the token, one without it is text too), or follows it at all
when that one is fused to a line of text, or if that one is fused to a line
of text after a closing fence without the token that names the block, or if
the block's only closing fences name another block or token; a later closing
fence that names another block or token does not count. A used unclear block
raises
VerbatimBlockError; an unused one is ignored. Closing fences withonly blank lines between them are a stutter: the first one ends the block.
non-blank body line starts with it; otherwise the body is kept as written.
that the response ends inside raises
VerbatimBlockError.token quotes that block: its text would end at that opener, so a used one
raises
VerbatimBlockError; an unused one is ignored.defined, a reference to a block named like a schema class, constant or
builtin, and misnamed blocks that match neither 1:1 nor by ordinal or name
stem raise
VerbatimBlockErrorbeforeexec, so autofix is never asked toinvent a payload. A lone unused block is not renamed to a lone undefined
reference if its name agrees with a block that was already used, or only
shares that block's name stem: the blocks are numbered off by one
(references
CODE_1,CODE_2; blocksCODE_2,CODE_3).a string literal of the response, a
+chain of those that the responseitself writes (evaluated with the blocks' texts), or the field's default.
Other expressions, f-strings included, are not evaluated.
names a block of that field (named after the field, or claimed by no other
field) is replaced by the block's text; text that came from a block is never
expanded. A quoted value that equals a block name derived from its field
(
BASH_CODEorBASH_CODE_2forbash_code) raisesVerbatimBlockErrorifno block has that name; other literals are kept.
response also defines blocks named after the field (
BASH_CODE,BASH_CODE_2forbash_code) that the object does not use and none of themholds the literal's text: that raises
VerbatimBlockError. A literal thatrepeats such a block is accepted.
Opt-in: no default changes; existing protocols and callers behave as before.