Skip to content

0.4.x #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .jane-openapi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

return [
'openapi-file' => __DIR__ . '/var/openapi-no-deprecated.yaml',
'openapi-file' => __DIR__ . '/openai-openapi/openapi.yaml',
'namespace' => 'Sourceability\OpenAIClient\Generated',
'directory' => __DIR__ . '/generated',
];
];
8 changes: 2 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ SHELL = /bin/bash
openai-openapi:
git clone [email protected]:openai/openai-openapi.git

var/openapi-no-deprecated.yaml:
mkdir -p ./var
yq eval 'del(.paths | .. | select(has("deprecated") and .deprecated == true))' ./openai-openapi/openapi.yaml > $@

generated: openai-openapi
vendor/bin/jane-openapi generate

Expand Down Expand Up @@ -39,5 +35,5 @@ pre-commit:
$(MAKE) -k rector cs phpunit

.PHONY: build
build: var/openapi-no-deprecated.yaml
time (time bin/jane generate ; for in in {1..4} ; do time (vendor/bin/rector process ; vendor/bin/ecs --fix); done)
build:
time (time bin/jane generate ; for in in {1..2} ; do time (vendor/bin/rector process --no-diffs ; vendor/bin/ecs --fix > /dev/null); done)
120 changes: 119 additions & 1 deletion build/ModelWithConstructorGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Sourceability\OpenAIClient\Build;

use Jane\Component\JsonSchema\Generator\Context\Context;
use Jane\Component\JsonSchema\Guesser\Guess\ClassGuess as BaseClassGuess;
use Jane\Component\JsonSchema\Guesser\Guess\Property;
use Jane\Component\OpenApi3\JsonSchema\Model\Schema;
use Jane\Component\OpenApiCommon\Generator\ModelGenerator;
use PhpParser\Comment\Doc;
use PhpParser\Node\Arg;
Expand All @@ -23,6 +25,27 @@

