File size: 5,447 Bytes
6170d37
 
 
ce3b075
 
6170d37
689e710
 
 
6170d37
689e710
 
 
6170d37
689e710
 
 
 
 
6170d37
 
689e710
 
 
6170d37
689e710
 
 
6170d37
689e710
 
 
 
 
6170d37
 
 
8b9bbfa
6170d37
8b9bbfa
6170d37
 
8b9bbfa
 
689e710
 
6170d37
689e710
8b9bbfa
689e710
 
 
 
 
 
 
0d53358
689e710
 
0b5b38e
8b9bbfa
 
6170d37
 
 
92ef27f
689e710
 
 
 
0d53358
 
6170d37
 
0d53358
 
 
 
 
6170d37
0d53358
 
6170d37
0d53358
689e710
 
 
 
6170d37
689e710
 
 
 
 
 
 
 
6170d37
689e710
6170d37
689e710
 
6170d37
 
689e710
 
6170d37
689e710
 
 
 
6170d37
689e710
 
 
 
6170d37
689e710
 
0d53358
689e710
6170d37
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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)

@app.route('/run_proxy_task', methods=['POST'])
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

@app.route('/')
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