mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-03 14:33:11 -05:00
feat: OpenAI Custom Headers/Params and Debug Page (#4227)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
52
mealie/routes/admin/admin_debug.py
Normal file
52
mealie/routes/admin/admin_debug.py
Normal 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}"',
|
||||
)
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
6
mealie/schema/admin/debug.py
Normal file
6
mealie/schema/admin/debug.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class DebugResponse(MealieModel):
|
||||
success: bool
|
||||
response: str | None = None
|
||||
@@ -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
|
||||
|
||||
1
mealie/services/openai/prompts/debug.txt
Normal file
1
mealie/services/openai/prompts/debug.txt
Normal file
@@ -0,0 +1 @@
|
||||
You are a simple chatbot being used for debugging purposes.
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user