1
+ <?php
2
+
3
+ declare (strict_types=1 );
4
+
5
+ namespace PhpLlm \LlmChain \Tests \Platform \Bridge \Albert ;
6
+
7
+ use PhpLlm \LlmChain \Platform \Bridge \Albert \EmbeddingsModelClient ;
8
+ use PhpLlm \LlmChain \Platform \Bridge \OpenAI \Embeddings ;
9
+ use PhpLlm \LlmChain \Platform \Bridge \OpenAI \GPT ;
10
+ use PhpLlm \LlmChain \Platform \Exception \InvalidArgumentException ;
11
+ use PHPUnit \Framework \Attributes \CoversClass ;
12
+ use PHPUnit \Framework \Attributes \DataProvider ;
13
+ use PHPUnit \Framework \Attributes \Small ;
14
+ use PHPUnit \Framework \Attributes \Test ;
15
+ use PHPUnit \Framework \TestCase ;
16
+ use Symfony \Component \HttpClient \MockHttpClient ;
17
+ use Symfony \Component \HttpClient \Response \JsonMockResponse ;
18
+
19
+ #[CoversClass(EmbeddingsModelClient::class)]
20
+ #[Small]
21
+ final class EmbeddingsModelClientTest extends TestCase
22
+ {
23
+ #[Test]
24
+ public function constructorThrowsExceptionForEmptyApiKey (): void
25
+ {
26
+ $ this ->expectException (InvalidArgumentException::class);
27
+ $ this ->expectExceptionMessage ('The API key must not be empty. ' );
28
+
29
+ new EmbeddingsModelClient (
30
+ new MockHttpClient (),
31
+ '' ,
32
+ 'https://albert.example.com/ '
33
+ );
34
+ }
35
+
36
+ #[Test]
37
+ public function constructorThrowsExceptionForEmptyBaseUrl (): void
38
+ {
39
+ $ this ->expectException (InvalidArgumentException::class);
40
+ $ this ->expectExceptionMessage ('The base URL must not be empty. ' );
41
+
42
+ new EmbeddingsModelClient (
43
+ new MockHttpClient (),
44
+ 'test-api-key ' ,
45
+ ''
46
+ );
47
+ }
48
+
49
+ #[Test]
50
+ public function supportsEmbeddingsModel (): void
51
+ {
52
+ $ client = new EmbeddingsModelClient (
53
+ new MockHttpClient (),
54
+ 'test-api-key ' ,
55
+ 'https://albert.example.com/ '
56
+ );
57
+
58
+ $ embeddingsModel = new Embeddings ('text-embedding-ada-002 ' );
59
+ self ::assertTrue ($ client ->supports ($ embeddingsModel ));
60
+ }
61
+
62
+ #[Test]
63
+ public function doesNotSupportNonEmbeddingsModel (): void
64
+ {
65
+ $ client = new EmbeddingsModelClient (
66
+ new MockHttpClient (),
67
+ 'test-api-key ' ,
68
+ 'https://albert.example.com/ '
69
+ );
70
+
71
+ $ gptModel = new GPT ('gpt-3.5-turbo ' );
72
+ self ::assertFalse ($ client ->supports ($ gptModel ));
73
+ }
74
+
75
+ #[Test]
76
+ #[DataProvider('requestDataProvider ' )]
77
+ public function requestSendsCorrectHttpRequest (array |string $ payload , array $ options , array |string $ expectedJson ): void
78
+ {
79
+ $ capturedRequest = null ;
80
+ $ httpClient = new MockHttpClient (function ($ method , $ url , $ options ) use (&$ capturedRequest ) {
81
+ $ capturedRequest = ['method ' => $ method , 'url ' => $ url , 'options ' => $ options ];
82
+ return new JsonMockResponse (['data ' => []]);
83
+ });
84
+
85
+ $ client = new EmbeddingsModelClient (
86
+ $ httpClient ,
87
+ 'test-api-key ' ,
88
+ 'https://albert.example.com/v1/ '
89
+ );
90
+
91
+ $ model = new Embeddings ('text-embedding-ada-002 ' );
92
+ $ response = $ client ->request ($ model , $ payload , $ options );
93
+
94
+ self ::assertNotNull ($ capturedRequest );
95
+ self ::assertSame ('POST ' , $ capturedRequest ['method ' ]);
96
+ self ::assertSame ('https://albert.example.com/v1/embeddings ' , $ capturedRequest ['url ' ]);
97
+ self ::assertArrayHasKey ('normalized_headers ' , $ capturedRequest ['options ' ]);
98
+ self ::assertArrayHasKey ('authorization ' , $ capturedRequest ['options ' ]['normalized_headers ' ]);
99
+ self ::assertStringContainsString ('Bearer test-api-key ' , $ capturedRequest ['options ' ]['normalized_headers ' ]['authorization ' ][0 ]);
100
+
101
+ // Check JSON body - it might be in 'body' after processing
102
+ if (isset ($ capturedRequest ['options ' ]['body ' ])) {
103
+ $ actualJson = json_decode ($ capturedRequest ['options ' ]['body ' ], true );
104
+ self ::assertEquals ($ expectedJson , $ actualJson );
105
+ } else {
106
+ self ::assertSame ($ expectedJson , $ capturedRequest ['options ' ]['json ' ]);
107
+ }
108
+ }
109
+
110
+ public static function requestDataProvider (): \Iterator
111
+ {
112
+ yield 'with array payload and no options ' => [
113
+ 'payload ' => ['input ' => 'test text ' , 'model ' => 'text-embedding-ada-002 ' ],
114
+ 'options ' => [],
115
+ 'expectedJson ' => ['input ' => 'test text ' , 'model ' => 'text-embedding-ada-002 ' ],
116
+ ];
117
+
118
+ yield 'with string payload and no options ' => [
119
+ 'payload ' => 'test text ' ,
120
+ 'options ' => [],
121
+ 'expectedJson ' => 'test text ' ,
122
+ ];
123
+
124
+ yield 'with array payload and options ' => [
125
+ 'payload ' => ['input ' => 'test text ' , 'model ' => 'text-embedding-ada-002 ' ],
126
+ 'options ' => ['dimensions ' => 1536 ],
127
+ 'expectedJson ' => ['dimensions ' => 1536 , 'input ' => 'test text ' , 'model ' => 'text-embedding-ada-002 ' ],
128
+ ];
129
+
130
+ yield 'options override payload values ' => [
131
+ 'payload ' => ['input ' => 'test text ' , 'model ' => 'text-embedding-ada-002 ' ],
132
+ 'options ' => ['model ' => 'text-embedding-3-small ' ],
133
+ 'expectedJson ' => ['model ' => 'text-embedding-3-small ' , 'input ' => 'test text ' ],
134
+ ];
135
+ }
136
+
137
+ #[Test]
138
+ public function requestHandlesBaseUrlWithoutTrailingSlash (): void
139
+ {
140
+ $ capturedUrl = null ;
141
+ $ httpClient = new MockHttpClient (function ($ method , $ url ) use (&$ capturedUrl ) {
142
+ $ capturedUrl = $ url ;
143
+ return new JsonMockResponse (['data ' => []]);
144
+ });
145
+
146
+ $ client = new EmbeddingsModelClient (
147
+ $ httpClient ,
148
+ 'test-api-key ' ,
149
+ 'https://albert.example.com/v1 '
150
+ );
151
+
152
+ $ model = new Embeddings ('text-embedding-ada-002 ' );
153
+ $ client ->request ($ model , ['input ' => 'test ' ]);
154
+
155
+ self ::assertSame ('https://albert.example.com/v1/embeddings ' , $ capturedUrl );
156
+ }
157
+
158
+ #[Test]
159
+ public function requestHandlesBaseUrlWithTrailingSlash (): void
160
+ {
161
+ $ capturedUrl = null ;
162
+ $ httpClient = new MockHttpClient (function ($ method , $ url ) use (&$ capturedUrl ) {
163
+ $ capturedUrl = $ url ;
164
+ return new JsonMockResponse (['data ' => []]);
165
+ });
166
+
167
+ $ client = new EmbeddingsModelClient (
168
+ $ httpClient ,
169
+ 'test-api-key ' ,
170
+ 'https://albert.example.com/v1/ '
171
+ );
172
+
173
+ $ model = new Embeddings ('text-embedding-ada-002 ' );
174
+ $ client ->request ($ model , ['input ' => 'test ' ]);
175
+
176
+ self ::assertSame ('https://albert.example.com/v1/embeddings ' , $ capturedUrl );
177
+ }
178
+ }
0 commit comments