import gradio as gr import re from smolagents.memory import ActionStep, PlanningStep, FinalAnswerStep from smolagents.models import ChatMessageStreamDelta def get_step_footnote_content(step_log: ActionStep | PlanningStep, step_name: str) -> str: """Get a footnote string for a step log with duration and token information""" step_footnote = f"**{step_name}**" if hasattr(step_log, 'token_usage') and step_log.token_usage is not None: step_footnote += f" | Input tokens: {step_log.token_usage.input_tokens:,} | Output tokens: {step_log.token_usage.output_tokens:,}" if hasattr(step_log, 'timing') and step_log.timing and step_log.timing.duration: step_footnote += f" | Duration: {round(float(step_log.timing.duration), 2)}s" return f"""{step_footnote} """ def _clean_model_output(model_output: str) -> str: if not model_output: return "" model_output = model_output.strip() model_output = re.sub(r"```\s*", "```", model_output) model_output = re.sub(r"\s*```", "```", model_output) model_output = re.sub(r"```\s*\n\s*", "```", model_output) return model_output def _process_action_step(step_log: ActionStep, skip_model_outputs: bool = False): step_number = f"Step {step_log.step_number}" if not skip_model_outputs and getattr(step_log, "model_output", ""): model_output = _clean_model_output(step_log.model_output) yield gr.ChatMessage( role="assistant", content=model_output, metadata={"title": f"💭 Reasoning ({step_number})", "status": "done"} ) if getattr(step_log, "tool_calls", []): first_tool_call = step_log.tool_calls[0] args = first_tool_call.arguments content = str(args.get("answer", str(args))) if isinstance(args, dict) else str(args).strip() tool_name = first_tool_call.name icon = "🔍" if "search" in tool_name else "🛠️" yield gr.ChatMessage( role="assistant", content=f"```python\n{content}\n```", metadata={"title": f"{icon} Used tool: {tool_name}", "status": "done"} ) if getattr(step_log, "observations", "") and step_log.observations.strip(): log_content = step_log.observations.strip() yield gr.ChatMessage( role="assistant", content=f"```text\n{log_content}\n```", metadata={"title": "📋 Tool Output", "status": "done"} ) if getattr(step_log, "error", None): yield gr.ChatMessage( role="assistant", content=f"⚠️ **Error:** {str(step_log.error)}", metadata={"title": "🚫 Error", "status": "done"} ) yield gr.ChatMessage( role="assistant", content=get_step_footnote_content(step_log, step_number), metadata={"status": "done"} ) yield gr.ChatMessage(role="assistant", content="---", metadata={"status": "done"}) def _process_planning_step(step_log: PlanningStep, skip_model_outputs: bool = False): if not skip_model_outputs: yield gr.ChatMessage( role="assistant", content=step_log.plan, metadata={"title": "🧠 Planning Phase", "status": "done"} ) yield gr.ChatMessage( role="assistant", content=get_step_footnote_content(step_log, "Planning Stats"), metadata={"status": "done"} ) yield gr.ChatMessage(role="assistant", content="---", metadata={"status": "done"}) def _process_final_answer_step(step_log: FinalAnswerStep): """ Extracts the final answer from the step log, handling multiple possible attributes. """ final_answer = None possible_attrs = ['output', 'answer', 'final_answer'] for attr in possible_attrs: if hasattr(step_log, attr): final_answer = getattr(step_log, attr) if final_answer: break if final_answer is None: final_answer = str(step_log) match = re.search(r"output=(['\"])(.*?)\1\)", final_answer, re.DOTALL) if match: final_answer = match.group(2).encode('utf-8').decode('unicode_escape') content = final_answer if hasattr(content, 'to_string'): content = content.to_string() else: content = str(content) yield gr.ChatMessage( role="assistant", content=f"📜 **Final Answer**\n\n{content}", metadata={"status": "done"} ) def pull_messages_from_step(step_log, skip_model_outputs=False): if isinstance(step_log, PlanningStep): yield from _process_planning_step(step_log, skip_model_outputs) elif isinstance(step_log, ActionStep): yield from _process_action_step(step_log, skip_model_outputs) elif isinstance(step_log, FinalAnswerStep): yield from _process_final_answer_step(step_log) def stream_to_gradio(agent, task: str, reset_agent_memory: bool = False): """Main generator function for the Chat Interface""" for event in agent.run(task, stream=True, max_steps=10, reset=reset_agent_memory): if isinstance(event, (ActionStep, PlanningStep, FinalAnswerStep)): for message in pull_messages_from_step(event): yield message elif isinstance(event, ChatMessageStreamDelta): if event.content: yield event.content