When an error occurs while streaming an assistant response (e.g. inside chat$stream_async()), chat_append_stream() catches it and appends (via chat_append_message()) sanitized_chat_error(reason) into the chat UI as a normal chat bubble.
sanitized_chat_error() lives in pkg-r/R/utils.R, along with further helpers:
needs_sanitized <- function(err) {
isTRUE(getOption("shiny.sanitize.errors")) &&
!inherits(err, "shiny.custom.error")
}
sanitized_error_message <- function(err) {
if (needs_sanitized(err)) {
"An error occurred. Please try again or contact the app author."
} else {
strip_ansi(conditionMessage(err))
}
}
notify_error <- function(prefix, err) {
shiny::showNotification(
paste0(prefix, ": ", sanitized_error_message(err)),
type = "error",
duration = NULL
)
rlang::warn(prefix, parent = err)
}
sanitized_chat_error <- function(err) {
if (needs_sanitized(err)) {
sprintf("\n\n**%s**", sanitized_error_message(err))
} else {
sprintf(
"\n\n**An error occurred:**\n\n```\n%s\n```",
sanitized_error_message(err)
)
}
}
As we see, if shiny.sanitize.errors is explicitly set to TRUE, the chat shows the generic message. Otherwise, the chat bubble will show: **An error occurred:** followed by the raw conditionMessage(). A conditionMessage() text is whatever it is—it may be satisfactory, but chances are that it is not helpful, reveals confidential information, ...
shiny.sanitize.errors is a global option. If an app developer wants to scope it to a single shinychat function, or to otherwise take ownership of a specific error message shown by shinychat, things get complicated. Not because of a flaw in the implementation—shiny.sanitize.errors being read asynchronously, inside chat_append_stream()'s own promises::catch() handler, is absolutely fine as is.
Proposal
Add an argument to the following functions, that allows app authors to define how the error is displayed in the chat:
chat_append() (passing on to chat_append_stream())
chat_server() (passing on to chat_append() three times:
|
chat_append(ui_id, stream) |
,
|
chat_append(id, response, role = role, icon = icon, session = session) |
,
|
chat_append(id, msg$content, role = msg$role, session = session) |
)
chat_app() (passing on to chat_server())
I haven't thought about the name of the argument yet, and I'm open to what values the argument should take. Here are some initial notes:
- Argument name
error_message, ... . on_error would likely be misleading in multiple ways.
- Accepted values String? Function? Enum, mirroring Python:
"auto" ("actual" or "sanitize" depending on shiny.sanitize.errors)/"actual" (e.g. what we now have if shiny.sanitize.errors is not TRUE) / "sanitize" (e.g. what we now have if shiny.sanitize.errors is TRUE) ("unhandled" may or may not make sense)? Or a combination of some of them?
Python package
shinychat's Python Chat class exposes an on_error parameter, set once per Chat instance. However, Python's enum options control whether/how a popup appears, while the R proposal is about what text appears in the chat itself.
When an error occurs while streaming an assistant response (e.g. inside
chat$stream_async()),chat_append_stream()catches it and appends (viachat_append_message())sanitized_chat_error(reason)into the chat UI as a normal chat bubble.sanitized_chat_error()lives inpkg-r/R/utils.R, along with further helpers:As we see, if
shiny.sanitize.errorsis explicitly set toTRUE, the chat shows the generic message. Otherwise, the chat bubble will show:**An error occurred:**followed by the rawconditionMessage(). AconditionMessage()text is whatever it is—it may be satisfactory, but chances are that it is not helpful, reveals confidential information, ...shiny.sanitize.errorsis a global option. If an app developer wants to scope it to a single shinychat function, or to otherwise take ownership of a specific error message shown by shinychat, things get complicated. Not because of a flaw in the implementation—shiny.sanitize.errorsbeing read asynchronously, insidechat_append_stream()'s ownpromises::catch()handler, is absolutely fine as is.Proposal
Add an argument to the following functions, that allows app authors to define how the error is displayed in the chat:
chat_append()(passing on tochat_append_stream())chat_server()(passing on tochat_append()three times:shinychat/pkg-r/R/chat_app.R
Line 323 in e74ed26
shinychat/pkg-r/R/chat_app.R
Line 486 in e74ed26
shinychat/pkg-r/R/chat_app.R
Line 688 in e74ed26
chat_app()(passing on tochat_server())I haven't thought about the name of the argument yet, and I'm open to what values the argument should take. Here are some initial notes:
error_message, ... .on_errorwould likely be misleading in multiple ways."auto"("actual"or"sanitize"depending onshiny.sanitize.errors)/"actual"(e.g. what we now have ifshiny.sanitize.errorsis notTRUE) /"sanitize"(e.g. what we now have ifshiny.sanitize.errorsisTRUE) ("unhandled"may or may not make sense)? Or a combination of some of them?Python package
shinychat's PythonChatclass exposes anon_errorparameter, set once perChatinstance. However, Python's enum options control whether/how a popup appears, while the R proposal is about what text appears in the chat itself.