class ModelWithConstructorGenerator extends ModelGenerator
{
public function generate(\Jane\Component\JsonSchema\Registry\Schema $schema, string $className, Context $context): void
{
foreach ($schema->getClasses() as $class) {
if ($class->getName() === 'CreateChatCompletionResponse') {
// allow null
$class->getProperty('system_fingerprint')->getType()->getObject()->setNullable(true);
// dd($class->getProperty('system_fingerprint')->getType());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove debug comment

$class->getProperty('system_fingerprint')->getType()->getObject()->setDefault(null);
$properties = $class->getLocalProperties();
foreach ($properties as $index => $property) {
if ($property->getName() === 'system_fingerprint') {
$properties[$index] = $this->getPropertyWithNullable($property, true);
}
}
$class->setProperties($properties);
}
}

parent::generate($schema, $className, $context);
}

/**
* @param array<Stmt\Property> $properties
*/
Expand All @@ -36,8 +59,42 @@ protected function doCreateModel(BaseClassGuess $class, array $properties, array
$property->props[0]->default = new Name('null');
}

// Move properties with a default value to the end of the list
// That way objects with discriminator like properties will have the discriminator property last
// To avoid having to use named arguments
$localProperties = $class->getLocalProperties();
usort(
$localProperties,
function (Property $a, Property $b): int {
if ($a->getType()->getObject()->getDefault() === null && $b->getType()->getObject()->getDefault() !== null) {
return -1;
}

if ($a->getType()->getObject()->getDefault() !== null && $b->getType()->getObject()->getDefault() === null) {
return 1;
}

return 0;
}
);

// Remove logprobs default value which causes issues with vision api calls
if ($class->getName() === 'CreateChatCompletionRequest') {
foreach ($properties as $property) {
if ($property->props[0]->name->toString() === 'logprobs') {
$property->props[0]->default = new Name('null');
}
}
foreach ($localProperties as &$localProperty) {
if ($localProperty->getName() === 'logprobs') {
$localProperty = $this->getPropertyWithDefault($localProperty, null);
}
}
}
$class->setProperties($localProperties);

$methods = [
$this->createConstructorMethod($class->getLocalProperties()),
$this->createConstructorMethod($localProperties),
...$methods,
];

Expand All @@ -47,6 +104,9 @@ protected function doCreateModel(BaseClassGuess $class, array $properties, array
protected function createConstructorArgumentDoc(Property $property): string
{
$docTypeHint = $property->getType()->getDocTypeHint('');
if ($docTypeHint instanceof Name) {
$docTypeHint = $docTypeHint->toString();
}
if ($property->isNullable() && ! str_contains($docTypeHint, 'null')) {
$docTypeHint .= '|null';
}
Expand Down Expand Up @@ -80,6 +140,64 @@ protected function createConstructorArgumentDoc(Property $property): string
return $docBlock;
}

protected function createProperty(Property $property, string $namespace, $default = null, bool $strict = true): Stmt
{
if ($property->getType()->getName() === 'string'
&& $property->getType()->getObject() instanceof Schema
&& count($property->getType()->getObject()->getEnum() ?? []) === 1
&& $property->getType()->getObject()->getDefault() === null
) {
// Useful for completion message role properties which are defined as enums with a single value
$newDefault = $property->getType()->getObject()->getEnum()[0];

$property = $this->getPropertyWithDefault($property, $newDefault);
}

return parent::createProperty($property, $namespace, $default, $strict);
}

private function getPropertyWithDefault(Property $property, $newDefault): Property
{
$property->getType()->getObject()->setDefault($newDefault);
$newProperty = new Property(
$property->getObject(),
$property->getName(),
$property->getReference(),
$property->isNullable(),
$property->isRequired(),
$property->getType(),
$property->getDescription(),
$newDefault,
$property->isReadOnly()
);
$newProperty->setPhpName($property->getName());
$newProperty->setAccessorName($property->getAccessorName());
$newProperty->setDeprecated($property->isDeprecated());

return $newProperty;
}

private function getPropertyWithNullable(Property $property, bool $newNullable): Property
{
$property->getType()->getObject()->setNullable($newNullable);
$newProperty = new Property(
$property->getObject(),
$property->getName(),
$property->getReference(),
$newNullable,
$property->isRequired(),
$property->getType(),
$property->getDescription(),
$property->getDefault(),
$property->isReadOnly()
);
$newProperty->setPhpName($property->getName());
$newProperty->setAccessorName($property->getAccessorName());
$newProperty->setDeprecated($property->isDeprecated());

return $newProperty;
}

/**
* @param array<Property> $properties
*/
Expand Down
14 changes: 10 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
"guzzlehttp/psr7": "^2.4",
"guzzlehttp/guzzle": "^7.5",
"php-http/guzzle7-adapter": "^1.0",
"jane-php/open-api-3": "^7.4",
"rector/rector": "^0.15.16",
"symplify/easy-coding-standard": "^11.2",
"slevomat/coding-standard": "^8.8",
"jane-php/open-api-3": "dev-openapi-oneof as 7.5.x-dev",
"rector/rector": "^0.19.0",
"symplify/easy-coding-standard": "^12.1",
"slevomat/coding-standard": "^8.14",
"php-http/logger-plugin": "^1.3",
"phpunit/phpunit": "^10.0"
},
Expand All @@ -41,6 +41,12 @@
"dealerdirect/phpcodesniffer-composer-installer": false
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/adrienbrault/open-api-3"
}
],
"extra": {
"symfony": {
"allow-contrib": "true"
Expand Down
3 changes: 2 additions & 1 deletion ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
SetList::DOCBLOCK,
SetList::NAMESPACES,
SetList::SPACES,
SetList::STRICT,
]);

$ecsConfig->ruleWithConfiguration(ArraySyntaxFixer::class, [
'syntax' => 'short',
]);
};
};
38 changes: 38 additions & 0 deletions example-together-ai.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

require __DIR__ . '/vendor/autoload.php';

use Http\Client\Common\Plugin\LoggerPlugin;
use Http\Message\Formatter\FullHttpMessageFormatter;
use Sourceability\OpenAIClient\Client;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartImage;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartImageImageUrl;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartText;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestUserMessage;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionRequest;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionResponse;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\ConsoleOutput;

