File size: 3,957 Bytes
4c40bac
eff2c28
bf0b08e
eff2c28
 
 
6784fde
4c40bac
eff2c28
4c40bac
 
 
 
eff2c28
 
4c40bac
eff2c28
2a464ca
 
eff2c28
 
 
3b8a72f
 
eff2c28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6784fde
 
 
bf0b08e
 
 
6784fde
 
 
 
bf0b08e
6784fde
 
bf0b08e
6784fde
 
 
 
 
 
 
 
 
 
 
 
bf0b08e
 
6784fde
 
 
 
 
 
 
bf0b08e
 
6784fde
 
 
bf0b08e
3b8a72f
 
eff2c28
6784fde
 
3b8a72f
bf0b08e
e6c065b
bf0b08e
e6c065b
 
 
6784fde
bf0b08e
6784fde
 
eff2c28
6784fde
 
eff2c28
6784fde
 
 
 
e6c065b
 
eff2c28
 
 
4c40bac
 
eff2c28
 
 
 
6784fde
4c40bac
 
eff2c28
 
4c40bac
 
eff2c28
 
 
bf0b08e
eff2c28
bf0b08e
4c40bac
 
eff2c28
3b8a72f
4c40bac
 
eff2c28
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import gradio as gr
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import StreamingResponse
import random
import threading
import uvicorn
import aiohttp

from src.config import get_api_keys
from src.model_tester import ModelTester
from src.scheduler import Scheduler


model_tester = ModelTester()
scheduler = Scheduler(task_callback=lambda: model_tester.scan_all_models())

fastapi_app = FastAPI(title="OpenRouter Free API")


@fastapi_app.on_event("startup")
async def startup_event():
    threading.Thread(target=lambda: scheduler.start(), daemon=True).start()


@fastapi_app.get("/v1/models")
async def list_models():
    available = model_tester.get_available_models(free_only=False)
    available_free = model_tester.get_available_models(free_only=True)
    
    models = []
    for model_id in available:
        is_free = model_id in available_free
        models.append({
            "id": model_id,
            "object": "model",
            "created": 1677610602,
            "owned_by": "openrouter",
            "free": is_free
        })
    
    return {"object": "list", "data": models}


async def proxy_request(body: dict):
    """透传请求,只替换 model 和 key"""
    api_key = random.choice(get_api_keys())
    
    model_tester.refresh_model_list()
    available_free = model_tester.get_all_free_models()
    
    model_hint = body.get("model")
    target_model = None
    
    if model_hint and available_free:
        for m in available_free:
            model_name = m.replace(":free", "").split("/")[-1]
            if model_hint.lower() in model_name.lower():
                target_model = m
                break
    
    if not target_model and available_free:
        target_model = random.choice(available_free[:5])
    
    if not target_model:
        raise HTTPException(status_code=400, detail="No available model")
    
    body["model"] = target_model
    
    url = "https://openrouter.ai/api/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=body, headers=headers, timeout=aiohttp.ClientTimeout(total=120)) as response:
            if body.get("stream"):
                async for chunk in response.content:
                    yield chunk
            else:
                yield await response.json()


@fastapi_app.post("/v1/chat/completions")
async def chat_completions(request: Request):
    body = await request.json()
    
    if body.get("stream"):
        return StreamingResponse(
            proxy_request(body),
            media_type="text/event-stream"
        )
    
    result = None
    async for data in proxy_request(body):
        result = data
        break
    
    if not result:
        raise HTTPException(status_code=400, detail="Request failed")
    
    if "error" in result:
        raise HTTPException(status_code=400, detail=result["error"])
    
    return result


@fastapi_app.get("/health")
async def health():
    return {"status": "ok"}


def get_scan_status():
    scan_result = model_tester.scan_result
    total = scan_result.get("total_available", 0)
    free = scan_result.get("free_available", 0)
    return f"Free: {free} | Total: {total}"


def format_model_list(models):
    return "\n".join(models) if models else "No models available"


with gr.Blocks(title="OpenRouter Free API") as demo:
    gr.Markdown("# OpenRouter Free API")
    gr.Markdown("Standard OpenAI-compatible API with free model support")
    gr.Markdown(f"**Status: {get_scan_status()}**")
    gr.Markdown("## Available Free Models")
    gr.Textbox(value=format_model_list(model_tester.get_available_models(free_only=True)), lines=15, interactive=False)


app = gr.mount_gradio_app(fastapi_app, demo, path="/")


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=7860)