Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding duration, step number, token counts, and nested structure to gradio UI #270

Closed
Closed
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
106 changes: 80 additions & 26 deletions src/smolagents/gradio_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,79 @@
from .utils import _is_package_available


def pull_messages_from_step(step_log: AgentStepLog):
"""Extract ChatMessage objects from agent steps"""
import gradio as gr

def pull_messages_from_step(step_log: AgentStepLog, test_mode: bool = True):
"""Extract ChatMessage objects from agent steps with proper nesting"""
if isinstance(step_log, ActionStep):
yield gr.ChatMessage(role="assistant", content=step_log.llm_output or "")
# First yield the thought/reasoning from the LLM
if step_log.llm_output is not None and not test_mode:
yield gr.ChatMessage(role="assistant", content=step_log.llm_output.strip())

# For tool calls, create a parent message
if step_log.tool_calls is not None:
first_tool_call = step_log.tool_calls[0]
used_code = first_tool_call.name == "code interpreter"
used_code = first_tool_call.name == "python_interpreter"
parent_id = f"call_{len(step_log.tool_calls)}"

# Calculate token information
token_str = ""
if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
token_str = f"Input-tokens:{step_log.input_token_count:,} Output-tokens:{step_log.output_token_count:,}"

step_str = f"Step {step_log.step}" if step_log.step is not None else ""

timing_str = " | ".join(filter(None, [step_str, token_str]))

# Tool call becomes the parent message with timing info
content = first_tool_call.arguments
if used_code:
content = f"```py\n{content}\n```"
yield gr.ChatMessage(
content = f"```python\n{content}\n```"

parent_message = gr.ChatMessage(
role="assistant",
metadata={"title": f"πŸ› οΈ Used tool {first_tool_call.name}"},
content=str(content),
content=content,
metadata={
"title": f"πŸ› οΈ Used tool {first_tool_call.name}" + (f" ({timing_str})" if timing_str else ""),
"id": parent_id,
"status": "pending",
"duration": step_log.duration if step_log.duration else None
}
)
if step_log.observations is not None:
yield gr.ChatMessage(role="assistant", content=step_log.observations)
if step_log.error is not None:
yield parent_message

# Nest execution logs under the tool call if they exist
if step_log.observations is not None:
log_content = step_log.observations
yield gr.ChatMessage(
role="assistant",
content=f"Execution logs:\n{log_content}",
metadata={
"title": "πŸ“ Execution Logs",
"parent_id": parent_id,
"status": "done"
}
)

# Nest any errors under the tool call
if step_log.error is not None:
yield gr.ChatMessage(
role="assistant",
content=str(step_log.error),
metadata={
"title": "πŸ’₯ Error",
"parent_id": parent_id,
"status": "done"
}
)

# Update parent message metadata to done status (without yielding a new message)
parent_message.metadata["status"] = "done"

# Handle standalone errors (not from tool calls)
elif step_log.error is not None:
yield gr.ChatMessage(
role="assistant",
content=str(step_log.error),
metadata={"title": "πŸ’₯ Error"},
metadata={"title": "πŸ’₯ Error"}
)


Expand All @@ -58,14 +107,21 @@ def stream_to_gradio(
additional_args: Optional[dict] = None,
):
"""Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
if not _is_package_available("gradio"):
raise ModuleNotFoundError(
"Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[audio]'`"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep this message here, no? But there's a typo: smolagents[audio] -> smolagents[gradio]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @abidlabs! Changing this in #384

)
import gradio as gr
total_input_tokens = 0
total_output_tokens = 0

for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
for message in pull_messages_from_step(step_log):
for step_log in agent.run(
task, stream=True, reset=reset_agent_memory, additional_args=additional_args
):
# Track tokens if model provides them
if hasattr(agent.model, "last_input_token_count"):
total_input_tokens += agent.model.last_input_token_count
total_output_tokens += agent.model.last_output_token_count
if isinstance(step_log, ActionStep):
step_log.input_token_count = agent.model.last_input_token_count
step_log.output_token_count = agent.model.last_output_token_count

for message in pull_messages_from_step(step_log, test_mode=test_mode):
yield message

final_answer = step_log # Last log is the run's final_answer
Expand Down Expand Up @@ -174,9 +230,7 @@ def log_user_message(self, text_input, file_uploads_log):
)

def launch(self):
import gradio as gr

with gr.Blocks() as demo:
with gr.Blocks(fill_height=True) as demo:
stored_messages = gr.State([])
file_uploads_log = gr.State([])
chatbot = gr.Chatbot(
Expand All @@ -187,6 +241,7 @@ def launch(self):
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
),
resizeable=True,
scale=1
)
# If an upload folder is provided, enable the upload feature
if self.file_upload_folder is not None:
Expand All @@ -204,7 +259,6 @@ def launch(self):
[stored_messages, text_input],
).then(self.interact_with_agent, [stored_messages, chatbot], [chatbot])

demo.launch()

demo.launch(debug=True, share=True)

__all__ = ["stream_to_gradio", "GradioUI"]
Loading