Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[Draft] Explaining Widget Behavior #643

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 35 additions & 34 deletions content/kb/tutorials/llm/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ slug: /knowledge-base/tutorials/llm-quickstart
---

# LLM quickstart

## OpenAI, LangChain, and Streamlit in 18 lines of code

In this tutorial, you will build a Streamlit LLM app that can generate text from a user-provided prompt. This Python app will use the LangChain framework and Streamlit. Optionally, you can deploy your app to [Streamlit Community Cloud](https://streamlit.io/cloud) when you're done.

*This tutorial is adapted from a blog post by Chanin Nantesanamat: [LangChain tutorial #1: Build an LLM-powered app in 18 lines of code](https://blog.streamlit.io/langchain-tutorial-1-build-an-llm-powered-app-in-18-lines-of-code/).*
_This tutorial is adapted from a blog post by Chanin Nantesanamat: [LangChain tutorial #1: Build an LLM-powered app in 18 lines of code](https://blog.streamlit.io/langchain-tutorial-1-build-an-llm-powered-app-in-18-lines-of-code/)._

<Cloud src="https://doc-tutorial-llm-18-lines-of-code.streamlit.app/?embed=true" height="600" />

Expand Down Expand Up @@ -75,64 +76,64 @@ To start, create a new Python file and save it as `streamlit_app.py` in the roo

1. Import the necessary Python libraries.

```python
import streamlit as st
from langchain.llms import OpenAI
```
```python
import streamlit as st
from langchain.llms import OpenAI
```

2. Create the app's title using `st.title`.

```python
st.title('🦜🔗 Quickstart App')
```
```python
st.title('🦜🔗 Quickstart App')
```

3. Add a text input box for the user to enter their OpenAI API key.

```python
openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
```
```python
openai_api_key = st.sidebar.text_input('OpenAI API Key', type='password')
```

4. Define a function to authenticate to OpenAI API with the user's key, send a prompt, and get an AI-generated response. This function accepts the user's prompt as an argument and displays the AI-generated response in a blue box using `st.info`.

```python
def generate_response(input_text):
llm = OpenAI(temperature=0.7, openai_api_key=openai_api_key)
st.info(llm(input_text))
```
```python
def generate_response(input_text):
llm = OpenAI(temperature=0.7, openai_api_key=openai_api_key)
st.info(llm(input_text))
```

5. Finally, use `st.form()` to create a text box (`st.text_area()`) for user input. When the user clicks `Submit`, the `generate-response()` function is called with the user's input as an argument.

```python
with st.form('my_form'):
text = st.text_area('Enter text:', 'What are the three key pieces of advice for learning how to code?')
submitted = st.form_submit_button('Submit')
if not openai_api_key.startswith('sk-'):
st.warning('Please enter your OpenAI API key!', icon='⚠')
if submitted and openai_api_key.startswith('sk-'):
generate_response(text)
```
```python
with st.form('my_form'):
text = st.text_area('Enter text:', 'What are the three key pieces of advice for learning how to code?')
submitted = st.form_submit_button('Submit')
if not openai_api_key.startswith('sk-'):
st.warning('Please enter your OpenAI API key!', icon='⚠')
if submitted and openai_api_key.startswith('sk-'):
generate_response(text)
```

6. Remember to save your file!
7. Return to your computer's terminal to run the app.

```bash
streamlit run streamlit_app.py
```
```bash
streamlit run streamlit_app.py
```

## Deploying the app

To deploy the app to the Streamlit Cloud, follow these steps:

1. Create a GitHub repository for the app. Your repository should contain two files:

```
your-repository/
├── streamlit_app.py
└── requirements.txt
```
```
your-repository/
├── streamlit_app.py
└── requirements.txt
```

1. Go to [Streamlit Community Cloud](http://share.streamlit.io), click the `New app` button from your workspace, then specify the repository, branch, and main file path. Optionally, you can customize your app's URL by choosing a custom subdomain.
2. Click the `Deploy!` button.
1. Click the `Deploy!` button.

Your app will now be deployed to Streamlit Community Cloud and can be accessed from around the world! 🌎

Expand Down
184 changes: 174 additions & 10 deletions content/library/advanced-features/advanced-widget-behavior.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,180 @@
---
title: Widget semantics
slug: /library/advanced-features/widget-semantics
title: Widget behavior
slug: /library/advanced-features/widget-behavior
---

# Advanced notes on widget behavior
# Understanding widget behavior

Widgets are magical and often work how you want. But they can have surprising behavior in some situations. Here is a high-level, abstract description of widget behavior, including some common edge-cases:
Widgets are magical and often work how you want. But they can have surprising behavior in some situations. Understanding the different parts of a widget and the precise order in which events occur is helpful for achieving desired results.

1. If you call a widget function before the widget state exists, the widget state defaults to a value. This value depends on the widget and its arguments.
2. A widget function call returns the current widget state value. The return value is a simple Python type, and the exact type depends on the widget and its arguments.
3. Widget states depend on a particular session (browser connection). The actions of one user do not affect the widgets of any other user.
4. A widget's identity depends on the arguments passed to the widget function. If those change, the call will create a new widget (with a default value, per 1).
5. If you don't call a widget function in a script run, we neither store the widget state nor render the widget. If you call a widget function with the same arguments later, Streamlit treats it as a new widget.
## Anatomy of a widget

4 and 5 are the most likely to be surprising and may pose a problem for some application designs. When you want to persist widget state for recreating a widget, use [Session State](/library/api-reference/session-state) to work around 5.
There are four parts to every widget:

1. The frontend component as seen by the user.
2. The backend value or value as seen through `st.session_state`.
3. The return value given by the widget's function.
4. The key of the widget used to access its value via `st.session_state`.

### Session dependence

These parts are dependent on a particular session (browser connection). The actions of one user do not affect the widgets of any other user. Furthermore, if a user opens up multiple tabs to access an app, then each tab will be a unique session. Hence, changing a widget in one tab will not affect the same widget in another tab.

### Data types

The backend value (as seen through `st.session_state`) and the return value of a widget are of simple Python types. For example, `st.button` returns a boolean value and will have the same boolean value saved in `st.session_state`.

### Widget keys

Widget keys serve two purposes:

1. Distinguishing two otherwise identical widgets.
2. Creating a means to access and manipulate the widget's value through
`st.session_state`.

A widget's identity depends on the arguments passed to the widget function. If you have two widgets of the same type with the same arguments, you will get a `DuplicateWidgetID` error. In this case, you will need to assign unique keys to the two widgets.

### Persistence

If a widget's function is not called during a script run, then none of its parts will be retained, including its value in `st.session_state`. If you have assigned a key to a widget and you navigate away from that widget, its key and associated value in `st.session_state` will be deleted. Even temporarily hiding a widget will cause it to reset when it reappears; Streamlit will treat it like a new widget.

### Statefulness

As long as the defining parameters of a widget remain the same and that widget is continuously rendered on the frontend, then it will be stateful. If any of the defining parameters of a widget change, Streamlit will see it as a new widget and it will reset. The use of manually assigned keys and default values is particularly important in this case.

In this example, we have a slider with whose min and max values are changed. Try setting values on the slider and changing the min or max values to see how each behaves.

```python
import streamlit as st

cols = st.columns([2,1,2])
minimum = cols[0].number_input('Minimum', 1, 5)
maximum = cols[2].number_input('Maximum', 6, 10, 10)

st.slider('No default, no key', minimum, maximum)

st.slider('With default, no key', minimum, maximum, value=5)

st.slider('No default, with key', minimum, maximum, key='a')

st.slider('With default, with key', minimum, maximum, value=5, key='b')
```

#### No default, no key

As soon as the min or max value is changed, the slider will reset to the minimum value. The changing of the min or max value makes it a "new" widget from Streamlit's perspective and so it is recreated with the default value equal to the minimum value since it is not defined otherwise.

#### With default, no key

As with the previous case, a change to the min or max value will result in the widget being seen as "new" and thus recreated. Since a default value of 5 is defined, the widget will reset to 5 whenever the min or max is changed.

#### No default, with key

This is closer to expected behavior. Since the widget has a key, Streamlit has a way of reconnecting the state of the widget even with a change in min or max value that would otherwise make the widget be treated as "new." In this case, the change in min and max does cause the widget to be reconstructed, but the existence of the key in `st.session_state` with an assigned value causes the new widget's state to be overwritten from its otherwise default value. This example is not perfect though; try this:

1. Set the slider to a value of 10.
2. Reduce the max to 9.
3. See the slider still appear to have a value of 10.
4. Set the slider to a value less than 10.
5. See the max of the slider update to 9.
The value of the slider in `st.session_state` can become invalid if the current selection becomes out-of-range through an update of min or max. A corrected example follows this one.

#### With default, with key

This is a tricky case. The default value and key are at odds.The default value trumps the value in `st.session_state` and the slider behaves the same as "With default, no key." As before, a change in min or max value results in a "new" widget. If you create a new widget with both a default value and a key, one of two things will happen:

1. The key was associated to another widget from the previous script run and the new widget with load with the default value.
2. The key was not associated to another widget from the previous script run and the new widget will load with the value assigned to the key (with a warning if it's different than the default value).

#### Best practice

Generally speaking, if you will be dynamically modifying a widget, stick to using a key and don't assign the default value through the optional parameter. Here is an exapanded example to handle this case:

```python
import streamlit as st

# Set default value
if 'a' not in st.session_state:
st.session_state.a = 5

cols = st.columns(2)
minimum = cols[0].number_input('Min', 1, 5, key='min')
maximum = cols[1].number_input('Max', 6, 10, 10, key='max')

def update_value():
st.session_state.a = min(st.session_state.a, maximum)
st.session_state.a = max(st.session_state.a, minimum)

# Validate the slider value before rendering
update_value()
st.slider('A', minimum, maximum, key='a')
```

## Order of operations

When a user interacts with a widget, the order of logic is:

1. Its value in `st.session_state` is updated.
2. The callback function (if any) is executed.
3. The page reruns, with the widget function returning its new value.

If the callback function writes anything to the screen, that content will appear above the rest of the page. A callback function runs as a prefix to the page reloading. Consequently, that means anything written via a callback function will disappear as soon as the user performs their next action. Other widgets should generally not be created within a callback function.

<Note>

If a callback function is passed any args or kwargs, those arguments will be established when the widget is rendered. In particular, if you want to use a widget's new value in its own callback function, you cannot pass that value to the callback function via the `args` parameter; you will have to assign a key to the widget and look up its new value using a call to `st.session_state` _within the callback function_.

</Note>

[//]: # "TODO: simple example and form example"

## Life cycle

When a widget function is called, Streamlit will check if it already has a widget with the same parameters. Streamlit will reconnect if it thinks the widget already exists. Otherwise, it will make a new one.

Streamlit identifies widgets as "being the same" based on their construction parameters: labels, min or max values, default values, placeholder texts, help text, and keys are all used by Streamlit to identify a widget. On the other hand, callback functions, callback args and kwargs, disabling options, and label visibility do not affect a widget's identity.

### Calling a widget function when the widget doesn't already exist

1. Streamlit will build the frontend and backend parts of the widget.
2. If the widget has been assigned a key, Streamlit will check if that key already exists in session state.
a. If it exists and is not currently associated to another widget, Streamlit will attach to that key and take on its value for the widget.
b. Otherwise, it will assign the default value to the key in `st.session_state`.
3. If there are args or kwargs for a callback function, they are computed and saved at this point in time.
4. The default value is then returned by the function.

Step 2 can be tricky. If you have a widget:

```python
st.number_input('Alpha',key='A')
```

and you change it on a page rerun to:

```python
st.number_input('Beta',key='A')
```

Streamlit will see that as a new widget because of the label change. The key `'A'` will be considered part of the widget labeled `'Alpha'` and will not be attached as-is to the new widget labeled `'Beta'`. Streamlit will destroy `st.session_state.A` and recreate it with the default value.

If a widget attaches to a pre-existing key when created and is also manually assigned a default value, you will get a warning if there is a disparity. If you want to control a widget's value through `st.session_state`, initialize the widget's value through `st.session_state` and avoid the default value argument to prevent conflict.

[//]: # "TODO: simple example and multipage example"

### Calling a widget function when the widget already exists

1. Streamlit will connect to the existing frontend and backend parts.
2. If the widget has a key that was deleted from `st.session_state`, then Streamlit will recreate the key using the current frontend value. (e.g Deleting a key will not revert the widget to a default value.)
3. It will return the current value of the widget.

[//]: # "TODO: Examples with the key copy workaround and pseudo key workflow"

### Cleaning up

When Streamlit gets to the end of a page run, it will delete the data for any widgets it has in memory that were not rendered on the screen. Most importantly, that means Streamlit will delete all key-value pairs in `st.session_state` associated to a widget not currently on screen.

If you navigate away from a widget with some key `'my_key'` and save data to `st.session_state.my_key` on the new page, you will interrupt the widget cleanup process and prevent the key-value pair from being deleted. The rest of the widget will still be deleted and you will be left with an unassociated key-value in `st.session_state`. To preserve a widget's data, you can do something as trivial as resaving the key at the top of every page:

```python
st.session_state.my_key = st.session_state.my_key
```
6 changes: 3 additions & 3 deletions content/library/advanced-features/dataframes.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,9 @@ if st.button('Get results'):

In addition to column configuration, `st.dataframe` and `st.data_editor` have a few more parameters to customize the display of your dataframe.

* `hide_index` : Set to `True` to hide the dataframe's index.
* `column_order` : Pass a list of column labels to specify the order of display.
* `disabled` : Pass a list of column labels to disable them from editing. This let's you avoid disabling them individually.
- `hide_index` : Set to `True` to hide the dataframe's index.
- `column_order` : Pass a list of column labels to specify the order of display.
- `disabled` : Pass a list of column labels to disable them from editing. This let's you avoid disabling them individually.

## Handling large datasets

Expand Down
4 changes: 2 additions & 2 deletions content/library/advanced-features/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ Working with timezones can be tricky. This section provides a high-level descrip

</RefCard>

<RefCard href="/library/advanced-features/widget-semantics" size="full">
<RefCard href="/library/advanced-features/widget-behavior" size="full">

##### Advanced notes on widget behavior
##### Understanding widget behavior

Widgets are magical and often work how you want. But they can have surprising behavior in some situations. This section provides is a high-level, abstract description of widget behavior, including some common edge-cases.

Expand Down
2 changes: 1 addition & 1 deletion content/library/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ _Release date: Sep 22, 2021_
- 💅 We updated our UI to a more polished look with a new font.
- 🎨 We now support `theme.base` in the theme object when it's sent to custom components.
- 🧠 We've modified session state to reset widgets if any of their arguments changed even if they provide a key.
- Some widget behavior may have changed, but we believe this change makes the most sense. We have added a section to [our documentation](/library/advanced-features/widget-semantics) describing how they behave.
- Some widget behavior may have changed, but we believe this change makes the most sense. We have added a section to [our documentation](/library/advanced-features/widget-behavior) describing how they behave.

**Other Changes**

Expand Down
4 changes: 2 additions & 2 deletions content/streamlit-cloud/deploy-your-app/secrets-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ If you need to add or edit your secrets for an app that is already deployed, you
2. Click the overflow menu icon (<i style={{ verticalAlign: "-.25em" }} className={{ class: "material-icons-sharp" }}>more_vert</i>) for your app.
3. Click "**Settings**".
![Access your app settings from your workspace](/images/streamlit-community-cloud/workspace-app-settings.png)
3. A modal will appear. Click "**Secrets**" on the left.
4. A modal will appear. Click "**Secrets**" on the left.
![Access your secrets through your app settings](/images/streamlit-community-cloud/workspace-app-settings-secrets.png)
4. After you edit your secrets, click "**Save**". It might take a minute for the update to be propagated to your app, but the new values will be reflected when the app re-runs.
5. After you edit your secrets, click "**Save**". It might take a minute for the update to be propagated to your app, but the new values will be reflected when the app re-runs.

### Develop locally with secrets

Expand Down
7 changes: 4 additions & 3 deletions content/streamlit-cloud/get-started/create-your-account.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ Streamlit Community Cloud accounts have two underlying identities: primary and s
## #

Although you can begin the sign-up process with GitHub, we recommend starting with Google or email in order to have a complete account from the start.
* [Step 1: Primary identity](#step-1-primary-identity) (Google or email)
* [Step 2: Source control](#step-2-source-control) (GitHub)
* [Step 3: Set up your account](#step-3-set-up-your-account)

- [Step 1: Primary identity](#step-1-primary-identity) (Google or email)
- [Step 2: Source control](#step-2-source-control) (GitHub)
- [Step 3: Set up your account](#step-3-set-up-your-account)

### Step 1: Primary identity

Expand Down
Loading