diff --git a/pkg-py/docs/greet.qmd b/pkg-py/docs/greet.qmd index 5750ae06..8ab59908 100644 --- a/pkg-py/docs/greet.qmd +++ b/pkg-py/docs/greet.qmd @@ -5,11 +5,17 @@ title: Greet users ### Provide a greeting -When the querychat UI first appears, you will usually want it to greet the user with some basic instructions. By default, these instructions are auto-generated every time a user arrives; this is slow, wasteful, and unpredictable. Instead, you should create a greeting file and pass it when creating your `QueryChat` object: +When the querychat UI first appears, you will usually want it to greet the user with some basic instructions. By default, these instructions are auto-generated every time a user arrives. In a production setting with multiple users/visitors, this is slow, wasteful, and non-deterministic. Instead, you should create a greeting file and pass it when creating your `QueryChat` object: -```python +```{.python filename="titanic-app.py"} +from querychat import QueryChat +from querychat.data import titanic from pathlib import Path -qc = QueryChat(titanic, "titanic", greeting=Path("greeting.md")) + +app_dir = Path(__file__).parent + +qc = QueryChat(titanic(), "titanic", greeting=app_dir / "greeting.md") +app = qc.app() ``` You can provide suggestions to the user by using the ` ` tag: @@ -33,13 +39,13 @@ You can see this behavior in our [`querychat template`](https://shiny.posit.co/p If you need help coming up with a greeting, you can use the `.generate_greeting()` method: -```python +```{.python filename="penguins-greeting.py"} from palmerpenguins import load_penguins from querychat import QueryChat +from pathlib import Path # Create QueryChat object with your dataset -penguins = load_penguins() -qc = QueryChat(penguins, "penguins") +qc = QueryChat(load_penguins(), "penguins") # Generate a greeting (this calls the LLM) greeting_text = qc.generate_greeting() @@ -50,9 +56,21 @@ greeting_text = qc.generate_greeting() # Save it for reuse with open("penguins_greeting.md", "w") as f: f.write(greeting_text) +``` + +This approach generates a greeting once and saves it for reuse, avoiding the latency and cost of generating it for every user. + +```{.python filename="penguins-app.py"} +from palmerpenguins import load_penguins +from querychat import QueryChat +from pathlib import Path # Then use the saved greeting in your app -qc = QueryChat(penguins, "penguins", greeting=Path("penguins_greeting.md")) +app_dir = Path(__file__).parent +qc = QueryChat( + load_penguins(), + "penguins", + greeting=app_dir / "penguins_greeting.md", +) +app = qc.app() ``` - -This approach generates a greeting once and saves it for reuse, avoiding the latency and cost of generating it for every user. \ No newline at end of file diff --git a/pkg-py/src/querychat/_querychat.py b/pkg-py/src/querychat/_querychat.py index 299ce2ec..a32ef934 100644 --- a/pkg-py/src/querychat/_querychat.py +++ b/pkg-py/src/querychat/_querychat.py @@ -3,7 +3,6 @@ import copy import os import re -import sys from pathlib import Path from typing import TYPE_CHECKING, Literal, Optional, overload @@ -17,7 +16,7 @@ from ._datasource import DataFrameSource, DataSource, SQLAlchemySource from ._icons import bs_icon -from ._querychat_module import ServerValues, mod_server, mod_ui +from ._querychat_module import GREETING_PROMPT, ServerValues, mod_server, mod_ui if TYPE_CHECKING: import pandas as pd @@ -48,14 +47,6 @@ def __init__( self.id = id or table_name - if greeting is None: - print( - "Warning: No greeting provided; the LLM will be invoked at conversation start to generate one. " - "For faster startup, lower cost, and determinism, please save a greeting and pass it to init().", - "You can also use `querychat.greeting()` to help generate a greeting.", - file=sys.stderr, - ) - self.greeting = greeting.read_text() if isinstance(greeting, Path) else greeting prompt = get_system_prompt( @@ -244,8 +235,7 @@ def generate_greeting(self, *, echo: Literal["none", "output"] = "none"): """ client = copy.deepcopy(self._client) client.set_turns([]) - prompt = "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list." - return str(client.chat(prompt, echo=echo)) + return str(client.chat(GREETING_PROMPT, echo=echo)) @property def system_prompt(self) -> str: diff --git a/pkg-py/src/querychat/_querychat_module.py b/pkg-py/src/querychat/_querychat_module.py index 7e8beb68..356ca22c 100644 --- a/pkg-py/src/querychat/_querychat_module.py +++ b/pkg-py/src/querychat/_querychat_module.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import warnings from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Callable, Union @@ -134,10 +135,14 @@ async def greet_on_startup(): if greeting: await chat_ui.append_message(greeting) elif greeting is None: - stream = await chat.stream_async( - "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list.", - echo="none", + warnings.warn( + "No greeting provided to `QueryChat()`. Using the LLM `client` to generate one now. " + "For faster startup, lower cost, and determinism, consider providing a greeting " + "to `QueryChat()` and `.generate_greeting()` to generate one beforehand.", + GreetWarning, + stacklevel=2, ) + stream = await chat.stream_async(GREETING_PROMPT, echo="none") await chat_ui.append_message_stream(stream) has_greeted.set(True) @@ -180,3 +185,10 @@ def _on_restore(x: RestoreState) -> None: has_greeted.set(vals["querychat_has_greeted"]) return ServerValues(df=filtered_df, sql=sql, title=title, client=chat) + + +GREETING_PROMPT: str = "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list." + + +class GreetWarning(Warning): + """Warning raised when no greeting is provided to QueryChat.""" diff --git a/pkg-r/R/QueryChat.R b/pkg-r/R/QueryChat.R index de998397..67cc0136 100644 --- a/pkg-r/R/QueryChat.R +++ b/pkg-r/R/QueryChat.R @@ -511,8 +511,7 @@ QueryChat <- R6::R6Class( chat <- private$.client$clone() chat$set_turns(list()) - prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list." - as.character(chat$chat(prompt, echo = echo)) + as.character(chat$chat(GREETING_PROMPT, echo = echo)) }, #' @description diff --git a/pkg-r/R/querychat_module.R b/pkg-r/R/querychat_module.R index b721a2ea..7496fedb 100644 --- a/pkg-r/R/querychat_module.R +++ b/pkg-r/R/querychat_module.R @@ -71,13 +71,11 @@ mod_server <- function( greeting_content <- if (!is.null(greeting) && any(nzchar(greeting))) { greeting } else { - # Generate greeting on the fly if none provided rlang::warn(c( - "No greeting provided; generating one now. This adds latency and cost.", - "i" = "Consider using $generate_greeting() to create a reusable greeting." + "No greeting provided to `QueryChat()`. Using the LLM `client` to generate one now.", + "i" = "For faster startup, lower cost, and determinism, consider providing a greeting to `QueryChat()` and `$generate_greeting()` to generate one beforehand." )) - prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list." - chat$stream_async(prompt) + chat$stream_async(GREETING_PROMPT) } shinychat::chat_append("chat", greeting_content) @@ -137,3 +135,6 @@ mod_server <- function( ) }) } + + +GREETING_PROMPT <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list."