main.py 15.3 KB
Newer Older
1
from fastapi import FastAPI, Request, HTTPException, Depends
Timothy J. Baek's avatar
Timothy J. Baek committed
2
from fastapi.middleware.cors import CORSMiddleware
3
from fastapi.responses import StreamingResponse, FileResponse
Timothy J. Baek's avatar
Timothy J. Baek committed
4
5

import requests
Timothy J. Baek's avatar
Timothy J. Baek committed
6
7
import aiohttp
import asyncio
Timothy J. Baek's avatar
Timothy J. Baek committed
8
import json
9
import logging
Timothy J. Baek's avatar
Timothy J. Baek committed
10

Timothy J. Baek's avatar
Timothy J. Baek committed
11
from pydantic import BaseModel
12
from starlette.background import BackgroundTask
Timothy J. Baek's avatar
Timothy J. Baek committed
13

14
from apps.webui.models.models import Models
Timothy J. Baek's avatar
Timothy J. Baek committed
15
from constants import ERROR_MESSAGES
Timothy J. Baek's avatar
Timothy J. Baek committed
16
17
18
19
from utils.utils import (
    get_verified_user,
    get_admin_user,
)
Michael Poluektov's avatar
Michael Poluektov committed
20
21
22
23
from utils.misc import (
    apply_model_params_to_body_openai,
    apply_model_system_prompt_to_body,
)
24

25
from config import (
26
    SRC_LOG_LEVELS,
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
27
    ENABLE_OPENAI_API,
28
    AIOHTTP_CLIENT_TIMEOUT,
29
30
31
    OPENAI_API_BASE_URLS,
    OPENAI_API_KEYS,
    CACHE_DIR,
Timothy J. Baek's avatar
Timothy J. Baek committed
32
    ENABLE_MODEL_FILTER,
33
    MODEL_FILTER_LIST,
34
    AppConfig,
35
)
36
from typing import List, Optional, Literal, overload
Timothy J. Baek's avatar
Timothy J. Baek committed
37

Timothy J. Baek's avatar
Timothy J. Baek committed
38
39
40

import hashlib
from pathlib import Path
Timothy J. Baek's avatar
Timothy J. Baek committed
41

42
43
44
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["OPENAI"])

Timothy J. Baek's avatar
Timothy J. Baek committed
45
46
47
48
49
50
51
52
53
app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Timothy J. Baek's avatar
Timothy J. Baek committed
54

55
56
app.state.config = AppConfig()

Timothy J. Baek's avatar
Timothy J. Baek committed
57
58
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
59
60

app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
61
62
app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
Timothy J. Baek's avatar
Timothy J. Baek committed
63
64
65

app.state.MODELS = {}

Timothy J. Baek's avatar
Timothy J. Baek committed
66

Timothy J. Baek's avatar
Timothy J. Baek committed
67
68
69
70
@app.middleware("http")
async def check_url(request: Request, call_next):
    if len(app.state.MODELS) == 0:
        await get_all_models()
Timothy J. Baek's avatar
Timothy J. Baek committed
71

Timothy J. Baek's avatar
Timothy J. Baek committed
72
73
    response = await call_next(request)
    return response
Timothy J. Baek's avatar
Timothy J. Baek committed
74
75


Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@app.get("/config")
async def get_config(user=Depends(get_admin_user)):
    return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}


class OpenAIConfigForm(BaseModel):
    enable_openai_api: Optional[bool] = None


@app.post("/config/update")
async def update_config(form_data: OpenAIConfigForm, user=Depends(get_admin_user)):
    app.state.config.ENABLE_OPENAI_API = form_data.enable_openai_api
    return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}


Timothy J. Baek's avatar
Timothy J. Baek committed
91
92
class UrlsUpdateForm(BaseModel):
    urls: List[str]
Timothy J. Baek's avatar
Timothy J. Baek committed
93
94


Timothy J. Baek's avatar
Timothy J. Baek committed
95
96
class KeysUpdateForm(BaseModel):
    keys: List[str]
