From 3f192100d6997751d0246b396a4fd8eaa86a826b Mon Sep 17 00:00:00 2001 From: Dmitry Ustalov Date: Mon, 23 Dec 2024 23:33:22 +0100 Subject: [PATCH 1/3] Declare exports in __all__ for type checking (#10238) * Declare exports * add changeset * type fixes * more type fixes * add changeset * notebooks * changes --------- Co-authored-by: gradio-pr-bot Co-authored-by: Freddy Boulton Co-authored-by: Abubakar Abid --- .changeset/young-geckos-brake.md | 6 + client/python/gradio_client/client.py | 4 +- demo/agent_chatbot/run.ipynb | 2 +- demo/agent_chatbot/run.py | 4 +- gradio/__init__.py | 118 ++++++++++++++++++ gradio/blocks.py | 5 +- gradio/cli/commands/components/_docs_utils.py | 2 +- gradio/components/native_plot.py | 8 +- gradio/components/video.py | 2 +- gradio/external.py | 7 +- gradio/themes/base.py | 3 +- gradio/utils.py | 2 +- test/components/test_gallery.py | 2 +- test/test_utils.py | 2 +- 14 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 .changeset/young-geckos-brake.md diff --git a/.changeset/young-geckos-brake.md b/.changeset/young-geckos-brake.md new file mode 100644 index 0000000000000..a350d60010dac --- /dev/null +++ b/.changeset/young-geckos-brake.md @@ -0,0 +1,6 @@ +--- +"gradio": patch +"gradio_client": patch +--- + +fix:Declare exports in __all__ for type checking diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py index 36e17a1a8cef0..9f8ca4597b844 100644 --- a/client/python/gradio_client/client.py +++ b/client/python/gradio_client/client.py @@ -1356,8 +1356,8 @@ def _upload_file(self, f: dict, data_index: int) -> dict[str, str]: f"File {file_path} exceeds the maximum file size of {max_file_size} bytes " f"set in {component_config.get('label', '') + ''} component." ) - with open(file_path, "rb") as f: - files = [("files", (orig_name.name, f))] + with open(file_path, "rb") as f_: + files = [("files", (orig_name.name, f_))] r = httpx.post( self.client.upload_url, headers=self.client.headers, diff --git a/demo/agent_chatbot/run.ipynb b/demo/agent_chatbot/run.ipynb index ccf9c2fe27edc..aec9695588921 100644 --- a/demo/agent_chatbot/run.ipynb +++ b/demo/agent_chatbot/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: agent_chatbot"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio transformers>=4.47.0"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from dataclasses import asdict\n", "from transformers import Tool, ReactCodeAgent # type: ignore\n", "from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore\n", "\n", "# Import tool from Hub\n", "image_generation_tool = Tool.from_space(\n", " space_id=\"black-forest-labs/FLUX.1-schnell\",\n", " name=\"image_generator\",\n", " description=\"Generates an image following your prompt. Returns a PIL Image.\",\n", " api_name=\"/infer\",\n", ")\n", "\n", "llm_engine = HfApiEngine(\"Qwen/Qwen2.5-Coder-32B-Instruct\")\n", "# Initialize the agent with both tools and engine\n", "agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)\n", "\n", "\n", "def interact_with_agent(prompt, history):\n", " messages = []\n", " yield messages\n", " for msg in stream_to_gradio(agent, prompt):\n", " messages.append(asdict(msg))\n", " yield messages\n", " yield messages\n", "\n", "\n", "demo = gr.ChatInterface(\n", " interact_with_agent,\n", " chatbot= gr.Chatbot(\n", " label=\"Agent\",\n", " type=\"messages\",\n", " avatar_images=(\n", " None,\n", " \"https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png\",\n", " ),\n", " ),\n", " examples=[\n", " [\"Generate an image of an astronaut riding an alligator\"],\n", " [\"I am writing a children's book for my daughter. Can you help me with some illustrations?\"],\n", " ],\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 +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: agent_chatbot"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio transformers>=4.47.0"]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "from dataclasses import asdict\n", "from transformers import Tool, ReactCodeAgent # type: ignore\n", "from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore\n", "\n", "# Import tool from Hub\n", "image_generation_tool = Tool.from_space( # type: ignore\n", " space_id=\"black-forest-labs/FLUX.1-schnell\",\n", " name=\"image_generator\",\n", " description=\"Generates an image following your prompt. Returns a PIL Image.\",\n", " api_name=\"/infer\",\n", ")\n", "\n", "llm_engine = HfApiEngine(\"Qwen/Qwen2.5-Coder-32B-Instruct\")\n", "# Initialize the agent with both tools and engine\n", "agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)\n", "\n", "\n", "def interact_with_agent(prompt, history):\n", " messages = []\n", " yield messages\n", " for msg in stream_to_gradio(agent, prompt):\n", " messages.append(asdict(msg)) # type: ignore\n", " yield messages\n", " yield messages\n", "\n", "\n", "demo = gr.ChatInterface(\n", " interact_with_agent,\n", " chatbot= gr.Chatbot(\n", " label=\"Agent\",\n", " type=\"messages\",\n", " avatar_images=(\n", " None,\n", " \"https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png\",\n", " ),\n", " ),\n", " examples=[\n", " [\"Generate an image of an astronaut riding an alligator\"],\n", " [\"I am writing a children's book for my daughter. Can you help me with some illustrations?\"],\n", " ],\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/agent_chatbot/run.py b/demo/agent_chatbot/run.py index 4e6cf36214536..7467971e88245 100644 --- a/demo/agent_chatbot/run.py +++ b/demo/agent_chatbot/run.py @@ -4,7 +4,7 @@ from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore # Import tool from Hub -image_generation_tool = Tool.from_space( +image_generation_tool = Tool.from_space( # type: ignore space_id="black-forest-labs/FLUX.1-schnell", name="image_generator", description="Generates an image following your prompt. Returns a PIL Image.", @@ -20,7 +20,7 @@ def interact_with_agent(prompt, history): messages = [] yield messages for msg in stream_to_gradio(agent, prompt): - messages.append(asdict(msg)) + messages.append(asdict(msg)) # type: ignore yield messages yield messages diff --git a/gradio/__init__.py b/gradio/__init__.py index 9375c4c512e4e..e5c0ae975313b 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -119,3 +119,121 @@ from gradio.ipython_ext import load_ipython_extension __version__ = get_package_version() + +__all__ = [ + "Accordion", + "AnnotatedImage", + "Annotatedimage", + "Audio", + "BarPlot", + "Blocks", + "BrowserState", + "Brush", + "Button", + "CSVLogger", + "ChatInterface", + "ChatMessage", + "Chatbot", + "Checkbox", + "CheckboxGroup", + "Checkboxgroup", + "ClearButton", + "Code", + "ColorPicker", + "Column", + "CopyData", + "DataFrame", + "Dataframe", + "Dataset", + "DateTime", + "DeletedFileData", + "DownloadButton", + "DownloadData", + "Dropdown", + "DuplicateButton", + "EditData", + "Eraser", + "Error", + "EventData", + "Examples", + "File", + "FileData", + "FileExplorer", + "FileSize", + "Files", + "FlaggingCallback", + "Gallery", + "Group", + "HTML", + "Highlight", + "HighlightedText", + "Highlightedtext", + "IS_WASM", + "Image", + "ImageEditor", + "ImageMask", + "Info", + "Interface", + "JSON", + "Json", + "KeyUpData", + "Label", + "LikeData", + "LinePlot", + "List", + "LoginButton", + "Markdown", + "Matrix", + "MessageDict", + "Mic", + "Microphone", + "Model3D", + "MultimodalTextbox", + "NO_RELOAD", + "Number", + "Numpy", + "OAuthProfile", + "OAuthToken", + "Paint", + "ParamViewer", + "PlayableVideo", + "Plot", + "Progress", + "Radio", + "Request", + "RetryData", + "Row", + "ScatterPlot", + "SelectData", + "SimpleCSVLogger", + "Sketchpad", + "Slider", + "State", + "Tab", + "TabItem", + "TabbedInterface", + "Tabs", + "Text", + "TextArea", + "Textbox", + "Theme", + "Timer", + "UndoData", + "UploadButton", + "Video", + "Warning", + "WaveformOptions", + "__version__", + "close_all", + "deploy", + "get_package_version", + "load", + "load_chat", + "load_ipython_extension", + "mount_gradio_app", + "on", + "render", + "set_static_paths", + "skip", + "update", +] diff --git a/gradio/blocks.py b/gradio/blocks.py index 8aabdbfb418e1..b4f227822d894 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -1475,10 +1475,7 @@ def is_callable(self, fn_index: int = 0) -> bool: return False if any(block.stateful for block in dependency.inputs): return False - if any(block.stateful for block in dependency.outputs): - return False - - return True + return not any(block.stateful for block in dependency.outputs) def __call__(self, *inputs, fn_index: int = 0, api_name: str | None = None): """ diff --git a/gradio/cli/commands/components/_docs_utils.py b/gradio/cli/commands/components/_docs_utils.py index 9b61b76889149..9cf879e491df5 100644 --- a/gradio/cli/commands/components/_docs_utils.py +++ b/gradio/cli/commands/components/_docs_utils.py @@ -71,7 +71,7 @@ def get_param_name(param): def format_none(value): """Formats None and NonType values.""" - if value is None or value is type(None) or value == "None" or value == "NoneType": + if value is None or value is type(None) or value in ("None", "NoneType"): return "None" return value diff --git a/gradio/components/native_plot.py b/gradio/components/native_plot.py index 9016febdb17e8..dfe9a675d7a5e 100644 --- a/gradio/components/native_plot.py +++ b/gradio/components/native_plot.py @@ -147,10 +147,10 @@ def __init__( every=every, inputs=inputs, ) - for key, val in kwargs.items(): - if key == "color_legend_title": + for key_, val in kwargs.items(): + if key_ == "color_legend_title": self.color_title = val - if key in [ + if key_ in [ "stroke_dash", "overlay_point", "x_label_angle", @@ -161,7 +161,7 @@ def __init__( "width", ]: warnings.warn( - f"Argument '{key}' has been deprecated.", DeprecationWarning + f"Argument '{key_}' has been deprecated.", DeprecationWarning ) def get_block_name(self) -> str: diff --git a/gradio/components/video.py b/gradio/components/video.py index 83a0078c07da2..f84471e65c77c 100644 --- a/gradio/components/video.py +++ b/gradio/components/video.py @@ -276,7 +276,7 @@ def postprocess( """ if self.streaming: return value # type: ignore - if value is None or value == [None, None] or value == (None, None): + if value is None or value in ([None, None], (None, None)): return None if isinstance(value, (str, Path)): processed_files = (self._format_video(value), None) diff --git a/gradio/external.py b/gradio/external.py index d410dcdce304b..2e2d0ccf76860 100644 --- a/gradio/external.py +++ b/gradio/external.py @@ -615,7 +615,7 @@ def load_chat( [{"role": "system", "content": system_message}] if system_message else [] ) - def open_api(message: str, history: list | None) -> str: + def open_api(message: str, history: list | None) -> str | None: history = history or start_message if len(history) > 0 and isinstance(history[0], (list, tuple)): history = ChatInterface._tuples_to_messages(history) @@ -641,7 +641,8 @@ def open_api_stream( ) response = "" for chunk in stream: - response += chunk.choices[0].delta.content - yield response + if chunk.choices[0].delta.content is not None: + response += chunk.choices[0].delta.content + yield response return ChatInterface(open_api_stream if streaming else open_api, type="messages") diff --git a/gradio/themes/base.py b/gradio/themes/base.py index 51230be69623d..31d571c49d959 100644 --- a/gradio/themes/base.py +++ b/gradio/themes/base.py @@ -126,8 +126,7 @@ def to_dict(self): if ( not prop.startswith("_") or prop.startswith("_font") - or prop == "_stylesheets" - or prop == "name" + or prop in ("_stylesheets", "name") ) and isinstance(getattr(self, prop), (list, str)): schema["theme"][prop] = getattr(self, prop) return schema diff --git a/gradio/utils.py b/gradio/utils.py index 7ad4baa84da78..c6d5ba5a6aace 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -1244,7 +1244,7 @@ def compare_objects(obj1, obj2, path=None): if obj1 == obj2: return edits - if type(obj1) != type(obj2): + if type(obj1) is not type(obj2): edits.append(("replace", path, obj2)) return edits diff --git a/test/components/test_gallery.py b/test/components/test_gallery.py index 9b9ca67022461..eac9ce763a596 100644 --- a/test/components/test_gallery.py +++ b/test/components/test_gallery.py @@ -126,5 +126,5 @@ def test_gallery_format(self): output = gallery.postprocess( [np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)] ) - if type(output.root[0]) == GalleryImage: + if isinstance(output.root[0], GalleryImage): assert output.root[0].image.path.endswith(".jpeg") diff --git a/test/test_utils.py b/test/test_utils.py index 7b28c101a2a8a..7f60554bc28c6 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -304,7 +304,7 @@ class GenericObject: for x in test_objs: hints = get_type_hints(x) assert len(hints) == 1 - assert hints["s"] == str + assert hints["s"] is str assert len(get_type_hints(GenericObject())) == 0 From 3e4e0de51a2ece9a1cab70a69c065bda685569e8 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Mon, 23 Dec 2024 18:19:46 -0500 Subject: [PATCH 2/3] Add `gr.BrowserState` change event (#10245) * changes * changes * add changeset * format * changes --------- Co-authored-by: gradio-pr-bot --- .changeset/solid-moments-mate.md | 6 ++++++ demo/browserstate/run.ipynb | 2 +- demo/browserstate/run.py | 11 ++++++++++- gradio/components/browser_state.py | 2 ++ js/browserstate/Index.svelte | 12 +++++++++++- js/browserstate/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 7 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changeset/solid-moments-mate.md diff --git a/.changeset/solid-moments-mate.md b/.changeset/solid-moments-mate.md new file mode 100644 index 0000000000000..19474197b2391 --- /dev/null +++ b/.changeset/solid-moments-mate.md @@ -0,0 +1,6 @@ +--- +"@gradio/browserstate": minor +"gradio": minor +--- + +feat:Add `gr.BrowserState` change event diff --git a/demo/browserstate/run.ipynb b/demo/browserstate/run.ipynb index b3fc96115affb..0f5b3fa80b1ea 100644 --- a/demo/browserstate/run.ipynb +++ b/demo/browserstate/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: browserstate"]}, {"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 random\n", "import string\n", "import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\"Your Username and Password will get saved in the browser's local storage. \"\n", " \"If you refresh the page, the values will be retained.\")\n", " username = gr.Textbox(label=\"Username\")\n", " password = gr.Textbox(label=\"Password\", type=\"password\")\n", " btn = gr.Button(\"Generate Randomly\")\n", " local_storage = gr.BrowserState([\"\", \"\"])\n", "\n", " @btn.click(outputs=[username, password])\n", " def generate_randomly():\n", " u = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " p = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " return u, p\n", "\n", " @demo.load(inputs=[local_storage], outputs=[username, password])\n", " def load_from_local_storage(saved_values):\n", " print(\"loading from local storage\", saved_values)\n", " return saved_values[0], saved_values[1]\n", "\n", " @gr.on([username.change, password.change], inputs=[username, password], outputs=[local_storage])\n", " def save_to_local_storage(username, password):\n", " return [username, password]\n", "\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: browserstate"]}, {"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 random\n", "import string\n", "import gradio as gr\n", "import time\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\"Your Username and Password will get saved in the browser's local storage. \"\n", " \"If you refresh the page, the values will be retained.\")\n", " username = gr.Textbox(label=\"Username\")\n", " password = gr.Textbox(label=\"Password\", type=\"password\")\n", " btn = gr.Button(\"Generate Randomly\")\n", " local_storage = gr.BrowserState([\"\", \"\"])\n", " saved_message = gr.Markdown(\"\u2705 Saved to local storage\", visible=False)\n", "\n", " @btn.click(outputs=[username, password])\n", " def generate_randomly():\n", " u = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " p = \"\".join(random.choices(string.ascii_letters + string.digits, k=10))\n", " return u, p\n", "\n", " @demo.load(inputs=[local_storage], outputs=[username, password])\n", " def load_from_local_storage(saved_values):\n", " print(\"loading from local storage\", saved_values)\n", " return saved_values[0], saved_values[1]\n", "\n", " @gr.on([username.change, password.change], inputs=[username, password], outputs=[local_storage])\n", " def save_to_local_storage(username, password):\n", " return [username, password]\n", "\n", " @gr.on(local_storage.change, outputs=[saved_message])\n", " def show_saved_message():\n", " timestamp = time.strftime(\"%I:%M:%S %p\")\n", " return gr.Markdown(\n", " f\"\u2705 Saved to local storage at {timestamp}\",\n", " visible=True\n", " )\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/browserstate/run.py b/demo/browserstate/run.py index 294b56fd58f64..a150825146457 100644 --- a/demo/browserstate/run.py +++ b/demo/browserstate/run.py @@ -1,7 +1,7 @@ import random import string import gradio as gr - +import time with gr.Blocks() as demo: gr.Markdown("Your Username and Password will get saved in the browser's local storage. " "If you refresh the page, the values will be retained.") @@ -9,6 +9,7 @@ password = gr.Textbox(label="Password", type="password") btn = gr.Button("Generate Randomly") local_storage = gr.BrowserState(["", ""]) + saved_message = gr.Markdown("✅ Saved to local storage", visible=False) @btn.click(outputs=[username, password]) def generate_randomly(): @@ -25,4 +26,12 @@ def load_from_local_storage(saved_values): def save_to_local_storage(username, password): return [username, password] + @gr.on(local_storage.change, outputs=[saved_message]) + def show_saved_message(): + timestamp = time.strftime("%I:%M:%S %p") + return gr.Markdown( + f"✅ Saved to local storage at {timestamp}", + visible=True + ) + demo.launch() diff --git a/gradio/components/browser_state.py b/gradio/components/browser_state.py index 178893d9d53d6..2fc41cf2b1214 100644 --- a/gradio/components/browser_state.py +++ b/gradio/components/browser_state.py @@ -9,10 +9,12 @@ from gradio_client.documentation import document from gradio.components.base import Component +from gradio.events import Events @document() class BrowserState(Component): + EVENTS = [Events.change] """ Special component that stores state in the browser's localStorage in an encrypted format. """ diff --git a/js/browserstate/Index.svelte b/js/browserstate/Index.svelte index caba0c450d544..eedf9c86c5ad2 100644 --- a/js/browserstate/Index.svelte +++ b/js/browserstate/Index.svelte @@ -4,6 +4,7 @@ import { beforeUpdate } from "svelte"; import { encrypt, decrypt } from "./crypto"; import { dequal } from "dequal/lite"; + import type { Gradio } from "@gradio/utils"; export let storage_key: string; export let secret: string; @@ -11,6 +12,9 @@ export let value = default_value; let initialized = false; let old_value = value; + export let gradio: Gradio<{ + change: never; + }>; function load_value(): void { const stored = localStorage.getItem(storage_key); @@ -40,7 +44,13 @@ } } - $: value && !dequal(value, old_value) && save_value(); + $: value && + (() => { + if (!dequal(value, old_value)) { + save_value(); + gradio.dispatch("change"); + } + })(); beforeUpdate(() => { if (!initialized) { diff --git a/js/browserstate/package.json b/js/browserstate/package.json index db249f087ed75..9ef5384793759 100644 --- a/js/browserstate/package.json +++ b/js/browserstate/package.json @@ -17,7 +17,8 @@ }, "dependencies": { "dequal": "^2.0.2", - "crypto-js": "^4.1.1" + "crypto-js": "^4.1.1", + "@gradio/utils": "workspace:^" }, "peerDependencies": { "svelte": "^4.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da308be259b85..3c8e825748e42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -710,6 +710,9 @@ importers: js/browserstate: dependencies: + '@gradio/utils': + specifier: workspace:^ + version: link:../utils crypto-js: specifier: ^4.1.1 version: 4.2.0 From aa2eec284e58d7cb6359389176ddd005c5165c27 Mon Sep 17 00:00:00 2001 From: Yuvraj Sharma <48665385+yvrjsharma@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:26:14 +0530 Subject: [PATCH 3/3] Update the LLM Agents guide with Gemini2.0 Thinking demo (#10247) * Update the LLM Agents guide with Gemini2.0 Thinking demo * Addressed review comments on PR #10247 --- .../05_chatbots/03_agents-and-tool-usage.md | 148 +++++++++++++++++- 1 file changed, 142 insertions(+), 6 deletions(-) diff --git a/guides/05_chatbots/03_agents-and-tool-usage.md b/guides/05_chatbots/03_agents-and-tool-usage.md index 66b2caee756a7..ea74017230c6f 100644 --- a/guides/05_chatbots/03_agents-and-tool-usage.md +++ b/guides/05_chatbots/03_agents-and-tool-usage.md @@ -3,14 +3,14 @@ Tags: LLM, AGENTS, CHAT Related spaces: https://huggingface.co/spaces/gradio/agent_chatbot, https://huggingface.co/spaces/gradio/langchain-agent -The Gradio Chatbot can natively display intermediate thoughts and tool usage. This makes it perfect for creating UIs for LLM agents. This guide will show you how. +The Gradio Chatbot can natively display intermediate thoughts and tool usage. This makes it perfect for creating UIs for LLM agents and chain-of-thought (CoT) demos. This guide will show you how. ## The metadata key In addition to the `content` and `role` keys, the messages dictionary accepts a `metadata` key. At present, the `metadata` key accepts a dictionary with a single key called `title`. If you specify a `title` for the message, it will be displayed in a collapsible box. -Here is an example, were we display the agent's thought to use a weather API tool to answer the user query. +Here is an example, where we display the agent's thought to use a weather API tool to answer the user query. ```python with gr.Blocks() as demo: @@ -24,7 +24,9 @@ with gr.Blocks() as demo: ![simple-metadat-chatbot](https://github.com/freddyaboulton/freddyboulton/assets/41651716/3941783f-6835-4e5e-89a6-03f850d9abde) -## A real example using transformers.agents +## Building with Agents + +### A real example using transformers.agents We'll create a Gradio application simple agent that has access to a text-to-image tool. @@ -87,12 +89,11 @@ You can see the full demo code [here](https://huggingface.co/spaces/gradio/agent ![transformers_agent_code](https://github.com/freddyaboulton/freddyboulton/assets/41651716/c8d21336-e0e6-4878-88ea-e6fcfef3552d) -## A real example using langchain agents +### A real example using langchain agents We'll create a UI for langchain agent that has access to a search engine. -We'll begin with imports and setting up the langchain agent. Note that you'll need an .env file with -the following environment variables set - +We'll begin with imports and setting up the langchain agent. Note that you'll need an .env file with the following environment variables set - ``` SERPAPI_API_KEY= @@ -166,4 +167,139 @@ demo.launch() That's it! See our finished langchain demo [here](https://huggingface.co/spaces/gradio/langchain-agent). +## Building with Visibly Thinking LLMs + + +The Gradio Chatbot can natively display intermediate thoughts of a _thinking_ LLM. This makes it perfect for creating UIs that show how an AI model "thinks" while generating responses. Below guide will show you how to build a chatbot that displays Gemini AI's thought process in real-time. + + +### A real example using Gemini 2.0 Flash Thinking API + +Let's create a complete chatbot that shows its thoughts and responses in real-time. We'll use Google's Gemini API for accessing Gemini 2.0 Flash Thinking LLM and Gradio for the UI. + +We'll begin with imports and setting up the gemini client. Note that you'll need to [acquire a Google Gemini API key](https://aistudio.google.com/apikey) first - + +```python +import gradio as gr +from gradio import ChatMessage +from typing import Iterator +import google.generativeai as genai + +genai.configure(api_key="your-gemini-api-key") +model = genai.GenerativeModel("gemini-2.0-flash-thinking-exp-1219") +``` + +First, let's set up our streaming function that handles the model's output: + +```python +def stream_gemini_response(user_message: str, messages: list) -> Iterator[list]: + """ + Streams both thoughts and responses from the Gemini model. + """ + # Initialize response from Gemini + response = model.generate_content(user_message, stream=True) + + # Initialize buffers + thought_buffer = "" + response_buffer = "" + thinking_complete = False + + # Add initial thinking message + messages.append( + ChatMessage( + role="assistant", + content="", + metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"} + ) + ) + + for chunk in response: + parts = chunk.candidates[0].content.parts + current_chunk = parts[0].text + + if len(parts) == 2 and not thinking_complete: + # Complete thought and start response + thought_buffer += current_chunk + messages[-1] = ChatMessage( + role="assistant", + content=thought_buffer, + metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"} + ) + + # Add response message + messages.append( + ChatMessage( + role="assistant", + content=parts[1].text + ) + ) + thinking_complete = True + + elif thinking_complete: + # Continue streaming response + response_buffer += current_chunk + messages[-1] = ChatMessage( + role="assistant", + content=response_buffer + ) + + else: + # Continue streaming thoughts + thought_buffer += current_chunk + messages[-1] = ChatMessage( + role="assistant", + content=thought_buffer, + metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"} + ) + + yield messages +``` + +Then, let's create the Gradio interface: + +```python +with gr.Blocks() as demo: + gr.Markdown("# Chat with Gemini 2.0 Flash and See its Thoughts 💭") + + chatbot = gr.Chatbot( + type="messages", + label="Gemini2.0 'Thinking' Chatbot", + render_markdown=True, + ) + + input_box = gr.Textbox( + lines=1, + label="Chat Message", + placeholder="Type your message here and press Enter..." + ) + + # Set up event handlers + msg_store = gr.State("") # Store for preserving user message + + input_box.submit( + lambda msg: (msg, msg, ""), # Store message and clear input + inputs=[input_box], + outputs=[msg_store, input_box, input_box], + queue=False + ).then( + user_message, # Add user message to chat + inputs=[msg_store, chatbot], + outputs=[input_box, chatbot], + queue=False + ).then( + stream_gemini_response, # Generate and stream response + inputs=[msg_store, chatbot], + outputs=chatbot + ) + +demo.launch() +``` + +This creates a chatbot that: + +- Displays the model's thoughts in a collapsible section +- Streams the thoughts and final response in real-time +- Maintains a clean chat history + + That's it! You now have a chatbot that not only responds to users but also shows its thinking process, creating a more transparent and engaging interaction. See our finished Gemini 2.0 Flash Thinking demo [here](https://huggingface.co/spaces/ysharma/Gemini2-Flash-Thinking).