diff --git a/.changeset/rich-ducks-grow.md b/.changeset/rich-ducks-grow.md
new file mode 100644
index 0000000000000..993163ee6dec4
--- /dev/null
+++ b/.changeset/rich-ducks-grow.md
@@ -0,0 +1,7 @@
+---
+"@gradio/chatbot": minor
+"gradio": minor
+"website": minor
+---
+
+feat:Add support for thinking LLMs directly in `gr.ChatInterface`
diff --git a/demo/chatinterface_nested_thoughts/run.ipynb b/demo/chatinterface_nested_thoughts/run.ipynb
new file mode 100644
index 0000000000000..cf756f381fe51
--- /dev/null
+++ b/demo/chatinterface_nested_thoughts/run.ipynb
@@ -0,0 +1 @@
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_nested_thoughts"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from gradio import ChatMessage\n", "import time\n", "\n", "sleep_time = 0.1\n", "long_sleep_time = 1\n", "\n", "def generate_response(message, history):\n", " start_time = time.time()\n", " responses = [\n", " ChatMessage(\n", " content=\"In order to find the current weather in San Francisco, I will need to use my weather tool.\",\n", " )\n", " ]\n", " yield responses\n", " time.sleep(sleep_time)\n", "\n", " main_thought = ChatMessage(\n", " content=\"\",\n", " metadata={\"title\": \"Using Weather Tool\", \"id\": 1, \"status\": \"pending\"},\n", " )\n", "\n", " responses.append(main_thought)\n", "\n", " yield responses\n", " time.sleep(long_sleep_time)\n", " responses[-1].content = \"Will check: weather.com and sunny.org\"\n", " yield responses\n", " time.sleep(sleep_time)\n", " responses.append(\n", " ChatMessage(\n", " content=\"Received weather from weather.com.\",\n", " metadata={\"title\": \"Checking weather.com\", \"parent_id\": 1, \"id\": 2, \"duration\": 0.05},\n", " )\n", " )\n", " yield responses\n", "\n", " sunny_start_time = time.time()\n", " time.sleep(sleep_time)\n", " sunny_thought = ChatMessage(\n", " content=\"API Error when connecting to sunny.org \ud83d\udca5\",\n", " metadata={\"title\": \"Checking sunny.org\", \"parent_id\": 1, \"id\": 3, \"status\": \"pending\"},\n", " )\n", "\n", " responses.append(sunny_thought)\n", " yield responses\n", "\n", " time.sleep(sleep_time)\n", " responses.append(\n", " ChatMessage(\n", " content=\"Failed again\",\n", " metadata={\"title\": \"I will try again\", \"id\": 4, \"parent_id\": 3, \"duration\": 0.1},\n", "\n", " )\n", " )\n", " sunny_thought.metadata[\"status\"] = \"done\"\n", " sunny_thought.metadata[\"duration\"] = time.time() - sunny_start_time\n", "\n", " main_thought.metadata[\"status\"] = \"done\"\n", " main_thought.metadata[\"duration\"] = time.time() - start_time\n", "\n", " yield responses\n", "\n", " time.sleep(long_sleep_time)\n", "\n", " responses.append(\n", " ChatMessage(\n", " content=\"Based on the data only from weather.com, the current weather in San Francisco is 60 degrees and sunny.\",\n", " )\n", " )\n", " yield responses\n", "\n", "demo = gr.ChatInterface(\n", " generate_response,\n", " type=\"messages\",\n", " title=\"Nested Thoughts Chat Interface\",\n", " examples=[\"What is the weather in San Francisco right now?\"]\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/chatinterface_nested_thoughts/run.py b/demo/chatinterface_nested_thoughts/run.py
new file mode 100644
index 0000000000000..7322fc0736f91
--- /dev/null
+++ b/demo/chatinterface_nested_thoughts/run.py
@@ -0,0 +1,81 @@
+import gradio as gr
+from gradio import ChatMessage
+import time
+
+sleep_time = 0.1
+long_sleep_time = 1
+
+def generate_response(message, history):
+ start_time = time.time()
+ responses = [
+ ChatMessage(
+ content="In order to find the current weather in San Francisco, I will need to use my weather tool.",
+ )
+ ]
+ yield responses
+ time.sleep(sleep_time)
+
+ main_thought = ChatMessage(
+ content="",
+ metadata={"title": "Using Weather Tool", "id": 1, "status": "pending"},
+ )
+
+ responses.append(main_thought)
+
+ yield responses
+ time.sleep(long_sleep_time)
+ responses[-1].content = "Will check: weather.com and sunny.org"
+ yield responses
+ time.sleep(sleep_time)
+ responses.append(
+ ChatMessage(
+ content="Received weather from weather.com.",
+ metadata={"title": "Checking weather.com", "parent_id": 1, "id": 2, "duration": 0.05},
+ )
+ )
+ yield responses
+
+ sunny_start_time = time.time()
+ time.sleep(sleep_time)
+ sunny_thought = ChatMessage(
+ content="API Error when connecting to sunny.org 💥",
+ metadata={"title": "Checking sunny.org", "parent_id": 1, "id": 3, "status": "pending"},
+ )
+
+ responses.append(sunny_thought)
+ yield responses
+
+ time.sleep(sleep_time)
+ responses.append(
+ ChatMessage(
+ content="Failed again",
+ metadata={"title": "I will try again", "id": 4, "parent_id": 3, "duration": 0.1},
+
+ )
+ )
+ sunny_thought.metadata["status"] = "done"
+ sunny_thought.metadata["duration"] = time.time() - sunny_start_time
+
+ main_thought.metadata["status"] = "done"
+ main_thought.metadata["duration"] = time.time() - start_time
+
+ yield responses
+
+ time.sleep(long_sleep_time)
+
+ responses.append(
+ ChatMessage(
+ content="Based on the data only from weather.com, the current weather in San Francisco is 60 degrees and sunny.",
+ )
+ )
+ yield responses
+
+demo = gr.ChatInterface(
+ generate_response,
+ type="messages",
+ title="Nested Thoughts Chat Interface",
+ examples=["What is the weather in San Francisco right now?"]
+)
+
+if __name__ == "__main__":
+ demo.launch()
diff --git a/demo/chatinterface_options/run.ipynb b/demo/chatinterface_options/run.ipynb
index a76a63b090d13..8be849ed28023 100644
--- a/demo/chatinterface_options/run.ipynb
+++ b/demo/chatinterface_options/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_options"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import random\n", "\n", "example_code = \"\"\"\n", "Here's an example Python lambda function:\n", "\n", "lambda x: x + {}\n", "\n", "Is this correct?\n", "\"\"\"\n", "\n", "def chat(message, history):\n", " if message == \"Yes, that's correct.\":\n", " return \"Great!\"\n", " else:\n", " return {\n", " \"role\": \"assistant\",\n", " \"content\": example_code.format(random.randint(1, 100)),\n", " \"options\": [\n", " {\"value\": \"Yes, that's correct.\", \"label\": \"Yes\"},\n", " {\"value\": \"No\"}\n", " ]\n", " }\n", "\n", "demo = gr.ChatInterface(\n", " chat,\n", " type=\"messages\",\n", " examples=[\"Write an example Python lambda function.\"]\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_options"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import random\n", "\n", "example_code = \"\"\"\n", "Here's an example Python lambda function:\n", "\n", "lambda x: x + {}\n", "\n", "Is this correct?\n", "\"\"\"\n", "\n", "def chat(message, history):\n", " if message == \"Yes, that's correct.\":\n", " return \"Great!\"\n", " else:\n", " return gr.ChatMessage(\n", " content=example_code.format(random.randint(1, 100)),\n", " options=[\n", " {\"value\": \"Yes, that's correct.\", \"label\": \"Yes\"},\n", " {\"value\": \"No\"}\n", " ]\n", " )\n", "\n", "demo = gr.ChatInterface(\n", " chat,\n", " type=\"messages\",\n", " examples=[\"Write an example Python lambda function.\"]\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/chatinterface_options/run.py b/demo/chatinterface_options/run.py
index 4afbf49fc1fc1..6c684074092d5 100644
--- a/demo/chatinterface_options/run.py
+++ b/demo/chatinterface_options/run.py
@@ -13,14 +13,13 @@ def chat(message, history):
if message == "Yes, that's correct.":
return "Great!"
else:
- return {
- "role": "assistant",
- "content": example_code.format(random.randint(1, 100)),
- "options": [
+ return gr.ChatMessage(
+ content=example_code.format(random.randint(1, 100)),
+ options=[
{"value": "Yes, that's correct.", "label": "Yes"},
{"value": "No"}
- ]
- }
+ ]
+ )
demo = gr.ChatInterface(
chat,
diff --git a/demo/chatinterface_thoughts/run.ipynb b/demo/chatinterface_thoughts/run.ipynb
new file mode 100644
index 0000000000000..dd26607b9c03f
--- /dev/null
+++ b/demo/chatinterface_thoughts/run.ipynb
@@ -0,0 +1 @@
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatinterface_thoughts"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from gradio import ChatMessage\n", "import time\n", "\n", "sleep_time = 0.5\n", "\n", "def simulate_thinking_chat(message, history):\n", " start_time = time.time()\n", " response = ChatMessage(\n", " content=\"\",\n", " metadata={\"title\": \"_Thinking_ step-by-step\", \"id\": 0, \"status\": \"pending\"}\n", " )\n", " yield response\n", "\n", " thoughts = [\n", " \"First, I need to understand the core aspects of the query...\",\n", " \"Now, considering the broader context and implications...\",\n", " \"Analyzing potential approaches to formulate a comprehensive answer...\",\n", " \"Finally, structuring the response for clarity and completeness...\"\n", " ]\n", "\n", " accumulated_thoughts = \"\"\n", " for thought in thoughts:\n", " time.sleep(sleep_time)\n", " accumulated_thoughts += f\"- {thought}\\n\\n\"\n", " response.content = accumulated_thoughts.strip()\n", " yield response\n", "\n", " response.metadata[\"status\"] = \"done\"\n", " response.metadata[\"duration\"] = time.time() - start_time\n", " yield response\n", "\n", " response = [\n", " response,\n", " ChatMessage(\n", " content=\"Based on my thoughts and analysis above, my response is: This dummy repro shows how thoughts of a thinking LLM can be progressively shown before providing its final answer.\"\n", " )\n", " ]\n", " yield response\n", "\n", "\n", "demo = gr.ChatInterface(\n", " simulate_thinking_chat,\n", " title=\"Thinking LLM Chat Interface \ud83e\udd14\",\n", " type=\"messages\",\n", ")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/chatinterface_thoughts/run.py b/demo/chatinterface_thoughts/run.py
new file mode 100644
index 0000000000000..8ced54f2a0f40
--- /dev/null
+++ b/demo/chatinterface_thoughts/run.py
@@ -0,0 +1,49 @@
+import gradio as gr
+from gradio import ChatMessage
+import time
+
+sleep_time = 0.5
+
+def simulate_thinking_chat(message, history):
+ start_time = time.time()
+ response = ChatMessage(
+ content="",
+ metadata={"title": "_Thinking_ step-by-step", "id": 0, "status": "pending"}
+ )
+ yield response
+
+ thoughts = [
+ "First, I need to understand the core aspects of the query...",
+ "Now, considering the broader context and implications...",
+ "Analyzing potential approaches to formulate a comprehensive answer...",
+ "Finally, structuring the response for clarity and completeness..."
+ ]
+
+ accumulated_thoughts = ""
+ for thought in thoughts:
+ time.sleep(sleep_time)
+ accumulated_thoughts += f"- {thought}\n\n"
+ response.content = accumulated_thoughts.strip()
+ yield response
+
+ response.metadata["status"] = "done"
+ response.metadata["duration"] = time.time() - start_time
+ yield response
+
+ response = [
+ response,
+ ChatMessage(
+ content="Based on my thoughts and analysis above, my response is: This dummy repro shows how thoughts of a thinking LLM can be progressively shown before providing its final answer."
+ )
+ ]
+ yield response
+
+
+demo = gr.ChatInterface(
+ simulate_thinking_chat,
+ title="Thinking LLM Chat Interface 🤔",
+ type="messages",
+)
+
+if __name__ == "__main__":
+ demo.launch()
diff --git a/gradio/chat_interface.py b/gradio/chat_interface.py
index 3b412a2037398..46afe4a1d312a 100644
--- a/gradio/chat_interface.py
+++ b/gradio/chat_interface.py
@@ -6,6 +6,7 @@
import builtins
import copy
+import dataclasses
import inspect
import os
import warnings
@@ -32,6 +33,7 @@
get_component_instance,
)
from gradio.components.chatbot import (
+ ChatMessage,
ExampleMessage,
Message,
MessageDict,
@@ -808,6 +810,11 @@ def _message_as_message_dict(
for msg in message:
if isinstance(msg, Message):
message_dicts.append(msg.model_dump())
+ elif isinstance(msg, ChatMessage):
+ msg.role = role
+ message_dicts.append(
+ dataclasses.asdict(msg, dict_factory=utils.dict_factory)
+ )
elif isinstance(msg, (str, Component)):
message_dicts.append({"role": role, "content": msg})
elif (
diff --git a/gradio/components/chatbot.py b/gradio/components/chatbot.py
index 430382b4702b9..450ad98b1ea27 100644
--- a/gradio/components/chatbot.py
+++ b/gradio/components/chatbot.py
@@ -37,6 +37,8 @@ class MetadataDict(TypedDict):
title: Union[str, None]
id: NotRequired[int | str]
parent_id: NotRequired[int | str]
+ duration: NotRequired[float]
+ status: NotRequired[Literal["pending", "done"]]
class Option(TypedDict):
@@ -59,7 +61,6 @@ class MessageDict(TypedDict):
role: Literal["user", "assistant", "system"]
metadata: NotRequired[MetadataDict]
options: NotRequired[list[Option]]
- duration: NotRequired[int]
class FileMessage(GradioModel):
@@ -87,6 +88,14 @@ class Metadata(GradioModel):
title: Optional[str] = None
id: Optional[int | str] = None
parent_id: Optional[int | str] = None
+ duration: Optional[float] = None
+ status: Optional[Literal["pending", "done"]] = None
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ setattr(self, key, value)
+
+ def __getitem__(self, key: str) -> Any:
+ return getattr(self, key)
class Message(GradioModel):
@@ -94,7 +103,6 @@ class Message(GradioModel):
metadata: Metadata = Field(default_factory=Metadata)
content: Union[str, FileMessage, ComponentMessage]
options: Optional[list[Option]] = None
- duration: Optional[int] = None
class ExampleMessage(TypedDict):
@@ -110,13 +118,22 @@ class ExampleMessage(TypedDict):
] # list of file paths or URLs to be added to chatbot when example is clicked
+@document()
@dataclass
class ChatMessage:
- role: Literal["user", "assistant", "system"]
+ """
+ A dataclass to represent a message in the Chatbot component (type="messages").
+ Parameters:
+ content: The content of the message. Can be a string or a Gradio component.
+ role: The role of the message, which determines the alignment of the message in the chatbot. Can be "user", "assistant", or "system". Defaults to "assistant".
+ metadata: The metadata of the message, which is used to display intermediate thoughts / tool usage. Should be a dictionary with the following keys: "title" (required to display the thought), and optionally: "id" and "parent_id" (to nest thoughts), "duration" (to display the duration of the thought), "status" (to display the status of the thought).
+ options: The options of the message. A list of Option objects, which are dictionaries with the following keys: "label" (the text to display in the option), and optionally "value" (the value to return when the option is selected if different from the label).
+ """
+
content: str | FileData | Component | FileDataDict | tuple | list
+ role: Literal["user", "assistant", "system"] = "assistant"
metadata: MetadataDict | Metadata = field(default_factory=Metadata)
options: Optional[list[Option]] = None
- duration: Optional[int] = None
class ChatbotDataMessages(GradioRootModel):
@@ -545,7 +562,6 @@ def _postprocess_message_messages(
content=message.content, # type: ignore
metadata=message.metadata, # type: ignore
options=message.options,
- duration=message.duration,
)
elif isinstance(message, Message):
return message
diff --git a/gradio/monitoring_dashboard.py b/gradio/monitoring_dashboard.py
index a18a2e5088506..1a41e7b937f06 100644
--- a/gradio/monitoring_dashboard.py
+++ b/gradio/monitoring_dashboard.py
@@ -60,7 +60,7 @@ def gen_plot(start, end, selected_fn):
if selected_fn != "All":
df = df[df["function"] == selected_fn]
df = df[(df["time"] >= start) & (df["time"] <= end)]
- df["time"] = pd.to_datetime(df["time"], unit="s")
+ df["time"] = pd.to_datetime(df["time"], unit="s") # type: ignore
unique_users = len(df["session_hash"].unique()) # type: ignore
total_requests = len(df)
diff --git a/gradio/utils.py b/gradio/utils.py
index 1f9585d8e3d41..769f7dc82bb70 100644
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -1609,3 +1609,16 @@ def get_icon_path(icon_name: str) -> str:
set_static_paths(icon_path)
return icon_path
raise ValueError(f"Icon file not found: {icon_name}")
+
+
+def dict_factory(items):
+ """
+ A utility function to convert a dataclass that includes pydantic fields to a dictionary.
+ """
+ d = {}
+ for key, value in items:
+ if hasattr(value, "model_dump"):
+ d[key] = value.model_dump()
+ else:
+ d[key] = value
+ return d
diff --git a/guides/05_chatbots/01_creating-a-chatbot-fast.md b/guides/05_chatbots/01_creating-a-chatbot-fast.md
index 27ef1b7692003..cf1a4b452cdce 100644
--- a/guides/05_chatbots/01_creating-a-chatbot-fast.md
+++ b/guides/05_chatbots/01_creating-a-chatbot-fast.md
@@ -4,9 +4,9 @@ Tags: LLM, CHATBOT, NLP
## Introduction
-Chatbots are a popular application of large language models (LLMs). Using Gradio, you can easily build a demo of your LLM and share that with your users, or try it yourself using an intuitive chatbot UI.
+Chatbots are a popular application of large language models (LLMs). Using Gradio, you can easily build a chat application and share that with your users, or try it yourself using an intuitive UI.
-This tutorial uses `gr.ChatInterface()`, which is a high-level abstraction that allows you to create your chatbot UI fast, often with a _single line of Python_. It can be easily adapted to support multimodal chatbots, or chatbots that require further customization.
+This tutorial uses `gr.ChatInterface()`, which is a high-level abstraction that allows you to create your chatbot UI fast, often with a _few lines of Python_. It can be easily adapted to support multimodal chatbots, or chatbots that require further customization.
**Prerequisites**: please make sure you are using the latest version of Gradio:
@@ -14,9 +14,9 @@ This tutorial uses `gr.ChatInterface()`, which is a high-level abstraction that
$ pip install --upgrade gradio
```
-## Quickly loading from Ollama or any OpenAI-API compatible endpoint
+## OpenAI-API compatible endpoints
-If you have a chat server serving an OpenAI API compatible endpoint (skip ahead if you don't), you can spin up a ChatInterface in a single line. First, also run `pip install openai`. Then, with your own URL, model, and optional token:
+If you have a chat server serving an OpenAI-API compatible endpoint (e.g. Ollama), you can spin up a ChatInterface in a single line of Python. First, also run `pip install openai`. Then, with your own URL, model, and optional token:
```python
import gradio as gr
@@ -24,9 +24,11 @@ import gradio as gr
gr.load_chat("http://localhost:11434/v1/", model="llama3.2", token=None).launch()
```
+If not, don't worry, keep reading to see how to create an application around any chat model!
+
## Defining a chat function
-When working with `gr.ChatInterface()`, the first thing you should do is define your **chat function**. In the simplest case, your chat function should accept two arguments: `message` and `history` (the arguments can be named anything, but must be in this order).
+To create a chat application with `gr.ChatInterface()`, the first thing you should do is define your **chat function**. In the simplest case, your chat function should accept two arguments: `message` and `history` (the arguments can be named anything, but must be in this order).
- `message`: a `str` representing the user's most recent message.
- `history`: a list of openai-style dictionaries with `role` and `content` keys, representing the previous conversation history. May also include additional keys representing message metadata.
@@ -40,9 +42,19 @@ For example, the `history` could look like this:
]
```
+while the next `message` would be:
+
+```py
+"And what is its largest city?"
+```
+
Your chat function simply needs to return:
-* a `str` value, which is the chatbot's response based on the chat `history` and most recent `message`.
+* a `str` value, which is the chatbot's response based on the chat `history` and most recent `message`, for example, in this case:
+
+```
+Paris is also the largest city.
+```
Let's take a look at a few example chat functions:
@@ -280,7 +292,8 @@ $code_chatinterface_artifacts
## Returning Complex Responses
-We mentioned earlier that in the simplest case, your chat function should return a `str` response, which will be rendered as text in the chatbot. However, you can also return more complex responses as we discuss below:
+We mentioned earlier that in the simplest case, your chat function should return a `str` response, which will be rendered as Markdown in the chatbot. However, you can also return more complex responses as we discuss below:
+
**Returning files or Gradio components**
@@ -313,21 +326,55 @@ gr.ChatInterface(
Similarly, you could return image files with `gr.Image`, video files with `gr.Video`, or arbitrary files with the `gr.File` component.
-**Providing preset responses**
+**Returning Multiple Messages**
-You may want to provide preset responses that a user can choose between when conversing with your chatbot. To do this, return a complete openai-style message dictionary from your chat function, and add the `options` key to the dictionary returned from your chat function to set these responses.
+You can return multiple assistant messages from your chat function simply by returning a `list` of messages, each of which is a valid chat type. This lets you, for example, send a message along with files, as in the following example:
-The value corresponding to the `options` key should be a list of dictionaries, each with a `value` (a string that is the value that should be sent to the chat function when this response is clicked) and an optional `label` (if provided, is the text displayed as the preset response instead of the `value`).
+$code_chatinterface_echo_multimodal
-This example illustrates how to use preset responses:
-$code_chatinterface_options
+**Displaying intermediate thoughts or tool usage**
-**Returning Multiple Messages**
+The `gr.ChatInterface` class supports displaying intermediate thoughts or tool usage direct in the chatbot.
-You can return multiple assistant messages from your chat function simply by returning a `list` of messages of any of the above types (you can even mix-and-match). This lets you, for example, send a message along with files, as in the following example:
+![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/nested-thought.png)
-$code_chatinterface_echo_multimodal
+ To do this, you will need to return a `gr.ChatMessage` object from your chat function. Here is the schema of the `gr.ChatMessage` data class as well as two internal typed dictionaries:
+
+ ```py
+@dataclass
+class ChatMessage:
+ content: str | Component
+ metadata: MetadataDict = None
+ options: list[Option] = None
+
+class MetadataDict(TypedDict):
+ title: Union[str, None]
+ id: NotRequired[int | str]
+ parent_id: NotRequired[int | str]
+ duration: NotRequired[int]
+ status: NotRequired[Literal["pending", "done"]]
+
+class Option(TypedDict):
+ label: NotRequired[str]
+ value: str
+ ```
+
+As you can see, the `gr.ChatMessage` dataclass is similar to the openai-style message format, e.g. it has a "content" key that refers to the chat message content. But it also includes a "metadata" key whose value is a dictionary. If this dictionary includes a "title" key, the resulting message is displayed as an intermediate thought with the title being displayed on top of the thought. Here's an example showing the usage:
+
+$code_chatinterface_thoughts
+
+You can even show nested thoughts, which is useful for agent demos in which one tool may call other tools. To display nested thoughts, include "id" and "parent_id" keys in the "metadata" dictionary. See [this example for a complete demonstration](https://github.com/gradio-app/gradio/blob/main/demo/chatinterface_nested_thoughts/run.py).
+
+**Providing preset responses**
+
+When returning an assistant message, you may want to provide preset options that a user can choose in response. To do this, again, you will again return a `gr.ChatMessage` instance from your chat function. This time, make sure to set the `options` key specifying the preset responses.
+
+As shown in the schema for `gr.ChatMessage` above, the value corresponding to the `options` key should be a list of dictionaries, each with a `value` (a string that is the value that should be sent to the chat function when this response is clicked) and an optional `label` (if provided, is the text displayed as the preset response instead of the `value`).
+
+This example illustrates how to use preset responses:
+
+$code_chatinterface_options
## Using Your Chatbot via API
diff --git a/js/_website/src/lib/templates/gradio/03_components/01_introduction.svx b/js/_website/src/lib/templates/gradio/03_components/01_introduction.svx
index 0734637e3b1ce..0f527669aaf0f 100644
--- a/js/_website/src/lib/templates/gradio/03_components/01_introduction.svx
+++ b/js/_website/src/lib/templates/gradio/03_components/01_introduction.svx
@@ -6,6 +6,7 @@
delete components_no_dataclasses["brush"];
delete components_no_dataclasses["eraser"];
delete components_no_dataclasses["waveformoptions"];
+ delete components_no_dataclasses["chatmessage"];
let events = get_object("events");
let events_matrix = get_object("events_matrix");
diff --git a/js/_website/src/lib/templates/gradio/03_components/chatbot.svx b/js/_website/src/lib/templates/gradio/03_components/chatbot.svx
index 3cd1587b0e88c..40c636480a3a9 100644
--- a/js/_website/src/lib/templates/gradio/03_components/chatbot.svx
+++ b/js/_website/src/lib/templates/gradio/03_components/chatbot.svx
@@ -10,6 +10,7 @@
import { style_formatted_text } from "$lib/text";
let obj = get_object("chatbot");
+ let chatmessage_obj = get_object("chatmessage");
let embedded_demo_obj = `[
{"role": "user", "content": "Hello World"},
@@ -53,53 +54,41 @@ The `'tuples'` type is deprecated and will be removed in a future version of Gra
If the `type` is `'messages'`, then the data sent to/from the chatbot will be a list of dictionaries
with `role` and `content` keys. This format is compliant with the format expected by most LLM APIs (HuggingChat, OpenAI, Claude).
-The `role` key is either `'user'` or `'assistant'` and the `content` key can be one of the following:
+The `role` key is either `'user'` or `'assistant'` and the `content` key can be one of the following should be a string (rendered as markdown/html) or a Gradio component (useful for displaying files).
-1. A string (markdown/html is also supported).
-2. A dictionary with `path` and `alt_text` keys. In this case, the file at `path` will be displayed in the chat history. Image, audio, and video files are fully embedded and visualized in the chat bubble.
-The `path` key can point to a valid publicly available URL. The `alt_text` key is optional but it's good practice to provide [alt text](https://en.wikipedia.org/wiki/Alt_attribute).
-3. An instance of another Gradio component.
-
-
-We will show examples for all three cases below -
+As an example:
```python
-def generate_response(history):
- # A plain text response
- history.append(
- {"role": "assistant", content="I am happy to provide you that report and plot."}
- )
- # Embed the quaterly sales report in the chat
- history.append(
- {"role": "assistant", content={"path": "quaterly_sales.txt", "alt_text": "Sales Report for Q2 2024"}}
- )
- # Make a plot of sales data
- history.append(
- {"role": "assistant", content=gr.Plot(value=make_plot_from_file('quaterly_sales.txt'))}
- )
- return history
+import gradio as gr
+
+history = [
+ {"role": "assistant", content="I am happy to provide you that report and plot."}
+ {"role": "assistant", content=gr.Plot(value=make_plot_from_file('quaterly_sales.txt'))}
+]
+
+with gr.Blocks() as demo:
+ gr.Chatbot(history)
+
+demo.launch()
```
For convenience, you can use the `ChatMessage` dataclass so that your text editor can give you autocomplete hints and typechecks.
```python
-from gradio import ChatMessage
+import gradio as gr
-def generate_response(history):
- history.append(
- ChatMessage(role="assistant",
- content="How can I help you?")
- )
- return history
-```
+history = [
+ gr.ChatMessage(role="assistant", content="How can I help you?"),
+ gr.ChatMessage(role="user", content="Can you make me a plot of quarterly sales?"},
+ gr.ChatMessage(role="assistant", content="I am happy to provide you that report and plot."}
+]
-### Tuples format
+with gr.Blocks() as demo:
+ gr.Chatbot(history)
+
+demo.launch()
+```
-If `type` is `'tuples'`, then the data sent to/from the chatbot will be a list of tuples.
-The first element of each tuple is the user message and the second element is the bot's response.
-Each element can be a string (markdown/html is supported),
-a tuple (in which case the first element is a filepath that will be displayed in the chatbot),
-or a gradio component (see the Examples section for more details).
### Initialization
@@ -178,6 +167,29 @@ demo.launch()