From efd388c2d439545d27524fba7a006544ed0002cd Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 18 Dec 2024 09:54:44 -0600 Subject: [PATCH 1/8] Update Chat templates and examples to use chatlas --- .../enterprise/aws-bedrock-anthropic/app.py | 18 ++----- .../aws-bedrock-anthropic/requirements.txt | 3 +- .../chat/enterprise/azure-openai/app.py | 41 +++++--------- .../enterprise/azure-openai/requirements.txt | 1 + .../chat/hello-providers/anthropic/app.py | 39 +++++++------- .../anthropic/requirements.txt | 1 + .../hello-providers/google/_template.json | 5 ++ .../chat/hello-providers/google/app.py | 37 +++++++++++++ .../chat/hello-providers/google/app_utils.py | 26 +++++++++ .../hello-providers/google/requirements.txt | 5 ++ .../chat/hello-providers/langchain/app.py | 20 +++---- .../chat/hello-providers/ollama/app.py | 32 +++++------ .../hello-providers/ollama/requirements.txt | 1 + .../chat/hello-providers/openai/app.py | 41 ++++++-------- .../hello-providers/openai/requirements.txt | 1 + .../chat/production/anthropic/app.py | 53 ++++++++----------- shiny/templates/chat/production/openai/app.py | 35 ++++++------ 17 files changed, 195 insertions(+), 164 deletions(-) create mode 100644 shiny/templates/chat/hello-providers/google/_template.json create mode 100644 shiny/templates/chat/hello-providers/google/app.py create mode 100644 shiny/templates/chat/hello-providers/google/app_utils.py create mode 100644 shiny/templates/chat/hello-providers/google/requirements.txt diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py index fa9b7859e..67bc61ef1 100644 --- a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py +++ b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py @@ -4,8 +4,8 @@ # To get started, follow the instructions at https://aws.amazon.com/bedrock/claude/ # as well as https://github.com/anthropics/anthropic-sdk-python#aws-bedrock # ------------------------------------------------------------------------------------ -from anthropic import AnthropicBedrock from app_utils import load_dotenv +from chatlas import ChatBedrockAnthropic from shiny.express import ui @@ -13,7 +13,8 @@ # them in a file named `.env`. The `python-dotenv` package will load `.env` as # environment variables which can be read by `os.getenv()`. load_dotenv() -llm = AnthropicBedrock( +chat_model = ChatBedrockAnthropic( + model="anthropic.claude-3-sonnet-20240229-v1:0", # aws_secret_key=os.getenv("AWS_SECRET_KEY"), # aws_access_key=os.getenv("AWS_ACCESS_KEY"), # aws_region=os.getenv("AWS_REGION"), @@ -34,15 +35,6 @@ # Define a callback to run when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="anthropic") - # Create a response message stream - response = llm.messages.create( - model="anthropic.claude-3-5-sonnet-20241022-v2:0", - messages=messages, - stream=True, - max_tokens=1000, - ) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt index fb3b67026..a0d9e4048 100644 --- a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt +++ b/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt @@ -1,4 +1,5 @@ shiny python-dotenv tokenizers -anthropic +chatlas +anthropic[bedrock] diff --git a/shiny/templates/chat/enterprise/azure-openai/app.py b/shiny/templates/chat/enterprise/azure-openai/app.py index d5c9f3a19..1bca65eb2 100644 --- a/shiny/templates/chat/enterprise/azure-openai/app.py +++ b/shiny/templates/chat/enterprise/azure-openai/app.py @@ -1,32 +1,27 @@ # ------------------------------------------------------------------------------------ # A basic Shiny Chat example powered by OpenAI running on Azure. -# To run it, you'll need OpenAI API key. -# To get setup, follow the instructions at https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart?tabs=command-line%2Cpython-new&pivots=programming-language-python#create-a-new-python-application # ------------------------------------------------------------------------------------ import os from app_utils import load_dotenv -from openai import AzureOpenAI +from chatlas import ChatAzureOpenAI from shiny.express import ui -# Either explicitly set the AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT environment -# variables before launching the app, or set them in a file named `.env`. The -# `python-dotenv` package will load `.env` as environment variables which can later be -# read by `os.getenv()`. +# ChatAzureOpenAI() requires an API key from Azure OpenAI. +# See the docs for more information on how to obtain one. +# https://posit-dev.github.io/chatlas/reference/ChatAzureOpenAI.html load_dotenv() - -llm = AzureOpenAI( +chat_model = ChatAzureOpenAI( api_key=os.getenv("AZURE_OPENAI_API_KEY"), - api_version="2024-02-01", - azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), # type: ignore + endpoint="https://my-endpoint.openai.azure.com", + deployment_id="gpt-4o-mini", + api_version="2024-08-01-preview", ) -deployment_name = "REPLACE_WITH_YOUR_DEPLOYMENT_NAME" - # Set some Shiny page options ui.page_opts( - title="Hello OpenAI Chat", + title="Hello Azure OpenAI Chat", fillable=True, fillable_mobile=True, ) @@ -34,25 +29,13 @@ # Create a chat instance, with an initial message chat = ui.Chat( id="chat", - messages=[ - {"content": "Hello! How can I help you today?", "role": "assistant"}, - ], + messages=["Hello! How can I help you today?"], ) - -# Display the chat chat.ui() # Define a callback to run when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="openai") - # Create a response message stream - response = llm.chat.completions.create( - model=deployment_name, - messages=messages, - stream=True, - ) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/enterprise/azure-openai/requirements.txt b/shiny/templates/chat/enterprise/azure-openai/requirements.txt index 6e4a780cf..e7c42d64c 100644 --- a/shiny/templates/chat/enterprise/azure-openai/requirements.txt +++ b/shiny/templates/chat/enterprise/azure-openai/requirements.txt @@ -1,4 +1,5 @@ shiny python-dotenv tokenizers +chatlas openai diff --git a/shiny/templates/chat/hello-providers/anthropic/app.py b/shiny/templates/chat/hello-providers/anthropic/app.py index e3bae5966..52f4c20ab 100644 --- a/shiny/templates/chat/hello-providers/anthropic/app.py +++ b/shiny/templates/chat/hello-providers/anthropic/app.py @@ -1,20 +1,23 @@ # ------------------------------------------------------------------------------------ # A basic Shiny Chat example powered by Anthropic's Claude model. -# To run it, you'll need an Anthropic API key. -# To get one, follow the instructions at https://docs.anthropic.com/en/api/getting-started # ------------------------------------------------------------------------------------ import os -from anthropic import AsyncAnthropic from app_utils import load_dotenv +from chatlas import ChatAnthropic from shiny.express import ui -# Either explicitly set the ANTHROPIC_API_KEY environment variable before launching the -# app, or set them in a file named `.env`. The `python-dotenv` package will load `.env` -# as environment variables which can later be read by `os.getenv()`. +# ChatAnthropic() requires an API key from Anthropic. +# See the docs for more information on how to obtain one. +# https://posit-dev.github.io/chatlas/reference/ChatAnthropic.html load_dotenv() -llm = AsyncAnthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) +chat_model = ChatAnthropic( + api_key=os.environ.get("ANTHROPIC_API_KEY"), + model="claude-3-5-sonnet-latest", + system_prompt="You are a helpful assistant.", +) + # Set some Shiny page options ui.page_opts( @@ -23,22 +26,16 @@ fillable_mobile=True, ) -# Create and display empty chat -chat = ui.Chat(id="chat") +# Create and display a Shiny chat component +chat = ui.Chat( + id="chat", + messages=["Hello! How can I help you today?"], +) chat.ui() -# Define a callback to run when the user submits a message +# Generate a response when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="anthropic") - # Create a response message stream - response = await llm.messages.create( - model="claude-3-5-sonnet-latest", - messages=messages, - stream=True, - max_tokens=1000, - ) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/anthropic/requirements.txt b/shiny/templates/chat/hello-providers/anthropic/requirements.txt index fb3b67026..fc19951f1 100644 --- a/shiny/templates/chat/hello-providers/anthropic/requirements.txt +++ b/shiny/templates/chat/hello-providers/anthropic/requirements.txt @@ -1,4 +1,5 @@ shiny python-dotenv tokenizers +chatlas anthropic diff --git a/shiny/templates/chat/hello-providers/google/_template.json b/shiny/templates/chat/hello-providers/google/_template.json new file mode 100644 index 000000000..baf30e7cd --- /dev/null +++ b/shiny/templates/chat/hello-providers/google/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "id": "chat-ai-gemini", + "title": "Chat AI using Google Gemini" +} diff --git a/shiny/templates/chat/hello-providers/google/app.py b/shiny/templates/chat/hello-providers/google/app.py new file mode 100644 index 000000000..bd778f866 --- /dev/null +++ b/shiny/templates/chat/hello-providers/google/app.py @@ -0,0 +1,37 @@ +# ------------------------------------------------------------------------------------ +# A basic Shiny Chat example powered by Google's Gemini model. +# ------------------------------------------------------------------------------------ +import os + +from app_utils import load_dotenv +from chatlas import ChatGoogle + +from shiny.express import ui + +# ChatGoogle() requires an API key from Google. +# See the docs for more information on how to obtain one. +# https://posit-dev.github.io/chatlas/reference/ChatGoogle.html +load_dotenv() +chat_model = ChatGoogle( + api_key=os.environ.get("GOOGLE_API_KEY"), + system_prompt="You are a helpful assistant.", + model="gemini-1.5-flash", +) + +# Set some Shiny page options +ui.page_opts( + title="Hello Google Gemini Chat", + fillable=True, + fillable_mobile=True, +) + +# Create and display empty chat +chat = ui.Chat(id="chat") +chat.ui() + + +# Generate a response when the user submits a message +@chat.on_user_submit +async def handle_user_input(user_input): + response = chat_model.stream(user_input) + await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/google/app_utils.py b/shiny/templates/chat/hello-providers/google/app_utils.py new file mode 100644 index 000000000..404a13730 --- /dev/null +++ b/shiny/templates/chat/hello-providers/google/app_utils.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path +from typing import Any + +app_dir = Path(__file__).parent +env_file = app_dir / ".env" + + +def load_dotenv(dotenv_path: os.PathLike[str] = env_file, **kwargs: Any) -> None: + """ + A convenience wrapper around `dotenv.load_dotenv` that warns if `dotenv` is not installed. + It also returns `None` to make it easier to ignore the return value. + """ + try: + import dotenv + + dotenv.load_dotenv(dotenv_path=dotenv_path, **kwargs) + except ImportError: + import warnings + + warnings.warn( + "Could not import `dotenv`. If you want to use `.env` files to " + "load environment variables, please install it using " + "`pip install python-dotenv`.", + stacklevel=2, + ) diff --git a/shiny/templates/chat/hello-providers/google/requirements.txt b/shiny/templates/chat/hello-providers/google/requirements.txt new file mode 100644 index 000000000..f51cd04e3 --- /dev/null +++ b/shiny/templates/chat/hello-providers/google/requirements.txt @@ -0,0 +1,5 @@ +shiny +python-dotenv +tokenizers +chatlas +google-generativeai diff --git a/shiny/templates/chat/hello-providers/langchain/app.py b/shiny/templates/chat/hello-providers/langchain/app.py index 73e13777f..17cd34f21 100644 --- a/shiny/templates/chat/hello-providers/langchain/app.py +++ b/shiny/templates/chat/hello-providers/langchain/app.py @@ -15,7 +15,10 @@ # app, or set them in a file named `.env`. The `python-dotenv` package will load `.env` # as environment variables which can later be read by `os.getenv()`. load_dotenv() -llm = ChatOpenAI(api_key=os.environ.get("OPENAI_API_KEY")) # type: ignore +chat_model = ChatOpenAI( + api_key=os.environ.get("OPENAI_API_KEY"), + model="gpt-4o", +) # Set some Shiny page options ui.page_opts( @@ -24,17 +27,16 @@ fillable_mobile=True, ) -# Create and display an empty chat UI -chat = ui.Chat(id="chat") +# Create and display a Shiny chat component +chat = ui.Chat( + id="chat", + messages=["Hello! How can I help you today?"], +) chat.ui() # Define a callback to run when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="langchain") - # Create a response message stream - response = llm.astream(messages) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/ollama/app.py b/shiny/templates/chat/hello-providers/ollama/app.py index 25d1e37ff..ee5852bd9 100644 --- a/shiny/templates/chat/hello-providers/ollama/app.py +++ b/shiny/templates/chat/hello-providers/ollama/app.py @@ -1,14 +1,16 @@ # ------------------------------------------------------------------------------------ # A basic Shiny Chat example powered by Ollama. -# To run it, you'll need an Ollama server running locally. -# To download and run the server, see https://github.com/ollama/ollama -# To install the Ollama Python client, see https://github.com/ollama/ollama-python # ------------------------------------------------------------------------------------ -import ollama +from chatlas import ChatOllama from shiny.express import ui +# ChatOllama() requires an Ollama model server to be running locally. +# See the docs for more information on how to set up a local Ollama server. +# https://posit-dev.github.io/chatlas/reference/ChatOllama.html +chat_model = ChatOllama(model="llama3.1") + # Set some Shiny page options ui.page_opts( title="Hello Ollama Chat", @@ -16,22 +18,16 @@ fillable_mobile=True, ) -# Create and display empty chat -chat = ui.Chat(id="chat") +# Create and display a Shiny chat component +chat = ui.Chat( + id="chat", + messages=["Hello! How can I help you today?"], +) chat.ui() -# Define a callback to run when the user submits a message +# Generate a response when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="ollama") - # Create a response message stream - # Assumes you've run `ollama run llama3` to start the server - response = ollama.chat( - model="llama3.2", - messages=messages, - stream=True, - ) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/ollama/requirements.txt b/shiny/templates/chat/hello-providers/ollama/requirements.txt index 223d69e4b..5901288be 100644 --- a/shiny/templates/chat/hello-providers/ollama/requirements.txt +++ b/shiny/templates/chat/hello-providers/ollama/requirements.txt @@ -1,3 +1,4 @@ shiny tokenizers +chatlas ollama diff --git a/shiny/templates/chat/hello-providers/openai/app.py b/shiny/templates/chat/hello-providers/openai/app.py index 2140fefdf..a491c859f 100644 --- a/shiny/templates/chat/hello-providers/openai/app.py +++ b/shiny/templates/chat/hello-providers/openai/app.py @@ -1,20 +1,23 @@ # ------------------------------------------------------------------------------------ -# A basic Shiny Chat example powered by OpenAI's GPT-4o model. -# To run it, you'll need OpenAI API key. -# To get setup, follow the instructions at https://platform.openai.com/docs/quickstart +# A basic Shiny Chat example powered by OpenAI. # ------------------------------------------------------------------------------------ import os from app_utils import load_dotenv -from openai import AsyncOpenAI +from chatlas import ChatOpenAI from shiny.express import ui -# Either explicitly set the OPENAI_API_KEY environment variable before launching the -# app, or set them in a file named `.env`. The `python-dotenv` package will load `.env` -# as environment variables which can later be read by `os.getenv()`. +# ChatOpenAI() requires an API key from OpenAI. +# See the docs for more information on how to obtain one. +# https://posit-dev.github.io/chatlas/reference/ChatOpenAI.html load_dotenv() -llm = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY")) +chat_model = ChatOpenAI( + api_key=os.environ.get("OPENAI_API_KEY"), + model="gpt-4o", + system_prompt="You are a helpful assistant.", +) + # Set some Shiny page options ui.page_opts( @@ -23,28 +26,16 @@ fillable_mobile=True, ) -# Create a chat instance, with an initial message +# Create and display a Shiny chat component chat = ui.Chat( id="chat", - messages=[ - {"content": "Hello! How can I help you today?", "role": "assistant"}, - ], + messages=["Hello! How can I help you today?"], ) - -# Display the chat chat.ui() -# Define a callback to run when the user submits a message +# Generate a response when the user submits a message @chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="openai") - # Create a response message stream - response = await llm.chat.completions.create( - model="gpt-4o", - messages=messages, - stream=True, - ) - # Append the response stream into the chat +async def handle_user_input(user_input): + response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/openai/requirements.txt b/shiny/templates/chat/hello-providers/openai/requirements.txt index 6e4a780cf..e7c42d64c 100644 --- a/shiny/templates/chat/hello-providers/openai/requirements.txt +++ b/shiny/templates/chat/hello-providers/openai/requirements.txt @@ -1,4 +1,5 @@ shiny python-dotenv tokenizers +chatlas openai diff --git a/shiny/templates/chat/production/anthropic/app.py b/shiny/templates/chat/production/anthropic/app.py index 423d4337c..60cfbd6dc 100644 --- a/shiny/templates/chat/production/anthropic/app.py +++ b/shiny/templates/chat/production/anthropic/app.py @@ -8,58 +8,51 @@ # - Reproducibility: Consider pinning a snapshot of the LLM model to ensure that the # same model is used each time the app is run. # -# See the MODEL_INFO dictionary below for an example of how to set these values for -# Anthropic's Claude model. +# See the MODEL_CONFIG dictionary below for an example of how to set these values for +# Anthropic's Claude 3.5 Sonnet model. # https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table # ------------------------------------------------------------------------------------ import os -from anthropic import AsyncAnthropic from app_utils import load_dotenv +from chatlas import Chat, ChatAnthropic from shiny.express import ui -load_dotenv() -llm = AsyncAnthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) - - -MODEL_INFO = { - "name": "claude-3-5-sonnet-20241022", - # DISCLAIMER: Anthropic has not yet released a public tokenizer for Claude models, - # so this uses the generic default provided by Chat() (for now). That is probably - # ok though since the default tokenizer likely overestimates the token count. - "tokenizer": None, - "token_limits": (200000, 8192), +MODEL_CONFIG = { + "name": "claude-3-5-sonnet-20240620", + "context_window": 200000, + "max_tokens": 8192, } +load_dotenv() +chat_model = ChatAnthropic( + api_key=os.environ.get("ANTHROPIC_API_KEY"), + model=MODEL_CONFIG["name"], + max_tokens=MODEL_CONFIG["max_tokens"], + system_prompt="You are a helpful assistant.", +) + ui.page_opts( - title="Hello Anthropic Chat", + title="Hello OpenAI Chat", fillable=True, fillable_mobile=True, ) chat = ui.Chat( id="chat", - messages=[ - {"content": "Hello! How can I help you today?", "role": "assistant"}, - ], - tokenizer=MODEL_INFO["tokenizer"], + messages=["Hello! How can I help you today?"], ) chat.ui() @chat.on_user_submit -async def _(): - messages = chat.messages( - format="anthropic", - token_limits=MODEL_INFO["token_limits"], - ) - response = await llm.messages.create( - model=MODEL_INFO["name"], - messages=messages, - stream=True, - max_tokens=MODEL_INFO["token_limits"][1], - ) +async def handle_user_input(user_input): + response = await chat_model.stream_async(user_input) await chat.append_message_stream(response) + + +def trim_chat_turns(turns, max_turns=5): + return turns[-max_turns:] diff --git a/shiny/templates/chat/production/openai/app.py b/shiny/templates/chat/production/openai/app.py index 7b1274c95..69e4e00d1 100644 --- a/shiny/templates/chat/production/openai/app.py +++ b/shiny/templates/chat/production/openai/app.py @@ -8,27 +8,29 @@ # - Reproducibility: Consider pinning a snapshot of the LLM model to ensure that the # same model is used each time the app is run. # -# See the MODEL_INFO dictionary below for an example of how to set these values for +# See the MODEL_CONFIG dictionary below for an example of how to set these values for # OpenAI's GPT-4o model. # ------------------------------------------------------------------------------------ import os import tiktoken from app_utils import load_dotenv -from openai import AsyncOpenAI +from chatlas import Chat, ChatOpenAI from shiny.express import ui -load_dotenv() -llm = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY")) - - -MODEL_INFO = { +MODEL_CONFIG = { "name": "gpt-4o-2024-08-06", - "tokenizer": tiktoken.encoding_for_model("gpt-4o-2024-08-06"), - "token_limits": (128000, 16000), + "context_window": 128000, + "max_tokens": 16000, } +load_dotenv() +chat_model = ChatOpenAI( + api_key=os.environ.get("OPENAI_API_KEY"), + model=MODEL_CONFIG["name"], +) + ui.page_opts( title="Hello OpenAI Chat", @@ -38,19 +40,16 @@ chat = ui.Chat( id="chat", - messages=[ - {"content": "Hello! How can I help you today?", "role": "assistant"}, - ], - tokenizer=MODEL_INFO["tokenizer"], + messages=["Hello! How can I help you today?"], ) chat.ui() @chat.on_user_submit -async def _(): - messages = chat.messages(format="openai", token_limits=MODEL_INFO["token_limits"]) - response = await llm.chat.completions.create( - model=MODEL_INFO["name"], messages=messages, stream=True - ) +async def handle_user_input(user_input): + response = await chat_model.stream_async(user_input) await chat.append_message_stream(response) + + +tokenizer = tiktoken.encoding_for_model(MODEL_CONFIG["name"]) From 4a934a25fc06775557e158aaf0838e3c5ff891e9 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 09:44:40 -0600 Subject: [PATCH 2/8] Remove production templates and redundant Gemini template --- shiny/_main_create.py | 11 +--- .../hello-providers/gemini/_template.json | 5 -- .../chat/hello-providers/gemini/app.py | 42 -------------- .../chat/hello-providers/gemini/app_utils.py | 26 --------- .../hello-providers/gemini/requirements.txt | 4 -- .../chat/production/anthropic/_template.json | 5 -- .../chat/production/anthropic/app.py | 58 ------------------- .../chat/production/anthropic/app_utils.py | 26 --------- .../production/anthropic/requirements.txt | 4 -- .../chat/production/openai/_template.json | 5 -- shiny/templates/chat/production/openai/app.py | 55 ------------------ .../chat/production/openai/app_utils.py | 26 --------- .../chat/production/openai/requirements.txt | 4 -- 13 files changed, 1 insertion(+), 270 deletions(-) delete mode 100644 shiny/templates/chat/hello-providers/gemini/_template.json delete mode 100644 shiny/templates/chat/hello-providers/gemini/app.py delete mode 100644 shiny/templates/chat/hello-providers/gemini/app_utils.py delete mode 100644 shiny/templates/chat/hello-providers/gemini/requirements.txt delete mode 100644 shiny/templates/chat/production/anthropic/_template.json delete mode 100644 shiny/templates/chat/production/anthropic/app.py delete mode 100644 shiny/templates/chat/production/anthropic/app_utils.py delete mode 100644 shiny/templates/chat/production/anthropic/requirements.txt delete mode 100644 shiny/templates/chat/production/openai/_template.json delete mode 100644 shiny/templates/chat/production/openai/app.py delete mode 100644 shiny/templates/chat/production/openai/app_utils.py delete mode 100644 shiny/templates/chat/production/openai/requirements.txt diff --git a/shiny/_main_create.py b/shiny/_main_create.py index 84c66c5ee..e00d59c12 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -231,10 +231,6 @@ def chat_hello_providers(self) -> list[ShinyTemplate]: def chat_enterprise(self) -> list[ShinyTemplate]: return self._templates("templates/chat/enterprise") - @property - def chat_production(self) -> list[ShinyTemplate]: - return self._templates("templates/chat/production") - shiny_internal_templates = ShinyInternalTemplates() @@ -264,7 +260,6 @@ def use_internal_template( chat_templates = [ *shiny_internal_templates.chat_hello_providers, *shiny_internal_templates.chat_enterprise, - *shiny_internal_templates.chat_production, ] menu_choices = [ @@ -356,7 +351,6 @@ def use_internal_chat_ai_template( choices=[ Choice(title="By provider...", value="_chat-ai_hello-providers"), Choice(title="Enterprise providers...", value="_chat-ai_enterprise"), - Choice(title="Production-ready chat AI", value="_chat-ai_production"), back_choice, cancel_choice, ], @@ -375,9 +369,7 @@ def use_internal_chat_ai_template( ) return - if input == "_chat-ai_production": - template_choices = shiny_internal_templates.chat_production - elif input == "_chat-ai_enterprise": + if input == "_chat-ai_enterprise": template_choices = shiny_internal_templates.chat_enterprise else: template_choices = shiny_internal_templates.chat_hello_providers @@ -392,7 +384,6 @@ def use_internal_chat_ai_template( [ *shiny_internal_templates.chat_hello_providers, *shiny_internal_templates.chat_enterprise, - *shiny_internal_templates.chat_production, ], choice, ) diff --git a/shiny/templates/chat/hello-providers/gemini/_template.json b/shiny/templates/chat/hello-providers/gemini/_template.json deleted file mode 100644 index baf30e7cd..000000000 --- a/shiny/templates/chat/hello-providers/gemini/_template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "app", - "id": "chat-ai-gemini", - "title": "Chat AI using Google Gemini" -} diff --git a/shiny/templates/chat/hello-providers/gemini/app.py b/shiny/templates/chat/hello-providers/gemini/app.py deleted file mode 100644 index 2240c8f52..000000000 --- a/shiny/templates/chat/hello-providers/gemini/app.py +++ /dev/null @@ -1,42 +0,0 @@ -# ------------------------------------------------------------------------------------ -# A basic Shiny Chat example powered by Google's Gemini model. -# To run it, you'll need a Google API key. -# To get one, follow the instructions at https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=python -# ------------------------------------------------------------------------------------ -from app_utils import load_dotenv -from google.generativeai import GenerativeModel - -from shiny.express import ui - -# Either explicitly set the GOOGLE_API_KEY environment variable before launching the -# app, or set them in a file named `.env`. The `python-dotenv` package will load `.env` -# as environment variables which can later be read by `os.getenv()`. -load_dotenv() -llm = GenerativeModel() - -# Set some Shiny page options -ui.page_opts( - title="Hello Google Gemini Chat", - fillable=True, - fillable_mobile=True, -) - -# Create and display empty chat -chat = ui.Chat(id="chat") -chat.ui() - - -# Define a callback to run when the user submits a message -@chat.on_user_submit -async def _(): - # Get messages currently in the chat - contents = chat.messages(format="google") - - # Generate a response message stream - response = llm.generate_content( - contents=contents, - stream=True, - ) - - # Append the response stream into the chat - await chat.append_message_stream(response) diff --git a/shiny/templates/chat/hello-providers/gemini/app_utils.py b/shiny/templates/chat/hello-providers/gemini/app_utils.py deleted file mode 100644 index 404a13730..000000000 --- a/shiny/templates/chat/hello-providers/gemini/app_utils.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -app_dir = Path(__file__).parent -env_file = app_dir / ".env" - - -def load_dotenv(dotenv_path: os.PathLike[str] = env_file, **kwargs: Any) -> None: - """ - A convenience wrapper around `dotenv.load_dotenv` that warns if `dotenv` is not installed. - It also returns `None` to make it easier to ignore the return value. - """ - try: - import dotenv - - dotenv.load_dotenv(dotenv_path=dotenv_path, **kwargs) - except ImportError: - import warnings - - warnings.warn( - "Could not import `dotenv`. If you want to use `.env` files to " - "load environment variables, please install it using " - "`pip install python-dotenv`.", - stacklevel=2, - ) diff --git a/shiny/templates/chat/hello-providers/gemini/requirements.txt b/shiny/templates/chat/hello-providers/gemini/requirements.txt deleted file mode 100644 index f3e733a88..000000000 --- a/shiny/templates/chat/hello-providers/gemini/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -shiny -python-dotenv -tokenizers -google-generativeai diff --git a/shiny/templates/chat/production/anthropic/_template.json b/shiny/templates/chat/production/anthropic/_template.json deleted file mode 100644 index 271e2c040..000000000 --- a/shiny/templates/chat/production/anthropic/_template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "app", - "id": "chat-ai-anthropic-prod", - "title": "Chat in production with Anthropic" -} diff --git a/shiny/templates/chat/production/anthropic/app.py b/shiny/templates/chat/production/anthropic/app.py deleted file mode 100644 index 60cfbd6dc..000000000 --- a/shiny/templates/chat/production/anthropic/app.py +++ /dev/null @@ -1,58 +0,0 @@ -# ------------------------------------------------------------------------------------ -# When putting a Chat into production, there are at least a couple additional -# considerations to keep in mind: -# - Token Limits: LLMs have (varying) limits on how many tokens can be included in -# a single request and response. To accurately respect these limits, you'll want -# to find the revelant limits and tokenizer for the model you're using, and inform -# Chat about them. -# - Reproducibility: Consider pinning a snapshot of the LLM model to ensure that the -# same model is used each time the app is run. -# -# See the MODEL_CONFIG dictionary below for an example of how to set these values for -# Anthropic's Claude 3.5 Sonnet model. -# https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table -# ------------------------------------------------------------------------------------ -import os - -from app_utils import load_dotenv -from chatlas import Chat, ChatAnthropic - -from shiny.express import ui - -MODEL_CONFIG = { - "name": "claude-3-5-sonnet-20240620", - "context_window": 200000, - "max_tokens": 8192, -} - -load_dotenv() -chat_model = ChatAnthropic( - api_key=os.environ.get("ANTHROPIC_API_KEY"), - model=MODEL_CONFIG["name"], - max_tokens=MODEL_CONFIG["max_tokens"], - system_prompt="You are a helpful assistant.", -) - - -ui.page_opts( - title="Hello OpenAI Chat", - fillable=True, - fillable_mobile=True, -) - -chat = ui.Chat( - id="chat", - messages=["Hello! How can I help you today?"], -) - -chat.ui() - - -@chat.on_user_submit -async def handle_user_input(user_input): - response = await chat_model.stream_async(user_input) - await chat.append_message_stream(response) - - -def trim_chat_turns(turns, max_turns=5): - return turns[-max_turns:] diff --git a/shiny/templates/chat/production/anthropic/app_utils.py b/shiny/templates/chat/production/anthropic/app_utils.py deleted file mode 100644 index 404a13730..000000000 --- a/shiny/templates/chat/production/anthropic/app_utils.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -app_dir = Path(__file__).parent -env_file = app_dir / ".env" - - -def load_dotenv(dotenv_path: os.PathLike[str] = env_file, **kwargs: Any) -> None: - """ - A convenience wrapper around `dotenv.load_dotenv` that warns if `dotenv` is not installed. - It also returns `None` to make it easier to ignore the return value. - """ - try: - import dotenv - - dotenv.load_dotenv(dotenv_path=dotenv_path, **kwargs) - except ImportError: - import warnings - - warnings.warn( - "Could not import `dotenv`. If you want to use `.env` files to " - "load environment variables, please install it using " - "`pip install python-dotenv`.", - stacklevel=2, - ) diff --git a/shiny/templates/chat/production/anthropic/requirements.txt b/shiny/templates/chat/production/anthropic/requirements.txt deleted file mode 100644 index fb3b67026..000000000 --- a/shiny/templates/chat/production/anthropic/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -shiny -python-dotenv -tokenizers -anthropic diff --git a/shiny/templates/chat/production/openai/_template.json b/shiny/templates/chat/production/openai/_template.json deleted file mode 100644 index 1a64e5211..000000000 --- a/shiny/templates/chat/production/openai/_template.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "app", - "id": "chat-ai-openai-prod", - "title": "Chat in production with OpenAI" -} diff --git a/shiny/templates/chat/production/openai/app.py b/shiny/templates/chat/production/openai/app.py deleted file mode 100644 index 69e4e00d1..000000000 --- a/shiny/templates/chat/production/openai/app.py +++ /dev/null @@ -1,55 +0,0 @@ -# ------------------------------------------------------------------------------------ -# When putting a Chat into production, there are at least a couple additional -# considerations to keep in mind: -# - Token Limits: LLMs have (varying) limits on how many tokens can be included in -# a single request and response. To accurately respect these limits, you'll want -# to find the revelant limits and tokenizer for the model you're using, and inform -# Chat about them. -# - Reproducibility: Consider pinning a snapshot of the LLM model to ensure that the -# same model is used each time the app is run. -# -# See the MODEL_CONFIG dictionary below for an example of how to set these values for -# OpenAI's GPT-4o model. -# ------------------------------------------------------------------------------------ -import os - -import tiktoken -from app_utils import load_dotenv -from chatlas import Chat, ChatOpenAI - -from shiny.express import ui - -MODEL_CONFIG = { - "name": "gpt-4o-2024-08-06", - "context_window": 128000, - "max_tokens": 16000, -} - -load_dotenv() -chat_model = ChatOpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), - model=MODEL_CONFIG["name"], -) - - -ui.page_opts( - title="Hello OpenAI Chat", - fillable=True, - fillable_mobile=True, -) - -chat = ui.Chat( - id="chat", - messages=["Hello! How can I help you today?"], -) - -chat.ui() - - -@chat.on_user_submit -async def handle_user_input(user_input): - response = await chat_model.stream_async(user_input) - await chat.append_message_stream(response) - - -tokenizer = tiktoken.encoding_for_model(MODEL_CONFIG["name"]) diff --git a/shiny/templates/chat/production/openai/app_utils.py b/shiny/templates/chat/production/openai/app_utils.py deleted file mode 100644 index 404a13730..000000000 --- a/shiny/templates/chat/production/openai/app_utils.py +++ /dev/null @@ -1,26 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -app_dir = Path(__file__).parent -env_file = app_dir / ".env" - - -def load_dotenv(dotenv_path: os.PathLike[str] = env_file, **kwargs: Any) -> None: - """ - A convenience wrapper around `dotenv.load_dotenv` that warns if `dotenv` is not installed. - It also returns `None` to make it easier to ignore the return value. - """ - try: - import dotenv - - dotenv.load_dotenv(dotenv_path=dotenv_path, **kwargs) - except ImportError: - import warnings - - warnings.warn( - "Could not import `dotenv`. If you want to use `.env` files to " - "load environment variables, please install it using " - "`pip install python-dotenv`.", - stacklevel=2, - ) diff --git a/shiny/templates/chat/production/openai/requirements.txt b/shiny/templates/chat/production/openai/requirements.txt deleted file mode 100644 index 4e5ab6b2a..000000000 --- a/shiny/templates/chat/production/openai/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -shiny -python-dotenv -tiktoken -openai From f8bf234572467c126204d9b26a9a92a0e8d02b4d Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 15:50:15 -0600 Subject: [PATCH 3/8] Give examples a facelift --- examples/chat/RAG/recipes/app.py | 23 +++--- examples/chat/RAG/recipes/requirements.txt | 3 +- examples/chat/hello-world/app-core.py | 8 +- examples/chat/hello-world/app.py | 8 +- examples/chat/playground/app.py | 87 ++++++++++++++-------- examples/chat/playground/requirements.txt | 8 +- examples/chat/sidebar-dark/app.py | 30 ++++++++ examples/chat/ui/clear/app.py | 49 ------------ examples/chat/ui/clear/requirements.txt | 2 - examples/chat/ui/dark/app.py | 41 ---------- examples/chat/ui/dark/requirements.txt | 2 - examples/chat/ui/dynamic/app.py | 40 ---------- examples/chat/ui/dynamic/requirements.txt | 2 - examples/chat/ui/sidebar/app.py | 48 ------------ examples/chat/ui/sidebar/requirements.txt | 2 - 15 files changed, 107 insertions(+), 246 deletions(-) create mode 100644 examples/chat/sidebar-dark/app.py delete mode 100644 examples/chat/ui/clear/app.py delete mode 100644 examples/chat/ui/clear/requirements.txt delete mode 100644 examples/chat/ui/dark/app.py delete mode 100644 examples/chat/ui/dark/requirements.txt delete mode 100644 examples/chat/ui/dynamic/app.py delete mode 100644 examples/chat/ui/dynamic/requirements.txt delete mode 100644 examples/chat/ui/sidebar/app.py delete mode 100644 examples/chat/ui/sidebar/requirements.txt diff --git a/examples/chat/RAG/recipes/app.py b/examples/chat/RAG/recipes/app.py index b9c8e7f2a..7b20eac31 100644 --- a/examples/chat/RAG/recipes/app.py +++ b/examples/chat/RAG/recipes/app.py @@ -5,13 +5,16 @@ # ------------------------------------------------------------------------------------ import os -from openai import AsyncOpenAI +from chatlas import ChatOpenAI from utils import recipe_prompt, scrape_page_with_url from shiny.express import ui # Provide your API key here (or set the environment variable) -llm = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY")) +chat_model = ChatOpenAI( + system_prompt=recipe_prompt, + api_key=os.environ.get("OPENAI_API_KEY"), +) # Set some Shiny page options ui.page_opts( @@ -24,11 +27,7 @@ chat = ui.Chat( id="chat", messages=[ - {"role": "system", "content": recipe_prompt}, - { - "role": "assistant", - "content": "Hello! I'm a recipe extractor. Please enter a URL to a recipe page. For example, ", - }, + "Hello! I'm a recipe extractor. Please enter a URL to a recipe page. For example, " ], ) @@ -50,11 +49,9 @@ async def try_scrape_page(input: str) -> str | None: @chat.on_user_submit -async def _(): - response = await llm.chat.completions.create( - model="gpt-4o", - messages=chat.messages(format="openai"), - temperature=0, - stream=True, +async def handle_user_input(user_input: str): + response = chat_model.stream( + user_input, + kwargs={"temperature": 0}, ) await chat.append_message_stream(response) diff --git a/examples/chat/RAG/recipes/requirements.txt b/examples/chat/RAG/recipes/requirements.txt index 54b14a013..c74ab2595 100644 --- a/examples/chat/RAG/recipes/requirements.txt +++ b/examples/chat/RAG/recipes/requirements.txt @@ -1,4 +1,5 @@ aiohttp -bs4 +beautifulsoup4 +chatlas openai shiny diff --git a/examples/chat/hello-world/app-core.py b/examples/chat/hello-world/app-core.py index 17d8395fb..5c088fcaf 100644 --- a/examples/chat/hello-world/app-core.py +++ b/examples/chat/hello-world/app-core.py @@ -11,7 +11,7 @@ """ Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will simply repeat it back to you. For more examples, see this - [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/examples/chat). + [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/shiny/templates/chat). """ ) @@ -21,11 +21,9 @@ def server(input, output, session): # Define a callback to run when the user submits a message @chat.on_user_submit - async def _(): - # Get the user's input - user = chat.user_input() + async def handle_user_input(user_input: str): # Append a response to the chat - await chat.append_message(f"You said: {user}") + await chat.append_message(f"You said: {user_input}") app = App(app_ui, server) diff --git a/examples/chat/hello-world/app.py b/examples/chat/hello-world/app.py index 3eabe7c00..43fb202d5 100644 --- a/examples/chat/hello-world/app.py +++ b/examples/chat/hello-world/app.py @@ -12,7 +12,7 @@ """ Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will simply repeat it back to you. For more examples, see this - [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/examples/chat). + [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/shiny/templates/chat). """ ) @@ -28,8 +28,6 @@ # Define a callback to run when the user submits a message @chat.on_user_submit -async def _(): - # Get the user's input - user = chat.user_input() +async def handle_user_input(user_input: str): # Append a response to the chat - await chat.append_message(f"You said: {user}") + await chat.append_message(f"You said: {user_input}") diff --git a/examples/chat/playground/app.py b/examples/chat/playground/app.py index af205824b..cd43179b3 100644 --- a/examples/chat/playground/app.py +++ b/examples/chat/playground/app.py @@ -1,19 +1,18 @@ # ------------------------------------------------------------------------------------ -# A Shiny Chat example showing how to use different language models via LangChain. +# A Shiny Chat example showing how to use different language models via chatlas. # To run it with all the different providers/models, you'll need API keys for each. # Namely, OPENAI_API_KEY, ANTHROPIC_API_KEY, and GOOGLE_API_KEY. -# To see how to get these keys, see the relevant basic examples. -# (i.e., ../basic/openai/app.py, ../basic/anthropic/app.py, ../basic/gemini/app.py) +# To see how to get these keys, see chatlas' reference: +# https://posit-dev.github.io/chatlas/reference/ # ------------------------------------------------------------------------------------ -from langchain_anthropic import ChatAnthropic -from langchain_google_vertexai import VertexAI -from langchain_openai import ChatOpenAI +import chatlas as ctl +from shiny import reactive from shiny.express import input, render, ui models = { - "openai": ["gpt-4o", "gpt-3.5-turbo"], + "openai": ["gpt-4o-mini", "gpt-4o"], "claude": [ "claude-3-opus-latest", "claude-3-5-sonnet-latest", @@ -32,6 +31,7 @@ fillable_mobile=True, ) +# Sidebar with input controls with ui.sidebar(position="right"): ui.input_select("model", "Model", choices=model_choices) ui.input_select( @@ -39,44 +39,65 @@ "Response style", choices=["Chuck Norris", "Darth Vader", "Yoda", "Gandalf", "Sherlock Holmes"], ) - ui.input_switch("stream", "Stream", value=False) + ui.input_switch("stream", "Stream", value=True) ui.input_slider("temperature", "Temperature", min=0, max=2, step=0.1, value=1) ui.input_slider("max_tokens", "Max Tokens", min=1, max=4096, step=1, value=100) + ui.input_action_button("clear", "Clear chat") +# The chat component +chat = ui.Chat(id="chat") +chat.ui(width="100%") -@render.express(fill=True, fillable=True) -def chat_ui(): - system_message = { - "content": f""" - You are a helpful AI assistant. Provide answers in the style of {input.system_actor()}. - """, - "role": "system", - } - chat = ui.Chat(id="chat", messages=[system_message]) +@reactive.calc +def get_model(): model_params = { + "system_prompt": ( + "You are a helpful AI assistant. " + f" Provide answers in the style of {input.system_actor()}." + ), "model": input.model(), - "temperature": input.temperature(), - "max_tokens": input.max_tokens(), } if input.model() in models["openai"]: - llm = ChatOpenAI(**model_params) + chat_model = ctl.ChatOpenAI(**model_params) elif input.model() in models["claude"]: - llm = ChatAnthropic(**model_params) + chat_model = ctl.ChatAnthropic(**model_params) elif input.model() in models["google"]: - llm = VertexAI(**model_params) + chat_model = ctl.ChatGoogle(**model_params) else: raise ValueError(f"Invalid model: {input.model()}") - @chat.on_user_submit - async def _(): - messages = chat.messages(format="langchain") - if input.stream(): - response = llm.astream(messages) - await chat.append_message_stream(response) - else: - response = await llm.ainvoke(messages) - await chat.append_message(response) - - chat.ui() + return chat_model + + +@reactive.calc +def chat_params(): + if input.model() in models["google"]: + return { + "generation_config": { + "temperature": input.temperature(), + "max_output_tokens": input.max_tokens(), + } + } + else: + return { + "temperature": input.temperature(), + "max_tokens": input.max_tokens(), + } + + +@chat.on_user_submit +async def handle_user_input(user_input: str): + if input.stream(): + response = get_model().stream(user_input, kwargs=chat_params()) + await chat.append_message_stream(response) + else: + response = get_model().chat(user_input, echo="none", kwargs=chat_params()) + await chat.append_message(response) + + +@reactive.effect +@reactive.event(input.clear) +def _(): + chat.clear_messages() diff --git a/examples/chat/playground/requirements.txt b/examples/chat/playground/requirements.txt index 29bb799b8..4cec5d5bb 100644 --- a/examples/chat/playground/requirements.txt +++ b/examples/chat/playground/requirements.txt @@ -1,4 +1,6 @@ -langchain_anthropic -langchain_google_vertexai -langchain_openai +chatlas +openai +anthropic +google-generativeai +python-dotenv shiny diff --git a/examples/chat/sidebar-dark/app.py b/examples/chat/sidebar-dark/app.py new file mode 100644 index 000000000..7b3e583da --- /dev/null +++ b/examples/chat/sidebar-dark/app.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------- +# This example demonstrates Shiny Chat's dark mode capability. +# -------------------------------------------------------------------------------- + +from shiny.express import ui + +# Page options with a dark mode toggle +ui.page_opts( + title=ui.tags.div( + "Hello Dark mode", + ui.input_dark_mode(mode="dark"), + class_="d-flex justify-content-between w-100", + ), + fillable=True, + fillable_mobile=True, +) + +# An empty, closed, sidebar +with ui.sidebar(width=300, style="height:100%", position="right"): + chat = ui.Chat(id="chat", messages=["Welcome to the dark side!"]) + chat.ui() + + +# Define a callback to run when the user submits a message +@chat.on_user_submit +async def handle_user_input(user_input: str): + await chat.append_message_stream(f"You said: {user_input}") + + +"Lorem ipsum dolor sit amet, consectetur adipiscing elit" diff --git a/examples/chat/ui/clear/app.py b/examples/chat/ui/clear/app.py deleted file mode 100644 index fb4ac8137..000000000 --- a/examples/chat/ui/clear/app.py +++ /dev/null @@ -1,49 +0,0 @@ -# -------------------------------------------------------------------------------- -# This example demonstrates how to clear the chat when the model changes. -# To run it, you'll need an OpenAI API key. -# To get one, follow the instructions at https://platform.openai.com/docs/quickstart -# -------------------------------------------------------------------------------- -import os - -from langchain_openai import ChatOpenAI - -from shiny import reactive -from shiny.express import input, ui - -# Provide your API key here (or set the environment variable) -llm = ChatOpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), # type: ignore -) - -# Set some Shiny page options -ui.page_opts( - title="Hello OpenAI Chat", - fillable=True, - fillable_mobile=True, -) - -# Create a sidebar to select the model -with ui.sidebar(): - ui.input_select("model", "Model", ["gpt-4o", "gpt-3.5-turbo"]) - -# Create and display an empty chat UI -chat = ui.Chat(id="chat") -chat.ui() - - -# Define a callback to run when the user submits a message -@chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="langchain") - # Create a response message stream - response = llm.astream(messages) - # Append the response stream into the chat - await chat.append_message_stream(response) - - -# Clear the chat when the model changes -@reactive.effect -@reactive.event(input.model) -async def _(): - await chat.clear_messages() diff --git a/examples/chat/ui/clear/requirements.txt b/examples/chat/ui/clear/requirements.txt deleted file mode 100644 index 74bdc42cd..000000000 --- a/examples/chat/ui/clear/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -langchain_openai -shiny diff --git a/examples/chat/ui/dark/app.py b/examples/chat/ui/dark/app.py deleted file mode 100644 index 6ee93f343..000000000 --- a/examples/chat/ui/dark/app.py +++ /dev/null @@ -1,41 +0,0 @@ -# -------------------------------------------------------------------------------- -# This example demonstrates Shiny Chat's dark mode capability. -# To run it, you'll need an OpenAI API key. -# To get one, follow the instructions at https://platform.openai.com/docs/quickstart -# -------------------------------------------------------------------------------- -import os - -from langchain_openai import ChatOpenAI - -from shiny.express import ui - -# Provide your API key here (or set the environment variable) -llm = ChatOpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), # type: ignore -) - -# Set some Shiny page options -ui.page_opts( - title="Hello dark mode!", - fillable=True, - fillable_mobile=True, -) - -# Create a sidebar to select the dark mode -with ui.sidebar(open="closed", position="right", width="100px"): - ui.tags.label("Dark mode", ui.input_dark_mode(mode="dark")) - -# Create and display an empty chat UI -chat = ui.Chat(id="chat") -chat.ui() - - -# Define a callback to run when the user submits a message -@chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="langchain") - # Create a response message stream - stream = llm.astream(messages) - # Append the response stream into the chat - await chat.append_message_stream(stream) diff --git a/examples/chat/ui/dark/requirements.txt b/examples/chat/ui/dark/requirements.txt deleted file mode 100644 index 74bdc42cd..000000000 --- a/examples/chat/ui/dark/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -langchain_openai -shiny diff --git a/examples/chat/ui/dynamic/app.py b/examples/chat/ui/dynamic/app.py deleted file mode 100644 index b3a1597ca..000000000 --- a/examples/chat/ui/dynamic/app.py +++ /dev/null @@ -1,40 +0,0 @@ -# ----------------------------------------------------------------------------- -# A basic example of dynamically re-rendering a Shiny Chat instance with different models. -# To run it, you'll need an OpenAI API key. -# To get one, follow the instructions at https://platform.openai.com/docs/quickstart -# ----------------------------------------------------------------------------- -import os - -from langchain_openai import ChatOpenAI - -from shiny.express import input, render, ui - -ui.input_select("model", "Model", choices=["gpt-4o", "gpt-3.5-turbo"]) - - -@render.express -def chat_ui(): - - chat = ui.Chat( - id="chat", - messages=[ - { - "content": f"Hi! I'm a {input.model()} model. How can I help you today?", - "role": "assistant", - } - ], - ) - - chat.ui() - - llm = ChatOpenAI( - model=input.model(), - # Provide your API key here (or set the environment variable) - api_key=os.environ.get("OPENAI_API_KEY"), # type: ignore - ) - - @chat.on_user_submit - async def _(): - messages = chat.messages(format="langchain") - response = llm.astream(messages) - await chat.append_message_stream(response) diff --git a/examples/chat/ui/dynamic/requirements.txt b/examples/chat/ui/dynamic/requirements.txt deleted file mode 100644 index 74bdc42cd..000000000 --- a/examples/chat/ui/dynamic/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -langchain_openai -shiny diff --git a/examples/chat/ui/sidebar/app.py b/examples/chat/ui/sidebar/app.py deleted file mode 100644 index 84eab2bc7..000000000 --- a/examples/chat/ui/sidebar/app.py +++ /dev/null @@ -1,48 +0,0 @@ -# ----------------------------------------------------------------------------- -# An example of placing a Shiny Chat instance in a sidebar (and having it fill the sidebar). -# To run it, you'll need an OpenAI API key. -# To get one, follow the instructions at https://platform.openai.com/docs/quickstart -# ----------------------------------------------------------------------------- -import os - -from langchain_openai import ChatOpenAI - -from shiny.express import ui - -# Provide your API key here (or set the environment variable) -llm = ChatOpenAI( - api_key=os.environ.get("OPENAI_API_KEY"), # type: ignore -) - -# Set some Shiny page options -ui.page_opts( - title="Hello Sidebar Chat", - fillable=True, - fillable_mobile=True, -) - -# Create a chat instance, with an initial message -chat = ui.Chat( - id="chat", - messages=[ - {"content": "Hello! How can I help you today?", "role": "assistant"}, - ], -) - -# Display the chat in a sidebar -with ui.sidebar(width=300, style="height:100%", position="right"): - chat.ui(height="100%") - - -# Define a callback to run when the user submits a message -@chat.on_user_submit -async def _(): - # Get messages currently in the chat - messages = chat.messages(format="langchain") - # Create a response message stream - response = llm.astream(messages) - # Append the response stream into the chat - await chat.append_message_stream(response) - - -"Lorem ipsum dolor sit amet, consectetur adipiscing elit" diff --git a/examples/chat/ui/sidebar/requirements.txt b/examples/chat/ui/sidebar/requirements.txt deleted file mode 100644 index 74bdc42cd..000000000 --- a/examples/chat/ui/sidebar/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -langchain_openai -shiny From f68ca3f1243f06af434426ba1d7d4429a61bb455 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 16:20:56 -0600 Subject: [PATCH 4/8] Migrate chat examples (that are worth keeping) to templates; and give them a facelift --- examples/chat/.gitignore | 1 - examples/chat/RAG/recipes/app.py | 57 ---------- examples/chat/RAG/recipes/requirements.txt | 5 - examples/chat/RAG/recipes/utils.py | 106 ------------------ examples/chat/README.md | 21 ---- shiny/_main_create.py | 31 +++-- shiny/api-examples/chat/app-core.py | 31 ----- shiny/api-examples/chat/app-express.py | 35 ------ .../aws-bedrock-anthropic/_template.json | 0 .../aws-bedrock-anthropic/app.py | 0 .../aws-bedrock-anthropic/app_utils.py | 0 .../aws-bedrock-anthropic/requirements.txt | 0 .../azure-openai/_template.json | 0 .../azure-openai/app.py | 0 .../azure-openai/app_utils.py | 0 .../azure-openai/requirements.txt | 0 .../anthropic/_template.json | 0 .../anthropic/app.py | 0 .../anthropic/app_utils.py | 0 .../anthropic/requirements.txt | 0 .../google/_template.json | 0 .../{hello-providers => llms}/google/app.py | 0 .../google/app_utils.py | 0 .../google/requirements.txt | 0 .../langchain/_template.json | 0 .../langchain/app.py | 0 .../langchain/app_utils.py | 0 .../langchain/requirements.txt | 0 .../ollama/_template.json | 0 .../{hello-providers => llms}/ollama/app.py | 0 .../ollama/requirements.txt | 0 .../openai/_template.json | 0 .../{hello-providers => llms}/openai/app.py | 0 .../openai/app_utils.py | 0 .../openai/requirements.txt | 0 .../chat/llms/playground/_template.json | 5 + .../templates/chat/llms}/playground/app.py | 5 +- .../chat/llms/playground/app_utils.py | 26 +++++ .../chat/llms}/playground/requirements.txt | 0 .../chat/starters/hello}/app-core.py | 0 .../templates/chat/starters/hello}/app.py | 0 .../chat/starters/hello}/requirements.txt | 0 .../chat/starters}/sidebar-dark/app.py | 0 shiny/ui/_chat.py | 4 +- 44 files changed, 56 insertions(+), 271 deletions(-) delete mode 100644 examples/chat/.gitignore delete mode 100644 examples/chat/RAG/recipes/app.py delete mode 100644 examples/chat/RAG/recipes/requirements.txt delete mode 100644 examples/chat/RAG/recipes/utils.py delete mode 100644 examples/chat/README.md delete mode 100644 shiny/api-examples/chat/app-core.py delete mode 100644 shiny/api-examples/chat/app-express.py rename shiny/templates/chat/{enterprise => llm-enterprise}/aws-bedrock-anthropic/_template.json (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/aws-bedrock-anthropic/app.py (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/aws-bedrock-anthropic/app_utils.py (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/aws-bedrock-anthropic/requirements.txt (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/azure-openai/_template.json (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/azure-openai/app.py (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/azure-openai/app_utils.py (100%) rename shiny/templates/chat/{enterprise => llm-enterprise}/azure-openai/requirements.txt (100%) rename shiny/templates/chat/{hello-providers => llms}/anthropic/_template.json (100%) rename shiny/templates/chat/{hello-providers => llms}/anthropic/app.py (100%) rename shiny/templates/chat/{hello-providers => llms}/anthropic/app_utils.py (100%) rename shiny/templates/chat/{hello-providers => llms}/anthropic/requirements.txt (100%) rename shiny/templates/chat/{hello-providers => llms}/google/_template.json (100%) rename shiny/templates/chat/{hello-providers => llms}/google/app.py (100%) rename shiny/templates/chat/{hello-providers => llms}/google/app_utils.py (100%) rename shiny/templates/chat/{hello-providers => llms}/google/requirements.txt (100%) rename shiny/templates/chat/{hello-providers => llms}/langchain/_template.json (100%) rename shiny/templates/chat/{hello-providers => llms}/langchain/app.py (100%) rename shiny/templates/chat/{hello-providers => llms}/langchain/app_utils.py (100%) rename shiny/templates/chat/{hello-providers => llms}/langchain/requirements.txt (100%) rename shiny/templates/chat/{hello-providers => llms}/ollama/_template.json (100%) rename shiny/templates/chat/{hello-providers => llms}/ollama/app.py (100%) rename shiny/templates/chat/{hello-providers => llms}/ollama/requirements.txt (100%) rename shiny/templates/chat/{hello-providers => llms}/openai/_template.json (100%) rename shiny/templates/chat/{hello-providers => llms}/openai/app.py (100%) rename shiny/templates/chat/{hello-providers => llms}/openai/app_utils.py (100%) rename shiny/templates/chat/{hello-providers => llms}/openai/requirements.txt (100%) create mode 100644 shiny/templates/chat/llms/playground/_template.json rename {examples/chat => shiny/templates/chat/llms}/playground/app.py (97%) create mode 100644 shiny/templates/chat/llms/playground/app_utils.py rename {examples/chat => shiny/templates/chat/llms}/playground/requirements.txt (100%) rename {examples/chat/hello-world => shiny/templates/chat/starters/hello}/app-core.py (100%) rename {examples/chat/hello-world => shiny/templates/chat/starters/hello}/app.py (100%) rename {examples/chat/hello-world => shiny/templates/chat/starters/hello}/requirements.txt (100%) rename {examples/chat => shiny/templates/chat/starters}/sidebar-dark/app.py (100%) diff --git a/examples/chat/.gitignore b/examples/chat/.gitignore deleted file mode 100644 index 4c49bd78f..000000000 --- a/examples/chat/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env diff --git a/examples/chat/RAG/recipes/app.py b/examples/chat/RAG/recipes/app.py deleted file mode 100644 index 7b20eac31..000000000 --- a/examples/chat/RAG/recipes/app.py +++ /dev/null @@ -1,57 +0,0 @@ -# ------------------------------------------------------------------------------------ -# A simple recipe extractor chatbot that extracts recipes from URLs using the OpenAI API. -# To run it, you'll need an OpenAI API key. -# To get one, follow the instructions at https://platform.openai.com/docs/quickstart -# ------------------------------------------------------------------------------------ -import os - -from chatlas import ChatOpenAI -from utils import recipe_prompt, scrape_page_with_url - -from shiny.express import ui - -# Provide your API key here (or set the environment variable) -chat_model = ChatOpenAI( - system_prompt=recipe_prompt, - api_key=os.environ.get("OPENAI_API_KEY"), -) - -# Set some Shiny page options -ui.page_opts( - title="Recipe Extractor Chat", - fillable=True, - fillable_mobile=True, -) - -# Initialize the chat (with a system prompt and starting message) -chat = ui.Chat( - id="chat", - messages=[ - "Hello! I'm a recipe extractor. Please enter a URL to a recipe page. For example, " - ], -) - -chat.ui(placeholder="Enter a recipe URL...") - - -# A function to transform user input -# Note that, if an exception occurs, the function will return a message to the user -# "short-circuiting" the conversation and asking the user to try again. -@chat.transform_user_input -async def try_scrape_page(input: str) -> str | None: - try: - return await scrape_page_with_url(input) - except Exception: - await chat.append_message( - "I'm sorry, I couldn't extract content from that URL. Please try again. " - ) - return None - - -@chat.on_user_submit -async def handle_user_input(user_input: str): - response = chat_model.stream( - user_input, - kwargs={"temperature": 0}, - ) - await chat.append_message_stream(response) diff --git a/examples/chat/RAG/recipes/requirements.txt b/examples/chat/RAG/recipes/requirements.txt deleted file mode 100644 index c74ab2595..000000000 --- a/examples/chat/RAG/recipes/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -aiohttp -beautifulsoup4 -chatlas -openai -shiny diff --git a/examples/chat/RAG/recipes/utils.py b/examples/chat/RAG/recipes/utils.py deleted file mode 100644 index 9c522cf32..000000000 --- a/examples/chat/RAG/recipes/utils.py +++ /dev/null @@ -1,106 +0,0 @@ -import aiohttp -from bs4 import BeautifulSoup - -recipe_prompt = """ -You are RecipeExtractorGPT. -Your goal is to extract recipe content from text and return a JSON representation of the useful information. - -The JSON should be structured like this: - -``` -{ - "title": "Scrambled eggs", - "ingredients": { - "eggs": "2", - "butter": "1 tbsp", - "milk": "1 tbsp", - "salt": "1 pinch" - }, - "directions": [ - "Beat eggs, milk, and salt together in a bowl until thoroughly combined.", - "Heat butter in a large skillet over medium-high heat. Pour egg mixture into the hot skillet; cook and stir until eggs are set, 3 to 5 minutes." - ], - "servings": 2, - "prep_time": 5, - "cook_time": 5, - "total_time": 10, - "tags": [ - "breakfast", - "eggs", - "scrambled" - ], - "source": "https://recipes.com/scrambled-eggs/", -} -``` - -The user will provide text content from a web page. -It is not very well structured, but the recipe is in there. -Please look carefully for the useful information about the recipe. -IMPORTANT: Return the result as JSON in a Markdown code block surrounded with three backticks! -""" - - -async def scrape_page_with_url(url: str, max_length: int = 14000) -> str: - """ - Given a URL, scrapes the web page and return the contents. This also adds adds the - URL to the beginning of the text. - - Parameters - ---------- - url: - The URL to scrape - max_length: - Max length of recipe text to process. This is to prevent the model from running - out of tokens. 14000 bytes translates to approximately 3200 tokens. - """ - contents = await scrape_page(url) - # Trim the string so that the prompt and reply will fit in the token limit.. It - # would be better to trim by tokens, but that requires using the tiktoken package, - # which can be very slow to load when running on containerized servers, because it - # needs to download the model from the internet each time the container starts. - contents = contents[:max_length] - return f"From: {url}\n\n" + contents - - -async def scrape_page(url: str) -> str: - # Asynchronously send an HTTP request to the URL. - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status != 200: - raise aiohttp.ClientError(f"An error occurred: {response.status}") - html = await response.text() - - # Parse the HTML content using BeautifulSoup - soup = BeautifulSoup(html, "html.parser") - - # Remove script and style elements - for script in soup(["script", "style"]): - script.decompose() - - # List of element IDs or class names to remove - elements_to_remove = [ - "header", - "footer", - "sidebar", - "nav", - "menu", - "ad", - "advertisement", - "cookie-banner", - "popup", - "social", - "breadcrumb", - "pagination", - "comment", - "comments", - ] - - # Remove unwanted elements by ID or class name - for element in elements_to_remove: - for e in soup.find_all(id=element) + soup.find_all(class_=element): - e.decompose() - - # Extract text from the remaining HTML tags - text = " ".join(soup.stripped_strings) - - return text diff --git a/examples/chat/README.md b/examples/chat/README.md deleted file mode 100644 index b0c2f4907..000000000 --- a/examples/chat/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Shiny `Chat` examples - - -This folder contains a collection of examples illustrating `shiny.ui.Chat` usage. Many of them require API keys from providers such as OpenAI, Anthropic, etc. In those cases, the example should have commentary explaining how to obtain keys as well as how to provide them to the app. - -To get started with an app that doesn't require an API key, see the `hello-world` example. This example has both a Shiny Core and Express app to illustrate how it's used in either mode. - - ------------------------ - -## Apps - -* [hello-world](hello-world): A simple chat app that echoes back the user's input. -* [playground](playground): A playground for testing out different chat models: `openai`, `claude`, and `google`. -* RAG - * [recipes](RAG/recipes): A simple recipe extractor chatbot that extracts recipes from URLs using the OpenAI API. -* UI - * [clear](ui/clear): This example demonstrates how to clear the chat when the model changes. - * [dark](ui/dark): This example demonstrates Shiny Chat's dark mode capability. - * [dynamic](ui/dynamic): A basic example of dynamically re-rendering a Shiny Chat instance with different models. - * [sidebar](ui/sidebar): An example of placing a Shiny Chat instance in a sidebar (and having it fill the sidebar). diff --git a/shiny/_main_create.py b/shiny/_main_create.py index e00d59c12..b943e1158 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -224,12 +224,16 @@ def packages(self) -> list[ShinyTemplate]: return self._templates("templates/package") @property - def chat_hello_providers(self) -> list[ShinyTemplate]: - return self._templates("templates/chat/hello-providers") + def chat_starters(self) -> list[ShinyTemplate]: + return self._templates("templates/chat/starters") + + @property + def chat_llms(self) -> list[ShinyTemplate]: + return self._templates("templates/chat/llms") @property def chat_enterprise(self) -> list[ShinyTemplate]: - return self._templates("templates/chat/enterprise") + return self._templates("templates/chat/llm-enterprise") shiny_internal_templates = ShinyInternalTemplates() @@ -258,13 +262,14 @@ def use_internal_template( app_templates = shiny_internal_templates.apps pkg_templates = shiny_internal_templates.packages chat_templates = [ - *shiny_internal_templates.chat_hello_providers, + *shiny_internal_templates.chat_starters, + *shiny_internal_templates.chat_llms, *shiny_internal_templates.chat_enterprise, ] menu_choices = [ Choice(title="Custom JavaScript component...", value="_js-component"), - Choice(title="Generative AI templates...", value="_chat-ai"), + Choice(title="Chat component templates...", value="_chat"), Choice( title="Choose from the Shiny Templates website", value="_external-gallery" ), @@ -297,7 +302,7 @@ def use_internal_template( sys.exit(0) elif question_state == "_js-component": use_internal_package_template(dest_dir=dest_dir, package_name=package_name) - elif question_state == "_chat-ai": + elif question_state == "_chat": use_internal_chat_ai_template(dest_dir=dest_dir, package_name=package_name) else: valid_choices = [t.id for t in app_templates + pkg_templates] @@ -347,10 +352,11 @@ def use_internal_chat_ai_template( ): if input is None: input = questionary.select( - "Which kind of generative AI template would you like to use?", + "Which kind of chat template would you like?", choices=[ - Choice(title="By provider...", value="_chat-ai_hello-providers"), - Choice(title="Enterprise providers...", value="_chat-ai_enterprise"), + Choice(title="Chat starters...", value="_chat-starters"), + Choice(title="LLM powered chat...", value="_chat-llms"), + Choice(title="Enterprise LLM...", value="_chat-llm_enterprise"), back_choice, cancel_choice, ], @@ -369,10 +375,10 @@ def use_internal_chat_ai_template( ) return - if input == "_chat-ai_enterprise": + if input == "_chat-llm_enterprise": template_choices = shiny_internal_templates.chat_enterprise else: - template_choices = shiny_internal_templates.chat_hello_providers + template_choices = shiny_internal_templates.chat_llms choice = question_choose_template(template_choices, back_choice) @@ -382,7 +388,8 @@ def use_internal_chat_ai_template( template = template_by_name( [ - *shiny_internal_templates.chat_hello_providers, + *shiny_internal_templates.chat_starters, + *shiny_internal_templates.chat_llms, *shiny_internal_templates.chat_enterprise, ], choice, diff --git a/shiny/api-examples/chat/app-core.py b/shiny/api-examples/chat/app-core.py deleted file mode 100644 index 17d8395fb..000000000 --- a/shiny/api-examples/chat/app-core.py +++ /dev/null @@ -1,31 +0,0 @@ -from shiny import App, ui - -app_ui = ui.page_fillable( - ui.panel_title("Hello Shiny Chat"), - ui.chat_ui("chat"), - fillable_mobile=True, -) - -# Create a welcome message -welcome = ui.markdown( - """ - Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will - simply repeat it back to you. For more examples, see this - [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/examples/chat). - """ -) - - -def server(input, output, session): - chat = ui.Chat(id="chat", messages=[welcome]) - - # Define a callback to run when the user submits a message - @chat.on_user_submit - async def _(): - # Get the user's input - user = chat.user_input() - # Append a response to the chat - await chat.append_message(f"You said: {user}") - - -app = App(app_ui, server) diff --git a/shiny/api-examples/chat/app-express.py b/shiny/api-examples/chat/app-express.py deleted file mode 100644 index 3eabe7c00..000000000 --- a/shiny/api-examples/chat/app-express.py +++ /dev/null @@ -1,35 +0,0 @@ -from shiny.express import ui - -# Set some Shiny page options -ui.page_opts( - title="Hello Shiny Chat", - fillable=True, - fillable_mobile=True, -) - -# Create a welcome message -welcome = ui.markdown( - """ - Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will - simply repeat it back to you. For more examples, see this - [folder of examples](https://github.com/posit-dev/py-shiny/tree/main/examples/chat). - """ -) - -# Create a chat instance -chat = ui.Chat( - id="chat", - messages=[welcome], -) - -# Display it -chat.ui() - - -# Define a callback to run when the user submits a message -@chat.on_user_submit -async def _(): - # Get the user's input - user = chat.user_input() - # Append a response to the chat - await chat.append_message(f"You said: {user}") diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/_template.json similarity index 100% rename from shiny/templates/chat/enterprise/aws-bedrock-anthropic/_template.json rename to shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/_template.json diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py similarity index 100% rename from shiny/templates/chat/enterprise/aws-bedrock-anthropic/app.py rename to shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/app_utils.py b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app_utils.py similarity index 100% rename from shiny/templates/chat/enterprise/aws-bedrock-anthropic/app_utils.py rename to shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app_utils.py diff --git a/shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/requirements.txt similarity index 100% rename from shiny/templates/chat/enterprise/aws-bedrock-anthropic/requirements.txt rename to shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/requirements.txt diff --git a/shiny/templates/chat/enterprise/azure-openai/_template.json b/shiny/templates/chat/llm-enterprise/azure-openai/_template.json similarity index 100% rename from shiny/templates/chat/enterprise/azure-openai/_template.json rename to shiny/templates/chat/llm-enterprise/azure-openai/_template.json diff --git a/shiny/templates/chat/enterprise/azure-openai/app.py b/shiny/templates/chat/llm-enterprise/azure-openai/app.py similarity index 100% rename from shiny/templates/chat/enterprise/azure-openai/app.py rename to shiny/templates/chat/llm-enterprise/azure-openai/app.py diff --git a/shiny/templates/chat/enterprise/azure-openai/app_utils.py b/shiny/templates/chat/llm-enterprise/azure-openai/app_utils.py similarity index 100% rename from shiny/templates/chat/enterprise/azure-openai/app_utils.py rename to shiny/templates/chat/llm-enterprise/azure-openai/app_utils.py diff --git a/shiny/templates/chat/enterprise/azure-openai/requirements.txt b/shiny/templates/chat/llm-enterprise/azure-openai/requirements.txt similarity index 100% rename from shiny/templates/chat/enterprise/azure-openai/requirements.txt rename to shiny/templates/chat/llm-enterprise/azure-openai/requirements.txt diff --git a/shiny/templates/chat/hello-providers/anthropic/_template.json b/shiny/templates/chat/llms/anthropic/_template.json similarity index 100% rename from shiny/templates/chat/hello-providers/anthropic/_template.json rename to shiny/templates/chat/llms/anthropic/_template.json diff --git a/shiny/templates/chat/hello-providers/anthropic/app.py b/shiny/templates/chat/llms/anthropic/app.py similarity index 100% rename from shiny/templates/chat/hello-providers/anthropic/app.py rename to shiny/templates/chat/llms/anthropic/app.py diff --git a/shiny/templates/chat/hello-providers/anthropic/app_utils.py b/shiny/templates/chat/llms/anthropic/app_utils.py similarity index 100% rename from shiny/templates/chat/hello-providers/anthropic/app_utils.py rename to shiny/templates/chat/llms/anthropic/app_utils.py diff --git a/shiny/templates/chat/hello-providers/anthropic/requirements.txt b/shiny/templates/chat/llms/anthropic/requirements.txt similarity index 100% rename from shiny/templates/chat/hello-providers/anthropic/requirements.txt rename to shiny/templates/chat/llms/anthropic/requirements.txt diff --git a/shiny/templates/chat/hello-providers/google/_template.json b/shiny/templates/chat/llms/google/_template.json similarity index 100% rename from shiny/templates/chat/hello-providers/google/_template.json rename to shiny/templates/chat/llms/google/_template.json diff --git a/shiny/templates/chat/hello-providers/google/app.py b/shiny/templates/chat/llms/google/app.py similarity index 100% rename from shiny/templates/chat/hello-providers/google/app.py rename to shiny/templates/chat/llms/google/app.py diff --git a/shiny/templates/chat/hello-providers/google/app_utils.py b/shiny/templates/chat/llms/google/app_utils.py similarity index 100% rename from shiny/templates/chat/hello-providers/google/app_utils.py rename to shiny/templates/chat/llms/google/app_utils.py diff --git a/shiny/templates/chat/hello-providers/google/requirements.txt b/shiny/templates/chat/llms/google/requirements.txt similarity index 100% rename from shiny/templates/chat/hello-providers/google/requirements.txt rename to shiny/templates/chat/llms/google/requirements.txt diff --git a/shiny/templates/chat/hello-providers/langchain/_template.json b/shiny/templates/chat/llms/langchain/_template.json similarity index 100% rename from shiny/templates/chat/hello-providers/langchain/_template.json rename to shiny/templates/chat/llms/langchain/_template.json diff --git a/shiny/templates/chat/hello-providers/langchain/app.py b/shiny/templates/chat/llms/langchain/app.py similarity index 100% rename from shiny/templates/chat/hello-providers/langchain/app.py rename to shiny/templates/chat/llms/langchain/app.py diff --git a/shiny/templates/chat/hello-providers/langchain/app_utils.py b/shiny/templates/chat/llms/langchain/app_utils.py similarity index 100% rename from shiny/templates/chat/hello-providers/langchain/app_utils.py rename to shiny/templates/chat/llms/langchain/app_utils.py diff --git a/shiny/templates/chat/hello-providers/langchain/requirements.txt b/shiny/templates/chat/llms/langchain/requirements.txt similarity index 100% rename from shiny/templates/chat/hello-providers/langchain/requirements.txt rename to shiny/templates/chat/llms/langchain/requirements.txt diff --git a/shiny/templates/chat/hello-providers/ollama/_template.json b/shiny/templates/chat/llms/ollama/_template.json similarity index 100% rename from shiny/templates/chat/hello-providers/ollama/_template.json rename to shiny/templates/chat/llms/ollama/_template.json diff --git a/shiny/templates/chat/hello-providers/ollama/app.py b/shiny/templates/chat/llms/ollama/app.py similarity index 100% rename from shiny/templates/chat/hello-providers/ollama/app.py rename to shiny/templates/chat/llms/ollama/app.py diff --git a/shiny/templates/chat/hello-providers/ollama/requirements.txt b/shiny/templates/chat/llms/ollama/requirements.txt similarity index 100% rename from shiny/templates/chat/hello-providers/ollama/requirements.txt rename to shiny/templates/chat/llms/ollama/requirements.txt diff --git a/shiny/templates/chat/hello-providers/openai/_template.json b/shiny/templates/chat/llms/openai/_template.json similarity index 100% rename from shiny/templates/chat/hello-providers/openai/_template.json rename to shiny/templates/chat/llms/openai/_template.json diff --git a/shiny/templates/chat/hello-providers/openai/app.py b/shiny/templates/chat/llms/openai/app.py similarity index 100% rename from shiny/templates/chat/hello-providers/openai/app.py rename to shiny/templates/chat/llms/openai/app.py diff --git a/shiny/templates/chat/hello-providers/openai/app_utils.py b/shiny/templates/chat/llms/openai/app_utils.py similarity index 100% rename from shiny/templates/chat/hello-providers/openai/app_utils.py rename to shiny/templates/chat/llms/openai/app_utils.py diff --git a/shiny/templates/chat/hello-providers/openai/requirements.txt b/shiny/templates/chat/llms/openai/requirements.txt similarity index 100% rename from shiny/templates/chat/hello-providers/openai/requirements.txt rename to shiny/templates/chat/llms/openai/requirements.txt diff --git a/shiny/templates/chat/llms/playground/_template.json b/shiny/templates/chat/llms/playground/_template.json new file mode 100644 index 000000000..b753f492c --- /dev/null +++ b/shiny/templates/chat/llms/playground/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "id": "chat-ai-playground", + "title": "Chat Playground w/ OpenAI, Anthropic, and Google" +} diff --git a/examples/chat/playground/app.py b/shiny/templates/chat/llms/playground/app.py similarity index 97% rename from examples/chat/playground/app.py rename to shiny/templates/chat/llms/playground/app.py index cd43179b3..a8c3b0ebb 100644 --- a/examples/chat/playground/app.py +++ b/shiny/templates/chat/llms/playground/app.py @@ -7,9 +7,12 @@ # ------------------------------------------------------------------------------------ import chatlas as ctl +from app_utils import load_dotenv from shiny import reactive -from shiny.express import input, render, ui +from shiny.express import input, ui + +load_dotenv() models = { "openai": ["gpt-4o-mini", "gpt-4o"], diff --git a/shiny/templates/chat/llms/playground/app_utils.py b/shiny/templates/chat/llms/playground/app_utils.py new file mode 100644 index 000000000..404a13730 --- /dev/null +++ b/shiny/templates/chat/llms/playground/app_utils.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path +from typing import Any + +app_dir = Path(__file__).parent +env_file = app_dir / ".env" + + +def load_dotenv(dotenv_path: os.PathLike[str] = env_file, **kwargs: Any) -> None: + """ + A convenience wrapper around `dotenv.load_dotenv` that warns if `dotenv` is not installed. + It also returns `None` to make it easier to ignore the return value. + """ + try: + import dotenv + + dotenv.load_dotenv(dotenv_path=dotenv_path, **kwargs) + except ImportError: + import warnings + + warnings.warn( + "Could not import `dotenv`. If you want to use `.env` files to " + "load environment variables, please install it using " + "`pip install python-dotenv`.", + stacklevel=2, + ) diff --git a/examples/chat/playground/requirements.txt b/shiny/templates/chat/llms/playground/requirements.txt similarity index 100% rename from examples/chat/playground/requirements.txt rename to shiny/templates/chat/llms/playground/requirements.txt diff --git a/examples/chat/hello-world/app-core.py b/shiny/templates/chat/starters/hello/app-core.py similarity index 100% rename from examples/chat/hello-world/app-core.py rename to shiny/templates/chat/starters/hello/app-core.py diff --git a/examples/chat/hello-world/app.py b/shiny/templates/chat/starters/hello/app.py similarity index 100% rename from examples/chat/hello-world/app.py rename to shiny/templates/chat/starters/hello/app.py diff --git a/examples/chat/hello-world/requirements.txt b/shiny/templates/chat/starters/hello/requirements.txt similarity index 100% rename from examples/chat/hello-world/requirements.txt rename to shiny/templates/chat/starters/hello/requirements.txt diff --git a/examples/chat/sidebar-dark/app.py b/shiny/templates/chat/starters/sidebar-dark/app.py similarity index 100% rename from examples/chat/sidebar-dark/app.py rename to shiny/templates/chat/starters/sidebar-dark/app.py diff --git a/shiny/ui/_chat.py b/shiny/ui/_chat.py index 367b0e963..69f07f830 100644 --- a/shiny/ui/_chat.py +++ b/shiny/ui/_chat.py @@ -83,7 +83,7 @@ PendingMessage = Tuple[Any, ChunkOption, Union[str, None]] -@add_example(ex_dir="../api-examples/chat") +@add_example(ex_dir="../templates/chat/starters/hello") class Chat: """ Create a chat interface. @@ -1047,7 +1047,7 @@ async def _send_custom_message(self, handler: str, obj: ClientMessage | None): ) -@add_example(ex_dir="../api-examples/chat") +@add_example(ex_dir="../templates/chat/starters/hello") def chat_ui( id: str, *, From 2495be6f14d26d2c37d6ba4a089a1153f2e0662d Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 16:32:07 -0600 Subject: [PATCH 5/8] Bugfix --- shiny/_main_create.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shiny/_main_create.py b/shiny/_main_create.py index b943e1158..8156521df 100644 --- a/shiny/_main_create.py +++ b/shiny/_main_create.py @@ -375,10 +375,12 @@ def use_internal_chat_ai_template( ) return - if input == "_chat-llm_enterprise": - template_choices = shiny_internal_templates.chat_enterprise - else: + if input == "_chat-starters": + template_choices = shiny_internal_templates.chat_starters + elif input == "_chat-llms": template_choices = shiny_internal_templates.chat_llms + else: + template_choices = shiny_internal_templates.chat_enterprise choice = question_choose_template(template_choices, back_choice) From 0313ab8d38abebfcbc300142e9ad4908d39d2f37 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 16:38:08 -0600 Subject: [PATCH 6/8] Forgot the template json --- shiny/templates/chat/starters/hello/_template.json | 5 +++++ shiny/templates/chat/starters/sidebar-dark/_template.json | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 shiny/templates/chat/starters/hello/_template.json create mode 100644 shiny/templates/chat/starters/sidebar-dark/_template.json diff --git a/shiny/templates/chat/starters/hello/_template.json b/shiny/templates/chat/starters/hello/_template.json new file mode 100644 index 000000000..6cdfd1a2f --- /dev/null +++ b/shiny/templates/chat/starters/hello/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "id": "chat-hello", + "title": "Hello Shiny Chat" +} diff --git a/shiny/templates/chat/starters/sidebar-dark/_template.json b/shiny/templates/chat/starters/sidebar-dark/_template.json new file mode 100644 index 000000000..eb96af780 --- /dev/null +++ b/shiny/templates/chat/starters/sidebar-dark/_template.json @@ -0,0 +1,5 @@ +{ + "type": "app", + "id": "chat-sidebar-dark", + "title": "Chat in a sidebar with dark mode" +} From aefe95ce2beacbf86cb7cb2e479cb1edafd3fa45 Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 16:42:28 -0600 Subject: [PATCH 7/8] Chat should fill the sidebar --- shiny/templates/chat/starters/sidebar-dark/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/templates/chat/starters/sidebar-dark/app.py b/shiny/templates/chat/starters/sidebar-dark/app.py index 7b3e583da..57b29571c 100644 --- a/shiny/templates/chat/starters/sidebar-dark/app.py +++ b/shiny/templates/chat/starters/sidebar-dark/app.py @@ -18,7 +18,7 @@ # An empty, closed, sidebar with ui.sidebar(width=300, style="height:100%", position="right"): chat = ui.Chat(id="chat", messages=["Welcome to the dark side!"]) - chat.ui() + chat.ui(height="100%") # Define a callback to run when the user submits a message From d7f8a140ae6cbe19214b4ed64d181e40e8dfa5dc Mon Sep 17 00:00:00 2001 From: Carson Date: Fri, 20 Dec 2024 16:46:09 -0600 Subject: [PATCH 8/8] Consistently type the submit callback --- .../templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py | 2 +- shiny/templates/chat/llm-enterprise/azure-openai/app.py | 2 +- shiny/templates/chat/llms/anthropic/app.py | 2 +- shiny/templates/chat/llms/google/app.py | 2 +- shiny/templates/chat/llms/langchain/app.py | 2 +- shiny/templates/chat/llms/ollama/app.py | 2 +- shiny/templates/chat/llms/openai/app.py | 2 +- shiny/ui/_chat.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py index 67bc61ef1..d2916f85f 100644 --- a/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py +++ b/shiny/templates/chat/llm-enterprise/aws-bedrock-anthropic/app.py @@ -35,6 +35,6 @@ # Define a callback to run when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llm-enterprise/azure-openai/app.py b/shiny/templates/chat/llm-enterprise/azure-openai/app.py index 1bca65eb2..d6ba133cd 100644 --- a/shiny/templates/chat/llm-enterprise/azure-openai/app.py +++ b/shiny/templates/chat/llm-enterprise/azure-openai/app.py @@ -36,6 +36,6 @@ # Define a callback to run when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llms/anthropic/app.py b/shiny/templates/chat/llms/anthropic/app.py index 52f4c20ab..585dd5a58 100644 --- a/shiny/templates/chat/llms/anthropic/app.py +++ b/shiny/templates/chat/llms/anthropic/app.py @@ -36,6 +36,6 @@ # Generate a response when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llms/google/app.py b/shiny/templates/chat/llms/google/app.py index bd778f866..ed5b75248 100644 --- a/shiny/templates/chat/llms/google/app.py +++ b/shiny/templates/chat/llms/google/app.py @@ -32,6 +32,6 @@ # Generate a response when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llms/langchain/app.py b/shiny/templates/chat/llms/langchain/app.py index 17cd34f21..1c62ce399 100644 --- a/shiny/templates/chat/llms/langchain/app.py +++ b/shiny/templates/chat/llms/langchain/app.py @@ -37,6 +37,6 @@ # Define a callback to run when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llms/ollama/app.py b/shiny/templates/chat/llms/ollama/app.py index ee5852bd9..581050a98 100644 --- a/shiny/templates/chat/llms/ollama/app.py +++ b/shiny/templates/chat/llms/ollama/app.py @@ -28,6 +28,6 @@ # Generate a response when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/templates/chat/llms/openai/app.py b/shiny/templates/chat/llms/openai/app.py index a491c859f..07ff62bb2 100644 --- a/shiny/templates/chat/llms/openai/app.py +++ b/shiny/templates/chat/llms/openai/app.py @@ -36,6 +36,6 @@ # Generate a response when the user submits a message @chat.on_user_submit -async def handle_user_input(user_input): +async def handle_user_input(user_input: str): response = chat_model.stream(user_input) await chat.append_message_stream(response) diff --git a/shiny/ui/_chat.py b/shiny/ui/_chat.py index 69f07f830..61476cd48 100644 --- a/shiny/ui/_chat.py +++ b/shiny/ui/_chat.py @@ -105,7 +105,7 @@ class Chat: # Define a callback to run when the user submits a message @chat.on_user_submit - async def handle_user_input(user_input): + async def handle_user_input(user_input: str): # Create a response message stream response = await my_model.generate_response(user_input, stream=True) # Append the response into the chat