@@ -155,6 +155,83 @@ public void VerifyToFunctionCallContent()
155
155
Assert . NotNull ( content . Arguments ) ;
156
156
}
157
157
158
+ /// <summary>
159
+ /// Verify that ReasoningResponseItem with SummaryParts generates ReasoningContent correctly.
160
+ /// </summary>
161
+ [ Fact ]
162
+ public void VerifyToChatMessageContentWithReasoningResponseItem ( )
163
+ {
164
+ // Arrange
165
+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Let me think about this step by step..." ) ;
166
+
167
+ // Act
168
+ ChatMessageContent ? chatMessageContent = reasoningResponseItem . ToChatMessageContent ( ) ;
169
+
170
+ // Assert
171
+ Assert . NotNull ( chatMessageContent ) ;
172
+ Assert . Equal ( AuthorRole . Assistant , chatMessageContent . Role ) ;
173
+ Assert . Single ( chatMessageContent . Items ) ;
174
+
175
+ var reasoningContent = chatMessageContent . Items [ 0 ] as ReasoningContent ;
176
+ Assert . NotNull ( reasoningContent ) ;
177
+ Assert . Equal ( "Let me think about this step by step..." , reasoningContent . Text ) ;
178
+ }
179
+
180
+ /// <summary>
181
+ /// Verify that ReasoningResponseItem converts to correct ChatMessageContentItemCollection with ReasoningContent.
182
+ /// </summary>
183
+ [ Fact ]
184
+ public void VerifyToChatMessageContentItemCollectionWithReasoningResponseItem ( )
185
+ {
186
+ // Arrange
187
+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Analyzing the problem..." ) ;
188
+
189
+ // Act
190
+ ChatMessageContentItemCollection collection = reasoningResponseItem . ToChatMessageContentItemCollection ( ) ;
191
+
192
+ // Assert
193
+ Assert . NotNull ( collection ) ;
194
+ Assert . Single ( collection ) ;
195
+ Assert . IsType < ReasoningContent > ( collection [ 0 ] ) ;
196
+
197
+ var reasoningContent = collection [ 0 ] as ReasoningContent ;
198
+ Assert . Equal ( "Analyzing the problem..." , reasoningContent ? . Text ) ;
199
+ }
200
+
201
+ /// <summary>
202
+ /// Verify that OpenAIResponse with both ReasoningResponseItem and MessageResponseItem generates correct content types.
203
+ /// </summary>
204
+ [ Fact ]
205
+ public void VerifyToChatMessageContentWithMixedResponseItems ( )
206
+ {
207
+ // Arrange
208
+ var reasoningResponseItem = this . CreateReasoningResponseItem ( "Thinking about the answer..." ) ;
209
+ var messageResponseItem = ResponseItem . CreateAssistantMessageItem ( "Here is my response." ) ;
210
+
211
+ OpenAIResponse mockResponse = this . CreateMockOpenAIResponse ( "gpt-4o-mini" ,
212
+ [
213
+ reasoningResponseItem ,
214
+ messageResponseItem
215
+ ] ) ;
216
+
217
+ // Act
218
+ ChatMessageContent chatMessageContent = mockResponse . ToChatMessageContent ( ) ;
219
+
220
+ // Assert
221
+ Assert . NotNull ( chatMessageContent ) ;
222
+ Assert . Equal ( 2 , chatMessageContent . Items . Count ) ;
223
+
224
+ // First item should be ReasoningContent
225
+ Assert . IsType < ReasoningContent > ( chatMessageContent . Items [ 0 ] ) ;
226
+ var reasoningContent = chatMessageContent . Items [ 0 ] as ReasoningContent ;
227
+ Assert . Equal ( "Thinking about the answer..." , reasoningContent ? . Text ) ;
228
+
229
+ // Second item should be TextContent
230
+ Assert . IsType < TextContent > ( chatMessageContent . Items [ 1 ] ) ;
231
+ var textContent = chatMessageContent . Items [ 1 ] as TextContent ;
232
+ Assert . Equal ( "Here is my response." , textContent ? . Text ) ;
233
+ }
234
+
158
235
#region private
159
236
private OpenAIResponse CreateMockOpenAIResponse ( string model , IEnumerable < ResponseItem > outputItems )
160
237
{
@@ -250,5 +327,112 @@ private OpenAIResponse CreateMockOpenAIResponse(string id, DateTimeOffset create
250
327
throw new InvalidOperationException ( "Constructor not found." ) ;
251
328
}
252
329
330
+ private ReasoningResponseItem CreateReasoningResponseItem ( string ? reasoningText = null , IReadOnlyList < ReasoningSummaryPart > ? summaryParts = null )
331
+ {
332
+ Type reasoningResponseItemType = typeof ( ReasoningResponseItem ) ;
333
+ Type reasoningSummaryTextPartType = typeof ( ReasoningSummaryTextPart ) ;
334
+
335
+ // If reasoningText is provided and summaryParts is not, create summaryParts with the text
336
+ if ( reasoningText != null && summaryParts == null )
337
+ {
338
+ // Try to find any public static factory method or constructor that can create ReasoningSummaryTextPart
339
+ var createTextPartMethod = typeof ( ReasoningSummaryPart ) . GetMethod (
340
+ "CreateTextPart" ,
341
+ BindingFlags . Static | BindingFlags . Public ,
342
+ null ,
343
+ [ typeof ( string ) ] ,
344
+ null ) ;
345
+
346
+ if ( createTextPartMethod != null )
347
+ {
348
+ var textPart = createTextPartMethod . Invoke ( null , [ reasoningText ] ) as ReasoningSummaryTextPart ;
349
+ summaryParts = textPart != null ? new List < ReasoningSummaryPart > { textPart } : new List < ReasoningSummaryPart > ( ) ;
350
+ }
351
+ else
352
+ {
353
+ // Try to find constructor - search for all constructors
354
+ var textPartConstructors = reasoningSummaryTextPartType . GetConstructors (
355
+ BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public ) ;
356
+
357
+ ConstructorInfo ? textPartConstructor = null ;
358
+ foreach ( var ctor in textPartConstructors )
359
+ {
360
+ var parameters = ctor . GetParameters ( ) ;
361
+ if ( parameters . Length >= 1 && parameters [ 0 ] . ParameterType == typeof ( string ) )
362
+ {
363
+ textPartConstructor = ctor ;
364
+ break ;
365
+ }
366
+ }
367
+
368
+ if ( textPartConstructor != null )
369
+ {
370
+ var ctorParams = textPartConstructor . GetParameters ( ) ;
371
+ var args = new object ? [ ctorParams . Length ] ;
372
+ args [ 0 ] = reasoningText ;
373
+ // Fill in any additional parameters with null or default values
374
+ for ( int i = 1 ; i < ctorParams . Length ; i ++ )
375
+ {
376
+ args [ i ] = null ;
377
+ }
378
+
379
+ var textPart = textPartConstructor . Invoke ( args ) as ReasoningSummaryTextPart ;
380
+ summaryParts = textPart != null ? new List < ReasoningSummaryPart > { textPart } : new List < ReasoningSummaryPart > ( ) ;
381
+ }
382
+ else
383
+ {
384
+ throw new InvalidOperationException ( "Could not find a way to create ReasoningSummaryTextPart." ) ;
385
+ }
386
+ }
387
+ }
388
+
389
+ // Convert null summaryParts to empty list for method calls
390
+ var partsToPass = summaryParts ?? new List < ReasoningSummaryPart > ( ) ;
391
+
392
+ // Try to find a static factory method first
393
+ var createReasoningItemMethod = typeof ( ResponseItem ) . GetMethod (
394
+ "CreateReasoningItem" ,
395
+ BindingFlags . Static | BindingFlags . Public ,
396
+ null ,
397
+ [ typeof ( IEnumerable < ReasoningSummaryPart > ) ] ,
398
+ null ) ;
399
+
400
+ if ( createReasoningItemMethod != null )
401
+ {
402
+ return ( ReasoningResponseItem ) createReasoningItemMethod . Invoke ( null , [ partsToPass ] ) ! ;
403
+ }
404
+
405
+ // If no factory method, look for constructors
406
+ var constructors = reasoningResponseItemType . GetConstructors (
407
+ BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public ) ;
408
+
409
+ foreach ( var ctor in constructors )
410
+ {
411
+ var parameters = ctor . GetParameters ( ) ;
412
+
413
+ // Look for constructor that takes IReadOnlyList<ReasoningSummaryPart> or similar
414
+ if ( parameters . Length >= 1 )
415
+ {
416
+ var firstParamType = parameters [ 0 ] . ParameterType ;
417
+ if ( firstParamType . IsAssignableFrom ( typeof ( List < ReasoningSummaryPart > ) ) ||
418
+ firstParamType . IsAssignableFrom ( typeof ( IReadOnlyList < ReasoningSummaryPart > ) ) ||
419
+ firstParamType . IsAssignableFrom ( typeof ( IEnumerable < ReasoningSummaryPart > ) ) )
420
+ {
421
+ var args = new object ? [ parameters . Length ] ;
422
+ args [ 0 ] = partsToPass ;
423
+ // Fill in any additional parameters with null
424
+ for ( int i = 1 ; i < parameters . Length ; i ++ )
425
+ {
426
+ args [ i ] = null ;
427
+ }
428
+
429
+ return ( ReasoningResponseItem ) ctor . Invoke ( args ) ;
430
+ }
431
+ }
432
+ }
433
+
434
+ throw new InvalidOperationException ( "Constructor not found for ReasoningResponseItem." ) ;
435
+ }
436
+
253
437
#endregion
254
438
}
0 commit comments