|
1 | | -classdef (Abstract) hopenAIChat < matlab.unittest.TestCase |
| 1 | +classdef (Abstract) hopenAIChat < hstructuredOutput |
2 | 2 | % Tests for OpenAI-based chats (openAIChat, azureChat) |
3 | 3 |
|
4 | 4 | % Copyright 2023-2024 The MathWorks, Inc. |
|
17 | 17 | constructor |
18 | 18 | defaultModel |
19 | 19 | visionModel |
20 | | - structuredModel |
21 | | - noStructuredOutputModel |
22 | 20 | end |
23 | 21 |
|
24 | 22 | methods(Test) |
@@ -195,66 +193,6 @@ function generateOverridesProperties(testCase) |
195 | 193 | testCase.verifyThat(text, EndsWithSubstring("3, ")); |
196 | 194 | end |
197 | 195 |
|
198 | | - function generateWithStructuredOutput(testCase) |
199 | | - import matlab.unittest.constraints.IsEqualTo |
200 | | - import matlab.unittest.constraints.StartsWithSubstring |
201 | | - res = generate(testCase.structuredModel,"Which animal produces honey?",... |
202 | | - ResponseFormat = struct(commonName = "dog", scientificName = "Canis familiaris")); |
203 | | - testCase.assertClass(res,"struct"); |
204 | | - testCase.verifySize(fieldnames(res),[2,1]); |
205 | | - testCase.verifyThat(res.commonName, IsEqualTo("Honeybee") | IsEqualTo("Honey bee") | IsEqualTo("Honey Bee")); |
206 | | - testCase.verifyThat(res.scientificName, StartsWithSubstring("Apis")); |
207 | | - end |
208 | | - |
209 | | - function generateListWithStructuredOutput(testCase) |
210 | | - prototype = struct("plantName",{"appletree","pear"}, ... |
211 | | - "fruit",{"apple","pear"}, ... |
212 | | - "edible",[true,true], ... |
213 | | - "ignore", missing); |
214 | | - res = generate(testCase.structuredModel,"What is harvested in August?", ResponseFormat = prototype); |
215 | | - testCase.verifyCompatibleStructs(res, prototype); |
216 | | - end |
217 | | - |
218 | | - function generateWithNestedStructs(testCase) |
219 | | - stepsPrototype = struct("explanation",{"a","b"},"assumptions",{"a","b"}); |
220 | | - prototype = struct("steps",stepsPrototype,"final_answer","a"); |
221 | | - res = generate(testCase.structuredModel,"What is the positive root of x^2-2*x+1?", ... |
222 | | - ResponseFormat=prototype); |
223 | | - testCase.verifyCompatibleStructs(res,prototype); |
224 | | - end |
225 | | - |
226 | | - function incompleteJSONResponse(testCase) |
227 | | - country = ["USA";"UK"]; |
228 | | - capital = ["Washington, D.C.";"London"]; |
229 | | - population = [345716792;69203012]; |
230 | | - prototype = struct("country",country,"capital",capital,"population",population); |
231 | | - |
232 | | - testCase.verifyError(@() generate(testCase.structuredModel, ... |
233 | | - "What are the five largest countries whose English names" + ... |
234 | | - " start with the letter A?", ... |
235 | | - ResponseFormat = prototype, MaxNumTokens=3), "llms:apiReturnedIncompleteJSON"); |
236 | | - end |
237 | | - |
238 | | - function generateWithExplicitSchema(testCase) |
239 | | - import matlab.unittest.constraints.IsSameSetAs |
240 | | - schema = iGetSchema(); |
241 | | - |
242 | | - genUser = generate(testCase.structuredModel,"Create a sample user",ResponseFormat=schema); |
243 | | - genAddress = generate(testCase.structuredModel,"Create a sample address",ResponseFormat=schema); |
244 | | - |
245 | | - testCase.verifyClass(genUser,"string"); |
246 | | - genUserDecoded = jsondecode(genUser); |
247 | | - testCase.verifyClass(genUserDecoded.item,"struct"); |
248 | | - testCase.verifyThat(fieldnames(genUserDecoded.item),... |
249 | | - IsSameSetAs({'name','age'})); |
250 | | - |
251 | | - testCase.verifyClass(genAddress,"string"); |
252 | | - genAddressDecoded = jsondecode(genAddress); |
253 | | - testCase.verifyClass(genAddressDecoded.item,"struct"); |
254 | | - testCase.verifyThat(fieldnames(genAddressDecoded.item),... |
255 | | - IsSameSetAs({'number','street','city'})); |
256 | | - end |
257 | | - |
258 | 196 | function invalidInputsGenerate(testCase, InvalidGenerateInput) |
259 | 197 | f = openAIFunction("validfunction"); |
260 | 198 | chat = testCase.constructor(Tools=f, APIKey="this-is-not-a-real-key"); |
@@ -321,89 +259,4 @@ function keyNotFound(testCase) |
321 | 259 | testCase.verifyError(testCase.constructor, "llms:keyMustBeSpecified"); |
322 | 260 | end |
323 | 261 | end |
324 | | - |
325 | | - methods |
326 | | - function verifyCompatibleStructs(testCase,data,prototype) |
327 | | - import matlab.unittest.constraints.IsSameSetAs |
328 | | - testCase.assertClass(data,"struct"); |
329 | | - if ~isscalar(data) |
330 | | - arrayfun(@(d) testCase.verifyCompatibleStructs(d,prototype), data); |
331 | | - return |
332 | | - end |
333 | | - testCase.assertClass(prototype,"struct"); |
334 | | - if ~isscalar(prototype) |
335 | | - prototype = prototype(1); |
336 | | - end |
337 | | - testCase.assertThat(fieldnames(data),IsSameSetAs(fieldnames(prototype))); |
338 | | - for name = fieldnames(data).' |
339 | | - field = name{1}; |
340 | | - testCase.verifyClass(data.(field),class(prototype.(field))); |
341 | | - if isstruct(data.(field)) |
342 | | - testCase.verifyCompatibleStructs(data.(field),prototype.(field)); |
343 | | - end |
344 | | - end |
345 | | - end |
346 | | - end |
347 | | -end |
348 | | - |
349 | | -function str = iGetSchema() |
350 | | -% an example from https://platform.openai.com/docs/guides/structured-outputs/supported-schemas |
351 | | -str = string(join({ |
352 | | - '{' |
353 | | - ' "type": "object",' |
354 | | - ' "properties": {' |
355 | | - ' "item": {' |
356 | | - ' "anyOf": [' |
357 | | - ' {' |
358 | | - ' "type": "object",' |
359 | | - ' "description": "The user object to insert into the database",' |
360 | | - ' "properties": {' |
361 | | - ' "name": {' |
362 | | - ' "type": "string",' |
363 | | - ' "description": "The name of the user"' |
364 | | - ' },' |
365 | | - ' "age": {' |
366 | | - ' "type": "number",' |
367 | | - ' "description": "The age of the user"' |
368 | | - ' }' |
369 | | - ' },' |
370 | | - ' "additionalProperties": false,' |
371 | | - ' "required": [' |
372 | | - ' "name",' |
373 | | - ' "age"' |
374 | | - ' ]' |
375 | | - ' },' |
376 | | - ' {' |
377 | | - ' "type": "object",' |
378 | | - ' "description": "The address object to insert into the database",' |
379 | | - ' "properties": {' |
380 | | - ' "number": {' |
381 | | - ' "type": "string",' |
382 | | - ' "description": "The number of the address. Eg. for 123 main st, this would be 123"' |
383 | | - ' },' |
384 | | - ' "street": {' |
385 | | - ' "type": "string",' |
386 | | - ' "description": "The street name. Eg. for 123 main st, this would be main st"' |
387 | | - ' },' |
388 | | - ' "city": {' |
389 | | - ' "type": "string",' |
390 | | - ' "description": "The city of the address"' |
391 | | - ' }' |
392 | | - ' },' |
393 | | - ' "additionalProperties": false,' |
394 | | - ' "required": [' |
395 | | - ' "number",' |
396 | | - ' "street",' |
397 | | - ' "city"' |
398 | | - ' ]' |
399 | | - ' }' |
400 | | - ' ]' |
401 | | - ' }' |
402 | | - ' },' |
403 | | - ' "additionalProperties": false,' |
404 | | - ' "required": [' |
405 | | - ' "item"' |
406 | | - ' ]' |
407 | | - '}' |
408 | | -}, newline)); |
409 | 262 | end |
0 commit comments