javaeeduke commited on
Commit
88d8700
ยท
verified ยท
1 Parent(s): 54787e8

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +231 -105
main.py CHANGED
@@ -1,9 +1,12 @@
1
- from fastapi import FastAPI, HTTPException, Depends, Header
2
  from fastapi.middleware.cors import CORSMiddleware
 
3
  import uvicorn
4
  import os
5
  import logging
6
  import httpx
 
 
7
 
8
  logging.basicConfig(level=logging.INFO)
9
  logger = logging.getLogger(__name__)
@@ -18,47 +21,56 @@ app.add_middleware(
18
  )
19
 
20
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
21
- # ่ฏปๅ–ๅŽŸ้กน็›ฎๅ›บๅฎšๅ‘ฝๅ็š„ Secrets
22
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23
 
24
  PROVIDER_MAP = {
25
- "GOOGLE_API_KEY": "google",
26
- "GROQ_API_KEY": "groq",
27
- "GITHUB_TOKEN": "github",
28
- "OPENROUTER_API_KEY": "openrouter",
29
- "MISTRAL_API_KEY": "mistral",
30
- "TOGETHER_API_KEY": "together",
31
- "NVIDIA_API_KEY": "nvidia",
32
- "COHERE_API_KEY": "cohere",
33
- "HF_TOKEN": "huggingface",
34
- "CEREBRAS_API_KEY": "cerebras",
35
- "SAMBANOVA_API_KEY": "sambanova",
36
- "CLOUDFLARE_API_TOKEN": "cloudflare",
37
- "ZHIPU_API_KEY": "zhipu",
38
  }
39
 
