Nymbo's picture
adding tool use
8d2c697 verified
raw
history blame
33.6 kB
```text
File: app.py
``````python
import gradio as gr
from huggingface_hub import InferenceClient as HubInferenceClient # Renamed to avoid conflict
import os
import json
import base64
from PIL import Image
import io
# Smolagents imports
from smolagents import CodeAgent, Tool, LiteLLMModel, OpenAIServerModel, TransformersModel, InferenceClientModel as SmolInferenceClientModel
from smolagents.gradio_ui import stream_to_gradio
ACCESS_TOKEN = os.getenv("HF_TOKEN")
print("Access token loaded.")
# Function to encode image to base64
def encode_image(image_path):
if not image_path:
print("No image path provided")
return None
try:
print(f"Encoding image from path: {image_path}")
# If it's already a PIL Image
if isinstance(image_path, Image.Image):
image = image_path
else:
# Try to open the image file
image = Image.open(image_path)
# Convert to RGB if image has an alpha channel (RGBA)
if image.mode == 'RGBA':
image = image.convert('RGB')
# Encode to base64
buffered = io.BytesIO()
image.save(buffered, format="JPEG")
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
print("Image encoded successfully")
return img_str
except Exception as e:
print(f"Error encoding image: {e}")
return None
# --- Smolagents Tool Definition ---
try:
image_generation_tool = Tool.from_space(
"black-forest-labs/FLUX.1-schnell",
name="image_generator",
description="Generates an image from a textual prompt. Use this tool if the user asks to generate, create, or draw an image.",
token=ACCESS_TOKEN # Pass token if the space might be private or has rate limits
)
print("Image generation tool loaded successfully.")
SMOLAGENTS_TOOLS = [image_generation_tool]
except Exception as e:
print(f"Error loading image generation tool: {e}. Proceeding without it.")
SMOLAGENTS_TOOLS = []
def respond(
message,
image_files, # Changed parameter name and structure
history: list[tuple[str, str]],
system_message,
max_tokens,
temperature,
top_p,
frequency_penalty,
seed,
provider,
custom_api_key,
custom_model,
model_search_term,
selected_model
):
print(f"Received message: {message}")
print(f"Received {len(image_files) if image_files else 0} images")
# print(f"History: {history}") # Can be very verbose
print(f"System message: {system_message}")
print(f"Max tokens: {max_tokens}, Temperature: {temperature}, Top-P: {top_p}")
print(f"Frequency Penalty: {frequency_penalty}, Seed: {seed}")
print(f"Selected provider: {provider}")
print(f"Custom API Key provided: {bool(custom_api_key.strip())}")
print(f"Selected model (custom_model): {custom_model}")
print(f"Model search term: {model_search_term}")
print(f"Selected model from radio: {selected_model}")
# Determine which token to use
token_to_use = custom_api_key if custom_api_key.strip() != "" else ACCESS_TOKEN
if custom_api_key.strip() != "":
print("USING CUSTOM API KEY: BYOK token provided by user is being used for authentication")
else:
print("USING DEFAULT API KEY: Environment variable HF_TOKEN is being used for authentication")
# Determine which model to use, prioritizing custom_model if provided
model_to_use = custom_model.strip() if custom_model.strip() != "" else selected_model
print(f"Model selected for LLM: {model_to_use}")
# Prepare parameters for the LLM
llm_parameters = {
"max_tokens": max_tokens, # For LiteLLMModel, OpenAIServerModel
"max_new_tokens": max_tokens, # For TransformersModel, InferenceClientModel
"temperature": temperature,
"top_p": top_p,
"frequency_penalty": frequency_penalty,
}
if seed != -1:
llm_parameters["seed"] = seed
# Initialize the smolagents Model
# For simplicity, we'll use InferenceClientModel if provider is hf-inference,
# otherwise LiteLLMModel which supports many providers.
# You might want to add more sophisticated logic to select the right smolagents Model class.
if provider == "hf-inference" or provider is None or provider == "": # provider can be None if custom_model is a URL
smol_model = SmolInferenceClientModel(
model_id=model_to_use,
token=token_to_use,
provider=provider if provider else None, # Pass provider only if it's explicitly set and not hf-inference default
**llm_parameters
)
print(f"Using SmolInferenceClientModel for LLM with provider: {provider or 'default'}")
else:
# Assuming other providers might be LiteLLM compatible
# LiteLLM uses `model` for model_id and `api_key` for token
smol_model = LiteLLMModel(
model_id=f"{provider}/{model_to_use}" if provider else model_to_use, # LiteLLM often expects provider/model_name
api_key=token_to_use,
**llm_parameters
)
print(f"Using LiteLLMModel for LLM with provider: {provider}")
# Initialize smolagent
# We'll use CodeAgent as it's generally more powerful.
# The system_message from the UI will be part of the task for the agent.
agent_task = message
if system_message and system_message.strip():
agent_task = f"System Instructions: {system_message}\n\nUser Task: {message}"
print(f"Initializing CodeAgent with model: {model_to_use}")
agent = CodeAgent(
tools=SMOLAGENTS_TOOLS, # Use the globally defined tools
model=smol_model,
stream_outputs=True # Important for streaming
)
print("CodeAgent initialized.")
# Prepare multimodal inputs for the agent if images are present
agent_images = []
if image_files and len(image_files) > 0:
for img_path in image_files:
if img_path:
try:
# Smolagents expects PIL Image objects for images
pil_image = Image.open(img_path)
agent_images.append(pil_image)
except Exception as e:
print(f"Error opening image for agent: {e}")
print(f"Prepared {len(agent_images)} images for the agent.")
# Start with an empty string to build the response as tokens stream in
response_text = ""
print(f"Running agent with task: {agent_task}")
try:
# Use stream_to_gradio for handling agent's streaming output
# The history needs to be converted to the format smolagents expects if we want to continue conversations.
# For now, we'll pass reset=True to simplify, meaning each call is a new conversation for the agent.
# To support conversation history with the agent, `history` needs to be transformed into agent.memory.steps
# or passed appropriately. The `stream_to_gradio` function expects the agent's internal stream.
# Simplified history for agent (if needed, but stream_to_gradio handles Gradio's history)
# For `agent.run`, we don't directly pass Gradio's history.
# `stream_to_gradio` will yield messages that Gradio's chatbot can append.
# The `stream_to_gradio` function itself is a generator.
# It takes the agent and task, and yields Gradio-compatible chat messages.
# The `bot` function in Gradio needs to yield these messages.
# The `respond` function is already a generator, so we can yield from `stream_to_gradio`.
# Gradio's history (list of tuples) is not directly used by agent.run()
# Instead, the agent's own memory would handle conversational context if reset=False.
# Here, we'll let stream_to_gradio handle the output formatting.
print("Streaming response from agent...")
for content_chunk in stream_to_gradio(
agent,
task=agent_task,
task_images=agent_images if agent_images else None,
reset_agent_memory=True # For simplicity, treat each interaction as new for the agent
):
# stream_to_gradio yields either a string (for text delta) or a ChatMessage object
if isinstance(content_chunk, str): # This is a text delta
response_text += content_chunk
yield response_text
elif hasattr(content_chunk, 'content'): # This is a ChatMessage object
if isinstance(content_chunk.content, dict) and 'path' in content_chunk.content: # Image/Audio
# Gradio's chatbot can handle dicts for files directly if msg.submit is used
# For streaming, we yield the path or a markdown representation
yield f"![file]({content_chunk.content['path']})"
elif isinstance(content_chunk.content, str):
response_text = content_chunk.content # Replace if it's a full message
yield response_text
else: # Should not happen with stream_to_gradio's typical output
print(f"Unexpected chunk type from stream_to_gradio: {type(content_chunk)}")
yield str(content_chunk)
print("\nCompleted response generation from agent.")
except Exception as e:
print(f"Error during agent execution: {e}")
response_text += f"\nError: {str(e)}"
yield response_text
# Function to validate provider selection based on BYOK
def validate_provider(api_key, provider):
if not api_key.strip() and provider != "hf-inference":
return gr.update(value="hf-inference")
return gr.update(value=provider)
# GRADIO UI
with gr.Blocks(theme="Nymbo/Nymbo_Theme") as demo:
# Create the chatbot component
chatbot = gr.Chatbot(
height=600,
show_copy_button=True,
placeholder="Select a model and begin chatting. Now supports multiple inference providers, multimodal inputs, and image generation tool.",
layout="panel",
show_share_button=True # Added for easy sharing
)
print("Chatbot interface created.")
# Multimodal textbox for messages (combines text and file uploads)
msg = gr.MultimodalTextbox(
placeholder="Type a message or upload images... (e.g., 'generate an image of a cat playing chess')",
show_label=False,
container=False,
scale=12,
file_types=["image"],
file_count="multiple",
sources=["upload"]
)
# Create accordion for settings
with gr.Accordion("Settings", open=False):
# System message
system_message_box = gr.Textbox(
value="You are a helpful AI assistant that can understand images and text. If asked to generate an image, use the available image_generator tool.",
placeholder="You are a helpful assistant.",
label="System Prompt"
)
# Generation parameters
with gr.Row():
with gr.Column():
max_tokens_slider = gr.Slider(
minimum=1,
maximum=4096,
value=1024, # Increased default for potentially longer agent outputs
step=1,
label="Max tokens"
)
temperature_slider = gr.Slider(
minimum=0.1,
maximum=4.0,
value=0.7,
step=0.1,
label="Temperature"
)
top_p_slider = gr.Slider(
minimum=0.1,
maximum=1.0,
value=0.95,
step=0.05,
label="Top-P"
)
with gr.Column():
frequency_penalty_slider = gr.Slider(
minimum=-2.0,
maximum=2.0,
value=0.0,
step=0.1,
label="Frequency Penalty"
)
seed_slider = gr.Slider(
minimum=-1,
maximum=65535,
value=-1,
step=1,
label="Seed (-1 for random)"
)
# Provider selection
providers_list = [
"hf-inference", # Default Hugging Face Inference
"cerebras", # Cerebras provider
"together", # Together AI
"sambanova", # SambaNova
"novita", # Novita AI
"cohere", # Cohere
"fireworks-ai", # Fireworks AI
"hyperbolic", # Hyperbolic
"nebius", # Nebius
# Add other providers supported by LiteLLM if desired
]
provider_radio = gr.Radio(
choices=providers_list,
value="hf-inference",
label="Inference Provider",
)
# New BYOK textbox
byok_textbox = gr.Textbox(
value="",
label="BYOK (Bring Your Own Key)",
info="Enter a custom Hugging Face API key here. When empty, only 'hf-inference' provider can be used. For other providers, this key will be used as their respective API key.",
placeholder="Enter your API token",
type="password" # Hide the API key for security
)
# Custom model box
custom_model_box = gr.Textbox(
value="",
label="Custom Model",
info="(Optional) Provide a custom Hugging Face model path (e.g., 'meta-llama/Llama-3.3-70B-Instruct') or a model name compatible with the selected provider. Overrides any selected featured model.",
placeholder="meta-llama/Llama-3.3-70B-Instruct"
)
# Model search
model_search_box = gr.Textbox(
label="Filter Models",
placeholder="Search for a featured model...",
lines=1
)
# Featured models list
models_list = [
"meta-llama/Llama-3.2-11B-Vision-Instruct",
"meta-llama/Llama-3.3-70B-Instruct",
"meta-llama/Llama-3.1-70B-Instruct",
"meta-llama/Llama-3.0-70B-Instruct",
"meta-llama/Llama-3.2-3B-Instruct",
"meta-llama/Llama-3.2-1B-Instruct",
"meta-llama/Llama-3.1-8B-Instruct",
"NousResearch/Hermes-3-Llama-3.1-8B",
"NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
"mistralai/Mistral-Nemo-Instruct-2407",
"mistralai/Mixtral-8x7B-Instruct-v0.1",
"mistralai/Mistral-7B-Instruct-v0.3",
"mistralai/Mistral-7B-Instruct-v0.2",
"Qwen/Qwen3-235B-A22B",
"Qwen/Qwen3-32B",
"Qwen/Qwen2.5-72B-Instruct",
"Qwen/Qwen2.5-3B-Instruct",
"Qwen/Qwen2.5-0.5B-Instruct",
"Qwen/QwQ-32B",
"Qwen/Qwen2.5-Coder-32B-Instruct",
"microsoft/Phi-3.5-mini-instruct",
"microsoft/Phi-3-mini-128k-instruct",
"microsoft/Phi-3-mini-4k-instruct",
]
featured_model_radio = gr.Radio(
label="Select a model below (or specify a custom one above)",
choices=models_list,
value="meta-llama/Llama-3.2-11B-Vision-Instruct", # Default to a multimodal model
interactive=True
)
gr.Markdown("[View all Text-to-Text models](https://huggingface.co/models?inference_provider=all&pipeline_tag=text-generation&sort=trending) | [View all multimodal models](https://huggingface.co/models?inference_provider=all&pipeline_tag=image-text-to-text&sort=trending)")
# Chat history state
chat_history = gr.State([])
# Function to filter models
def filter_models(search_term):
print(f"Filtering models with search term: {search_term}")
filtered = [m for m in models_list if search_term.lower() in m.lower()]
print(f"Filtered models: {filtered}")
return gr.update(choices=filtered)
# Function to set custom model from radio (actually, sets the selected_model which is then overridden by custom_model_box if filled)
def set_selected_model_from_radio(selected):
print(f"Featured model selected: {selected}")
# This function's output will be one of the inputs to `respond`
return selected
# Function for the chat interface
def user(user_message_input, history):
# user_message_input is a dict from MultimodalTextbox: {"text": str, "files": list[str]}
print(f"User input received: {user_message_input}")
text_content = user_message_input.get("text", "").strip()
files = user_message_input.get("files", [])
if not text_content and not files:
print("Empty message, skipping history update.")
return history # Or gr.skip() if Gradio version supports it well
# Append to Gradio's history format
# For multimodal, Gradio expects a list of (text, file_path) tuples or (None, file_path)
# We will represent this as a single user turn which might have text and multiple images.
# The `respond` function will then parse this.
# Gradio's Chatbot can display images if the message is a tuple (None, filepath)
# or if text contains markdown like ![alt](filepath)
current_turn_display = []
if text_content:
current_turn_display.append(text_content)
if files:
for file_path in files:
current_turn_display.append((file_path,)) # Tuple for Gradio to recognize as file
if not current_turn_display: # Should not happen if we check above
return history
# For simplicity in history, we'll just append the text and a note about images.
# The actual image data is passed separately to `respond`.
display_message = text_content
if files:
display_message += f" ({len(files)} image(s) uploaded)"
history.append([display_message, None])
return history
# Define bot response function
def bot(history, system_msg, max_tokens_val, temperature_val, top_p_val, freq_penalty_val, seed_val, provider_val, api_key_val, custom_model_val, search_term_val, selected_model_val, request: gr.Request):
if not history or not history[-1][0]: # If no user message
yield history
return
# The user's latest input is in history[-1][0]
# The MultimodalTextbox sends a dict: {"text": str, "files": list[str]}
# However, our `user` function above simplifies this for display in `chatbot`.
# We need to retrieve the original input from the request if possible, or parse history.
# For simplicity with Gradio's streaming and history, we'll re-parse the last user message.
# This is not ideal but works for this setup.
last_user_turn_display = history[-1][0]
# This is a simplified parsing. A more robust way would be to pass
# the raw MultimodalTextbox output to `bot` directly.
user_text_content = ""
user_image_files = []
if isinstance(last_user_turn_display, str):
# Check if it's a simple text or a text with image count
img_count_match = re.search(r" \((\d+) image\(s\) uploaded\)$", last_user_turn_display)
if img_count_match:
user_text_content = last_user_turn_display[:img_count_match.start()]
# We can't get back the actual file paths from this string alone.
# This part needs the raw input from MultimodalTextbox.
# For now, we'll assume image_files are passed correctly to `respond`
# This means `msg.submit` should pass `msg` directly to `respond`'s `message` param.
else:
user_text_content = last_user_turn_display
# The `msg` (MultimodalTextbox) component's value is what we need for image_files
# We assume `msg.value` is implicitly passed or accessible via `request` if Gradio supports it,
# or it should be an explicit input to `bot`.
# For this implementation, we rely on `msg` being passed to `respond` via the `submit` chain.
# The `history` argument to `bot` is for the chatbot display.
# The actual call to `respond` will happen via the `msg.submit` chain.
# This `bot` function is primarily for updating the chatbot display.
history[-1][1] = "" # Clear previous bot response
# `respond` is a generator. We need to iterate through its yields.
# The `msg` component's value (which includes text and files) is the first argument to `respond`.
# We need to ensure that `msg` is correctly passed.
# The current `msg.submit` passes `msg` (the component itself) to `user`, then `user`'s output to `bot`.
# This is problematic for getting the raw files.
# Correct approach: `msg.submit` should pass `msg` (value) to `respond` (or a wrapper).
# Let's assume `respond` will be called correctly by the `msg.submit` chain.
# This `bot` function will just yield the history updates.
# The actual generation is now handled by `msg.submit(...).then(respond, ...)`
# This `bot` function is mostly a placeholder in the new structure if `respond` directly yields to chatbot.
# However, Gradio's `chatbot.then(bot, ...)` expects `bot` to be the generator.
# Re-structuring: `msg.submit` calls `user` to update history for display.
# Then, `user`'s output (which is just `history`) is passed to `bot`.
# `bot` then calls `respond` with all necessary parameters.
# Extract the latest user message components (text and files)
# This is tricky because `history` only has the display string.
# We need the raw `msg` value.
# The `request: gr.Request` can sometimes hold component values if using `gr.Interface`.
# For Blocks, it's better to pass `msg` directly.
# Let's assume `user_text_content` and `user_image_files` are correctly extracted
# from the `msg` component's value when `respond` is called.
# The `bot` function here will iterate over what `respond` yields.
# The `message` param for `respond` should be the raw output of `msg`
# So, `msg` (the component) should be an input to `bot`.
# Then `bot` extracts `text` and `files` from `msg.value` (or `msg` if it's already the value).
# The `msg.submit` chain needs to be:
# msg.submit(fn=user_interaction_handler, inputs=[msg, chatbot, ...other_params...], outputs=[chatbot])
# where user_interaction_handler calls `user` then `respond`.
# For now, let's assume `respond` is correctly called by the `msg.submit` chain
# and this `bot` function is what updates the chatbot display.
# The `inputs` to `bot` in `msg.submit(...).then(bot, inputs=[...])` are crucial.
# The `message` and `image_files` for `respond` will come from the `msg` component.
# The `history` for `respond` will be `history[:-1]` (all but the current user turn).
# This `bot` function is essentially the core of `respond` now.
# It needs `msg_value` as an input.
# Let's rename this function to reflect it's the main generation logic
# and ensure it gets the raw `msg` value.
# The Gradio `msg.submit` will call a wrapper that then calls this.
# For simplicity, we'll assume `respond` is called correctly by the chain.
# This `bot` function is what `chatbot.then(bot, ...)` uses.
# The `history` object here is the one managed by Gradio's Chatbot.
# `history[-1][0]` is the user's latest displayed message.
# `history[-1][1]` is where the bot's response goes.
# The `respond` function needs the raw message and files.
# The `msg` component itself should be an input to this `bot` function.
# Let's adjust the `msg.submit` call later.
# For now, this `bot` function is the generator that `chatbot.then()` expects.
# It will internally call `respond`.
# The `message` and `image_files` for `respond` must be sourced from the `msg` component's value,
# not from `history[-1][0]`.
# This function signature is what `chatbot.then(bot, ...)` will use.
# The `inputs` to this `bot` must be correctly specified in `msg.submit(...).then(bot, inputs=...)`.
# `msg_input` should be the value of the `msg` MultimodalTextbox.
# Let's assume `msg_input` is correctly passed as the first argument to this `bot` function.
# We'll rename `history` to `chatbot_history` to avoid confusion.
# The `msg.submit` chain should be:
# 1. `user` function: takes `msg_input`, `chatbot_history` -> updates `chatbot_history` for display, returns raw `msg_input` and `chatbot_history[:-1]` for `respond`.
# 2. `respond` function: takes raw `msg_input`, `history_for_respond`, and other params -> yields response chunks.
# Simpler: `msg.submit` calls `respond_wrapper` which handles history and calls `respond`.
# The current structure: `msg.submit` calls `user`, then `bot`.
# `user` appends user's input to `chatbot` (history).
# `bot` gets this updated `chatbot` (history).
# `bot` needs to extract the latest user input (text & files) to pass to `respond`.
# This is difficult because `history` only has display strings.
# Solution: `msg` (the component's value) must be passed to `bot`.
# Let's adjust the `msg.submit` later. For now, assume `message_and_files_input` is passed.
# This function's signature for `chatbot.then(bot, ...)`:
# bot(chatbot_history, system_msg, ..., msg_input_value)
# The `msg_input_value` will be the first argument if we adjust the `inputs` list.
# Let's assume the first argument `chatbot_history` is the chatbot's state.
# The actual user input (text + files) needs to be passed separately.
# The `inputs` to `bot` in the `.then(bot, inputs=[...])` call must include `msg`.
# If `respond` is called directly by `msg.submit().then()`, then `respond` itself is the generator.
# The `chatbot` component updates based on what `respond` yields.
# The current `msg.submit` structure is:
# .then(user, [msg, chatbot], [chatbot]) <- `user` updates chatbot with user's message
# .then(bot, [chatbot, ...other_params...], [chatbot]) <- `bot` generates response
# `bot` needs the raw `msg` value. Let's add `msg` as an input to `bot`.
# The `inputs` list for `.then(bot, ...)` will need to include `msg`.
# The `message` and `image_files` for `respond` should come from `msg_val` (the value of the msg component)
# `history_for_api` should be `chatbot_history[:-1]`
# The `chatbot` variable passed to `bot` is the current state of the Chatbot UI.
# `chatbot[-1][0]` is the latest user message displayed.
# `chatbot[-1][1]` is where the bot's response will be streamed.
# We need the raw `msg` value. Let's assume it's passed as an argument to `bot`.
# The `inputs` in `.then(bot, inputs=[msg, chatbot, ...])`
# The `respond` function will be called with:
# - message: text from msg_val
# - image_files: files from msg_val
# - history: chatbot_history[:-1] (all previous turns)
# This `bot` function is the one that `chatbot.then()` will call.
# It needs `msg_val` as an input.
# The `inputs` for this `bot` function in the Gradio chain will be:
# [chatbot, system_message_box, ..., msg]
# So, `msg_val` will be the last parameter.
msg_val = history.pop('_msg_val_temp_') # Retrieve the raw msg value
raw_text_input = msg_val.get("text", "")
raw_file_inputs = msg_val.get("files", [])
# The history for the API should be all turns *before* the current user input
history_for_api = [turn for turn in history[:-1]] # all but the last (current) turn
history[-1][1] = "" # Clear placeholder for bot response
for chunk in respond(
message=raw_text_input,
image_files=raw_file_inputs,
history=history_for_api, # Pass history *before* current user turn
system_message=system_msg,
max_tokens=max_tokens_val,
temperature=temperature_val,
top_p=top_p_val,
frequency_penalty=freq_penalty_val,
seed=seed_val,
provider=provider_val,
custom_api_key=api_key_val,
custom_model=custom_model_val,
selected_model=selected_model_val, # selected_model is now the one from radio
model_search_term=search_term_val # Though search_term is not directly used by respond
):
history[-1][1] = chunk # Stream to the last message's bot part
yield history
# Event handlers
# We need to pass the raw `msg` value to the `bot` function.
# We can temporarily store it in the `history` state object if Gradio allows modifying state objects directly.
# A cleaner way is to have a single handler function.
def combined_user_and_bot(msg_val, chatbot_history, system_msg, max_tokens_val, temperature_val, top_p_val, freq_penalty_val, seed_val, provider_val, api_key_val, custom_model_val, search_term_val, selected_model_val):
# 1. Call user to update chatbot display
updated_chatbot_history = user(msg_val, chatbot_history)
yield updated_chatbot_history # Show user message immediately
# 2. Call respond (which is now the core generation logic)
# The history for `respond` should be `updated_chatbot_history[:-1]`
# Clear placeholder for bot's response in the last turn
if updated_chatbot_history and updated_chatbot_history[-1] is not None:
updated_chatbot_history[-1][1] = ""
history_for_api = updated_chatbot_history[:-1] if updated_chatbot_history else []
for chunk in respond(
message=msg_val.get("text", ""),
image_files=msg_val.get("files", []),
history=history_for_api,
system_message=system_msg,
max_tokens=max_tokens_val,
temperature=temperature_val,
top_p=top_p_val,
frequency_penalty=freq_penalty_val,
seed=seed_val,
provider=provider_val,
custom_api_key=api_key_val,
custom_model=custom_model_val,
selected_model=selected_model_val,
model_search_term=search_term_val
):
if updated_chatbot_history and updated_chatbot_history[-1] is not None:
updated_chatbot_history[-1][1] = chunk
yield updated_chatbot_history
msg.submit(
combined_user_and_bot,
[msg, chatbot, system_message_box, max_tokens_slider, temperature_slider, top_p_slider,
frequency_penalty_slider, seed_slider, provider_radio, byok_textbox, custom_model_box,
model_search_box, featured_model_radio], # Pass `msg` (value of MultimodalTextbox)
[chatbot]
).then(
lambda: {"text": "", "files": []}, # Clear inputs after submission
None,
[msg]
)
# Connect the model filter to update the radio choices
model_search_box.change(
fn=filter_models,
inputs=model_search_box,
outputs=featured_model_radio
)
print("Model search box change event linked.")
# Connect the featured model radio to update the custom model box (if user selects from radio, it populates custom_model_box)
featured_model_radio.change(
fn=lambda selected_model_from_radio: selected_model_from_radio, # Directly pass the value
inputs=featured_model_radio,
outputs=custom_model_box # This makes custom_model_box reflect the radio selection
# User can then override it by typing.
)
print("Featured model radio button change event linked.")
# Connect the BYOK textbox to validate provider selection
byok_textbox.change(
fn=validate_provider,
inputs=[byok_textbox, provider_radio],
outputs=provider_radio
)
print("BYOK textbox change event linked.")
# Also validate provider when the radio changes to ensure consistency
provider_radio.change(
fn=validate_provider,
inputs=[byok_textbox, provider_radio],
outputs=provider_radio
)
print("Provider radio button change event linked.")
print("Gradio interface initialized.")
if __name__ == "__main__":
print("Launching the demo application.")
demo.launch(show_api=True, share=True) # Added share=True for easier testing