From 2bd9b86d3affb23d39d0e9e67a76a9ded480c04c Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Wed, 18 Dec 2024 10:09:31 -0800 Subject: [PATCH 01/10] Chat feedback tutorial draft --- .../tutorials/llms/chat-response-feedback.md | 299 ++++++++++++++++++ content/menu.md | 2 + 2 files changed, 301 insertions(+) create mode 100644 content/develop/tutorials/llms/chat-response-feedback.md diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md new file mode 100644 index 000000000..61bc93e03 --- /dev/null +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -0,0 +1,299 @@ +--- +title: Collect user feedback about LLM responses +slug: /develop/tutorials/llms/chat-response-feedback +--- + +# Collect user feedback about LLM responses + +A common task in a chat app is to collect user feedback about an LLM's response. Streamlit includes `st.feedback` to conveniently collect user sentiment with a group of icon buttons. + +This tutorial uses Streamlit's chat commands along with `st.feedback` to build a simple chat app that collects user feedback about each response. + +## Applied concepts + +- Use `st.chat_input` and `st.chat_message` to create a chat interface. +- Use `st.feedback` to collect user sentiment about chat responses. + +## Prerequisites + +- The following must be installed in your Python environment: + + ```text + streamlit>=1.42.0 + ``` + +- You should have a clean working directory called `your-repository`. +- You should have a basic understanding of Session State. + +## Summary + +In this example, you'll build a chat interface. For simplicity, the chat app will echo the user's prompt within a fixed response. Each chat response will be followed by a feedback widget where the user can vote "thumbs up" or "thumbs down." In the code that follows, the feedback widget will prevent the user from changing their feedback after it's given, but you can modify this with a few simple code changes discussed at the end of this tutorial. + +Here's a look at what you'll build: + + + +```python +import streamlit as st +import time + +def chat_stream(prompt): + response = f'You said, "{prompt}" ...interesting.' + for char in response: + yield char + time.sleep(.02) + +def save_feedback(index): + st.session_state.history[index]["feedback"] = st.session_state[f"feedback_{index}"] + +if "history" not in st.session_state: + st.session_state.history = [] + +for i, message in enumerate(st.session_state.history): + with st.chat_message(message["role"]): + st.write(message["content"]) + if message["role"] == "assistant": + feedback = message.get("feedback", None) + st.session_state[f"feedback_{i}"] = feedback + st.feedback( + "thumbs", + key=f"feedback_{i}", + disabled=feedback is not None, + on_change=save_feedback, + args=[i] + ) + +if prompt := st.chat_input("Say something"): + with st.chat_message("user"): + st.write(prompt) + st.session_state.history.append({"role":"user","content":prompt}) + with st.chat_message("assistant"): + response = st.write_stream(chat_stream(prompt)) + st.feedback( + "thumbs", + key=f"feedback_{len(st.session_state.history)}", + on_change=save_feedback, + args=[len(st.session_state.history)] + ) + st.session_state.history.append({"role":"assistant","content":response}) +``` + + + + + +## Build the example + +### Initialize your app + +1. In `your_repository`, create a file named `app.py`. +1. In a terminal, change directories to `your_repository` and start your app. + + ```bash + streamlit run app.py + ``` + + Your app will be blank since you still need to add code. + +1. In `app.py`, write the following: + + ```python + import streamlit as st + import time + ``` + + You'll use `time` to build a simulated chat response stream. + +1. Save your `app.py` file and view your running app. +1. Click "**Always rerun**" or hit your "**A**" key in your running app. + + Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + +### Build a function to simulate a chat response stream + +To begin with, you'll define a function to stream a fixed chat response. + + + +```python +def chat_stream(prompt): + response = f'You said, "{prompt}" ...interesting.' + for char in response: + yield char + time.sleep(.02) +``` + + + +1. Define a function which accepts a prompt and formulates a response: + + ```python + def chat_stream(prompt): + response = f'You said, "{prompt}" ...interesting.' + ``` + +1. Loop through the characters and yield each one at 0.02-second intervals. + + ```python + for char in response: + yield char + time.sleep(.02) + ``` + +You now have a complete generator function to simulate a chat stream object. + +### Initialize and render your chat history + +To make your chat app stateful, you'll save the conversation history into Session State as a list of messages. Each message is a dictionary of message attributes. The dictionary keys include the following: + +- `"role"`: This has a value of `"user"` or `"assistant"` to indicate the source of the message. +- `"content"`: This is the body of the message as a string. +- `"feedback"`: This is an integer to indicate a user's feedback. This is only included with `"assistant"` messages after a user has submitted their feedback. + +1. Initialize the chat history in Session State. + + ```python + if "history" not in st.session_state: + st.session_state.history = [] + ``` + +1. Iterate through the messages in your chat history and render their contents in chat message containers. + + ```python + for i, message in enumerate(st.session_state.history): + with st.chat_message(message["role"]): + st.write(message["content"]) + ``` + + In a later step, you'll need to create unique keys for each assistant message. You can do this using the index of the message in your chat history. Therefore, use `enumerate()` to get an index along with each message dictionary. + +1. For each assistant message, check if feedback has been saved. + + ```python + if message["role"] == "assistant": + feedback = message.get("feedback", None) + ``` + + If no feedback is saved for the current message, the `.get()` method will return the declared default of `None`. + +1. Save the feedback value into Session State under a unique key for that message. + + ```python + st.session_state[f"feedback_{i}"] = feedback + ``` + + Since you have an index for the current message in the ordered chat history, you can use the index as the key. For readability, you can add the prefix of "feedback\_" to the index. + +1. Add a feedback widget to the chat message container. + + ```python + st.feedback( + "thumbs", + key=f"feedback_{i}", + disabled=feedback is not None, + ) + ``` + + The code you've written so far will show the chat history. For all chat responses that the user has already rated, the feedback widget will show the rating and be disabled. However, for all of the messages that are not yet rated, the associated feedback widget has no way to save that information into the chat history. To solve this, use a callback. When a user interacts with the widget, a callback will update the chat history before the app reruns. + +1. At the top of your app, after the definition of `chat_stream()` and before you initialize your chat history, define a function to use as a callback. + + ```python + def save_feedback(index): + st.session_state.history[index]["feedback"] = st.session_state[f"feedback_{index}"] + ``` + + The `save_feedback()` function accepts an index. It uses the index to get the associated widget value from Session State and update the associated message in your chat history. + +1. Add the callback and index argument to your `st.feedback` widget. + + ```diff + st.feedback( + "thumbs", + key=f"feedback_{i}", + disabled=feedback is not None, + + on_change=save_feedback, + + args=[i] + ) + ``` + +### Add chat input + +1. Accept the user's prompt for the `st.chat_input` widget, display it in a chat message container, and save it to the chat history. + + ```python + if prompt := st.chat_input("Say something"): + with st.chat_message("user"): + st.write(prompt) + st.session_state.history.append({"role":"user","content":prompt}) + ``` + + The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the preceding code displays the chat history. When this conditional block executes, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will be cleared from the widget and instead displayed as part of the history. + + The `:=` notation is shorthand to assign a variable within an expression. The following code is equivalent to the previous code in this step: + + ```python + prompt = st.chat_input("Say something") + if prompt: + with st.chat_message("user"): + st.write(prompt) + st.session_state.history.append({"role":"user","content":prompt}) + ``` + +1. Process the prompt and display the response in another chat message container along with a feedback widget. When the chat stream is finished, append the response to the chat history. + + ```python + with st.chat_message("assistant"): + response = st.write_stream(chat_stream(prompt)) + st.feedback( + "thumbs", + key=f"feedback_{len(st.session_state.history)}", + on_change=save_feedback, + args=[len(st.session_state.history)] + ) + st.session_state.history.append({"role":"assistant","content":response}) + ``` + + This is the same pattern used for the user's prompt. Within the body of the conditional block, the response is displayed and then added to the history. On the next rerun, this response will be display as a part of the history. + + When Streamlit executes the `st.feedback` command, the response is not yet added to the chat history. Use an index equal to the length of the chat history because that is the index that the response will have when it's added to the chat history on the next line. + +1. Save your file and go to your browser to try your new app. + +### (Optional) Change the feeback behavior + +At this point, the app allows users to rate any response once at any point in time. If you only want users to rate the most recent response, you can remove the widget from the chat-history loop: + +```python + for i, message in enumerate(st.session_state.history): + with st.chat_message(message["role"]): + st.write(message["content"]) +- if message["role"] == "assistant": +- feedback = message.get("feedback", None) +- st.session_state[f"feedback_{i}"] = feedback +- st.feedback( +- "thumbs", +- key=f"feedback_{i}", +- disabled=feedback is not None, +- on_change=save_feedback, +- args=[i] +- ) +``` + +Alternatively, if you want to allow users to change their responses, you can just remove the `disabled` parameter: + +```python + for i, message in enumerate(st.session_state.history): + with st.chat_message(message["role"]): + st.write(message["content"]) + if message["role"] == "assistant": + feedback = message.get("feedback", None) + st.session_state[f"feedback_{i}"] = feedback + st.feedback( + "thumbs", + key=f"feedback_{i}", +- disabled=feedback is not None, + on_change=save_feedback, + args=[i] + ) +``` diff --git a/content/menu.md b/content/menu.md index ce6fdea1f..5649c2440 100644 --- a/content/menu.md +++ b/content/menu.md @@ -693,6 +693,8 @@ site_menu: url: /develop/tutorials/llms/build-conversational-apps - category: Develop / Tutorials / Work with LLMs / Build an LLM app using LangChain url: /develop/tutorials/llms/llm-quickstart + - category: Develop / Tutorials / Work with LLMs / Get chat response feedback + url: /develop/tutorials/llms/chat-response-feedback - category: Develop / Quick reference url: /develop/quick-reference - category: Develop / Quick reference / Cheat sheet From 582d2a3f5fc237fdffa219942c27e3cdc124c9ef Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Wed, 18 Dec 2024 10:44:18 -0800 Subject: [PATCH 02/10] Typos --- content/develop/tutorials/llms/chat-response-feedback.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index 61bc93e03..c5b8beb9a 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -260,11 +260,11 @@ To make your chat app stateful, you'll save the conversation history into Sessio 1. Save your file and go to your browser to try your new app. -### (Optional) Change the feeback behavior +### Optional: Change the feeback behavior At this point, the app allows users to rate any response once at any point in time. If you only want users to rate the most recent response, you can remove the widget from the chat-history loop: -```python +```diff for i, message in enumerate(st.session_state.history): with st.chat_message(message["role"]): st.write(message["content"]) @@ -282,7 +282,7 @@ At this point, the app allows users to rate any response once at any point in ti Alternatively, if you want to allow users to change their responses, you can just remove the `disabled` parameter: -```python +```diff for i, message in enumerate(st.session_state.history): with st.chat_message(message["role"]): st.write(message["content"]) From a483ebb590106d508364a3101c7779908fceb732 Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Wed, 1 Jan 2025 10:47:22 -0800 Subject: [PATCH 03/10] Chat response editing draft --- content/develop/tutorials/llms/_index.md | 2 +- .../tutorials/llms/chat-response-feedback.md | 4 +- .../tutorials/llms/chat-response-revision.md | 550 ++++++++++++++++++ .../tutorials/llms/conversational-apps.md | 2 +- .../develop/tutorials/llms/llm-quickstart.md | 2 +- content/menu.md | 18 +- public/_redirects | 3 + 7 files changed, 568 insertions(+), 13 deletions(-) create mode 100644 content/develop/tutorials/llms/chat-response-revision.md diff --git a/content/develop/tutorials/llms/_index.md b/content/develop/tutorials/llms/_index.md index 7d1be65ba..3d699994d 100644 --- a/content/develop/tutorials/llms/_index.md +++ b/content/develop/tutorials/llms/_index.md @@ -1,6 +1,6 @@ --- title: Build LLM apps -slug: /develop/tutorials/llms +slug: /develop/tutorials/chat-and-llm-apps --- # Build LLM apps diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index c5b8beb9a..b42f99669 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -1,6 +1,6 @@ --- title: Collect user feedback about LLM responses -slug: /develop/tutorials/llms/chat-response-feedback +slug: /develop/tutorials/chat-and-llm-apps/chat-response-feedback --- # Collect user feedback about LLM responses @@ -105,7 +105,7 @@ if prompt := st.chat_input("Say something"): You'll use `time` to build a simulated chat response stream. 1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. In your browser, view your app, and click "**Always rerun**" or hit your "**A**" key. Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md new file mode 100644 index 000000000..762b10f73 --- /dev/null +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -0,0 +1,550 @@ +--- +title: Validate and edit chat responses +slug: /develop/tutorials/chat-and-llm-apps/validate-and-edit-chat-responses +--- + +# Validate and edit chat responses + +As you train LLM models, you may want users to correct or improve chat responses. With Streamlit, you can build a chat app that lets users improve chat responses. + +This tutorial uses Streamlit's chat commands to build a simple chat app that lets users modify chat responses to improve them. + +## Applied concepts + +- Use `st.chat_input` and `st.chat_message` to create a chat interface. +- Use Session State to manage stages of a process. + +## Prerequisites + +- The following must be installed in your Python environment: + + ```text + streamlit>=1.24.0 + ``` + +- You should have a clean working directory called `your-repository`. +- You should have a basic understanding of Session State. + +## Summary + +In this example, you'll build a chat interface. To avoid API calls, the app will include a generator function to simulate a chat stream object. When the chat assistant "responds," a validation function evaluates the response and highlights possible "errors" for the user to review. The user must accept, correct, or rewrite the response before proceeding. + +Here's a look at what you'll build: + + + +```python + +``` + + + + + +## Build the example + +### Initialize your app + +1. In `your_repository`, create a file named `app.py`. +1. In a terminal, change directories to `your_repository` and start your app. + + ```bash + streamlit run app.py + ``` + + Your app will be blank since you still need to add code. + +1. In `app.py`, write the following: + + ```python + import streamlit as st + import lorem + from random import randint + import time + ``` + + You'll use `lorem`, `random`, and `time` to build a simulated chat response stream. + +1. Save your `app.py` file and view your running app. +1. In your browser, view your app, and click "**Always rerun**" or hit your "**A**" key. + + Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + +### Build a function to simulate a chat response stream + +To begin with, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate four to ten random "sentences." + + + +```python +def chat_stream(): + for i in range(randint(3, 9)): + yield lorem.sentence() + " " + time.sleep(0.2) +``` + + + +1. Start a function for your simulated chat stream. + + ```python + def chat_stream(): + ``` + +1. Create a loop that executes four to ten times. + + ```python + for i in range(randint(3, 9)): + ``` + +1. Within the loop, yield a random sentence from `lorem` with a space at the end. + + ```python + yield lorem.sentence() + " " + time.sleep(0.2) + ``` + + In order to create a streaming effect, use `time.sleep` to create a small delay between yields. + +You now have a complete generator function to simulate a chat stream object. + +### Create a validation function + +The app will "validate" the streamed responses to assist users in identifying possible errors. To evaluate a response, you'll create a list of its sentences. Any sentence with fewer than five words will be marked as a potential error. This is an arbitrary standard applied for the sake of illustration. + +1. Define a function that accepts a string response and breaks it apart into segments. + + ```python + def validate(response): + response_segments = response.split(". ") + ``` + +1. Strip the segments of leading and trailing spaces and periods, and then restore a period to the end. + + ```python + response_segments = [ + segment.strip(". ") + "." + for segment in response_segments + if segment.strip(". ") != "" + ] + ``` + + Since the user will be modifying responses, `segment.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures each sentence ends with a single period. As added protection, `if segment.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. + +1. Create a boolean list of corresponding evaluations for the sentences. Use `True` for an approved sentence, and `False` for an unapproved sentence. + + ```python + validation_list = [ + True if sentence.count(" ") > 4 else False for sentence in response_segments + ] + ``` + + As stated before, a "good" sentence has at least five words. Use list comprehension to count the spaces in the sentence. + +1. Finally, return the segment and validation lists as a tuple. + + ```python + return response_segments, validation_list + ``` + +### Create a helper function to highlight text + +To show your validation results to your user, you can highlight sentences that are flagged for review. Create a helper function to add text and background color to the "errors." + +1. Define a function that accepts the list of sentences and list of evaluations. + + ```python + def add_highlights(response_segments, validation_list, bg="red", text="red"): + ``` + + For convenience, add parameters to set the text and background color, using a default of `"red"`. You'll use the function to highlight all errors in red immediately after validation. If the user chooses to step through the errors one by one, you'll highlight the errors in gray and show only one error in red. + +1. Use list comprehension to return a modified list of sentences that include the Markdown highlights. + + ```python + return [ + f":{text}[:{bg}-background[" + segment + "]]" if not is_valid else segment + for segment, is_valid in zip(response_segments, validation_list) + ] + ``` + +### Initialize and render your chat history + +Your app will use Session State to track the stages of the evaluation and correction process. + +1. Initialize Session State. + + ```python + if "stage" not in st.session_state: + st.session_state.stage = "user" + st.session_state.history = [] + st.session_state.pending = None + st.session_state.validation = {} + ``` + + - `st.session_state.stage` tracks where the user is in the multistage process. `"user"` means that the app is waiting for the user to enter a new prompt. Other values are `"validate"`, `"correct"`, and `"rewrite"`, which will be defined later. + - `st.session_state.history` stores the conversation history as a list of messages. Each message is a dictionary of message attributes (`"role"` and `"content"`). + - `st.session_state.pending` stores the next response before it is approved. + - `st.session_state.validation` stores the evaluation information for the pending response. This is dictionary with keys `"segments"` and `"valid"` to store the lists of sentences and their evaluation, respectively. + +1. Iterate through the messages in your chat history and render their contents in chat message containers. + + ```python + for message in st.session_state.history: + with st.chat_message(message["role"]): + st.write(message["content"]) + ``` + +### Define the `"user"` stage + +When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. + +1. Start a conditional block for the `"user"` stage. + + ```python + if st.session_state.stage == "user": + ``` + +1. Render a chat input widget and start a nested conditional block for its output. + + ```python + if user_input := st.chat_input("Enter a prompt"): + ``` + + This nested block won't execute until a user submits a prompt so this is effectively the end of the script before the user enters a prompt. + + The `:=` notation is shorthand to assign a variable within an expression. + +1. Append the user prompt to the chat history and render it in a chat message container. + + ```python + st.session_state.history.append({"role": "user", "content": user_input}) + with st.chat_message("user"): + st.write(user_input) + ``` + +1. After the user's chat message container, render the chat response in another chat message container. After the message is done streaming, save the pending message in Session State. + + ```python + with st.chat_message("assistant"): + response = st.write_stream(chat_stream()) + st.session_state.pending = response + ``` + +1. Update the stage to `"validate"` and rerun the app. + + ```python + st.session_state.stage = "validate" + st.rerun() + ``` + + When a user submits a new prompt, the script run will end here and a new one will begin. The next script run will use the `"validate"` stage which you'll build in the next section. + +### Define the `"validate"` stage + +When `st.session_state.stage` is `"validate"`, the app will validate the pending response and dispaly the results to the user. The user will then choose how to proceed (accept, correct, or rewrite the response). + +1. Start a conditional block for the `"validate"` stage. + + ```python + elif st.session_state.stage == "validate": + ``` + +1. Display a disabled chat input for visual consistency. + + ```python + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + ``` + + For the user's clarify, use placeholder to inform the user to review the pending response. + +1. Parse the response and highlight any errors using your helper functions. + + ```python + response_segments, validation_list = validate(st.session_state.pending) + highlighted_segments = add_highlights(response_segments, validation_list) + ``` + +1. Join the highlighted response segments and display them in a chat message container. To separate the response from the buttons that will follow, add a divider. + + ```python + with st.chat_message("assistant"): + st.markdown(" ".join(highlighted_segments)) + st.divider() + ``` + +1. To display the buttons in a row, create three columns. + + ```python + cols = st.columns(3) + ``` + +1. In the first column, start a conditional block and display a primary-type button to correct any errors. Disable the button if there are no detected errors. + + ```python + if cols[0].button( + "Correct errors", type="primary", disabled=all(validation_list) + ): + ``` + +1. Within the conditional block, save the validation information into Session State, update the stage, and then rerun the app. + + ```python + st.session_state.validation = { + "segments": response_segments, + "valid": validation_list, + } + st.session_state.stage = "correct" + st.rerun() + ``` + + If the user clicks the "**Correct**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and enter the `"correct"` stage. + +1. In the second column, start a conditional block and display a button to accept the response as-is. + + ```python + if cols[1].button("Accept"): + ``` + +1. Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. + + ```python + st.session_state.history.append( + {"role": "assistant", "content": st.session_state.pending} + ) + st.session_state.pending = None + st.session_state.validation = {} + ``` + +1. Update the stage to `"user"`, and rerun the app. + + ```python + st.session_state.stage = "user" + st.rerun() + ``` + + If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. + +1. In the third column, start a conditional block and display a tertiary-type button to rewrite the response. + + ```python + if cols[2].button("Rewrite answer", type="tertiary"): + ``` + +1. Within the conditional block, update the stage to `"rewrite"` and rerun the app. + + ```python + st.session_state.stage = "rewrite" + st.rerun() + ``` + + If the user clicks the "**Rewrite answer**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. + + You don't need to save any information into `st.session_state.validation` since the `"rewrite"` stage does not use this information. + +### Define the `"correct"` stage + +When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app highlights only the first error in the list. When a user addresses an error, it's removed from the list, and the next error becomes the first error for the next script run. This continues until all errors are cleared at which point the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. + +1. Start a conditional block for the `"correct"` stage. + + ```python + elif st.session_state.stage == "correct": + ``` + +1. Display a disabled chat input for visual consistency. + + ```python + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + ``` + +1. For convenience, retrieve the validation information from Session State and save it into variables. + + ```python + response_segments = st.session_state.validation["segments"] + validation_list = st.session_state.validation["valid"] + ``` + +1. Use your helper function to highlight the response segments with errors. Use gray for the highlight. + + ```python + highlighted_segments = add_highlights( + response_segments, validation_list, "gray", "gray" + ) + ``` + + In a following step, you'll change the highlight color for a single error for the user to address. + +1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated response segment. + + ```python + if not all(validation_list): + focus = validation_list.index(False) + highlighted_segments[focus] = ":red[:red" + highlighted_segments[focus][11:] + ``` + +1. Set a fallback value for `focus` if there are no errors. + + ```python + else: + focus = None + ``` + +1. In a chat message container, display the highlighted response. To separate the response from the buttons that will follow, add a divider. + + ```python + with st.chat_message("assistant"): + st.markdown(" ".join(highlighted_segments)) + st.divider() + ``` + +1. Start a conditional block: if there are errors, display a text input for the user to edit the first one (which you highlighted in red in a preceding step). + + ```python + if focus is not None: + new_segment = st.text_input( + "Replacement text:", value=response_segments[focus] + ) + ``` + + `value=response_segments[focus]` prefills the text input with the response segment associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it. + +1. To display buttons in a row, create two columns. + + ```python + cols = st.columns(2) + ``` + +1. In the first column, start a conditional block and display a primary-type button to update the response segment with the user's edits. Disable the button if text input is empty. + + ```python + if cols[0].button( + "Update", type="primary", disabled=len(new_segment.strip()) < 1 + ): + ``` + +1. Within the conditional block, update the response segment and mark it as valid in Session State. + + ```python + st.session_state.validation["segments"][focus] = ( + new_segment.strip(". ") + "." + ) + st.session_state.validation["valid"][focus] = True + ``` + +1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app. + + ```python + st.session_state.pending = " ".join( + st.session_state.validation["segments"] + ) + st.rerun() + ``` + + If the user clicks the "**Update**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. + +1. In the second column, start a conditional block and display a button to remove the response segment. Within the conditional block, pop response segment and validation information out of their lists in Session State, and rerun the app. + + ```python + if cols[1].button("Remove"): + st.session_state.validation["segments"].pop(focus) + st.session_state.validation["valid"].pop(focus) + st.rerun() + ``` + + If the user clicks the "**Remove**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. + +1. Define `else` for when there are no errors. To display buttons in a row, create two columns. + + ```python + else: + cols = st.columns(2) + ``` + + After a user has resolved all the errors, they need to confirm the final result. + +1. In the first column, start a conditional block and display a primary-type button to accept the current response. Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. + + ```python + if cols[0].button("Accept", type="primary"): + st.session_state.history.append( + {"role": "assistant", "content": st.session_state.pending} + ) + st.session_state.pending = None + st.session_state.validation = {} + ``` + +1. Update the stage to `"user"`, and rerun the app. + + ```python + st.session_state.stage = "user" + st.rerun() + ``` + + If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. + +1. In the second column, start a conditional block and display a button to rewrite the response. + + ```python + if cols[1].button("Re-validate"): + ``` + +1. Within the conditional block, clear the validation information from Session State, update the stage to `"validate"`, and rerun the app. + + ```python + st.session_state.validation = {} + st.session_state.stage = "validate" + st.rerun() + ``` + + If the user clicks the "**Rewrite answer**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. + +### Define the `"rewrite"` stage + +When `st.session_state.stage` is `"rewrite"`, the user can freely edit the response in a text area. + +1. Start a conditional block for the `"rewrite"` stage. + + ```python + elif st.session_state.stage == "rewrite": + ``` + +1. Display a disabled chat input for visual consistency. + + ```python + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + ``` + +1. In a chat message container, display a text area input for the user to edit the pending response. + + ```python + with st.chat_message("assistant"): + new = st.text_area("Rewrite the answer", value=st.session_state.pending) + ``` + + `value=st.session_state.pending` prefills the text area input with the pending response. The user can edit it or replace the text entirely. + +1. In the first column, start a conditional block and display a primary-type button to update the response with the user's edits. Disable the button if text area input is empty. + + ```python + if st.button( + "Update", type="primary", disabled=new is None or new.strip(". ") == "" + ): + ``` + +1. Within the conditional block, add the new response to the chat history, and clear the pending and validation information from Session State.. + + ```python + st.session_state.history.append({"role": "assistant", "content": new}) + st.session_state.pending = None + st.session_state.validation = {} + ``` + +1. Update the stage to `"user"`, and rerun the app. + + ```python + st.session_state.stage = "user" + st.rerun() + ``` + + If the user clicks the "**Update**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. diff --git a/content/develop/tutorials/llms/conversational-apps.md b/content/develop/tutorials/llms/conversational-apps.md index 46dc518df..a6fe65eeb 100644 --- a/content/develop/tutorials/llms/conversational-apps.md +++ b/content/develop/tutorials/llms/conversational-apps.md @@ -1,6 +1,6 @@ --- title: Build a basic LLM chat app -slug: /develop/tutorials/llms/build-conversational-apps +slug: /develop/tutorials/chat-and-llm-apps/build-conversational-apps --- # Build a basic LLM chat app diff --git a/content/develop/tutorials/llms/llm-quickstart.md b/content/develop/tutorials/llms/llm-quickstart.md index 7b0564732..38ea9440f 100644 --- a/content/develop/tutorials/llms/llm-quickstart.md +++ b/content/develop/tutorials/llms/llm-quickstart.md @@ -1,6 +1,6 @@ --- title: Build an LLM app using LangChain -slug: /develop/tutorials/llms/llm-quickstart +slug: /develop/tutorials/chat-and-llm-apps/llm-quickstart --- # Build an LLM app using LangChain diff --git a/content/menu.md b/content/menu.md index 5649c2440..a4f7587c1 100644 --- a/content/menu.md +++ b/content/menu.md @@ -687,14 +687,16 @@ site_menu: - category: Develop / Tutorials / Multipage apps / Build navigation with st.page_link url: /develop/tutorials/multipage/st.page_link-nav visible: false - - category: Develop / Tutorials / Work with LLMs - url: /develop/tutorials/llms - - category: Develop / Tutorials / Work with LLMs / Build a basic LLM chat app - url: /develop/tutorials/llms/build-conversational-apps - - category: Develop / Tutorials / Work with LLMs / Build an LLM app using LangChain - url: /develop/tutorials/llms/llm-quickstart - - category: Develop / Tutorials / Work with LLMs / Get chat response feedback - url: /develop/tutorials/llms/chat-response-feedback + - category: Develop / Tutorials / Chat & LLM apps + url: /develop/tutorials/chat-and-llm-apps + - category: Develop / Tutorials / Chat & LLM apps / Build a basic LLM chat app + url: /develop/tutorials/chat-and-llm-apps/build-conversational-apps + - category: Develop / Tutorials / Chat & LLM apps / Build an LLM app using LangChain + url: /develop/tutorials/chat-and-llm-apps/llm-quickstart + - category: Develop / Tutorials / Chat & LLM apps / Get chat response feedback + url: /develop/tutorials/chat-and-llm-apps/chat-response-feedback + - category: Develop / Tutorials / Chat & LLM apps / Validate & edit chat responses + url: /develop/tutorials/chat-and-llm-apps/validate-and-edit-chat-responses - category: Develop / Quick reference url: /develop/quick-reference - category: Develop / Quick reference / Cheat sheet diff --git a/public/_redirects b/public/_redirects index a5240a9fc..712353baf 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1143,6 +1143,9 @@ /knowledge-base/deploy/unable-to-edit-or-delete-apps-in-streamlit-community-cloud-after-modifying-github-username /deploy/streamlit-community-cloud/manage-your-app/rename-your-app /develop/tutorials/databases/deta-base /develop/tutorials/databases/ /develop/quick-reference/older-versions /develop/quick-reference/release-notes/2024 +/develop/tutorials/llms /develop/tutorials/chat-and-llm-apps +/develop/tutorials/llms/build-conversational-apps /develop/tutorials/chat-and-llm-apps/build-conversational-apps +/develop/tutorials/llms/llm-quickstart /develop/tutorials/chat-and-llm-apps/llm-quickstart # Deep links included in streamlit/streamlit source code /st.connections.snowflakeconnection-configuration /develop/api-reference/connections/st.connections.snowflakeconnection From 2cbb83457d2b67f8907c475851c5c6d80f0bf563 Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Thu, 9 Jan 2025 23:08:25 -0800 Subject: [PATCH 04/10] Update chat revision tutorial --- .../elements/charts/annotate-altair-chart.md | 8 +- .../elements/dataframes/row_selections.md | 8 +- .../create-a-multiple-container-fragment.md | 8 +- .../start-and-stop-fragment-auto-reruns.md | 8 +- ...ger-a-full-script-rerun-from-a-fragment.md | 8 +- .../tutorials/llms/chat-response-feedback.md | 8 +- .../tutorials/llms/chat-response-revision.md | 321 ++++++++++++++---- .../multipage-apps/dynamic-navigation.md | 8 +- 8 files changed, 279 insertions(+), 98 deletions(-) diff --git a/content/develop/tutorials/elements/charts/annotate-altair-chart.md b/content/develop/tutorials/elements/charts/annotate-altair-chart.md index b0c71e31c..01b6de678 100644 --- a/content/develop/tutorials/elements/charts/annotate-altair-chart.md +++ b/content/develop/tutorials/elements/charts/annotate-altair-chart.md @@ -117,7 +117,7 @@ st.altair_chart(combined_chart, use_container_width=True) ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -140,10 +140,10 @@ st.altair_chart(combined_chart, use_container_width=True) - You'll maniputate the data using `pandas`. - You'll define a chart using `altair`. -1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build the data layer diff --git a/content/develop/tutorials/elements/dataframes/row_selections.md b/content/develop/tutorials/elements/dataframes/row_selections.md index 59f1d9b08..c7cbc717a 100644 --- a/content/develop/tutorials/elements/dataframes/row_selections.md +++ b/content/develop/tutorials/elements/dataframes/row_selections.md @@ -135,7 +135,7 @@ Here's a look at what you'll build: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -159,10 +159,10 @@ Here's a look at what you'll build: - You'll generate random activity data with `numpy`. - You'll manipulate the data with `pandas`. -1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to create random member data diff --git a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md index 8497ec084..b4ec27e13 100644 --- a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md @@ -99,7 +99,7 @@ with st.sidebar: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -116,10 +116,10 @@ with st.sidebar: You'll use `time.sleep()` to slow things down and see the fragments working. -1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Frame out your app's containers diff --git a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md index 6349e6a65..9a3e5f2ad 100644 --- a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md +++ b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md @@ -102,7 +102,7 @@ show_latest_data() ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -125,10 +125,10 @@ show_latest_data() - You'll generate random data with `numpy`. - The data will have `datetime.datetime` index values. -1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to generate random, recent data diff --git a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md index 4e35e6150..e3b972c70 100644 --- a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md @@ -137,7 +137,7 @@ with monthly: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -164,10 +164,10 @@ with monthly: - The products sold will be "Widget A" through "Widget Z," so you'll use `string` for easy access to an alphabetical string. - Optional: To help add emphasis at the end, you'll use `time.sleep()` to slow things down and see the fragment working. -1. Save your `app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to create random sales data diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index b42f99669..ceec4514b 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -87,7 +87,7 @@ if prompt := st.chat_input("Say something"): ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -104,10 +104,10 @@ if prompt := st.chat_input("Say something"): You'll use `time` to build a simulated chat response stream. -1. Save your `app.py` file and view your running app. -1. In your browser, view your app, and click "**Always rerun**" or hit your "**A**" key. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to simulate a chat response stream diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 762b10f73..4f45eb0b5 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -34,7 +34,150 @@ Here's a look at what you'll build: ```python +import streamlit as st +import lorem +from random import randint +import time +if "stage" not in st.session_state: + st.session_state.stage = "user" + st.session_state.history = [] + st.session_state.pending = None + st.session_state.validation = {} + + +def chat_stream(): + for i in range(randint(3, 9)): + yield lorem.sentence() + " " + time.sleep(0.2) + + +def validate(response): + response_sentences = response.split(". ") + response_sentences = [ + sentence.strip(". ") + "." + for sentence in response_sentences + if sentence.strip(". ") != "" + ] + validation_list = [ + True if sentence.count(" ") > 4 else False for sentence in response_sentences + ] + return response_sentences, validation_list + + +def add_highlights(response_sentences, validation_list, bg="red", text="red"): + return [ + f":{text}[:{bg}-background[" + sentence + "]]" if not is_valid else sentence + for sentence, is_valid in zip(response_sentences, validation_list) + ] + + +for message in st.session_state.history: + with st.chat_message(message["role"]): + st.write(message["content"]) + +if st.session_state.stage == "user": + if user_input := st.chat_input("Enter a prompt"): + st.session_state.history.append({"role": "user", "content": user_input}) + with st.chat_message("user"): + st.write(user_input) + with st.chat_message("assistant"): + response = st.write_stream(chat_stream()) + st.session_state.pending = response + st.session_state.stage = "validate" + st.rerun() + +elif st.session_state.stage == "validate": + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + response_sentences, validation_list = validate(st.session_state.pending) + highlighted_sentences = add_highlights(response_sentences, validation_list) + with st.chat_message("assistant"): + st.markdown(" ".join(highlighted_sentences)) + st.divider() + cols = st.columns(3) + if cols[0].button( + "Correct errors", type="primary", disabled=all(validation_list) + ): + st.session_state.validation = { + "sentences": response_sentences, + "valid": validation_list, + } + st.session_state.stage = "correct" + st.rerun() + if cols[1].button("Accept"): + st.session_state.history.append( + {"role": "assistant", "content": st.session_state.pending} + ) + st.session_state.pending = None + st.session_state.validation = {} + st.session_state.stage = "user" + st.rerun() + if cols[2].button("Rewrite answer", type="tertiary"): + st.session_state.stage = "rewrite" + st.rerun() + +elif st.session_state.stage == "correct": + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + response_sentences = st.session_state.validation["sentences"] + validation_list = st.session_state.validation["valid"] + highlighted_sentences = add_highlights( + response_sentences, validation_list, "gray", "gray" + ) + if not all(validation_list): + focus = validation_list.index(False) + highlighted_sentences[focus] = ":red[:red" + highlighted_sentences[focus][11:] + else: + focus = None + with st.chat_message("assistant"): + st.markdown(" ".join(highlighted_sentences)) + st.divider() + if focus is not None: + new_sentence = st.text_input( + "Replacement text:", value=response_sentences[focus] + ) + cols = st.columns(2) + if cols[0].button( + "Update", type="primary", disabled=len(new_sentence.strip()) < 1 + ): + st.session_state.validation["sentences"][focus] = ( + new_sentence.strip(". ") + "." + ) + st.session_state.validation["valid"][focus] = True + st.session_state.pending = " ".join( + st.session_state.validation["sentences"] + ) + st.rerun() + if cols[1].button("Remove"): + st.session_state.validation["sentences"].pop(focus) + st.session_state.validation["valid"].pop(focus) + st.rerun() + else: + cols = st.columns(2) + if cols[0].button("Accept", type="primary"): + st.session_state.history.append( + {"role": "assistant", "content": st.session_state.pending} + ) + st.session_state.pending = None + st.session_state.validation = {} + st.session_state.stage = "user" + st.rerun() + if cols[1].button("Re-validate"): + st.session_state.validation = {} + st.session_state.stage = "validate" + st.rerun() + +elif st.session_state.stage == "rewrite": + st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) + with st.chat_message("assistant"): + new = st.text_area("Rewrite the answer", value=st.session_state.pending) + if st.button( + "Update", type="primary", disabled=new is None or new.strip(". ") == "" + ): + st.session_state.history.append({"role": "assistant", "content": new}) + st.session_state.pending = None + st.session_state.validation = {} + st.session_state.stage = "user" + st.rerun() ``` @@ -46,7 +189,7 @@ Here's a look at what you'll build: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run app.py @@ -65,14 +208,14 @@ Here's a look at what you'll build: You'll use `lorem`, `random`, and `time` to build a simulated chat response stream. -1. Save your `app.py` file and view your running app. -1. In your browser, view your app, and click "**Always rerun**" or hit your "**A**" key. +1. Save your `app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to simulate a chat response stream -To begin with, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate four to ten random "sentences." +To begin, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate four to ten random "sentences." @@ -85,12 +228,14 @@ def chat_stream(): -1. Start a function for your simulated chat stream. +1. Define a function for your simulated chat stream. ```python def chat_stream(): ``` + For this example, the chat stream does not have any arguments. The streamed response will be random and independent of the user's prompt. + 1. Create a loop that executes four to ten times. ```python @@ -104,67 +249,95 @@ def chat_stream(): time.sleep(0.2) ``` - In order to create a streaming effect, use `time.sleep` to create a small delay between yields. - -You now have a complete generator function to simulate a chat stream object. + In order to create a streaming effect, use `time.sleep(0.2)` to create a small delay between yields. You now have a complete generator function to simulate a chat stream object. ### Create a validation function The app will "validate" the streamed responses to assist users in identifying possible errors. To evaluate a response, you'll create a list of its sentences. Any sentence with fewer than five words will be marked as a potential error. This is an arbitrary standard applied for the sake of illustration. -1. Define a function that accepts a string response and breaks it apart into segments. + + +```python +def validate(response): + response_sentences = response.split(". ") + response_sentences = [ + sentence.strip(". ") + "." + for sentence in response_sentences + if sentence.strip(". ") != "" + ] + validation_list = [ + True if sentence.count(" ") > 4 else False for sentence in response_sentences + ] + return response_sentences, validation_list +``` + + + +1. Define a function that accepts a string response and breaks it apart into sentences. ```python def validate(response): - response_segments = response.split(". ") + response_sentences = response.split(". ") ``` -1. Strip the segments of leading and trailing spaces and periods, and then restore a period to the end. +1. Use list comprehension to replace the list of sentences. For each sentence, strip any leading and trailing spaces and periods, and then restore a period to the end. ```python - response_segments = [ - segment.strip(". ") + "." - for segment in response_segments - if segment.strip(". ") != "" + response_sentences = [ + sentence.strip(". ") + "." + for sentence in response_sentences + if sentence.strip(". ") != "" ] ``` - Since the user will be modifying responses, `segment.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures each sentence ends with a single period. As added protection, `if segment.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. + Because the user will be modifying responses, whitespaces and punctuation may vary. `sentence.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures each sentence ends with a single period. Furthermore, `if sentence.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. -1. Create a boolean list of corresponding evaluations for the sentences. Use `True` for an approved sentence, and `False` for an unapproved sentence. +1. Create a boolean list of evaluations for the sentences. Use `True` for an approved sentence, and `False` for an unapproved sentence. ```python validation_list = [ - True if sentence.count(" ") > 4 else False for sentence in response_segments + True if sentence.count(" ") > 4 else False for sentence in response_sentences ] ``` - As stated before, a "good" sentence has at least five words. Use list comprehension to count the spaces in the sentence. + As stated before, a "good" sentence has at least five words. Use list comprehension to count the spaces in each sentence, and save a boolean value. -1. Finally, return the segment and validation lists as a tuple. +1. Finally, return the sentence and validation lists as a tuple. ```python - return response_segments, validation_list + return response_sentences, validation_list ``` ### Create a helper function to highlight text -To show your validation results to your user, you can highlight sentences that are flagged for review. Create a helper function to add text and background color to the "errors." +To show your validation results to your user, you can highlight sentences that are marked as errors. Create a helper function to add text and background color to the detected errors. -1. Define a function that accepts the list of sentences and list of evaluations. + + +```python +def add_highlights(response_sentences, validation_list, bg="red", text="red"): + return [ + f":{text}[:{bg}-background[" + sentence + "]]" if not is_valid else sentence + for sentence, is_valid in zip(response_sentences, validation_list) + ] +``` + + + +1. Define a function that accepts the lists of sentences and evaluations. Include parameters for the text and background colors of the highlight. ```python - def add_highlights(response_segments, validation_list, bg="red", text="red"): + def add_highlights(response_sentences, validation_list, bg="red", text="red"): ``` - For convenience, add parameters to set the text and background color, using a default of `"red"`. You'll use the function to highlight all errors in red immediately after validation. If the user chooses to step through the errors one by one, you'll highlight the errors in gray and show only one error in red. + For convenience, use a default of `"red"` for the highlight colors. You'll use this function to highlight all errors in red when summarizing the evaluation. If the user chooses to step through the errors one by one, you'll highlight the all the errors in gray (except the one in focus). -1. Use list comprehension to return a modified list of sentences that include the Markdown highlights. +1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where appropriate. ```python return [ - f":{text}[:{bg}-background[" + segment + "]]" if not is_valid else segment - for segment, is_valid in zip(response_segments, validation_list) + f":{text}[:{bg}-background[" + sentence + "]]" if not is_valid else sentence + for sentence, is_valid in zip(response_sentences, validation_list) ] ``` @@ -182,10 +355,10 @@ Your app will use Session State to track the stages of the evaluation and correc st.session_state.validation = {} ``` - - `st.session_state.stage` tracks where the user is in the multistage process. `"user"` means that the app is waiting for the user to enter a new prompt. Other values are `"validate"`, `"correct"`, and `"rewrite"`, which will be defined later. + - `st.session_state.stage` tracks where the user is in the multistage process. `"user"` means that the app is waiting for the user to enter a new prompt. The other values are `"validate"`, `"correct"`, and `"rewrite"`, which will be defined later. - `st.session_state.history` stores the conversation history as a list of messages. Each message is a dictionary of message attributes (`"role"` and `"content"`). - `st.session_state.pending` stores the next response before it is approved. - - `st.session_state.validation` stores the evaluation information for the pending response. This is dictionary with keys `"segments"` and `"valid"` to store the lists of sentences and their evaluation, respectively. + - `st.session_state.validation` stores the evaluation information for the pending response. This is dictionary with keys `"sentences"` and `"valid"` to store the lists of sentences and their evaluation, respectively. 1. Iterate through the messages in your chat history and render their contents in chat message containers. @@ -205,13 +378,13 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. if st.session_state.stage == "user": ``` -1. Render a chat input widget and start a nested conditional block for its output. +1. Render a chat input widget, and start a nested conditional block from its output. ```python if user_input := st.chat_input("Enter a prompt"): ``` - This nested block won't execute until a user submits a prompt so this is effectively the end of the script before the user enters a prompt. + This nested block won't execute until a user submits a prompt. When the app first loads (or returns to the `"user"` stage after finaling a response), this is effectively the end of the script. The `:=` notation is shorthand to assign a variable within an expression. @@ -231,14 +404,14 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. st.session_state.pending = response ``` -1. Update the stage to `"validate"` and rerun the app. +1. Update the stage to `"validate"`, and rerun the app. ```python st.session_state.stage = "validate" st.rerun() ``` - When a user submits a new prompt, the script run will end here and a new one will begin. The next script run will use the `"validate"` stage which you'll build in the next section. + When a user submits a new prompt, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"validate"` stage. ### Define the `"validate"` stage @@ -250,36 +423,38 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending elif st.session_state.stage == "validate": ``` + You can use `if` or `elif` for each of the stages. Everywhere you update the stage in Session State, you will immediately rerun the app. Therefore, you'll never execute two different stages in the same script run. + 1. Display a disabled chat input for visual consistency. ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) ``` - For the user's clarify, use placeholder to inform the user to review the pending response. + For the user's clarity, use placeholder text to direct them to review the pending response. 1. Parse the response and highlight any errors using your helper functions. ```python - response_segments, validation_list = validate(st.session_state.pending) - highlighted_segments = add_highlights(response_segments, validation_list) + response_sentences, validation_list = validate(st.session_state.pending) + highlighted_sentences = add_highlights(response_sentences, validation_list) ``` -1. Join the highlighted response segments and display them in a chat message container. To separate the response from the buttons that will follow, add a divider. +1. Join the highlighted sentences into a single string, and display them in a chat message container. To separate the response from the buttons that will follow, add a divider. ```python with st.chat_message("assistant"): - st.markdown(" ".join(highlighted_segments)) + st.markdown(" ".join(highlighted_sentences)) st.divider() ``` -1. To display the buttons in a row, create three columns. +1. To display buttons in a row, create three columns. ```python cols = st.columns(3) ``` -1. In the first column, start a conditional block and display a primary-type button to correct any errors. Disable the button if there are no detected errors. +1. In the first column, start a conditional block and display a primary-type button labeled "Correct errors." Disable the button if there are no detected errors. ```python if cols[0].button( @@ -291,16 +466,16 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending ```python st.session_state.validation = { - "segments": response_segments, + "sentences": response_sentences, "valid": validation_list, } st.session_state.stage = "correct" st.rerun() ``` - If the user clicks the "**Correct**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and enter the `"correct"` stage. + If the user clicks the "**Correct errors**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and enter the `"correct"` stage. -1. In the second column, start a conditional block and display a button to accept the response as-is. +1. In the second column, start a conditional block and display a button labeled "Accept." ```python if cols[1].button("Accept"): @@ -325,7 +500,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the third column, start a conditional block and display a tertiary-type button to rewrite the response. +1. In the third column, start a conditional block and display a tertiary-type button labeled "Rewrite answer." ```python if cols[2].button("Rewrite answer", type="tertiary"): @@ -344,7 +519,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending ### Define the `"correct"` stage -When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app highlights only the first error in the list. When a user addresses an error, it's removed from the list, and the next error becomes the first error for the next script run. This continues until all errors are cleared at which point the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. +When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app highlights only the first error in the list. When a user addresses an error, it's removed from the list, and the next error is highlighted in the next script run. This continues until all errors are cleared. Then, the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. 1. Start a conditional block for the `"correct"` stage. @@ -361,28 +536,30 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the 1. For convenience, retrieve the validation information from Session State and save it into variables. ```python - response_segments = st.session_state.validation["segments"] + response_sentences = st.session_state.validation["sentences"] validation_list = st.session_state.validation["valid"] ``` -1. Use your helper function to highlight the response segments with errors. Use gray for the highlight. +1. Use your helper function to highlight the response sentences with errors. Use gray for the highlight. ```python - highlighted_segments = add_highlights( - response_segments, validation_list, "gray", "gray" + highlighted_sentences = add_highlights( + response_sentences, validation_list, "gray", "gray" ) ``` - In a following step, you'll change the highlight color for a single error for the user to address. + In a following step, you'll change the highlight color for a single error for the user to focus on. -1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated response segment. +1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated response sentence. ```python if not all(validation_list): focus = validation_list.index(False) - highlighted_segments[focus] = ":red[:red" + highlighted_segments[focus][11:] + highlighted_sentences[focus] = ":red[:red" + highlighted_sentences[focus][11:] ``` + `highlighted_sentences[focus]` begins with `":gray[:gray-background["`. Therefore, `highlighted_sentences[focus][11:]` removes the first eleven characters so you can prepend `":red[:red"` instead. + 1. Set a fallback value for `focus` if there are no errors. ```python @@ -394,7 +571,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ```python with st.chat_message("assistant"): - st.markdown(" ".join(highlighted_segments)) + st.markdown(" ".join(highlighted_sentences)) st.divider() ``` @@ -402,12 +579,12 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ```python if focus is not None: - new_segment = st.text_input( - "Replacement text:", value=response_segments[focus] + new_sentence = st.text_input( + "Replacement text:", value=response_sentences[focus] ) ``` - `value=response_segments[focus]` prefills the text input with the response segment associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it. + `value=response_sentences[focus]` prefills the text input with the response sentence associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it instead. 1. To display buttons in a row, create two columns. @@ -415,19 +592,19 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the cols = st.columns(2) ``` -1. In the first column, start a conditional block and display a primary-type button to update the response segment with the user's edits. Disable the button if text input is empty. +1. In the first column, start a conditional block and display a primary-type button labeled "Update." Disable the button if the text input is empty. ```python if cols[0].button( - "Update", type="primary", disabled=len(new_segment.strip()) < 1 + "Update", type="primary", disabled=len(new_sentence.strip()) < 1 ): ``` -1. Within the conditional block, update the response segment and mark it as valid in Session State. +1. Within the conditional block, update the response sentence and mark it as valid in Session State. ```python - st.session_state.validation["segments"][focus] = ( - new_segment.strip(". ") + "." + st.session_state.validation["sentences"][focus] = ( + new_sentence.strip(". ") + "." ) st.session_state.validation["valid"][focus] = True ``` @@ -436,18 +613,18 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ```python st.session_state.pending = " ".join( - st.session_state.validation["segments"] + st.session_state.validation["sentences"] ) st.rerun() ``` If the user clicks the "**Update**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. In the second column, start a conditional block and display a button to remove the response segment. Within the conditional block, pop response segment and validation information out of their lists in Session State, and rerun the app. +1. In the second column, start a conditional block and display a button labeled "Remove." Within the conditional block, pop the response sentence and validation information out of their lists in Session State, and rerun the app. ```python if cols[1].button("Remove"): - st.session_state.validation["segments"].pop(focus) + st.session_state.validation["sentences"].pop(focus) st.session_state.validation["valid"].pop(focus) st.rerun() ``` @@ -463,7 +640,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the After a user has resolved all the errors, they need to confirm the final result. -1. In the first column, start a conditional block and display a primary-type button to accept the current response. Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. +1. In the first column, start a conditional block and display a primary-type button labeled "Accept." Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. ```python if cols[0].button("Accept", type="primary"): @@ -483,7 +660,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the second column, start a conditional block and display a button to rewrite the response. +1. In the second column, start a conditional block and display a button labeled "Re-validate." ```python if cols[1].button("Re-validate"): @@ -497,7 +674,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.rerun() ``` - If the user clicks the "**Rewrite answer**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. + If the user clicks the "**Re-validate**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. ### Define the `"rewrite"` stage @@ -524,7 +701,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo `value=st.session_state.pending` prefills the text area input with the pending response. The user can edit it or replace the text entirely. -1. In the first column, start a conditional block and display a primary-type button to update the response with the user's edits. Disable the button if text area input is empty. +1. In the first column, start a conditional block and display a primary-type button labeled "Update." Disable the button if text area input is empty. ```python if st.button( @@ -548,3 +725,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo ``` If the user clicks the "**Update**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. + +## Improve the example + +Now that you have a working app, you can iteratively improve it. Since there are some common elements between stages, you may want to introduce additional functions to reduce duplicate code. Alternatively, you can use callbacks with the buttons so the app doesn't rerun twice in a row. diff --git a/content/develop/tutorials/multipage-apps/dynamic-navigation.md b/content/develop/tutorials/multipage-apps/dynamic-navigation.md index e58099a87..579eecd0e 100644 --- a/content/develop/tutorials/multipage-apps/dynamic-navigation.md +++ b/content/develop/tutorials/multipage-apps/dynamic-navigation.md @@ -145,7 +145,7 @@ pg.run() ### Initialize your app 1. In `your_repository`, create a file named `streamlit_app.py`. -1. In a terminal, change directories to `your_repository` and start your app. +1. In a terminal, change directories to `your_repository`, and start your app. ```bash streamlit run streamlit_app.py @@ -159,10 +159,10 @@ pg.run() import streamlit as st ``` -1. Save your `streamlit_app.py` file and view your running app. -1. Click "**Always rerun**" or hit your "**A**" key in your running app. +1. Save your `streamlit_app.py` file, and view your running app. +1. Select "**Always rerun**", or press your "**A**" key. - Your running preview will automatically update as you save changes to `streamlit_app.py`. Your preview will still be blank. Return to your code. + Your preview will be blank but will automatically update as you save changes to `streamlit_app.py`. Return to your code. ### Add your page and image files From a6147ca9f50e20952871b204b37119f2f24aec8f Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Fri, 10 Jan 2025 10:38:51 -0800 Subject: [PATCH 05/10] Tutorial code formattings --- .../tutorials/llms/chat-response-feedback.md | 29 ++++++++++--------- .../tutorials/llms/chat-response-revision.md | 13 ++++++++- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index ceec4514b..bae1a8de3 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -37,15 +37,18 @@ Here's a look at what you'll build: import streamlit as st import time + def chat_stream(prompt): response = f'You said, "{prompt}" ...interesting.' for char in response: yield char - time.sleep(.02) + time.sleep(0.02) + def save_feedback(index): st.session_state.history[index]["feedback"] = st.session_state[f"feedback_{index}"] + if "history" not in st.session_state: st.session_state.history = [] @@ -60,22 +63,22 @@ for i, message in enumerate(st.session_state.history): key=f"feedback_{i}", disabled=feedback is not None, on_change=save_feedback, - args=[i] + args=[i], ) if prompt := st.chat_input("Say something"): with st.chat_message("user"): st.write(prompt) - st.session_state.history.append({"role":"user","content":prompt}) + st.session_state.history.append({"role": "user", "content": prompt}) with st.chat_message("assistant"): response = st.write_stream(chat_stream(prompt)) st.feedback( "thumbs", key=f"feedback_{len(st.session_state.history)}", on_change=save_feedback, - args=[len(st.session_state.history)] + args=[len(st.session_state.history)], ) - st.session_state.history.append({"role":"assistant","content":response}) + st.session_state.history.append({"role": "assistant", "content": response}) ``` @@ -120,7 +123,7 @@ def chat_stream(prompt): response = f'You said, "{prompt}" ...interesting.' for char in response: yield char - time.sleep(.02) + time.sleep(0.02) ``` @@ -213,7 +216,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio key=f"feedback_{i}", disabled=feedback is not None, + on_change=save_feedback, - + args=[i] + + args=[i], ) ``` @@ -225,7 +228,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio if prompt := st.chat_input("Say something"): with st.chat_message("user"): st.write(prompt) - st.session_state.history.append({"role":"user","content":prompt}) + st.session_state.history.append({"role": "user", "content": prompt}) ``` The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the preceding code displays the chat history. When this conditional block executes, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will be cleared from the widget and instead displayed as part of the history. @@ -237,7 +240,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio if prompt: with st.chat_message("user"): st.write(prompt) - st.session_state.history.append({"role":"user","content":prompt}) + st.session_state.history.append({"role": "user", "content": prompt}) ``` 1. Process the prompt and display the response in another chat message container along with a feedback widget. When the chat stream is finished, append the response to the chat history. @@ -249,9 +252,9 @@ To make your chat app stateful, you'll save the conversation history into Sessio "thumbs", key=f"feedback_{len(st.session_state.history)}", on_change=save_feedback, - args=[len(st.session_state.history)] + args=[len(st.session_state.history)], ) - st.session_state.history.append({"role":"assistant","content":response}) + st.session_state.history.append({"role": "assistant", "content": response}) ``` This is the same pattern used for the user's prompt. Within the body of the conditional block, the response is displayed and then added to the history. On the next rerun, this response will be display as a part of the history. @@ -276,7 +279,7 @@ At this point, the app allows users to rate any response once at any point in ti - key=f"feedback_{i}", - disabled=feedback is not None, - on_change=save_feedback, -- args=[i] +- args=[i], - ) ``` @@ -294,6 +297,6 @@ Alternatively, if you want to allow users to change their responses, you can jus key=f"feedback_{i}", - disabled=feedback is not None, on_change=save_feedback, - args=[i] + args=[i], ) ``` diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 4f45eb0b5..e10a5569b 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -150,6 +150,9 @@ elif st.session_state.stage == "correct": if cols[1].button("Remove"): st.session_state.validation["sentences"].pop(focus) st.session_state.validation["valid"].pop(focus) + st.session_state.pending = " ".join( + st.session_state.validation["sentences"] + ) st.rerun() else: cols = st.columns(2) @@ -620,12 +623,20 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Update**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. In the second column, start a conditional block and display a button labeled "Remove." Within the conditional block, pop the response sentence and validation information out of their lists in Session State, and rerun the app. +1. In the second column, start a conditional block and display a button labeled "Remove." Within the conditional block, pop the response sentence and validation information out of their lists in Session State. ```python if cols[1].button("Remove"): st.session_state.validation["sentences"].pop(focus) st.session_state.validation["valid"].pop(focus) + ``` + +1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app. + + ```python + st.session_state.pending = " ".join( + st.session_state.validation["sentences"] + ) st.rerun() ``` From 88c8cc8821c83e7c6f9e1ead080f773b3a900c0c Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Fri, 10 Jan 2025 10:52:13 -0800 Subject: [PATCH 06/10] Update chat-response-revision.md --- content/develop/tutorials/llms/chat-response-revision.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index e10a5569b..37165cf40 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -185,7 +185,7 @@ elif st.session_state.stage == "rewrite": - + ## Build the example From 0312880311f20f74f99c09287570aef0a9e4d361 Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Fri, 10 Jan 2025 11:30:44 -0800 Subject: [PATCH 07/10] Update chat-response-revision.md --- .../tutorials/llms/chat-response-revision.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 37165cf40..1d176893e 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -739,4 +739,30 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo ## Improve the example -Now that you have a working app, you can iteratively improve it. Since there are some common elements between stages, you may want to introduce additional functions to reduce duplicate code. Alternatively, you can use callbacks with the buttons so the app doesn't rerun twice in a row. +Now that you have a working app, you can iteratively improve it. Since there are some common elements between stages, you may want to introduce additional functions to reduce duplicate code. You can use callbacks with the buttons so the app doesn't rerun twice in a row. Alternatively, you can handle more edge cases. + +The example includes some protection against saving an empty response, but it isn't comprehensive. If every sentence in a response is marked as an error, a user can remove each of them in the `"correct"` stage and accept the empty result. Try disabling the "**Accept**" but in the `"correct"` stage if the response is empty. + +To see another edge case, try this in the running example: + +1. Submit a prompt. +1. Select "**Rewrite answer**." +1. In the text area, highlight all text and press delete. Do not click or tab outside of the text area. +1. Immediatly click "**Update**." + +When you click a button with an unsubmitted value in another widget, Streamlit will update that widget's value and the button's value in succession before triggering the rerun. Because there isn't a rerun between updating the text area and updating the button, the "**Update**" button doesn't get disabled as expected. To correct this, you can add an extra check for an empty text area within the `"rewrite"` stage: + +```diff +- if st.button( +- "Update", type="primary", disabled=new is None or new.strip(". ") == "" +- ): ++ is_empty = new is None or new.strip(". ") == "" ++ if st.button("Update", type="primary", disabled=is_empty) and not is_empty: + st.session_state.history.append({"role": "assistant", "content": new}) + st.session_state.pending = None + st.session_state.validation = {} + st.session_state.stage = "user" + st.rerun() +``` + +Now if you repeat the steps mentioned previously, when the app reruns, the conditional block won't be executed even though the button triggered the rerun. The button will be disabled and the user can proceed as if they had just clicked or tabbed out of the text area. From 46af6388943963bc9b29528feaab5e5f6bfc133f Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Thu, 23 Jan 2025 16:52:53 -0800 Subject: [PATCH 08/10] Edits for style --- .../elements/charts/annotate-altair-chart.md | 4 +- .../elements/dataframes/row_selections.md | 4 +- .../create-a-multiple-container-fragment.md | 4 +- .../start-and-stop-fragment-auto-reruns.md | 4 +- ...ger-a-full-script-rerun-from-a-fragment.md | 4 +- content/develop/tutorials/llms/_index.md | 18 +++ .../tutorials/llms/chat-response-feedback.md | 40 ++++--- .../tutorials/llms/chat-response-revision.md | 108 +++++++++--------- .../multipage-apps/dynamic-navigation.md | 4 +- styles/text.scss | 2 +- 10 files changed, 108 insertions(+), 84 deletions(-) diff --git a/content/develop/tutorials/elements/charts/annotate-altair-chart.md b/content/develop/tutorials/elements/charts/annotate-altair-chart.md index 01b6de678..e86af00ac 100644 --- a/content/develop/tutorials/elements/charts/annotate-altair-chart.md +++ b/content/develop/tutorials/elements/charts/annotate-altair-chart.md @@ -123,7 +123,7 @@ st.altair_chart(combined_chart, use_container_width=True) streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -141,7 +141,7 @@ st.altair_chart(combined_chart, use_container_width=True) - You'll define a chart using `altair`. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. diff --git a/content/develop/tutorials/elements/dataframes/row_selections.md b/content/develop/tutorials/elements/dataframes/row_selections.md index c7cbc717a..b618619ca 100644 --- a/content/develop/tutorials/elements/dataframes/row_selections.md +++ b/content/develop/tutorials/elements/dataframes/row_selections.md @@ -141,7 +141,7 @@ Here's a look at what you'll build: streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -160,7 +160,7 @@ Here's a look at what you'll build: - You'll manipulate the data with `pandas`. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. diff --git a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md index b4ec27e13..5335d83fe 100644 --- a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md @@ -105,7 +105,7 @@ with st.sidebar: streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -117,7 +117,7 @@ with st.sidebar: You'll use `time.sleep()` to slow things down and see the fragments working. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. diff --git a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md index 9a3e5f2ad..d2b5f80fa 100644 --- a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md +++ b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md @@ -108,7 +108,7 @@ show_latest_data() streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -126,7 +126,7 @@ show_latest_data() - The data will have `datetime.datetime` index values. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. diff --git a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md index e3b972c70..5ef7dd428 100644 --- a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md @@ -143,7 +143,7 @@ with monthly: streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -165,7 +165,7 @@ with monthly: - Optional: To help add emphasis at the end, you'll use `time.sleep()` to slow things down and see the fragment working. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. diff --git a/content/develop/tutorials/llms/_index.md b/content/develop/tutorials/llms/_index.md index 3d699994d..f94ddfb32 100644 --- a/content/develop/tutorials/llms/_index.md +++ b/content/develop/tutorials/llms/_index.md @@ -23,4 +23,22 @@ Build a chat app using the LangChain framework with OpenAI. + + +
Get chat response feedback
+ +Buid a chat app and let users rate the responses. +(thumb_up +thumb_down) + +
+ + + +
Validate and edit chat responses
+ +Build a chat app with response validation. Let users correct or edit the responses. + +
+ diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index bae1a8de3..ca6502a45 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -5,7 +5,7 @@ slug: /develop/tutorials/chat-and-llm-apps/chat-response-feedback # Collect user feedback about LLM responses -A common task in a chat app is to collect user feedback about an LLM's response. Streamlit includes `st.feedback` to conveniently collect user sentiment with a group of icon buttons. +A common task in a chat app is to collect user feedback about an LLM's responses. Streamlit includes `st.feedback` to conveniently collect user sentiment by displaying a group of selectable sentiment icons. This tutorial uses Streamlit's chat commands along with `st.feedback` to build a simple chat app that collects user feedback about each response. @@ -23,11 +23,11 @@ This tutorial uses Streamlit's chat commands along with `st.feedback` to build a ``` - You should have a clean working directory called `your-repository`. -- You should have a basic understanding of Session State. +- You should have a basic understanding of [Session State](/develop/concepts/architecture/session-state). ## Summary -In this example, you'll build a chat interface. For simplicity, the chat app will echo the user's prompt within a fixed response. Each chat response will be followed by a feedback widget where the user can vote "thumbs up" or "thumbs down." In the code that follows, the feedback widget will prevent the user from changing their feedback after it's given, but you can modify this with a few simple code changes discussed at the end of this tutorial. +In this example, you'll build a chat interface. To avoid API calls, the chat app will echo the user's prompt within a fixed response. Each chat response will be followed by a feedback widget where the user can vote "thumb up" or "thumb down." In the following code, a user can't change their feedback after it's given. If you want to let users change their rating, see the optional instructions at the end of this tutorial. Here's a look at what you'll build: @@ -96,7 +96,7 @@ if prompt := st.chat_input("Say something"): streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -108,13 +108,13 @@ if prompt := st.chat_input("Say something"): You'll use `time` to build a simulated chat response stream. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to simulate a chat response stream -To begin with, you'll define a function to stream a fixed chat response. +To begin with, you'll define a function to stream a fixed chat response. It's okay to skip this section if you just want to copy the function. @@ -151,7 +151,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio - `"role"`: This has a value of `"user"` or `"assistant"` to indicate the source of the message. - `"content"`: This is the body of the message as a string. -- `"feedback"`: This is an integer to indicate a user's feedback. This is only included with `"assistant"` messages after a user has submitted their feedback. +- `"feedback"`: This is an integer that indicates a user's feedback. This is only included when the message role is `"assistant"` because the user will not leave feedback on their own prompts.. 1. Initialize the chat history in Session State. @@ -168,7 +168,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.write(message["content"]) ``` - In a later step, you'll need to create unique keys for each assistant message. You can do this using the index of the message in your chat history. Therefore, use `enumerate()` to get an index along with each message dictionary. + In a later step, you'll need a unique key for each assistant message. You can do this using the index of the message in your chat history. Therefore, use `enumerate()` to get an index along with each message dictionary. 1. For each assistant message, check if feedback has been saved. @@ -177,7 +177,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio feedback = message.get("feedback", None) ``` - If no feedback is saved for the current message, the `.get()` method will return the declared default of `None`. + If no feedback is saved for the current message, the `.get()` method will return the specified default of `None`. 1. Save the feedback value into Session State under a unique key for that message. @@ -185,7 +185,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state[f"feedback_{i}"] = feedback ``` - Since you have an index for the current message in the ordered chat history, you can use the index as the key. For readability, you can add the prefix of "feedback\_" to the index. + Because the message index within the ordered chat history is unique, you can use the index as the key. For readability, you can add a prefix, "feedback\_", to the index. In the next step, to make the feedback widget show this value, you'll assign the same key to the widget. 1. Add a feedback widget to the chat message container. @@ -197,7 +197,9 @@ To make your chat app stateful, you'll save the conversation history into Sessio ) ``` - The code you've written so far will show the chat history. For all chat responses that the user has already rated, the feedback widget will show the rating and be disabled. However, for all of the messages that are not yet rated, the associated feedback widget has no way to save that information into the chat history. To solve this, use a callback. When a user interacts with the widget, a callback will update the chat history before the app reruns. + The code you've written so far will show the chat history. If a user has already rated a message in the chat history, the feedback widget will show the rating and be disabled. The user won't be able to change their rating. + + All unrated messages include an enabled feedback widget. However, if a user interacts with one of those widgets, there is no code to save that information into the chat history yet. To solve this, use a callback as shown in the following steps. 1. At the top of your app, after the definition of `chat_stream()` and before you initialize your chat history, define a function to use as a callback. @@ -206,7 +208,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history[index]["feedback"] = st.session_state[f"feedback_{index}"] ``` - The `save_feedback()` function accepts an index. It uses the index to get the associated widget value from Session State and update the associated message in your chat history. + The `save_feedback()` function accepts an index and uses the index to get the associated widget value from Session State. Then, this value is saved into chat history. 1. Add the callback and index argument to your `st.feedback` widget. @@ -220,9 +222,11 @@ To make your chat app stateful, you'll save the conversation history into Sessio ) ``` + When a user interacts with the feedback widget, the callback will update the chat history before the app reruns. + ### Add chat input -1. Accept the user's prompt for the `st.chat_input` widget, display it in a chat message container, and save it to the chat history. +1. Accept the user's prompt from an `st.chat_input` widget, display it in a chat message container, and then save it to the chat history. ```python if prompt := st.chat_input("Say something"): @@ -231,7 +235,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history.append({"role": "user", "content": prompt}) ``` - The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the preceding code displays the chat history. When this conditional block executes, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will be cleared from the widget and instead displayed as part of the history. + The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the previous code displays the chat history. When this conditional block executes, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will just be displayed as part of the history. The `:=` notation is shorthand to assign a variable within an expression. The following code is equivalent to the previous code in this step: @@ -243,7 +247,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history.append({"role": "user", "content": prompt}) ``` -1. Process the prompt and display the response in another chat message container along with a feedback widget. When the chat stream is finished, append the response to the chat history. +1. In another chat message container, process the prompt, display the response, and add a feedback widget. Finally, append the response to the chat history. ```python with st.chat_message("assistant"): @@ -257,7 +261,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history.append({"role": "assistant", "content": response}) ``` - This is the same pattern used for the user's prompt. Within the body of the conditional block, the response is displayed and then added to the history. On the next rerun, this response will be display as a part of the history. + This is the same pattern used for the user's prompt. Within the body of the conditional block, the response is displayed and then added to the history. On the next rerun, this response will be displayed as a part of the chat history. When Streamlit executes the `st.feedback` command, the response is not yet added to the chat history. Use an index equal to the length of the chat history because that is the index that the response will have when it's added to the chat history on the next line. @@ -265,7 +269,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio ### Optional: Change the feeback behavior -At this point, the app allows users to rate any response once at any point in time. If you only want users to rate the most recent response, you can remove the widget from the chat-history loop: +Your app currently allows users to rate any response once. They can submit their rating at any time, but can't change it. If you only want users to rate the most recent response, you can remove the widgets from the chat history. ```diff for i, message in enumerate(st.session_state.history): @@ -283,7 +287,7 @@ At this point, the app allows users to rate any response once at any point in ti - ) ``` -Alternatively, if you want to allow users to change their responses, you can just remove the `disabled` parameter: +Alternatively, if you want to allow users to change their responses, you can just remove the `disabled` parameter. ```diff for i, message in enumerate(st.session_state.history): diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 1d176893e..34dbdfa62 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -23,11 +23,11 @@ This tutorial uses Streamlit's chat commands to build a simple chat app that let ``` - You should have a clean working directory called `your-repository`. -- You should have a basic understanding of Session State. +- You should have a basic understanding of [Session State](/develop/concepts/architecture/session-state). ## Summary -In this example, you'll build a chat interface. To avoid API calls, the app will include a generator function to simulate a chat stream object. When the chat assistant "responds," a validation function evaluates the response and highlights possible "errors" for the user to review. The user must accept, correct, or rewrite the response before proceeding. +In this example, you'll build a chat interface. To avoid API calls, the app will include a generator function to simulate a chat stream object. When the simulated chat assistant responds, a function validates the response and highlights possible "errors" for the user to review. The user must accept, correct, or rewrite the response before proceeding. Here's a look at what you'll build: @@ -198,7 +198,7 @@ elif st.session_state.stage == "rewrite": streamlit run app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `app.py`, write the following: @@ -212,13 +212,13 @@ elif st.session_state.stage == "rewrite": You'll use `lorem`, `random`, and `time` to build a simulated chat response stream. 1. Save your `app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. ### Build a function to simulate a chat response stream -To begin, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate four to ten random "sentences." +To begin, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate three to nine random sentences. It's okay to skip this section if you just want to copy the function. @@ -239,24 +239,24 @@ def chat_stream(): For this example, the chat stream does not have any arguments. The streamed response will be random and independent of the user's prompt. -1. Create a loop that executes four to ten times. +1. Create a loop that executes three to nine times. ```python - for i in range(randint(3, 9)): + for i in range(randint(3, 9)): ``` 1. Within the loop, yield a random sentence from `lorem` with a space at the end. ```python - yield lorem.sentence() + " " - time.sleep(0.2) + yield lorem.sentence() + " " + time.sleep(0.2) ``` In order to create a streaming effect, use `time.sleep(0.2)` to create a small delay between yields. You now have a complete generator function to simulate a chat stream object. ### Create a validation function -The app will "validate" the streamed responses to assist users in identifying possible errors. To evaluate a response, you'll create a list of its sentences. Any sentence with fewer than five words will be marked as a potential error. This is an arbitrary standard applied for the sake of illustration. +The app will validate the streamed responses to assist users in identifying possible errors. To validate a response, you'll first create a list of its sentences. Any sentence with fewer than six words will be marked as a potential error. This is an arbitrary standard for the sake of illustration. @@ -283,7 +283,7 @@ def validate(response): response_sentences = response.split(". ") ``` -1. Use list comprehension to replace the list of sentences. For each sentence, strip any leading and trailing spaces and periods, and then restore a period to the end. +1. Use list comprehension to clean the list of sentences. For each sentence, strip any leading and trailing spaces and periods, and then restore a period to the end. ```python response_sentences = [ @@ -295,7 +295,7 @@ def validate(response): Because the user will be modifying responses, whitespaces and punctuation may vary. `sentence.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures each sentence ends with a single period. Furthermore, `if sentence.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. -1. Create a boolean list of evaluations for the sentences. Use `True` for an approved sentence, and `False` for an unapproved sentence. +1. Create a boolean list of sentence validations. Use `True` for an approved sentence, and `False` for an unapproved sentence. ```python validation_list = [ @@ -303,7 +303,7 @@ def validate(response): ] ``` - As stated before, a "good" sentence has at least five words. Use list comprehension to count the spaces in each sentence, and save a boolean value. + As stated previously, a "good" sentence has at least six words (i.e., at least five spaces). Use list comprehension to count the spaces in each sentence, and save a boolean value. 1. Finally, return the sentence and validation lists as a tuple. @@ -327,15 +327,15 @@ def add_highlights(response_sentences, validation_list, bg="red", text="red"): -1. Define a function that accepts the lists of sentences and evaluations. Include parameters for the text and background colors of the highlight. +1. Define a function that accepts the lists of sentences and their validations. Include parameters for the text and background colors of the highlight. ```python def add_highlights(response_sentences, validation_list, bg="red", text="red"): ``` - For convenience, use a default of `"red"` for the highlight colors. You'll use this function to highlight all errors in red when summarizing the evaluation. If the user chooses to step through the errors one by one, you'll highlight the all the errors in gray (except the one in focus). + For convenience, use a default of `"red"` for the highlight colors. You'll use this function to highlight all errors in red when summarizing the validation. If the user chooses to step through the errors one by one, you'll highlight all the errors in gray (except the one in focus). -1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where appropriate. +1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where errors were detected. ```python return [ @@ -344,9 +344,9 @@ def add_highlights(response_sentences, validation_list, bg="red", text="red"): ] ``` -### Initialize and render your chat history +### Initialize and display your chat history -Your app will use Session State to track the stages of the evaluation and correction process. +Your app will use Session State to track the stages of the validation and correction process. 1. Initialize Session State. @@ -361,9 +361,9 @@ Your app will use Session State to track the stages of the evaluation and correc - `st.session_state.stage` tracks where the user is in the multistage process. `"user"` means that the app is waiting for the user to enter a new prompt. The other values are `"validate"`, `"correct"`, and `"rewrite"`, which will be defined later. - `st.session_state.history` stores the conversation history as a list of messages. Each message is a dictionary of message attributes (`"role"` and `"content"`). - `st.session_state.pending` stores the next response before it is approved. - - `st.session_state.validation` stores the evaluation information for the pending response. This is dictionary with keys `"sentences"` and `"valid"` to store the lists of sentences and their evaluation, respectively. + - `st.session_state.validation` stores the validation information for the pending response. This is a dictionary with the keys `"sentences"` and `"valid"` to store the lists of sentences and their validations, respectively. -1. Iterate through the messages in your chat history and render their contents in chat message containers. +1. Iterate through the messages in your chat history and display their contents in chat message containers. ```python for message in st.session_state.history: @@ -381,17 +381,17 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. if st.session_state.stage == "user": ``` -1. Render a chat input widget, and start a nested conditional block from its output. +1. Display a chat input widget, and start a nested conditional block from its output. ```python if user_input := st.chat_input("Enter a prompt"): ``` - This nested block won't execute until a user submits a prompt. When the app first loads (or returns to the `"user"` stage after finaling a response), this is effectively the end of the script. + This nested block won't execute until a user submits a prompt. When the app first loads (or returns to the `"user"` stage after finalizing a response), this is effectively the end of the script. The `:=` notation is shorthand to assign a variable within an expression. -1. Append the user prompt to the chat history and render it in a chat message container. +1. Append the user prompt to the chat history and display it in a chat message container. ```python st.session_state.history.append({"role": "user", "content": user_input}) @@ -399,7 +399,7 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. st.write(user_input) ``` -1. After the user's chat message container, render the chat response in another chat message container. After the message is done streaming, save the pending message in Session State. +1. After the user's chat message container, display the chat response in another chat message container. Save the complete streamed response as a pending message in Session State. ```python with st.chat_message("assistant"): @@ -418,7 +418,7 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. ### Define the `"validate"` stage -When `st.session_state.stage` is `"validate"`, the app will validate the pending response and dispaly the results to the user. The user will then choose how to proceed (accept, correct, or rewrite the response). +When `st.session_state.stage` is `"validate"`, the app will validate the pending response and display the results to the user. The user will then choose how to proceed (accept, correct, or rewrite the response). 1. Start a conditional block for the `"validate"` stage. @@ -428,7 +428,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending You can use `if` or `elif` for each of the stages. Everywhere you update the stage in Session State, you will immediately rerun the app. Therefore, you'll never execute two different stages in the same script run. -1. Display a disabled chat input for visual consistency. +1. For visual consistency, display a disabled chat input. ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) @@ -443,7 +443,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending highlighted_sentences = add_highlights(response_sentences, validation_list) ``` -1. Join the highlighted sentences into a single string, and display them in a chat message container. To separate the response from the buttons that will follow, add a divider. +1. Join the highlighted sentences into a single string, and display them in a chat message container. To separate the response from the buttons which will follow, add a divider. ```python with st.chat_message("assistant"): @@ -457,7 +457,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending cols = st.columns(3) ``` -1. In the first column, start a conditional block and display a primary-type button labeled "Correct errors." Disable the button if there are no detected errors. +1. In the first column, start a conditional block, and display a primary-type button labeled "Correct errors." Disable the button if there are no detected errors. ```python if cols[0].button( @@ -478,7 +478,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Correct errors**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and enter the `"correct"` stage. -1. In the second column, start a conditional block and display a button labeled "Accept." +1. In the second column, start a conditional block, and display a button labeled "Accept." ```python if cols[1].button("Accept"): @@ -503,7 +503,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the third column, start a conditional block and display a tertiary-type button labeled "Rewrite answer." +1. In the third column, start a conditional block, and display a tertiary-type button labeled "Rewrite answer." ```python if cols[2].button("Rewrite answer", type="tertiary"): @@ -522,7 +522,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending ### Define the `"correct"` stage -When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app highlights only the first error in the list. When a user addresses an error, it's removed from the list, and the next error is highlighted in the next script run. This continues until all errors are cleared. Then, the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. +When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app focuses the user on the first error in the list. When the user addresses an error, the error removed from the list, and the next error is highlighted in the next script run. This continues until all errors are removed. Then, the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. 1. Start a conditional block for the `"correct"` stage. @@ -530,20 +530,20 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the elif st.session_state.stage == "correct": ``` -1. Display a disabled chat input for visual consistency. +1. For visual consistency, display a disabled chat input. ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) ``` -1. For convenience, retrieve the validation information from Session State and save it into variables. +1. For coding convenience, retrieve the validation information from Session State and save it into variables. ```python response_sentences = st.session_state.validation["sentences"] validation_list = st.session_state.validation["valid"] ``` -1. Use your helper function to highlight the response sentences with errors. Use gray for the highlight. +1. Use your helper function to highlight the sentences with errors. Use gray for the highlight. ```python highlighted_sentences = add_highlights( @@ -551,9 +551,9 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ) ``` - In a following step, you'll change the highlight color for a single error for the user to focus on. + In a following step, to focus the user on one error, you'll change the highlight color for one sentence. -1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated response sentence. +1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated sentence. ```python if not all(validation_list): @@ -563,14 +563,14 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the `highlighted_sentences[focus]` begins with `":gray[:gray-background["`. Therefore, `highlighted_sentences[focus][11:]` removes the first eleven characters so you can prepend `":red[:red"` instead. -1. Set a fallback value for `focus` if there are no errors. +1. Set a fallback value for `focus` for when there are no errors. ```python else: focus = None ``` -1. In a chat message container, display the highlighted response. To separate the response from the buttons that will follow, add a divider. +1. In a chat message container, display the highlighted response. To separate the response from the buttons which will follow, add a divider. ```python with st.chat_message("assistant"): @@ -578,7 +578,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.divider() ``` -1. Start a conditional block: if there are errors, display a text input for the user to edit the first one (which you highlighted in red in a preceding step). +1. Start a conditional block: if there are errors, display a text input prefilled with the first error. This is the error you highlighted in red. ```python if focus is not None: @@ -587,7 +587,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ) ``` - `value=response_sentences[focus]` prefills the text input with the response sentence associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it instead. + `value=response_sentences[focus]` prefills the text input with the sentence associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it instead. 1. To display buttons in a row, create two columns. @@ -595,7 +595,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the cols = st.columns(2) ``` -1. In the first column, start a conditional block and display a primary-type button labeled "Update." Disable the button if the text input is empty. +1. In the first column, start a conditional block, and display a primary-type button labeled "Update." Disable the button if the text input is empty. ```python if cols[0].button( @@ -603,7 +603,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ): ``` -1. Within the conditional block, update the response sentence and mark it as valid in Session State. +1. Within the conditional block, update the sentence and its validation. ```python st.session_state.validation["sentences"][focus] = ( @@ -623,7 +623,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Update**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. In the second column, start a conditional block and display a button labeled "Remove." Within the conditional block, pop the response sentence and validation information out of their lists in Session State. +1. In the second column, start a conditional block, and display a button labeled "Remove." Within the conditional block, pop the sentence and validation information out of their lists in Session State. ```python if cols[1].button("Remove"): @@ -642,16 +642,16 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Remove**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. Define `else` for when there are no errors. To display buttons in a row, create two columns. +1. Start an `else` block for when there are no errors. To display buttons in a row, create two columns. ```python else: cols = st.columns(2) ``` - After a user has resolved all the errors, they need to confirm the final result. + After a user has resolved all the errors, they need to confirm the final result. Instead of "**Update**" and "**Remove**" buttons, you'll display "**Accept**" and "**Re-validate**" buttons. -1. In the first column, start a conditional block and display a primary-type button labeled "Accept." Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. +1. In the first column, start a conditional block, and display a primary-type button labeled "Accept." Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. ```python if cols[0].button("Accept", type="primary"): @@ -671,7 +671,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the second column, start a conditional block and display a button labeled "Re-validate." +1. In the second column, start a conditional block, and display a button labeled "Re-validate." ```python if cols[1].button("Re-validate"): @@ -685,7 +685,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.rerun() ``` - If the user clicks the "**Re-validate**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. + If the user clicks the "**Re-validate**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"validate"` stage. ### Define the `"rewrite"` stage @@ -697,13 +697,13 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo elif st.session_state.stage == "rewrite": ``` -1. Display a disabled chat input for visual consistency. +1. For visual consistency, display a disabled chat input. ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) ``` -1. In a chat message container, display a text area input for the user to edit the pending response. +1. To let the user edit the pending response, in a chat message container, display a text area input. ```python with st.chat_message("assistant"): @@ -712,7 +712,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo `value=st.session_state.pending` prefills the text area input with the pending response. The user can edit it or replace the text entirely. -1. In the first column, start a conditional block and display a primary-type button labeled "Update." Disable the button if text area input is empty. +1. Start a conditional block, and display a primary-type button labeled "Update." Disable the button if text area input is empty. ```python if st.button( @@ -737,11 +737,13 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo If the user clicks the "**Update**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. +1. Save your file and go to your browser to try your new app. + ## Improve the example Now that you have a working app, you can iteratively improve it. Since there are some common elements between stages, you may want to introduce additional functions to reduce duplicate code. You can use callbacks with the buttons so the app doesn't rerun twice in a row. Alternatively, you can handle more edge cases. -The example includes some protection against saving an empty response, but it isn't comprehensive. If every sentence in a response is marked as an error, a user can remove each of them in the `"correct"` stage and accept the empty result. Try disabling the "**Accept**" but in the `"correct"` stage if the response is empty. +The example includes some protection against saving an empty response, but it isn't comprehensive. If every sentence in a response is marked as an error, a user can remove each of them in the `"correct"` stage and accept the empty result. Try disabling the "**Accept**" button in the `"correct"` stage if the response is empty or changing it to "**Rewrite**." To see another edge case, try this in the running example: @@ -765,4 +767,4 @@ When you click a button with an unsubmitted value in another widget, Streamlit w st.rerun() ``` -Now if you repeat the steps mentioned previously, when the app reruns, the conditional block won't be executed even though the button triggered the rerun. The button will be disabled and the user can proceed as if they had just clicked or tabbed out of the text area. +Now, if you repeat the steps mentioned previously, when the app reruns, the conditional block won't be executed even though the button triggered the rerun. The button will be disabled and the user can proceed as if they had just clicked or tabbed out of the text area. diff --git a/content/develop/tutorials/multipage-apps/dynamic-navigation.md b/content/develop/tutorials/multipage-apps/dynamic-navigation.md index 579eecd0e..66060dde5 100644 --- a/content/develop/tutorials/multipage-apps/dynamic-navigation.md +++ b/content/develop/tutorials/multipage-apps/dynamic-navigation.md @@ -151,7 +151,7 @@ pg.run() streamlit run streamlit_app.py ``` - Your app will be blank since you still need to add code. + Your app will be blank because you still need to add code. 1. In `streamlit_app.py`, write the following: @@ -160,7 +160,7 @@ pg.run() ``` 1. Save your `streamlit_app.py` file, and view your running app. -1. Select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press your "**A**" key. Your preview will be blank but will automatically update as you save changes to `streamlit_app.py`. Return to your code. diff --git a/styles/text.scss b/styles/text.scss index c4b55b79e..5b04b972d 100644 --- a/styles/text.scss +++ b/styles/text.scss @@ -108,7 +108,7 @@ button:focus-visible { } /* Inline code blocks */ -p code, +p code, li>code, /* Inline code blocks in docstrings */ tt.docutils.literal { @apply break-words px-1 rounded; From 405f4717d99e706d1459f9ee3a1f1f91b175efeb Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Mon, 27 Jan 2025 17:33:17 -0800 Subject: [PATCH 09/10] Editorial review --- .../elements/charts/annotate-altair-chart.md | 6 +- .../elements/dataframes/row_selections.md | 8 +- .../create-a-multiple-container-fragment.md | 8 +- .../start-and-stop-fragment-auto-reruns.md | 8 +- ...ger-a-full-script-rerun-from-a-fragment.md | 8 +- .../tutorials/llms/chat-response-feedback.md | 48 +++--- .../tutorials/llms/chat-response-revision.md | 145 +++++++++--------- .../multipage-apps/dynamic-navigation.md | 8 +- content/menu.md | 100 ++++++------ 9 files changed, 181 insertions(+), 158 deletions(-) diff --git a/content/develop/tutorials/elements/charts/annotate-altair-chart.md b/content/develop/tutorials/elements/charts/annotate-altair-chart.md index e86af00ac..fb1d86e06 100644 --- a/content/develop/tutorials/elements/charts/annotate-altair-chart.md +++ b/content/develop/tutorials/elements/charts/annotate-altair-chart.md @@ -117,7 +117,7 @@ st.altair_chart(combined_chart, use_container_width=True) ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -143,7 +143,9 @@ st.altair_chart(combined_chart, use_container_width=True) 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build the data layer diff --git a/content/develop/tutorials/elements/dataframes/row_selections.md b/content/develop/tutorials/elements/dataframes/row_selections.md index b618619ca..2ea0fd8cc 100644 --- a/content/develop/tutorials/elements/dataframes/row_selections.md +++ b/content/develop/tutorials/elements/dataframes/row_selections.md @@ -15,7 +15,7 @@ This tutorial uses row selections, which were introduced in Streamlit version 1. ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.35.0 @@ -135,7 +135,7 @@ Here's a look at what you'll build: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -162,7 +162,9 @@ Here's a look at what you'll build: 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build a function to create random member data diff --git a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md index 5335d83fe..6b677c116 100644 --- a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md @@ -14,7 +14,7 @@ Streamlit lets you turn functions into [fragments](/develop/concepts/architectur ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.37.0 @@ -99,7 +99,7 @@ with st.sidebar: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -119,7 +119,9 @@ with st.sidebar: 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Frame out your app's containers diff --git a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md index d2b5f80fa..56ea955a5 100644 --- a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md +++ b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md @@ -14,7 +14,7 @@ Streamlit lets you turn functions into [fragments](/develop/concepts/architectur ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.37.0 @@ -102,7 +102,7 @@ show_latest_data() ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -128,7 +128,9 @@ show_latest_data() 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build a function to generate random, recent data diff --git a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md index 5ef7dd428..36000ee94 100644 --- a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md @@ -13,7 +13,7 @@ Streamlit lets you turn functions into [fragments](/develop/concepts/architectur ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.37.0 @@ -137,7 +137,7 @@ with monthly: ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -167,7 +167,9 @@ with monthly: 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build a function to create random sales data diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index ca6502a45..6ed4db7b4 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -16,7 +16,7 @@ This tutorial uses Streamlit's chat commands along with `st.feedback` to build a ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.42.0 @@ -90,7 +90,7 @@ if prompt := st.chat_input("Say something"): ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -110,11 +110,13 @@ if prompt := st.chat_input("Say something"): 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build a function to simulate a chat response stream -To begin with, you'll define a function to stream a fixed chat response. It's okay to skip this section if you just want to copy the function. +To begin, you'll define a function to stream a fixed chat response. You can skip this section if you just want to copy the function. @@ -135,7 +137,7 @@ def chat_stream(prompt): response = f'You said, "{prompt}" ...interesting.' ``` -1. Loop through the characters and yield each one at 0.02-second intervals. +1. Loop through the characters and yield each one at 0.02-second intervals: ```python for char in response: @@ -149,18 +151,18 @@ You now have a complete generator function to simulate a chat stream object. To make your chat app stateful, you'll save the conversation history into Session State as a list of messages. Each message is a dictionary of message attributes. The dictionary keys include the following: -- `"role"`: This has a value of `"user"` or `"assistant"` to indicate the source of the message. -- `"content"`: This is the body of the message as a string. -- `"feedback"`: This is an integer that indicates a user's feedback. This is only included when the message role is `"assistant"` because the user will not leave feedback on their own prompts.. +- `"role"`: Indicates the source of the message (either `"user"` or `"assistant"`). +- `"content"`: The body of the message as a string. +- `"feedback"`: An integer that indicates a user's feedback. This is only included when the message role is `"assistant"` because users do not leave feedback on their own prompts. -1. Initialize the chat history in Session State. +1. Initialize the chat history in Session State: ```python if "history" not in st.session_state: st.session_state.history = [] ``` -1. Iterate through the messages in your chat history and render their contents in chat message containers. +1. Iterate through the messages in your chat history and render their contents in chat message containers: ```python for i, message in enumerate(st.session_state.history): @@ -168,9 +170,9 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.write(message["content"]) ``` - In a later step, you'll need a unique key for each assistant message. You can do this using the index of the message in your chat history. Therefore, use `enumerate()` to get an index along with each message dictionary. + In a later step, you'll need a unique key for each assistant message. You can use the index of the message in your chat history to create a unique key. Therefore, use `enumerate()` to get an index along with each message dictionary. -1. For each assistant message, check if feedback has been saved. +1. For each assistant message, check whether feedback has been saved: ```python if message["role"] == "assistant": @@ -179,7 +181,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio If no feedback is saved for the current message, the `.get()` method will return the specified default of `None`. -1. Save the feedback value into Session State under a unique key for that message. +1. Save the feedback value into Session State under a unique key for that message: ```python st.session_state[f"feedback_{i}"] = feedback @@ -187,7 +189,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio Because the message index within the ordered chat history is unique, you can use the index as the key. For readability, you can add a prefix, "feedback\_", to the index. In the next step, to make the feedback widget show this value, you'll assign the same key to the widget. -1. Add a feedback widget to the chat message container. +1. Add a feedback widget to the chat message container: ```python st.feedback( @@ -201,7 +203,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio All unrated messages include an enabled feedback widget. However, if a user interacts with one of those widgets, there is no code to save that information into the chat history yet. To solve this, use a callback as shown in the following steps. -1. At the top of your app, after the definition of `chat_stream()` and before you initialize your chat history, define a function to use as a callback. +1. At the top of your app, after the definition of `chat_stream()` and before you initialize your chat history, define a function to use as a callback: ```python def save_feedback(index): @@ -210,7 +212,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio The `save_feedback()` function accepts an index and uses the index to get the associated widget value from Session State. Then, this value is saved into chat history. -1. Add the callback and index argument to your `st.feedback` widget. +1. Add the callback and index argument to your `st.feedback` widget: ```diff st.feedback( @@ -226,7 +228,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio ### Add chat input -1. Accept the user's prompt from an `st.chat_input` widget, display it in a chat message container, and then save it to the chat history. +1. Accept the user's prompt from an `st.chat_input` widget, display it in a chat message container, and then save it to the chat history: ```python if prompt := st.chat_input("Say something"): @@ -235,7 +237,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history.append({"role": "user", "content": prompt}) ``` - The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the previous code displays the chat history. When this conditional block executes, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will just be displayed as part of the history. + The `st.chat_input` widget acts like a button. When a user enters a prompt and clicks the send icon, it triggers a rerun. During the rerun, the previous code displays the chat history. When this conditional block is executed, the user's new prompt is displayed and then added to the history. On the next rerun, this prompt will be displayed as part of the history. The `:=` notation is shorthand to assign a variable within an expression. The following code is equivalent to the previous code in this step: @@ -247,7 +249,7 @@ To make your chat app stateful, you'll save the conversation history into Sessio st.session_state.history.append({"role": "user", "content": prompt}) ``` -1. In another chat message container, process the prompt, display the response, and add a feedback widget. Finally, append the response to the chat history. +1. In another chat message container, process the prompt, display the response, add a feedback widget, and append the response to the chat history: ```python with st.chat_message("assistant"): @@ -267,9 +269,11 @@ To make your chat app stateful, you'll save the conversation history into Sessio 1. Save your file and go to your browser to try your new app. -### Optional: Change the feeback behavior +### Optional: Change the feedback behavior + +Your app currently allows users to rate any response once. They can submit their rating at any time, but can't change it. -Your app currently allows users to rate any response once. They can submit their rating at any time, but can't change it. If you only want users to rate the most recent response, you can remove the widgets from the chat history. +If you want users to rate only the _most recent_ response, you can remove the widgets from the chat history: ```diff for i, message in enumerate(st.session_state.history): @@ -287,7 +291,7 @@ Your app currently allows users to rate any response once. They can submit their - ) ``` -Alternatively, if you want to allow users to change their responses, you can just remove the `disabled` parameter. +Or, if you want to allow users to change their responses, you can just remove the `disabled` parameter: ```diff for i, message in enumerate(st.session_state.history): diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 34dbdfa62..0aa5951ca 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -16,7 +16,7 @@ This tutorial uses Streamlit's chat commands to build a simple chat app that let ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ```text streamlit>=1.24.0 @@ -192,7 +192,7 @@ elif st.session_state.stage == "rewrite": ### Initialize your app 1. In `your_repository`, create a file named `app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run app.py @@ -214,11 +214,13 @@ elif st.session_state.stage == "rewrite": 1. Save your `app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `app.py`. + +1. Return to your code. ### Build a function to simulate a chat response stream -To begin, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate three to nine random sentences. It's okay to skip this section if you just want to copy the function. +To begin, you'll define a function to stream a random chat response. The simulated chat stream will use `lorem` to generate three to nine random sentences. You can skip this section if you just want to copy the function. @@ -231,7 +233,7 @@ def chat_stream(): -1. Define a function for your simulated chat stream. +1. Define a function for your simulated chat stream: ```python def chat_stream(): @@ -239,20 +241,25 @@ def chat_stream(): For this example, the chat stream does not have any arguments. The streamed response will be random and independent of the user's prompt. -1. Create a loop that executes three to nine times. +1. Create a loop that executes three to nine times: ```python for i in range(randint(3, 9)): ``` -1. Within the loop, yield a random sentence from `lorem` with a space at the end. +1. Within the loop, yield a random sentence from `lorem` with a space at the end: ```python yield lorem.sentence() + " " + ``` + +1. To create a streaming effect, add a small delay with `time.sleep(0.2)` between yields: + + ```python time.sleep(0.2) ``` - In order to create a streaming effect, use `time.sleep(0.2)` to create a small delay between yields. You now have a complete generator function to simulate a chat stream object. +You now have a complete generator function to simulate a chat stream object. ### Create a validation function @@ -276,14 +283,14 @@ def validate(response): -1. Define a function that accepts a string response and breaks it apart into sentences. +1. Define a function that accepts a string response and breaks it into sentences: ```python def validate(response): response_sentences = response.split(". ") ``` -1. Use list comprehension to clean the list of sentences. For each sentence, strip any leading and trailing spaces and periods, and then restore a period to the end. +1. Use list comprehension to clean the list of sentences. For each sentence, strip any leading and trailing spaces and periods, and then restore a period to the end: ```python response_sentences = [ @@ -293,9 +300,9 @@ def validate(response): ] ``` - Because the user will be modifying responses, whitespaces and punctuation may vary. `sentence.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures each sentence ends with a single period. Furthermore, `if sentence.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. + Because the user will be modifying responses, whitespaces and punctuation may vary. The code `sentence.strip(". ") + "."` removes leading and trailing spaces and periods. It also ensures that each sentence ends with a single period. Furthermore, the code `if sentence.strip(". ") != ""` discards any empty sentences. This simple example doesn't address other punctuation that may terminate a sentence. -1. Create a boolean list of sentence validations. Use `True` for an approved sentence, and `False` for an unapproved sentence. +1. Create a Boolean list of sentence validations, using `True` for an approved sentence and `False` for an unapproved sentence: ```python validation_list = [ @@ -303,9 +310,9 @@ def validate(response): ] ``` - As stated previously, a "good" sentence has at least six words (i.e., at least five spaces). Use list comprehension to count the spaces in each sentence, and save a boolean value. + As stated previously, a "good" sentence has at least six words (i.e., at least five spaces). This code uses list comprehension to count the spaces in each sentence and saves a Boolean value. -1. Finally, return the sentence and validation lists as a tuple. +1. Return the sentence and validation lists as a tuple: ```python return response_sentences, validation_list @@ -327,15 +334,15 @@ def add_highlights(response_sentences, validation_list, bg="red", text="red"): -1. Define a function that accepts the lists of sentences and their validations. Include parameters for the text and background colors of the highlight. +1. Define a function that accepts the lists of sentences and their validations. Include parameters for the text and background colors of the highlight: ```python def add_highlights(response_sentences, validation_list, bg="red", text="red"): ``` - For convenience, use a default of `"red"` for the highlight colors. You'll use this function to highlight all errors in red when summarizing the validation. If the user chooses to step through the errors one by one, you'll highlight all the errors in gray (except the one in focus). + For convenience, use a default of `"red"` for the highlight colors. You'll use this function to highlight all errors in red when summarizing the validation. If the user chooses to step through the errors individually, you'll highlight all the errors in gray (except the one in focus). -1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where errors were detected. +1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where errors were detected: ```python return [ @@ -348,7 +355,7 @@ def add_highlights(response_sentences, validation_list, bg="red", text="red"): Your app will use Session State to track the stages of the validation and correction process. -1. Initialize Session State. +1. Initialize Session State: ```python if "stage" not in st.session_state: @@ -363,7 +370,7 @@ Your app will use Session State to track the stages of the validation and correc - `st.session_state.pending` stores the next response before it is approved. - `st.session_state.validation` stores the validation information for the pending response. This is a dictionary with the keys `"sentences"` and `"valid"` to store the lists of sentences and their validations, respectively. -1. Iterate through the messages in your chat history and display their contents in chat message containers. +1. Iterate through the messages in your chat history and display their contents in chat message containers: ```python for message in st.session_state.history: @@ -375,23 +382,23 @@ Your app will use Session State to track the stages of the validation and correc When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. -1. Start a conditional block for the `"user"` stage. +1. Start a conditional block for the `"user"` stage: ```python if st.session_state.stage == "user": ``` -1. Display a chat input widget, and start a nested conditional block from its output. +1. Display a chat input widget, and start a nested conditional block from its output: ```python if user_input := st.chat_input("Enter a prompt"): ``` - This nested block won't execute until a user submits a prompt. When the app first loads (or returns to the `"user"` stage after finalizing a response), this is effectively the end of the script. + This nested block won't be executed until a user submits a prompt. When the app first loads (or returns to the `"user"` stage after finalizing a response), this is effectively the end of the script. The `:=` notation is shorthand to assign a variable within an expression. -1. Append the user prompt to the chat history and display it in a chat message container. +1. Append the user prompt to the chat history and display it in a chat message container: ```python st.session_state.history.append({"role": "user", "content": user_input}) @@ -399,7 +406,7 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. st.write(user_input) ``` -1. After the user's chat message container, display the chat response in another chat message container. Save the complete streamed response as a pending message in Session State. +1. Following the user's chat message container, display the chat response in another chat message container. Save the complete streamed response as a pending message in Session State: ```python with st.chat_message("assistant"): @@ -407,7 +414,7 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. st.session_state.pending = response ``` -1. Update the stage to `"validate"`, and rerun the app. +1. Update the stage to `"validate"`, and rerun the app: ```python st.session_state.stage = "validate" @@ -420,7 +427,7 @@ When `st.session_state.stage` is `"user"`, the app is waiting for a new prompt. When `st.session_state.stage` is `"validate"`, the app will validate the pending response and display the results to the user. The user will then choose how to proceed (accept, correct, or rewrite the response). -1. Start a conditional block for the `"validate"` stage. +1. Start a conditional block for the `"validate"` stage: ```python elif st.session_state.stage == "validate": @@ -428,7 +435,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending You can use `if` or `elif` for each of the stages. Everywhere you update the stage in Session State, you will immediately rerun the app. Therefore, you'll never execute two different stages in the same script run. -1. For visual consistency, display a disabled chat input. +1. For visual consistency, display a disabled chat input: ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) @@ -436,14 +443,14 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending For the user's clarity, use placeholder text to direct them to review the pending response. -1. Parse the response and highlight any errors using your helper functions. +1. Parse the response and highlight any errors using your helper functions: ```python response_sentences, validation_list = validate(st.session_state.pending) highlighted_sentences = add_highlights(response_sentences, validation_list) ``` -1. Join the highlighted sentences into a single string, and display them in a chat message container. To separate the response from the buttons which will follow, add a divider. +1. Join the highlighted sentences into a single string, and display them in a chat message container. To separate the response from the buttons that follow, add a divider: ```python with st.chat_message("assistant"): @@ -451,13 +458,13 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending st.divider() ``` -1. To display buttons in a row, create three columns. +1. To display buttons in a row, create three columns: ```python cols = st.columns(3) ``` -1. In the first column, start a conditional block, and display a primary-type button labeled "Correct errors." Disable the button if there are no detected errors. +1. In the first column, start a conditional block, and display a primary-type button labeled "Correct errors." Disable the button if there are no detected errors: ```python if cols[0].button( @@ -465,7 +472,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending ): ``` -1. Within the conditional block, save the validation information into Session State, update the stage, and then rerun the app. +1. Within the conditional block, save the validation information into Session State, update the stage, and then rerun the app: ```python st.session_state.validation = { @@ -478,13 +485,13 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Correct errors**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and enter the `"correct"` stage. -1. In the second column, start a conditional block, and display a button labeled "Accept." +1. In the second column, start a conditional block, and display a button labeled "Accept:" ```python if cols[1].button("Accept"): ``` -1. Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. +1. Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State: ```python st.session_state.history.append( @@ -494,7 +501,7 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending st.session_state.validation = {} ``` -1. Update the stage to `"user"`, and rerun the app. +1. Update the stage to `"user"`, and rerun the app: ```python st.session_state.stage = "user" @@ -503,13 +510,13 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the third column, start a conditional block, and display a tertiary-type button labeled "Rewrite answer." +1. In the third column, start a conditional block, and display a tertiary-type button labeled "Rewrite answer:" ```python if cols[2].button("Rewrite answer", type="tertiary"): ``` -1. Within the conditional block, update the stage to `"rewrite"` and rerun the app. +1. Within the conditional block, update the stage to `"rewrite"` and rerun the app: ```python st.session_state.stage = "rewrite" @@ -518,32 +525,32 @@ When `st.session_state.stage` is `"validate"`, the app will validate the pending If the user clicks the "**Rewrite answer**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and enter the `"rewrite"` stage. - You don't need to save any information into `st.session_state.validation` since the `"rewrite"` stage does not use this information. + You don't need to save any information into `st.session_state.validation` because the `"rewrite"` stage does not use this information. ### Define the `"correct"` stage -When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app focuses the user on the first error in the list. When the user addresses an error, the error removed from the list, and the next error is highlighted in the next script run. This continues until all errors are removed. Then, the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. +When `st.session_state.stage` is `"correct"`, the user can correct or accept the errors identified in `st.session_state.validation`. With each script run, the app focuses the user on the first error in the list. When the user addresses an error, the error is removed from the list, and the next error is highlighted in the next script run. This continues until all errors are removed. Then, the user can accept the result, return to the `"validate"` stage, or go to the `"rewrite"` stage. -1. Start a conditional block for the `"correct"` stage. +1. Start a conditional block for the `"correct"` stage: ```python elif st.session_state.stage == "correct": ``` -1. For visual consistency, display a disabled chat input. +1. For visual consistency, display a disabled chat input: ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) ``` -1. For coding convenience, retrieve the validation information from Session State and save it into variables. +1. For coding convenience, retrieve the validation information from Session State and save it into variables: ```python response_sentences = st.session_state.validation["sentences"] validation_list = st.session_state.validation["valid"] ``` -1. Use your helper function to highlight the sentences with errors. Use gray for the highlight. +1. Use your helper function to highlight the sentences with errors. Use gray for the highlight: ```python highlighted_sentences = add_highlights( @@ -553,7 +560,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the In a following step, to focus the user on one error, you'll change the highlight color for one sentence. -1. Check if there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated sentence. +1. Check whether there are any errors in `validation_list`. If there are errors, get the index of the first one, and replace the Markdown highlight for the associated sentence: ```python if not all(validation_list): @@ -563,14 +570,14 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the `highlighted_sentences[focus]` begins with `":gray[:gray-background["`. Therefore, `highlighted_sentences[focus][11:]` removes the first eleven characters so you can prepend `":red[:red"` instead. -1. Set a fallback value for `focus` for when there are no errors. +1. Set a fallback value for `focus` for when there are no errors: ```python else: focus = None ``` -1. In a chat message container, display the highlighted response. To separate the response from the buttons which will follow, add a divider. +1. In a chat message container, display the highlighted response. To separate the response from the buttons that follow, add a divider: ```python with st.chat_message("assistant"): @@ -578,7 +585,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.divider() ``` -1. Start a conditional block: if there are errors, display a text input prefilled with the first error. This is the error you highlighted in red. +1. Start a conditional block: if there are errors, display a text input prefilled with the first error. This is the error you highlighted in red: ```python if focus is not None: @@ -589,13 +596,13 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the `value=response_sentences[focus]` prefills the text input with the sentence associated to `focus`. The user can edit it or replace the text entirely. You'll also add a button so they can choose to remove it instead. -1. To display buttons in a row, create two columns. +1. To display buttons in a row, create two columns: ```python cols = st.columns(2) ``` -1. In the first column, start a conditional block, and display a primary-type button labeled "Update." Disable the button if the text input is empty. +1. In the first column, start a conditional block, and display a primary-type button labeled "Update." Disable the button if the text input is empty: ```python if cols[0].button( @@ -603,7 +610,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the ): ``` -1. Within the conditional block, update the sentence and its validation. +1. Within the conditional block, update the sentence and its validation: ```python st.session_state.validation["sentences"][focus] = ( @@ -612,7 +619,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.session_state.validation["valid"][focus] = True ``` -1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app. +1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app: ```python st.session_state.pending = " ".join( @@ -623,7 +630,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Update**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. In the second column, start a conditional block, and display a button labeled "Remove." Within the conditional block, pop the sentence and validation information out of their lists in Session State. +1. In the second column, start a conditional block, and display a button labeled "Remove." Within the conditional block, pop the sentence and validation information out of their lists in Session State: ```python if cols[1].button("Remove"): @@ -631,7 +638,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.session_state.validation["valid"].pop(focus) ``` -1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app. +1. Update the complete response in `st.session_state.pending` with the new, resultant response, and rerun the app: ```python st.session_state.pending = " ".join( @@ -642,7 +649,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Remove**" button, the app will rerun and execute this conditional block. At the end of this block, the app will rerun again and continue in the `"correct"` stage with the next error highlighted. -1. Start an `else` block for when there are no errors. To display buttons in a row, create two columns. +1. Start an `else` block for when there are no errors. To display buttons in a row, create two columns: ```python else: @@ -651,7 +658,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the After a user has resolved all the errors, they need to confirm the final result. Instead of "**Update**" and "**Remove**" buttons, you'll display "**Accept**" and "**Re-validate**" buttons. -1. In the first column, start a conditional block, and display a primary-type button labeled "Accept." Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State. +1. In the first column, start a conditional block, and display a primary-type button labeled "Accept." Within the conditional block, save the pending message into the chat history, and clear the pending and validation information from Session State: ```python if cols[0].button("Accept", type="primary"): @@ -662,7 +669,7 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the st.session_state.validation = {} ``` -1. Update the stage to `"user"`, and rerun the app. +1. Update the stage to `"user"`, and rerun the app: ```python st.session_state.stage = "user" @@ -671,13 +678,13 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the If the user clicks the "**Accept**" button, the app will rerun and execute this block. At the end of this block, the app will rerun again and return to the `"user"` stage. -1. In the second column, start a conditional block, and display a button labeled "Re-validate." +1. In the second column, start a conditional block, and display a button labeled "Re-validate:" ```python if cols[1].button("Re-validate"): ``` -1. Within the conditional block, clear the validation information from Session State, update the stage to `"validate"`, and rerun the app. +1. Within the conditional block, clear the validation information from Session State, update the stage to `"validate"`, and rerun the app: ```python st.session_state.validation = {} @@ -691,19 +698,19 @@ When `st.session_state.stage` is `"correct"`, the user can correct or accept the When `st.session_state.stage` is `"rewrite"`, the user can freely edit the response in a text area. -1. Start a conditional block for the `"rewrite"` stage. +1. Start a conditional block for the `"rewrite"` stage: ```python elif st.session_state.stage == "rewrite": ``` -1. For visual consistency, display a disabled chat input. +1. For visual consistency, display a disabled chat input: ```python st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True) ``` -1. To let the user edit the pending response, in a chat message container, display a text area input. +1. To let the user edit the pending response, in a chat message container, display a text area input: ```python with st.chat_message("assistant"): @@ -712,7 +719,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo `value=st.session_state.pending` prefills the text area input with the pending response. The user can edit it or replace the text entirely. -1. Start a conditional block, and display a primary-type button labeled "Update." Disable the button if text area input is empty. +1. Start a conditional block, and display a primary-type button labeled "Update." Disable the button if text area input is empty: ```python if st.button( @@ -720,7 +727,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo ): ``` -1. Within the conditional block, add the new response to the chat history, and clear the pending and validation information from Session State.. +1. Within the conditional block, add the new response to the chat history, and clear the pending and validation information from Session State: ```python st.session_state.history.append({"role": "assistant", "content": new}) @@ -728,7 +735,7 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo st.session_state.validation = {} ``` -1. Update the stage to `"user"`, and rerun the app. +1. Update the stage to `"user"`, and rerun the app: ```python st.session_state.stage = "user" @@ -741,16 +748,16 @@ When `st.session_state.stage` is `"rewrite"`, the user can freely edit the respo ## Improve the example -Now that you have a working app, you can iteratively improve it. Since there are some common elements between stages, you may want to introduce additional functions to reduce duplicate code. You can use callbacks with the buttons so the app doesn't rerun twice in a row. Alternatively, you can handle more edge cases. +Now that you have a working app, you can iteratively improve it. Because there are some common elements between stages, you might want to introduce additional functions to reduce duplicate code. You can use callbacks with the buttons so the app doesn't rerun twice in a row. Alternatively, you can handle more edge cases. -The example includes some protection against saving an empty response, but it isn't comprehensive. If every sentence in a response is marked as an error, a user can remove each of them in the `"correct"` stage and accept the empty result. Try disabling the "**Accept**" button in the `"correct"` stage if the response is empty or changing it to "**Rewrite**." +The example includes some protection against saving an empty response, but it isn't comprehensive. If every sentence in a response is marked as an error, a user can remove each of them in the `"correct"` stage and accept the empty result. If the response is empty in the `"correct"` stage, consider disabling the "**Accept**" button or changing it to "**Rewrite**." To see another edge case, try this in the running example: 1. Submit a prompt. 1. Select "**Rewrite answer**." -1. In the text area, highlight all text and press delete. Do not click or tab outside of the text area. -1. Immediatly click "**Update**." +1. In the text area, highlight all text, and press `Delete`. Do not click or tab outside of the text area. +1. Immediately click the "**Update**" button. When you click a button with an unsubmitted value in another widget, Streamlit will update that widget's value and the button's value in succession before triggering the rerun. Because there isn't a rerun between updating the text area and updating the button, the "**Update**" button doesn't get disabled as expected. To correct this, you can add an extra check for an empty text area within the `"rewrite"` stage: @@ -767,4 +774,4 @@ When you click a button with an unsubmitted value in another widget, Streamlit w st.rerun() ``` -Now, if you repeat the steps mentioned previously, when the app reruns, the conditional block won't be executed even though the button triggered the rerun. The button will be disabled and the user can proceed as if they had just clicked or tabbed out of the text area. +Now, if you repeat the listed steps, when the app reruns, the conditional block won't be executed even though the button triggered the rerun. The button will be disabled and the user can proceed as if they had just clicked or tabbed out of the text area. diff --git a/content/develop/tutorials/multipage-apps/dynamic-navigation.md b/content/develop/tutorials/multipage-apps/dynamic-navigation.md index 66060dde5..e2e204d19 100644 --- a/content/develop/tutorials/multipage-apps/dynamic-navigation.md +++ b/content/develop/tutorials/multipage-apps/dynamic-navigation.md @@ -17,7 +17,7 @@ This tutorial uses `st.navigation` and `st.Page`, which were introduced in Strea ## Prerequisites -- The following must be installed in your Python environment: +- This tutorial requires the following version of Streamlit: ``` streamlit>=1.36.0 @@ -145,7 +145,7 @@ pg.run() ### Initialize your app 1. In `your_repository`, create a file named `streamlit_app.py`. -1. In a terminal, change directories to `your_repository`, and start your app. +1. In a terminal, change directories to `your_repository`, and start your app: ```bash streamlit run streamlit_app.py @@ -162,7 +162,9 @@ pg.run() 1. Save your `streamlit_app.py` file, and view your running app. 1. In your app, select "**Always rerun**", or press your "**A**" key. - Your preview will be blank but will automatically update as you save changes to `streamlit_app.py`. Return to your code. + Your preview will be blank but will automatically update as you save changes to `streamlit_app.py`. + +1. Return to your code. ### Add your page and image files diff --git a/content/menu.md b/content/menu.md index a4f7587c1..6cd8b4383 100644 --- a/content/menu.md +++ b/content/menu.md @@ -80,13 +80,13 @@ site_menu: - category: Develop / Concepts / App design / Working with timezones url: /develop/concepts/design/timezone-handling - category: Develop / Concepts / ADDITIONAL - - category: Develop / Concepts / Connections and secrets + - category: Develop / Concepts / Connections & secrets url: /develop/concepts/connections - - category: Develop / Concepts / Connections and secrets / Connecting to data + - category: Develop / Concepts / Connections & secrets / Connecting to data url: /develop/concepts/connections/connecting-to-data - - category: Develop / Concepts / Connections and secrets / Secrets management + - category: Develop / Concepts / Connections & secrets / Secrets management url: /develop/concepts/connections/secrets-management - - category: Develop / Concepts / Connections and secrets / Security reminders + - category: Develop / Concepts / Connections & secrets / Security reminders url: /develop/concepts/connections/security-reminders - category: Develop / Concepts / Custom components url: /develop/concepts/custom-components @@ -100,15 +100,15 @@ site_menu: url: /develop/concepts/custom-components/limitations - category: Develop / Concepts / Custom components / Component gallery url: https://streamlit.io/components - - category: Develop / Concepts / Configuration and theming + - category: Develop / Concepts / Configuration & theming url: /develop/concepts/configuration - - category: Develop / Concepts / Configuration and theming / Configuration options + - category: Develop / Concepts / Configuration & theming / Configuration options url: /develop/concepts/configuration/options - - category: Develop / Concepts / Configuration and theming / HTTPS support + - category: Develop / Concepts / Configuration & theming / HTTPS support url: /develop/concepts/configuration/https-support - - category: Develop / Concepts / Configuration and theming / Serving static files + - category: Develop / Concepts / Configuration & theming / Serving static files url: /develop/concepts/configuration/serving-static-files - - category: Develop / Concepts / Configuration and theming / Customize your theme + - category: Develop / Concepts / Configuration & theming / Customize your theme url: /develop/concepts/configuration/theming - category: Develop / Concepts / App testing url: /develop/concepts/app-testing @@ -126,15 +126,15 @@ site_menu: - category: Develop / API reference url: /develop/api-reference - category: Develop / API reference / PAGE ELEMENTS - - category: Develop / API reference / Write and magic + - category: Develop / API reference / Write & magic url: /develop/api-reference/write-magic - - category: Develop / API reference / Write and magic / st.write + - category: Develop / API reference / Write & magic / st.write url: /develop/api-reference/write-magic/st.write isVersioned: true - - category: Develop / API reference / Write and magic / st.write_stream + - category: Develop / API reference / Write & magic / st.write_stream url: /develop/api-reference/write-magic/st.write_stream isVersioned: true - - category: Develop / API reference / Write and magic / magic + - category: Develop / API reference / Write & magic / magic url: /develop/api-reference/write-magic/magic - category: Develop / API reference / Text elements url: /develop/api-reference/text @@ -383,33 +383,33 @@ site_menu: - category: Develop / API reference / Media elements / st.video url: /develop/api-reference/media/st.video isVersioned: true - - category: Develop / API reference / Layouts and containers + - category: Develop / API reference / Layouts & containers url: /develop/api-reference/layout - - category: Develop / API reference / Layouts and containers / st.columns + - category: Develop / API reference / Layouts & containers / st.columns url: /develop/api-reference/layout/st.columns isVersioned: true - - category: Develop / API reference / Layouts and containers / st.container + - category: Develop / API reference / Layouts & containers / st.container url: /develop/api-reference/layout/st.container isVersioned: true - - category: Develop / API reference / Layouts and containers / st.dialog + - category: Develop / API reference / Layouts & containers / st.dialog url: https://docs.streamlit.io/develop/api-reference/execution-flow/st.dialog isVersioned: true - - category: Develop / API reference / Layouts and containers / st.empty + - category: Develop / API reference / Layouts & containers / st.empty url: /develop/api-reference/layout/st.empty isVersioned: true - - category: Develop / API reference / Layouts and containers / st.expander + - category: Develop / API reference / Layouts & containers / st.expander url: /develop/api-reference/layout/st.expander isVersioned: true - - category: Develop / API reference / Layouts and containers / st.form + - category: Develop / API reference / Layouts & containers / st.form url: https://docs.streamlit.io/develop/api-reference/execution-flow/st.form isVersioned: true - - category: Develop / API reference / Layouts and containers / st.popover + - category: Develop / API reference / Layouts & containers / st.popover url: /develop/api-reference/layout/st.popover isVersioned: true - - category: Develop / API reference / Layouts and containers / st.sidebar + - category: Develop / API reference / Layouts & containers / st.sidebar url: /develop/api-reference/layout/st.sidebar isVersioned: true - - category: Develop / API reference / Layouts and containers / st.tabs + - category: Develop / API reference / Layouts & containers / st.tabs url: /develop/api-reference/layout/st.tabs isVersioned: true - category: Develop / API reference / Chat elements @@ -466,18 +466,18 @@ site_menu: - category: Develop / API reference / Third-party components url: https://streamlit.io/components - category: Develop / API reference / APPLICATION LOGIC - - category: Develop / API reference / Navigation and pages + - category: Develop / API reference / Navigation & pages url: /develop/api-reference/navigation - - category: Develop / API reference / Navigation and pages / st.navigation + - category: Develop / API reference / Navigation & pages / st.navigation url: /develop/api-reference/navigation/st.navigation isVersioned: true - - category: Develop / API reference / Navigation and pages / st.Page + - category: Develop / API reference / Navigation & pages / st.Page url: /develop/api-reference/navigation/st.page isVersioned: true - - category: Develop / API reference / Navigation and pages / st.page_link + - category: Develop / API reference / Navigation & pages / st.page_link url: https://docs.streamlit.io/develop/api-reference/widgets/st.page_link isVersioned: true - - category: Develop / API reference / Navigation and pages / st.switch_page + - category: Develop / API reference / Navigation & pages / st.switch_page url: /develop/api-reference/navigation/st.switch_page isVersioned: true - category: Develop / API reference / Execution flow @@ -505,67 +505,67 @@ site_menu: isVersioned: true isDeprecated: true visible: false - - category: Develop / API reference / Caching and state + - category: Develop / API reference / Caching & state url: /develop/api-reference/caching-and-state - - category: Develop / API reference / Caching and state / st.cache_data + - category: Develop / API reference / Caching & state / st.cache_data url: /develop/api-reference/caching-and-state/st.cache_data isVersioned: true - - category: Develop / API reference / Caching and state / st.cache_resource + - category: Develop / API reference / Caching & state / st.cache_resource url: /develop/api-reference/caching-and-state/st.cache_resource isVersioned: true - - category: Develop / API reference / Caching and state / st.experimental_memo + - category: Develop / API reference / Caching & state / st.experimental_memo url: /develop/api-reference/caching-and-state/st.experimental_memo isVersioned: true isDeprecated: true visible: false - - category: Develop / API reference / Caching and state / st.experimental_singleton + - category: Develop / API reference / Caching & state / st.experimental_singleton url: /develop/api-reference/caching-and-state/st.experimental_singleton isVersioned: true isDeprecated: true visible: false - - category: Develop / API reference / Caching and state / st.session_state + - category: Develop / API reference / Caching & state / st.session_state url: /develop/api-reference/caching-and-state/st.session_state - - category: Develop / API reference / Caching and state / st.query_params + - category: Develop / API reference / Caching & state / st.query_params url: /develop/api-reference/caching-and-state/st.query_params isVersioned: true - - category: Develop / API reference / Caching and state / st.experimental_get_query_params + - category: Develop / API reference / Caching & state / st.experimental_get_query_params url: /develop/api-reference/caching-and-state/st.experimental_get_query_params isVersioned: true isDeprecated: true - - category: Develop / API reference / Caching and state / st.experimental_set_query_params + - category: Develop / API reference / Caching & state / st.experimental_set_query_params url: /develop/api-reference/caching-and-state/st.experimental_set_query_params isVersioned: true isDeprecated: true - - category: Develop / API reference / Connections and secrets + - category: Develop / API reference / Connections & secrets url: /develop/api-reference/connections - - category: Develop / API reference / Connections and secrets / SECRETS - - category: Develop / API reference / Connections and secrets / st.secrets + - category: Develop / API reference / Connections & secrets / SECRETS + - category: Develop / API reference / Connections & secrets / st.secrets url: /develop/api-reference/connections/st.secrets - - category: Develop / API reference / Connections and secrets / secrets.toml + - category: Develop / API reference / Connections & secrets / secrets.toml url: /develop/api-reference/connections/secrets.toml - - category: Develop / API reference / Connections and secrets / CONNECTIONS - - category: Develop / API reference / Connections and secrets / st.connection + - category: Develop / API reference / Connections & secrets / CONNECTIONS + - category: Develop / API reference / Connections & secrets / st.connection url: /develop/api-reference/connections/st.connection isVersioned: true - - category: Develop / API reference / Connections and secrets / SnowflakeConnection + - category: Develop / API reference / Connections & secrets / SnowflakeConnection url: /develop/api-reference/connections/st.connections.snowflakeconnection isVersioned: true - - category: Develop / API reference / Connections and secrets / SQLConnection + - category: Develop / API reference / Connections & secrets / SQLConnection url: /develop/api-reference/connections/st.connections.sqlconnection isVersioned: true - - category: Develop / API reference / Connections and secrets / BaseConnection + - category: Develop / API reference / Connections & secrets / BaseConnection url: /develop/api-reference/connections/st.connections.baseconnection isVersioned: true - - category: Develop / API reference / Connections and secrets / st.experimental_connection + - category: Develop / API reference / Connections & secrets / st.experimental_connection url: /develop/api-reference/connections/st.experimental_connection isVersioned: true isDeprecated: true visible: false - - category: Develop / API reference / Connections and secrets / SnowparkConnection + - category: Develop / API reference / Connections & secrets / SnowparkConnection url: /develop/api-reference/connections/st.connections.snowparkconnection isVersioned: true isDeprecated: true - - category: Develop / API reference / Connections and secrets / ExperimentalBaseConnection + - category: Develop / API reference / Connections & secrets / ExperimentalBaseConnection url: /develop/api-reference/connections/st.connections.experimentalbaseconnection isVersioned: true isDeprecated: true @@ -695,7 +695,7 @@ site_menu: url: /develop/tutorials/chat-and-llm-apps/llm-quickstart - category: Develop / Tutorials / Chat & LLM apps / Get chat response feedback url: /develop/tutorials/chat-and-llm-apps/chat-response-feedback - - category: Develop / Tutorials / Chat & LLM apps / Validate & edit chat responses + - category: Develop / Tutorials / Chat & LLM apps / Validate and edit chat responses url: /develop/tutorials/chat-and-llm-apps/validate-and-edit-chat-responses - category: Develop / Quick reference url: /develop/quick-reference From 062c35b399abab515f44f8bf05881160211c5570 Mon Sep 17 00:00:00 2001 From: Debbie Matthews Date: Mon, 27 Jan 2025 19:05:22 -0800 Subject: [PATCH 10/10] Edits --- .../tutorials/elements/charts/annotate-altair-chart.md | 2 +- .../develop/tutorials/elements/dataframes/row_selections.md | 2 +- .../fragments/create-a-multiple-container-fragment.md | 2 +- .../fragments/start-and-stop-fragment-auto-reruns.md | 2 +- .../fragments/trigger-a-full-script-rerun-from-a-fragment.md | 2 +- content/develop/tutorials/llms/chat-response-feedback.md | 4 ++-- content/develop/tutorials/llms/chat-response-revision.md | 2 +- .../develop/tutorials/multipage-apps/dynamic-navigation.md | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/content/develop/tutorials/elements/charts/annotate-altair-chart.md b/content/develop/tutorials/elements/charts/annotate-altair-chart.md index fb1d86e06..c9e8392d0 100644 --- a/content/develop/tutorials/elements/charts/annotate-altair-chart.md +++ b/content/develop/tutorials/elements/charts/annotate-altair-chart.md @@ -141,7 +141,7 @@ st.altair_chart(combined_chart, use_container_width=True) - You'll define a chart using `altair`. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/elements/dataframes/row_selections.md b/content/develop/tutorials/elements/dataframes/row_selections.md index 2ea0fd8cc..6e63f5995 100644 --- a/content/develop/tutorials/elements/dataframes/row_selections.md +++ b/content/develop/tutorials/elements/dataframes/row_selections.md @@ -160,7 +160,7 @@ Here's a look at what you'll build: - You'll manipulate the data with `pandas`. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md index 6b677c116..970d282c4 100644 --- a/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/create-a-multiple-container-fragment.md @@ -117,7 +117,7 @@ with st.sidebar: You'll use `time.sleep()` to slow things down and see the fragments working. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md index 56ea955a5..7faa3f7a5 100644 --- a/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md +++ b/content/develop/tutorials/execution-flow/fragments/start-and-stop-fragment-auto-reruns.md @@ -126,7 +126,7 @@ show_latest_data() - The data will have `datetime.datetime` index values. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md index 36000ee94..7463d2283 100644 --- a/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md +++ b/content/develop/tutorials/execution-flow/fragments/trigger-a-full-script-rerun-from-a-fragment.md @@ -165,7 +165,7 @@ with monthly: - Optional: To help add emphasis at the end, you'll use `time.sleep()` to slow things down and see the fragment working. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/llms/chat-response-feedback.md b/content/develop/tutorials/llms/chat-response-feedback.md index 6ed4db7b4..dec67a318 100644 --- a/content/develop/tutorials/llms/chat-response-feedback.md +++ b/content/develop/tutorials/llms/chat-response-feedback.md @@ -7,7 +7,7 @@ slug: /develop/tutorials/chat-and-llm-apps/chat-response-feedback A common task in a chat app is to collect user feedback about an LLM's responses. Streamlit includes `st.feedback` to conveniently collect user sentiment by displaying a group of selectable sentiment icons. -This tutorial uses Streamlit's chat commands along with `st.feedback` to build a simple chat app that collects user feedback about each response. +This tutorial uses Streamlit's chat commands and `st.feedback` to build a simple chat app that collects user feedback about each response. ## Applied concepts @@ -108,7 +108,7 @@ if prompt := st.chat_input("Say something"): You'll use `time` to build a simulated chat response stream. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md index 0aa5951ca..30d7883dc 100644 --- a/content/develop/tutorials/llms/chat-response-revision.md +++ b/content/develop/tutorials/llms/chat-response-revision.md @@ -212,7 +212,7 @@ elif st.session_state.stage == "rewrite": You'll use `lorem`, `random`, and `time` to build a simulated chat response stream. 1. Save your `app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `app.py`. diff --git a/content/develop/tutorials/multipage-apps/dynamic-navigation.md b/content/develop/tutorials/multipage-apps/dynamic-navigation.md index e2e204d19..e71e0c4dd 100644 --- a/content/develop/tutorials/multipage-apps/dynamic-navigation.md +++ b/content/develop/tutorials/multipage-apps/dynamic-navigation.md @@ -160,7 +160,7 @@ pg.run() ``` 1. Save your `streamlit_app.py` file, and view your running app. -1. In your app, select "**Always rerun**", or press your "**A**" key. +1. In your app, select "**Always rerun**", or press the "**A**" key. Your preview will be blank but will automatically update as you save changes to `streamlit_app.py`.