$apiClient = Client::create(
additionalPlugins: [
new LoggerPlugin(
new ConsoleLogger(new ConsoleOutput(ConsoleOutput::VERBOSITY_DEBUG)),
new FullHttpMessageFormatter()
)
],
baseUri: 'https://api.together.xyz/v1',
apiKey: getenv('TOGETHER_API_KEY')
);

$completionResponse = $apiClient->createChatCompletion(new CreateChatCompletionRequest(
model: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
temperature: 0,
messages: [
new ChatCompletionRequestUserMessage('The jane php library is very useful because')
]
));

var_dump(
$completionResponse->getChoices()[0]->getMessage()->getContent()
);
53 changes: 53 additions & 0 deletions examples/chat-vision.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

require __DIR__ . '/vendor/autoload.php';

use Http\Client\Common\Plugin\LoggerPlugin;
use Http\Message\Formatter\FullHttpMessageFormatter;
use Sourceability\OpenAIClient\Client;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartImage;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartImageImageUrl;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessageContentPartText;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestUserMessage;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionRequest;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionResponse;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\ConsoleOutput;

$apiClient = Client::create(
additionalPlugins: [
new LoggerPlugin(
new ConsoleLogger(new ConsoleOutput(ConsoleOutput::VERBOSITY_DEBUG)),
new FullHttpMessageFormatter()
)
],
apiKey: getenv('OPENAI_API_KEY')
);

$completionResponse = $apiClient->createChatCompletion(new CreateChatCompletionRequest(
model: 'gpt-4-vision-preview',
temperature: 0,
messages: [
new ChatCompletionRequestUserMessage(
[
new ChatCompletionRequestMessageContentPartImage(
new ChatCompletionRequestMessageContentPartImageImageUrl(
'https://symfony.com/screenshots/symfony-profiler.png'
)
),
new ChatCompletionRequestMessageContentPartText(
'Reply with a JSON object describing the timeline shown.'
)
]
)
],
maxTokens: 2000,
));

$content = $completionResponse->getChoices()[0]->getMessage()->getContent();
$content = preg_replace('#^\S*```json#', '', $content);
$content = trim($content, "`\n ");

var_dump(
json_decode($content, true)
);
12 changes: 3 additions & 9 deletions example-chat.php → examples/chat.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Http\Client\Common\Plugin\LoggerPlugin;
use Http\Message\Formatter\FullHttpMessageFormatter;
use Sourceability\OpenAIClient\Client;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestMessage;
use Sourceability\OpenAIClient\Generated\Model\ChatCompletionRequestUserMessage;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionRequest;
use Sourceability\OpenAIClient\Generated\Model\CreateChatCompletionResponse;
use Symfony\Component\Console\Logger\ConsoleLogger;
Expand All @@ -26,20 +26,14 @@
model: 'gpt-3.5-turbo',
temperature: 0,
messages: [
new ChatCompletionRequestMessage(
role: 'user',
content: 'The jane php library is very useful because'
)
new ChatCompletionRequestUserMessage('The jane php library is very useful because')
],
),
new CreateChatCompletionRequest(
model: 'gpt-3.5-turbo',
temperature: 0,
messages: [
new ChatCompletionRequestMessage(
role: 'user',
content: 'Symfony symfony symfony is like sourceability on a'
)
new ChatCompletionRequestUserMessage('Symfony symfony symfony is like sourceability on a')
],
),
];
Expand Down
File renamed without changes.
30 changes: 30 additions & 0 deletions generated/Authentication/ApiKeyAuthAuthentication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Sourceability\OpenAIClient\Generated\Authentication;

use Jane\Component\OpenApiRuntime\Client\AuthenticationPlugin;
use Psr\Http\Message\RequestInterface;

class ApiKeyAuthAuthentication implements AuthenticationPlugin
{
private $token;

public function __construct(string $token)
{
$this->{'token'} = $token;
}

public function authentication(RequestInterface $request): RequestInterface
{
$header = sprintf('Bearer %s', $this->{'token'});
$request = $request->withHeader('Authorization', $header);
return $request;
}

public function getScope(): string
{
return 'ApiKeyAuth';
}
}
Loading