Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
50.00% |
21 / 42 |
|
20.00% |
1 / 5 |
CRAP | |
0.00% |
0 / 1 |
AiInstructor | |
50.00% |
21 / 42 |
|
20.00% |
1 / 5 |
16.00 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
instruct | |
90.91% |
20 / 22 |
|
0.00% |
0 / 1 |
4.01 | |||
createForCustomConfig | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
createForOllama | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
createForOpenAi | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | namespace Apie\AiInstructor; |
3 | |
4 | use Apie\Core\Context\ApieContext; |
5 | use Apie\Core\ValueObjects\NonEmptyString; |
6 | use Apie\SchemaGenerator\ComponentsBuilderFactory; |
7 | use Apie\SchemaGenerator\SchemaGenerator; |
8 | use Apie\Serializer\Serializer; |
9 | use Apie\TypeConverter\ReflectionTypeFactory; |
10 | use ReflectionNamedType; |
11 | use ReflectionUnionType; |
12 | use SensitiveParameter; |
13 | use Symfony\Component\HttpClient\HttpClient; |
14 | |
15 | final class AiInstructor |
16 | { |
17 | public function __construct( |
18 | private readonly SchemaGenerator $schemaGenerator, |
19 | private readonly Serializer $serializer, |
20 | private readonly AiClient $aiClient |
21 | ) { |
22 | } |
23 | |
24 | public function instruct( |
25 | ReflectionNamedType|ReflectionUnionType|string $type, |
26 | NonEmptyString|string $model, |
27 | string $systemMessage, |
28 | string $prompt |
29 | ) { |
30 | if (is_string($type)) { |
31 | $type = ReflectionTypeFactory::createReflectionType($type); |
32 | } |
33 | if (is_string($model)) { |
34 | $model = NonEmptyString::fromNative($model); |
35 | } |
36 | $schema = $this->schemaGenerator->createSchema((string) $type); |
37 | $response = $this->aiClient->ask( |
38 | $systemMessage, |
39 | $prompt, |
40 | $schema, |
41 | $model |
42 | ); |
43 | try { |
44 | return $this->serializer->denormalizeNewObject( |
45 | json_decode($response, true), |
46 | (string) $type, |
47 | new ApieContext() |
48 | ); |
49 | } catch (\Exception $exception) { |
50 | throw new \LogicException( |
51 | "I could not map the AI response '" . $response . "' to '" . ((string) $type) . "', error: '" . $exception->getMessage() . '"', |
52 | 0, |
53 | $exception |
54 | ); |
55 | } |
56 | } |
57 | |
58 | public static function createForCustomConfig(#[SensitiveParameter] string $apiKey, string $baseUrl): self |
59 | { |
60 | return new self( |
61 | new SchemaGenerator(ComponentsBuilderFactory::createComponentsBuilderFactory()), |
62 | Serializer::create(), |
63 | AiClient::create( |
64 | HttpClient::create([ |
65 | 'max_redirects' => 7, |
66 | ]), |
67 | $baseUrl, |
68 | $apiKey |
69 | ) |
70 | ); |
71 | } |
72 | |
73 | public static function createForOllama(string $baseUrl = 'http://localhost:11434/'): self |
74 | { |
75 | return self::createForCustomConfig( |
76 | 'IGNORED', |
77 | $baseUrl, |
78 | ); |
79 | } |
80 | |
81 | public static function createForOpenAi(#[SensitiveParameter] string $apiSecret): self |
82 | { |
83 | return self::createForCustomConfig( |
84 | $apiSecret, |
85 | 'https://api.openai.com/v1', |
86 | ); |
87 | } |
88 | } |