openaivec 0.10.0__py3-none-any.whl → 1.0.10__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.
- openaivec/__init__.py +13 -4
- openaivec/_cache/__init__.py +12 -0
- openaivec/_cache/optimize.py +109 -0
- openaivec/_cache/proxy.py +806 -0
- openaivec/_di.py +326 -0
- openaivec/_embeddings.py +203 -0
- openaivec/{log.py → _log.py} +2 -2
- openaivec/_model.py +113 -0
- openaivec/{prompt.py → _prompt.py} +95 -28
- openaivec/_provider.py +207 -0
- openaivec/_responses.py +511 -0
- openaivec/_schema/__init__.py +9 -0
- openaivec/_schema/infer.py +340 -0
- openaivec/_schema/spec.py +350 -0
- openaivec/_serialize.py +234 -0
- openaivec/{util.py → _util.py} +25 -85
- openaivec/pandas_ext.py +1635 -425
- openaivec/spark.py +604 -335
- openaivec/task/__init__.py +27 -29
- openaivec/task/customer_support/__init__.py +9 -15
- openaivec/task/customer_support/customer_sentiment.py +51 -41
- openaivec/task/customer_support/inquiry_classification.py +86 -61
- openaivec/task/customer_support/inquiry_summary.py +44 -45
- openaivec/task/customer_support/intent_analysis.py +56 -41
- openaivec/task/customer_support/response_suggestion.py +49 -43
- openaivec/task/customer_support/urgency_analysis.py +76 -71
- openaivec/task/nlp/__init__.py +4 -4
- openaivec/task/nlp/dependency_parsing.py +19 -20
- openaivec/task/nlp/keyword_extraction.py +22 -24
- openaivec/task/nlp/morphological_analysis.py +25 -25
- openaivec/task/nlp/named_entity_recognition.py +26 -28
- openaivec/task/nlp/sentiment_analysis.py +29 -21
- openaivec/task/nlp/translation.py +24 -30
- openaivec/task/table/__init__.py +3 -0
- openaivec/task/table/fillna.py +183 -0
- openaivec-1.0.10.dist-info/METADATA +399 -0
- openaivec-1.0.10.dist-info/RECORD +39 -0
- {openaivec-0.10.0.dist-info → openaivec-1.0.10.dist-info}/WHEEL +1 -1
- openaivec/embeddings.py +0 -172
- openaivec/responses.py +0 -392
- openaivec/serialize.py +0 -225
- openaivec/task/model.py +0 -84
- openaivec-0.10.0.dist-info/METADATA +0 -546
- openaivec-0.10.0.dist-info/RECORD +0 -29
- {openaivec-0.10.0.dist-info → openaivec-1.0.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ construction of a prompt in a structured way, including setting the
|
|
|
6
6
|
purpose, adding cautions, and providing examples.
|
|
7
7
|
|
|
8
8
|
```python
|
|
9
|
-
from openaivec
|
|
9
|
+
from openaivec import FewShotPromptBuilder
|
|
10
10
|
|
|
11
11
|
prompt_str: str = (
|
|
12
12
|
FewShotPromptBuilder()
|
|
@@ -44,13 +44,15 @@ this will produce an XML string that looks like this:
|
|
|
44
44
|
|
|
45
45
|
import difflib
|
|
46
46
|
import logging
|
|
47
|
-
from typing import Any, List
|
|
48
47
|
from xml.etree import ElementTree
|
|
49
48
|
|
|
50
49
|
from openai import OpenAI
|
|
51
50
|
from openai.types.responses import ParsedResponse
|
|
52
51
|
from pydantic import BaseModel
|
|
53
52
|
|
|
53
|
+
from openaivec._model import ResponsesModelName
|
|
54
|
+
from openaivec._provider import CONTAINER
|
|
55
|
+
|
|
54
56
|
__all__ = [
|
|
55
57
|
"FewShotPrompt",
|
|
56
58
|
"FewShotPromptBuilder",
|
|
@@ -87,8 +89,8 @@ class FewShotPrompt(BaseModel):
|
|
|
87
89
|
"""
|
|
88
90
|
|
|
89
91
|
purpose: str
|
|
90
|
-
cautions:
|
|
91
|
-
examples:
|
|
92
|
+
cautions: list[str]
|
|
93
|
+
examples: list[Example]
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
class Step(BaseModel):
|
|
@@ -113,7 +115,7 @@ class Request(BaseModel):
|
|
|
113
115
|
|
|
114
116
|
|
|
115
117
|
class Response(BaseModel):
|
|
116
|
-
iterations:
|
|
118
|
+
iterations: list[Step]
|
|
117
119
|
|
|
118
120
|
|
|
119
121
|
_PROMPT: str = """
|
|
@@ -123,6 +125,7 @@ _PROMPT: str = """
|
|
|
123
125
|
Receive the prompt in JSON format with fields "purpose",
|
|
124
126
|
"cautions", and "examples". Ensure the entire prompt is free
|
|
125
127
|
from logical contradictions, redundancies, and ambiguities.
|
|
128
|
+
IMPORTANT: The "examples" array must always contain at least one example throughout all iterations.
|
|
126
129
|
</Instruction>
|
|
127
130
|
<Instruction id="2">
|
|
128
131
|
- Modify only one element per iteration among “purpose”, “examples”, or
|
|
@@ -152,8 +155,10 @@ _PROMPT: str = """
|
|
|
152
155
|
</Instruction>
|
|
153
156
|
<Instruction id="6">
|
|
154
157
|
In the "examples" field, enhance the examples to cover a wide range of scenarios.
|
|
158
|
+
CRITICAL: The examples array must NEVER be empty - always maintain at least one example.
|
|
155
159
|
Add as many non-redundant examples as possible,
|
|
156
160
|
since having more examples leads to better coverage and understanding.
|
|
161
|
+
You may modify existing examples or add new ones, but never remove all examples.
|
|
157
162
|
</Instruction>
|
|
158
163
|
<Instruction id="7">
|
|
159
164
|
Verify that the improved prompt adheres to the Request and
|
|
@@ -163,6 +168,7 @@ _PROMPT: str = """
|
|
|
163
168
|
Generate the final refined FewShotPrompt as an iteration in
|
|
164
169
|
the Response, ensuring the final output is consistent,
|
|
165
170
|
unambiguous, and free from any redundancies or contradictions.
|
|
171
|
+
MANDATORY: Verify that the examples array contains at least one example before completing.
|
|
166
172
|
</Instruction>
|
|
167
173
|
</Instructions>
|
|
168
174
|
<Example>
|
|
@@ -203,7 +209,9 @@ _PROMPT: str = """
|
|
|
203
209
|
"iterations": [
|
|
204
210
|
{
|
|
205
211
|
"id": 1,
|
|
206
|
-
"analysis": "The original purpose was vague and did not explicitly state the main objective.
|
|
212
|
+
"analysis": "The original purpose was vague and did not explicitly state the main objective.
|
|
213
|
+
This ambiguity could lead to confusion about the task. In this iteration, we refined the purpose to
|
|
214
|
+
clearly specify that the goal is to determine the correct category for a given word based on its context.",
|
|
207
215
|
"prompt": {
|
|
208
216
|
"purpose": "Determine the correct category for a given word by analyzing its context for clear meaning.",
|
|
209
217
|
"cautions": [
|
|
@@ -225,7 +233,10 @@ _PROMPT: str = """
|
|
|
225
233
|
},
|
|
226
234
|
{
|
|
227
235
|
"id": 2,
|
|
228
|
-
"analysis": "Next, we focused solely on the cautions section. The original cautions were generic and
|
|
236
|
+
"analysis": "Next, we focused solely on the cautions section. The original cautions were generic and
|
|
237
|
+
did not mention potential pitfalls like homonyms or polysemy. Failing to address these could result in
|
|
238
|
+
misclassification. Therefore, we added a specific caution regarding homonyms while keeping the purpose
|
|
239
|
+
and examples unchanged.",
|
|
229
240
|
"prompt": {
|
|
230
241
|
"purpose": "Determine the correct category for a given word by analyzing its context for clear meaning.",
|
|
231
242
|
"cautions": [
|
|
@@ -248,7 +259,10 @@ _PROMPT: str = """
|
|
|
248
259
|
},
|
|
249
260
|
{
|
|
250
261
|
"id": 3,
|
|
251
|
-
"analysis": "In this step, we improved the examples section to cover a broader range of scenarios and
|
|
262
|
+
"analysis": "In this step, we improved the examples section to cover a broader range of scenarios and
|
|
263
|
+
address potential ambiguities. By adding examples that include words with multiple interpretations
|
|
264
|
+
(such as 'Mercury' for both a planet and an element), we enhance clarity and ensure better coverage.
|
|
265
|
+
This iteration only modifies the examples section, leaving purpose and cautions intact.",
|
|
252
266
|
"prompt": {
|
|
253
267
|
"purpose": "Determine the correct category for a given word by analyzing its context for clear meaning.",
|
|
254
268
|
"cautions": [
|
|
@@ -328,11 +342,29 @@ def _render_prompt(prompt: FewShotPrompt) -> str:
|
|
|
328
342
|
|
|
329
343
|
|
|
330
344
|
class FewShotPromptBuilder:
|
|
345
|
+
"""Builder for creating few-shot prompts with validation.
|
|
346
|
+
|
|
347
|
+
Usage:
|
|
348
|
+
builder = (FewShotPromptBuilder()
|
|
349
|
+
.purpose("Your task description")
|
|
350
|
+
.example("input1", "output1") # At least one required
|
|
351
|
+
.example("input2", "output2")
|
|
352
|
+
.build())
|
|
353
|
+
|
|
354
|
+
Note:
|
|
355
|
+
Both .purpose() and at least one .example() call are required before
|
|
356
|
+
calling .build(), .improve(), or .get_object().
|
|
357
|
+
"""
|
|
358
|
+
|
|
331
359
|
_prompt: FewShotPrompt
|
|
332
|
-
_steps:
|
|
360
|
+
_steps: list[Step]
|
|
333
361
|
|
|
334
362
|
def __init__(self):
|
|
335
|
-
"""Initialize an empty FewShotPromptBuilder.
|
|
363
|
+
"""Initialize an empty FewShotPromptBuilder.
|
|
364
|
+
|
|
365
|
+
Note:
|
|
366
|
+
You must call .purpose() and at least one .example() before building.
|
|
367
|
+
"""
|
|
336
368
|
self._prompt = FewShotPrompt(purpose="", cautions=[], examples=[])
|
|
337
369
|
|
|
338
370
|
@classmethod
|
|
@@ -391,6 +423,8 @@ class FewShotPromptBuilder:
|
|
|
391
423
|
) -> "FewShotPromptBuilder":
|
|
392
424
|
"""Add a single input/output example.
|
|
393
425
|
|
|
426
|
+
At least one example is required before calling .build(), .improve(), or .get_object().
|
|
427
|
+
|
|
394
428
|
Args:
|
|
395
429
|
input_value (str | BaseModel): Example input; if a Pydantic model is
|
|
396
430
|
provided it is serialised to JSON.
|
|
@@ -409,45 +443,67 @@ class FewShotPromptBuilder:
|
|
|
409
443
|
|
|
410
444
|
def improve(
|
|
411
445
|
self,
|
|
412
|
-
client: OpenAI,
|
|
413
|
-
model_name: str,
|
|
414
|
-
|
|
415
|
-
top_p: float = 1.0,
|
|
446
|
+
client: OpenAI | None = None,
|
|
447
|
+
model_name: str | None = None,
|
|
448
|
+
**api_kwargs,
|
|
416
449
|
) -> "FewShotPromptBuilder":
|
|
417
450
|
"""Iteratively refine the prompt using an LLM.
|
|
418
451
|
|
|
419
452
|
The method calls a single LLM request that returns multiple
|
|
420
453
|
editing steps and stores each step for inspection.
|
|
421
454
|
|
|
455
|
+
When client is None, automatically creates a client using environment variables:
|
|
456
|
+
- For OpenAI: ``OPENAI_API_KEY``
|
|
457
|
+
- For Azure OpenAI: ``AZURE_OPENAI_API_KEY``, ``AZURE_OPENAI_BASE_URL``, ``AZURE_OPENAI_API_VERSION``
|
|
458
|
+
|
|
422
459
|
Args:
|
|
423
|
-
client (
|
|
424
|
-
model_name (str): Model identifier
|
|
425
|
-
|
|
426
|
-
top_p (float, optional): Nucleus sampling parameter. Defaults to 1.0.
|
|
460
|
+
client (OpenAI | None): Configured OpenAI client. If None, uses DI container with environment variables.
|
|
461
|
+
model_name (str | None): Model identifier. If None, uses default ``gpt-4.1-mini``.
|
|
462
|
+
**api_kwargs: Additional OpenAI API parameters (temperature, top_p, etc.).
|
|
427
463
|
|
|
428
464
|
Returns:
|
|
429
465
|
FewShotPromptBuilder: The current builder instance containing the refined prompt and iteration history.
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
ValueError: If the prompt is not valid (missing purpose or examples).
|
|
430
469
|
"""
|
|
470
|
+
# Validate before making API call to provide early feedback
|
|
471
|
+
self._validate()
|
|
431
472
|
|
|
432
|
-
|
|
433
|
-
|
|
473
|
+
_client = client or CONTAINER.resolve(OpenAI)
|
|
474
|
+
_model_name = model_name or CONTAINER.resolve(ResponsesModelName).value
|
|
475
|
+
|
|
476
|
+
response: ParsedResponse[Response] = _client.responses.parse(
|
|
477
|
+
model=_model_name,
|
|
434
478
|
instructions=_PROMPT,
|
|
435
479
|
input=Request(prompt=self._prompt).model_dump_json(),
|
|
436
|
-
temperature=temperature,
|
|
437
|
-
top_p=top_p,
|
|
438
480
|
text_format=Response,
|
|
481
|
+
**api_kwargs,
|
|
439
482
|
)
|
|
440
483
|
|
|
441
484
|
# keep the original prompt
|
|
442
485
|
self._steps = [Step(id=0, analysis="Original Prompt", prompt=self._prompt)]
|
|
443
486
|
|
|
444
487
|
# add the histories
|
|
445
|
-
|
|
446
|
-
|
|
488
|
+
if response.output_parsed:
|
|
489
|
+
for step in response.output_parsed.iterations:
|
|
490
|
+
self._steps.append(step)
|
|
447
491
|
|
|
448
492
|
# set the final prompt
|
|
449
493
|
self._prompt = self._steps[-1].prompt
|
|
450
494
|
|
|
495
|
+
# Validate the improved prompt to ensure examples weren't removed by LLM
|
|
496
|
+
try:
|
|
497
|
+
self._validate()
|
|
498
|
+
except ValueError as e:
|
|
499
|
+
_logger.warning(f"LLM produced invalid prompt during improve(): {e}")
|
|
500
|
+
# Restore original prompt if LLM produced invalid result
|
|
501
|
+
self._prompt = self._steps[0].prompt
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"LLM improvement failed to maintain required fields: {e}. "
|
|
504
|
+
"This may indicate an issue with the improvement instructions or model behavior."
|
|
505
|
+
)
|
|
506
|
+
|
|
451
507
|
return self
|
|
452
508
|
|
|
453
509
|
def explain(self) -> "FewShotPromptBuilder":
|
|
@@ -456,6 +512,10 @@ class FewShotPromptBuilder:
|
|
|
456
512
|
Returns:
|
|
457
513
|
FewShotPromptBuilder: The current builder instance.
|
|
458
514
|
"""
|
|
515
|
+
if not hasattr(self, "_steps") or not self._steps:
|
|
516
|
+
print("No improvement steps available. Call improve() first.")
|
|
517
|
+
return self
|
|
518
|
+
|
|
459
519
|
for previous, current in zip(self._steps, self._steps[1:]):
|
|
460
520
|
print(f"=== Iteration {current.id} ===\n")
|
|
461
521
|
print(f"Instruction: {current.analysis}")
|
|
@@ -479,9 +539,14 @@ class FewShotPromptBuilder:
|
|
|
479
539
|
"""
|
|
480
540
|
# Validate that 'purpose' and 'examples' are not empty.
|
|
481
541
|
if not self._prompt.purpose:
|
|
482
|
-
raise ValueError(
|
|
542
|
+
raise ValueError(
|
|
543
|
+
"Purpose is required. Please call .purpose('your purpose description') before building the prompt."
|
|
544
|
+
)
|
|
483
545
|
if not self._prompt.examples or len(self._prompt.examples) == 0:
|
|
484
|
-
raise ValueError(
|
|
546
|
+
raise ValueError(
|
|
547
|
+
"At least one example is required. Please add examples using "
|
|
548
|
+
".example('input', 'output') before building the prompt."
|
|
549
|
+
)
|
|
485
550
|
|
|
486
551
|
def get_object(self) -> FewShotPrompt:
|
|
487
552
|
"""Return the underlying FewShotPrompt object.
|
|
@@ -501,11 +566,13 @@ class FewShotPromptBuilder:
|
|
|
501
566
|
self._validate()
|
|
502
567
|
return self.build_xml()
|
|
503
568
|
|
|
504
|
-
def build_json(self, **kwargs
|
|
569
|
+
def build_json(self, **kwargs) -> str:
|
|
505
570
|
"""Build and return the prompt as a JSON string.
|
|
506
571
|
|
|
507
572
|
Args:
|
|
508
|
-
**kwargs: Keyword arguments forwarded to ``model_dump_json``.
|
|
573
|
+
**kwargs: Keyword arguments forwarded to Pydantic's ``model_dump_json``.
|
|
574
|
+
Common options include ``indent``, ``include``, ``exclude``,
|
|
575
|
+
``by_alias``, ``exclude_unset``, ``exclude_defaults``, ``exclude_none``.
|
|
509
576
|
|
|
510
577
|
Returns:
|
|
511
578
|
str: JSON representation of the prompt.
|
openaivec/_provider.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
import tiktoken
|
|
5
|
+
from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI
|
|
6
|
+
|
|
7
|
+
from openaivec import _di as di
|
|
8
|
+
from openaivec._model import (
|
|
9
|
+
AzureOpenAIAPIKey,
|
|
10
|
+
AzureOpenAIAPIVersion,
|
|
11
|
+
AzureOpenAIBaseURL,
|
|
12
|
+
EmbeddingsModelName,
|
|
13
|
+
OpenAIAPIKey,
|
|
14
|
+
ResponsesModelName,
|
|
15
|
+
)
|
|
16
|
+
from openaivec._schema import SchemaInferer
|
|
17
|
+
from openaivec._util import TextChunker
|
|
18
|
+
|
|
19
|
+
__all__ = []
|
|
20
|
+
|
|
21
|
+
CONTAINER = di.Container()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _build_missing_credentials_error(
|
|
25
|
+
openai_api_key: str | None,
|
|
26
|
+
azure_api_key: str | None,
|
|
27
|
+
azure_base_url: str | None,
|
|
28
|
+
azure_api_version: str | None,
|
|
29
|
+
) -> str:
|
|
30
|
+
"""Build a detailed error message for missing credentials.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
openai_api_key (str | None): The OpenAI API key value.
|
|
34
|
+
azure_api_key (str | None): The Azure OpenAI API key value.
|
|
35
|
+
azure_base_url (str | None): The Azure OpenAI base URL value.
|
|
36
|
+
azure_api_version (str | None): The Azure OpenAI API version value.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
str: A detailed error message with missing variables and setup instructions.
|
|
40
|
+
"""
|
|
41
|
+
lines = ["No valid OpenAI or Azure OpenAI credentials found.", ""]
|
|
42
|
+
|
|
43
|
+
# Check OpenAI
|
|
44
|
+
lines.append("Option 1: Set OPENAI_API_KEY for OpenAI")
|
|
45
|
+
if openai_api_key:
|
|
46
|
+
lines.append(" ✓ OPENAI_API_KEY is set")
|
|
47
|
+
else:
|
|
48
|
+
lines.append(" ✗ OPENAI_API_KEY is not set")
|
|
49
|
+
lines.append(' Example: export OPENAI_API_KEY="sk-..."')
|
|
50
|
+
lines.append("")
|
|
51
|
+
|
|
52
|
+
# Check Azure OpenAI
|
|
53
|
+
lines.append("Option 2: Set all Azure OpenAI variables")
|
|
54
|
+
azure_vars = [
|
|
55
|
+
("AZURE_OPENAI_API_KEY", azure_api_key, '"your-azure-api-key"'),
|
|
56
|
+
("AZURE_OPENAI_BASE_URL", azure_base_url, '"https://YOUR-RESOURCE-NAME.services.ai.azure.com/openai/v1/"'),
|
|
57
|
+
("AZURE_OPENAI_API_VERSION", azure_api_version, '"2024-12-01-preview"'),
|
|
58
|
+
]
|
|
59
|
+
for var_name, var_value, example in azure_vars:
|
|
60
|
+
if var_value:
|
|
61
|
+
lines.append(f" ✓ {var_name} is set")
|
|
62
|
+
else:
|
|
63
|
+
lines.append(f" ✗ {var_name} is not set")
|
|
64
|
+
lines.append(f" Example: export {var_name}={example}")
|
|
65
|
+
|
|
66
|
+
return "\n".join(lines)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _check_azure_v1_api_url(base_url: str) -> None:
|
|
70
|
+
"""Check if Azure OpenAI base URL uses the recommended v1 API format.
|
|
71
|
+
|
|
72
|
+
Issues a warning if the URL doesn't end with '/openai/v1/' to encourage
|
|
73
|
+
migration to the v1 API format as recommended by Microsoft.
|
|
74
|
+
|
|
75
|
+
Reference: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
base_url (str): The Azure OpenAI base URL to check.
|
|
79
|
+
"""
|
|
80
|
+
if base_url and not base_url.rstrip("/").endswith("/openai/v1"):
|
|
81
|
+
warnings.warn(
|
|
82
|
+
"⚠️ Azure OpenAI v1 API is recommended. Your base URL should end with '/openai/v1/'. "
|
|
83
|
+
f"Current URL: '{base_url}'. "
|
|
84
|
+
"Consider updating to: 'https://YOUR-RESOURCE-NAME.services.ai.azure.com/openai/v1/' "
|
|
85
|
+
"for better performance and future compatibility. "
|
|
86
|
+
"See: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/api-version-lifecycle",
|
|
87
|
+
UserWarning,
|
|
88
|
+
stacklevel=3,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def provide_openai_client() -> OpenAI:
|
|
93
|
+
"""Provide OpenAI client based on environment variables.
|
|
94
|
+
|
|
95
|
+
Automatically detects and prioritizes OpenAI over Azure OpenAI configuration.
|
|
96
|
+
Checks the following environment variables in order:
|
|
97
|
+
1. OPENAI_API_KEY - if set, creates standard OpenAI client
|
|
98
|
+
2. Azure OpenAI variables (AZURE_OPENAI_API_KEY, AZURE_OPENAI_BASE_URL,
|
|
99
|
+
AZURE_OPENAI_API_VERSION) - if all set, creates Azure OpenAI client
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
OpenAI: Configured OpenAI or AzureOpenAI client instance.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
ValueError: If no valid environment variables are found for either service.
|
|
106
|
+
"""
|
|
107
|
+
openai_api_key = CONTAINER.resolve(OpenAIAPIKey)
|
|
108
|
+
if openai_api_key.value:
|
|
109
|
+
return OpenAI()
|
|
110
|
+
|
|
111
|
+
azure_api_key = CONTAINER.resolve(AzureOpenAIAPIKey)
|
|
112
|
+
azure_base_url = CONTAINER.resolve(AzureOpenAIBaseURL)
|
|
113
|
+
azure_api_version = CONTAINER.resolve(AzureOpenAIAPIVersion)
|
|
114
|
+
|
|
115
|
+
if all(param.value for param in [azure_api_key, azure_base_url, azure_api_version]):
|
|
116
|
+
# Type checker support: values are guaranteed non-None by the all() check above
|
|
117
|
+
assert azure_api_key.value is not None
|
|
118
|
+
assert azure_base_url.value is not None
|
|
119
|
+
assert azure_api_version.value is not None
|
|
120
|
+
|
|
121
|
+
_check_azure_v1_api_url(azure_base_url.value)
|
|
122
|
+
return AzureOpenAI(
|
|
123
|
+
api_key=azure_api_key.value,
|
|
124
|
+
base_url=azure_base_url.value,
|
|
125
|
+
api_version=azure_api_version.value,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
raise ValueError(
|
|
129
|
+
_build_missing_credentials_error(
|
|
130
|
+
openai_api_key=openai_api_key.value,
|
|
131
|
+
azure_api_key=azure_api_key.value,
|
|
132
|
+
azure_base_url=azure_base_url.value,
|
|
133
|
+
azure_api_version=azure_api_version.value,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def provide_async_openai_client() -> AsyncOpenAI:
|
|
139
|
+
"""Provide asynchronous OpenAI client based on environment variables.
|
|
140
|
+
|
|
141
|
+
Automatically detects and prioritizes OpenAI over Azure OpenAI configuration.
|
|
142
|
+
Checks the following environment variables in order:
|
|
143
|
+
1. OPENAI_API_KEY - if set, creates standard AsyncOpenAI client
|
|
144
|
+
2. Azure OpenAI variables (AZURE_OPENAI_API_KEY, AZURE_OPENAI_BASE_URL,
|
|
145
|
+
AZURE_OPENAI_API_VERSION) - if all set, creates AsyncAzureOpenAI client
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
AsyncOpenAI: Configured AsyncOpenAI or AsyncAzureOpenAI client instance.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValueError: If no valid environment variables are found for either service.
|
|
152
|
+
"""
|
|
153
|
+
openai_api_key = CONTAINER.resolve(OpenAIAPIKey)
|
|
154
|
+
if openai_api_key.value:
|
|
155
|
+
return AsyncOpenAI()
|
|
156
|
+
|
|
157
|
+
azure_api_key = CONTAINER.resolve(AzureOpenAIAPIKey)
|
|
158
|
+
azure_base_url = CONTAINER.resolve(AzureOpenAIBaseURL)
|
|
159
|
+
azure_api_version = CONTAINER.resolve(AzureOpenAIAPIVersion)
|
|
160
|
+
|
|
161
|
+
if all(param.value for param in [azure_api_key, azure_base_url, azure_api_version]):
|
|
162
|
+
# Type checker support: values are guaranteed non-None by the all() check above
|
|
163
|
+
assert azure_api_key.value is not None
|
|
164
|
+
assert azure_base_url.value is not None
|
|
165
|
+
assert azure_api_version.value is not None
|
|
166
|
+
|
|
167
|
+
_check_azure_v1_api_url(azure_base_url.value)
|
|
168
|
+
return AsyncAzureOpenAI(
|
|
169
|
+
api_key=azure_api_key.value,
|
|
170
|
+
base_url=azure_base_url.value,
|
|
171
|
+
api_version=azure_api_version.value,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
raise ValueError(
|
|
175
|
+
_build_missing_credentials_error(
|
|
176
|
+
openai_api_key=openai_api_key.value,
|
|
177
|
+
azure_api_key=azure_api_key.value,
|
|
178
|
+
azure_base_url=azure_base_url.value,
|
|
179
|
+
azure_api_version=azure_api_version.value,
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def set_default_registrations():
|
|
185
|
+
CONTAINER.register(ResponsesModelName, lambda: ResponsesModelName("gpt-4.1-mini"))
|
|
186
|
+
CONTAINER.register(EmbeddingsModelName, lambda: EmbeddingsModelName("text-embedding-3-small"))
|
|
187
|
+
CONTAINER.register(OpenAIAPIKey, lambda: OpenAIAPIKey(os.getenv("OPENAI_API_KEY")))
|
|
188
|
+
CONTAINER.register(AzureOpenAIAPIKey, lambda: AzureOpenAIAPIKey(os.getenv("AZURE_OPENAI_API_KEY")))
|
|
189
|
+
CONTAINER.register(AzureOpenAIBaseURL, lambda: AzureOpenAIBaseURL(os.getenv("AZURE_OPENAI_BASE_URL")))
|
|
190
|
+
CONTAINER.register(
|
|
191
|
+
cls=AzureOpenAIAPIVersion,
|
|
192
|
+
provider=lambda: AzureOpenAIAPIVersion(os.getenv("AZURE_OPENAI_API_VERSION", "preview")),
|
|
193
|
+
)
|
|
194
|
+
CONTAINER.register(OpenAI, provide_openai_client)
|
|
195
|
+
CONTAINER.register(AsyncOpenAI, provide_async_openai_client)
|
|
196
|
+
CONTAINER.register(tiktoken.Encoding, lambda: tiktoken.get_encoding("o200k_base"))
|
|
197
|
+
CONTAINER.register(TextChunker, lambda: TextChunker(CONTAINER.resolve(tiktoken.Encoding)))
|
|
198
|
+
CONTAINER.register(
|
|
199
|
+
SchemaInferer,
|
|
200
|
+
lambda: SchemaInferer(
|
|
201
|
+
client=CONTAINER.resolve(OpenAI),
|
|
202
|
+
model_name=CONTAINER.resolve(ResponsesModelName).value,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
set_default_registrations()
|