Add Round abstraction over Turn history#1056
Open
gadenbuie wants to merge 7 commits into
Open
Conversation
Add a `Round` S7 class grouping a real user turn with the assistant and tool-result turns that follow it, derived from the flat turn history on read. Adds `Chat$get_rounds()` and `Chat$last_round()`; storage stays flat and there is no `$set_rounds()`. Also consolidates `is_tool_result_turn()` into R/turns.R (previously duplicated in R/otel.R). Fixes #507
…omplete Round@input becomes a list of turns (user turn last) so leading and interleaved system turns fold into the round they precede rather than sitting as bare elements. Round@complete now excludes interrupted AssistantPartialTurns. Chat$get_rounds(include_system_prompt = FALSE) drops all system turns; TRUE folds them in.
print(<Round>) renders each turn via print.Turn so roles are visible. Add contents_text/markdown/html for Round: text uses XML role tags, markdown and html use role headings.
Extract the per-turn role-heading markdown rendering (and role title-casing) into turns_markdown()/turn_role_title() in turns.R, used by both contents_markdown(<Chat>) and contents_markdown(<Round>).
…ntract last_round() now delegates to get_rounds(include_system_prompt = TRUE) so it is consistent with get_rounds() and acts as a complete historical record (a system-only chat returns a system-only Round). Document that Round@input may hold more than just the triggering user turn, since Chat$set_turns() accepts arbitrary turn lists.
complete is a computed/read-only property, not a Round() constructor argument, so documenting it via @param produced an \arguments entry with no matching \usage entry and an R CMD check WARNING.
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.
Closes #507
Summary
Chatstores conversation history as a flat list ofTurns(
system, user, assistant, user(tool-result), assistant, ...). When toolcalling triggers a loop, several
user/assistantturn pairs accumulate forwhat is conceptually a single "round" of interaction. There was previously no
API to get back "the last real exchange" as a unit.
This PR introduces a
RoundS7 class (R/rounds.R) that groups the flatturn list on read, without changing what's stored:
Roundhas@input(the leading input-side turns: any system turnsfollowed by the real user turn) and
@response(a raw list of theassistant/tool-result turns that followed), plus a computed
@completeproperty that reuses the existing
turn_has_tool_request()predicate fromthe tool loop to determine whether the round finished cleanly.
get_rounds(turns)groups a flat turn list intoRounds in a singlelinear pass. System turns fold into the
@inputof the round theyprecede rather than appearing as bare elements.
Chat$get_rounds(include_system_prompt = FALSE)andChat$last_round()are added as the public API, mirroring
get_turns()/last_turn().private$.turnsremains the single source of truth; there isintentionally no
$set_rounds().is_tool_result_turn()moved fromR/otel.RtoR/turns.Rso it can beshared between
rounds.Randotel.R.format.Round/print.Roundfollow the existingTurnprint pattern, anda shared
turns_markdown()helper is used by bothChatandRound.A few things worth flagging for review:
@responseis a raw list ofTurns, not an aggregated result object(final text + usage + tool calls). Kept minimal for now since
Chatalready has
get_tokens()/get_cost()for cross-turn aggregation; anaggregate can be layered on top later without breaking this shape.
@completetreats a round ending in a partial (interrupted/cancelled)assistant turn as
FALSE, notTRUE— an in-progress stream isn't afinished round.
@inputcan hold more than one turn: system turns fold into the@inputof the round they precede instead of appearing as bare elementsin
get_rounds()'s output, so@inputis "the input-side turns of theround" rather than always a single
UserTurn. In practice it's almostalways length-1.
Chat$last_round()always includes system turns (equivalent toget_rounds(include_system_prompt = TRUE)), even thoughChat$get_rounds()defaults to excluding them, sincelast_round()ismeant to be a complete historical record.
Full design rationale and alternatives considered (aggregate response object
vs. raw turn list, naming survey against other SDKs) are in the issue
discussion linked above.
Verification