promptlayer 1.0.16__py3-none-any.whl → 1.0.78__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,960 @@
1
+ """
2
+ Response handlers for different LLM providers
3
+
4
+ This module contains handlers that process streaming responses from various
5
+ LLM providers and return both the final response and prompt blueprint.
6
+ """
7
+
8
+ import json
9
+ from typing import Any, AsyncIterable, List
10
+
11
+
12
+ def openai_stream_chat(results: list):
13
+ """Process OpenAI streaming chat results and return response + blueprint"""
14
+ from openai.types.chat import (
15
+ ChatCompletion,
16
+ ChatCompletionChunk,
17
+ ChatCompletionMessage,
18
+ ChatCompletionMessageToolCall,
19
+ )
20
+ from openai.types.chat.chat_completion import Choice
21
+ from openai.types.chat.chat_completion_message_tool_call import Function
22
+
23
+ chat_completion_chunks: List[ChatCompletionChunk] = results
24
+ response: ChatCompletion = ChatCompletion(
25
+ id="",
26
+ object="chat.completion",
27
+ choices=[
28
+ Choice(
29
+ finish_reason="stop",
30
+ index=0,
31
+ message=ChatCompletionMessage(role="assistant"),
32
+ )
33
+ ],
34
+ created=0,
35
+ model="",
36
+ )
37
+ last_result = chat_completion_chunks[-1]
38
+ response.id = last_result.id
39
+ response.created = last_result.created
40
+ response.model = last_result.model
41
+ response.system_fingerprint = last_result.system_fingerprint
42
+ response.usage = last_result.usage
43
+ content = ""
44
+ tool_calls: List[ChatCompletionMessageToolCall] = []
45
+
46
+ for result in chat_completion_chunks:
47
+ choices = result.choices
48
+ if len(choices) == 0:
49
+ continue
50
+ if choices[0].delta.content:
51
+ content = f"{content}{result.choices[0].delta.content}"
52
+
53
+ delta = choices[0].delta
54
+ if delta.tool_calls:
55
+ last_tool_call = None
56
+ if len(tool_calls) > 0:
57
+ last_tool_call = tool_calls[-1]
58
+ tool_call = delta.tool_calls[0]
59
+ if not tool_call.function:
60
+ continue
61
+ if not last_tool_call or tool_call.id:
62
+ tool_calls.append(
63
+ ChatCompletionMessageToolCall(
64
+ id=tool_call.id or "",
65
+ function=Function(
66
+ name=tool_call.function.name or "",
67
+ arguments=tool_call.function.arguments or "",
68
+ ),
69
+ type=tool_call.type or "function",
70
+ )
71
+ )
72
+ continue
73
+ last_tool_call.function.name = f"{last_tool_call.function.name}{tool_call.function.name or ''}"
74
+ last_tool_call.function.arguments = (
75
+ f"{last_tool_call.function.arguments}{tool_call.function.arguments or ''}"
76
+ )
77
+
78
+ response.choices[0].message.content = content
79
+ response.choices[0].message.tool_calls = tool_calls if tool_calls else None
80
+ return response
81
+
82
+
83
+ async def aopenai_stream_chat(generator: AsyncIterable[Any]) -> Any:
84
+ """Async version of openai_stream_chat"""
85
+ from openai.types.chat import (
86
+ ChatCompletion,
87
+ ChatCompletionChunk,
88
+ ChatCompletionMessage,
89
+ ChatCompletionMessageToolCall,
90
+ )
91
+ from openai.types.chat.chat_completion import Choice
92
+ from openai.types.chat.chat_completion_message_tool_call import Function
93
+
94
+ chat_completion_chunks: List[ChatCompletionChunk] = []
95
+ response: ChatCompletion = ChatCompletion(
96
+ id="",
97
+ object="chat.completion",
98
+ choices=[
99
+ Choice(
100
+ finish_reason="stop",
101
+ index=0,
102
+ message=ChatCompletionMessage(role="assistant"),
103
+ )
104
+ ],
105
+ created=0,
106
+ model="",
107
+ )
108
+ content = ""
109
+ tool_calls: List[ChatCompletionMessageToolCall] = []
110
+
111
+ async for result in generator:
112
+ chat_completion_chunks.append(result)
113
+ choices = result.choices
114
+ if len(choices) == 0:
115
+ continue
116
+ if choices[0].delta.content:
117
+ content = f"{content}{choices[0].delta.content}"
118
+
119
+ delta = choices[0].delta
120
+ if delta.tool_calls:
121
+ last_tool_call = None
122
+ if len(tool_calls) > 0:
123
+ last_tool_call = tool_calls[-1]
124
+ tool_call = delta.tool_calls[0]
125
+ if not tool_call.function:
126
+ continue
127
+ if not last_tool_call or tool_call.id:
128
+ tool_calls.append(
129
+ ChatCompletionMessageToolCall(
130
+ id=tool_call.id or "",
131
+ function=Function(
132
+ name=tool_call.function.name or "",
133
+ arguments=tool_call.function.arguments or "",
134
+ ),
135
+ type=tool_call.type or "function",
136
+ )
137
+ )
138
+ continue
139
+ last_tool_call.function.name = f"{last_tool_call.function.name}{tool_call.function.name or ''}"
140
+ last_tool_call.function.arguments = (
141
+ f"{last_tool_call.function.arguments}{tool_call.function.arguments or ''}"
142
+ )
143
+
144
+ # After collecting all chunks, set the response attributes
145
+ if chat_completion_chunks:
146
+ last_result = chat_completion_chunks[-1]
147
+ response.id = last_result.id
148
+ response.created = last_result.created
149
+ response.model = last_result.model
150
+ response.system_fingerprint = getattr(last_result, "system_fingerprint", None)
151
+ response.usage = last_result.usage
152
+
153
+ response.choices[0].message.content = content
154
+ response.choices[0].message.tool_calls = tool_calls if tool_calls else None
155
+ return response
156
+
157
+
158
+ def _initialize_openai_response_data():
159
+ """Initialize the response data structure for OpenAI responses"""
160
+ return {
161
+ "id": None,
162
+ "object": "response",
163
+ "created_at": None,
164
+ "status": None,
165
+ "error": None,
166
+ "incomplete_details": None,
167
+ "instructions": None,
168
+ "max_output_tokens": None,
169
+ "model": None,
170
+ "output": [],
171
+ "parallel_tool_calls": True,
172
+ "previous_response_id": None,
173
+ "reasoning": {"effort": None, "summary": None},
174
+ "store": True,
175
+ "temperature": 1,
176
+ "text": {"format": {"type": "text"}},
177
+ "tool_choice": "auto",
178
+ "tools": [],
179
+ "top_p": 1,
180
+ "truncation": "disabled",
181
+ "usage": None,
182
+ "user": None,
183
+ "metadata": {},
184
+ }
185
+
186
+
187
+ def _process_openai_response_event(chunk_dict, response_data, current_items):
188
+ """Process a single OpenAI response event and update the response data"""
189
+ event_type = chunk_dict.get("type")
190
+ has_reasoning = False
191
+
192
+ if event_type == "response.created":
193
+ response_info = chunk_dict.get("response", {})
194
+ response_data["id"] = response_info.get("id")
195
+ response_data["created_at"] = response_info.get("created_at")
196
+ response_data["model"] = response_info.get("model")
197
+ response_data["status"] = response_info.get("status")
198
+ response_data["parallel_tool_calls"] = response_info.get("parallel_tool_calls", True)
199
+ response_data["temperature"] = response_info.get("temperature", 1)
200
+ response_data["tool_choice"] = response_info.get("tool_choice", "auto")
201
+ response_data["tools"] = response_info.get("tools", [])
202
+ response_data["top_p"] = response_info.get("top_p", 1)
203
+ response_data["truncation"] = response_info.get("truncation", "disabled")
204
+ response_data["max_output_tokens"] = response_info.get("max_output_tokens")
205
+ response_data["previous_response_id"] = response_info.get("previous_response_id")
206
+ response_data["store"] = response_info.get("store", True)
207
+ response_data["user"] = response_info.get("user")
208
+ response_data["metadata"] = response_info.get("metadata", {})
209
+
210
+ text_config = response_info.get("text", {})
211
+ if text_config:
212
+ response_data["text"] = text_config
213
+
214
+ reasoning = response_info.get("reasoning", {})
215
+ if reasoning:
216
+ response_data["reasoning"] = reasoning
217
+ has_reasoning = True
218
+
219
+ elif event_type == "response.in_progress":
220
+ response_info = chunk_dict.get("response", {})
221
+ response_data["status"] = response_info.get("status")
222
+
223
+ elif event_type == "response.output_item.added":
224
+ item = chunk_dict.get("item", {})
225
+ item_id = item.get("id")
226
+ item_type = item.get("type")
227
+
228
+ if item_type == "reasoning":
229
+ current_items[item_id] = {
230
+ "type": "reasoning",
231
+ "id": item_id,
232
+ "summary": [],
233
+ "status": item.get("status", "in_progress"),
234
+ }
235
+ has_reasoning = True
236
+
237
+ elif item_type == "function_call":
238
+ current_items[item_id] = {
239
+ "type": "function_call",
240
+ "id": item_id,
241
+ "call_id": item.get("call_id"),
242
+ "name": item.get("name"),
243
+ "arguments": "",
244
+ "status": item.get("status", "in_progress"),
245
+ }
246
+
247
+ elif item_type == "message":
248
+ current_items[item_id] = {
249
+ "type": "message",
250
+ "id": item_id,
251
+ "role": item.get("role", "assistant"),
252
+ "content": [],
253
+ "status": item.get("status", "in_progress"),
254
+ }
255
+
256
+ elif item_type == "code_interpreter_call":
257
+ current_items[item_id] = {
258
+ "type": "code_interpreter_call",
259
+ "id": item_id,
260
+ "code": item.get("code", ""),
261
+ "container_id": item.get("container_id"),
262
+ "status": item.get("status", "in_progress"),
263
+ }
264
+
265
+ elif event_type == "response.reasoning_summary_part.added":
266
+ item_id = chunk_dict.get("item_id")
267
+ part = chunk_dict.get("part", {})
268
+
269
+ if item_id in current_items and current_items[item_id]["type"] == "reasoning":
270
+ summary_part = {"type": part.get("type", "summary_text"), "text": part.get("text", "")}
271
+ current_items[item_id]["summary"].append(summary_part)
272
+
273
+ elif event_type == "response.reasoning_summary_text.delta":
274
+ item_id = chunk_dict.get("item_id")
275
+ delta = chunk_dict.get("delta", "")
276
+ summary_index = chunk_dict.get("summary_index", 0)
277
+
278
+ if item_id in current_items and current_items[item_id]["type"] == "reasoning":
279
+ while len(current_items[item_id]["summary"]) <= summary_index:
280
+ current_items[item_id]["summary"].append({"type": "summary_text", "text": ""})
281
+
282
+ current_items[item_id]["summary"][summary_index]["text"] += delta
283
+
284
+ elif event_type == "response.reasoning_summary_text.done":
285
+ item_id = chunk_dict.get("item_id")
286
+ final_text = chunk_dict.get("text", "")
287
+ summary_index = chunk_dict.get("summary_index", 0)
288
+
289
+ if item_id in current_items and current_items[item_id]["type"] == "reasoning":
290
+ while len(current_items[item_id]["summary"]) <= summary_index:
291
+ current_items[item_id]["summary"].append({"type": "summary_text", "text": ""})
292
+
293
+ current_items[item_id]["summary"][summary_index]["text"] = final_text
294
+
295
+ elif event_type == "response.reasoning_summary_part.done":
296
+ item_id = chunk_dict.get("item_id")
297
+ part = chunk_dict.get("part", {})
298
+
299
+ if item_id in current_items and current_items[item_id]["type"] == "reasoning":
300
+ summary_index = chunk_dict.get("summary_index", 0)
301
+ if summary_index < len(current_items[item_id]["summary"]):
302
+ current_items[item_id]["summary"][summary_index] = {
303
+ "type": part.get("type", "summary_text"),
304
+ "text": part.get("text", ""),
305
+ }
306
+
307
+ elif event_type == "response.function_call_arguments.delta":
308
+ item_id = chunk_dict.get("item_id")
309
+ delta = chunk_dict.get("delta", "")
310
+
311
+ if item_id in current_items:
312
+ current_items[item_id]["arguments"] += delta
313
+
314
+ elif event_type == "response.function_call_arguments.done":
315
+ item_id = chunk_dict.get("item_id")
316
+ final_arguments = chunk_dict.get("arguments", "")
317
+
318
+ if item_id in current_items:
319
+ current_items[item_id]["arguments"] = final_arguments
320
+
321
+ elif event_type == "response.content_part.added":
322
+ part = chunk_dict.get("part", {})
323
+
324
+ message_item = None
325
+ for item in current_items.values():
326
+ if item.get("type") == "message":
327
+ message_item = item
328
+ break
329
+
330
+ if message_item:
331
+ content_part = {
332
+ "type": part.get("type", "output_text"),
333
+ "text": part.get("text", ""),
334
+ "annotations": part.get("annotations", []),
335
+ }
336
+ message_item["content"].append(content_part)
337
+
338
+ elif event_type == "response.output_text.delta":
339
+ delta_text = chunk_dict.get("delta", "")
340
+
341
+ for item in current_items.values():
342
+ if item.get("type") == "message" and item.get("content"):
343
+ if item["content"] and item["content"][-1].get("type") == "output_text":
344
+ item["content"][-1]["text"] += delta_text
345
+ break
346
+
347
+ elif event_type == "response.output_text.done":
348
+ final_text = chunk_dict.get("text", "")
349
+
350
+ for item in current_items.values():
351
+ if item.get("type") == "message" and item.get("content"):
352
+ if item["content"] and item["content"][-1].get("type") == "output_text":
353
+ item["content"][-1]["text"] = final_text
354
+ break
355
+
356
+ elif event_type == "response.output_item.done":
357
+ item = chunk_dict.get("item", {})
358
+ item_id = item.get("id")
359
+
360
+ if item_id in current_items:
361
+ current_items[item_id]["status"] = item.get("status", "completed")
362
+
363
+ if item.get("type") == "reasoning":
364
+ current_items[item_id].update({"summary": item.get("summary", current_items[item_id]["summary"])})
365
+ elif item.get("type") == "function_call":
366
+ current_items[item_id].update(
367
+ {
368
+ "arguments": item.get("arguments", current_items[item_id]["arguments"]),
369
+ "call_id": item.get("call_id", current_items[item_id]["call_id"]),
370
+ "name": item.get("name", current_items[item_id]["name"]),
371
+ }
372
+ )
373
+ elif item.get("type") == "message":
374
+ current_items[item_id].update(
375
+ {
376
+ "content": item.get("content", current_items[item_id]["content"]),
377
+ "role": item.get("role", current_items[item_id]["role"]),
378
+ }
379
+ )
380
+
381
+ response_data["output"].append(current_items[item_id])
382
+
383
+ elif event_type == "response.completed":
384
+ response_info = chunk_dict.get("response", {})
385
+ response_data["status"] = response_info.get("status", "completed")
386
+ response_data["usage"] = response_info.get("usage")
387
+ response_data["output"] = response_info.get("output", response_data["output"])
388
+
389
+ if response_info.get("reasoning"):
390
+ response_data["reasoning"] = response_info["reasoning"]
391
+
392
+ return has_reasoning
393
+
394
+
395
+ def openai_responses_stream_chat(results: list):
396
+ """Process OpenAI Responses streaming chat results and return response"""
397
+ from openai.types.responses import Response
398
+
399
+ response_data = _initialize_openai_response_data()
400
+ current_items = {}
401
+
402
+ for chunk in results:
403
+ chunk_dict = chunk.model_dump()
404
+ _process_openai_response_event(chunk_dict, response_data, current_items)
405
+
406
+ return Response(**response_data)
407
+
408
+
409
+ async def aopenai_responses_stream_chat(generator: AsyncIterable[Any]) -> Any:
410
+ """Async version of openai_responses_stream_chat"""
411
+ from openai.types.responses import Response
412
+
413
+ response_data = _initialize_openai_response_data()
414
+ current_items = {}
415
+
416
+ async for chunk in generator:
417
+ chunk_dict = chunk.model_dump()
418
+ _process_openai_response_event(chunk_dict, response_data, current_items)
419
+
420
+ return Response(**response_data)
421
+
422
+
423
+ def anthropic_stream_message(results: list):
424
+ """Process Anthropic streaming message results and return response + blueprint"""
425
+ from anthropic.types import Message, MessageStreamEvent, Usage
426
+
427
+ from promptlayer.utils import build_anthropic_content_blocks
428
+
429
+ message_stream_events: List[MessageStreamEvent] = results
430
+ response: Message = Message(
431
+ id="",
432
+ model="",
433
+ content=[],
434
+ role="assistant",
435
+ type="message",
436
+ stop_reason="stop_sequence",
437
+ stop_sequence=None,
438
+ usage=Usage(input_tokens=0, output_tokens=0),
439
+ )
440
+
441
+ for event in message_stream_events:
442
+ if event.type == "message_start":
443
+ response = event.message
444
+ break
445
+
446
+ content_blocks, usage, stop_reason = build_anthropic_content_blocks(message_stream_events)
447
+ response.content = content_blocks
448
+ if usage:
449
+ response.usage.output_tokens = usage.output_tokens
450
+ if stop_reason:
451
+ response.stop_reason = stop_reason
452
+
453
+ return response
454
+
455
+
456
+ async def aanthropic_stream_message(generator: AsyncIterable[Any]) -> Any:
457
+ """Async version of anthropic_stream_message"""
458
+ from anthropic.types import Message, MessageStreamEvent, Usage
459
+
460
+ from promptlayer.utils import build_anthropic_content_blocks
461
+
462
+ message_stream_events: List[MessageStreamEvent] = []
463
+ response: Message = Message(
464
+ id="",
465
+ model="",
466
+ content=[],
467
+ role="assistant",
468
+ type="message",
469
+ stop_reason="stop_sequence",
470
+ stop_sequence=None,
471
+ usage=Usage(input_tokens=0, output_tokens=0),
472
+ )
473
+
474
+ async for event in generator:
475
+ if event.type == "message_start":
476
+ response = event.message
477
+ message_stream_events.append(event)
478
+
479
+ content_blocks, usage, stop_reason = build_anthropic_content_blocks(message_stream_events)
480
+ response.content = content_blocks
481
+ if usage:
482
+ response.usage.output_tokens = usage.output_tokens
483
+ if stop_reason:
484
+ response.stop_reason = stop_reason
485
+
486
+ return response
487
+
488
+
489
+ def openai_stream_completion(results: list):
490
+ from openai.types.completion import Completion, CompletionChoice
491
+
492
+ completions: List[Completion] = results
493
+ last_chunk = completions[-1]
494
+ response = Completion(
495
+ id=last_chunk.id,
496
+ created=last_chunk.created,
497
+ model=last_chunk.model,
498
+ object="text_completion",
499
+ choices=[CompletionChoice(finish_reason="stop", index=0, text="")],
500
+ )
501
+ text = ""
502
+ for completion in completions:
503
+ usage = completion.usage
504
+ system_fingerprint = completion.system_fingerprint
505
+ if len(completion.choices) > 0 and completion.choices[0].text:
506
+ text = f"{text}{completion.choices[0].text}"
507
+ if usage:
508
+ response.usage = usage
509
+ if system_fingerprint:
510
+ response.system_fingerprint = system_fingerprint
511
+ response.choices[0].text = text
512
+ return response
513
+
514
+
515
+ async def aopenai_stream_completion(generator: AsyncIterable[Any]) -> Any:
516
+ from openai.types.completion import Completion, CompletionChoice
517
+
518
+ completions: List[Completion] = []
519
+ text = ""
520
+ response = Completion(
521
+ id="",
522
+ created=0,
523
+ model="",
524
+ object="text_completion",
525
+ choices=[CompletionChoice(finish_reason="stop", index=0, text="")],
526
+ )
527
+
528
+ async for completion in generator:
529
+ completions.append(completion)
530
+ usage = completion.usage
531
+ system_fingerprint = getattr(completion, "system_fingerprint", None)
532
+ if len(completion.choices) > 0 and completion.choices[0].text:
533
+ text = f"{text}{completion.choices[0].text}"
534
+ if usage:
535
+ response.usage = usage
536
+ if system_fingerprint:
537
+ response.system_fingerprint = system_fingerprint
538
+
539
+ # After collecting all completions, set the response attributes
540
+ if completions:
541
+ last_chunk = completions[-1]
542
+ response.id = last_chunk.id
543
+ response.created = last_chunk.created
544
+ response.model = last_chunk.model
545
+
546
+ response.choices[0].text = text
547
+ return response
548
+
549
+
550
+ def anthropic_stream_completion(results: list):
551
+ from anthropic.types import Completion
552
+
553
+ completions: List[Completion] = results
554
+ last_chunk = completions[-1]
555
+ response = Completion(
556
+ id=last_chunk.id,
557
+ completion="",
558
+ model=last_chunk.model,
559
+ stop_reason="stop",
560
+ type="completion",
561
+ )
562
+
563
+ text = ""
564
+ for completion in completions:
565
+ text = f"{text}{completion.completion}"
566
+ response.completion = text
567
+ return response
568
+
569
+
570
+ async def aanthropic_stream_completion(generator: AsyncIterable[Any]) -> Any:
571
+ from anthropic.types import Completion
572
+
573
+ completions: List[Completion] = []
574
+ text = ""
575
+ response = Completion(
576
+ id="",
577
+ completion="",
578
+ model="",
579
+ stop_reason="stop",
580
+ type="completion",
581
+ )
582
+
583
+ async for completion in generator:
584
+ completions.append(completion)
585
+ text = f"{text}{completion.completion}"
586
+
587
+ # After collecting all completions, set the response attributes
588
+ if completions:
589
+ last_chunk = completions[-1]
590
+ response.id = last_chunk.id
591
+ response.model = last_chunk.model
592
+
593
+ response.completion = text
594
+ return response
595
+
596
+
597
+ def _build_google_response_from_parts(thought_content: str, regular_content: str, function_calls: list, last_result):
598
+ """Helper function to build Google response with thought, regular, and function call parts."""
599
+ from google.genai.chats import Part
600
+
601
+ response = last_result.model_copy()
602
+ final_parts = []
603
+
604
+ if thought_content:
605
+ thought_part = Part(text=thought_content, thought=True)
606
+ final_parts.append(thought_part)
607
+
608
+ if regular_content:
609
+ text_part = Part(text=regular_content, thought=None)
610
+ final_parts.append(text_part)
611
+
612
+ for function_call in function_calls:
613
+ function_part = Part(function_call=function_call, thought=None)
614
+ final_parts.append(function_part)
615
+
616
+ if final_parts:
617
+ response.candidates[0].content.parts = final_parts
618
+
619
+ return response
620
+
621
+
622
+ async def amap_google_stream_response(generator: AsyncIterable[Any]):
623
+ from google.genai.chats import GenerateContentResponse
624
+
625
+ response = GenerateContentResponse()
626
+
627
+ thought_content = ""
628
+ regular_content = ""
629
+ function_calls = []
630
+ last_result = None
631
+
632
+ async for result in generator:
633
+ last_result = result
634
+ if result.candidates and result.candidates[0].content.parts:
635
+ for part in result.candidates[0].content.parts:
636
+ if hasattr(part, "text") and part.text:
637
+ if hasattr(part, "thought") and part.thought:
638
+ thought_content = f"{thought_content}{part.text}"
639
+ else:
640
+ regular_content = f"{regular_content}{part.text}"
641
+ elif hasattr(part, "function_call") and part.function_call:
642
+ function_calls.append(part.function_call)
643
+
644
+ if not last_result:
645
+ return response
646
+
647
+ return _build_google_response_from_parts(thought_content, regular_content, function_calls, last_result)
648
+
649
+
650
+ async def agoogle_stream_chat(generator: AsyncIterable[Any]):
651
+ return await amap_google_stream_response(generator)
652
+
653
+
654
+ async def agoogle_stream_completion(generator: AsyncIterable[Any]):
655
+ return await amap_google_stream_response(generator)
656
+
657
+
658
+ def map_google_stream_response(results: list):
659
+ from google.genai.chats import GenerateContentResponse
660
+
661
+ response = GenerateContentResponse()
662
+ if not results:
663
+ return response
664
+ results: List[GenerateContentResponse] = results
665
+
666
+ thought_content = ""
667
+ regular_content = ""
668
+ function_calls = []
669
+
670
+ for result in results:
671
+ if result.candidates and result.candidates[0].content.parts:
672
+ for part in result.candidates[0].content.parts:
673
+ if hasattr(part, "text") and part.text:
674
+ if hasattr(part, "thought") and part.thought:
675
+ thought_content = f"{thought_content}{part.text}"
676
+ else:
677
+ regular_content = f"{regular_content}{part.text}"
678
+ elif hasattr(part, "function_call") and part.function_call:
679
+ function_calls.append(part.function_call)
680
+
681
+ return _build_google_response_from_parts(thought_content, regular_content, function_calls, results[-1])
682
+
683
+
684
+ def google_stream_chat(results: list):
685
+ return map_google_stream_response(results)
686
+
687
+
688
+ def google_stream_completion(results: list):
689
+ return map_google_stream_response(results)
690
+
691
+
692
+ def mistral_stream_chat(results: list):
693
+ from openai.types.chat import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall
694
+ from openai.types.chat.chat_completion import Choice
695
+ from openai.types.chat.chat_completion_message_tool_call import Function
696
+
697
+ last_result = results[-1]
698
+ response = ChatCompletion(
699
+ id=last_result.data.id,
700
+ object="chat.completion",
701
+ choices=[
702
+ Choice(
703
+ finish_reason=last_result.data.choices[0].finish_reason or "stop",
704
+ index=0,
705
+ message=ChatCompletionMessage(role="assistant"),
706
+ )
707
+ ],
708
+ created=last_result.data.created,
709
+ model=last_result.data.model,
710
+ )
711
+
712
+ content = ""
713
+ tool_calls = None
714
+
715
+ for result in results:
716
+ choices = result.data.choices
717
+ if len(choices) == 0:
718
+ continue
719
+
720
+ delta = choices[0].delta
721
+ if delta.content is not None:
722
+ content = f"{content}{delta.content}"
723
+
724
+ if delta.tool_calls:
725
+ tool_calls = tool_calls or []
726
+ for tool_call in delta.tool_calls:
727
+ if len(tool_calls) == 0 or tool_call.id:
728
+ tool_calls.append(
729
+ ChatCompletionMessageToolCall(
730
+ id=tool_call.id or "",
731
+ function=Function(
732
+ name=tool_call.function.name,
733
+ arguments=tool_call.function.arguments,
734
+ ),
735
+ type="function",
736
+ )
737
+ )
738
+ else:
739
+ last_tool_call = tool_calls[-1]
740
+ if tool_call.function.name:
741
+ last_tool_call.function.name = f"{last_tool_call.function.name}{tool_call.function.name}"
742
+ if tool_call.function.arguments:
743
+ last_tool_call.function.arguments = (
744
+ f"{last_tool_call.function.arguments}{tool_call.function.arguments}"
745
+ )
746
+
747
+ response.choices[0].message.content = content
748
+ response.choices[0].message.tool_calls = tool_calls
749
+ response.usage = last_result.data.usage
750
+ return response
751
+
752
+
753
+ async def amistral_stream_chat(generator: AsyncIterable[Any]) -> Any:
754
+ from openai.types.chat import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall
755
+ from openai.types.chat.chat_completion import Choice
756
+ from openai.types.chat.chat_completion_message_tool_call import Function
757
+
758
+ completion_chunks = []
759
+ response = ChatCompletion(
760
+ id="",
761
+ object="chat.completion",
762
+ choices=[
763
+ Choice(
764
+ finish_reason="stop",
765
+ index=0,
766
+ message=ChatCompletionMessage(role="assistant"),
767
+ )
768
+ ],
769
+ created=0,
770
+ model="",
771
+ )
772
+ content = ""
773
+ tool_calls = None
774
+
775
+ async for result in generator:
776
+ completion_chunks.append(result)
777
+ choices = result.data.choices
778
+ if len(choices) == 0:
779
+ continue
780
+ delta = choices[0].delta
781
+ if delta.content is not None:
782
+ content = f"{content}{delta.content}"
783
+
784
+ if delta.tool_calls:
785
+ tool_calls = tool_calls or []
786
+ for tool_call in delta.tool_calls:
787
+ if len(tool_calls) == 0 or tool_call.id:
788
+ tool_calls.append(
789
+ ChatCompletionMessageToolCall(
790
+ id=tool_call.id or "",
791
+ function=Function(
792
+ name=tool_call.function.name,
793
+ arguments=tool_call.function.arguments,
794
+ ),
795
+ type="function",
796
+ )
797
+ )
798
+ else:
799
+ last_tool_call = tool_calls[-1]
800
+ if tool_call.function.name:
801
+ last_tool_call.function.name = f"{last_tool_call.function.name}{tool_call.function.name}"
802
+ if tool_call.function.arguments:
803
+ last_tool_call.function.arguments = (
804
+ f"{last_tool_call.function.arguments}{tool_call.function.arguments}"
805
+ )
806
+
807
+ if completion_chunks:
808
+ last_result = completion_chunks[-1]
809
+ response.id = last_result.data.id
810
+ response.created = last_result.data.created
811
+ response.model = last_result.data.model
812
+ response.usage = last_result.data.usage
813
+
814
+ response.choices[0].message.content = content
815
+ response.choices[0].message.tool_calls = tool_calls
816
+ return response
817
+
818
+
819
+ def bedrock_stream_message(results: list):
820
+ """Process Amazon Bedrock streaming message results and return response + blueprint"""
821
+
822
+ response = {"ResponseMetadata": {}, "output": {"message": {}}, "stopReason": "end_turn", "metrics": {}, "usage": {}}
823
+
824
+ content_blocks = []
825
+ current_tool_call = None
826
+ current_tool_input = ""
827
+ current_text = ""
828
+ current_signature = ""
829
+ current_thinking = ""
830
+
831
+ for event in results:
832
+ if "contentBlockStart" in event:
833
+ content_block = event["contentBlockStart"]
834
+ if "start" in content_block and "toolUse" in content_block["start"]:
835
+ tool_use = content_block["start"]["toolUse"]
836
+ current_tool_call = {"toolUse": {"toolUseId": tool_use["toolUseId"], "name": tool_use["name"]}}
837
+ current_tool_input = ""
838
+
839
+ elif "contentBlockDelta" in event:
840
+ delta = event["contentBlockDelta"]["delta"]
841
+ if "text" in delta:
842
+ current_text += delta["text"]
843
+ elif "reasoningContent" in delta:
844
+ reasoning_content = delta["reasoningContent"]
845
+ if "text" in reasoning_content:
846
+ current_thinking += reasoning_content["text"]
847
+ elif "signature" in reasoning_content:
848
+ current_signature += reasoning_content["signature"]
849
+ elif "toolUse" in delta:
850
+ if "input" in delta["toolUse"]:
851
+ input_chunk = delta["toolUse"]["input"]
852
+ current_tool_input += input_chunk
853
+ if not input_chunk.strip():
854
+ continue
855
+
856
+ elif "contentBlockStop" in event:
857
+ if current_tool_call and current_tool_input:
858
+ try:
859
+ current_tool_call["toolUse"]["input"] = json.loads(current_tool_input)
860
+ except json.JSONDecodeError:
861
+ current_tool_call["toolUse"]["input"] = {}
862
+ content_blocks.append(current_tool_call)
863
+ current_tool_call = None
864
+ current_tool_input = ""
865
+ elif current_text:
866
+ content_blocks.append({"text": current_text})
867
+ current_text = ""
868
+ elif current_thinking and current_signature:
869
+ content_blocks.append(
870
+ {
871
+ "reasoningContent": {
872
+ "reasoningText": {"text": current_thinking, "signature": current_signature},
873
+ }
874
+ }
875
+ )
876
+ current_thinking = ""
877
+ current_signature = ""
878
+
879
+ elif "messageStop" in event:
880
+ response["stopReason"] = event["messageStop"]["stopReason"]
881
+
882
+ elif "metadata" in event:
883
+ metadata = event["metadata"]
884
+ response["usage"] = metadata.get("usage", {})
885
+ response["metrics"] = metadata.get("metrics", {})
886
+
887
+ response["output"]["message"] = {"role": "assistant", "content": content_blocks}
888
+ return response
889
+
890
+
891
+ async def abedrock_stream_message(generator: AsyncIterable[Any]) -> Any:
892
+ """Async version of bedrock_stream_message"""
893
+
894
+ response = {"ResponseMetadata": {}, "output": {"message": {}}, "stopReason": "end_turn", "metrics": {}, "usage": {}}
895
+
896
+ content_blocks = []
897
+ current_tool_call = None
898
+ current_tool_input = ""
899
+ current_text = ""
900
+ current_signature = ""
901
+ current_thinking = ""
902
+
903
+ async for event in generator:
904
+ if "contentBlockStart" in event:
905
+ content_block = event["contentBlockStart"]
906
+ if "start" in content_block and "toolUse" in content_block["start"]:
907
+ tool_use = content_block["start"]["toolUse"]
908
+ current_tool_call = {"toolUse": {"toolUseId": tool_use["toolUseId"], "name": tool_use["name"]}}
909
+ current_tool_input = ""
910
+
911
+ elif "contentBlockDelta" in event:
912
+ delta = event["contentBlockDelta"]["delta"]
913
+ if "text" in delta:
914
+ current_text += delta["text"]
915
+ elif "reasoningContent" in delta:
916
+ reasoning_content = delta["reasoningContent"]
917
+ if "text" in reasoning_content:
918
+ current_thinking += reasoning_content["text"]
919
+ elif "signature" in reasoning_content:
920
+ current_signature += reasoning_content["signature"]
921
+ elif "toolUse" in delta:
922
+ if "input" in delta["toolUse"]:
923
+ input_chunk = delta["toolUse"]["input"]
924
+ current_tool_input += input_chunk
925
+ if not input_chunk.strip():
926
+ continue
927
+
928
+ elif "contentBlockStop" in event:
929
+ if current_tool_call and current_tool_input:
930
+ try:
931
+ current_tool_call["toolUse"]["input"] = json.loads(current_tool_input)
932
+ except json.JSONDecodeError:
933
+ current_tool_call["toolUse"]["input"] = {}
934
+ content_blocks.append(current_tool_call)
935
+ current_tool_call = None
936
+ current_tool_input = ""
937
+ elif current_text:
938
+ content_blocks.append({"text": current_text})
939
+ current_text = ""
940
+ elif current_thinking and current_signature:
941
+ content_blocks.append(
942
+ {
943
+ "reasoningContent": {
944
+ "reasoningText": {"text": current_thinking, "signature": current_signature},
945
+ }
946
+ }
947
+ )
948
+ current_thinking = ""
949
+ current_signature = ""
950
+
951
+ elif "messageStop" in event:
952
+ response["stopReason"] = event["messageStop"]["stopReason"]
953
+
954
+ elif "metadata" in event:
955
+ metadata = event["metadata"]
956
+ response["usage"] = metadata.get("usage", {})
957
+ response["metrics"] = metadata.get("metrics", {})
958
+
959
+ response["output"]["message"] = {"role": "assistant", "content": content_blocks}
960
+ return response