Skip to content

Commit

Permalink
Update error handling and test cases in GenerateCalendarJob
Browse files Browse the repository at this point in the history
Enhanced error handling in GenerateCalendarJob and updated related test cases. During calendar generation, errors are now logged with more details including the error code and message. Improved execution flow by using fail() instead of release().
  • Loading branch information
hrsa committed May 15, 2024
1 parent e87ab2b commit 58c3b47
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 21 deletions.
22 changes: 18 additions & 4 deletions app/Jobs/GenerateCalendarJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,19 @@ public function handle(): void
$this->icsEvent->update(['error' => "I'm sorry, my servers are having hiccups. Please try again in 30-60 minutes!"]);
Log::alert("Mistral error generating IcsEvent #{$this->icsEvent->id}: {$e->getMessage()}");
IcsEventProcessed::dispatch($this->icsEvent);
$this->fail($e);
$this->fail("{$e->getCode()} : {$e->getMessage()}");

return;
}
}

$jsonIcs = $result?->choices[0]->message->content;

if (!$jsonIcs) {
$this->release(300);
$this->icsEvent->update(['error' => "I'm sorry, my servers are having hiccups. Please try again in 30-60 minutes!"]);
Log::alert("Total failure for IcsEvent #{$this->icsEvent->id}");
IcsEventProcessed::dispatch($this->icsEvent);
$this->fail("Total failure for IcsEvent #{$this->icsEvent->id}");
}

$decodedReply = json_decode($jsonIcs, true);
Expand Down Expand Up @@ -96,17 +101,26 @@ private function generateOpenAIResponse(string $systemPrompt): CreateResponse
]);
}

/**
* @throws Exception
*/
private function generateMistralResponse(string $systemPrompt): stdClass
{
$result = Http::mistral()->post('/chat/completions', [
$response = Http::mistral()->timeout(10)->post('/chat/completions', [
'model' => 'mistral-large-latest',
'messages' => [
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $this->icsEvent->prompt],
],
'max_tokens' => 2800,
'response_format' => ['type' => 'json_object'],
])->json();
]);

if ($response->failed()) {
throw new Exception('Mistral HTTP Error: ' . $response->body(), $response->getStatusCode());
}

$result = $response->json();

return json_decode(json_encode($result));
}
Expand Down
10 changes: 5 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 10 additions & 12 deletions database/factories/IcsEventFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,16 @@ public function icsProcessed(): static
$timezone = $this->faker->timezone;
$dateTime = Carbon::create($this->faker->dateTimeThisMonth());

$ics = <<< ICSEVENTDATA
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Calendar//Calendize 2.0//EN
BEGIN:VEVENT
SUMMARY: {$this->faker->words(4, true)}
DTSTART;TZID={$timezone}:{$dateTime}
DTEND;TZID={$timezone}:{$dateTime->addHours($this->faker->numberBetween(1, 3))}
DESCRIPTION:{$this->faker->words(14, true)}
END:VEVENT
END:VCALENDAR
ICSEVENTDATA;
$ics = "BEGIN:VCALENDAR\n" .
"VERSION:2.0\n" .
"PRODID:-//Calendar//Calendize 2.0//EN\n" .
"BEGIN:VEVENT\n" .
"SUMMARY: {$this->faker->words(4, true)}\n" .
"DTSTART;TZID={$timezone}:{$dateTime}\n" .
"DTEND;TZID={$timezone}:{$dateTime->addHours($this->faker->numberBetween(1, 3))}\n" .
"DESCRIPTION:{$this->faker->words(14, true)}\n" .
"END:VEVENT\n" .
'END:VCALENDAR';

return compact('ics');
});
Expand Down
165 changes: 165 additions & 0 deletions tests/Feature/Jobs/GenerateCalendarJobTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

use App\Events\IcsEventProcessed;
use App\Jobs\GenerateCalendarJob;
use App\Models\IcsEvent;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Http;
use OpenAI\Laravel\Facades\OpenAI;
use OpenAI\Responses\Chat\CreateResponse;

