mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04: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:
parent
7c274de778
commit
ea1f727a8b
@ -105,18 +105,21 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
|
|||||||
:octicons-tag-24: v1.7.0
|
:octicons-tag-24: v1.7.0
|
||||||
|
|
||||||
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md).
|
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md).
|
||||||
|
For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values as JSON encoded strings (e.g. `OPENAI_CUSTOM_PARAMS='{"k1": "v1", "k2": "v2"}'`)
|
||||||
|
|
||||||
| Variables | Default | Description |
|
| Variables | Default | Description |
|
||||||
| ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
|
| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
|
||||||
| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
|
| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
|
||||||
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
|
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
|
||||||
|
| OPENAI_CUSTOM_HEADERS | None | Custom HTTP headers to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
|
||||||
|
| OPENAI_CUSTOM_PARAMS | None | Custom HTTP query params to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
|
||||||
| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
|
| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
|
||||||
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
|
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
|
||||||
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
|
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
|
||||||
| OPENAI_REQUEST_TIMEOUT | 60 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
|
| OPENAI_REQUEST_TIMEOUT | 60 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
|
||||||
|
|
||||||
### Themeing
|
### Theming
|
||||||
|
|
||||||
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -84,13 +84,12 @@
|
|||||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to">
|
<v-list-item v-for="child in nav.children" :key="child.key || child.title" exact :to="child.to" class="ml-2">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ child.icon }}</v-icon>
|
<v-icon>{{ child.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
<v-list-item-title>{{ child.title }}</v-list-item-title>
|
<v-list-item-title>{{ child.title }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-divider class="mb-4"></v-divider>
|
|
||||||
</v-list-group>
|
</v-list-group>
|
||||||
|
|
||||||
<!-- Single Item -->
|
<!-- Single Item -->
|
||||||
|
@ -1246,7 +1246,11 @@
|
|||||||
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
|
"here-are-a-few-things-to-help-you-get-started": "Here are a few things to help you get started with Mealie",
|
||||||
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
|
"restore-from-v1-backup": "Have a backup from a previous instance of Mealie v1? You can restore it here.",
|
||||||
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."
|
"manage-profile-or-get-invite-link": "Manage your own profile, or grab an invite link to share with others."
|
||||||
}
|
},
|
||||||
|
"debug-openai-services": "Debug OpenAI Services",
|
||||||
|
"debug-openai-services-description": "Use this page to debug OpenAI services. You can test your OpenAI connection and see the results here. If you have image services enabled, you can also provide an image.",
|
||||||
|
"run-test": "Run Test",
|
||||||
|
"test-results": "Test Results"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"welcome-user": "👋 Welcome, {0}!",
|
"welcome-user": "👋 Welcome, {0}!",
|
||||||
|
@ -92,10 +92,23 @@ export default defineComponent({
|
|||||||
restricted: true,
|
restricted: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: $globals.icons.slotMachine,
|
icon: $globals.icons.robot,
|
||||||
to: "/admin/parser",
|
title: i18n.tc("recipe.debug"),
|
||||||
title: i18n.tc("sidebar.parser"),
|
|
||||||
restricted: true,
|
restricted: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: $globals.icons.robot,
|
||||||
|
to: "/admin/debug/openai",
|
||||||
|
title: i18n.tc("admin.openai"),
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: $globals.icons.slotMachine,
|
||||||
|
to: "/admin/debug/parser",
|
||||||
|
title: i18n.tc("sidebar.parser"),
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
21
frontend/lib/api/admin/admin-debug.ts
Normal file
21
frontend/lib/api/admin/admin-debug.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { BaseAPI } from "../base/base-clients";
|
||||||
|
import { DebugResponse } from "~/lib/api/types/admin";
|
||||||
|
|
||||||
|
const prefix = "/api";
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
openai: `${prefix}/admin/debug/openai`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AdminDebugAPI extends BaseAPI {
|
||||||
|
async debugOpenAI(fileObject: Blob | File | undefined = undefined, fileName = "") {
|
||||||
|
let formData: FormData | null = null;
|
||||||
|
if (fileObject) {
|
||||||
|
formData = new FormData();
|
||||||
|
formData.append("image", fileObject);
|
||||||
|
formData.append("extension", fileName.split(".").pop() ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.requests.post<DebugResponse>(routes.openai, formData);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import { AdminGroupsApi } from "./admin/admin-groups";
|
|||||||
import { AdminBackupsApi } from "./admin/admin-backups";
|
import { AdminBackupsApi } from "./admin/admin-backups";
|
||||||
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
|
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
|
||||||
import { AdminAnalyticsApi } from "./admin/admin-analytics";
|
import { AdminAnalyticsApi } from "./admin/admin-analytics";
|
||||||
|
import { AdminDebugAPI } from "./admin/admin-debug";
|
||||||
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
||||||
|
|
||||||
export class AdminAPI {
|
export class AdminAPI {
|
||||||
@ -15,6 +16,7 @@ export class AdminAPI {
|
|||||||
public backups: AdminBackupsApi;
|
public backups: AdminBackupsApi;
|
||||||
public maintenance: AdminMaintenanceApi;
|
public maintenance: AdminMaintenanceApi;
|
||||||
public analytics: AdminAnalyticsApi;
|
public analytics: AdminAnalyticsApi;
|
||||||
|
public debug: AdminDebugAPI;
|
||||||
|
|
||||||
constructor(requests: ApiRequestInstance) {
|
constructor(requests: ApiRequestInstance) {
|
||||||
this.about = new AdminAboutAPI(requests);
|
this.about = new AdminAboutAPI(requests);
|
||||||
@ -24,6 +26,7 @@ export class AdminAPI {
|
|||||||
this.backups = new AdminBackupsApi(requests);
|
this.backups = new AdminBackupsApi(requests);
|
||||||
this.maintenance = new AdminMaintenanceApi(requests);
|
this.maintenance = new AdminMaintenanceApi(requests);
|
||||||
this.analytics = new AdminAnalyticsApi(requests);
|
this.analytics = new AdminAnalyticsApi(requests);
|
||||||
|
this.debug = new AdminDebugAPI(requests);
|
||||||
|
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,10 @@ export interface CustomPageOut {
|
|||||||
categories?: RecipeCategoryResponse[];
|
categories?: RecipeCategoryResponse[];
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
export interface DebugResponse {
|
||||||
|
success: boolean;
|
||||||
|
response?: string | null;
|
||||||
|
}
|
||||||
export interface EmailReady {
|
export interface EmailReady {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
}
|
}
|
||||||
|
127
frontend/pages/admin/debug/openai.vue
Normal file
127
frontend/pages/admin/debug/openai.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="pa-0">
|
||||||
|
<v-container>
|
||||||
|
<BaseCardSectionTitle :title="$tc('admin.debug-openai-services')">
|
||||||
|
{{ $t('admin.debug-openai-services-description') }}
|
||||||
|
<br />
|
||||||
|
<DocLink class="mt-2" link="/documentation/getting-started/installation/open-ai" />
|
||||||
|
</BaseCardSectionTitle>
|
||||||
|
</v-container>
|
||||||
|
<v-form ref="uploadForm" @submit.prevent="testOpenAI">
|
||||||
|
<div>
|
||||||
|
<v-card-text>
|
||||||
|
<v-container class="pa-0">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto" align-self="center">
|
||||||
|
<AppButtonUpload
|
||||||
|
v-if="!uploadedImage"
|
||||||
|
class="ml-auto"
|
||||||
|
url="none"
|
||||||
|
file-name="image"
|
||||||
|
accept="image/*"
|
||||||
|
:text="$i18n.tc('recipe.upload-image')"
|
||||||
|
:text-btn="false"
|
||||||
|
:post="false"
|
||||||
|
@uploaded="uploadImage"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-if="!!uploadedImage"
|
||||||
|
color="error"
|
||||||
|
@click="clearImage"
|
||||||
|
>
|
||||||
|
<v-icon left>{{ $globals.icons.close }}</v-icon>
|
||||||
|
{{ $i18n.tc("recipe.remove-image") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer />
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="uploadedImage && uploadedImagePreviewUrl" style="max-width: 25%;">
|
||||||
|
<v-spacer />
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-img :src="uploadedImagePreviewUrl" />
|
||||||
|
</v-col>
|
||||||
|
<v-spacer />
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<BaseButton
|
||||||
|
type="submit"
|
||||||
|
:text="$i18n.tc('admin.run-test')"
|
||||||
|
:icon="$globals.icons.check"
|
||||||
|
:loading="loading"
|
||||||
|
class="ml-auto"
|
||||||
|
/>
|
||||||
|
</v-card-actions>
|
||||||
|
</div>
|
||||||
|
</v-form>
|
||||||
|
<v-divider v-if="response" class="mt-4" />
|
||||||
|
<v-container v-if="response" class="ma-0 pa-0">
|
||||||
|
<v-card-title> {{ $t('admin.test-results') }} </v-card-title>
|
||||||
|
<v-card-text> {{ response }} </v-card-text>
|
||||||
|
</v-container>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useAdminApi } from "~/composables/api";
|
||||||
|
import { alert } from "~/composables/use-toast";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
layout: "admin",
|
||||||
|
setup() {
|
||||||
|
const api = useAdminApi();
|
||||||
|
const loading = ref(false);
|
||||||
|
const response = ref("");
|
||||||
|
|
||||||
|
const uploadForm = ref<VForm | null>(null);
|
||||||
|
const uploadedImage = ref<Blob | File>();
|
||||||
|
const uploadedImageName = ref<string>("");
|
||||||
|
const uploadedImagePreviewUrl = ref<string>();
|
||||||
|
|
||||||
|
function uploadImage(fileObject: File) {
|
||||||
|
uploadedImage.value = fileObject;
|
||||||
|
uploadedImageName.value = fileObject.name;
|
||||||
|
uploadedImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearImage() {
|
||||||
|
uploadedImage.value = undefined;
|
||||||
|
uploadedImageName.value = "";
|
||||||
|
uploadedImagePreviewUrl.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testOpenAI() {
|
||||||
|
response.value = "";
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.debug.debugOpenAI(uploadedImage.value);
|
||||||
|
loading.value = false;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
alert.error("Unable to test OpenAI services");
|
||||||
|
} else {
|
||||||
|
response.value = data.response || (data.success ? "Test Successful" : "Test Failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
response,
|
||||||
|
uploadForm,
|
||||||
|
uploadedImage,
|
||||||
|
uploadedImagePreviewUrl,
|
||||||
|
uploadImage,
|
||||||
|
clearImage,
|
||||||
|
testOpenAI,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
head() {
|
||||||
|
return {
|
||||||
|
title: this.$t("admin.debug-openai-services"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -3,7 +3,7 @@ import os
|
|||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NamedTuple
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
from dateutil.tz import tzlocal
|
from dateutil.tz import tzlocal
|
||||||
from pydantic import field_validator
|
from pydantic import field_validator
|
||||||
@ -305,6 +305,10 @@ class AppSettings(AppLoggingSettings):
|
|||||||
"""Your OpenAI API key. Required to enable OpenAI features"""
|
"""Your OpenAI API key. Required to enable OpenAI features"""
|
||||||
OPENAI_MODEL: str = "gpt-4o"
|
OPENAI_MODEL: str = "gpt-4o"
|
||||||
"""Which OpenAI model to send requests to. Leave this unset for most usecases"""
|
"""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
|
OPENAI_ENABLE_IMAGE_SERVICES: bool = True
|
||||||
"""Whether to enable image-related features in OpenAI"""
|
"""Whether to enable image-related features in OpenAI"""
|
||||||
OPENAI_WORKERS: int = 2
|
OPENAI_WORKERS: int = 2
|
||||||
|
@ -3,6 +3,7 @@ from mealie.routes._base.routers import AdminAPIRouter
|
|||||||
from . import (
|
from . import (
|
||||||
admin_about,
|
admin_about,
|
||||||
admin_backups,
|
admin_backups,
|
||||||
|
admin_debug,
|
||||||
admin_email,
|
admin_email,
|
||||||
admin_maintenance,
|
admin_maintenance,
|
||||||
admin_management_groups,
|
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_email.router, tags=["Admin: Email"])
|
||||||
router.include_router(admin_backups.router, tags=["Admin: Backups"])
|
router.include_router(admin_backups.router, tags=["Admin: Backups"])
|
||||||
router.include_router(admin_maintenance.router, tags=["Admin: Maintenance"])
|
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
|
# This file is auto-generated by gen_schema_exports.py
|
||||||
from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, OIDCInfo
|
from .about import AdminAboutInfo, AppInfo, AppStartupInfo, AppStatistics, AppTheme, CheckAppConfig, OIDCInfo
|
||||||
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
|
from .backup import AllBackups, BackupFile, BackupOptions, CreateBackup, ImportJob
|
||||||
|
from .debug import DebugResponse
|
||||||
from .email import EmailReady, EmailSuccess, EmailTest
|
from .email import EmailReady, EmailSuccess, EmailTest
|
||||||
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
|
from .maintenance import MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary
|
||||||
from .migration import ChowdownURL, MigrationFile, MigrationImport, Migrations
|
from .migration import ChowdownURL, MigrationFile, MigrationImport, Migrations
|
||||||
@ -49,4 +50,5 @@ __all__ = [
|
|||||||
"EmailReady",
|
"EmailReady",
|
||||||
"EmailSuccess",
|
"EmailSuccess",
|
||||||
"EmailTest",
|
"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,
|
base_url=settings.OPENAI_BASE_URL,
|
||||||
api_key=settings.OPENAI_API_KEY,
|
api_key=settings.OPENAI_API_KEY,
|
||||||
timeout=settings.OPENAI_REQUEST_TIMEOUT,
|
timeout=settings.OPENAI_REQUEST_TIMEOUT,
|
||||||
|
default_headers=settings.OPENAI_CUSTOM_HEADERS,
|
||||||
|
default_query=settings.OPENAI_CUSTOM_PARAMS,
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -176,6 +178,5 @@ class OpenAIService(BaseService):
|
|||||||
if not response.choices:
|
if not response.choices:
|
||||||
return None
|
return None
|
||||||
return response.choices[0].message.content
|
return response.choices[0].message.content
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self.logger.exception("OpenAI Request Failed")
|
raise Exception(f"OpenAI Request Failed. {e.__class__.__name__}: {e}") from e
|
||||||
return None
|
|
||||||
|
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))
|
tasks.append(service.get_response(prompt, message, force_json_response=True))
|
||||||
|
|
||||||
# re-combine chunks into one response
|
# re-combine chunks into one response
|
||||||
responses_json = await asyncio.gather(*tasks)
|
try:
|
||||||
responses = [
|
responses_json = await asyncio.gather(*tasks)
|
||||||
OpenAIIngredients.parse_openai_response(response_json) for response_json in responses_json if responses_json
|
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:
|
if not responses:
|
||||||
raise Exception("No response from OpenAI")
|
raise Exception("No response from OpenAI")
|
||||||
|
|
||||||
|
@ -487,7 +487,13 @@ class OpenAIRecipeService(RecipeServiceBase):
|
|||||||
if translate_language:
|
if translate_language:
|
||||||
message += f" Please translate the recipe to {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:
|
try:
|
||||||
openai_recipe = OpenAIRecipe.parse_openai_response(response)
|
openai_recipe = OpenAIRecipe.parse_openai_response(response)
|
||||||
recipe = self._convert_recipe(openai_recipe)
|
recipe = self._convert_recipe(openai_recipe)
|
||||||
|
@ -11,6 +11,8 @@ admin_backups = "/api/admin/backups"
|
|||||||
"""`/api/admin/backups`"""
|
"""`/api/admin/backups`"""
|
||||||
admin_backups_upload = "/api/admin/backups/upload"
|
admin_backups_upload = "/api/admin/backups/upload"
|
||||||
"""`/api/admin/backups/upload`"""
|
"""`/api/admin/backups/upload`"""
|
||||||
|
admin_debug_openai = "/api/admin/debug/openai"
|
||||||
|
"""`/api/admin/debug/openai`"""
|
||||||
admin_email = "/api/admin/email"
|
admin_email = "/api/admin/email"
|
||||||
"""`/api/admin/email`"""
|
"""`/api/admin/email`"""
|
||||||
admin_groups = "/api/admin/groups"
|
admin_groups = "/api/admin/groups"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user