xbnz/google-gemini-api

用于访问谷歌AI模型和OAuth2服务的HTTP客户端

1.0.0 2024-05-23 02:49 UTC

This package is auto-updated.

Last update: 2024-09-13 00:02:04 UTC


README

Test suite Test suite Release License

带有OAuth2认证的Google Gemini API客户端

动机

其他库依赖于generativelanguage.googleapis.com API,该API缺少一些模型。例如,新引入的“Gemini Experimental”模型,它是免费的(!)无法通过该API访问。这个库解决了这个问题。

注意

Gemini Experimental API可能不会永远存在。如果您正在使用在generativelanguage.googleapis.com API上可用的模型,您也可以使用这个库来访问它。

安装

composer require xbnz/gemini

先决条件

在Google Cloud中创建服务帐户

  1. 前往 Google Cloud Console
  2. 前往 IAM & Admin
  3. 前往 服务帐户
  4. 点击您的项目
  5. 如果您还没有,点击“创建服务帐户”
  6. 给它一个名字并点击“创建”
  7. 点击“完成”
  8. 回到您刚刚创建的服务帐户
  9. 点击“添加密钥”并创建一个JSON密钥。文件将下载到您的计算机
  10. 从JSON文件中获取必要的信息
    • client_email
    • private_key

入门

示例应用程序

这里是一个使用此库的示例Laravel命令服务提供者 是将接口绑定到具体实现的地方。

注册到依赖注入容器(可选)

// Laravel example

namespace App\Providers;

clsss AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(GoogleOAuth2Interface::class, function (Application $app) {
            return new GoogleOAuth2Service(
                logger: $app->make(LoggerInterface::class)
            );
        });

        $this->app->bind(GoogleAIPlatformInterface::class, function (Application $app) {
            return new GoogleAIPlatformService(
                (new GoogleAIPlatformConnector(
                    $app->make('config')->get('services.google_ai_platform.project_id'),
                    $app->make('config')->get('services.google_ai_platform.region'),
                ))->authenticate(
                    new TokenAuthenticator(
                        $app->make(GoogleOAuth2Interface::class)->token(
                            new TokenRequestDTO(
                                googleServiceAccount: new GoogleServiceAccount(
                                    $app->make('config')->get('services.google_ai_platform.client_email'),
                                    $app->make('config')->get('services.google_ai_platform.private_key'),
                                ),
                                scope: 'https://www.googleapis.com/auth/cloud-platform',
                                issuedAt: CarbonImmutable::now(),
                                expiration: CarbonImmutable::now()->addHour()
                            )
                        )->accessToken
                    )
                ),
                $app->make('log')
            );
        });
    }
}

使用AIPlatform服务进行单轮或多轮对话

namespace App\Console\Commands;

class SomeCommand
{
    public function __construct(
        private readonly AIPlatformInterface $aiPlatformService
    ) {}

    public function handle(): void
    {
        $response = $this->aiPlatformService->generateContent(
            new GenerateContentRequestDTO(
                'publishers/google/models/gemini-experimental',
                Collection::make([
                    new ContentDTO(
                        Role::User,
                        Collection::make([
                            new TextPart('Explain what is happening in the image'),
                            new BlobPart(
                                'image/jpeg',
                                'base64 image...',
                            )
                        ])
                    ),
                    new ContentDTO(
                        Role::Model,
                        Collection::make([
                            new TextPart('Sure! This is an image of a cat.'),
                        ]),
                    ),
                    new ContentDTO(
                        Role::User,
                        Collection::make([
                            new TextPart('What color is the cat?'),
                        ]),
                    ),
                    // and so on...
                ])
                Collection::make([
                    new SafetySettings(HarmCategory::HarmCategoryHarassment, SafetyThreshold::BlockOnlyHigh),
                    new SafetySettings(HarmCategory::HarmCategoryHateSpeech, SafetyThreshold::BlockOnlyHigh),
                    new SafetySettings(HarmCategory::HarmCategorySexuallyExplicit, SafetyThreshold::BlockOnlyHigh),
                    new SafetySettings(HarmCategory::HarmCategoryDangerousContent, SafetyThreshold::BlockOnlyHigh),
                ]),
                systemInstructions: Collection::make([
                    new TextPart('Your instructions here...'),
                ]),
                generationConfig: new GenerationConfig(...) // Optional
            )
        );
        
        if ($response->finishReason->consideredSuccessful() === false) {
            // Handle the model not being able to generate content (probably due to unsafe content)
        }
        
        // Do something with the healthy response
        $response->usage->promptTokenCount;
        $modelResponse = $response
            ->content
            ->parts
            ->sole(fn(PartContract $part) => $part instanceof TextPart) // There should only be one TextPart::class in a model response (for now)
            ->text;
    }
}

注意

如果您不想使用依赖注入容器,可以直接new这些类

注意

您可以将模型从TextPart::class响应序列化并存储在您的数据库中。这将允许您与模型进行持久的聊天会话,类似于ChatGPT界面。

核心概念

可扩展的身份验证

谷歌 aiplatform.googleapis.com API允许多种形式的身份验证。这个库倾向于使用从您的服务帐户派生的access_token。然而,这个库的底层HTTP客户端(SaloonPHP)允许在new之前扩展“Connector”类以使用任何认证器。因此,您可以创建自己的Saloon认证策略并将其传递给构造函数

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(GoogleAIPlatformInterface::class, function (Application $app) {
            return new GoogleAIPlatformService(
                (new GoogleAIPlatformConnector(...))->authenticate(new MyCustomAuthenticator),
            );
        });
    }
}

