Skip to content

Azure OpenAI OAuth - R Shiny chat app #202

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 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/azure-openai-oauth-rshiny/.Rprofile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
source("renv/activate.R")
1 change: 1 addition & 0 deletions extensions/azure-openai-oauth-rshiny/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.posit/
19 changes: 19 additions & 0 deletions extensions/azure-openai-oauth-rshiny/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# R Shiny App using an Azure OpenAI OAuth Integration

An R Shiny app that uses `ellmer` and `shinychat` to create an interactive chat interface that answers questions about the `palmerpenguins` dataset. The app demonstrates the OAuth credential handoff made possible by the Azure OpenAI OAuth integration which gives the shiny app access to Azure OpenAI resources.

# Setup

An Azure OpenAI OAuth Integration must be configured by the Posit Connect administrator using application specific fields from the Azure administrator.

Alternatively, if testing this shiny app locally, the environment variable `AZURE_OPENAI_API_KEY` must be set.

# Usage

Deploy the app to Connect.

**Note**:

Only members of the "Connect" group have been assigned the proper data action role to use the Azure OpenAI `gpt-4o-mini` deployment that this app is set up to interact with. Because of this, the primary purpose of the app is to serve as a blueprint for what utilizing the Azure OpenAI OAuth integration might look like.

To adapt this shiny app to a different azure endpoint and model deployment, the `deployment_id`, `api_version`, and `endpoint` arguments passed to `ellmer::chat_azure_openai()`, must be respecified.
135 changes: 135 additions & 0 deletions extensions/azure-openai-oauth-rshiny/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
library(shiny)
library(shinychat)
library(ellmer)
library(palmerpenguins)
library(connectapi)
library(bslib)

data(penguins)

setup_ui <- bslib::page_fluid(
theme = bslib::bs_theme(bootswatch = "flatly"),
bslib::layout_columns(
col_widths = c(2, 8, 2),
NULL,
bslib::card(
bslib::card_header("Azure OpenAI OAuth Setup Instructions"),
bslib::card_body(
tags$h4("Configuration Required"),
tags$p("This application requires an Azure OpenAI OAuth integration to be properly configured."),
tags$p("For more detailed instructions, please refer to the ",
tags$a(href="https://docs.posit.co/connect/admin/integrations/oauth-integrations/azure-openai/",
"Posit Connect Azure OpenAI OAuth integration documentation",
target="_blank"))
),
width = "100%",
class = "shadow"
),
NULL
)
)

app_ui <- bslib::page_fluid(
theme = bslib::bs_theme(bootswatch = "flatly"),
bslib::layout_columns(
col_widths = c(2, 8, 2),
NULL,
bslib::card(
bslib::card_header("Palmer Penguins Chat Assistant"),
shinychat::chat_ui("chat", height = "300px"),
width = "100%",
class = "shadow"
),
NULL
)
)

screen_ui <- shiny::uiOutput("screen")


server <- function(input, output, session) {

# Get Connect OAuth credentials using connectapi
get_oauth_credentials <- function() {

if (Sys.getenv("POSIT_PRODUCT") == "CONNECT") {

client <- connectapi::connect()

user_session_token <- session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN
oauth_response <- connectapi::get_oauth_credentials(client, user_session_token)
credentials <- list(Authorization = paste("Bearer", oauth_response$access_token))

} else {

# Local development mode
api_key <- Sys.getenv("AZURE_OPENAI_API_KEY")
if (api_key != "") {
credentials <- list("api-key" = api_key)
} else {
credentials <- NULL
}

}

credentials

}

OAUTH_INTEGRATION_ENABLED <- TRUE
user_session_token <- session$request$HTTP_POSIT_CONNECT_USER_SESSION_TOKEN

if (!is.null(user_session_token)) {
# Capture any messages that might contain the error code
msg <- capture.output(
try(connectapi::connect(token = user_session_token)),
type = "message"
)

if (any(grepl("212", msg))) {
OAUTH_INTEGRATION_ENABLED <- FALSE
}
}


output$screen <- shiny::renderUI({
if (OAUTH_INTEGRATION_ENABLED) {
app_ui
} else {
setup_ui
}
})

# Create Azure OpenAI chat function
penguin_chat <- function() {

credentials <- get_oauth_credentials()

# Create a custom chat function for Azure OpenAI
azure_chat <- ellmer::chat_azure_openai(
deployment_id = "gpt-4o-mini",
api_version = "2024-12-01-preview",
endpoint = "https://8ul4l3wq0g.openai.azure.com",
credentials = credentials,
system_prompt = paste0(
"You are a data assistant helping with the Palmer Penguins dataset. ",
"The dataset contains measurements for Adelie, Chinstrap, and Gentoo penguins observed on islands in the Palmer Archipelago. ",
"Answer questions about the dataset concisely and accurately. ",
"The dataset structure is: \n", paste(capture.output(str(penguins)), collapse = "\n")
)
)
}

shiny::observeEvent(input$chat_user_input, {

current_chat <- penguin_chat()

stream <- current_chat$stream_async(input$chat_user_input)
shinychat::chat_append("chat", stream)


})

}

shiny::shinyApp(screen_ui, server)
Loading