Spaces:
Running
Running
# app.py | |
# IMPORTANT: These two lines MUST be at the very top, before any other imports. | |
# They patch asyncio to work cooperatively with gevent workers. | |
import gevent.monkey | |
gevent.monkey.patch_all(asyncio=True) | |
import asyncio | |
from flask import Flask, request, jsonify | |
from proxy_lite import Runner, RunnerConfig | |
from proxy_lite.config import RecorderConfig # Needed for explicit instantiation | |
import os | |
import logging | |
# Configure logging for better visibility in Hugging Face Space logs | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
app = Flask(__name__) | |
# Global runner instance. It will be initialized once per worker process/greenlet | |
# upon the first request to ensure correct event loop binding. | |
_runner = None | |
async def initialize_runner(): | |
"""Initializes the Proxy-lite Runner asynchronously.""" | |
global _runner | |
if _runner is None: | |
logger.info("Initializing Proxy-lite Runner...") | |
# Retrieve Hugging Face API Token from environment variables (set as Space Secret) | |
hf_api_token = os.environ.get("HF_API_TOKEN") | |
if not hf_api_token: | |
logger.error("HF_API_TOKEN environment variable not set. Cannot initialize Runner.") | |
raise ValueError("HF_API_TOKEN environment variable not set. Please set it as a Space secret.") | |
# --- EXPLICITLY CREATE RECORDER_CONFIG --- | |
# This addresses the PermissionError by ensuring the root_path is explicitly set | |
# and passed as a Pydantic object, rather than relying on nested dict parsing. | |
recorder_config_instance = RecorderConfig(root_path="/tmp/proxy_lite_runs") | |
# --- END EXPLICIT RECORDER_CONFIG --- | |
# --- EXPLICITLY CREATE RUNNER_CONFIG --- | |
# Pass all configuration parameters as keyword arguments to the Pydantic model. | |
config = RunnerConfig( | |
environment={ | |
"name": "webbrowser", | |
"homepage": "https://www.google.com", | |
"headless": True, # Keep headless for server environment | |
}, | |
solver={ | |
"name": "simple", | |
"agent": { | |
"name": "proxy_lite", | |
"client": { | |
"name": "convergence", | |
"model_id": "convergence-ai/proxy-lite-3b", | |
"api_base": "https://api-inference.huggingface.co/models/convergence-ai/proxy-lite-3b", | |
"api_key": hf_api_token | |
} | |
} | |
}, | |
recorder=recorder_config_instance # Pass the explicitly created instance | |
) | |
# --- END EXPLICIT RUNNER_CONFIG --- | |
# Instantiate the Runner, passing the config as a keyword argument (Pydantic style) | |
_runner = Runner(config=config) | |
logger.info("Proxy-lite Runner initialized successfully.") | |
return _runner | |
def run_async_task(coro): | |
""" | |
Helper to run async coroutines in a synchronous context (like Flask routes). | |
Ensures an event loop exists for the current thread/greenlet and runs the coroutine. | |
This addresses the "no current event loop" errors. | |
""" | |
try: | |
# Try to get the running loop (for current thread/greenlet) | |
loop = asyncio.get_running_loop() | |
except RuntimeError: | |
# If no loop is running, create a new one for this thread/greenlet | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
# Run the coroutine until it completes | |
return loop.run_until_complete(coro) | |
def run_proxy_task_endpoint(): | |
"""API endpoint to receive a task and execute it with Proxy-lite.""" | |
data = request.json | |
task = data.get('task') | |
if not task: | |
logger.warning("Received request without 'task' field. Returning 400.") | |
return jsonify({"error": "No 'task' provided in request body"}), 400 | |
logger.info(f"Received task for proxy-lite: '{task}'") | |
try: | |
# Ensure runner is initialized (and event loop handled) before use | |
runner = run_async_task(initialize_runner()) | |
# Run the task using the proxy-lite runner | |
result = run_async_task(runner.run(task)) | |
logger.info(f"Proxy-lite task completed. Output: {result[:200]}...") # Log truncated output | |
# Return the result as a JSON response | |
return jsonify({"output": result}) | |
except Exception as e: | |
logger.exception(f"Error processing task '{task}':") # Logs full traceback for debugging | |
return jsonify({"error": f"An error occurred: {str(e)}. Check logs for details."}), 500 | |
def root(): | |
"""Basic root endpoint for health check and user info.""" | |
logger.info("Root endpoint accessed.") | |
return "Proxy-lite API is running. Send POST requests to /run_proxy_task with a 'task' in JSON body." | |
if __name__ == '__main__': | |
# During local development, ensure HF_API_TOKEN is set manually | |
if not os.environ.get("HF_API_TOKEN"): | |
logger.error("HF_API_TOKEN environment variable is not set. Please set it for local testing.") | |
exit(1) | |
logger.info("Starting Flask development server on 0.0.0.0:7860...") | |
# Hugging Face Spaces expects binding to 0.0.0.0 and listening on port 7860 | |
app.run(host='0.0.0.0', port=7860, debug=True) # debug=True for local testing, disable for production |