40
  PROVIDER_CONFIG = {
41
  "google": {
42
  "base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
43
  "models": [
44
- "gemini-2.0-flash","gemini-2.0-flash-lite",
45
- "gemini-1.5-pro","gemini-1.5-flash","gemini-1.5-flash-8b"
 
 
 
46
  ],
47
  },
48
  "groq": {
49
  "base_url": "https://api.groq.com/openai/v1",
50
  "models": [
51
- "llama-3.3-70b-versatile","llama-3.1-8b-instant",
52
- "llama3-70b-8192","llama3-8b-8192",
53
- "mixtral-8x7b-32768","gemma2-9b-it"
 
 
 
54
  ],
55
  },
56
  "github": {
57
  "base_url": "https://models.inference.ai.azure.com",
58
  "models": [
59
- "gpt-4o","gpt-4o-mini",
60
- "Phi-3.5-mini-instruct","Phi-3.5-MoE-instruct",
61
- "Meta-Llama-3.1-70B-Instruct","Meta-Llama-3.1-405B-Instruct"
 
 
 
62
  ],
63
  },
64
  "openrouter": {
@@ -68,13 +80,16 @@ PROVIDER_CONFIG = {
68
  "meta-llama/llama-3.2-3b-instruct:free",
69
  "google/gemma-3-1b-it:free",
70
  "deepseek/deepseek-r1:free",
 
71
  ],
72
  },
73
  "mistral": {
74
  "base_url": "https://api.mistral.ai/v1",
75
  "models": [
76
- "mistral-small-latest","mistral-large-latest",
77
- "open-mistral-7b","open-mixtral-8x7b"
 
 
78
  ],
79
  },
80
  "together": {
@@ -82,7 +97,8 @@ PROVIDER_CONFIG = {
82
  "models": [
83
  "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo",
84
  "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
85
- "mistralai/Mixtral-8x7B-Instruct-v0.1"
 
86
  ],
87
  },
88
  "nvidia": {
@@ -90,46 +106,61 @@ PROVIDER_CONFIG = {
90
  "models": [
91
  "meta/llama-3.1-70b-instruct",
92
  "meta/llama-3.1-8b-instruct",
93
- "mistralai/mixtral-8x7b-instruct"
94
  ],
95
  },
96
  "cohere": {
97
  "base_url": "https://api.cohere.com/v2",
98
- "models": ["command-r-plus","command-r","command"],
 
 
 
 
99
  },
100
  "huggingface": {
101
  "base_url": "https://api-inference.huggingface.co/v1",
102
  "models": [
103
  "meta-llama/Llama-3.2-3B-Instruct",
104
- "mistralai/Mistral-7B-Instruct-v0.3"
105
  ],
106
  },
107
  "cerebras": {
108
  "base_url": "https://api.cerebras.ai/v1",
109
- "models": ["llama3.1-8b","llama3.1-70b"],
 
 
 
110
  },
111
  "sambanova": {
112
  "base_url": "https://api.sambanova.ai/v1",
113
  "models": [
114
  "Meta-Llama-3.1-8B-Instruct",
115
  "Meta-Llama-3.1-70B-Instruct",
116
- "Meta-Llama-3.1-405B-Instruct"
117
  ],
118
  },
119
  "cloudflare": {
120
- "base_url": "https://api.cloudflare.com/client/v4/accounts/{}/ai/v1",
121
  "models": [
122
  "@cf/meta/llama-3.1-8b-instruct",
123
- "@cf/mistral/mistral-7b-instruct-v0.1"
124
  ],
125
  },
126
  "zhipu": {
127
  "base_url": "https://open.bigmodel.cn/api/paas/v4",
128
- "models": ["glm-4-flash","glm-4","glm-3-turbo"],
 
 
 
 
129
  },
130
  }
131
 
132
 
 
 
 
 
133
  def load_config():
134
  raw_keys = os.getenv("API_KEYS", "")
135
  api_keys = set(k.strip() for k in raw_keys.split(",") if k.strip())
@@ -140,20 +171,34 @@ def load_config():
140
  if not key_value:
141
  continue
142
  cfg = PROVIDER_CONFIG.get(provider_name, {})
 
 
 
 
 
 
 
143
  providers[provider_name] = {
144
  "api_key": key_value,
145
- "base_url": cfg.get("base_url", ""),
146
  "models": cfg.get("models", []),
147
  }
148
  logger.info(f"โœ… ๅŠ ่ฝฝ Provider: {provider_name}")
149
 
150
- logger.info(f"โœ… ๅŠ ่ฝฝไบ† {len(api_keys)} ไธช API Key")
151
- logger.info(f"โœ… ๅŠ ่ฝฝไบ† {len(providers)} ไธช Provider: {list(providers.keys())}")
152
  return api_keys, providers
153
 
154
 
155
  API_KEYS, PROVIDERS = load_config()
156
 
 
 
 
 
 
 
 
157
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
158
  # ้‰ดๆƒ
159
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -164,81 +209,167 @@ def verify_api_key(authorization: str = Header(...)):
164
  raise HTTPException(status_code=401, detail="Invalid API key")
165
  return token
166
 
 
167
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
168
- # โ˜… ็”จ APIRouter ็ปŸไธ€ๅŠ  /v1 ๅ‰็ผ€
169
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
170
 
171
  from fastapi import APIRouter
172
 
173
- router = APIRouter(prefix="/v1") # โ† ๅ…ณ้”ฎ๏ผๆ‰€ๆœ‰่ทฏ็”ฑ่‡ชๅŠจๅธฆ /v1
174
 
175
 
176
- @router.get("/models")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  async def list_models(_: str = Depends(verify_api_key)):
178
  data = []
179
- for p_cfg in PROVIDERS.values():
180
- for m in p_cfg["models"]:
181
- data.append({"id": m, "object": "model"})
 
 
 
 
 
182
  return {"object": "list", "data": data}
183
 
184
 
185
- @router.post("/chat/completions")
 
186
  async def chat_completions(
187
- body: dict,
188
- _: str = Depends(verify_api_key)
189
  ):
190
- model = body.get("model", "")
191
- provider = None
192
-
193
- for p_cfg in PROVIDERS.values():
194
- if model in p_cfg["models"]:
195
- provider = p_cfg
196
- break
197
 
198
- if not provider:
 
 
199
  raise HTTPException(
200
  status_code=404,
201
- detail=f"ๆฒกๆœ‰ Provider ๆ”ฏๆŒๆจกๅž‹: {model}"
202
  )
203
 
204
- async with httpx.AsyncClient(timeout=60) as client:
205
- resp = await client.post(
206
- f"{provider['base_url']}/chat/completions",
207
- headers={"Authorization": f"Bearer {provider['api_key']}"},
208
- json=body,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  )
210
- return resp.json()
211
 
 
212
 
213
- # โ˜… ๆŠŠ router ๆณจๅ†Œๅˆฐ app
214
- app.include_router(router)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
 
217
- # ๅฅๅบทๆฃ€ๆŸฅไธ้œ€่ฆ /v1 ๅ‰็ผ€๏ผŒไฟๆŒๆ น่ทฏๅพ„
218
  @app.get("/health")
219
  async def health():
220
  return {
221
- "status": "ok",
222
- "keys": len(API_KEYS),
223
- "providers": list(PROVIDERS.keys()),
 
224
  }
 
 
225
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
226
- # ่ฐƒ่ฏ•่ทฏ็”ฑ๏ผˆๆŽ’้”™็”จ๏ผ‰
227
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
228
 
229
  @app.get("/debug")
230
  async def debug():
231
- """ๆŸฅ็œ‹ๆ‰€ๆœ‰้…็ฝฎๅ’Œ่ทฏ็”ฑ๏ผŒๆŽ’้”™็”จ"""
232
-
233
- # 1. ๆฃ€ๆŸฅ็Žฏๅขƒๅ˜้‡
234
  env_check = {}
235
  for env_name in PROVIDER_MAP.keys():
236
  val = os.getenv(env_name, "")
237
  env_check[env_name] = "โœ… ๅทฒ่ฎพ็ฝฎ" if val else "โŒ ๆœช่ฎพ็ฝฎ"
238
-
239
- api_keys_raw = os.getenv("API_KEYS", "")
240
-
241
- # 2. ๆฃ€ๆŸฅๆ‰€ๆœ‰ๆณจๅ†Œ่ทฏ็”ฑ
242
  routes = []
243
  for route in app.routes:
244
  if hasattr(route, "methods"):
@@ -246,57 +377,52 @@ async def debug():
246
  "path": route.path,
247
  "methods": list(route.methods),
248
  })