Timothy J. Baek's avatar
Timothy J. Baek committed
97
98


Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
@app.get("/urls")
async def get_openai_urls(user=Depends(get_admin_user)):
101
    return {"OPENAI_API_BASE_URLS": app.state.config.OPENAI_API_BASE_URLS}
102

Timothy J. Baek's avatar
Timothy J. Baek committed
103

Timothy J. Baek's avatar
Timothy J. Baek committed
104
105
@app.post("/urls/update")
async def update_openai_urls(form_data: UrlsUpdateForm, user=Depends(get_admin_user)):
106
    await get_all_models()
107
108
    app.state.config.OPENAI_API_BASE_URLS = form_data.urls
    return {"OPENAI_API_BASE_URLS": app.state.config.OPENAI_API_BASE_URLS}
Timothy J. Baek's avatar
Timothy J. Baek committed
109
110


Timothy J. Baek's avatar
Timothy J. Baek committed
111
112
@app.get("/keys")
async def get_openai_keys(user=Depends(get_admin_user)):
113
    return {"OPENAI_API_KEYS": app.state.config.OPENAI_API_KEYS}
Timothy J. Baek's avatar
Timothy J. Baek committed
114
115
116
117


@app.post("/keys/update")
async def update_openai_key(form_data: KeysUpdateForm, user=Depends(get_admin_user)):
118
119
    app.state.config.OPENAI_API_KEYS = form_data.keys
    return {"OPENAI_API_KEYS": app.state.config.OPENAI_API_KEYS}
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121


Timothy J. Baek's avatar
Timothy J. Baek committed
122
@app.post("/audio/speech")
123
async def speech(request: Request, user=Depends(get_verified_user)):
Timothy J. Baek's avatar
Timothy J. Baek committed
124
125
    idx = None
    try:
126
        idx = app.state.config.OPENAI_API_BASE_URLS.index("https://api.openai.com/v1")
Timothy J. Baek's avatar
Timothy J. Baek committed
127
128
129
130
131
132
133
134
135
136
137
138
139
        body = await request.body()
        name = hashlib.sha256(body).hexdigest()

        SPEECH_CACHE_DIR = Path(CACHE_DIR).joinpath("./audio/speech/")
        SPEECH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
        file_path = SPEECH_CACHE_DIR.joinpath(f"{name}.mp3")
        file_body_path = SPEECH_CACHE_DIR.joinpath(f"{name}.json")

        # Check if the file already exists in the cache
        if file_path.is_file():
            return FileResponse(file_path)

        headers = {}
140
        headers["Authorization"] = f"Bearer {app.state.config.OPENAI_API_KEYS[idx]}"
Timothy J. Baek's avatar
Timothy J. Baek committed
141
        headers["Content-Type"] = "application/json"
142
143
144
        if "openrouter.ai" in app.state.config.OPENAI_API_BASE_URLS[idx]:
            headers["HTTP-Referer"] = "https://openwebui.com/"
            headers["X-Title"] = "Open WebUI"
Timothy J. Baek's avatar
Timothy J. Baek committed
145
        r = None
Timothy J. Baek's avatar
Timothy J. Baek committed
146
147
        try:
            r = requests.post(
148
                url=f"{app.state.config.OPENAI_API_BASE_URLS[idx]}/audio/speech",
Timothy J. Baek's avatar
Timothy J. Baek committed
149
150
151
152
                data=body,
                headers=headers,
                stream=True,
            )
Timothy J. Baek's avatar
Timothy J. Baek committed
153

Timothy J. Baek's avatar
Timothy J. Baek committed
154
            r.raise_for_status()
Timothy J. Baek's avatar
Timothy J. Baek committed
155

Timothy J. Baek's avatar
Timothy J. Baek committed
156
157
158
159
            # Save the streaming content to a file
            with open(file_path, "wb") as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
Timothy J. Baek's avatar
Timothy J. Baek committed
160

Timothy J. Baek's avatar
Timothy J. Baek committed
161
162
            with open(file_body_path, "w") as f:
                json.dump(json.loads(body.decode("utf-8")), f)
