@@ -199,6 +199,157 @@ describe('OpenAIProvider', () => {
199199 } ) ;
200200 } ) ;
201201
202+ describe ( 'invokeStructuredModel' , ( ) => {
203+ it ( 'invokes OpenAI with structured output and returns parsed response' , async ( ) => {
204+ const mockResponse = {
205+ choices : [
206+ {
207+ message : {
208+ content : '{"name": "John", "age": 30, "city": "New York"}' ,
209+ } ,
210+ } ,
211+ ] ,
212+ usage : {
213+ prompt_tokens : 20 ,
214+ completion_tokens : 10 ,
215+ total_tokens : 30 ,
216+ } ,
217+ } ;
218+
219+ ( mockOpenAI . chat . completions . create as jest . Mock ) . mockResolvedValue ( mockResponse as any ) ;
220+
221+ const messages = [ { role : 'user' as const , content : 'Tell me about a person' } ] ;
222+ const responseStructure = {
223+ type : 'object' ,
224+ properties : {
225+ name : { type : 'string' } ,
226+ age : { type : 'number' } ,
227+ city : { type : 'string' } ,
228+ } ,
229+ required : [ 'name' , 'age' , 'city' ] ,
230+ } ;
231+
232+ const result = await provider . invokeStructuredModel ( messages , responseStructure ) ;
233+
234+ expect ( mockOpenAI . chat . completions . create ) . toHaveBeenCalledWith ( {
235+ model : 'gpt-3.5-turbo' ,
236+ messages : [ { role : 'user' , content : 'Tell me about a person' } ] ,
237+ response_format : {
238+ type : 'json_schema' ,
239+ json_schema : {
240+ name : 'structured_output' ,
241+ schema : responseStructure ,
242+ strict : true ,
243+ } ,
244+ } ,
245+ } ) ;
246+
247+ expect ( result ) . toEqual ( {
248+ data : {
249+ name : 'John' ,
250+ age : 30 ,
251+ city : 'New York' ,
252+ } ,
253+ rawResponse : '{"name": "John", "age": 30, "city": "New York"}' ,
254+ metrics : {
255+ success : true ,
256+ usage : {
257+ total : 30 ,
258+ input : 20 ,
259+ output : 10 ,
260+ } ,
261+ } ,
262+ } ) ;
263+ } ) ;
264+
265+ it ( 'returns unsuccessful response when no content in structured response' , async ( ) => {
266+ const mockResponse = {
267+ choices : [
268+ {
269+ message : {
270+ // content is missing
271+ } ,
272+ } ,
273+ ] ,
274+ } ;
275+
276+ ( mockOpenAI . chat . completions . create as jest . Mock ) . mockResolvedValue ( mockResponse as any ) ;
277+
278+ const messages = [ { role : 'user' as const , content : 'Tell me about a person' } ] ;
279+ const responseStructure = { type : 'object' } ;
280+
281+ const result = await provider . invokeStructuredModel ( messages , responseStructure ) ;
282+
283+ expect ( result ) . toEqual ( {
284+ data : { } ,
285+ rawResponse : '' ,
286+ metrics : {
287+ success : false ,
288+ usage : undefined ,
289+ } ,
290+ } ) ;
291+ } ) ;
292+
293+ it ( 'handles JSON parsing errors gracefully' , async ( ) => {
294+ const mockResponse = {
295+ choices : [
296+ {
297+ message : {
298+ content : 'invalid json content' ,
299+ } ,
300+ } ,
301+ ] ,
302+ usage : {
303+ prompt_tokens : 10 ,
304+ completion_tokens : 5 ,
305+ total_tokens : 15 ,
306+ } ,
307+ } ;
308+
309+ ( mockOpenAI . chat . completions . create as jest . Mock ) . mockResolvedValue ( mockResponse as any ) ;
310+
311+ const messages = [ { role : 'user' as const , content : 'Tell me about a person' } ] ;
312+ const responseStructure = { type : 'object' } ;
313+
314+ const result = await provider . invokeStructuredModel ( messages , responseStructure ) ;
315+
316+ expect ( result ) . toEqual ( {
317+ data : { } ,
318+ rawResponse : 'invalid json content' ,
319+ metrics : {
320+ success : false ,
321+ usage : {
322+ total : 15 ,
323+ input : 10 ,
324+ output : 5 ,
325+ } ,
326+ } ,
327+ } ) ;
328+ } ) ;
329+
330+ it ( 'handles empty choices array in structured response' , async ( ) => {
331+ const mockResponse = {
332+ choices : [ ] ,
333+ } ;
334+
335+ ( mockOpenAI . chat . completions . create as jest . Mock ) . mockResolvedValue ( mockResponse as any ) ;
336+
337+ const messages = [ { role : 'user' as const , content : 'Tell me about a person' } ] ;
338+ const responseStructure = { type : 'object' } ;
339+
340+ const result = await provider . invokeStructuredModel ( messages , responseStructure ) ;
341+
342+ expect ( result ) . toEqual ( {
343+ data : { } ,
344+ rawResponse : '' ,
345+ metrics : {
346+ success : false ,
347+ usage : undefined ,
348+ } ,
349+ } ) ;
350+ } ) ;
351+ } ) ;
352+
202353 describe ( 'getClient' , ( ) => {
203354 it ( 'returns the underlying OpenAI client' , ( ) => {
204355 const client = provider . getClient ( ) ;
0 commit comments