249
-
250
- # 3. ๆฃ€ๆŸฅๅทฒๅŠ ่ฝฝ็š„ Provider
251
- providers_loaded = {}
252
  for name, cfg in PROVIDERS.items():
253
- providers_loaded[name] = {
254
  "base_url": cfg["base_url"],
255
  "models": cfg["models"],
256
- "api_key": cfg["api_key"][:6] + "******", # ๅชๆ˜พ็คบๅ‰6ไฝ
257
  }
258
-
259
  return {
260
- "โ‘  ็Žฏๅขƒๅ˜้‡ๆฃ€ๆŸฅ": env_check,
261
- "โ‘ก API_KEYS่ฎพ็ฝฎ": "โœ… ๅทฒ่ฎพ็ฝฎ" if api_keys_raw else "โŒ ๆœช่ฎพ็ฝฎ๏ผˆ่ฟ™ๆ˜ฏๅฏผ่‡ด401็š„ๅŽŸๅ› ๏ผ‰",
262
- "โ‘ข ๅทฒๅŠ ่ฝฝProvider": providers_loaded,
263
- "โ‘ฃ ๅทฒๆณจๅ†Œ่ทฏ็”ฑ": routes,
264
- "โ‘ค ๅ†…ๅญ˜ไธญKeyๆ•ฐ้‡": len(API_KEYS),
 
265
  }
266
 
267
 
268
- @app.get("/debug/test-provider/{provider_name}")
269
  async def test_provider(provider_name: str):
270
- """ๆต‹่ฏ•ๆŸไธช Provider ๆ˜ฏๅฆ่ƒฝๆญฃๅธธ่ฏทๆฑ‚"""
271
-
272
  if provider_name not in PROVIDERS:
273
  return {
274
- "status": "โŒ ๅคฑ่ดฅ",
275
- "reason": f"Provider '{provider_name}' ๆœชๅŠ ่ฝฝ",
276
- "ๅทฒๅŠ ่ฝฝ็š„": list(PROVIDERS.keys()),
277
  }
278
-
279
  cfg = PROVIDERS[provider_name]
280
-
281
  try:
282
  async with httpx.AsyncClient(timeout=10) as client:
283
  resp = await client.get(
284
  f"{cfg['base_url']}/models",
285
  headers={"Authorization": f"Bearer {cfg['api_key']}"},
286
  )
287
- return {
288
- "status": "โœ… ๆˆๅŠŸ" if resp.status_code == 200 else f"โš ๏ธ ็Šถๆ€็  {resp.status_code}",
289
- "provider": provider_name,
290
- "base_url": cfg["base_url"],
291
- "status_code": resp.status_code,
292
- "response": resp.json() if resp.status_code == 200 else resp.text[:200],
293
- }
294
- except Exception as e:
295
  return {
296
- "status": "โŒ ่ฏทๆฑ‚ๅคฑ่ดฅ",
297
- "provider": provider_name,
298
- "error": str(e),
299
  }
 
 
 
 
 
 
 
300
 
301
  if __name__ == "__main__":
302
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ from fastapi import FastAPI, HTTPException, Header, Depends, Request
2
  from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import StreamingResponse, JSONResponse
4
  import uvicorn
5
  import os
6
  import logging
7
  import httpx
8
+ import json
9
+ import asyncio
10
 
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
 
21
  )
22
 
23
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
24
+ # Provider ้…็ฝฎ
25
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
26
 
27
  PROVIDER_MAP = {
28
+ "GOOGLE_API_KEY": "google",
29
+ "GROQ_API_KEY": "groq",
30
+ "GITHUB_TOKEN": "github",
31
+ "OPENROUTER_API_KEY": "openrouter",
32
+ "MISTRAL_API_KEY": "mistral",
33
+ "TOGETHER_API_KEY": "together",
34
+ "NVIDIA_API_KEY": "nvidia",
35
+ "COHERE_API_KEY": "cohere",
36
+ "HF_TOKEN": "huggingface",
37
+ "CEREBRAS_API_KEY": "cerebras",
38
+ "SAMBANOVA_API_KEY": "sambanova",
39
+ "CLOUDFLARE_API_TOKEN": "cloudflare",
40
+ "ZHIPU_API_KEY": "zhipu",
41
  }
42
 
43
  PROVIDER_CONFIG = {
44
  "google": {
45
  "base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
46
  "models": [
47
+ "gemini-2.0-flash",
48
+ "gemini-2.0-flash-lite",
49
+ "gemini-1.5-pro",
50
+ "gemini-1.5-flash",
51
+ "gemini-1.5-flash-8b",
52
  ],
53
  },
54
  "groq": {
55
  "base_url": "https://api.groq.com/openai/v1",
56
  "models": [
57
+ "llama-3.3-70b-versatile",
58
+ "llama-3.1-8b-instant",
59
+ "llama3-70b-8192",
60
+ "llama3-8b-8192",
61
+ "mixtral-8x7b-32768",
62
+ "gemma2-9b-it",
63
  ],
64
  },
65
  "github": {
66
  "base_url": "https://models.inference.ai.azure.com",
67
  "models": [
68
+ "gpt-4o",
69
+ "gpt-4o-mini",
70
+ "Phi-3.5-mini-instruct",
71
+ "Phi-3.5-MoE-instruct",
72
+ "Meta-Llama-3.1-70B-Instruct",
73
+ "Meta-Llama-3.1-405B-Instruct",
74
  ],
75
  },
76
  "openrouter": {
 
80
  "meta-llama/llama-3.2-3b-instruct:free",
81
  "google/gemma-3-1b-it:free",
82
  "deepseek/deepseek-r1:free",
83
+ "deepseek/deepseek-chat:free",
84
  ],
85
  },
86
  "mistral": {
87
  "base_url": "https://api.mistral.ai/v1",
88
  "models": [
89
+ "mistral-small-latest",
90
+ "mistral-large-latest",
91
+ "open-mistral-7b",
92
+ "open-mixtral-8x7b",
93
  ],
94
  },
95
  "together": {
 
97
  "models": [
98
  "meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo",
99
  "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
100
+ "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
101
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
102
  ],
103
  },
104
  "nvidia": {
 
106
  "models": [
107
  "meta/llama-3.1-70b-instruct",
108
  "meta/llama-3.1-8b-instruct",
109
+ "mistralai/mixtral-8x7b-instruct",
110
  ],
111
  },