Timothy J. Baek's avatar
Timothy J. Baek committed
163

Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
            # Return the saved file
            return FileResponse(file_path)
Timothy J. Baek's avatar
Timothy J. Baek committed
166

Timothy J. Baek's avatar
Timothy J. Baek committed
167
        except Exception as e:
168
            log.exception(e)
Timothy J. Baek's avatar
Timothy J. Baek committed
169
170
171
172
173
174
            error_detail = "Open WebUI: Server Connection Error"
            if r is not None:
                try:
                    res = r.json()
                    if "error" in res:
                        error_detail = f"External: {res['error']}"
175
                except Exception:
Timothy J. Baek's avatar
Timothy J. Baek committed
176
177
                    error_detail = f"External: {e}"

Timothy J. Baek's avatar
Timothy J. Baek committed
178
179
180
            raise HTTPException(
                status_code=r.status_code if r else 500, detail=error_detail
            )
Timothy J. Baek's avatar
Timothy J. Baek committed
181
182
183

    except ValueError:
        raise HTTPException(status_code=401, detail=ERROR_MESSAGES.OPENAI_NOT_FOUND)
Timothy J. Baek's avatar
Timothy J. Baek committed
184
185


Timothy J. Baek's avatar
Timothy J. Baek committed
186
async def fetch_url(url, key):
Timothy J. Baek's avatar
Timothy J. Baek committed
187
    timeout = aiohttp.ClientTimeout(total=5)
Timothy J. Baek's avatar
Timothy J. Baek committed
188
    try:
189
        headers = {"Authorization": f"Bearer {key}"}
190
        async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
191
192
            async with session.get(url, headers=headers) as response:
                return await response.json()
Timothy J. Baek's avatar
Timothy J. Baek committed
193
194
    except Exception as e:
        # Handle connection error here
195
        log.error(f"Connection error: {e}")
Timothy J. Baek's avatar
Timothy J. Baek committed
196
197
198
        return None


199
200
201
202
203
204
205
206
207
208
async def cleanup_response(
    response: Optional[aiohttp.ClientResponse],
    session: Optional[aiohttp.ClientSession],
):
    if response:
        response.close()
    if session:
        await session.close()


Timothy J. Baek's avatar
Timothy J. Baek committed
209
def merge_models_lists(model_lists):
210
    log.debug(f"merge_models_lists {model_lists}")
Timothy J. Baek's avatar
Timothy J. Baek committed
211
212
213
    merged_list = []

    for idx, models in enumerate(model_lists):
Timothy J. Baek's avatar
Timothy J. Baek committed
214
215
216
        if models is not None and "error" not in models:
            merged_list.extend(
                [
217
218
                    {
                        **model,
219
                        "name": model.get("name", model["id"]),
220
221
222
223
                        "owned_by": "openai",
                        "openai": model,
                        "urlIdx": idx,
                    }
Timothy J. Baek's avatar
Timothy J. Baek committed
224
                    for model in models
225
                    if "api.openai.com"
226
                    not in app.state.config.OPENAI_API_BASE_URLS[idx]
Timothy J. Baek's avatar
Timothy J. Baek committed
227
228
229
                    or "gpt" in model["id"]
                ]
            )
Timothy J. Baek's avatar
Timothy J. Baek committed
230

Timothy J. Baek's avatar
Timothy J. Baek committed
231
    return merged_list
Timothy J. Baek's avatar
Timothy J. Baek committed
232
233


234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def is_openai_api_disabled():
    api_keys = app.state.config.OPENAI_API_KEYS
    no_keys = len(api_keys) == 1 and api_keys[0] == ""
    return no_keys or not app.state.config.ENABLE_OPENAI_API


