import gradio as gr import logging import os import time import io # Required for StringIO log capture from agent import ResearchAgent # Ensure agent.py is in the same directory or accessible # --- Logging Setup for Gradio --- # Use a distinct logger name if needed, or configure the root logger log_stream = io.StringIO() # In-memory stream to capture logs # Configure root logger to send INFO+ logs to the stream AND console logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s', handlers=[ logging.StreamHandler(), # Log to console logging.StreamHandler(log_stream) # Log to our stream ]) # Optional: Set level specifically for the agent's logger if it uses one # logging.getLogger('agent_logger_name').setLevel(logging.INFO) # --- Agent Initialization --- try: # Pass max_iterations from a config or fixed value agent_instance = ResearchAgent(max_iterations=3) logging.info("ResearchAgent initialized successfully.") except Exception as e: # Log critical error to console even if stream handler isn't fully working yet print(f"CRITICAL: Failed to initialize ResearchAgent: {e}") logging.critical(f"Failed to initialize ResearchAgent: {e}", exc_info=True) agent_instance = None # --- Gradio Interaction Function --- def run_research(query: str): """ Function called by Gradio button click. Runs the agent and returns results. Yields status updates and final report/errors/logs. """ if not agent_instance: # Use yield to update Gradio components even for errors yield "Error: Agent could not be initialized. Please check console logs.", "", "" if not query or not query.strip(): yield "Please enter a research query.", "", "" return # Stop execution # Clear the log stream for this new run log_stream.seek(0) log_stream.truncate(0) logging.info(f"Gradio received query: '{query}'") start_time = time.time() final_report_content = "Starting..." final_error_content = "" final_log_content = "" try: # --- Initial Status Update --- yield "Processing query... (Agent is running, logs will appear after completion)", "", log_stream.getvalue() # --- Run the Agent --- # This call blocks until the agent finishes final_state = agent_instance.run(query.strip()) # --- Process Final State --- end_time = time.time() duration = end_time - start_time logging.info(f"Agent run finished in {duration:.2f} seconds.") final_report_content = final_state.get("final_report", "No report generated.") errors = final_state.get("error_log", []) # Format errors for display if errors: final_error_content = "\n".join(f"- {e}" for e in errors) logging.warning(f"Agent run completed with errors.") else: final_error_content = "None" # Get all logs captured during the run final_log_content = log_stream.getvalue() # --- Final Update --- yield final_report_content, final_error_content, final_log_content except Exception as e: end_time = time.time() duration = end_time - start_time logging.critical(f"An unexpected error occurred during agent execution ({duration:.2f}s): {e}", exc_info=True) # Update UI with critical error info yield f"An critical error occurred: {e}", final_error_content, log_stream.getvalue() + f"\nCRITICAL ERROR: {e}" # --- Gradio UI Definition --- # Updated explanation reflecting Tavily flow explanation_markdown = """ # Web Research Agent Demo & Explanation (Tavily Version) This application demonstrates an AI-powered Web Research Agent built using LangGraph, Google's Gemini LLM, and the **Tavily Search API**. ## What it Does Given a research query, the agent attempts to autonomously: 1. **Understand** the query's intent and key topics using an LLM. 2. **Search** the web using **Tavily** for AI-optimized results, including summaries and potential direct answers. 3. **Evaluate** the gathered information using an LLM. 4. **Iterate** the search process with refined queries if more information is needed. 5. **Synthesize** the findings from Tavily into a coherent report using an LLM. ## How it Works: The Agent's Flow (LangGraph + Tavily) The agent operates as a state machine orchestrated by **LangGraph**: 1. **`analyze_query`**: Uses Gemini to analyze the query and generate initial search terms for Tavily. 2. **`tavily_search`**: Uses the **Tavily API** to perform the search, retrieving ranked results with content summaries and optionally a direct answer. 3. **`evaluate_progress`**: Uses Gemini to review the Tavily results (summaries/answer) against the original query goals. It decides whether to `continue_search` (generating new queries for Tavily) or `synthesize`. 4. **`synthesize_report`**: Uses Gemini to combine the information gathered from Tavily into a final, structured report, citing the sources provided by Tavily. *This flow replaces previous versions that required separate web scraping and content analysis steps.* ## Core Technologies * **Orchestration:** LangGraph * **LLM:** Google Gemini 1.5 Flash * **Web Search & Content:** **Tavily Search API** (`tavily-python`) * **UI:** Gradio ## Limitations * **API Dependencies:** Relies on Google Gemini and Tavily APIs. * **Tavily Content Quality:** Research quality depends on Tavily's results and summaries. * **LLM Performance:** Analysis, evaluation, and synthesis quality depend on the LLM. * **Factuality:** Reports information found via Tavily; doesn't independently verify facts. * **Iteration Limit:** Stops after a fixed number of search cycles. ## How to Use 1. Enter your research question in the box below. 2. Click "Run Research". 3. Wait for processing. Status updates will appear in the "Research Report" box. 4. The final report appears in the "Research Report" box. Any errors appear in the "Errors Log" box. **A detailed log of the agent's operations during the run appears in the "Run Logs" box *after* completion.** """ # Use gr.Blocks for more layout control with gr.Blocks(theme=gr.themes.Soft(), title="Web Research Agent (Tavily)") as demo: gr.Markdown(explanation_markdown) # Display the updated explanation with gr.Row(): query_input = gr.Textbox(label="Enter your research query:", placeholder="e.g., Recent advancements in renewable energy?", scale=4) submit_button = gr.Button("Run Research", scale=1) with gr.Column(): # Textbox for status updates and final report report_output = gr.Textbox(label="Status / Research Report", lines=15, interactive=False) # Textbox for errors encountered error_output = gr.Textbox(label="Errors Log", lines=3, interactive=False) # Textbox for the detailed run logs log_output = gr.Textbox(label="Run Logs (Updates after completion)", lines=10, interactive=False) # Connect button click to function # Output components match the order yielded by run_research: report, error, log submit_button.click( fn=run_research, inputs=[query_input], outputs=[report_output, error_output, log_output] # Ensure 3 outputs match yield ) if __name__ == "__main__": if agent_instance is None: print("CRITICAL: Research Agent failed to initialize. Gradio UI cannot start properly.") # Optionally, launch Gradio with a basic error message # with gr.Blocks() as error_demo: # gr.Markdown("# Error\n\nResearch Agent failed to initialize. Please check the console logs for details.") # error_demo.launch() else: print("Launching Gradio UI...") # Set share=False for local use, share=True for temporary public link demo.launch(share=False)