Spaces:
Paused
Paused
| # 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"] | |
| ) | |
| 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 | |
| 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)} | |
| 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)} | |
| 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"]) |