• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / typo3-solver / 13279972588

12 Feb 2025 07:10AM UTC coverage: 81.987% (-6.0%) from 88.036%
13279972588

Pull #295

github

web-flow
Merge da0f95ebf into 2764f6ab2
Pull Request #295: [FEATURE] Add Gemini as additional solution provider

4 of 85 new or added lines in 7 files covered. (4.71%)

1 existing line in 1 file now uncovered.

883 of 1077 relevant lines covered (81.99%)

2.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Classes/ProblemSolving/Solution/Provider/GeminiSolutionProvider.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "solver".
7
 *
8
 * Copyright (C) 2023-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\Typo3Solver\ProblemSolving\Solution\Provider;
25

26
use EliasHaeussler\Typo3Solver\Configuration;
27
use EliasHaeussler\Typo3Solver\Exception;
28
use EliasHaeussler\Typo3Solver\ProblemSolving;
29
use GeminiAPI\Client;
30
use GeminiAPI\Enums;
31
use GeminiAPI\GenerationConfig;
32
use GeminiAPI\GenerativeModel;
33
use GeminiAPI\Resources;
34
use GeminiAPI\Responses;
35

36
/**
37
 * GeminiSolutionProvider
38
 *
39
 * @author Elias Häußler <elias@haeussler.dev>
40
 * @license GPL-2.0-or-later
41
 */
42
final class GeminiSolutionProvider implements StreamedSolutionProvider
43
{
44
    private readonly string $modelName;
45

NEW
46
    public function __construct(
×
47
        private readonly GenerativeModel $model,
48
        private readonly Configuration\Configuration $configuration,
49
    ) {
NEW
50
        $this->modelName = $this->model->modelName instanceof Enums\ModelName
×
NEW
51
            ? $this->model->modelName->value
×
NEW
52
            : $this->model->modelName
×
NEW
53
        ;
×
54
    }
55

56
    /**
57
     * @throws Exception\ApiKeyMissingException
58
     */
NEW
59
    public static function create(Client $client = null): static
×
60
    {
NEW
61
        $configuration = new Configuration\Configuration();
×
62

NEW
63
        if ($configuration->getApiKey() === null) {
×
NEW
64
            throw Exception\ApiKeyMissingException::create();
×
65
        }
66

NEW
67
        $client ??= new Client($configuration->getApiKey());
×
NEW
68
        $config = (new GenerationConfig())
×
NEW
69
            ->withCandidateCount($configuration->getNumberOfCompletions())
×
NEW
70
            ->withTemperature($configuration->getTemperature())
×
NEW
71
            ->withMaxOutputTokens($configuration->getMaxTokens())
×
NEW
72
        ;
×
NEW
73
        $model = $client->generativeModel($configuration->getModel())
×
NEW
74
            ->withGenerationConfig($config)
×
NEW
75
        ;
×
76

NEW
77
        return new self($model, $configuration);
×
78
    }
79

NEW
80
    public function getSolution(ProblemSolving\Problem\Problem $problem): ProblemSolving\Solution\Solution
×
81
    {
NEW
82
        if ($this->configuration->getApiKey() === null) {
×
NEW
83
            throw Exception\ApiKeyMissingException::create();
×
84
        }
85

86
        try {
NEW
87
            $response = $this->model->generateContent(new Resources\Parts\TextPart($problem->getPrompt()));
×
NEW
88
        } catch (\Exception $exception) {
×
NEW
89
            throw Exception\UnableToSolveException::create($problem, $exception);
×
90
        }
91

NEW
92
        return ProblemSolving\Solution\Solution::fromGeminiResponse($response, $this->modelName, $problem->getPrompt());
×
93
    }
94

NEW
95
    public function getStreamedSolution(ProblemSolving\Problem\Problem $problem): \Traversable
×
96
    {
97
        // Store all responses in array to merge them during streaming
NEW
98
        $responses = [];
×
99

NEW
100
        $fiber = new \Fiber(
×
NEW
101
            function () use ($problem) {
×
102
                // Suspend immediately to receive first response in while loop
NEW
103
                \Fiber::suspend();
×
104

NEW
105
                $this->model->generateContentStream(
×
NEW
106
                    static function (Responses\GenerateContentResponse $response) {
×
NEW
107
                        \Fiber::suspend($response);
×
NEW
108
                    },
×
NEW
109
                    [
×
NEW
110
                        new Resources\Parts\TextPart($problem->getPrompt()),
×
NEW
111
                    ],
×
NEW
112
                );
×
NEW
113
            },
×
NEW
114
        );
×
NEW
115
        $fiber->start();
×
116

117
        // Loop over each streamed response
NEW
118
        while (!$fiber->isTerminated()) {
×
119
            /** @var Responses\GenerateContentResponse|null $response */
NEW
120
            $response = $fiber->resume();
×
121

NEW
122
            if ($response !== null) {
×
NEW
123
                foreach ($response->candidates as $candidate) {
×
NEW
124
                    $completionResponse = ProblemSolving\Solution\Model\CompletionResponse::fromGeminiCandidate($candidate);
×
125

126
                    // Merge previous responses with currently streamed solution candidate
NEW
127
                    if (isset($responses[$candidate->index])) {
×
NEW
128
                        $completionResponse = $responses[$candidate->index]->merge($completionResponse);
×
129
                    }
130

NEW
131
                    $responses[$candidate->index] = $completionResponse;
×
132
                }
133

134
                // Yield solution with merged responses
NEW
135
                yield new ProblemSolving\Solution\Solution($responses, $this->modelName, $problem->getPrompt());
×
136
            }
137
        }
138
    }
139

NEW
140
    public function canBeUsed(\Throwable $exception): bool
×
141
    {
NEW
142
        return !\in_array($exception->getCode(), $this->configuration->getIgnoredCodes(), true);
×
143
    }
144

NEW
145
    public function isCacheable(): bool
×
146
    {
NEW
147
        return true;
×
148
    }
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc