@@ -46,15 +46,15 @@ internal sealed partial class BedrockChatClient : IChatClient
46
46
/// Initializes a new instance of the <see cref="BedrockChatClient"/> class.
47
47
/// </summary>
48
48
/// <param name="runtime">The <see cref="IAmazonBedrockRuntime"/> instance to wrap.</param>
49
- /// <param name="modelId ">Model ID to use as the default when no model ID is specified in a request.</param>
50
- public BedrockChatClient ( IAmazonBedrockRuntime runtime , string ? modelId )
49
+ /// <param name="defaultModelId ">Model ID to use as the default when no model ID is specified in a request.</param>
50
+ public BedrockChatClient ( IAmazonBedrockRuntime runtime , string ? defaultModelId )
51
51
{
52
52
Debug . Assert ( runtime is not null ) ;
53
53
54
54
_runtime = runtime ! ;
55
- _modelId = modelId ;
55
+ _modelId = defaultModelId ;
56
56
57
- _metadata = new ( AmazonBedrockRuntimeExtensions . ProviderName , defaultModelId : modelId ) ;
57
+ _metadata = new ( AmazonBedrockRuntimeExtensions . ProviderName , defaultModelId : defaultModelId ) ;
58
58
}
59
59
60
60
public void Dispose ( )
@@ -85,7 +85,9 @@ public async Task<ChatResponse> GetResponseAsync(
85
85
86
86
ChatMessage result = new ( )
87
87
{
88
+ RawRepresentation = response . Output ? . Message ,
88
89
Role = ChatRole . Assistant ,
90
+ MessageId = Guid . NewGuid ( ) . ToString ( "N" ) ,
89
91
} ;
90
92
91
93
if ( response . Output ? . Message ? . Content is { } contents )
@@ -94,27 +96,44 @@ public async Task<ChatResponse> GetResponseAsync(
94
96
{
95
97
if ( content . Text is string text )
96
98
{
97
- result . Contents . Add ( new TextContent ( text ) ) ;
99
+ result . Contents . Add ( new TextContent ( text ) { RawRepresentation = content } ) ;
100
+ }
101
+
102
+ if ( content . ReasoningContent is { ReasoningText . Text : not null } reasoningContent )
103
+ {
104
+ TextReasoningContent trc = new ( reasoningContent . ReasoningText . Text ) { RawRepresentation = content } ;
105
+
106
+ if ( reasoningContent . ReasoningText . Signature is string signature )
107
+ {
108
+ ( trc . AdditionalProperties ??= [ ] ) [ nameof ( reasoningContent . ReasoningText . Signature ) ] = signature ;
109
+ }
110
+
111
+ if ( reasoningContent . RedactedContent is { } redactedContent )
112
+ {
113
+ ( trc . AdditionalProperties ??= [ ] ) [ nameof ( reasoningContent . RedactedContent ) ] = redactedContent . ToArray ( ) ;
114
+ }
115
+
116
+ result . Contents . Add ( trc ) ;
98
117
}
99
118
100
119
if ( content . Image is { Source . Bytes : { } imageBytes , Format : { } imageFormat } )
101
120
{
102
- result . Contents . Add ( new DataContent ( imageBytes . ToArray ( ) , GetMimeType ( imageFormat ) ) ) ;
121
+ result . Contents . Add ( new DataContent ( imageBytes . ToArray ( ) , GetMimeType ( imageFormat ) ) { RawRepresentation = content } ) ;
103
122
}
104
123
105
124
if ( content . Video is { Source . Bytes : { } videoBytes , Format : { } videoFormat } )
106
125
{
107
- result . Contents . Add ( new DataContent ( videoBytes . ToArray ( ) , GetMimeType ( videoFormat ) ) ) ;
126
+ result . Contents . Add ( new DataContent ( videoBytes . ToArray ( ) , GetMimeType ( videoFormat ) ) { RawRepresentation = content } ) ;
108
127
}
109
128
110
129
if ( content . Document is { Source . Bytes : { } documentBytes , Format : { } documentFormat } )
111
130
{
112
- result . Contents . Add ( new DataContent ( documentBytes . ToArray ( ) , GetMimeType ( documentFormat ) ) ) ;
131
+ result . Contents . Add ( new DataContent ( documentBytes . ToArray ( ) , GetMimeType ( documentFormat ) ) { RawRepresentation = content } ) ;
113
132
}
114
133
115
134
if ( content . ToolUse is { } toolUse )
116
135
{
117
- result . Contents . Add ( new FunctionCallContent ( toolUse . ToolUseId , toolUse . Name , DocumentToDictionary ( toolUse . Input ) ) ) ;
136
+ result . Contents . Add ( new FunctionCallContent ( toolUse . ToolUseId , toolUse . Name , DocumentToDictionary ( toolUse . Input ) ) { RawRepresentation = content } ) ;
118
137
}
119
138
}
120
139
}
@@ -126,13 +145,11 @@ public async Task<ChatResponse> GetResponseAsync(
126
145
127
146
return new ( result )
128
147
{
148
+ CreatedAt = DateTimeOffset . UtcNow ,
129
149
FinishReason = response . StopReason is not null ? GetChatFinishReason ( response . StopReason ) : null ,
130
- Usage = response . Usage is TokenUsage usage ? new ( )
131
- {
132
- InputTokenCount = usage . InputTokens ,
133
- OutputTokenCount = usage . OutputTokens ,
134
- TotalTokenCount = usage . TotalTokens ,
135
- } : null ,
150
+ RawRepresentation = response ,
151
+ ResponseId = Guid . NewGuid ( ) . ToString ( "N" ) ,
152
+ Usage = response . Usage is TokenUsage usage ? CreateUsageDetails ( usage ) : null ,
136
153
} ;
137
154
}
138
155
@@ -161,13 +178,19 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
161
178
string ? toolId = null ;
162
179
StringBuilder ? toolInput = null ;
163
180
ChatFinishReason ? finishReason = null ;
181
+ string messageId = Guid . NewGuid ( ) . ToString ( "N" ) ;
182
+ string responseId = Guid . NewGuid ( ) . ToString ( "N" ) ;
164
183
await foreach ( var update in result . Stream . ConfigureAwait ( false ) )
165
184
{
166
185
switch ( update )
167
186
{
168
187
case MessageStartEvent messageStart :
169
188
yield return new ( )
170
189
{
190
+ CreatedAt = DateTimeOffset . UtcNow ,
191
+ MessageId = messageId ,
192
+ RawRepresentation = update ,
193
+ ResponseId = responseId ,
171
194
Role = ChatRole . Assistant ,
172
195
FinishReason = finishReason ,
173
196
} ;
@@ -188,7 +211,35 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
188
211
{
189
212
yield return new ( ChatRole . Assistant , text )
190
213
{
214
+ CreatedAt = DateTimeOffset . UtcNow ,
215
+ MessageId = messageId ,
216
+ RawRepresentation = update ,
217
+ FinishReason = finishReason ,
218
+ ResponseId = responseId ,
219
+ } ;
220
+ }
221
+
222
+ if ( contentBlockDelta . Delta . ReasoningContent is { Text : not null } reasoningContent )
223
+ {
224
+ TextReasoningContent trc = new ( reasoningContent . Text ) ;
225
+
226
+ if ( reasoningContent . Signature is not null )
227
+ {
228
+ ( trc . AdditionalProperties ??= [ ] ) [ nameof ( reasoningContent . Signature ) ] = reasoningContent . Signature ;
229
+ }
230
+
231
+ if ( reasoningContent . RedactedContent is { } redactedContent )
232
+ {
233
+ ( trc . AdditionalProperties ??= [ ] ) [ nameof ( reasoningContent . RedactedContent ) ] = redactedContent . ToArray ( ) ;
234
+ }
235
+
236
+ yield return new ( ChatRole . Assistant , [ trc ] )
237
+ {
238
+ CreatedAt = DateTimeOffset . UtcNow ,
239
+ MessageId = messageId ,
191
240
FinishReason = finishReason ,
241
+ RawRepresentation = update ,
242
+ ResponseId = responseId ,
192
243
} ;
193
244
}
194
245
break ;
@@ -199,9 +250,13 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
199
250
Dictionary < string , object ? > ? inputs = ParseToolInputs ( toolInput ? . ToString ( ) , out Exception ? parseError ) ;
200
251
yield return new ( )
201
252
{
202
- Role = ChatRole . Assistant ,
203
- FinishReason = finishReason ,
204
253
Contents = [ new FunctionCallContent ( toolId , toolName , inputs ) { Exception = parseError } ] ,
254
+ CreatedAt = DateTimeOffset . UtcNow ,
255
+ MessageId = messageId ,
256
+ FinishReason = finishReason ,
257
+ RawRepresentation = update ,
258
+ ResponseId = responseId ,
259
+ Role = ChatRole . Assistant ,
205
260
} ;
206
261
}
207
262
@@ -224,26 +279,24 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
224
279
225
280
yield return new ( )
226
281
{
227
- Role = ChatRole . Assistant ,
228
- FinishReason = finishReason ,
229
282
AdditionalProperties = additionalProps ,
283
+ CreatedAt = DateTimeOffset . UtcNow ,
284
+ MessageId = messageId ,
285
+ FinishReason = finishReason ,
286
+ RawRepresentation = update ,
287
+ ResponseId = responseId ,
288
+ Role = ChatRole . Assistant ,
230
289
} ;
231
290
break ;
232
291
233
292
case ConverseStreamMetadataEvent metadata when metadata . Usage is TokenUsage usage :
234
- yield return new ( )
293
+ yield return new ( ChatRole . Assistant , [ new UsageContent ( CreateUsageDetails ( usage ) ) ] )
235
294
{
236
- Role = ChatRole . Assistant ,
295
+ CreatedAt = DateTimeOffset . UtcNow ,
237
296
FinishReason = finishReason ,
238
- Contents =
239
- [
240
- new UsageContent ( new ( )
241
- {
242
- InputTokenCount = usage . InputTokens ,
243
- OutputTokenCount = usage . OutputTokens ,
244
- TotalTokenCount = usage . TotalTokens ,
245
- } )
246
- ] ,
297
+ MessageId = messageId ,
298
+ RawRepresentation = update ,
299
+ ResponseId = responseId ,
247
300
} ;
248
301
break ;
249
302
}
@@ -266,6 +319,29 @@ public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
266
319
null ;
267
320
}
268
321
322
+ /// <summary>Creates a <see cref="UsageDetails"/> from a <see cref="TokenUsage"/>.</summary>
323
+ private static UsageDetails CreateUsageDetails ( TokenUsage usage )
324
+ {
325
+ UsageDetails ud = new ( )
326
+ {
327
+ InputTokenCount = usage . InputTokens ,
328
+ OutputTokenCount = usage . OutputTokens ,
329
+ TotalTokenCount = usage . TotalTokens ,
330
+ } ;
331
+
332
+ if ( usage . CacheReadInputTokens is int cacheReadTokens )
333
+ {
334
+ ( ud . AdditionalCounts ??= [ ] ) . Add ( nameof ( usage . CacheReadInputTokens ) , cacheReadTokens ) ;
335
+ }
336
+
337
+ if ( usage . CacheWriteInputTokens is int cacheWriteTokens )
338
+ {
339
+ ( ud . AdditionalCounts ??= [ ] ) . Add ( nameof ( usage . CacheWriteInputTokens ) , cacheWriteTokens ) ;
340
+ }
341
+
342
+ return ud ;
343
+ }
344
+
269
345
/// <summary>Converts a <see cref="StopReason"/> into a <see cref="ChatFinishReason"/>.</summary>
270
346
private static ChatFinishReason GetChatFinishReason ( StopReason stopReason ) =>
271
347
stopReason . Value switch
@@ -340,6 +416,21 @@ private static List<ContentBlock> CreateContents(ChatMessage message)
340
416
contents . Add ( new ( ) { Text = tc . Text } ) ;
341
417
break ;
342
418
419
+ case TextReasoningContent trc :
420
+ contents . Add ( new ( )
421
+ {
422
+ ReasoningContent = new ( )
423
+ {
424
+ ReasoningText = new ( )
425
+ {
426
+ Text = trc . Text ,
427
+ Signature = trc . AdditionalProperties ? [ nameof ( ReasoningContentBlock . ReasoningText . Signature ) ] as string ,
428
+ } ,
429
+ RedactedContent = trc . AdditionalProperties ? [ nameof ( ReasoningContentBlock . RedactedContent ) ] is byte [ ] array ? new ( array ) : null ,
430
+ }
431
+ } ) ;
432
+ break ;
433
+
343
434
case DataContent dc :
344
435
if ( GetImageFormat ( dc . MediaType ) is ImageFormat imageFormat )
345
436
{
0 commit comments