112
  "cohere": {
113
  "base_url": "https://api.cohere.com/v2",
114
+ "models": [
115
+ "command-r-plus",
116
+ "command-r",
117
+ "command",
118
+ ],
119
  },
120
  "huggingface": {
121
  "base_url": "https://api-inference.huggingface.co/v1",
122
  "models": [
123
  "meta-llama/Llama-3.2-3B-Instruct",
124
+ "mistralai/Mistral-7B-Instruct-v0.3",
125
  ],
126
  },
127
  "cerebras": {
128
  "base_url": "https://api.cerebras.ai/v1",
129
+ "models": [
130
+ "llama3.1-8b",
131
+ "llama3.1-70b",
132
+ ],
133
  },
134
  "sambanova": {
135
  "base_url": "https://api.sambanova.ai/v1",
136
  "models": [
137
  "Meta-Llama-3.1-8B-Instruct",
138
  "Meta-Llama-3.1-70B-Instruct",
139
+ "Meta-Llama-3.1-405B-Instruct",
140
  ],
141
  },
142
  "cloudflare": {
143
+ "base_url": "https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/ai/v1",
144
  "models": [
145
  "@cf/meta/llama-3.1-8b-instruct",
146
+ "@cf/mistral/mistral-7b-instruct-v0.1",
147
  ],
148
  },
149
  "zhipu": {
150
  "base_url": "https://open.bigmodel.cn/api/paas/v4",
151
+ "models": [
152
+ "glm-4-flash",
153
+ "glm-4",
154
+ "glm-3-turbo",
155
+ ],
156
  },
157
  }
158
 
159
 
160
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
161
+ # ๅฏๅŠจๅŠ ่ฝฝ
162
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
163
+
164
  def load_config():
165
  raw_keys = os.getenv("API_KEYS", "")
166
  api_keys = set(k.strip() for k in raw_keys.split(",") if k.strip())
 
171
  if not key_value:
172
  continue
173
  cfg = PROVIDER_CONFIG.get(provider_name, {})
174
+ base_url = cfg.get("base_url", "")
175
+
176
+ # Cloudflare ้œ€่ฆ Account ID
177
+ if provider_name == "cloudflare":
178
+ cf_account = os.getenv("CLOUDFLARE_ACCOUNT_ID", "")
179
+ base_url = base_url.replace("CF_ACCOUNT_ID", cf_account)
180
+
181
  providers[provider_name] = {
182
  "api_key": key_value,
183
+ "base_url": base_url.rstrip("/"),
184
  "models": cfg.get("models", []),
185
  }
186
  logger.info(f"โœ… ๅŠ ่ฝฝ Provider: {provider_name}")
187
 
188
+ logger.info(f"โœ… API Keys ๆ•ฐ้‡: {len(api_keys)}")
189
+ logger.info(f"โœ… Provider ๆ•ฐ้‡: {len(providers)} {list(providers.keys())}")
190
  return api_keys, providers
191
 
192
 
193
  API_KEYS, PROVIDERS = load_config()
194
 
195
+ # model โ†’ provider ๅฟซ้€ŸๆŸฅๆ‰พ่กจ
196
+ MODEL_PROVIDER: dict = {}
197
+ for _pname, _pcfg in PROVIDERS.items():
198
+ for _m in _pcfg["models"]:
199
+ MODEL_PROVIDER[_m] = _pname
200
+
201
+
202
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
203
  # ้‰ดๆƒ
204
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
209
  raise HTTPException(status_code=401, detail="Invalid API key")
210
  return token
211
 
212
+
213
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
214
+ # /v1 ่ทฏ็”ฑ
215
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
216
 
217
  from fastapi import APIRouter
218
 
219
+ v1 = APIRouter(prefix="/v1")
220
 
221
 
