feat: save ai usage

This commit is contained in:
2026-05-24 11:12:04 +02:00
parent 24ec4165a6
commit 8715267361
11 changed files with 629 additions and 40 deletions
@@ -3,38 +3,66 @@
namespace App\Http\Controllers;
use App\Ai\Agents\MealImageAnalyzer;
use App\Enums\AiUsageType;
use App\Enums\IngredientUnit;
use App\Enums\MealPostType;
use App\Http\Requests\MealImageAnalysisRequest;
use App\Services\AiUsageRecorder;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use Laravel\Ai\Responses\Data\Usage;
use Throwable;
class MealImageAnalysisController extends Controller
{
public function store(MealImageAnalysisRequest $request): JsonResponse
public function store(MealImageAnalysisRequest $request, AiUsageRecorder $aiUsageRecorder): JsonResponse
{
$image = $request->file('image');
$input = $this->analysisInput($image);
try {
$response = MealImageAnalyzer::make()->prompt(
$this->prompt(),
[$request->file('image')],
[$image],
timeout: 90,
);
} catch (Throwable $exception) {
report($exception);
$aiUsageRecorder->failed(
user: $request->user(),
type: AiUsageType::MEAL_IMAGE_ANALYSIS,
exception: $exception,
input: $input,
);
return response()->json([
'message' => __('api.meal_image_analysis.failed'),
], 502);
}
$draft = $this->normalizeDraft(
$response instanceof Arrayable
? $response->toArray()
: (json_decode($response->text, true) ?: [])
);
$aiUsageRecorder->success(
user: $request->user(),
type: AiUsageType::MEAL_IMAGE_ANALYSIS,
input: $input,
output: $draft,
provider: $response->meta->provider ?? null,
model: $response->meta->model ?? null,
promptTokens: $response->usage->promptTokens ?? null,
completionTokens: $response->usage->completionTokens ?? null,
totalTokens: $this->totalTokens($response->usage ?? null),
);
return response()->json([
'data' => $this->normalizeDraft(
$response instanceof Arrayable
? $response->toArray()
: (json_decode($response->text, true) ?: [])
),
'data' => $draft,
]);
}
@@ -51,6 +79,26 @@ Contraintes:
PROMPT;
}
private function analysisInput(UploadedFile $image): array
{
return [
'image' => [
'client_original_name' => $image->getClientOriginalName(),
'mime_type' => $image->getMimeType(),
'size' => $image->getSize(),
],
];
}
private function totalTokens(?Usage $usage): ?int
{
if (! $usage) {
return null;
}
return $usage->promptTokens + $usage->completionTokens;
}
private function normalizeDraft(array $draft): array
{
$type = MealPostType::tryFrom($this->cleanString($draft['type'] ?? '', 32))