async def get_all_models_raw() -> list:
    if is_openai_api_disabled():
        return []

    # Check if API KEYS length is same than API URLS length
    num_urls = len(app.state.config.OPENAI_API_BASE_URLS)
    num_keys = len(app.state.config.OPENAI_API_KEYS)

    if num_keys != num_urls:
        # if there are more keys than urls, remove the extra keys
        if num_keys > num_urls:
            new_keys = app.state.config.OPENAI_API_KEYS[:num_urls]
            app.state.config.OPENAI_API_KEYS = new_keys
        # if there are more urls than keys, add empty keys
        else:
            app.state.config.OPENAI_API_KEYS += [""] * (num_urls - num_keys)

    tasks = [
        fetch_url(f"{url}/models", app.state.config.OPENAI_API_KEYS[idx])
        for idx, url in enumerate(app.state.config.OPENAI_API_BASE_URLS)
    ]

    responses = await asyncio.gather(*tasks)
    log.debug(f"get_all_models:responses() {responses}")

    return responses


268
269
270
271
272
273
274
275
276
@overload
async def get_all_models(raw: Literal[True]) -> list: ...


@overload
async def get_all_models(raw: Literal[False] = False) -> dict[str, list]: ...


async def get_all_models(raw=False) -> dict[str, list] | list:
277
    log.info("get_all_models()")
278
    if is_openai_api_disabled():
279
        return [] if raw else {"data": []}
280

281
    responses = await get_all_models_raw()
282
283
    if raw:
        return responses
284

285
286
287
288
289
290
    def extract_data(response):
        if response and "data" in response:
            return response["data"]
        if isinstance(response, list):
            return response
        return None
Timothy J. Baek's avatar
Timothy J. Baek committed
291

292
293
294
295
    models = {"data": merge_models_lists(map(extract_data, responses))}

    log.debug(f"models: {models}")
    app.state.MODELS = {model["id"]: model for model in models["data"]}
Timothy J. Baek's avatar
Timothy J. Baek committed
296

297
298
299
    return models


Timothy J. Baek's avatar
Timothy J. Baek committed
300
301
@app.get("/models")
@app.get("/models/{url_idx}")
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
302
async def get_models(url_idx: Optional[int] = None, user=Depends(get_verified_user)):
303
    if url_idx is None:
Timothy J. Baek's avatar
Timothy J. Baek committed
304
        models = await get_all_models()
Timothy J. Baek's avatar
Timothy J. Baek committed
305
        if app.state.config.ENABLE_MODEL_FILTER:
Timothy J. Baek's avatar
Timothy J. Baek committed
306
            if user.role == "user":
307
308
                models["data"] = list(
                    filter(
Timothy J. Baek's avatar
Timothy J. Baek committed
309
                        lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
310
311
                        models["data"],
                    )
Timothy J. Baek's avatar
Timothy J. Baek committed
312
313
314
                )
                return models
        return models
Timothy J. Baek's avatar
Timothy J. Baek committed
315
    else:
316
        url = app.state.config.OPENAI_API_BASE_URLS[url_idx]
317
318
319
320
321
        key = app.state.config.OPENAI_API_KEYS[url_idx]

        headers = {}
        headers["Authorization"] = f"Bearer {key}"
        headers["Content-Type"] = "application/json"
Timothy J. Baek's avatar
Timothy J. Baek committed
322
323
324

        r = None

Timothy J. Baek's avatar
Timothy J. Baek committed
325
        try:
326
            r = requests.request(method="GET", url=f"{url}/models", headers=headers)
Timothy J. Baek's avatar
Timothy J. Baek committed
327
328
329
330
331
332
333
334
335
336
            r.raise_for_status()

            response_data = r.json()
            if "api.openai.com" in url:
                response_data["data"] = list(
                    filter(lambda model: "gpt" in model["id"], response_data["data"])
                )

            return response_data
        except Exception as e:
337
            log.exception(e)
Timothy J. Baek's avatar
Timothy J. Baek committed
338
339
340
341
342
343
            error_detail = "Open WebUI: Server Connection Error"
            if r is not None:
                try:
                    res = r.json()
                    if "error" in res:
                        error_detail = f"External: {res['error']}"
344
                except Exception:
