explainor / modal_app.py
Emperor555's picture
Try explainor-v6 with Gradio 4.44.1
0df8486
raw
history blame
5.69 kB
"""Modal deployment configuration for Explainor.
Deploy with: modal deploy modal_app.py
Run locally: modal serve modal_app.py
"""
import modal
# Define the Modal app
app = modal.App("explainor-v6")
# Create image with dependencies
image = (
modal.Image.debian_slim(python_version="3.11")
.pip_install(
"gradio==4.44.1", # Use older stable version without aggressive SSE
"elevenlabs>=1.0.0",
"httpx>=0.25.0",
"python-dotenv>=1.0.0",
"fastapi",
"uvicorn",
)
.add_local_dir("src", remote_path="/app/src", copy=True)
)
@app.function(
image=image,
secrets=[
modal.Secret.from_name("nebius-api-key"),
modal.Secret.from_name("elevenlabs-api-key"),
],
timeout=600,
scaledown_window=300,
)
@modal.web_server(port=7860, startup_timeout=120)
def serve():
"""Serve the Gradio app via web_server."""
import subprocess
import sys
import os
os.chdir("/app")
sys.path.insert(0, "/app")
# Write a standalone gradio script
script = '''
import sys
sys.path.insert(0, "/app")
import os
import tempfile
import gradio as gr
from src.personas import get_persona_names, get_persona
from src.agent import run_agent
from src.tts import generate_speech
def format_sources(sources):
if not sources:
return "*No external sources used*"
md = ""
for i, src in enumerate(sources, 1):
if src.get("url"):
md += f"{i}. [{src['title']}]({src['url']})\\n"
else:
md += f"{i}. {src['title']} ({src.get('source', 'General')})\\n"
return md
def format_mcp_tools(tools):
if not tools:
return "*No tools used*"
md = "**Agent Tool Calls:**\\n\\n"
for tool in tools:
md += f"| {tool['icon']} | `{tool['name']}` | {tool['desc']} |\\n"
return md
def explain_topic(topic, persona_name, audience=""):
import traceback
if not topic.strip():
return "Please enter a topic!", "", "", ""
if not persona_name:
persona_name = "5-Year-Old"
# Check API key
nebius_key = os.getenv("NEBIUS_API_KEY")
if not nebius_key:
available_keys = [k for k in os.environ.keys() if "KEY" in k or "API" in k or "NEBIUS" in k]
return f"Error: NEBIUS_API_KEY not found. Available: {available_keys}", "", "", ""
steps_log = []
explanation = ""
sources = []
mcp_tools = []
try:
for update in run_agent(topic, persona_name, audience):
if update["type"] == "step":
steps_log.append(f"**{update['title']}**\\n{update['content']}")
if update["step"] == "research_done" and "sources" in update:
sources = update["sources"]
elif update["type"] == "result":
explanation = update["explanation"]
sources = update.get("sources", sources)
mcp_tools = update.get("mcp_tools", [])
except Exception as e:
return f"Error: {str(e)}\\n\\n{traceback.format_exc()}", "", "\\n\\n---\\n\\n".join(steps_log), ""
return explanation, format_sources(sources), "\\n\\n---\\n\\n".join(steps_log), format_mcp_tools(mcp_tools)
def generate_audio(explanation, persona_name):
if not explanation or not explanation.strip():
return None
if not persona_name:
persona_name = "5-Year-Old"
try:
persona = get_persona(persona_name)
audio_bytes = generate_speech(explanation, persona["voice_id"], persona.get("voice_settings"))
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
f.write(audio_bytes)
return f.name
except Exception as e:
raise gr.Error(f"Audio generation failed: {str(e)}")
# Get persona names as a static list
persona_names = list(get_persona_names())
with gr.Blocks(title="Explainor") as demo:
gr.Markdown("# Explainor\\n### Learn anything through the voice of your favorite characters!")
with gr.Row():
topic_input = gr.Textbox(label="Topic", placeholder="e.g., Quantum Computing")
persona_dropdown = gr.Dropdown(choices=persona_names, value="5-Year-Old", label="Persona")
audience_dropdown = gr.Dropdown(
choices=["Just me", "Confused grandmother", "Skeptical robot", "Alien"],
value="Just me",
label="Audience"
)
explain_btn = gr.Button("Explain!", variant="primary")
explanation_output = gr.Textbox(label="Explanation", lines=6)
read_aloud_btn = gr.Button("Read Aloud")
audio_output = gr.Audio(label="Listen", type="filepath", autoplay=True)
with gr.Accordion("Tools", open=False):
mcp_output = gr.Markdown("")
with gr.Accordion("Sources", open=False):
sources_output = gr.Markdown("")
with gr.Accordion("Trace", open=False):
steps_output = gr.Markdown("")
def do_explain(topic, persona, audience):
aud = "" if "Just me" in audience else audience
return explain_topic(topic, persona, aud)
explain_btn.click(
fn=do_explain,
inputs=[topic_input, persona_dropdown, audience_dropdown],
outputs=[explanation_output, sources_output, steps_output, mcp_output],
)
read_aloud_btn.click(
fn=generate_audio,
inputs=[explanation_output, persona_dropdown],
outputs=[audio_output],
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
'''
# Write to file and run
with open("/app/run_gradio.py", "w") as f:
f.write(script)
# Run the script with environment variables
env = os.environ.copy()
subprocess.Popen([sys.executable, "/app/run_gradio.py"], env=env)