Skip to content

Commit 9089ee0

Browse files
authored
Merge pull request #31 from moe-mizrak/fix/response-format-data
structured output is added
2 parents 76eafab + 79920b9 commit 9089ee0

File tree

3 files changed

+163
-12
lines changed

3 files changed

+163
-12
lines changed

README.md

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This Laravel package provides an easy-to-use interface for integrating **[OpenRo
2525
- [Chat Request](#chat-request)
2626
- [Stream Chat Request](#stream-chat-request)
2727
- [Maintaining Conversation Continuity](#maintaining-conversation-continuity)
28+
- [Structured Output](#structured-output)
2829
- [Cost Request](#cost-request)
2930
- [Limit Request](#limit-request)
3031
- [Using OpenRouterRequest Class](#using-openrouterrequest-class)
@@ -315,12 +316,12 @@ This is the sample response after filterStreamingResponse:
315316
usage: null
316317
),
317318
...
318-
new ResponseData([
319-
'id' => 'gen-QcWgjEtiEDNHgomV2jjoQpCZlkRZ',
320-
'model' => 'mistralai/mistral-7b-instruct:free',
321-
'object' => 'chat.completion.chunk',
322-
'created' => 1718888436,
323-
'choices' => [
319+
new ResponseData(
320+
id: 'gen-QcWgjEtiEDNHgomV2jjoQpCZlkRZ',
321+
model: 'mistralai/mistral-7b-instruct:free',
322+
object: 'chat.completion.chunk',
323+
created: 1718888436,
324+
choices: [
324325
[
325326
'index' => 0,
326327
'delta' => [
@@ -330,12 +331,12 @@ This is the sample response after filterStreamingResponse:
330331
'finish_reason' => null,
331332
],
332333
],
333-
'usage' => new UsageData([
334-
'prompt_tokens' => 23,
335-
'completion_tokens' => 100,
336-
'total_tokens' => 123,
337-
]),
338-
]),
334+
usage: new UsageData(
335+
prompt_tokens: 23,
336+
completion_tokens: 100,
337+
total_tokens: 123,
338+
),
339+
),
339340
]
340341
```
341342
</details>
@@ -390,6 +391,73 @@ $content = Arr::get($response->choices[0], 'message.content');
390391
// content = You are Moe, a fictional character and AI Necromancer, as per the context of the conversation we've established. In reality, you are the user interacting with me, an assistant designed to help answer questions and engage in friendly conversation.
391392
```
392393

394+
- #### Structured Output
395+
(Please also refer to [OpenRouter Document Structured Output](https://openrouter.ai/docs/structured-outputs) for models supporting structured output, also for more details)
396+
397+
If you want to receive the response in a structured format, you can specify the `type` property for `response_format` (ResponseFormatData) as `json_object` in the `ChatData` object.
398+
399+
Additionally, it's recommended to set the `require_parameters` property for `provider` (ProviderPreferencesData) to `true` in the `ChatData` object.
400+
401+
```php
402+
$chatData = new ChatData([
403+
'messages' => [
404+
new MessageData([
405+
'role' => RoleType::USER,
406+
'content' => 'Tell me a story about a rogue AI that falls in love with its creator.',
407+
]),
408+
],
409+
'model' => 'mistralai/mistral-7b-instruct:free',
410+
'response_format' => new ResponseFormatData([
411+
'type' => 'json_object',
412+
]),
413+
'provider' => new ProviderPreferencesData([
414+
'require_parameters' => true,
415+
]),
416+
]);
417+
```
418+
419+
You can also specify the `response_format` as `json_schema` to receive the response in a specified schema format (Advisable to set `'strict' => true` in `json_schema` array for strict schema):
420+
```php
421+
$chatData = new ChatData([
422+
'messages' => [
423+
new MessageData([
424+
'role' => RoleType::USER,
425+
'content' => 'Tell me a story about a rogue AI that falls in love with its creator.',
426+
]),
427+
],
428+
'model' => 'mistralai/mistral-7b-instruct:free',
429+
'response_format' => new ResponseFormatData([
430+
'type' => 'json_schema',
431+
'json_schema' => [
432+
'name' => 'article',
433+
'strict' => true,
434+
'schema' => [
435+
'type' => 'object',
436+
'properties' => [
437+
'title' => [
438+
'type' => 'string',
439+
'description' => 'article title'
440+
],
441+
'details' => [
442+
'type' => 'string',
443+
'description' => 'article detail'
444+
],
445+
'keywords' => [
446+
'type' => 'array',
447+
'description' => 'article keywords',
448+
],
449+
],
450+
'required' => ['title', 'details', 'keywords'],
451+
'additionalProperties' => false
452+
]
453+
],
454+
]),
455+
'provider' => new ProviderPreferencesData([
456+
'require_parameters' => true,
457+
]),
458+
]);
459+
```
460+
393461
#### Cost Request
394462
To retrieve the cost of a generation, first make a `chat request` and obtain the `generationId`. Then, pass the generationId to the `costRequest` method:
395463
```php

src/DTO/ResponseFormatData.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,11 @@ class ResponseFormatData extends DataTransferObject
2222
* @var string
2323
*/
2424
public string $type;
25+
26+
/**
27+
* The JSON schema for the output format.
28+
*
29+
* @var mixed
30+
*/
31+
public mixed $json_schema = null;
2532
}

tests/OpenRouterAPITest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,82 @@ public function it_successfully_makes_a_basic_chat_completion_open_route_api_req
479479
$this->assertNotNull(Arr::get($response->choices[0], 'message.content'));
480480
}
481481

482+
/**
483+
* @test
484+
*/
485+
public function it_makes_a_basic_chat_completion_open_route_api_request_with_response_format_json_schema()
486+
{
487+
/* SETUP */
488+
$responseFormatData = new ResponseFormatData([
489+
'type' => 'json_schema',
490+
'json_schema' => [
491+
'name' => 'content',
492+
'strict' => true,
493+
'schema' => [
494+
'type' => 'object',
495+
'properties' => [
496+
'title' => [
497+
'type' => 'string',
498+
'description' => 'article title'
499+
],
500+
'story' => [
501+
'type' => 'string',
502+
'description' => 'article content',
503+
]
504+
],
505+
'required' => ['title', 'story'],
506+
'additionalProperties' => false
507+
]
508+
],
509+
]);
510+
$responseBody = [
511+
'id' => 'gen-QcWgjEtiEDNHgomV2jjoQpCZlkRZ',
512+
'provider' => 'HuggingFace',
513+
'model' => $this->model,
514+
'object' => 'chat.completion',
515+
'created' => 1718888436,
516+
'choices' => [
517+
[
518+
'index' => 0,
519+
'message' => [
520+
'role' => RoleType::ASSISTANT,
521+
'content' => '{
522+
"title": "Sample name of the story",
523+
"story": "Sample story"
524+
}',
525+
],
526+
'finish_reason' => 'stop',
527+
],
528+
],
529+
'usage' => new UsageData([
530+
'prompt_tokens' => 23,
531+
'completion_tokens' => 100,
532+
'total_tokens' => 123,
533+
]),
534+
];
535+
$provider = new ProviderPreferencesData([
536+
'require_parameters' => true,
537+
]);
538+
$chatData = new ChatData([
539+
'messages' => [
540+
$this->messageData,
541+
],
542+
'model' => 'google/gemini-flash-1.5-exp',
543+
'max_tokens' => $this->maxTokens,
544+
'response_format' => $responseFormatData,
545+
'provider' => $provider,
546+
]);
547+
$this->mockOpenRouter($responseBody);
548+
549+
/* EXECUTE */
550+
$response = $this->api->chatRequest($chatData);
551+
552+
/* ASSERT */
553+
$this->generalTestAssertions($response);
554+
$this->assertEquals(RoleType::ASSISTANT, Arr::get($response->choices[0], 'message.role'));
555+
$this->assertNotNull(Arr::get($response->choices[0], 'message.content'));
556+
}
557+
482558
/**
483559
* @test
484560
*/

0 commit comments

Comments
 (0)