test('ICS event is updated with successful OpenAI response', function () {
$ics = IcsEvent::factory()->icsProcessed()->create();
$validIcsData = str_replace("\n", '\\n', $ics->ics);
$ics->update(['ics' => null]);
$ics->refresh();

expect($ics->ics)->toBeNull()
->and($validIcsData)->toBeString();

Event::fake();
OpenAI::fake([
CreateResponse::fake([
'choices' => [
[
'message' => [
'role' => 'assistant',
'content' => '{"ics": "' . $validIcsData . '"}',
],
],
],
]),
]);

GenerateCalendarJob::dispatch($ics);
Event::assertDispatched(IcsEventProcessed::class);

$ics->refresh();
expect($ics->ics)->not()->toBeNull()
->and($ics->error)->toBeNull()
->and($ics->getSummary())->toBeString();
});

test('ICS event is updated with error from OpenAI response', function () {
$ics = IcsEvent::factory()->create();

expect($ics->ics)->toBeNull()
->and($ics->error)->toBeNull();

Event::fake();
OpenAI::fake([
CreateResponse::fake([
'choices' => [
[
'message' => [
'role' => 'assistant',
'content' => '{"error": "Sorry an error is faked here!"}',
],
],
],
]),
]);

GenerateCalendarJob::dispatch($ics);
Event::assertDispatched(IcsEventProcessed::class);

$ics->refresh();
expect($ics->ics)->toBeNull()
->and($ics->error)->toEqual('Sorry an error is faked here!');
});

test('ICS event is updated with data from Mistral in case OpenAI fails', function () {
$ics = IcsEvent::factory()->icsProcessed()->create();
$validIcsData = str_replace("\n", '\\n', $ics->ics);
$ics->update(['ics' => null]);
$ics->refresh();

expect($ics->ics)->toBeNull()
->and($validIcsData)->toBeString();

Event::fake();
OpenAI::fake([
]);

Http::fake([
'api.mistral.ai/*' => Http::response([
'choices' => [0 => [
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => '{"ics": "' . $validIcsData . '"}',
],
'finish_reason' => 'stop',
], ],
'usage' => [
'prompt_tokens' => 14,
'total_tokens' => 29,
'completion_tokens' => 15,
],
]),
]);

GenerateCalendarJob::dispatch($ics);
Event::assertDispatched(IcsEventProcessed::class);

$ics->refresh();
expect($ics->ics)->not()->toBeNull()
->and($ics->error)->toBeNull()
->and($ics->getSummary())->toBeString();
});

test('ICS event is updated with error from Mistral response after OpenAI fails', function () {
$ics = IcsEvent::factory()->create();

expect($ics->ics)->toBeNull()
->and($ics->error)->toBeNull();

Event::fake();
OpenAI::fake([]);
Http::fake([
'api.mistral.ai/*' => Http::response([
'choices' => [0 => [
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => '{"error": "Sorry an error is faked here!"}',
],
'finish_reason' => 'stop',
], ],
'usage' => [
'prompt_tokens' => 14,
'total_tokens' => 29,
'completion_tokens' => 15,
],
]),
]);

GenerateCalendarJob::dispatch($ics);
Event::assertDispatched(IcsEventProcessed::class);

$ics->refresh();
expect($ics->ics)->toBeNull()
->and($ics->error)->toEqual('Sorry an error is faked here!');
});

test('ICS event is updated with error if both Mistral and OpenAI fail', function () {
$ics = IcsEvent::factory()->create();

expect($ics->ics)->toBeNull()
->and($ics->error)->toBeNull();

Event::fake();
OpenAI::fake([]);
Http::fake([
'api.mistral.ai/*' => Http::response("We're down, don't bother!", Symfony\Component\HttpFoundation\Response::HTTP_SERVICE_UNAVAILABLE),
]);

GenerateCalendarJob::dispatch($ics);

Event::assertDispatched(IcsEventProcessed::class);

$ics->refresh();

expect($ics->ics)->toBeNull()
->and($ics->error)->toEqual("I'm sorry, my servers are having hiccups. Please try again in 30-60 minutes!");
});

0 comments on commit 58c3b47

Please sign in to comment.