# modal_app.py - Modal deployment for ResearchCopilot import modal import os from pathlib import Path # Create Modal app app = modal.App("research-copilot") # Define the environment with required packages image = modal.Image.debian_slim(python_version="3.11").pip_install([ "gradio>=4.0.0", "httpx", "aiohttp", "python-dotenv", "requests", "beautifulsoup4", "openai", # For potential LLM integrations "anthropic", # For Claude integration ]) # Mount the application code code_mount = modal.Mount.from_local_dir( ".", remote_path="/app", condition=lambda path: path.suffix in [".py", ".txt", ".md"] ) @app.function( image=image, mounts=[code_mount], allow_concurrent_inputs=100, timeout=3600, # 1 hour timeout for long research tasks secrets=[ modal.Secret.from_name("research-copilot-secrets"), # API keys ] ) @modal.web_server(port=7860, startup_timeout=60) def run_gradio_app(): """Run the ResearchCopilot Gradio application""" import sys sys.path.append("/app") # Import and run the main application from ResearchCopilot.research_copilot import create_interface app = create_interface() app.launch( server_name="0.0.0.0", server_port=7860, share=False, # Modal handles the sharing show_error=True, enable_queue=True ) # Enhanced retriever with real API integrations @app.function( image=image, secrets=[modal.Secret.from_name("research-copilot-secrets")], timeout=300 ) async def search_perplexity(query: str, num_results: int = 5): """Search using Perplexity API""" import httpx import os api_key = os.getenv("PERPLEXITY_API_KEY") if not api_key: # Return mock data if no API key return { "results": [ { "title": f"Mock Result for: {query}", "url": "https://example.com/mock", "snippet": f"This is a mock result for the query: {query}", "source_type": "web" } ] } async with httpx.AsyncClient() as client: try: response = await client.post( "https://api.perplexity.ai/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json={ "model": "llama-3.1-sonar-small-128k-online", "messages": [ {"role": "user", "content": f"Search for: {query}"} ], "max_tokens": 1000, "temperature": 0.2, "return_citations": True } ) if response.status_code == 200: data = response.json() return {"results": data.get("choices", [{}])[0].get("message", {}).get("content", "")} else: return {"error": f"API error: {response.status_code}"} except Exception as e: return {"error": str(e)} @app.function( image=image, secrets=[modal.Secret.from_name("research-copilot-secrets")], timeout=300 ) async def search_google(query: str, num_results: int = 10): """Search using Google Custom Search API""" import httpx import os api_key = os.getenv("GOOGLE_API_KEY") search_engine_id = os.getenv("GOOGLE_SEARCH_ENGINE_ID") if not api_key or not search_engine_id: # Return mock data if no API keys return { "results": [ { "title": f"Google Search: {query}", "url": "https://example.com/google-mock", "snippet": f"Mock Google search result for: {query}", "source_type": "web" } ] } async with httpx.AsyncClient() as client: try: response = await client.get( "https://www.googleapis.com/customsearch/v1", params={ "key": api_key, "cx": search_engine_id, "q": query, "num": min(num_results, 10) } ) if response.status_code == 200: data = response.json() results = [] for item in data.get("items", []): results.append({ "title": item.get("title", ""), "url": item.get("link", ""), "snippet": item.get("snippet", ""), "source_type": "web" }) return {"results": results} else: return {"error": f"Google API error: {response.status_code}"} except Exception as e: return {"error": str(e)} @app.function( image=image, secrets=[modal.Secret.from_name("research-copilot-secrets")], timeout=600 ) async def summarize_with_claude(content: str, context: str = ""): """Summarize content using Claude API""" import httpx import os api_key = os.getenv("ANTHROPIC_API_KEY") if not api_key: # Return mock summary if no API key return { "summary": f"Mock summary of content: {content[:100]}...", "key_points": ["Point 1", "Point 2", "Point 3"] } async with httpx.AsyncClient() as client: try: response = await client.post( "https://api.anthropic.com/v1/messages", headers={ "x-api-key": api_key, "Content-Type": "application/json", "anthropic-version": "2023-06-01" }, json={ "model": "claude-3-sonnet-20240229", "max_tokens": 1000, "messages": [ { "role": "user", "content": f"Summarize this content and extract key points:\n\nContext: {context}\n\nContent: {content}" } ] } ) if response.status_code == 200: data = response.json() content_text = data.get("content", [{}])[0].get("text", "") return { "summary": content_text, "key_points": ["AI-generated summary", "Professional analysis", "Comprehensive overview"] } else: return {"error": f"Claude API error: {response.status_code}"} except Exception as e: return {"error": str(e)} if __name__ == "__main__": # For local development import subprocess subprocess.run(["python", "research_copilot.py"])