Skip to content

Commit

Permalink
Fix: debug mode now works for LLM API provider exception
Browse files Browse the repository at this point in the history
  • Loading branch information
ddebowczyk committed Aug 12, 2024
1 parent beb7c6f commit 0989148
Show file tree
Hide file tree
Showing 59 changed files with 144 additions and 1,564 deletions.
18 changes: 2 additions & 16 deletions docs/cookbook/examples/api_support/ollama.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ You can use Instructor with local Ollama instance.
Please note that, at least currently, OS models do not perform on par with OpenAI (GPT-3.5 or GPT-4) model for complex data schemas.

Supported modes:
- Mode::MdJson - fallback mode
- Mode::MdJson - fallback mode, works with any capable model
- Mode::Json - recommended
- Mode::Tools - supported
- Mode::Tools - supported (for selected models - check Ollama docs)

## Example

Expand All @@ -23,8 +23,6 @@ $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

use Cognesy\Instructor\Clients\Ollama\OllamaClient;
use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Events\Request\RequestSentToLLM;
use Cognesy\Instructor\Events\Request\ResponseReceivedFromLLM;
use Cognesy\Instructor\Instructor;

enum UserType : string {
Expand All @@ -48,20 +46,9 @@ $client = new OllamaClient();
/// Get Instructor with the default client component overridden with your own
$instructor = (new Instructor)->withClient($client);

// Listen to events to print request/response data
//$instructor->onEvent(RequestSentToLLM::class, function($event) {
// print("Request sent to LLM:\n\n");
// dump($event->request);
//});
//$instructor->onEvent(ResponseReceivedFromLLM::class, function($event) {
// print("Received response from LLM:\n\n");
// dump($event->response);
//});

$user = $instructor->respond(
messages: "Jason (@jxnlco) is 25 years old and is the admin of this project. He likes playing football and reading books.",
responseModel: User::class,
prompt: 'Parse the user data to JSON, respond using following JSON Schema: <|json_schema|>',
examples: [[
'input' => 'Ive got email Frank - their developer. Asked to connect via Twitter @frankch. Btw, he plays on drums!',
'output' => ['name' => 'Frank', 'role' => 'developer', 'hobbies' => ['playing drums'], 'username' => 'frankch', 'age' => null],
Expand All @@ -73,7 +60,6 @@ $user = $instructor->respond(
mode: Mode::Json,
);


print("Completed response model:\n\n");

dump($user);
Expand Down
4 changes: 3 additions & 1 deletion docs/cookbook/examples/basics/complex_extraction_claude.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ $loader = require 'vendor/autoload.php';
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

use Cognesy\Instructor\Clients\Anthropic\AnthropicClient;
use Cognesy\Instructor\Clients\OpenRouter\OpenRouterClient;
use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Extras\Sequence\Sequence;
use Cognesy\Instructor\Instructor;
Expand Down Expand Up @@ -124,7 +125,7 @@ $events = $instructor
responseModel: Sequence::of(ProjectEvent::class),
model: 'claude-3-haiku-20240307', //'claude-3-sonnet-20240229',
prompt: 'Extract a list of project events with all the details from the provided input in JSON format using schema: <|json_schema|>',
mode: Mode::MdJson,
mode: Mode::Json,
examples: [['input' => 'Acme Insurance project to implement SalesTech CRM solution is currently in RED status due to delayed delivery of document production system, led by 3rd party vendor - Alfatech. Customer (Acme) is discussing the resolution with the vendor. Production deployment plan has been finalized on Aug 15th and awaiting customer approval.', 'output' => [["type" => "object", "title" => "sequenceOfProjectEvent", "description" => "A sequence of ProjectEvent", "properties" => ["list" => [["title" => "Absorbing delay by deploying extra resources", "description" => "System integrator (SysCorp) are working to absorb some of the delay by deploying extra resources to speed up development when the doc production is done.", "type" => "action", "status" => "open", "stakeholders" => [["name" => "SysCorp", "role" => "system integrator", "details" => "System integrator",],], "date" => "2021-09-01",], ["title" => "Finalization of production deployment plan", "description" => "Production deployment plan has been finalized on Aug 15th and awaiting customer approval.", "type" => "progress", "status" => "open", "stakeholders" => [["name" => "Acme", "role" => "customer", "details" => "Customer",],], "date" => "2021-08-15",],],]]]]],
options: [
'max_tokens' => 4096,
Expand All @@ -133,6 +134,7 @@ $events = $instructor
->get();

echo "TOTAL EVENTS: " . count($events) . "\n";
//dump($events->list);

function displayEvent(ProjectEvent $event) : void {
echo "Event: {$event->title}\n";
Expand Down
16 changes: 13 additions & 3 deletions docs/cookbook/examples/techniques/transcription_to_tasks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Instructor;
use Cognesy\Instructor\Schema\Attributes\Description;

// Step 1: Define a class that represents the structure and semantics
// of the data you want to extract
Expand All @@ -33,7 +32,6 @@ enum Role : string {
class Task {
public string $title;
public string $description;
#[Description("Due date in ISO 8601 format")]
public DateTime $dueDate;
public Role $owner;
public TaskStatus $status;
Expand Down Expand Up @@ -78,6 +76,18 @@ print("Extracted data:\n");
dump($tasks);

assert($tasks->meetingDate->format('Y-m-d') === '2024-01-15');
assert(count($tasks->tasks) > 1);
assert(count($tasks->tasks) == 3);

assert($tasks->tasks[0]->dueDate->format('Y-m-d') === '2024-01-20');
assert($tasks->tasks[0]->status === TaskStatus::Pending);
assert($tasks->tasks[0]->owner === Role::Dev);

assert($tasks->tasks[1]->dueDate->format('Y-m-d') === '2024-01-18');
assert($tasks->tasks[1]->status === TaskStatus::Pending);
assert($tasks->tasks[1]->owner === Role::PM);

assert($tasks->tasks[2]->dueDate->format('Y-m-d') === '2024-01-16');
assert($tasks->tasks[2]->status === TaskStatus::Pending);
assert($tasks->tasks[2]->owner === Role::PM);
?>
```
19 changes: 18 additions & 1 deletion docs/cookbook/examples/troubleshooting/debugging.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class User {
}

$instructor = (new Instructor)->withClient(new OpenAIClient(
apiKey: Env::get('OPENAI_API_KEY'),// . 'invalid', // intentionally invalid API key
apiKey: Env::get('OPENAI_API_KEY'), // . 'invalid', // intentionally invalid API key
baseUri: Env::get('OPENAI_BASE_URI'),
));

Expand All @@ -45,7 +45,24 @@ $user = $instructor->withDebug()->respond(
echo "\nResult:\n";
dump($user);

echo "Debugging request and response:\n\n";
$user2 = $instructor->withDebug()->respond(
messages: "Anna is 21 years old.",
responseModel: User::class,
options: [ 'stream' => false ]
);

echo "\nResult 2:\n";
dump($user2);

assert(isset($user->name));
assert(isset($user->age));
assert($user->name === 'Jason');
assert($user->age === 25);

assert(isset($user2->name));
assert(isset($user2->age));
assert($user2->name === 'Anna');
assert($user2->age === 21);
?>
```
4 changes: 3 additions & 1 deletion examples/01_Basics/ComplexExtractionClaude/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');

use Cognesy\Instructor\Clients\Anthropic\AnthropicClient;
use Cognesy\Instructor\Clients\OpenRouter\OpenRouterClient;
use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Extras\Sequence\Sequence;
use Cognesy\Instructor\Instructor;
Expand Down Expand Up @@ -124,7 +125,7 @@ enum StakeholderRole: string {
responseModel: Sequence::of(ProjectEvent::class),
model: 'claude-3-haiku-20240307', //'claude-3-sonnet-20240229',
prompt: 'Extract a list of project events with all the details from the provided input in JSON format using schema: <|json_schema|>',
mode: Mode::MdJson,
mode: Mode::Json,
examples: [['input' => 'Acme Insurance project to implement SalesTech CRM solution is currently in RED status due to delayed delivery of document production system, led by 3rd party vendor - Alfatech. Customer (Acme) is discussing the resolution with the vendor. Production deployment plan has been finalized on Aug 15th and awaiting customer approval.', 'output' => [["type" => "object", "title" => "sequenceOfProjectEvent", "description" => "A sequence of ProjectEvent", "properties" => ["list" => [["title" => "Absorbing delay by deploying extra resources", "description" => "System integrator (SysCorp) are working to absorb some of the delay by deploying extra resources to speed up development when the doc production is done.", "type" => "action", "status" => "open", "stakeholders" => [["name" => "SysCorp", "role" => "system integrator", "details" => "System integrator",],], "date" => "2021-09-01",], ["title" => "Finalization of production deployment plan", "description" => "Production deployment plan has been finalized on Aug 15th and awaiting customer approval.", "type" => "progress", "status" => "open", "stakeholders" => [["name" => "Acme", "role" => "customer", "details" => "Customer",],], "date" => "2021-08-15",],],]]]]],
options: [
'max_tokens' => 4096,
Expand All @@ -133,6 +134,7 @@ enum StakeholderRole: string {
->get();

echo "TOTAL EVENTS: " . count($events) . "\n";
//dump($events->list);

function displayEvent(ProjectEvent $event) : void {
echo "Event: {$event->title}\n";
Expand Down
16 changes: 13 additions & 3 deletions examples/03_Techniques/TranscriptionToTasks/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Instructor;
use Cognesy\Instructor\Schema\Attributes\Description;

// Step 1: Define a class that represents the structure and semantics
// of the data you want to extract
Expand All @@ -33,7 +32,6 @@ enum Role : string {
class Task {
public string $title;
public string $description;
#[Description("Due date in ISO 8601 format")]
public DateTime $dueDate;
public Role $owner;
public TaskStatus $status;
Expand Down Expand Up @@ -78,6 +76,18 @@ class Tasks {
dump($tasks);

assert($tasks->meetingDate->format('Y-m-d') === '2024-01-15');
assert(count($tasks->tasks) > 1);
assert(count($tasks->tasks) == 3);

assert($tasks->tasks[0]->dueDate->format('Y-m-d') === '2024-01-20');
assert($tasks->tasks[0]->status === TaskStatus::Pending);
assert($tasks->tasks[0]->owner === Role::Dev);

assert($tasks->tasks[1]->dueDate->format('Y-m-d') === '2024-01-18');
assert($tasks->tasks[1]->status === TaskStatus::Pending);
assert($tasks->tasks[1]->owner === Role::PM);

assert($tasks->tasks[2]->dueDate->format('Y-m-d') === '2024-01-16');
assert($tasks->tasks[2]->status === TaskStatus::Pending);
assert($tasks->tasks[2]->owner === Role::PM);
?>
```
19 changes: 18 additions & 1 deletion examples/04_Troubleshooting/Debugging/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class User {
}

$instructor = (new Instructor)->withClient(new OpenAIClient(
apiKey: Env::get('OPENAI_API_KEY'),// . 'invalid', // intentionally invalid API key
apiKey: Env::get('OPENAI_API_KEY'), // . 'invalid', // intentionally invalid API key
baseUri: Env::get('OPENAI_BASE_URI'),
));

Expand All @@ -45,7 +45,24 @@ class User {
echo "\nResult:\n";
dump($user);

echo "Debugging request and response:\n\n";
$user2 = $instructor->withDebug()->respond(
messages: "Anna is 21 years old.",
responseModel: User::class,
options: [ 'stream' => false ]
);

echo "\nResult 2:\n";
dump($user2);

assert(isset($user->name));
assert(isset($user->age));
assert($user->name === 'Jason');
assert($user->age === 25);

assert(isset($user2->name));
assert(isset($user2->age));
assert($user2->name === 'Anna');
assert($user2->age === 21);
?>
```
18 changes: 2 additions & 16 deletions examples/05_APISupport/LLMSupportOllama/run.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
Please note that, at least currently, OS models do not perform on par with OpenAI (GPT-3.5 or GPT-4) model for complex data schemas.

Supported modes:
- Mode::MdJson - fallback mode
- Mode::MdJson - fallback mode, works with any capable model
- Mode::Json - recommended
- Mode::Tools - supported
- Mode::Tools - supported (for selected models - check Ollama docs)

## Example

Expand All @@ -23,8 +23,6 @@

use Cognesy\Instructor\Clients\Ollama\OllamaClient;
use Cognesy\Instructor\Enums\Mode;
use Cognesy\Instructor\Events\Request\RequestSentToLLM;
use Cognesy\Instructor\Events\Request\ResponseReceivedFromLLM;
use Cognesy\Instructor\Instructor;

enum UserType : string {
Expand All @@ -48,20 +46,9 @@ class User {
/// Get Instructor with the default client component overridden with your own
$instructor = (new Instructor)->withClient($client);

// Listen to events to print request/response data
//$instructor->onEvent(RequestSentToLLM::class, function($event) {
// print("Request sent to LLM:\n\n");
// dump($event->request);
//});
//$instructor->onEvent(ResponseReceivedFromLLM::class, function($event) {
// print("Received response from LLM:\n\n");
// dump($event->response);
//});

$user = $instructor->respond(
messages: "Jason (@jxnlco) is 25 years old and is the admin of this project. He likes playing football and reading books.",
responseModel: User::class,
prompt: 'Parse the user data to JSON, respond using following JSON Schema: <|json_schema|>',
examples: [[
'input' => 'Ive got email Frank - their developer. Asked to connect via Twitter @frankch. Btw, he plays on drums!',
'output' => ['name' => 'Frank', 'role' => 'developer', 'hobbies' => ['playing drums'], 'username' => 'frankch', 'age' => null],
Expand All @@ -73,7 +60,6 @@ class User {
mode: Mode::Json,
);


print("Completed response model:\n\n");

dump($user);
Expand Down
9 changes: 9 additions & 0 deletions src/ApiClient/Traits/HandlesApiResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Cognesy\Instructor\Events\ApiClient\ApiRequestSent;
use Cognesy\Instructor\Events\ApiClient\ApiResponseReceived;
use Exception;
use Saloon\Exceptions\Request\RequestException;
use Saloon\Http\Response;

trait HandlesApiResponse
Expand Down Expand Up @@ -51,6 +52,14 @@ protected function respondRaw(ApiRequest $request): Response {
body: (string) $response->getPsrRequest()->getBody(),
));
} catch (Exception $exception) {
if ($request->requestConfig()->isDebug() && method_exists($exception, 'getResponse')) {
$response = $exception->getResponse();
if (!empty($response)) {
Debugger::requestDebugger($response->getPendingRequest(), $response->getPsrRequest());
// body cannot be accessed - see Saloon issue: https://github.com/saloonphp/saloon/issues/447
Debugger::responseDebugger($response, $response->getPsrResponse(), $response->getPsrResponse()->getBody());
}
}
$this->events->dispatch(new ApiRequestErrorRaised($exception));
throw $exception;
}
Expand Down
11 changes: 11 additions & 0 deletions src/ApiClient/Traits/HandlesStreamApiResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Cognesy\Instructor\Events\ApiClient\ApiStreamUpdateReceived;
use Exception;
use Generator;
use Saloon\Exceptions\Request\RequestException;
use Saloon\Http\Response;

trait HandlesStreamApiResponse
{
Expand Down Expand Up @@ -49,6 +51,15 @@ protected function streamRaw(): Generator {
body: (string) $response->getPsrRequest()->getBody(),
));
} catch (Exception $exception) {
if ($request->requestConfig()->isDebug() && method_exists($exception, 'getResponse')) {
/** @var Response $response */
$response = $exception->getResponse();
if (!empty($response)) {
Debugger::requestDebugger($response->getPendingRequest(), $response->getPsrRequest());
// body cannot be accessed - see Saloon issue: https://github.com/saloonphp/saloon/issues/447
Debugger::responseDebugger($response, $response->getPsrResponse(), $response->getPsrResponse()->getBody());
}
}
$this?->events->dispatch(new ApiRequestErrorRaised($exception));
throw $exception;
}
Expand Down
17 changes: 17 additions & 0 deletions src/ApiClient/Utils/Debugger.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,21 @@ public static function getResponseHeaders(ResponseInterface $psrResponse) : arra
}
return $headers;
}

public static function getRequestData(RequestInterface $request): array {
return [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'headers' => $request->getHeaders(),
'body' => (string) $request->getBody(),
];
}

public static function getResponseData(ResponseInterface $response): array {
return [
'status' => $response->getStatusCode(),
'headers' => $response->getHeaders(),
'body' => (string) $response->getBody(),
];
}
}
3 changes: 1 addition & 2 deletions src/Clients/Gemini/GeminiApiRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ protected function defaultBody(): array {
$system = Messages::fromArray($this->messages)
->forRoles(['system'])
->toString();
$system = empty($system) ? [] : ['parts' => ['text' => $system]];
$contents = Messages::fromArray($this->messages)
->exceptRoles(['system'])
->toNativeArray(ClientType::fromRequestClass($this), mergePerRole: true);
$body = array_filter(
[
'systemInstruction' => $system,
'systemInstruction' => empty($system) ? [] : ['parts' => ['text' => $system]],
'contents' => $contents,
'generationConfig' => $this->options(),
],
Expand Down
8 changes: 8 additions & 0 deletions src/Clients/Gemini/Traits/HandlesRequestBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@

trait HandlesRequestBody
{
protected function model() : string {
return $this->model;
}

public function messages(): array {
return $this->messages;
}

public function tools(): array {
return $this->tools;
}
Expand Down
Loading

0 comments on commit 0989148

Please sign in to comment.