Bearer token认证

提供的GoogleOAuthInterface::token()方法允许您获取用于Bearer认证的新令牌。请注意,如果您使用此工具对AIPlatform进行认证,每次调用该方法都会生成新的令牌,这可能或可能不是所需的。

缓存您的令牌直到过期(Laravel示例)

由于大多数现代框架的缓存集成非常简单,因此此包不提供缓存机制——例如使用PSR缓存接口。以下是一个Laravel示例

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
          $this->app->bind(GoogleAIPlatformInterface::class, function (Application $app) {
            return new GoogleAIPlatformService(
                (new GoogleAIPlatformConnector(...))->authenticate(
                    new TokenAuthenticator(
                        $app->make('cache')->remember(
                            'google_api_platform_token',
                            CarbonImmutable::now()->addMinutes(60),
                            function () use ($app) {
                                return $app->make(GoogleOAuth2Interface::class)->token(
                                    new TokenRequestDTO(
                                        googleServiceAccount: new GoogleServiceAccount(
                                            $app->make('config')->get('services.google_ai_platform.client_email'),
                                            $app->make('config')->get('services.google_ai_platform.private_key'),
                                        ),
                                        scope: 'https://www.googleapis.com/auth/cloud-platform',
                                        issuedAt: CarbonImmutable::now(),
                                        expiration: CarbonImmutable::now()->addHour()
                                    )
                                )->accessToken;
                            }
                        ),
                    )
                ),
                $app->make('log')
            );
        });
    }
}

日志记录和异常处理

提供您自己的PSR兼容的记录器

这个库使用PSR记录器接口进行日志记录。您可以通过将其传递给服务的构造函数来提供自己的记录器

namespace App\Console\Commands;

class SomeCommand
{
    public function handle(): void
    {
        $service = new GoogleAIPlatformService(
            new GoogleAIPlatformConnector(...),
            new MyCustomLogger
        );
        
        try {
            $response = $service->generateContent(...);
        } catch (GoogleAIPlatformException $e) {
            // Handle the exception
        }
        
        // At this point, the logger will have logged the exception
    }
}

OAuth2服务上也有相同的功能。

请求和响应的生命周期钩子

这不适合基本用例。然而,API 会变化,维护者可能比较懒惰。如果谷歌在响应中添加了一个我无法处理的字段,你可以自由地挂钩到响应中并创建自己的 DTO,如果你不想提交 PR。

namespace App\Console\Commands;

class SomeCommand
{
    public function __construct(
        private readonly GoogleAIPlatformInterface $aiPlatform
    ) {}

    public function handle(): void
    {
        $this->aiPlatform->generateContent(
            requestDto: ...,
            beforeRequest: function (Request $request) {
                $request->headers()->merge([
                    'X-Custom-Header' => 'Value'
                ]);
                
                return $request;
            },
            afterResponse: function (Response $response) {
                // Return your own DTO or do something with the response
                
                return $response;
            }
        )
    }
}

重试失败的请求

当使用免费的 Gemini 模型时,会实施严格的速率限制。当遇到 429 响应时,你可以决定重试。

先决条件

未提供 Guzzle 重试中间件。你可以实现自己的或者使用你选择的库。例如

composer require caseyamcl/guzzle_retry_middleware
namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(GoogleAIPlatformInterface::class, function (Application $app) {
            $connector = new GoogleAIPlatformConnector(...);
            $connector->authenticate(...)
            $connector->sender()->getHandlerStack()->push(
                GuzzleRetryMiddleware::factory([
                    'max_retry_attempts' => 5,
                    'retry_on_status' => [429],
                ])
            )
        
            return new GoogleAIPlatformService(
                $connector
            );
        });
    }
}

测试

此库提供用于测试的模拟实现。例如,你可以这样使用 GoogleOAuth2ServiceFake::class

调用代码

namespace App\Console\Commands;

use XbNz\Gemini\OAuth2\GoogleOAuth2Interface;

class SomeCommand
{
    public function __construct(
        private readonly GoogleOAuth2Interface $googleOAuth2Service
    ) {}

    public function handle(): void
    {
        $response = $this->googleOAuth2Service->token(...);
    }
}

测试代码

namespace Tests\Feature;

use Tests\TestCase;
use XbNz\Gemini\OAuth2\DataTransferObjects\Requests\TokenRequestDTO;
use XbNz\Gemini\OAuth2\GoogleOAuth2ServiceFake;

class SomeCommandTest extends TestCase
{
    public function test_it_works(): void
    {
        // Swapping the real service with the fake one in the Laravel container
        $this->app->swap(GoogleOAuth2Interface::class, $fake = new GoogleOAuth2ServiceFake);
        
        // Basic testing helpers
        $fake->alwaysReturnToken(...);
        $fake->assertTokenRequestCount(...);
        
        // Asserting against requests
        $fake->assertTokenRequest(function (TokenRequestDTO) {
            return $dto->grantType === 'urn:ietf:params:oauth:grant-type:jwt-bearer';
        });
    }
}

注意

相同的理念可以应用于 AIPlatform 服务。模拟实现称为 GoogleAIPlatformServiceFake::class,并提供类似的测试断言