Timothy J. Baek's avatar
Timothy J. Baek committed
345
346
347
348
349
350
                    error_detail = f"External: {e}"

            raise HTTPException(
                status_code=r.status_code if r else 500,
                detail=error_detail,
            )
Timothy J. Baek's avatar
Timothy J. Baek committed
351
352


Timothy J. Baek's avatar
Timothy J. Baek committed
353
354
355
356
357
358
359
@app.post("/chat/completions")
@app.post("/chat/completions/{url_idx}")
async def generate_chat_completion(
    form_data: dict,
    url_idx: Optional[int] = None,
    user=Depends(get_verified_user),
):
Timothy J. Baek's avatar
Timothy J. Baek committed
360
    idx = 0
Timothy J. Baek's avatar
Timothy J. Baek committed
361
    payload = {**form_data}
362
    
Timothy J. Baek's avatar
Timothy J. Baek committed
363
    if "metadata" in payload:
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
364
        del payload["metadata"]
365
        
Timothy J. Baek's avatar
Timothy J. Baek committed
366
367
    model_id = form_data.get("model")
    model_info = Models.get_model_by_id(model_id)
Timothy J. Baek's avatar
Timothy J. Baek committed
368

Timothy J. Baek's avatar
Timothy J. Baek committed
369
370
371
    if model_info:
        if model_info.base_model_id:
            payload["model"] = model_info.base_model_id
Timothy J. Baek's avatar
Timothy J. Baek committed
372

373
        params = model_info.params.model_dump()
Michael Poluektov's avatar
Michael Poluektov committed
374
        payload = apply_model_params_to_body_openai(params, payload)
375
        payload = apply_model_system_prompt_to_body(params, payload, user)
Timothy J. Baek's avatar
Timothy J. Baek committed
376

Timothy J. Baek's avatar
Timothy J. Baek committed
377
378
    model = app.state.MODELS[payload.get("model")]
    idx = model["urlIdx"]
Timothy J. Baek's avatar
Timothy J. Baek committed
379

Timothy J. Baek's avatar
Timothy J. Baek committed
380
    if "pipeline" in model and model.get("pipeline"):
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
381
382
383
384
385
386
        payload["user"] = {
            "name": user.name,
            "id": user.id,
            "email": user.email,
            "role": user.role,
        }
Timothy J. Baek's avatar
Timothy J. Baek committed
387

Timothy J. Baek's avatar
Timothy J. Baek committed
388
389
    # Convert the modified body back to JSON
    payload = json.dumps(payload)
Timothy J. Baek's avatar
Timothy J. Baek committed
390

391
    log.debug(payload)
Timothy J. Baek's avatar
Timothy J. Baek committed
392

Timothy J. Baek's avatar
Timothy J. Baek committed
393
394
    url = app.state.config.OPENAI_API_BASE_URLS[idx]
    key = app.state.config.OPENAI_API_KEYS[idx]
Timothy J. Baek's avatar
Timothy J. Baek committed
395

Timothy J. Baek's avatar
Timothy J. Baek committed
396
397
398
    headers = {}
    headers["Authorization"] = f"Bearer {key}"
    headers["Content-Type"] = "application/json"
399
400
401
    if "openrouter.ai" in app.state.config.OPENAI_API_BASE_URLS[idx]:
        headers["HTTP-Referer"] = "https://openwebui.com/"
        headers["X-Title"] = "Open WebUI"
Timothy J. Baek's avatar
Timothy J. Baek committed
402

Timothy J. Baek's avatar
Timothy J. Baek committed
403
404
405
406
407
    r = None
    session = None
    streaming = False

    try:
408
409
410
        session = aiohttp.ClientSession(
            trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
        )
Timothy J. Baek's avatar
Timothy J. Baek committed
411
412
413
414
415
416
        r = await session.request(
            method="POST",
            url=f"{url}/chat/completions",
            data=payload,
            headers=headers,
        )
Timothy J. Baek's avatar
Timothy J. Baek committed
417

