feat: OpenAI Custom Headers/Params and Debug Page (#4227)

Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson
2024-09-23 04:04:36 -05:00
committed by GitHub
parent 7c274de778
commit ea1f727a8b
20 changed files with 277 additions and 17 deletions

View File

@@ -3,7 +3,7 @@ import os
import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import NamedTuple
from typing import Any, NamedTuple
from dateutil.tz import tzlocal
from pydantic import field_validator
@@ -305,6 +305,10 @@ class AppSettings(AppLoggingSettings):
"""Your OpenAI API key. Required to enable OpenAI features"""
OPENAI_MODEL: str = "gpt-4o"
"""Which OpenAI model to send requests to. Leave this unset for most usecases"""
OPENAI_CUSTOM_HEADERS: dict[str, str] = {}
"""Custom HTTP headers to send with each OpenAI request"""
OPENAI_CUSTOM_PARAMS: dict[str, Any] = {}
"""Custom HTTP parameters to send with each OpenAI request"""
OPENAI_ENABLE_IMAGE_SERVICES: bool = True
"""Whether to enable image-related features in OpenAI"""
OPENAI_WORKERS: int = 2

View File

@@ -3,6 +3,7 @@ from mealie.routes._base.routers import AdminAPIRouter
from . import (
admin_about,
admin_backups,
admin_debug,
admin_email,
admin_maintenance,
admin_management_groups,
@@ -19,3 +20,4 @@ router.include_router(admin_management_groups.router, tags=["Admin: Manage Group
router.include_router(admin_email.router, tags=["Admin: Email"])
router.include_router(admin_backups.router, tags=["Admin: Backups"])
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
router.include_router(admin_debug.router, tags=["Admin: Debug"])

View File

@@ -0,0 +1,52 @@
import os
import shutil
from fastapi import APIRouter, File, UploadFile
from mealie.core.dependencies.dependencies import get_temporary_path
from mealie.routes._base import BaseAdminController, controller
from mealie.schema.admin.debug import DebugResponse
from mealie.services.openai import OpenAILocalImage, OpenAIService
router = APIRouter(prefix="/debug")
@controller(router)
class AdminDebugController(BaseAdminController):
@router.post("/openai", response_model=DebugResponse)
async def debug_openai(self, image: UploadFile | None = File(None)):
if not self.settings.OPENAI_ENABLED:
return DebugResponse(success=False, response="OpenAI is not enabled")
if image and not self.settings.OPENAI_ENABLE_IMAGE_SERVICES:
return DebugResponse(
success=False, response="Image was provided, but OpenAI image services are not enabled"
)
with get_temporary_path() as temp_path:
if image:
with temp_path.joinpath(image.filename).open("wb") as buffer:
shutil.copyfileobj(image.file, buffer)
local_image_path = temp_path.joinpath(image.filename)
local_images = [OpenAILocalImage(filename=os.path.basename(local_image_path), path=local_image_path)]
else:
local_images = None
try:
openai_service = OpenAIService()
prompt = openai_service.get_prompt("debug")
message = "Hello, checking to see if I can reach you."
if local_images:
message = f"{message} Here is an image to test with:"
response = await openai_service.get_response(
prompt, message, images=local_images, force_json_response=False
)
return DebugResponse(success=True, response=f'OpenAI is working. Response: "{response}"')
except Exception as e:
self.logger.exception(e)
return DebugResponse(
success=False,
response=f'OpenAI request failed. Full error has been logged. {e.__class__.__name__}: "{e}"',
)

View File

@@ -1,6 +1,7 @@
# This file is auto-generated by gen_schema_exports.py
from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, OIDCInfo
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
from .debug import DebugResponse
from .email import EmailReady, EmailSuccess, EmailTest
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
from .migration import ChowdownURL, MigrationFile, MigrationImport, Migrations
@@ -49,4 +50,5 @@ __all__ = [
"EmailReady",
"EmailSuccess",
"EmailTest",
"DebugResponse",
]

View File

@@ -0,0 +1,6 @@
from mealie.schema._mealie import MealieModel
class DebugResponse(MealieModel):
success: bool
response: str | None = None

View File

@@ -90,6 +90,8 @@ class OpenAIService(BaseService):
base_url=settings.OPENAI_BASE_URL,
api_key=settings.OPENAI_API_KEY,
timeout=settings.OPENAI_REQUEST_TIMEOUT,
default_headers=settings.OPENAI_CUSTOM_HEADERS,
default_query=settings.OPENAI_CUSTOM_PARAMS,
)
super().__init__()
@@ -176,6 +178,5 @@ class OpenAIService(BaseService):
if not response.choices:
return None
return response.choices[0].message.content
except Exception:
self.logger.exception("OpenAI Request Failed")
return None
except Exception as e:
raise Exception(f"OpenAI Request Failed. {e.__class__.__name__}: {e}") from e

View File

@@ -0,0 +1 @@
You are a simple chatbot being used for debugging purposes.

View File

@@ -80,10 +80,20 @@ class OpenAIParser(ABCIngredientParser):
tasks.append(service.get_response(prompt, message, force_json_response=True))
# re-combine chunks into one response
responses_json = await asyncio.gather(*tasks)
responses = [
OpenAIIngredients.parse_openai_response(response_json) for response_json in responses_json if responses_json
]
try:
responses_json = await asyncio.gather(*tasks)
except Exception as e:
raise Exception("Failed to call OpenAI services") from e
try:
responses = [
OpenAIIngredients.parse_openai_response(response_json)
for response_json in responses_json
if responses_json
]
except Exception as e:
raise Exception("Failed to parse OpenAI response") from e
if not responses:
raise Exception("No response from OpenAI")

View File

@@ -487,7 +487,13 @@ class OpenAIRecipeService(RecipeServiceBase):
if translate_language:
message += f" Please translate the recipe to {translate_language}."
response = await openai_service.get_response(prompt, message, images=openai_images, force_json_response=True)
try:
response = await openai_service.get_response(
prompt, message, images=openai_images, force_json_response=True
)
except Exception as e:
raise Exception("Failed to call OpenAI services") from e
try:
openai_recipe = OpenAIRecipe.parse_openai_response(response)
recipe = self._convert_recipe(openai_recipe)