feat(py): citation-request tracker and tool-result tagging - #305
Merged
jat255 merged 2 commits intoSep 7, 2026
Conversation
jat255
changed the base branch from
jat255/m5-fec8-shared-prompt-source
to
jat255/fec8-shared-prompt-source
September 7, 2026 04:30
|
Preview deployed to Connect ( Deployed from commit 0ca0a94. |
|
Preview deployed to Connect ( Deployed from commit 0ca0a94. |
jat255
marked this pull request as ready for review
September 7, 2026 17:12
jat255
force-pushed
the
jat255/gege-citation-request
branch
from
September 7, 2026 19:42
2bb3518 to
c2fce17
Compare
simonpcouch
approved these changes
Sep 7, 2026
The citation contract is stated in the system prompt; a short reminder rides on the first tool result of a user turn whose output has to be cited. `CitationRequest` holds that once-per-turn flag and the reminder text, which comes from `prompts/citation-request.md` rather than a second copy of the words. `tool_result()` stamps the provenance tag on `ContentToolResult.extra` at construction, which is where the turn classifier reads it back. Where the reminder lands is behaviour both packages have to agree on, so `tests/shared/citation-request.json` pins it: which value shapes it joins and how, that it goes out once per turn, and which turns start a new request. The R suite reads the fixture in place of the two hand-written tests it had. The wiring that calls this is blocked with the rest of M5, so nothing constructs a tracker yet.
chatlas sends the error rather than the value, so a reminder added to an errored result's value would never reach the model, and the once-per-turn flag would be spent on nothing. Also add citation_reminder_text to __all__ alongside its siblings and give add_request a docstring.
jat255
force-pushed
the
jat255/gege-citation-request
branch
from
September 7, 2026 19:59
c2fce17 to
0ca0a94
Compare
|
Cleaned up 4 preview bundle(s) on https://dogfood.team.pct.posit.it: 368504, 368590, 368611, 368619 |
|
Cleaned up 4 preview bundle(s) on https://connect.staging.pct.posit.it: 2665, 2667, 2670, 2678 |
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.
The Python package can now ask the model for a citation, once per user turn, and tag a tool result with the provenance of the output it holds.
Summary
CitationRequestin_citations.pyholds the once-per-turn flag and the reminder text, which it reads fromprompts/citation-request.mdrather than restating the words.tool_result()stamps the tag onContentToolResult.extraat construction, which is where the turn classifier reads it back, andturn_has_user_message()decides which turns start a new request. An errored tool result passes through without spending the request: the model is sent the error rather than the value, so a reminder added to the value would never arrive.Where the reminder lands is behaviour both packages have to agree on, so
tests/shared/citation-request.jsonpins it: which value shapes it joins and how, that it goes out once per turn, and which turns reset the tracker. The wording stays in the prompt file, so the fixture uses a stand-in for it.Review notes
Nothing constructs a tracker yet. The
Commonswiring is blocked behind the layer classes; this is the part of that milestone that does not need them.The reset rule ships with the tracker rather than with the wiring, since it is the tracker's rule. Excluded, as the issue says: the restore-detection flag on
set_turns(), which is wiring.A value that is neither a string nor a list of parts becomes a two-part list, with the value kept as a single element. That matches R's
c(value, list(...))only for a scalar value: R'sc()flattens a list-backed value (a vector or data frame) into its elements, where Python keeps it whole. No commons tool on either side produces that shape, so it is tested Python-side only rather than pinned cross-language.Nothing is exported from
commons/__init__.py: in R this is private agent state and no user constructs one.Testing
Python 978 pass, ruff and pyrefly clean. R 7049 pass, 0 fail. Both suites read the new fixture, and I checked that it can fail rather than trivially pass: perturbing the expected values in the synced R copy fails both sections.
R-side summary
No R behaviour changes: this replaces tests, not code.
test-citations.Rloses the two hand-written tests foradd_citation_request()(one reminder per user turn, and appending aContentTextto a content list) and gains two that drive the same function from the shared fixture, covering the same sequence and the same two value shapes. It also gains coverage ofturn_has_user_message(), which had none, for the four turn shapes that decide whether a new request goes out.Two small helpers in the test file convert between the fixture's
{kind, text}or{kind, parts}shape and R content objects, so the fixture stays free of either language's types. The copy undertests/testthat/fixtures/shared/is generated byscripts/sync-shared.sh.