import os import textwrap # ─── CrewAI core ───────────────────────────────────────────────────────────── from crewai import Agent, Task, Crew, Process, LLM from crewai_tools import SerperDevTool, ScrapeWebsiteTool # ─── UI ────────────────────────────────────────────────────────────────────── import gradio as gr # ============================================================================= # API KEYS # ============================================================================= OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") SERPER_API_KEY = os.environ.get("SERPER_API_KEY", "") if not OPENAI_API_KEY: raise EnvironmentError( "❌ OPENAI_API_KEY not found. " "Add it in Space → Settings → Variables and secrets." ) if not SERPER_API_KEY: raise EnvironmentError( "❌ SERPER_API_KEY not found. " "Add it in Space → Settings → Variables and secrets." ) os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY os.environ["SERPER_API_KEY"] = SERPER_API_KEY # ============================================================================= # LLM # ============================================================================= llm = LLM( model="gpt-4o-mini", temperature=0.4, max_tokens=2000, ) # ============================================================================= # TOOLS # ============================================================================= search_tool = SerperDevTool() scrape_tool = ScrapeWebsiteTool() # ============================================================================= # MEMORY # ============================================================================= MEMORY_DIR = "/tmp/crewai_memory" os.makedirs(MEMORY_DIR, exist_ok=True) EMBEDDER_CONFIG = { "provider": "openai", "config": { "model": "text-embedding-3-small", "api_key": OPENAI_API_KEY, }, } # ============================================================================= # AGENTS # ============================================================================= researcher = Agent( role="Senior Research Analyst", goal=( "Find the most accurate, up-to-date, and relevant information on the given topic." ), backstory="Veteran research analyst with deep investigation skills.", tools=[search_tool, scrape_tool], llm=llm, verbose=True, allow_delegation=False, max_iter=5, ) writer = Agent( role="Professional Content Writer", goal="Write a compelling structured article from research.", backstory="Expert in long-form and technical writing.", llm=llm, verbose=True, allow_delegation=False, max_iter=4, ) editor = Agent( role="Senior Editor", goal="Polish article to publication quality.", backstory="Meticulous editor ensuring clarity and consistency.", llm=llm, verbose=True, allow_delegation=False, max_iter=3, ) # ============================================================================= # TASK BUILDER # ============================================================================= def build_tasks(topic: str, audience: str, tone: str, length: str): research_task = Task( description=f"Research topic: {topic}", expected_output="Structured research summary", agent=researcher, ) writing_task = Task( description=f"Write article on: {topic}", expected_output="Full article", agent=writer, context=[research_task], ) editing_task = Task( description="Edit article to final form", expected_output="Final polished article", agent=editor, context=[writing_task], ) return [research_task, writing_task, editing_task] # ============================================================================= # CREW RUNNER # ============================================================================= def run_crew(topic: str, audience: str, tone: str, length: str): if not topic.strip(): return "Please enter a topic." try: tasks = build_tasks(topic, audience, tone, length) crew = Crew( agents=[researcher, writer, editor], tasks=tasks, process=Process.sequential, verbose=True, memory=True, embedder=EMBEDDER_CONFIG, memory_config={"long_term": {"storage_path": MEMORY_DIR}}, ) result = crew.kickoff() return str(result) except Exception as e: return f"Error: {str(e)}" # ============================================================================= # UI # ============================================================================= with gr.Blocks() as demo: gr.Markdown("# Multi-Agent Article Generator") with gr.Row(): with gr.Column(): topic_input = gr.Textbox(label="Topic", lines=3) audience_input = gr.Dropdown( ["General Public", "Business", "Students"], value="General Public", label="Audience", ) tone_input = gr.Dropdown( ["Informative", "Conversational", "Academic"], value="Informative", label="Tone", ) length_input = gr.Radio( ["Short", "Medium", "Long"], value="Medium", label="Length", ) submit_btn = gr.Button("Generate") clear_btn = gr.Button("Clear") with gr.Column(): output_box = gr.Textbox( label="Output", lines=30, interactive=False, ) submit_btn.click( fn=run_crew, inputs=[topic_input, audience_input, tone_input, length_input], outputs=output_box, ) clear_btn.click( fn=lambda: ("", ""), inputs=[], outputs=[topic_input, output_box], ) # ============================================================================= # LAUNCH (UPDATED FOR GRADIO 6) # ============================================================================= demo.launch( server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate"), css=""" footer { display: none !important; } """, )