bridgic-llms-openai 0.1.0__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.

Potentially problematic release.


This version of bridgic-llms-openai might be problematic. Click here for more details.

@@ -0,0 +1,19 @@
1
+ """
2
+ The OpenAI integration module provides support for the OpenAI API.
3
+
4
+ This module implements integration interfaces with OpenAI language models, supporting
5
+ calls to large language models provided by OpenAI such as the GPT series, and provides
6
+ several wrappers for advanced functionality.
7
+
8
+ You can install the OpenAI integration package for Bridgic by running:
9
+
10
+ ```shell
11
+ pip install bridgic-llms-openai
12
+ ```
13
+ """
14
+
15
+ from importlib.metadata import version
16
+ from .openai_llm import OpenAIConfiguration, OpenAILlm
17
+
18
+ __version__ = version("bridgic-llms-openai")
19
+ __all__ = ["OpenAIConfiguration", "OpenAILlm", "__version__"]
@@ -0,0 +1,1158 @@
1
+ import json
2
+ import httpx
3
+ import warnings
4
+
5
+ from typing import List, Dict, Tuple, Optional, overload, Any, Union, Literal
6
+ from typing_extensions import override
7
+ from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessageFunctionToolCall, ChatCompletionNamedToolChoiceParam, ChatCompletionToolChoiceOptionParam
8
+ from openai.types.chat.chat_completion_message_tool_call_param import ChatCompletionMessageToolCallParam, Function
9
+ from pydantic import BaseModel
10
+ from openai import Stream, OpenAI, AsyncOpenAI
11
+ from openai.types.chat.chat_completion_message import ChatCompletionMessage
12
+ from openai.resources.chat.completions.completions import ChatCompletionMessageParam
13
+ from openai.types.chat.chat_completion_system_message_param import ChatCompletionSystemMessageParam
14
+ from openai.types.chat.chat_completion_user_message_param import ChatCompletionUserMessageParam
15
+ from openai.types.chat.chat_completion_assistant_message_param import ChatCompletionAssistantMessageParam
16
+ from openai.types.chat.chat_completion_tool_message_param import ChatCompletionToolMessageParam
17
+
18
+ from bridgic.core.model import BaseLlm
19
+ from bridgic.core.model.types import *
20
+ from bridgic.core.model.protocols import StructuredOutput, ToolSelection, PydanticModel, JsonSchema, Constraint
21
+ from bridgic.core.utils._console import printer
22
+ from bridgic.core.utils._collection import filter_dict, merge_dict, validate_required_params
23
+
24
+ class OpenAIConfiguration(BaseModel):
25
+ """Default configuration for OpenAI chat completions.
26
+
27
+ model : str
28
+ Model ID used to generate the response, like `gpt-4o` or `gpt-4`.
29
+ temperature : Optional[float]
30
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
31
+ make the output more random, while lower values like 0.2 will make it more
32
+ focused and deterministic.
33
+ top_p : Optional[float]
34
+ An alternative to sampling with temperature, called nucleus sampling, where the
35
+ model considers the results of the tokens with top_p probability mass.
36
+ presence_penalty : Optional[float]
37
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
38
+ whether they appear in the text so far, increasing the model's likelihood to
39
+ talk about new topics.
40
+ frequency_penalty : Optional[float]
41
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
42
+ existing frequency in the text so far, decreasing the model's likelihood to
43
+ repeat the same line verbatim.
44
+ max_tokens : Optional[int]
45
+ The maximum number of tokens that can be generated in the chat completion.
46
+ This value is now deprecated in favor of `max_completion_tokens`.
47
+ stop : Optional[List[str]]
48
+ Up to 4 sequences where the API will stop generating further tokens.
49
+ Not supported with latest reasoning models `o3` and `o3-mini`.
50
+ """
51
+ model: Optional[str] = None
52
+ temperature: Optional[float] = None
53
+ top_p: Optional[float] = None
54
+ presence_penalty: Optional[float] = None
55
+ frequency_penalty: Optional[float] = None
56
+ max_tokens: Optional[int] = None
57
+ stop: Optional[List[str]] = None
58
+
59
+ class OpenAILlm(BaseLlm, StructuredOutput, ToolSelection):
60
+ """
61
+ Wrapper class for OpenAI, providing common chat and stream calling interfaces for OpenAI model
62
+ and implementing the common protocols in the Bridgic framework.
63
+
64
+ Parameters
65
+ ----------
66
+ api_key : str
67
+ The API key for OpenAI services. Required for authentication.
68
+ api_base : Optional[str]
69
+ The base URL for the OpenAI API. If None, uses the default OpenAI endpoint.
70
+ timeout : Optional[float]
71
+ Request timeout in seconds. If None, no timeout is applied.
72
+ http_client : Optional[httpx.Client]
73
+ Custom synchronous HTTP client for requests. If None, creates a default client.
74
+ http_async_client : Optional[httpx.AsyncClient]
75
+ Custom asynchronous HTTP client for requests. If None, creates a default client.
76
+
77
+ Attributes
78
+ ----------
79
+ client : openai.OpenAI
80
+ The synchronous OpenAI client instance.
81
+ async_client : openai.AsyncOpenAI
82
+ The asynchronous OpenAI client instance.
83
+
84
+ Examples
85
+ --------
86
+ Basic usage for chat completion:
87
+
88
+ ```python
89
+ llm = OpenAILlm(api_key="your-api-key")
90
+ messages = [Message.from_text("Hello!", role=Role.USER)]
91
+ response = llm.chat(messages=messages, model="gpt-4")
92
+ ```
93
+
94
+ Structured output with Pydantic model:
95
+
96
+ ```python
97
+ class Answer(BaseModel):
98
+ reasoning: str
99
+ result: int
100
+
101
+ constraint = PydanticModel(model=Answer)
102
+ structured_response = llm.structured_output(
103
+ messages=messages,
104
+ constraint=constraint,
105
+ model="gpt-4"
106
+ )
107
+ ```
108
+
109
+ Tool calling:
110
+
111
+ ```python
112
+ tools = [Tool(name="calculator", description="Calculate math", parameters={})]
113
+ tool_calls, tool_call_response = llm.select_tool(messages=messages, tools=tools, model="gpt-4")
114
+ ```
115
+ """
116
+
117
+ api_base: str
118
+ api_key: str
119
+ configuration: OpenAIConfiguration
120
+ timeout: float
121
+ http_client: httpx.Client
122
+ http_async_client: httpx.AsyncClient
123
+
124
+ client: OpenAI
125
+ async_client: AsyncOpenAI
126
+
127
+ def __init__(
128
+ self,
129
+ api_key: str,
130
+ api_base: Optional[str] = None,
131
+ configuration: Optional[OpenAIConfiguration] = OpenAIConfiguration(),
132
+ timeout: Optional[float] = None,
133
+ http_client: Optional[httpx.Client] = None,
134
+ http_async_client: Optional[httpx.AsyncClient] = None,
135
+ ):
136
+ """
137
+ Initialize the OpenAI LLM client with configuration parameters.
138
+
139
+ Parameters
140
+ ----------
141
+ api_key : str
142
+ The API key for OpenAI services. Required for authentication.
143
+ api_base : Optional[str]
144
+ The base URL for the OpenAI API. If None, uses the default OpenAI endpoint.
145
+ configuration : Optional[OpenAIConfiguration]
146
+ The configuration for the OpenAI API. If None, uses the default configuration.
147
+ timeout : Optional[float]
148
+ Request timeout in seconds. If None, no timeout is applied.
149
+ http_client : Optional[httpx.Client]
150
+ Custom synchronous HTTP client for requests. If None, creates a default client.
151
+ http_async_client : Optional[httpx.AsyncClient]
152
+ Custom asynchronous HTTP client for requests. If None, creates a default client.
153
+ """
154
+ # Record for serialization / deserialization.
155
+ self.api_base = api_base
156
+ self.api_key = api_key
157
+ self.configuration = configuration
158
+ self.timeout = timeout
159
+ self.http_client = http_client
160
+ self.http_async_client = http_async_client
161
+
162
+ # Initialize clients.
163
+ self.client = OpenAI(base_url=api_base, api_key=api_key, timeout=timeout, http_client=http_client)
164
+ self.async_client = AsyncOpenAI(base_url=api_base, api_key=api_key, timeout=timeout, http_client=http_async_client)
165
+
166
+ def chat(
167
+ self,
168
+ messages: List[Message],
169
+ model: Optional[str] = None,
170
+ temperature: Optional[float] = None,
171
+ top_p: Optional[float] = None,
172
+ presence_penalty: Optional[float] = None,
173
+ frequency_penalty: Optional[float] = None,
174
+ max_tokens: Optional[int] = None,
175
+ stop: Optional[List[str]] = None,
176
+ tools: Optional[List[Tool]] = None,
177
+ extra_body: Optional[Dict[str, Any]] = None,
178
+ **kwargs,
179
+ ) -> Response:
180
+ """
181
+ Send a synchronous chat completion request to OpenAI.
182
+
183
+ Parameters
184
+ ----------
185
+ messages : List[Message]
186
+ A list of messages comprising the conversation so far.
187
+ model : str
188
+ Model ID used to generate the response, like `gpt-4o` or `gpt-4`.
189
+ temperature : Optional[float]
190
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
191
+ make the output more random, while lower values like 0.2 will make it more
192
+ focused and deterministic.
193
+ top_p : Optional[float]
194
+ An alternative to sampling with temperature, called nucleus sampling, where the
195
+ model considers the results of the tokens with top_p probability mass.
196
+ presence_penalty : Optional[float]
197
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
198
+ whether they appear in the text so far, increasing the model's likelihood to
199
+ talk about new topics.
200
+ frequency_penalty : Optional[float]
201
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
202
+ existing frequency in the text so far, decreasing the model's likelihood to
203
+ repeat the same line verbatim.
204
+ max_tokens : Optional[int]
205
+ The maximum number of tokens that can be generated in the chat completion.
206
+ This value is now deprecated in favor of `max_completion_tokens`.
207
+ stop : Optional[List[str]]
208
+ Up to 4 sequences where the API will stop generating further tokens.
209
+ Not supported with latest reasoning models `o3` and `o3-mini`.
210
+ tools : Optional[List[Tool]]
211
+ A list of tools to use in the chat completion.
212
+ extra_body : Optional[Dict[str, Any]]
213
+ Add additional JSON properties to the request.
214
+ **kwargs
215
+ Additional keyword arguments passed to the OpenAI API.
216
+
217
+ Returns
218
+ -------
219
+ Response
220
+ A response object containing the generated message and raw API response.
221
+ """
222
+ params = self._build_parameters(
223
+ messages=messages,
224
+ model=model,
225
+ temperature=temperature,
226
+ top_p=top_p,
227
+ presence_penalty=presence_penalty,
228
+ frequency_penalty=frequency_penalty,
229
+ max_tokens=max_tokens,
230
+ stop=stop,
231
+ extra_body=extra_body,
232
+ **kwargs,
233
+ )
234
+ # Validate required parameters for non-streaming chat completion
235
+ validate_required_params(params, ["messages", "model"])
236
+
237
+ response: ChatCompletion = self.client.chat.completions.create(**params)
238
+ return self._handle_chat_response(response)
239
+
240
+ def stream(
241
+ self,
242
+ messages: List[Message],
243
+ model: Optional[str] = None,
244
+ temperature: Optional[float] = None,
245
+ top_p: Optional[float] = None,
246
+ presence_penalty: Optional[float] = None,
247
+ frequency_penalty: Optional[float] = None,
248
+ max_tokens: Optional[int] = None,
249
+ stop: Optional[List[str]] = None,
250
+ extra_body: Optional[Dict[str, Any]] = None,
251
+ **kwargs,
252
+ ) -> StreamResponse:
253
+ """
254
+ Send a streaming chat completion request to OpenAI.
255
+
256
+ Parameters
257
+ ----------
258
+ messages : List[Message]
259
+ A list of messages comprising the conversation so far.
260
+ model : str
261
+ Model ID used to generate the response, like `gpt-4o` or `gpt-4`.
262
+ temperature : Optional[float]
263
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
264
+ make the output more random, while lower values like 0.2 will make it more
265
+ focused and deterministic.
266
+ top_p : Optional[float]
267
+ An alternative to sampling with temperature, called nucleus sampling, where the
268
+ model considers the results of the tokens with top_p probability mass.
269
+ presence_penalty : Optional[float]
270
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
271
+ whether they appear in the text so far, increasing the model's likelihood to
272
+ talk about new topics.
273
+ frequency_penalty : Optional[float]
274
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
275
+ existing frequency in the text so far, decreasing the model's likelihood to
276
+ repeat the same line verbatim.
277
+ max_tokens : Optional[int]
278
+ The maximum number of tokens that can be generated in the chat completion.
279
+ This value is now deprecated in favor of `max_completion_tokens`.
280
+ stop : Optional[List[str]]
281
+ Up to 4 sequences where the API will stop generating further tokens.
282
+ Not supported with latest reasoning models `o3` and `o3-mini`.
283
+ extra_body : Optional[Dict[str, Any]]
284
+ Add additional JSON properties to the request.
285
+ **kwargs
286
+ Additional keyword arguments passed to the OpenAI API.
287
+
288
+ Yields
289
+ ------
290
+ MessageChunk
291
+ Individual chunks of the response as they are received from the API.
292
+ Each chunk contains a delta (partial content) and the raw response.
293
+
294
+ Notes
295
+ -----
296
+ This method enables real-time streaming of the model's response,
297
+ useful for providing incremental updates to users as the response is generated.
298
+ """
299
+ params = self._build_parameters(
300
+ messages=messages,
301
+ model=model,
302
+ temperature=temperature,
303
+ top_p=top_p,
304
+ presence_penalty=presence_penalty,
305
+ frequency_penalty=frequency_penalty,
306
+ max_tokens=max_tokens,
307
+ stop=stop,
308
+ extra_body=extra_body,
309
+ stream=True,
310
+ **kwargs,
311
+ )
312
+ # Validate required parameters for streaming chat completion
313
+ validate_required_params(params, ["messages", "model", "stream"])
314
+
315
+ response: Stream[ChatCompletionChunk] = self.client.chat.completions.create(**params)
316
+ for chunk in response:
317
+ if chunk.choices and chunk.choices[0].delta.content:
318
+ delta_content = chunk.choices[0].delta.content
319
+ delta_content = delta_content if delta_content else ""
320
+ yield MessageChunk(delta=delta_content, raw=chunk)
321
+
322
+ async def achat(
323
+ self,
324
+ messages: List[Message],
325
+ model: Optional[str] = None,
326
+ temperature: Optional[float] = None,
327
+ top_p: Optional[float] = None,
328
+ presence_penalty: Optional[float] = None,
329
+ frequency_penalty: Optional[float] = None,
330
+ max_tokens: Optional[int] = None,
331
+ stop: Optional[List[str]] = None,
332
+ tools: Optional[List[Tool]] = None,
333
+ extra_body: Optional[Dict[str, Any]] = None,
334
+ **kwargs,
335
+ ) -> Response:
336
+ """
337
+ Send an asynchronous chat completion request to OpenAI.
338
+
339
+ Parameters
340
+ ----------
341
+ messages : List[Message]
342
+ A list of messages comprising the conversation so far.
343
+ model : str
344
+ Model ID used to generate the response, like `gpt-4o` or `gpt-4`.
345
+ temperature : Optional[float]
346
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
347
+ make the output more random, while lower values like 0.2 will make it more
348
+ focused and deterministic.
349
+ top_p : Optional[float]
350
+ An alternative to sampling with temperature, called nucleus sampling, where the
351
+ model considers the results of the tokens with top_p probability mass.
352
+ presence_penalty : Optional[float]
353
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
354
+ whether they appear in the text so far, increasing the model's likelihood to
355
+ talk about new topics.
356
+ frequency_penalty : Optional[float]
357
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
358
+ existing frequency in the text so far, decreasing the model's likelihood to
359
+ repeat the same line verbatim.
360
+ max_tokens : Optional[int]
361
+ The maximum number of tokens that can be generated in the chat completion.
362
+ This value is now deprecated in favor of `max_completion_tokens`.
363
+ stop : Optional[List[str]]
364
+ Up to 4 sequences where the API will stop generating further tokens.
365
+ Not supported with latest reasoning models `o3` and `o3-mini`.
366
+ tools : Optional[List[Tool]]
367
+ A list of tools to use in the chat completion.
368
+ extra_body : Optional[Dict[str, Any]]
369
+ Add additional JSON properties to the request.
370
+ **kwargs
371
+ Additional keyword arguments passed to the OpenAI API.
372
+
373
+ Returns
374
+ -------
375
+ Response
376
+ A response object containing the generated message and raw API response.
377
+
378
+ Notes
379
+ -----
380
+ This is the asynchronous version of the chat method, suitable for
381
+ concurrent processing and non-blocking I/O operations.
382
+ """
383
+ params = self._build_parameters(
384
+ messages=messages,
385
+ model=model,
386
+ temperature=temperature,
387
+ top_p=top_p,
388
+ presence_penalty=presence_penalty,
389
+ frequency_penalty=frequency_penalty,
390
+ max_tokens=max_tokens,
391
+ stop=stop,
392
+ extra_body=extra_body,
393
+ **kwargs,
394
+ )
395
+ # Validate required parameters for non-streaming chat completion
396
+ validate_required_params(params, ["messages", "model"])
397
+
398
+ response = await self.async_client.chat.completions.create(**params)
399
+ return self._handle_chat_response(response)
400
+
401
+ async def astream(
402
+ self,
403
+ messages: List[Message],
404
+ model: Optional[str] = None,
405
+ temperature: Optional[float] = None,
406
+ top_p: Optional[float] = None,
407
+ presence_penalty: Optional[float] = None,
408
+ frequency_penalty: Optional[float] = None,
409
+ max_tokens: Optional[int] = None,
410
+ stop: Optional[List[str]] = None,
411
+ extra_body: Optional[Dict[str, Any]] = None,
412
+ **kwargs,
413
+ ) -> AsyncStreamResponse:
414
+ """
415
+ Send an asynchronous streaming chat completion request to OpenAI.
416
+
417
+ Parameters
418
+ ----------
419
+ messages : List[Message]
420
+ A list of messages comprising the conversation so far.
421
+ model : str
422
+ Model ID used to generate the response, like `gpt-4o` or `gpt-4`.
423
+ temperature : Optional[float]
424
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
425
+ make the output more random, while lower values like 0.2 will make it more
426
+ focused and deterministic.
427
+ top_p : Optional[float]
428
+ An alternative to sampling with temperature, called nucleus sampling, where the
429
+ model considers the results of the tokens with top_p probability mass.
430
+ presence_penalty : Optional[float]
431
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
432
+ whether they appear in the text so far, increasing the model's likelihood to
433
+ talk about new topics.
434
+ frequency_penalty : Optional[float]
435
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
436
+ existing frequency in the text so far, decreasing the model's likelihood to
437
+ repeat the same line verbatim.
438
+ max_tokens : Optional[int]
439
+ The maximum number of tokens that can be generated in the chat completion.
440
+ This value is now deprecated in favor of `max_completion_tokens`.
441
+ stop : Optional[List[str]]
442
+ Up to 4 sequences where the API will stop generating further tokens.
443
+ Not supported with latest reasoning models `o3` and `o3-mini`.
444
+ extra_body : Optional[Dict[str, Any]]
445
+ Add additional JSON properties to the request.
446
+ **kwargs
447
+ Additional keyword arguments passed to the OpenAI API.
448
+
449
+ Yields
450
+ ------
451
+ MessageChunk
452
+ Individual chunks of the response as they are received from the API.
453
+ Each chunk contains a delta (partial content) and the raw response.
454
+
455
+ Notes
456
+ -----
457
+ This is the asynchronous version of the stream method, suitable for
458
+ concurrent processing and non-blocking streaming operations.
459
+ """
460
+ params = self._build_parameters(
461
+ messages=messages,
462
+ model=model,
463
+ temperature=temperature,
464
+ top_p=top_p,
465
+ presence_penalty=presence_penalty,
466
+ frequency_penalty=frequency_penalty,
467
+ max_tokens=max_tokens,
468
+ stop=stop,
469
+ extra_body=extra_body,
470
+ stream=True,
471
+ **kwargs,
472
+ )
473
+ # Validate required parameters for streaming chat completion
474
+ validate_required_params(params, ["messages", "model", "stream"])
475
+
476
+ response = await self.async_client.chat.completions.create(**params)
477
+ async for chunk in response:
478
+ if chunk.choices and chunk.choices[0].delta.content:
479
+ delta_content = chunk.choices[0].delta.content
480
+ delta_content = delta_content if delta_content else ""
481
+ yield MessageChunk(delta=delta_content, raw=chunk)
482
+
483
+ def _build_parameters(
484
+ self,
485
+ messages: List[Message],
486
+ model: Optional[str] = None,
487
+ temperature: Optional[float] = None,
488
+ top_p: Optional[float] = None,
489
+ presence_penalty: Optional[float] = None,
490
+ frequency_penalty: Optional[float] = None,
491
+ max_tokens: Optional[int] = None,
492
+ stop: Optional[List[str]] = None,
493
+ tools: Optional[List[Tool]] = None,
494
+ extra_body: Optional[Dict[str, Any]] = None,
495
+ stream: Optional[bool] = None,
496
+ response_format: Optional[Dict[str, Any]] = None,
497
+ tool_choice: Optional[ChatCompletionToolChoiceOptionParam] = None,
498
+ parallel_tool_calls: Optional[bool] = None,
499
+ **kwargs,
500
+ ) -> Dict[str, Any]:
501
+ msgs: List[ChatCompletionMessageParam] = [self._convert_chat_completions_message(msg) for msg in messages]
502
+
503
+ # Handle tools parameter - convert to list if provided, otherwise use empty list
504
+ json_desc_tools = [self._convert_tool_to_json(tool) for tool in tools] if tools is not None else None
505
+
506
+ # Build parameters dictionary and filter out None values
507
+ # The priority order is as follows: configuration passed through the interface > configuration of the instance itself.
508
+ merge_params = merge_dict(self.configuration.model_dump(), {
509
+ "messages": msgs,
510
+ "model": model,
511
+ "temperature": temperature,
512
+ "top_p": top_p,
513
+ "presence_penalty": presence_penalty,
514
+ "frequency_penalty": frequency_penalty,
515
+ "max_tokens": max_tokens,
516
+ "stop": stop,
517
+ "tools": json_desc_tools,
518
+ "extra_body": extra_body,
519
+ "stream": stream,
520
+ "response_format": response_format,
521
+ "tool_choice": tool_choice,
522
+ "parallel_tool_calls": parallel_tool_calls,
523
+ **kwargs,
524
+ })
525
+
526
+ params = filter_dict(merge_params, exclude_none=True)
527
+ return params
528
+
529
+ def _handle_chat_response(self, response: ChatCompletion) -> Response:
530
+ openai_message = response.choices[0].message
531
+ text = openai_message.content if openai_message.content else ""
532
+
533
+ if openai_message.refusal:
534
+ warnings.warn(openai_message.refusal, RuntimeWarning)
535
+
536
+ # Handle tool calls in the response
537
+ # if openai_message.tool_calls:
538
+ # # Create a message with both text content and tool calls
539
+ # blocks = []
540
+ # if text:
541
+ # blocks.append(TextBlock(text=text))
542
+ # else:
543
+ # # Ensure there's always some text content, even if empty
544
+ # blocks.append(TextBlock(text=""))
545
+
546
+ # for tool_call in openai_message.tool_calls:
547
+ # tool_call_block = ToolCallBlock(
548
+ # id=tool_call.id,
549
+ # name=tool_call.function.name,
550
+ # arguments=json.loads(tool_call.function.arguments)
551
+ # )
552
+ # blocks.append(tool_call_block)
553
+
554
+ # message = Message(role=Role.AI, blocks=blocks)
555
+ # else:
556
+ # # Regular text response
557
+ # message = Message.from_text(text, role=Role.AI)
558
+
559
+ return Response(
560
+ message=Message.from_text(text, role=Role.AI),
561
+ raw=response,
562
+ )
563
+
564
+ def _convert_chat_completions_message(self, message: Message) -> ChatCompletionMessageParam:
565
+ """
566
+ Convert a Bridgic Message to OpenAI ChatCompletionMessageParam.
567
+
568
+ This method handles different message types including:
569
+ - Text messages
570
+ - Messages with tool calls (ToolCallBlock)
571
+ - Messages with tool results (ToolResultBlock)
572
+
573
+ Parameters
574
+ ----
575
+ message : Message
576
+ The Bridgic message to convert
577
+
578
+ Returns
579
+ ----
580
+ ChatCompletionMessageParam
581
+ The converted OpenAI message parameter
582
+ """
583
+ # Extract text content from TextBlocks and ToolResultBlocks
584
+ content_list = []
585
+ for block in message.blocks:
586
+ if isinstance(block, TextBlock):
587
+ content_list.append(block.text)
588
+ elif isinstance(block, ToolResultBlock):
589
+ content_list.append(block.content)
590
+ content_txt = "\n\n".join(content_list) if content_list else ""
591
+
592
+ # Extract tool calls from ToolCallBlocks
593
+ tool_calls = []
594
+ for block in message.blocks:
595
+ if isinstance(block, ToolCallBlock):
596
+ tool_call = ChatCompletionMessageToolCallParam(
597
+ id=block.id,
598
+ type="function",
599
+ function=Function(
600
+ name=block.name,
601
+ arguments=json.dumps(block.arguments)
602
+ )
603
+ )
604
+ tool_calls.append(tool_call)
605
+
606
+ # Handle different message roles
607
+ if message.role == Role.SYSTEM:
608
+ return ChatCompletionSystemMessageParam(content=content_txt, role="system", **message.extras)
609
+ elif message.role == Role.USER:
610
+ return ChatCompletionUserMessageParam(content=content_txt, role="user", **message.extras)
611
+ elif message.role == Role.AI:
612
+ # For AI messages, include tool calls if present
613
+ if tool_calls:
614
+ return ChatCompletionAssistantMessageParam(
615
+ content=content_txt,
616
+ role="assistant",
617
+ tool_calls=tool_calls,
618
+ **message.extras
619
+ )
620
+ else:
621
+ return ChatCompletionAssistantMessageParam(content=content_txt, role="assistant", **message.extras)
622
+ elif message.role == Role.TOOL:
623
+ # For tool messages, extract tool_call_id from ToolResultBlock
624
+ tool_call_id = None
625
+ for block in message.blocks:
626
+ if isinstance(block, ToolResultBlock):
627
+ tool_call_id = block.id
628
+ break
629
+
630
+ if tool_call_id is None:
631
+ raise ValueError("Tool message must contain a ToolResultBlock with an ID")
632
+
633
+ return ChatCompletionToolMessageParam(
634
+ content=content_txt,
635
+ role="tool",
636
+ tool_call_id=tool_call_id,
637
+ **message.extras
638
+ )
639
+ else:
640
+ raise ValueError(f"Invalid role: {message.role}")
641
+
642
+ @overload
643
+ def structured_output(
644
+ self,
645
+ messages: List[Message],
646
+ constraint: PydanticModel,
647
+ model: Optional[str] = None,
648
+ temperature: Optional[float] = ...,
649
+ top_p: Optional[float] = ...,
650
+ presence_penalty: Optional[float] = ...,
651
+ frequency_penalty: Optional[float] = ...,
652
+ extra_body: Optional[Dict[str, Any]] = ...,
653
+ **kwargs,
654
+ ) -> BaseModel: ...
655
+
656
+ @overload
657
+ def structured_output(
658
+ self,
659
+ messages: List[Message],
660
+ constraint: JsonSchema,
661
+ model: Optional[str] = None,
662
+ temperature: Optional[float] = ...,
663
+ top_p: Optional[float] = ...,
664
+ presence_penalty: Optional[float] = ...,
665
+ frequency_penalty: Optional[float] = ...,
666
+ extra_body: Optional[Dict[str, Any]] = ...,
667
+ **kwargs,
668
+ ) -> Dict[str, Any]: ...
669
+
670
+
671
+ def structured_output(
672
+ self,
673
+ messages: List[Message],
674
+ constraint: Union[PydanticModel, JsonSchema],
675
+ model: Optional[str] = None,
676
+ temperature: Optional[float] = None,
677
+ top_p: Optional[float] = None,
678
+ presence_penalty: Optional[float] = None,
679
+ frequency_penalty: Optional[float] = None,
680
+ extra_body: Optional[Dict[str, Any]] = None,
681
+ **kwargs,
682
+ ) -> Union[BaseModel, Dict[str, Any]]:
683
+ """
684
+ Generate structured output in a specified format using OpenAI's structured output API.
685
+
686
+ This method leverages OpenAI's structured output capabilities to ensure the model
687
+ response conforms to a specified schema. Recommended for use with GPT-4o and later models.
688
+
689
+ Parameters
690
+ ----------
691
+ messages : List[Message]
692
+ A list of messages comprising the conversation so far.
693
+ constraint : Constraint
694
+ The constraint defining the desired output format (PydanticModel or JsonSchema).
695
+ model : str
696
+ Model ID used to generate the response. Structured outputs work best with GPT-4o and later.
697
+ temperature : Optional[float]
698
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
699
+ make the output more random, while lower values like 0.2 will make it more
700
+ focused and deterministic.
701
+ top_p : Optional[float]
702
+ An alternative to sampling with temperature, called nucleus sampling, where the
703
+ model considers the results of the tokens with top_p probability mass.
704
+ presence_penalty : Optional[float]
705
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
706
+ whether they appear in the text so far, increasing the model's likelihood to
707
+ talk about new topics.
708
+ frequency_penalty : Optional[float]
709
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
710
+ existing frequency in the text so far, decreasing the model's likelihood to
711
+ repeat the same line verbatim.
712
+ extra_body : Optional[Dict[str, Any]]
713
+ Add additional JSON properties to the request.
714
+ **kwargs
715
+ Additional keyword arguments passed to the OpenAI API.
716
+
717
+ Returns
718
+ -------
719
+ Union[BaseModel, Dict[str, Any]]
720
+ The structured response in the format specified by the constraint:
721
+ - BaseModel instance if constraint is PydanticModel
722
+ - Dict[str, Any] if constraint is JsonSchema
723
+
724
+ Examples
725
+ --------
726
+ Using a Pydantic model constraint:
727
+
728
+ ```python
729
+ class Answer(BaseModel):
730
+ reasoning: str
731
+ result: int
732
+
733
+ constraint = PydanticModel(model=Answer)
734
+ response = llm.structured_output(
735
+ messages=[Message.from_text("What is 2+2?", role=Role.USER)],
736
+ constraint=constraint,
737
+ model="gpt-4o"
738
+ )
739
+ print(response.reasoning, response.result)
740
+ ```
741
+
742
+ Using a JSON schema constraint:
743
+
744
+ ```python
745
+ schema = {"type": "object", "properties": {"answer": {"type": "string"}}}
746
+ constraint = JsonSchema(schema=schema)
747
+ response = llm.structured_output(
748
+ messages=[Message.from_text("Hello", role=Role.USER)],
749
+ constraint=constraint,
750
+ model="gpt-4o"
751
+ )
752
+ print(response["answer"])
753
+ ```
754
+
755
+ Notes
756
+ -----
757
+ - Utilizes OpenAI's native structured output API with strict schema validation
758
+ - All schemas automatically have additionalProperties set to False
759
+ - Best performance achieved with GPT-4o and later models (gpt-4o-mini, gpt-4o-2024-08-06, and later)
760
+ """
761
+ params = self._build_parameters(
762
+ messages=messages,
763
+ model=model,
764
+ temperature=temperature,
765
+ top_p=top_p,
766
+ presence_penalty=presence_penalty,
767
+ frequency_penalty=frequency_penalty,
768
+ extra_body=extra_body,
769
+ response_format=self._get_response_format(constraint),
770
+ **kwargs,
771
+ )
772
+ # Validate required parameters for structured output
773
+ validate_required_params(params, ["messages", "model"])
774
+
775
+ response = self.client.chat.completions.parse(**params)
776
+ return self._convert_response(constraint, response.choices[0].message.content)
777
+
778
+ async def astructured_output(
779
+ self,
780
+ messages: List[Message],
781
+ constraint: Union[PydanticModel, JsonSchema],
782
+ model: Optional[str] = None,
783
+ temperature: Optional[float] = None,
784
+ top_p: Optional[float] = None,
785
+ presence_penalty: Optional[float] = None,
786
+ frequency_penalty: Optional[float] = None,
787
+ extra_body: Optional[Dict[str, Any]] = None,
788
+ **kwargs,
789
+ ) -> Union[BaseModel, Dict[str, Any]]:
790
+ """
791
+ Asynchronously generate structured output in a specified format using OpenAI's API.
792
+
793
+ This is the asynchronous version of structured_output, suitable for concurrent
794
+ processing and non-blocking operations. It leverages OpenAI's structured output
795
+ capabilities to ensure the model response conforms to a specified schema.
796
+
797
+ Parameters
798
+ ----------
799
+ messages : List[Message]
800
+ A list of messages comprising the conversation so far.
801
+ constraint : Constraint
802
+ The constraint defining the desired output format (PydanticModel or JsonSchema).
803
+ model : str
804
+ Model ID used to generate the response. Structured outputs work best with GPT-4o and later.
805
+ temperature : Optional[float]
806
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
807
+ make the output more random, while lower values like 0.2 will make it more
808
+ focused and deterministic.
809
+ top_p : Optional[float]
810
+ An alternative to sampling with temperature, called nucleus sampling, where the
811
+ model considers the results of the tokens with top_p probability mass.
812
+ presence_penalty : Optional[float]
813
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
814
+ whether they appear in the text so far, increasing the model's likelihood to
815
+ talk about new topics.
816
+ frequency_penalty : Optional[float]
817
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
818
+ existing frequency in the text so far, decreasing the model's likelihood to
819
+ repeat the same line verbatim.
820
+ extra_body : Optional[Dict[str, Any]]
821
+ Add additional JSON properties to the request.
822
+ **kwargs
823
+ Additional keyword arguments passed to the OpenAI API.
824
+
825
+ Returns
826
+ -------
827
+ Union[BaseModel, Dict[str, Any]]
828
+ The structured response in the format specified by the constraint:
829
+ - BaseModel instance if constraint is PydanticModel
830
+ - Dict[str, Any] if constraint is JsonSchema
831
+
832
+ Examples
833
+ --------
834
+ Using asynchronous structured output:
835
+
836
+ ```python
837
+ async def get_structured_response():
838
+ llm = OpenAILlm(api_key="your-key")
839
+ constraint = PydanticModel(model=Answer)
840
+ response = await llm.astructured_output(
841
+ messages=[Message.from_text("Calculate 5+3", role=Role.USER)],
842
+ constraint=constraint,
843
+ model="gpt-4o"
844
+ )
845
+ return response
846
+ ```
847
+
848
+ Notes
849
+ -----
850
+ - This is the asynchronous version of structured_output
851
+ - Utilizes OpenAI's native structured output API with strict schema validation
852
+ - Suitable for concurrent processing and high-throughput applications
853
+ - Best performance achieved with GPT-4o and later models (gpt-4o-mini, gpt-4o-2024-08-06, and later)
854
+ """
855
+ params = self._build_parameters(
856
+ messages=messages,
857
+ model=model,
858
+ temperature=temperature,
859
+ top_p=top_p,
860
+ presence_penalty=presence_penalty,
861
+ frequency_penalty=frequency_penalty,
862
+ extra_body=extra_body,
863
+ response_format=self._get_response_format(constraint),
864
+ **kwargs,
865
+ )
866
+ # Validate required parameters for structured output
867
+ validate_required_params(params, ["messages", "model"])
868
+
869
+ response = await self.async_client.chat.completions.parse(**params)
870
+ return self._convert_response(constraint, response.choices[0].message.content)
871
+
872
+ def _add_schema_properties(self, schema: Dict[str, Any]) -> Dict[str, Any]:
873
+ """
874
+ OpenAI requires additionalProperties to be set to False for all objects
875
+ in structured output schemas. See:
876
+ [AdditionalProperties False Must Always Be Set in Objects](https://platform.openai.com/docs/guides/structured-outputs?example=moderation#additionalproperties-false-must-always-be-set-in-objects)
877
+ """
878
+ schema["additionalProperties"] = False
879
+ return schema
880
+
881
+ def _get_response_format(self, constraint: Union[PydanticModel, JsonSchema]) -> Dict[str, Any]:
882
+ if isinstance(constraint, PydanticModel):
883
+ result = {
884
+ "type": "json_schema",
885
+ "json_schema": {
886
+ "schema": self._add_schema_properties(constraint.model.model_json_schema()),
887
+ "name": constraint.model.__name__,
888
+ "strict": True,
889
+ },
890
+ }
891
+ return result
892
+ elif isinstance(constraint, JsonSchema):
893
+ return {
894
+ "type": "json_schema",
895
+ "json_schema": {
896
+ "schema": self._add_schema_properties(constraint.schema_dict),
897
+ "name": constraint.name,
898
+ "strict": True,
899
+ },
900
+ }
901
+ else:
902
+ raise ValueError(f"Unsupported constraint type '{constraint.constraint_type}'. More info about OpenAI structured output: https://platform.openai.com/docs/guides/structured-outputs")
903
+
904
+ def _convert_response(
905
+ self,
906
+ constraint: Union[PydanticModel, JsonSchema],
907
+ content: str,
908
+ ) -> Union[BaseModel, Dict[str, Any]]:
909
+ if isinstance(constraint, PydanticModel):
910
+ return constraint.model.model_validate_json(content)
911
+ elif isinstance(constraint, JsonSchema):
912
+ return json.loads(content)
913
+ else:
914
+ raise ValueError(f"Unsupported constraint type '{constraint.constraint_type}'. More info about OpenAI structured output: https://platform.openai.com/docs/guides/structured-outputs")
915
+
916
+ def select_tool(
917
+ self,
918
+ messages: List[Message],
919
+ tools: List[Tool],
920
+ model: Optional[str] = None,
921
+ temperature: Optional[float] = None,
922
+ top_p: Optional[float] = None,
923
+ presence_penalty: Optional[float] = None,
924
+ frequency_penalty: Optional[float] = None,
925
+ extra_body: Optional[Dict[str, Any]] = None,
926
+ parallel_tool_calls: Optional[bool] = None,
927
+ tool_choice: Union[Literal["auto", "required", "none"], ChatCompletionNamedToolChoiceParam] = None,
928
+ **kwargs,
929
+ ) -> Tuple[List[ToolCall], Optional[str]]:
930
+ """
931
+ Select and invoke tools from a list based on conversation context.
932
+
933
+ This method enables the model to intelligently select and call appropriate tools
934
+ from a provided list based on the conversation context. It supports OpenAI's
935
+ function calling capabilities with parallel execution and various control options.
936
+
937
+ More OpenAI information: [function-calling](https://platform.openai.com/docs/guides/function-calling)
938
+
939
+ Parameters
940
+ ----------
941
+ messages : List[Message]
942
+ A list of messages comprising the conversation so far providing context for tool selection.
943
+ tools : List[Tool]
944
+ A list of tools the model may call.
945
+ model : str
946
+ Model ID used to generate the response. Function calling requires compatible models.
947
+ temperature : Optional[float]
948
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
949
+ make the output more random, while lower values like 0.2 will make it more
950
+ focused and deterministic.
951
+ top_p : Optional[float]
952
+ An alternative to sampling with temperature, called nucleus sampling, where the
953
+ model considers the results of the tokens with top_p probability mass.
954
+ presence_penalty : Optional[float]
955
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
956
+ whether they appear in the text so far, increasing the model's likelihood to
957
+ talk about new topics.
958
+ frequency_penalty : Optional[float]
959
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
960
+ existing frequency in the text so far, decreasing the model's likelihood to
961
+ repeat the same line verbatim.
962
+ extra_body : Optional[Dict[str, Any]]
963
+ Add additional JSON properties to the request.
964
+ parallel_tool_calls : Optional[bool]
965
+ Whether to enable parallel function calling during tool use.
966
+ tool_choice : Union[Literal["auto", "required", "none"], ChatCompletionNamedToolChoiceParam]
967
+ Controls which tool, if any, the model may call.
968
+ - `none`: The model will not call any tool and will instead generate a message. This is the default when no tools are provided.
969
+ - `auto`: The model may choose to generate a message or call one or more tools. This is the default when tools are provided.
970
+ - `required`: The model must call one or more tools.
971
+ - To force a specific tool, pass `{"type": "function", "function": {"name": "my_function"}}`.
972
+ **kwargs
973
+ Additional keyword arguments passed to the OpenAI API.
974
+
975
+ Returns
976
+ -------
977
+ List[ToolCall]
978
+ List of selected tool calls with their IDs, names, and parsed arguments.
979
+ Union[str, None]
980
+ The content of the message from the model.
981
+ """
982
+ params = self._build_parameters(
983
+ messages=messages,
984
+ model=model,
985
+ temperature=temperature,
986
+ top_p=top_p,
987
+ presence_penalty=presence_penalty,
988
+ frequency_penalty=frequency_penalty,
989
+ tools=tools,
990
+ tool_choice=tool_choice,
991
+ parallel_tool_calls=parallel_tool_calls,
992
+ extra_body=extra_body,
993
+ **kwargs,
994
+ )
995
+ # Validate required parameters for tool selection
996
+ validate_required_params(params, ["messages", "model"])
997
+
998
+ response: ChatCompletion = self.client.chat.completions.create(**params)
999
+ tool_calls = response.choices[0].message.tool_calls
1000
+ content = response.choices[0].message.content
1001
+ return (self._convert_tool_calls(tool_calls), content)
1002
+
1003
+ async def aselect_tool(
1004
+ self,
1005
+ messages: List[Message],
1006
+ tools: List[Tool],
1007
+ model: Optional[str] = None,
1008
+ temperature: Optional[float] = None,
1009
+ top_p: Optional[float] = None,
1010
+ presence_penalty: Optional[float] = None,
1011
+ frequency_penalty: Optional[float] = None,
1012
+ extra_body: Optional[Dict[str, Any]] = None,
1013
+ parallel_tool_calls: Optional[bool] = None,
1014
+ tool_choice: Union[Literal["auto", "required", "none"], ChatCompletionNamedToolChoiceParam] = None,
1015
+ **kwargs,
1016
+ )-> Tuple[List[ToolCall], Optional[str]]:
1017
+ """
1018
+ Select and invoke tools from a list based on conversation context.
1019
+
1020
+ This method enables the model to intelligently select and call appropriate tools
1021
+ from a provided list based on the conversation context. It supports OpenAI's
1022
+ function calling capabilities with parallel execution and various control options.
1023
+
1024
+ More OpenAI information: [function-calling](https://platform.openai.com/docs/guides/function-calling)
1025
+
1026
+ Parameters
1027
+ ----------
1028
+ messages : List[Message]
1029
+ A list of messages comprising the conversation so far providing context for tool selection.
1030
+ tools : List[Tool]
1031
+ A list of tools the model may call.
1032
+ model : str
1033
+ Model ID used to generate the response. Function calling requires compatible models.
1034
+ temperature : Optional[float]
1035
+ What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
1036
+ make the output more random, while lower values like 0.2 will make it more
1037
+ focused and deterministic.
1038
+ top_p : Optional[float]
1039
+ An alternative to sampling with temperature, called nucleus sampling, where the
1040
+ model considers the results of the tokens with top_p probability mass.
1041
+ presence_penalty : Optional[float]
1042
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on
1043
+ whether they appear in the text so far, increasing the model's likelihood to
1044
+ talk about new topics.
1045
+ frequency_penalty : Optional[float]
1046
+ Number between -2.0 and 2.0. Positive values penalize new tokens based on their
1047
+ existing frequency in the text so far, decreasing the model's likelihood to
1048
+ repeat the same line verbatim.
1049
+ extra_body : Optional[Dict[str, Any]]
1050
+ Add additional JSON properties to the request.
1051
+ parallel_tool_calls : Optional[bool]
1052
+ Whether to enable parallel function calling during tool use.
1053
+ tool_choice : Union[Literal["auto", "required", "none"], ChatCompletionNamedToolChoiceParam]
1054
+ Controls which tool, if any, the model may call.
1055
+ - `none`: The model will not call any tool and will instead generate a message. This is the default when no tools are provided.
1056
+ - `auto`: The model may choose to generate a message or call one or more tools. This is the default when tools are provided.
1057
+ - `required`: The model must call one or more tools.
1058
+ - To force a specific tool, pass `{"type": "function", "function": {"name": "my_function"}}`.
1059
+
1060
+ **kwargs
1061
+ Additional keyword arguments passed to the OpenAI API.
1062
+
1063
+ Returns
1064
+ -------
1065
+ List[ToolCall]
1066
+ List of selected tool calls with their IDs, names, and parsed arguments.
1067
+ Union[str, None]
1068
+ The content of the message from the model.
1069
+ """
1070
+ params = self._build_parameters(
1071
+ messages=messages,
1072
+ model=model,
1073
+ temperature=temperature,
1074
+ top_p=top_p,
1075
+ presence_penalty=presence_penalty,
1076
+ frequency_penalty=frequency_penalty,
1077
+ tools=tools,
1078
+ tool_choice=tool_choice,
1079
+ parallel_tool_calls=parallel_tool_calls,
1080
+ extra_body=extra_body,
1081
+ **kwargs,
1082
+ )
1083
+ # Validate required parameters for tool selection
1084
+ validate_required_params(params, ["messages", "model"])
1085
+
1086
+ response: ChatCompletion = await self.async_client.chat.completions.create(**params)
1087
+ tool_calls = response.choices[0].message.tool_calls
1088
+ content = response.choices[0].message.content
1089
+ return (self._convert_tool_calls(tool_calls), content)
1090
+
1091
+ def _convert_parameters(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
1092
+ return {
1093
+ "type": "object",
1094
+ "properties": parameters.get("properties", {}),
1095
+ "required": parameters.get("required", []),
1096
+ "additionalProperties": False
1097
+ }
1098
+
1099
+ def _convert_tool_to_json(self, tool: Tool) -> Dict[str, Any]:
1100
+ return {
1101
+ "type": "function",
1102
+ "function": {
1103
+ "name": tool.name,
1104
+ "description": tool.description,
1105
+ "parameters": self._convert_parameters(tool.parameters),
1106
+ }
1107
+ }
1108
+
1109
+ def _convert_tool_calls(self, tool_calls: List[ChatCompletionMessageFunctionToolCall]) -> List[ToolCall]:
1110
+ return [] if tool_calls is None else [
1111
+ ToolCall(
1112
+ id=tool_call.id,
1113
+ name=tool_call.function.name,
1114
+ arguments=json.loads(tool_call.function.arguments),
1115
+ ) for tool_call in tool_calls
1116
+ ]
1117
+
1118
+ @override
1119
+ def dump_to_dict(self) -> Dict[str, Any]:
1120
+ state_dict = {
1121
+ "api_base": self.api_base,
1122
+ "api_key": self.api_key,
1123
+ "timeout": self.timeout,
1124
+ "configuration": self.configuration.model_dump(),
1125
+ }
1126
+ if self.http_client:
1127
+ warnings.warn(
1128
+ "httpx.Client is not serializable, so it will be set to None in the deserialization.",
1129
+ RuntimeWarning,
1130
+ )
1131
+ if self.http_async_client:
1132
+ warnings.warn(
1133
+ "httpx.AsyncClient is not serializable, so it will be set to None in the deserialization.",
1134
+ RuntimeWarning,
1135
+ )
1136
+ return state_dict
1137
+
1138
+ @override
1139
+ def load_from_dict(self, state_dict: Dict[str, Any]) -> None:
1140
+ self.api_base = state_dict["api_base"]
1141
+ self.api_key = state_dict["api_key"]
1142
+ self.timeout = state_dict["timeout"]
1143
+ self.configuration = OpenAIConfiguration(**state_dict.get("configuration", {}))
1144
+ self.http_client = None
1145
+ self.http_async_client = None
1146
+
1147
+ self.client = OpenAI(
1148
+ base_url=self.api_base,
1149
+ api_key=self.api_key,
1150
+ timeout=self.timeout,
1151
+ http_client=self.http_client,
1152
+ )
1153
+ self.async_client = AsyncOpenAI(
1154
+ base_url=self.api_base,
1155
+ api_key=self.api_key,
1156
+ timeout=self.timeout,
1157
+ http_client=self.http_async_client,
1158
+ )
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: bridgic-llms-openai
3
+ Version: 0.1.0
4
+ Summary: OpenAI adapters for Bridgic.
5
+ Author-email: Tielei Zhang <zhangtl04@gmail.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Classifier: Programming Language :: Python :: 3
9
+ Requires-Python: >=3.9
10
+ Requires-Dist: bridgic-core>=0.1.0
11
+ Requires-Dist: httpx-aiohttp>=0.1.8
12
+ Requires-Dist: httpx>=0.28.1
13
+ Requires-Dist: openai>=1.60.0
14
+ Description-Content-Type: text/markdown
15
+
16
+ Bridgic LLMs Integration
17
+ ========================
18
+
19
+ This package integrates OpenAI LLM into the Bridgic framework, providing the most basic chat/stream interface and implementing several protocols based on the OpenAI API.
20
+
21
+ Installation
22
+ ------------
23
+
24
+ ```shell
25
+ pip install bridgic-llms-openai
26
+ ```
@@ -0,0 +1,6 @@
1
+ bridgic/llms/openai/__init__.py,sha256=JFNSGUkLVioyLlm5N5O4SpZtZfB7amrHcmQQBTRH83E,616
2
+ bridgic/llms/openai/openai_llm.py,sha256=UXaHOzrhCPttBw7aRLZYiUfRSlB_HyQ7BKr245QaqT4,50649
3
+ bridgic_llms_openai-0.1.0.dist-info/METADATA,sha256=sicBPbK9EOi8pm0kIkhDpn1rkxio-xZIZFAQS8ElFgw,720
4
+ bridgic_llms_openai-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ bridgic_llms_openai-0.1.0.dist-info/licenses/LICENSE,sha256=f9RZk4nzmfthTiwLcCppWR0L-UolLD7J47uduHHeJhA,1108
6
+ bridgic_llms_openai-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 北京比特天空科技有限公司 (BitSky Inc).
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+