Timothy J. Baek's avatar
Timothy J. Baek committed
418
        r.raise_for_status()
Timothy J. Baek's avatar
Timothy J. Baek committed
419

Timothy J. Baek's avatar
Timothy J. Baek committed
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
        # Check if response is SSE
        if "text/event-stream" in r.headers.get("Content-Type", ""):
            streaming = True
            return StreamingResponse(
                r.content,
                status_code=r.status,
                headers=dict(r.headers),
                background=BackgroundTask(
                    cleanup_response, response=r, session=session
                ),
            )
        else:
            response_data = await r.json()
            return response_data
    except Exception as e:
        log.exception(e)
        error_detail = "Open WebUI: Server Connection Error"
        if r is not None:
            try:
                res = await r.json()
                print(res)
                if "error" in res:
                    error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
443
            except Exception:
Timothy J. Baek's avatar
Timothy J. Baek committed
444
445
446
447
448
449
450
451
452
453
454
455
456
457
                error_detail = f"External: {e}"
        raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
    finally:
        if not streaming and session:
            if r:
                r.close()
            await session.close()


@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
    idx = 0

    body = await request.body()
Timothy J. Baek's avatar
Timothy J. Baek committed
458

459
460
    url = app.state.config.OPENAI_API_BASE_URLS[idx]
    key = app.state.config.OPENAI_API_KEYS[idx]
Timothy J. Baek's avatar
Timothy J. Baek committed
461
462
463

    target_url = f"{url}/{path}"

464
    headers = {}
Timothy J. Baek's avatar
Timothy J. Baek committed
465
    headers["Authorization"] = f"Bearer {key}"
Timothy J. Baek's avatar
Timothy J. Baek committed
466
    headers["Content-Type"] = "application/json"
Timothy J. Baek's avatar
Timothy J. Baek committed
467

Timothy J. Baek's avatar
Timothy J. Baek committed
468
    r = None
469
470
    session = None
    streaming = False
Timothy J. Baek's avatar
Timothy J. Baek committed
471

Timothy J. Baek's avatar
Timothy J. Baek committed
472
    try:
473
        session = aiohttp.ClientSession(trust_env=True)
474
        r = await session.request(
Jun Siang Cheah's avatar
Jun Siang Cheah committed
475
476
            method=request.method,
            url=target_url,
Timothy J. Baek's avatar
Timothy J. Baek committed
477
            data=body,
Jun Siang Cheah's avatar
Jun Siang Cheah committed
478
            headers=headers,
Timothy J. Baek's avatar
Timothy J. Baek committed
479
480
481
482
        )

        r.raise_for_status()

483
484
        # Check if response is SSE
        if "text/event-stream" in r.headers.get("Content-Type", ""):
485
            streaming = True
486
            return StreamingResponse(
487
488
                r.content,
                status_code=r.status,
489
                headers=dict(r.headers),
490
491
492
                background=BackgroundTask(
                    cleanup_response, response=r, session=session
                ),
493
494
            )
        else:
495
            response_data = await r.json()
496
            return response_data
Timothy J. Baek's avatar
Timothy J. Baek committed
497
    except Exception as e:
498
        log.exception(e)
Timothy J. Baek's avatar
Timothy J. Baek committed
499
        error_detail = "Open WebUI: Server Connection Error"
Timothy J. Baek's avatar
Timothy J. Baek committed
500
501
        if r is not None:
            try:
502
                res = await r.json()
Timothy J. Baek's avatar
Timothy J. Baek committed
503
                print(res)
Timothy J. Baek's avatar
Timothy J. Baek committed
504
                if "error" in res:
Timothy J. Baek's avatar
Timothy J. Baek committed
505
                    error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}"
506
            except Exception:
Timothy J. Baek's avatar
Timothy J. Baek committed
507
                error_detail = f"External: {e}"
508
509
510
511
512
513
        raise HTTPException(status_code=r.status if r else 500, detail=error_detail)
    finally:
        if not streaming and session:
            if r:
                r.close()
            await session.close()