ResearchCopilot / modal_app.py
Ajey95
commit final files
4b88321
# 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"])