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

eliashaeussler / typo3-solver / 13271925856

11 Feb 2025 08:29PM UTC coverage: 81.979% (-6.2%) from 88.153%
13271925856

Pull #295

github

web-flow
Merge 140b2a87d into dc3a40050
Pull Request #295: [FEATURE] Add Gemini as additional solution provider

8 of 90 new or added lines in 7 files covered. (8.89%)

1 existing line in 1 file now uncovered.

878 of 1071 relevant lines covered (81.98%)

2.18 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\GenerationConfig;
31
use GeminiAPI\GenerativeModel;
32
use GeminiAPI\Resources;
33
use GeminiAPI\Responses;
34

35
/**
36
 * GeminiSolutionProvider
37
 *
38
 * @author Elias Häußler <elias@haeussler.dev>
39
 * @license GPL-2.0-or-later
40
 */
41
final class GeminiSolutionProvider implements StreamedSolutionProvider
42
{
NEW
43
    public function __construct(
×
44
        private readonly GenerativeModel $model,
45
        private readonly Configuration\Configuration $configuration,
NEW
46
    ) {}
×
47

48
    /**
49
     * @throws Exception\ApiKeyMissingException
50
     */
NEW
51
    public static function create(Client $client = null): static
×
52
    {
NEW
53
        $configuration = new Configuration\Configuration();
×
54

NEW
55
        if ($configuration->getApiKey() === null) {
×
NEW
56
            throw Exception\ApiKeyMissingException::create();
×
57
        }
58

NEW
59
        $client ??= new Client($configuration->getApiKey());
×
NEW
60
        $config = (new GenerationConfig())
×
NEW
61
            ->withCandidateCount($configuration->getNumberOfCompletions())
×
NEW
62
            ->withTemperature($configuration->getTemperature())
×
NEW
63
            ->withMaxOutputTokens($configuration->getMaxTokens())
×
NEW
64
        ;
×
NEW
65
        $model = $client->generativeModel($configuration->getModel())
×
NEW
66
            ->withGenerationConfig($config)
×
NEW
67
        ;
×
68

NEW
69
        return new self($model, $configuration);
×
70
    }
71

NEW
72
    public function getSolution(ProblemSolving\Problem\Problem $problem): ProblemSolving\Solution\Solution
×
73
    {
NEW
74
        if ($this->configuration->getApiKey() === null) {
×
NEW
75
            throw Exception\ApiKeyMissingException::create();
×
76
        }
77

78
        try {
NEW
79
            $response = $this->model->generateContent(new Resources\Parts\TextPart($problem->getPrompt()));
×
NEW
80
        } catch (\Exception $exception) {
×
NEW
81
            throw Exception\UnableToSolveException::create($problem, $exception);
×
82
        }
83

NEW
84
        return ProblemSolving\Solution\Solution::fromGeminiResponse(
×
NEW
85
            $response,
×
NEW
86
            $this->model->modelName,
×
NEW
87
            $problem->getPrompt(),
×
NEW
88
        );
×
89
    }
90

NEW
91
    public function getStreamedSolution(ProblemSolving\Problem\Problem $problem): \Traversable
×
92
    {
93
        // Store all responses in array to merge them during streaming
NEW
94
        $responses = [];
×
95

NEW
96
        $fiber = new \Fiber(
×
NEW
97
            function () use ($problem) {
×
98
                // Suspend immediately to receive first response in while loop
NEW
99
                \Fiber::suspend();
×
100

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

113
        // Loop over each streamed response
NEW
114
        while (!$fiber->isTerminated()) {
×
115
            /** @var Responses\GenerateContentResponse|null $response */
NEW
116
            $response = $fiber->resume();
×
117

NEW
118
            if ($response !== null) {
×
NEW
119
                foreach ($response->candidates as $candidate) {
×
NEW
120
                    $completionResponse = ProblemSolving\Solution\Model\CompletionResponse::fromGeminiCandidate($candidate);
×
121

122
                    // Merge previous responses with currently streamed solution candidate
NEW
123
                    if (isset($responses[$candidate->index])) {
×
NEW
124
                        $completionResponse = $responses[$candidate->index]->merge($completionResponse);
×
125
                    }
126

NEW
127
                    $responses[$candidate->index] = $completionResponse;
×
128
                }
129

130
                // Yield solution with merged responses
NEW
131
                yield new ProblemSolving\Solution\Solution(
×
NEW
132
                    $responses,
×
NEW
133
                    $this->model->modelName,
×
NEW
134
                    $problem->getPrompt(),
×
NEW
135
                );
×
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