7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
500 lines
17 KiB
Python
Executable File
500 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
HTTP API Examples for Local Deep Research v1.0+
|
|
|
|
This script demonstrates comprehensive usage of the LDR HTTP API with authentication.
|
|
Includes examples for research, settings management, and batch operations.
|
|
|
|
Requirements:
|
|
- LDR v1.0+ (with authentication features)
|
|
- LDR server running: python -m local_deep_research.web.app
|
|
- Beautiful Soup: pip install beautifulsoup4
|
|
|
|
================================================================================
|
|
IMPORTANT - LOCALHOST ONLY
|
|
================================================================================
|
|
This example ONLY works when connecting via localhost:
|
|
✅ http://localhost:5000
|
|
✅ http://127.0.0.1:5000
|
|
|
|
It will NOT work when connecting via:
|
|
❌ http://192.168.x.x:5000 (local network IP)
|
|
❌ http://your-server.com:5000 (remote server)
|
|
|
|
WHY: For security, session cookies require HTTPS for non-localhost connections.
|
|
|
|
SOLUTIONS for non-localhost access:
|
|
1. Use HTTPS with a reverse proxy (recommended for production)
|
|
2. SSH tunnel: ssh -L 5000:localhost:5000 user@server, then use localhost:5000
|
|
3. Set TESTING=1 when starting server (INSECURE - development only!)
|
|
|
|
WARNING: TESTING=1 disables secure cookie protection. Never use in production.
|
|
================================================================================
|
|
"""
|
|
|
|
import time
|
|
from typing import Any, Dict, List
|
|
import requests
|
|
from pathlib import Path
|
|
import sys
|
|
from bs4 import BeautifulSoup
|
|
|
|
# Add the src directory to Python path for programmatic user creation
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
|
|
|
|
from local_deep_research.database.encrypted_db import DatabaseManager
|
|
from local_deep_research.database.models import User
|
|
from local_deep_research.database.auth_db import auth_db_session
|
|
|
|
# Configuration
|
|
BASE_URL = "http://localhost:5000"
|
|
|
|
|
|
def create_test_user():
|
|
"""Create a test user programmatically - works out of the box!"""
|
|
username = f"testuser_{int(time.time())}"
|
|
password = "testpassword123"
|
|
|
|
print(f"Creating test user: {username}")
|
|
|
|
try:
|
|
# Create user in auth database
|
|
with auth_db_session() as session:
|
|
new_user = User(username=username)
|
|
session.add(new_user)
|
|
session.commit()
|
|
|
|
# Create encrypted database for user
|
|
db_manager = DatabaseManager()
|
|
db_manager.create_user_database(username, password)
|
|
|
|
print(f"✅ User created successfully: {username}")
|
|
return username, password
|
|
|
|
except Exception as e:
|
|
print(f"❌ Failed to create user: {e}")
|
|
return None, None
|
|
|
|
|
|
class LDRClient:
|
|
"""Client for interacting with LDR API v1.0+ with authentication"""
|
|
|
|
def __init__(self, base_url: str = BASE_URL):
|
|
self.base_url = base_url
|
|
self.session = requests.Session()
|
|
self.csrf_token = None
|
|
self.username = None
|
|
|
|
def login(self, username: str, password: str) -> bool:
|
|
"""Authenticate with the LDR server."""
|
|
try:
|
|
# Get login page and CSRF token
|
|
login_page = self.session.get(f"{self.base_url}/auth/login")
|
|
if login_page.status_code != 200:
|
|
return False
|
|
|
|
soup = BeautifulSoup(login_page.text, "html.parser")
|
|
csrf_input = soup.find("input", {"name": "csrf_token"})
|
|
login_csrf = csrf_input.get("value")
|
|
|
|
if not login_csrf:
|
|
return False
|
|
|
|
# Submit login form
|
|
login_response = self.session.post(
|
|
f"{self.base_url}/auth/login",
|
|
data={
|
|
"username": username,
|
|
"password": password,
|
|
"csrf_token": login_csrf,
|
|
},
|
|
allow_redirects=False,
|
|
)
|
|
|
|
if login_response.status_code not in [200, 302]:
|
|
return False
|
|
|
|
self.username = username
|
|
|
|
# Get API CSRF token for API calls
|
|
csrf_response = self.session.get(f"{self.base_url}/auth/csrf-token")
|
|
if csrf_response.status_code == 200:
|
|
self.csrf_token = csrf_response.json().get("csrf_token")
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
return False
|
|
|
|
def logout(self) -> None:
|
|
"""Logout from the server."""
|
|
if self.csrf_token:
|
|
self.session.post(
|
|
f"{self.base_url}/auth/logout",
|
|
headers={"X-CSRF-Token": self.csrf_token},
|
|
)
|
|
|
|
def _get_headers(self) -> Dict[str, str]:
|
|
"""Get headers with CSRF token."""
|
|
return {"X-CSRF-Token": self.csrf_token} if self.csrf_token else {}
|
|
|
|
def check_health(self) -> Dict[str, Any]:
|
|
"""Check API health status."""
|
|
response = self.session.get(f"{self.base_url}/auth/check")
|
|
return response.json()
|
|
|
|
def start_research(self, query: str, **kwargs) -> Dict[str, Any]:
|
|
"""Start a new research task."""
|
|
payload = {
|
|
"query": query,
|
|
"model": kwargs.get("model"),
|
|
"search_engines": kwargs.get("search_engines", ["wikipedia"]),
|
|
"iterations": kwargs.get("iterations", 2),
|
|
"questions_per_iteration": kwargs.get("questions_per_iteration", 3),
|
|
"temperature": kwargs.get("temperature", 0.7),
|
|
"local_context": kwargs.get("local_context", 2000),
|
|
"web_context": kwargs.get("web_context", 2000),
|
|
}
|
|
|
|
response = self.session.post(
|
|
f"{self.base_url}/api/start_research",
|
|
json=payload,
|
|
headers=self._get_headers(),
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
raise Exception(f"Failed to start research: {response.text}")
|
|
|
|
def get_research_status(self, research_id: str) -> Dict[str, Any]:
|
|
"""Get the status of a research task."""
|
|
response = self.session.get(
|
|
f"{self.base_url}/api/research/{research_id}/status"
|
|
)
|
|
return response.json()
|
|
|
|
def get_research_result(self, research_id: str) -> Dict[str, Any]:
|
|
"""Get the results of a completed research task."""
|
|
response = self.session.get(f"{self.base_url}/api/report/{research_id}")
|
|
return response.json()
|
|
|
|
def wait_for_research(
|
|
self, research_id: str, timeout: int = 300
|
|
) -> Dict[str, Any]:
|
|
"""Wait for research to complete and return results."""
|
|
start_time = time.time()
|
|
|
|
while time.time() - start_time < timeout:
|
|
status = self.get_research_status(research_id)
|
|
|
|
if status.get("status") == "completed":
|
|
return self.get_research_result(research_id)
|
|
if status.get("status") == "failed":
|
|
raise Exception(
|
|
f"Research failed: {status.get('error', 'Unknown error')}"
|
|
)
|
|
|
|
print(
|
|
f" Status: {status.get('status', 'unknown')} - {status.get('progress', 'N/A')}"
|
|
)
|
|
time.sleep(3)
|
|
|
|
raise TimeoutError(
|
|
f"Research {research_id} timed out after {timeout} seconds"
|
|
)
|
|
|
|
def get_settings(self) -> Dict[str, Any]:
|
|
"""Get all user settings."""
|
|
response = self.session.get(f"{self.base_url}/settings/api")
|
|
return response.json()
|
|
|
|
def get_setting(self, key: str) -> Any:
|
|
"""Get a specific setting value."""
|
|
response = self.session.get(f"{self.base_url}/settings/api/{key}")
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
return None
|
|
|
|
def update_setting(self, key: str, value: Any) -> bool:
|
|
"""Update a setting value."""
|
|
response = self.session.put(
|
|
f"{self.base_url}/settings/api/{key}",
|
|
json={"value": value},
|
|
headers=self._get_headers(),
|
|
)
|
|
return response.status_code in [200, 201]
|
|
|
|
def get_history(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
"""Get research history."""
|
|
response = self.session.get(
|
|
f"{self.base_url}/history/api", params={"limit": limit}
|
|
)
|
|
data = response.json()
|
|
return data.get("items", data.get("history", []))
|
|
|
|
def get_available_models(self) -> Dict[str, str]:
|
|
"""Get available LLM providers and models."""
|
|
response = self.session.get(
|
|
f"{self.base_url}/settings/api/available-models"
|
|
)
|
|
data = response.json()
|
|
return data.get("providers", data.get("models", {}))
|
|
|
|
def get_available_search_engines(self) -> List[str]:
|
|
"""Get available search engines."""
|
|
response = self.session.get(
|
|
f"{self.base_url}/settings/api/available-search-engines"
|
|
)
|
|
data = response.json()
|
|
return data.get("engines", data.get("engine_options", []))
|
|
|
|
|
|
def example_quick_research(client: LDRClient) -> None:
|
|
"""Example: Quick research with minimal parameters."""
|
|
print("\n=== Example 1: Quick Research ===")
|
|
|
|
research = client.start_research(
|
|
query="What are the key principles of machine learning?",
|
|
iterations=1,
|
|
questions_per_iteration=2,
|
|
)
|
|
|
|
print(f"Started research ID: {research['research_id']}")
|
|
|
|
# Wait for completion
|
|
result = client.wait_for_research(research["research_id"])
|
|
|
|
print(f"\nSummary: {result['summary'][:500]}...")
|
|
print(f"Sources: {len(result.get('sources', []))}")
|
|
print(f"Findings: {len(result.get('findings', []))}")
|
|
|
|
|
|
def example_detailed_research(client: LDRClient) -> None:
|
|
"""Example: Detailed research with multiple search engines."""
|
|
print("\n=== Example 2: Detailed Research ===")
|
|
|
|
# Check available search engines
|
|
engines = client.get_available_search_engines()
|
|
print(f"Available search engines: {engines}")
|
|
|
|
# Use multiple engines
|
|
selected_engines = (
|
|
["wikipedia", "arxiv"] if "arxiv" in engines else ["wikipedia"]
|
|
)
|
|
|
|
research = client.start_research(
|
|
query="Impact of climate change on global food security",
|
|
search_engines=selected_engines,
|
|
iterations=3,
|
|
questions_per_iteration=4,
|
|
temperature=0.7,
|
|
)
|
|
|
|
print(f"Started detailed research ID: {research['research_id']}")
|
|
|
|
# Monitor progress
|
|
result = client.wait_for_research(research["research_id"], timeout=600)
|
|
|
|
print(f"\nTitle: {result.get('query', 'N/A')}")
|
|
print(f"Summary length: {len(result['summary'])} characters")
|
|
print(f"Sources: {len(result.get('sources', []))}")
|
|
|
|
# Show some findings
|
|
findings = result.get("findings", [])
|
|
if findings:
|
|
print("\nTop findings:")
|
|
for i, finding in enumerate(findings[:3], 1):
|
|
print(f"{i}. {finding.get('text', 'N/A')[:100]}...")
|
|
|
|
|
|
def example_settings_management(client: LDRClient) -> None:
|
|
"""Example: Managing user settings."""
|
|
print("\n=== Example 3: Settings Management ===")
|
|
|
|
# Get current settings
|
|
settings = client.get_settings()
|
|
settings_data = settings.get("settings", {})
|
|
|
|
# Display current LLM configuration
|
|
llm_provider = settings_data.get("llm.provider", {}).get("value", "Not set")
|
|
llm_model = settings_data.get("llm.model", {}).get("value", "Not set")
|
|
|
|
print(f"Current LLM Provider: {llm_provider}")
|
|
print(f"Current LLM Model: {llm_model}")
|
|
|
|
# Get available models
|
|
models = client.get_available_models()
|
|
print(f"\nAvailable providers: {list(models.keys())}")
|
|
|
|
# Example: Update temperature setting
|
|
current_temp = settings_data.get("llm.temperature", {}).get("value", 0.7)
|
|
print(f"\nCurrent temperature: {current_temp}")
|
|
|
|
# Update temperature (example - uncomment to actually update)
|
|
# success = client.update_setting("llm.temperature", 0.5)
|
|
# print(f"Temperature update: {'Success' if success else 'Failed'}")
|
|
|
|
|
|
def example_batch_research(client: LDRClient) -> None:
|
|
"""Example: Running multiple research tasks in batch."""
|
|
print("\n=== Example 4: Batch Research ===")
|
|
|
|
queries = [
|
|
"What is quantum entanglement?",
|
|
"How does CRISPR gene editing work?",
|
|
"What are the applications of blockchain technology?",
|
|
]
|
|
|
|
research_ids = []
|
|
|
|
# Start all research tasks
|
|
for query in queries:
|
|
try:
|
|
research = client.start_research(
|
|
query=query, iterations=1, questions_per_iteration=2
|
|
)
|
|
research_ids.append(
|
|
{
|
|
"id": research["research_id"],
|
|
"query": query,
|
|
"status": "started",
|
|
}
|
|
)
|
|
print(f"Started: {query} (ID: {research['research_id']})")
|
|
except Exception as e:
|
|
print(f"Failed to start '{query}': {e}")
|
|
|
|
# Wait for all to complete
|
|
print("\nWaiting for batch completion...")
|
|
completed = 0
|
|
|
|
while completed < len(research_ids):
|
|
for research in research_ids:
|
|
if research["status"] != "completed":
|
|
try:
|
|
status = client.get_research_status(research["id"])
|
|
if status.get("status") == "completed":
|
|
research["status"] = "completed"
|
|
completed += 1
|
|
print(f"✓ Completed: {research['query']}")
|
|
except Exception:
|
|
pass
|
|
|
|
if completed < len(research_ids):
|
|
time.sleep(3)
|
|
|
|
# Get all results
|
|
print("\nBatch Results Summary:")
|
|
for research in research_ids:
|
|
try:
|
|
result = client.get_research_result(research["id"])
|
|
print(f"\n{research['query']}:")
|
|
print(f" - Summary: {result['summary'][:150]}...")
|
|
print(f" - Sources: {len(result.get('sources', []))}")
|
|
except Exception as e:
|
|
print(f" - Error getting results: {e}")
|
|
|
|
|
|
def example_research_history(client: LDRClient) -> None:
|
|
"""Example: Viewing research history."""
|
|
print("\n=== Example 5: Research History ===")
|
|
|
|
history = client.get_history(limit=5)
|
|
|
|
if not history:
|
|
print("No research history found.")
|
|
return
|
|
|
|
print(f"Found {len(history)} recent research items:\n")
|
|
|
|
for item in history:
|
|
created = item.get("created_at", "Unknown date")
|
|
query = item.get("query", "Unknown query")
|
|
status = item.get("status", "Unknown")
|
|
research_id = item.get("id", item.get("research_id", "N/A"))
|
|
|
|
print(f"ID: {research_id}")
|
|
print(f"Query: {query}")
|
|
print(f"Date: {created}")
|
|
print(f"Status: {status}")
|
|
print("-" * 40)
|
|
|
|
|
|
def main():
|
|
"""Run all examples."""
|
|
print("=== LDR HTTP API v1.0 Examples ===")
|
|
print(
|
|
"🎯 This example works completely out of the box - no manual setup required!"
|
|
)
|
|
|
|
# First, check if server is running
|
|
try:
|
|
response = requests.get(f"{BASE_URL}/auth/check", timeout=5)
|
|
if response.status_code in [
|
|
200,
|
|
401,
|
|
]: # 401 is expected when not authenticated
|
|
print("✅ Server is running")
|
|
else:
|
|
print(f"❌ Server returned status code: {response.status_code}")
|
|
print("Make sure the server is running:")
|
|
print(" python -m local_deep_research.web.app")
|
|
return
|
|
except requests.exceptions.ConnectionError:
|
|
print("❌ Cannot connect to LDR server!")
|
|
print("Make sure the server is running:")
|
|
print(" python -m local_deep_research.web.app")
|
|
return
|
|
|
|
# Create test user automatically
|
|
username, password = create_test_user()
|
|
if not username:
|
|
print("❌ Failed to create test user")
|
|
return
|
|
|
|
# Create client
|
|
client = LDRClient(BASE_URL)
|
|
|
|
try:
|
|
# Login with the created user
|
|
print(f"\nLogging in as: {username}")
|
|
if not client.login(username, password):
|
|
print("❌ Login failed!")
|
|
return
|
|
|
|
print("✅ Login successful")
|
|
|
|
# Check health
|
|
health = client.check_health()
|
|
print(f"Authenticated: {health.get('authenticated', False)}")
|
|
print(f"Username: {health.get('username', 'N/A')}")
|
|
|
|
# Run examples
|
|
example_quick_research(client)
|
|
example_detailed_research(client)
|
|
example_settings_management(client)
|
|
example_batch_research(client)
|
|
example_research_history(client)
|
|
|
|
print("\n✅ All examples completed successfully!")
|
|
print(f"🔑 Created user: {username}")
|
|
print("📝 You can now use this user for manual testing:")
|
|
print(f" Username: {username}")
|
|
print(f" Password: {password}")
|
|
print(f" Login URL: {BASE_URL}/auth/login")
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
print("\n❌ Cannot connect to LDR server!")
|
|
print("Make sure the server is running:")
|
|
print(" python -m local_deep_research.web.app")
|
|
except Exception as e:
|
|
print(f"\n❌ Error: {e}")
|
|
finally:
|
|
# Always logout
|
|
client.logout()
|
|
print("\n✅ Logged out")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|