|
|
|
import modal |
|
import os |
|
from pathlib import Path |
|
|
|
|
|
app = modal.App("research-copilot") |
|
|
|
|
|
image = modal.Image.debian_slim(python_version="3.11").pip_install([ |
|
"gradio>=4.0.0", |
|
"httpx", |
|
"aiohttp", |
|
"python-dotenv", |
|
"requests", |
|
"beautifulsoup4", |
|
"openai", |
|
"anthropic", |
|
]) |
|
|
|
|
|
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, |
|
secrets=[ |
|
modal.Secret.from_name("research-copilot-secrets"), |
|
] |
|
) |
|
@modal.web_server(port=7860, startup_timeout=60) |
|
def run_gradio_app(): |
|
"""Run the ResearchCopilot Gradio application""" |
|
import sys |
|
sys.path.append("/app") |
|
|
|
|
|
from ResearchCopilot.research_copilot import create_interface |
|
|
|
app = create_interface() |
|
app.launch( |
|
server_name="0.0.0.0", |
|
server_port=7860, |
|
share=False, |
|
show_error=True, |
|
enable_queue=True |
|
) |
|
|
|
|
|
@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 { |
|
"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 { |
|
"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 { |
|
"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__": |
|
|
|
import subprocess |
|
subprocess.run(["python", "research_copilot.py"]) |