| | import os |
| | import io |
| | import sys |
| | import re |
| | import traceback |
| | import subprocess |
| | import gradio as gr |
| | import pandas as pd |
| | from dotenv import load_dotenv |
| | from crewai import Crew, Agent, Task, Process, LLM |
| | from crewai_tools import FileReadTool |
| | from pydantic import BaseModel, Field |
| |
|
| | |
| | load_dotenv() |
| |
|
| | |
| | OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') |
| | if not OPENAI_API_KEY: |
| | raise ValueError("OPENAI_API_KEY environment variable not set") |
| |
|
| | llm = LLM( |
| | model="openai/gpt-4o", |
| | api_key=OPENAI_API_KEY, |
| | temperature=0.7 |
| | ) |
| |
|
| | |
| | query_parser_agent = Agent( |
| | role="Stock Data Analyst", |
| | goal="Extract stock details and fetch required data from this user query: {query}.", |
| | backstory="You are a financial analyst specializing in stock market data retrieval.", |
| | llm=llm, |
| | verbose=True, |
| | memory=True, |
| | ) |
| |
|
| | |
| | class QueryAnalysisOutput(BaseModel): |
| | """Structured output for the query analysis task.""" |
| | symbols: list[str] = Field( |
| | ..., |
| | json_schema_extra={"description": "List of stock ticker symbols (e.g., ['TSLA', 'AAPL'])."} |
| | ) |
| | timeframe: str = Field( |
| | ..., |
| | json_schema_extra={"description": "Time period (e.g., '1d', '1mo', '1y')."} |
| | ) |
| | action: str = Field( |
| | ..., |
| | json_schema_extra={"description": "Action to be performed (e.g., 'fetch', 'plot')."} |
| | ) |
| |
|
| |
|
| | query_parsing_task = Task( |
| | description="Analyze the user query and extract stock details.", |
| | expected_output="A dictionary with keys: 'symbol', 'timeframe', 'action'.", |
| | output_pydantic=QueryAnalysisOutput, |
| | agent=query_parser_agent, |
| | ) |
| |
|
| | |
| | code_writer_agent = Agent( |
| | role="Senior Python Developer", |
| | goal="Write Python code to visualize stock data.", |
| | backstory="""You are a Senior Python developer specializing in stock market data visualization. |
| | You are also a Pandas, Matplotlib and yfinance library expert. |
| | You are skilled at writing production-ready Python code. |
| | Ensure the code handles potential variations in the DataFrame structure returned by yfinance, |
| | especially for different timeframes or delisted stocks. |
| | Crucially, ensure the generated script saves any generated plot as 'plot.png' using `plt.savefig('plot.png')` before the script ends.""", |
| | llm=llm, |
| | verbose=True, |
| | ) |
| |
|
| | code_writer_task = Task( |
| | description="""Write Python code to visualize stock data based on the inputs from the stock analyst |
| | where you would find stock symbol, timeframe and action.""", |
| | expected_output="A clean and executable Python script file (.py) for stock visualization.", |
| | agent=code_writer_agent, |
| | ) |
| |
|
| | |
| | code_output_agent = Agent( |
| | role="Python Code Presenter", |
| | goal="Present the generated Python code for stock visualization.", |
| | backstory="You are an expert in presenting Python code in a clear and readable format.", |
| | allow_delegation=False, |
| | llm=llm, |
| | verbose=True, |
| | ) |
| |
|
| | code_output_task = Task( |
| | description="""Receive the Python code for stock visualization from the code writer agent and present it.""", |
| | expected_output="The complete Python script for stock visualization.", |
| | agent=code_output_agent, |
| | ) |
| |
|
| | crew = Crew( |
| | agents=[query_parser_agent, code_writer_agent, code_output_agent], |
| | tasks=[query_parsing_task, code_writer_task, code_output_task], |
| | process=Process.sequential |
| | ) |
| |
|
| |
|
| | def run_crewai_process(user_query, model, temperature): |
| | """ |
| | Runs the CrewAI process, captures agent thoughts, gets generated code, |
| | executes the code, and returns results, including plot. |
| | |
| | Args: |
| | user_query (str): The user's query for the CrewAI process. |
| | model (str): The model to use for the LLM. |
| | temperature (float): The temperature to use for the LLM. |
| | |
| | Yields: |
| | tuple: A tuple containing the agent thoughts (str), the final answer (list of dicts), |
| | the generated code (str), the execution output (str), and plot file path (str or None). |
| | """ |
| | |
| | output_buffer = io.StringIO() |
| | original_stdout = sys.stdout |
| | sys.stdout = output_buffer |
| | agent_thoughts = "" |
| | generated_code = "" |
| | execution_output = "" |
| | generated_plot_path = None |
| | final_answer_chat = [{"role": "user", "content": user_query}] |
| |
|
| | try: |
| | |
| | initial_message = {"role": "assistant", "content": "Starting CrewAI process..."} |
| | final_answer_chat = [{"role": "user", "content": str(user_query)}, initial_message] |
| | yield final_answer_chat, agent_thoughts, generated_code, execution_output, None, None |
| |
|
| | |
| | final_result = crew.kickoff(inputs={"query": user_query}) |
| |
|
| | |
| | agent_thoughts = output_buffer.getvalue() |
| | |
| | |
| | processing_message = {"role": "assistant", "content": "Processing complete. Generating code..."} |
| | final_answer_chat = [{"role": "user", "content": str(user_query)}, processing_message] |
| | yield final_answer_chat, agent_thoughts, generated_code, execution_output, None, None |
| |
|
| | |
| | generated_code_raw = str(final_result).strip() |
| |
|
| | |
| | code_match = re.search(r"```python\n(.*?)\n```", generated_code_raw, re.DOTALL) |
| | if code_match: |
| | generated_code = code_match.group(1).strip() |
| | else: |
| | |
| | generated_code = generated_code_raw |
| | if not generated_code.strip(): |
| | execution_output = "CrewAI process completed, but no code was generated." |
| | final_answer_chat.append({"role": "assistant", "content": execution_output}) |
| | yield agent_thoughts, final_answer_chat, generated_code, execution_output, generated_plot_path |
| | return |
| |
|
| | |
| | code_gen_message = {"role": "assistant", "content": "Code generation complete. See the 'Generated Code' box. Attempting to execute code..."} |
| | final_answer_chat = [{"role": "user", "content": str(user_query)}, code_gen_message] |
| | yield final_answer_chat, agent_thoughts, generated_code, execution_output, None, None |
| |
|
| | |
| | plot_file_path = 'plot.png' |
| |
|
| | if generated_code: |
| | try: |
| | |
| | temp_script_path = "generated_script.py" |
| | with open(temp_script_path, "w") as f: |
| | f.write(generated_code) |
| |
|
| | |
| | try: |
| | |
| | debug_script = f""" |
| | import traceback |
| | import sys |
| | import os |
| | import matplotlib |
| | # Use non-interactive backend to avoid display issues |
| | matplotlib.use('Agg') |
| | import matplotlib.pyplot as plt |
| | |
| | # Debug info |
| | print("="*80) |
| | print("STARTING SCRIPT EXECUTION") |
| | print("="*80) |
| | print(f"[DEBUG] Python version: {{sys.version}}") |
| | print(f"[DEBUG] Working directory: {{os.getcwd()}}") |
| | print("[DEBUG] Directory contents:") |
| | for f in os.listdir('.'): |
| | print(f" - {{f}}{' (dir)' if os.path.isdir(f) else ''}") |
| | print("\n" + "="*80 + "\n") |
| | |
| | try: |
| | # Create a test plot first to verify matplotlib is working |
| | test_fig, test_ax = plt.subplots() |
| | test_ax.plot([1, 2, 3], [1, 4, 9]) |
| | test_ax.set_title('Test Plot - If you see this, matplotlib is working') |
| | test_plot_path = 'test_plot.png' |
| | test_fig.savefig(test_plot_path, bbox_inches='tight') |
| | print(f"[DEBUG] Test plot saved to: {{os.path.abspath(test_plot_path)}}") |
| | print(f"[DEBUG] Test plot size: {{os.path.getsize(test_plot_path)}} bytes") |
| | |
| | # Execute the original script |
| | print("\n" + "="*80) |
| | print("EXECUTING USER SCRIPT") |
| | print("="*80) |
| | {generated_code} |
| | |
| | # Ensure any pending plots are drawn |
| | plt.ioff() |
| | |
| | # Save any open figures |
| | print("\n" + "="*80) |
| | print("SAVING PLOTS") |
| | print("="*80) |
| | |
| | # Get list of all figure numbers |
| | fig_nums = plt.get_fignums() |
| | print(f"[DEBUG] Found {{len(fig_nums)}} open figures") |
| | |
| | if fig_nums: |
| | # Save the last figure as plot.png |
| | last_fig = plt.figure(fig_nums[-1]) |
| | plot_path = os.path.abspath('plot.png') |
| | last_fig.savefig(plot_path, bbox_inches='tight', dpi=100) |
| | print(f"[DEBUG] Saved plot to: {{plot_path}}") |
| | print(f"[DEBUG] Plot file size: {{os.path.getsize(plot_path)}} bytes") |
| | |
| | # Save all figures with unique names |
| | for i, num in enumerate(fig_nums, 1): |
| | fig = plt.figure(num) |
| | fig_path = os.path.abspath(f'plot_{{i}}.png') |
| | fig.savefig(fig_path, bbox_inches='tight', dpi=100) |
| | print(f"[DEBUG] Saved additional plot to: {{fig_path}} ({{os.path.getsize(fig_path)}} bytes)") |
| | else: |
| | print("[WARNING] No figures were created in the script") |
| | |
| | # Print final directory contents |
| | print("\n" + "="*80) |
| | print("FINAL DIRECTORY CONTENTS") |
| | print("="*80) |
| | for f in os.listdir('.'): |
| | fpath = os.path.join('.', f) |
| | if os.path.isfile(fpath): |
| | print(f" - {{f}} ({{os.path.getsize(fpath)}} bytes)") |
| | else: |
| | print(f" - {{f}}/ (dir)") |
| | |
| | print("\n" + "="*80) |
| | print("SCRIPT EXECUTION COMPLETE") |
| | print("="*80) |
| | |
| | except Exception as e: |
| | print("\n" + "!"*80) |
| | print("ERROR DURING EXECUTION") |
| | print("!"*80) |
| | print(f"Error type: {{type(e).__name__}}") |
| | print(f"Error message: {{str(e)}}") |
| | print("\nTraceback:") |
| | traceback.print_exc() |
| | print("\n" + "!"*80 + "\n") |
| | raise |
| | |
| | finally: |
| | # Always close all figures to free memory |
| | plt.close('all') |
| | """ |
| | |
| | with open(temp_script_path, "w") as f: |
| | f.write(debug_script) |
| |
|
| | |
| | process = subprocess.run( |
| | ["python3", temp_script_path], |
| | capture_output=True, |
| | text=True, |
| | check=False |
| | ) |
| | |
| | |
| | execution_output = process.stdout |
| | if process.stderr: |
| | execution_output += "\n\n[ERROR] Script execution errors:\n" + process.stderr |
| | |
| | |
| | if "KeyError" in execution_output: |
| | execution_output += "\n\n[HELP] The script encountered a KeyError. This typically happens when trying to access a column that doesn't exist in the stock data.\n" |
| | execution_output += "Common causes:\n" |
| | execution_output += "1. The stock symbol might not be recognized by yfinance\n" |
| | execution_output += "2. The requested time period might not have data (e.g., weekends, holidays)\n" |
| | execution_output += "3. The data column names might be different than expected\n\n" |
| | execution_output += "Please try a different stock symbol or time period." |
| | |
| | if "No data" in execution_output or "not found" in execution_output.lower(): |
| | execution_output += "\n\n[HELP] No data was returned for the specified stock symbol or time period.\n" |
| | execution_output += "Please check the stock symbol and try a different time period." |
| | |
| | if "Figure(" in execution_output and "plot.png" not in os.listdir(): |
| | execution_output += "\n\n[HELP] A plot was created but not saved. Adding save command...\n" |
| | |
| | try: |
| | import matplotlib.pyplot as plt |
| | if plt.get_fignums(): |
| | plt.savefig('plot.png') |
| | execution_output += "Successfully saved plot to plot.png" |
| | generated_plot_path = 'plot.png' |
| | plt.close('all') |
| | except Exception as e: |
| | execution_output += f"Failed to save plot: {str(e)}" |
| | except Exception as e: |
| | execution_output = f"Error during script execution: {str(e)}\n\n" |
| | execution_output += "Please check the generated code for issues or try a different query." |
| |
|
| | |
| | plot_debug_info = [] |
| | plot_found = False |
| | |
| | |
| | current_dir = os.getcwd() |
| | plot_abs_path = os.path.abspath(plot_file_path) |
| | |
| | |
| | plot_debug_info.append(f"Current directory: {current_dir}") |
| | plot_debug_info.append("Directory contents:" + "\n- " + "\n- ".join(os.listdir('.'))) |
| | |
| | |
| | if os.path.exists(plot_file_path): |
| | plot_found = True |
| | plot_debug_info.append(f"✅ Plot file found at: {plot_abs_path}") |
| | generated_plot_path = plot_file_path |
| | else: |
| | |
| | for root, _, files in os.walk('.'): |
| | if plot_file_path in files: |
| | found_path = os.path.join(root, plot_file_path) |
| | plot_found = True |
| | plot_debug_info.append(f"✅ Plot file found at: {os.path.abspath(found_path)}") |
| | generated_plot_path = found_path |
| | break |
| | |
| | if not plot_found: |
| | plot_debug_info.append(f"❌ Plot file not found at: {plot_abs_path}") |
| | plot_debug_info.append("Troubleshooting tips:") |
| | plot_debug_info.append("1. Ensure the script calls plt.savefig('plot.png')") |
| | plot_debug_info.append("2. Check for any errors in the execution output") |
| | plot_debug_info.append("3. Verify the script has write permissions in the current directory") |
| | |
| | |
| | execution_output += "\n\n[PLOT DEBUG] " + "\n[PLOT DEBUG] ".join(plot_debug_info) |
| | |
| | if not plot_found: |
| | execution_output += f"\n\n[ERROR] Plot file '{plot_file_path}' was not generated. Check the debug information above for details." |
| |
|
| | except Exception as e: |
| | traceback_str = traceback.format_exc() |
| | execution_output = f"An error occurred during code execution: {e}\n{traceback_str}" |
| |
|
| | finally: |
| | |
| | if os.path.exists(temp_script_path): |
| | os.remove(temp_script_path) |
| |
|
| | else: |
| | execution_output = "No code was generated to execute." |
| |
|
| | |
| | execution_complete_msg = "Code execution finished. See 'Execution Output'." |
| | if generated_plot_path: |
| | plot_msg = "Plot generated successfully. See 'Generated Plot'." |
| | final_answer_chat = [ |
| | {"role": "user", "content": str(user_query)}, |
| | {"role": "assistant", "content": execution_complete_msg}, |
| | {"role": "assistant", "content": plot_msg} |
| | ] |
| | else: |
| | no_plot_msg = "No plot was generated. Check the execution output for details." |
| | final_answer_chat = [ |
| | {"role": "user", "content": str(user_query)}, |
| | {"role": "assistant", "content": execution_complete_msg}, |
| | {"role": "assistant", "content": no_plot_msg} |
| | ] |
| |
|
| | yield agent_thoughts, final_answer_chat, generated_code, execution_output, generated_plot_path |
| |
|
| | except Exception as e: |
| | |
| | traceback_str = traceback.format_exc() |
| | agent_thoughts += f"\nAn error occurred during CrewAI process: {e}\n{traceback_str}" |
| | error_message = f"An error occurred during CrewAI process: {e}" |
| | final_answer_chat = [ |
| | {"role": "user", "content": str(user_query)}, |
| | {"role": "assistant", "content": error_message} |
| | ] |
| | yield final_answer_chat, agent_thoughts, generated_code, execution_output, None, None |
| |
|
| | finally: |
| | |
| | sys.stdout = original_stdout |
| |
|
| |
|
| | def create_interface(): |
| | """Create and return the Gradio interface.""" |
| | with gr.Blocks(title="Financial Analytics Agent", theme=gr.themes.Soft()) as interface: |
| | gr.Markdown("# 📊 Financial Analytics Agent") |
| | gr.Markdown("Enter your financial query to analyze stock data and generate visualizations.") |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=2): |
| | user_query_input = gr.Textbox( |
| | label="Enter your financial query", |
| | placeholder="e.g., Show me the stock performance of AAPL and MSFT for the last year", |
| | lines=3 |
| | ) |
| | submit_btn = gr.Button("Analyze", variant="primary") |
| | |
| | with gr.Accordion("Advanced Options", open=False): |
| | gr.Markdown("### Model Settings") |
| | model_dropdown = gr.Dropdown( |
| | ["gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"], |
| | value="gpt-4o", |
| | label="Model" |
| | ) |
| | temperature = gr.Slider( |
| | minimum=0.1, |
| | maximum=1.0, |
| | value=0.7, |
| | step=0.1, |
| | label="Creativity (Temperature)" |
| | ) |
| | |
| | with gr.Column(scale=3): |
| | with gr.Tabs(): |
| | with gr.TabItem("Analysis"): |
| | final_answer_chat = gr.Chatbot( |
| | label="Analysis Results", |
| | height=300, |
| | show_copy_button=True, |
| | type="messages" |
| | ) |
| | |
| | with gr.TabItem("Agent Thoughts"): |
| | agent_thoughts = gr.Textbox( |
| | label="Agent Thinking Process", |
| | interactive=False, |
| | lines=15, |
| | max_lines=30, |
| | show_copy_button=True |
| | ) |
| | |
| | with gr.TabItem("Generated Code"): |
| | generated_code = gr.Code( |
| | label="Generated Python Code", |
| | language="python", |
| | interactive=False, |
| | lines=15 |
| | ) |
| | |
| | with gr.TabItem("Execution Output"): |
| | execution_output = gr.Textbox( |
| | label="Code Execution Output", |
| | interactive=False, |
| | lines=10, |
| | show_copy_button=True |
| | ) |
| | |
| | with gr.Row(): |
| | with gr.Column(): |
| | plot_output = gr.Plot( |
| | label="Generated Visualization", |
| | visible=False |
| | ) |
| | image_output = gr.Image( |
| | label="Generated Plot", |
| | type="filepath", |
| | visible=False |
| | ) |
| | |
| | |
| | inputs = [user_query_input, model_dropdown, temperature] |
| | outputs = [ |
| | final_answer_chat, |
| | agent_thoughts, |
| | generated_code, |
| | execution_output, |
| | plot_output, |
| | image_output |
| | ] |
| | |
| | submit_btn.click( |
| | fn=run_crewai_process, |
| | inputs=inputs, |
| | outputs=outputs, |
| | api_name="analyze" |
| | ) |
| | |
| | return interface |
| |
|
| |
|
| | def main(): |
| | """Run the Gradio interface.""" |
| | interface = create_interface() |
| | interface.launch(share=False, server_name="0.0.0.0", server_port=7860) |
| |
|
| |
|
| | if __name__ == "__main__": |
| | main() |