diff --git a/content/develop/tutorials/elements/charts/annotate-altair-chart.md b/content/develop/tutorials/elements/charts/annotate-altair-chart.md
index b0c71e31c..e86af00ac 100644
--- a/content/develop/tutorials/elements/charts/annotate-altair-chart.md
+++ b/content/develop/tutorials/elements/charts/annotate-altair-chart.md
@@ -117,13 +117,13 @@ 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
```
- 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:
@@ -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. In your app, 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..b618619ca 100644
--- a/content/develop/tutorials/elements/dataframes/row_selections.md
+++ b/content/develop/tutorials/elements/dataframes/row_selections.md
@@ -135,13 +135,13 @@ 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
```
- 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:
@@ -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. In your app, 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..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
@@ -99,13 +99,13 @@ 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
```
- 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:
@@ -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. In your app, 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..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
@@ -102,13 +102,13 @@ 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
```
- 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:
@@ -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. In your app, 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..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
@@ -137,13 +137,13 @@ 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
```
- 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:
@@ -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. In your app, 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/_index.md b/content/develop/tutorials/llms/_index.md
index 7d1be65ba..f94ddfb32 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
@@ -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
new file mode 100644
index 000000000..ca6502a45
--- /dev/null
+++ b/content/develop/tutorials/llms/chat-response-feedback.md
@@ -0,0 +1,306 @@
+---
+title: Collect user feedback about LLM responses
+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 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.
+
+## 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](/develop/concepts/architecture/session-state).
+
+## Summary
+
+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:
+
+
+
+```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(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 = []
+
+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 because 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. 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. It's okay to skip this section if you just want to copy the function.
+
+
+
+```python
+def chat_stream(prompt):
+ response = f'You said, "{prompt}" ...interesting.'
+ for char in response:
+ yield char
+ time.sleep(0.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 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.
+
+ ```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 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.
+
+ ```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 specified 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
+ ```
+
+ 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.
+
+ ```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. 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.
+
+ ```python
+ def save_feedback(index):
+ st.session_state.history[index]["feedback"] = st.session_state[f"feedback_{index}"]
+ ```
+
+ 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.
+
+ ```diff
+ st.feedback(
+ "thumbs",
+ key=f"feedback_{i}",
+ disabled=feedback is not None,
+ + on_change=save_feedback,
+ + args=[i],
+ )
+ ```
+
+ 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 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"):
+ 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 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:
+
+ ```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. 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"):
+ 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 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.
+
+1. Save your file and go to your browser to try your new app.
+
+### Optional: Change the feeback behavior
+
+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):
+ 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.
+
+```diff
+ 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/develop/tutorials/llms/chat-response-revision.md b/content/develop/tutorials/llms/chat-response-revision.md
new file mode 100644
index 000000000..34dbdfa62
--- /dev/null
+++ b/content/develop/tutorials/llms/chat-response-revision.md
@@ -0,0 +1,770 @@
+---
+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](/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 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:
+
+
+
+```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.session_state.pending = " ".join(
+ st.session_state.validation["sentences"]
+ )
+ 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()
+```
+
+
+
+
+
+## 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 because 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 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 three to nine random sentences. It's okay to skip this section if you just want to copy the function.
+
+
+
+```python
+def chat_stream():
+ for i in range(randint(3, 9)):
+ yield lorem.sentence() + " "
+ time.sleep(0.2)
+```
+
+
+
+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 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.
+
+ ```python
+ 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 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.
+
+
+
+```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_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.
+
+ ```python
+ response_sentences = [
+ sentence.strip(". ") + "."
+ for sentence in response_sentences
+ if sentence.strip(". ") != ""
+ ]
+ ```
+
+ 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 sentence validations. 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_sentences
+ ]
+ ```
+
+ 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.
+
+ ```python
+ 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 marked as errors. Create a helper function to add text and background color to the detected errors.
+
+
+
+```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 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).
+
+1. Use list comprehension to return a modified list of sentences that include the Markdown highlights where errors were detected.
+
+ ```python
+ return [
+ f":{text}[:{bg}-background[" + sentence + "]]" if not is_valid else sentence
+ for sentence, is_valid in zip(response_sentences, validation_list)
+ ]
+ ```
+
+### Initialize and display your chat history
+
+Your app will use Session State to track the stages of the validation 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. 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 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.
+
+ ```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. 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.
+
+ 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.
+
+ ```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, 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"):
+ 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 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
+
+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.
+
+ ```python
+ 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. For visual consistency, display a disabled chat input.
+
+ ```python
+ st.chat_input("Accept, correct, or rewrite the answer above.", disabled=True)
+ ```
+
+ 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_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.
+
+ ```python
+ with st.chat_message("assistant"):
+ st.markdown(" ".join(highlighted_sentences))
+ st.divider()
+ ```
+
+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.
+
+ ```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 = {
+ "sentences": response_sentences,
+ "valid": validation_list,
+ }
+ st.session_state.stage = "correct"
+ st.rerun()
+ ```
+
+ 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."
+
+ ```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 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.
+
+ ```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 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.
+
+ ```python
+ elif st.session_state.stage == "correct":
+ ```
+
+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.
+
+ ```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.
+
+ ```python
+ highlighted_sentences = add_highlights(
+ response_sentences, validation_list, "gray", "gray"
+ )
+ ```
+
+ 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.
+
+ ```python
+ if not all(validation_list):
+ focus = validation_list.index(False)
+ 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` 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.
+
+ ```python
+ with st.chat_message("assistant"):
+ st.markdown(" ".join(highlighted_sentences))
+ 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.
+
+ ```python
+ if focus is not None:
+ new_sentence = st.text_input(
+ "Replacement text:", value=response_sentences[focus]
+ )
+ ```
+
+ `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.
+
+ ```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.
+
+ ```python
+ if cols[0].button(
+ "Update", type="primary", disabled=len(new_sentence.strip()) < 1
+ ):
+ ```
+
+1. Within the conditional block, update the sentence and its validation.
+
+ ```python
+ st.session_state.validation["sentences"][focus] = (
+ new_sentence.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["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 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"):
+ 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()
+ ```
+
+ 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.
+
+ ```python
+ else:
+ cols = st.columns(2)
+ ```
+
+ 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.
+
+ ```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 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.
+
+ ```python
+ st.session_state.validation = {}
+ st.session_state.stage = "validate"
+ 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 `"validate"` 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. 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.
+
+ ```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. 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(
+ "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.
+
+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**" 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:
+
+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.
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/develop/tutorials/multipage-apps/dynamic-navigation.md b/content/develop/tutorials/multipage-apps/dynamic-navigation.md
index e58099a87..66060dde5 100644
--- a/content/develop/tutorials/multipage-apps/dynamic-navigation.md
+++ b/content/develop/tutorials/multipage-apps/dynamic-navigation.md
@@ -145,13 +145,13 @@ 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
```
- 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:
@@ -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. In your app, 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
diff --git a/content/menu.md b/content/menu.md
index ce6fdea1f..a4f7587c1 100644
--- a/content/menu.md
+++ b/content/menu.md
@@ -687,12 +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 / 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
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;