Skip to content

feat: Record and replay Turn objects and its dependencies #503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: main
Choose a base branch
from

Conversation

schloerke
Copy link

@schloerke schloerke commented May 15, 2025

To be used for bookmarking {ellmer} clients within {shinychat} (posit-dev/shinychat#28)

Fixes #502

Provides three methods:

  • contents_record(content, ..., chat) - Generic that will convert S7 objects into jsonifiable objects
  • contents_replay(obj, ..., chat) - Normal R method that takes the jsonifiable objects and recreate an S7 object
  • contents_replay_class(cls, obj, ..., chat) - Generic that will dispatch on the class object saved from contents_record().
    • While cls will not typically be utilized in custom implementations, it is useful for restoring in the base situation.
    • cls is needed for dispatch. Otherwise all obj values are named lists.

The chat object is required to be passed down in all methods to provide context without using global state.

The current test goes through the shiny bookmarking experience:

  • record the object to something serializable
  • Transform the object into something JSON safe
  • Serialize the object to JSON via shiny; "bookmark"
  • Unserialize the object from JSON via shiny; "restore"
  • Reverse the "JSON safe" transformation
  • replay the unserialized object to the original object
## shinychat bookmark
obj <- ellmer::contents_record(x, chat = chat)

# Work around Shiny's inconsistent JSON serialization
marshalled = as.character(jsonlite::serializeJSON(obj))

## /bookmark

# Bookmark
serialized <- shiny:::toJSON(marshalled)
# Restore
unserialized <- shiny:::safeFromJSON(serialized)

## shinychat restore
unmarshalled <- jsonlite::unserializeJSON(unserialized)

replayed <- ellmer::contents_replay(unmarshalled, chat = chat)
## /restore

TODO:

  • Finish tests
  • NEWS entry
  • Remove unused code
  • Improve docs

schloerke added 24 commits May 7, 2025 12:58
Need to remove with_chat and instead use as argument. Avoids temp global state state / clearly provides context
* main: (26 commits)
  Drop `completed` (tidyverse#495)
  Add PortkeyAI support (tidyverse#471)
  feat: Adds `stream = "content"` option to `$stream()` and `$stream_async()` (tidyverse#494)
  feat(Chat): `tool_request` and `tool_result` callbacks (tidyverse#493)
  feat: Add `tool_reject()` (tidyverse#490)
  fix: Tweak simple tools test to avoid Claude's empty strings (tidyverse#492)
  feat(chat_async): Adds `tool_mode` to choose between sequential or concurrent tool calling (tidyverse#488)
  And azure :|
  Update snapshot tests; fix bad bedrock model choice
  chore: Remove trailing whitespace from NEWS (tidyverse#489)
  Add (some) functions for listing models (tidyverse#454)
  Move `chat$extract_data_parallel()` to `parallel_chat_structured()` (tidyverse#486)
  refactor: Use generators for tool invocation (tidyverse#487)
  refactor: Remove private invoke_tools methods (tidyverse#485)
  Automatically convert tool inputs (tidyverse#463)
  Allow `$extract_data_parallel()` to return tokens + cost (tidyverse#449)
  Start work on programming vignette (tidyverse#458)
  Revise type coercion (tidyverse#484)
  Tweak `chat_openai_test()` (tidyverse#483)
  refactor(tools): `match_tools()` and `tool_results_as_turn()` (tidyverse#480)
  ...
@schloerke schloerke force-pushed the turns_record_replay branch from c4b2b03 to ae4dc7e Compare May 20, 2025 18:16
@schloerke schloerke marked this pull request as ready for review June 3, 2025 14:19
Copy link
Member

@hadley hadley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few changes/comments to get started. Thanks for working on this!

#' basic list that can be easily serialized.
#' * `contents_replay()` will accept a basic list (from `contents_record()`) and
#' return a corresponding [Turn] or [Content] related object.
#' * `contents_replay_class()` is a generic function that is dispatched from
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be exported?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the remaining comments come down to generalized support...

Should we allow for external package support? Ex: A tool call that returns an S7 object from a package outside ellmer.

The current implementation is set up for multiple package support (which would like contents_replay_class() to be exported for external package customization).


Another nice-ity that all props are auto inspected for S7 values. By default, if they are an S7 value or an unnamed list of S7 values, the value is recorded as well.

If we want to manually handle contents_record() for every ellmer classes only, then yes everything becomes simpler in implementation and we should error for contents_replay() values that are not replay values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we should just tightly scope to ellmer for now. We can consider expanding support if/when needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Serialization of Turns and Contents for bookmark support
2 participants