Coverage for presidio_analyzer / predefined_recognizers / third_party / azure_openai_provider.py: 100%
54 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-29 09:03 +0000
1"""Azure OpenAI Provider for LangExtract."""
3import logging
4import os
5from typing import Optional
7try:
8 import langextract as lx
9 import openai
10 from langextract.providers.openai import OpenAILanguageModel
11 LANGEXTRACT_OPENAI_AVAILABLE = True
12except ImportError: # pragma: no cover
13 LANGEXTRACT_OPENAI_AVAILABLE = False
14 lx = None
15 OpenAILanguageModel = None
16 openai = None
18try:
19 from presidio_analyzer.llm_utils.azure_auth_helper import (
20 get_azure_credential,
21 get_bearer_token_provider_for_scope,
22 )
23 AZURE_IDENTITY_AVAILABLE = True
24except ImportError: # pragma: no cover
25 AZURE_IDENTITY_AVAILABLE = False
26 get_azure_credential = None
27 get_bearer_token_provider_for_scope = None
29logger = logging.getLogger("presidio-analyzer")
32if LANGEXTRACT_OPENAI_AVAILABLE:
33 class AzureOpenAILanguageModel(OpenAILanguageModel):
34 """
35 Custom LangExtract provider for Azure OpenAI.
37 This provider extends OpenAILanguageModel to support Azure-specific
38 authentication and endpoint configuration. It reuses all inference logic
39 from the parent class and only overrides client initialization.
41 Registered to handle model_id with "azure:" prefix (e.g., "azure:gpt-4o").
42 The recognizer adds this prefix automatically.
43 """
45 def __init__(
46 self,
47 model_id: str,
48 api_key: Optional[str] = None,
49 azure_endpoint: Optional[str] = None,
50 api_version: Optional[str] = None,
51 azure_deployment: Optional[str] = None,
52 azure_ad_token_provider: Optional[any] = None,
53 **kwargs
54 ):
55 """
56 Initialize Azure OpenAI provider.
58 :param model_id: Azure OpenAI deployment name or model identifier.
59 :param api_key: Azure OpenAI API key (or set AZURE_OPENAI_API_KEY
60 env var). If not provided, will automatically use managed
61 identity (ChainedTokenCredential).
62 :param azure_endpoint: Azure OpenAI endpoint URL
63 (or set AZURE_OPENAI_ENDPOINT env var).
64 :param api_version: Azure OpenAI API version
65 (or set AZURE_OPENAI_API_VERSION env var).
66 :param azure_deployment: Explicit deployment name (optional,
67 defaults to model_id).
68 :param azure_ad_token_provider: Custom Azure AD token provider function.
69 :param kwargs: Additional parameters passed to parent class.
70 """
71 # Strip 'azure:' prefix if present (added by recognizer)
72 if model_id.lower().startswith("azure:"):
73 clean_model_id = model_id[6:] # len("azure:") = 6
74 else:
75 clean_model_id = model_id
77 self.model_id = clean_model_id
78 self.api_key = api_key or os.environ.get("AZURE_OPENAI_API_KEY")
79 self.organization = None
80 self.format_type = kwargs.get('format_type', lx.data.FormatType.JSON)
81 self.temperature = kwargs.get('temperature', 0.0)
82 self.max_workers = kwargs.get('max_workers', 10)
83 self._extra_kwargs = kwargs
85 # Azure-specific configuration
86 self.azure_endpoint = azure_endpoint or os.environ.get(
87 "AZURE_OPENAI_ENDPOINT"
88 )
89 self.api_version = api_version or os.environ.get(
90 "AZURE_OPENAI_API_VERSION", "2024-02-15-preview"
91 )
92 self.azure_deployment = azure_deployment or clean_model_id
94 # Validate and initialize Azure OpenAI client
95 self._client = self._create_azure_openai_client(
96 azure_ad_token_provider
97 )
99 def _create_azure_openai_client(
100 self,
101 azure_ad_token_provider: Optional[any] = None
102 ):
103 """
104 Create and configure Azure OpenAI client with appropriate authentication.
106 :param azure_ad_token_provider: Optional custom token provider
107 :return: Configured AzureOpenAI client instance
108 :raises ValueError: If azure_endpoint is not provided
109 :raises ImportError: If azure-identity is needed but not available
110 """
111 if not self.azure_endpoint:
112 raise ValueError(
113 "Azure OpenAI endpoint is required. Set AZURE_OPENAI_ENDPOINT "
114 "environment variable or pass azure_endpoint parameter."
115 )
117 if not self.api_key or azure_ad_token_provider:
118 if not AZURE_IDENTITY_AVAILABLE and not azure_ad_token_provider:
119 raise ImportError(
120 "azure-identity is required for managed identity "
121 "authentication. Install it with: pip install azure-identity"
122 )
124 if azure_ad_token_provider:
125 token_provider = azure_ad_token_provider
126 credential_type = "custom token provider"
127 else:
128 token_provider = get_bearer_token_provider_for_scope(
129 "https://cognitiveservices.azure.com/.default"
130 )
131 credential_type = (
132 "DefaultAzureCredential (development)"
133 if os.getenv('ENV') == 'development'
134 else "ChainedTokenCredential"
135 )
137 client = openai.AzureOpenAI(
138 azure_ad_token_provider=token_provider,
139 azure_endpoint=self.azure_endpoint,
140 api_version=self.api_version
141 )
143 logger.debug(
144 f"Initialized Azure OpenAI provider with {credential_type}"
145 )
146 return client
147 else:
148 client = openai.AzureOpenAI(
149 api_key=self.api_key,
150 azure_endpoint=self.azure_endpoint,
151 api_version=self.api_version
152 )
154 logger.debug(
155 "Initialized Azure OpenAI provider with API key authentication"
156 )
157 return client
159 def _get_client_model_id(self) -> str:
160 """
161 Return the model/deployment identifier for API calls.
163 For Azure OpenAI, this is the deployment name, not the base model name.
165 :return: Azure deployment name.
166 """
167 return self.azure_deployment
168else: # pragma: no cover
169 class AzureOpenAILanguageModel:
170 """Placeholder when langextract is not installed."""
172 def __init__(self, *args, **kwargs):
173 raise ImportError(
174 "LangExtract with OpenAI support is not installed. "
175 "Install it with: pip install presidio-analyzer[langextract,openai] "
176 "or: pip install langextract[openai]"
177 )
180# Register the provider with LangExtract
181if LANGEXTRACT_OPENAI_AVAILABLE:
182 try:
183 @lx.providers.registry.register(
184 r'^azure:',
185 priority=20
186 )
187 class RegisteredAzureOpenAILanguageModel(AzureOpenAILanguageModel):
188 """
189 Registered version of Azure OpenAI provider for LangExtract.
191 This class is automatically discovered by LangExtract's provider registry
192 and used when model_id matches any of the registered patterns.
193 """
195 pass
197 logger.debug("Azure OpenAI provider registered with LangExtract")
198 except Exception as e: # pragma: no cover
199 logger.error(f"Failed to register Azure OpenAI provider: {e}")
200 raise