222
+ # โ”€โ”€ GET /v1 โ”€โ”€ ่ฎฟ้—ฎ /v1 ไธๆŠฅ 404๏ผŒ่ฟ”ๅ›ž่ฏดๆ˜Ž
223
+ @v1.get("")
224
+ @v1.get("/")
225
+ async def v1_root():
226
+ return {
227
+ "message": "FreeLLMAPI is running",
228
+ "endpoints": {
229
+ "models": "/v1/models",
230
+ "chat_completions": "/v1/chat/completions",
231
+ },
232
+ "providers": list(PROVIDERS.keys()),
233
+ "total_models": len(MODEL_PROVIDER),
234
+ }
235
+
236
+
237
+ # โ”€โ”€ GET /v1/models โ”€โ”€
238
+ @v1.get("/models")
239
  async def list_models(_: str = Depends(verify_api_key)):
240
  data = []
241
+ for provider_name, pcfg in PROVIDERS.items():
242
+ for m in pcfg["models"]:
243
+ data.append({
244
+ "id": m,
245
+ "object": "model",
246
+ "owned_by": provider_name,
247
+ "created": 0,
248
+ })
249
  return {"object": "list", "data": data}
250
 
251
 
252
+ # โ”€โ”€ POST /v1/chat/completions โ”€โ”€
253
+ @v1.post("/chat/completions")
254
  async def chat_completions(
255
+ request: Request,
256
+ _: str = Depends(verify_api_key),
257
  ):
258
+ body = await request.json()
259
+ model = body.get("model", "")
260
+ stream = body.get("stream", False)
 
 
 
 
261
 
262
+ # ๆŸฅๆ‰พ provider
263
+ provider_name = MODEL_PROVIDER.get(model)
264
+ if not provider_name:
265
  raise HTTPException(
266
  status_code=404,
267
+ detail=f"ๆจกๅž‹ '{model}' ไธๅญ˜ๅœจ๏ผŒๅฏ็”จๆจกๅž‹: {list(MODEL_PROVIDER.keys())}",
268
  )
269
 
270
+ provider = PROVIDERS[provider_name]
271
+ url = f"{provider['base_url']}/chat/completions"
272
+ headers = {
273
+ "Authorization": f"Bearer {provider['api_key']}",
274
+ "Content-Type": "application/json",
275
+ }
276
+
277
+ logger.info(f"่ฝฌๅ‘่ฏทๆฑ‚ โ†’ {provider_name} | ๆจกๅž‹: {model} | ๆตๅผ: {stream}")
278
+
279
+ # โ”€โ”€ ๆตๅผๅ“ๅบ” โ”€โ”€
280
+ if stream:
281
+ async def event_stream():
282
+ async with httpx.AsyncClient(timeout=120) as client:
283
+ async with client.stream(
284
+ "POST", url, headers=headers, json=body
285
+ ) as resp:
286
+ async for chunk in resp.aiter_text():
287
+ yield chunk
288
+
289
+ return StreamingResponse(
290
+ event_stream(),
291
+ media_type="text/event-stream",
292
+ )
293
+
294
+ # โ”€โ”€ ๆ™ฎ้€šๅ“ๅบ” โ”€โ”€
295
+ async with httpx.AsyncClient(timeout=120) as client:
296
+ resp = await client.post(url, headers=headers, json=body)
297
+
298
+ if resp.status_code != 200:
299
+ logger.error(f"ไธŠๆธธ้”™่ฏฏ {resp.status_code}: {resp.text[:300]}")
300
+ raise HTTPException(
301
+ status_code=resp.status_code,
302
+ detail=resp.text,
303
  )
 
304
 
305
+ return resp.json()
306
 
307
+
308
+ # โ”€โ”€ POST /v1/embeddings๏ผˆ้ƒจๅˆ† Provider ๆ”ฏๆŒ๏ผ‰โ”€โ”€
309
+ @v1.post("/embeddings")
310
+ async def embeddings(
311
+ request: Request,
312
+ _: str = Depends(verify_api_key),
313
+ ):
314
+ body = await request.json()
315
+ model = body.get("model", "")
316
+
317
+ provider_name = MODEL_PROVIDER.get(model)
318
+ if not provider_name:
319
+ raise HTTPException(status_code=404, detail=f"ๆจกๅž‹ '{model}' ไธๅญ˜ๅœจ")
320
+
321
+ provider = PROVIDERS[provider_name]
322
+ url = f"{provider['base_url']}/embeddings"
323
+ headers = {"Authorization": f"Bearer {provider['api_key']}"}
324
+
325
+ async with httpx.AsyncClient(timeout=60) as client:
326
+ resp = await client.post(url, headers=headers, json=body)
327
+
328
+ return resp.json()
329
+
330
+
331
+ # ๆณจๅ†Œ่ทฏ็”ฑ
332
+ app.include_router(v1)
333
+
334
+
335
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
336
+ # ๆ น่ทฏ็”ฑ
337
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
338
+
339
+ @app.get("/")
340
+ async def root():
341
+ return {
342
+ "message": "FreeLLMAPI",
343
+ "version": "1.0.0",
344
+ "docs": "/docs",
345
+ "health": "/health",
346
+ "api_base": "/v1",
347
+ "providers": list(PROVIDERS.keys()),
348
+ "total_models": len(MODEL_PROVIDER),
349
+ }
350
 
