Following on from #760 (using condense_json() to de-duplicate prompt_json / response_json), I hit a few things in Response.log_to_db (llm/models.py) that look unintended. All three live in the same block:
replacements = {}
# Include replacements from previous responses
for previous_response in conversation.responses[:-1]:
for fragment in (previous_response.prompt.fragments or []) + (
previous_response.prompt.system_fragments or []
):
fragment_id = ensure_fragment(db, fragment)
replacements[f"f:{fragment_id}"] = fragment
replacements[f"r:{previous_response.id}"] = (
previous_response.text_or_raise()
)
for i, fragment in enumerate(self.prompt.fragments):
fragment_id = ensure_fragment(db, fragment)
replacements[f"f{fragment_id}"] = fragment
...
for i, fragment in enumerate(self.prompt.system_fragments):
fragment_id = ensure_fragment(db, fragment)
replacements[f"f{fragment_id}"] = fragment
...
response_text = self.text_or_raise()
replacements[f"r:{response_id}"] = response_text
1. A previous turn's response text is only de-duplicated if that turn used fragments
replacements[f"r:{previous_response.id}"] = ... is set inside the for fragment in ... loop. If a previous response had no fragments, that inner loop never runs, so its text is never registered as a replacement, and it is stored verbatim in the next turn's prompt_json instead of being condensed to a {"$": "r:<id>"} marker.
This is the common case (most conversations don't use -f), so #760's de-duplication is skipped for exactly the everyday multi-turn path.
Repro (2-turn conversation, turn 1 has no fragments, turn 2 does):
turn-2 stored prompt_json:
{"messages": [
{"role": "user", "content": "First question"},
{"role": "assistant", "content": "ASSISTANT_TURN_ONE_UNIQUE_LONG_ANSWER_THAT_SHOULD_BE_DEDUPED_LATER"},
{"role": "user", "content": {"$r": [{"$": "f1"}, "\nSecond question"]}}
]}
R1 assistant text de-duplicated (condensed to marker)? False
Fragment text condensed? True
Turn 1's assistant text is stored in full; only turn 2's fragment is condensed. Moving the r:{previous_response.id} registration out of the for fragment loop (so it runs once per previous response) restores the dedup.
2. Inconsistent fragment marker keys
Previous-turn fragments are keyed f:{fragment_id} (with colon), while current-turn prompt and system fragments are keyed f{fragment_id} (no colon). The same fragment therefore gets two different marker ids depending on which turn is being logged. It doesn't corrupt anything today, but it's an easy inconsistency to trip over if/when the markers are ever read back.
3. Question: are the raw markers in llm logs --json intended?
llm logs --json returns the condensed forms directly:
prompt_json -> {"messages": [{"role": "user", "content": {"$r": [{"$": "f1"}, "\n\nSummarize the note."]}}]}
response_json -> {"choices": [{"message": {"content": {"$r": ["Echoed: ", {"$": "f1"}]}}}]}
uncondense_json (the inverse in the condense-json library) isn't imported anywhere, so consumers of the --json output now get {"$": ...} / {"$r": [...]} marker objects where they used to get the literal content. I saw the prototype in #760 already showed markers, so this may well be intended (the readable text still lives in the prompt / response columns and the fragments table). Wanted to flag it as a possible compatibility consideration for anyone parsing prompt_json / response_json programmatically, and check whether reads are meant to uncondense_json back to full JSON.
Happy to send a PR for #1 (and #2) if the direction looks right — didn't want to cold-PR a change to the on-disk format.
Reproduced against main (llm 0.32a3 / release 0.31.1), Python 3.14.
Following on from #760 (using
condense_json()to de-duplicateprompt_json/response_json), I hit a few things inResponse.log_to_db(llm/models.py) that look unintended. All three live in the same block:1. A previous turn's response text is only de-duplicated if that turn used fragments
replacements[f"r:{previous_response.id}"] = ...is set inside thefor fragment in ...loop. If a previous response had no fragments, that inner loop never runs, so its text is never registered as a replacement, and it is stored verbatim in the next turn'sprompt_jsoninstead of being condensed to a{"$": "r:<id>"}marker.This is the common case (most conversations don't use
-f), so #760's de-duplication is skipped for exactly the everyday multi-turn path.Repro (2-turn conversation, turn 1 has no fragments, turn 2 does):
Turn 1's assistant text is stored in full; only turn 2's fragment is condensed. Moving the
r:{previous_response.id}registration out of thefor fragmentloop (so it runs once per previous response) restores the dedup.2. Inconsistent fragment marker keys
Previous-turn fragments are keyed
f:{fragment_id}(with colon), while current-turn prompt and system fragments are keyedf{fragment_id}(no colon). The same fragment therefore gets two different marker ids depending on which turn is being logged. It doesn't corrupt anything today, but it's an easy inconsistency to trip over if/when the markers are ever read back.3. Question: are the raw markers in
llm logs --jsonintended?llm logs --jsonreturns the condensed forms directly:uncondense_json(the inverse in thecondense-jsonlibrary) isn't imported anywhere, so consumers of the--jsonoutput now get{"$": ...}/{"$r": [...]}marker objects where they used to get the literal content. I saw the prototype in #760 already showed markers, so this may well be intended (the readable text still lives in theprompt/responsecolumns and thefragmentstable). Wanted to flag it as a possible compatibility consideration for anyone parsingprompt_json/response_jsonprogrammatically, and check whether reads are meant touncondense_jsonback to full JSON.Happy to send a PR for #1 (and #2) if the direction looks right — didn't want to cold-PR a change to the on-disk format.
Reproduced against
main(llm0.32a3 / release 0.31.1), Python 3.14.