351
 
 
352
  @app.get("/health")
353
  async def health():
354
  return {
355
+ "status": "ok",
356
+ "api_keys": len(API_KEYS),
357
+ "providers": list(PROVIDERS.keys()),
358
+ "total_models": len(MODEL_PROVIDER),
359
  }
360
+
361
+
362
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
363
+ # ่ฐƒ่ฏ•่ทฏ็”ฑ
364
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
365
 
366
  @app.get("/debug")
367
  async def debug():
 
 
 
368
  env_check = {}
369
  for env_name in PROVIDER_MAP.keys():
370
  val = os.getenv(env_name, "")
371
  env_check[env_name] = "โœ… ๅทฒ่ฎพ็ฝฎ" if val else "โŒ ๆœช่ฎพ็ฝฎ"
372
+
 
 
 
373
  routes = []
374
  for route in app.routes:
375
  if hasattr(route, "methods"):
 
377
  "path": route.path,
378
  "methods": list(route.methods),
379
  })
380
+
381
+ providers_info = {}
 
382
  for name, cfg in PROVIDERS.items():
383
+ providers_info[name] = {
384
  "base_url": cfg["base_url"],
385
  "models": cfg["models"],
386
+ "api_key": cfg["api_key"][:6] + "******",
387
  }
388
+
389
  return {
390
+ "โ‘  ็Žฏๅขƒๅ˜้‡": env_check,
391
+ "โ‘ก API_KEYS": "โœ… ๅทฒ่ฎพ็ฝฎ" if os.getenv("API_KEYS") else "โŒ ๆœช่ฎพ็ฝฎ",
392
+ "โ‘ข Providers": providers_info,
393
+ "โ‘ฃ ๅ…จ้ƒจๆจกๅž‹": list(MODEL_PROVIDER.keys()),
394
+ "โ‘ค ๆณจๅ†Œ่ทฏ็”ฑ": routes,
395
+ "โ‘ฅ Keyๆ•ฐ้‡": len(API_KEYS),
396
  }
397
 
398
 
399
+ @app.get("/debug/test/{provider_name}")
400
  async def test_provider(provider_name: str):
 
 
401
  if provider_name not in PROVIDERS:
402
  return {
403
+ "status": "โŒ ๆœชๅŠ ่ฝฝ",
404
+ "ๅทฒๅŠ ่ฝฝ": list(PROVIDERS.keys()),
 
405
  }
406
+
407
  cfg = PROVIDERS[provider_name]
 
408
  try:
409
  async with httpx.AsyncClient(timeout=10) as client:
410
  resp = await client.get(
411
  f"{cfg['base_url']}/models",
412
  headers={"Authorization": f"Bearer {cfg['api_key']}"},
413
  )
 
 
 
 
 
 
 
 
414
  return {
415
+ "status": "โœ… ่ฟž้€š" if resp.status_code == 200 else f"โš ๏ธ {resp.status_code}",
416
+ "provider": provider_name,
417
+ "status_code": resp.status_code,
418
  }
419
+ except Exception as e:
420
+ return {"status": "โŒ ๅคฑ่ดฅ", "error": str(e)}
421
+
422
+
423
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
424
+ # ๅฏๅŠจ
425
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
426
 
427
  if __name__ == "__main__":
428
  uvicorn.run(app, host="0.0.0.0", port=7860)