swarmauri_llm_azureopenai 0.11.0.dev2__tar.gz
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.
- swarmauri_llm_azureopenai-0.11.0.dev2/PKG-INFO +79 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/README.md +57 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/pyproject.toml +46 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/swarmauri_llm_azureopenai/AzureOpenAIChatRetryPolicy.py +34 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/swarmauri_llm_azureopenai/AzureOpenAIModel.py +111 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/swarmauri_llm_azureopenai/AzureOpenAIToolModel.py +121 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/swarmauri_llm_azureopenai/__init__.py +16 -0
- swarmauri_llm_azureopenai-0.11.0.dev2/swarmauri_llm_azureopenai/_client.py +29 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swarmauri_llm_azureopenai
|
|
3
|
+
Version: 0.11.0.dev2
|
|
4
|
+
Summary: Provider-native Swarmauri chat and tool-calling adapters for Azure OpenAI.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Keywords: swarmauri,llm,azure openai,microsoft foundry,entra id,tool calling,streaming
|
|
7
|
+
Author: Jacob Stewart
|
|
8
|
+
Author-email: jacob@swarmauri.com
|
|
9
|
+
Requires-Python: >=3.10,<3.13
|
|
10
|
+
Classifier: Development Status :: 1 - Planning
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: swarmauri_base (>=0.11.0.dev5)
|
|
16
|
+
Requires-Dist: swarmauri_core (>=0.11.0.dev5)
|
|
17
|
+
Requires-Dist: swarmauri_standard (>=0.11.0.dev5)
|
|
18
|
+
Requires-Dist: swarmauri_transport_httpx (>=0.1.0.dev4)
|
|
19
|
+
Requires-Dist: swarmauri_transport_retry (>=0.1.0.dev1)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<a href="https://pepy.tech/project/swarmauri_llm_azureopenai"><img src="https://static.pepy.tech/badge/swarmauri_llm_azureopenai/month" alt="Downloads"></a>
|
|
26
|
+
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/community/swarmauri_llm_azureopenai/"><img src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/community/swarmauri_llm_azureopenai.svg" alt="Hits"></a>
|
|
27
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue" alt="Python"></a>
|
|
28
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/pypi/l/swarmauri_llm_azureopenai" alt="License"></a>
|
|
29
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/pypi/v/swarmauri_llm_azureopenai" alt="Release"></a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
# Swarmauri Azure OpenAI LLM
|
|
33
|
+
|
|
34
|
+
`swarmauri_llm_azureopenai` provides provider-native Swarmauri adapters for Azure OpenAI and Microsoft Foundry chat-completions deployments.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- `AzureOpenAIModel` directly implements the Swarmauri LLM base contract.
|
|
39
|
+
- `AzureOpenAIToolModel` directly implements the tool-LLM base contract.
|
|
40
|
+
- API-key authentication with Azure's `api-key` header.
|
|
41
|
+
- Refreshable Microsoft Entra bearer-token providers kept outside serialized state.
|
|
42
|
+
- Sync, async, streaming, and batch workflows against the current `/openai/v1` API.
|
|
43
|
+
- Persistent, injectable Swarmauri HTTP clients with conservative rate-limit retries.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv add swarmauri_llm_azureopenai
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install swarmauri_llm_azureopenai
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import os
|
|
59
|
+
|
|
60
|
+
from swarmauri_llm_azureopenai import AzureOpenAIModel
|
|
61
|
+
from swarmauri_standard.conversations.Conversation import Conversation
|
|
62
|
+
from swarmauri_standard.messages.HumanMessage import HumanMessage
|
|
63
|
+
|
|
64
|
+
conversation = Conversation(messages=[HumanMessage(content="Summarize this release.")])
|
|
65
|
+
model = AzureOpenAIModel(
|
|
66
|
+
endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
67
|
+
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
|
68
|
+
name="my-gpt-deployment",
|
|
69
|
+
)
|
|
70
|
+
model.predict(conversation)
|
|
71
|
+
print(conversation.get_last().content)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For keyless authentication, omit `api_key` and pass a callable as `token_provider`; it is invoked for every request so refreshed Entra tokens are used. Use `AzureOpenAIToolModel` with a Swarmauri `Toolkit` for function calling.
|
|
75
|
+
|
|
76
|
+
Pass an `IHttpClient` as `http_client` to integrate a custom transport or to test without network access. Pass an `IHttpRetryPolicy` as `retry_policy` to replace the default policy, which retries safe reads and rate-limited chat submissions without replaying failed POST requests for generic server errors.
|
|
77
|
+
|
|
78
|
+
See the [Azure OpenAI chat REST reference](https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/chat) and [Entra authentication guide](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/configure-entra-id).
|
|
79
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://pepy.tech/project/swarmauri_llm_azureopenai"><img src="https://static.pepy.tech/badge/swarmauri_llm_azureopenai/month" alt="Downloads"></a>
|
|
5
|
+
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/community/swarmauri_llm_azureopenai/"><img src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/community/swarmauri_llm_azureopenai.svg" alt="Hits"></a>
|
|
6
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue" alt="Python"></a>
|
|
7
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/pypi/l/swarmauri_llm_azureopenai" alt="License"></a>
|
|
8
|
+
<a href="https://pypi.org/project/swarmauri_llm_azureopenai/"><img src="https://img.shields.io/pypi/v/swarmauri_llm_azureopenai" alt="Release"></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
# Swarmauri Azure OpenAI LLM
|
|
12
|
+
|
|
13
|
+
`swarmauri_llm_azureopenai` provides provider-native Swarmauri adapters for Azure OpenAI and Microsoft Foundry chat-completions deployments.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- `AzureOpenAIModel` directly implements the Swarmauri LLM base contract.
|
|
18
|
+
- `AzureOpenAIToolModel` directly implements the tool-LLM base contract.
|
|
19
|
+
- API-key authentication with Azure's `api-key` header.
|
|
20
|
+
- Refreshable Microsoft Entra bearer-token providers kept outside serialized state.
|
|
21
|
+
- Sync, async, streaming, and batch workflows against the current `/openai/v1` API.
|
|
22
|
+
- Persistent, injectable Swarmauri HTTP clients with conservative rate-limit retries.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv add swarmauri_llm_azureopenai
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install swarmauri_llm_azureopenai
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
|
|
39
|
+
from swarmauri_llm_azureopenai import AzureOpenAIModel
|
|
40
|
+
from swarmauri_standard.conversations.Conversation import Conversation
|
|
41
|
+
from swarmauri_standard.messages.HumanMessage import HumanMessage
|
|
42
|
+
|
|
43
|
+
conversation = Conversation(messages=[HumanMessage(content="Summarize this release.")])
|
|
44
|
+
model = AzureOpenAIModel(
|
|
45
|
+
endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
|
46
|
+
api_key=os.environ["AZURE_OPENAI_API_KEY"],
|
|
47
|
+
name="my-gpt-deployment",
|
|
48
|
+
)
|
|
49
|
+
model.predict(conversation)
|
|
50
|
+
print(conversation.get_last().content)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For keyless authentication, omit `api_key` and pass a callable as `token_provider`; it is invoked for every request so refreshed Entra tokens are used. Use `AzureOpenAIToolModel` with a Swarmauri `Toolkit` for function calling.
|
|
54
|
+
|
|
55
|
+
Pass an `IHttpClient` as `http_client` to integrate a custom transport or to test without network access. Pass an `IHttpRetryPolicy` as `retry_policy` to replace the default policy, which retries safe reads and rate-limited chat submissions without replaying failed POST requests for generic server errors.
|
|
56
|
+
|
|
57
|
+
See the [Azure OpenAI chat REST reference](https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/chat) and [Entra authentication guide](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/configure-entra-id).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "swarmauri_llm_azureopenai"
|
|
3
|
+
version = "0.11.0.dev2"
|
|
4
|
+
description = "Provider-native Swarmauri chat and tool-calling adapters for Azure OpenAI."
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.10,<3.13"
|
|
8
|
+
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
9
|
+
keywords = ["swarmauri", "llm", "azure openai", "microsoft foundry", "entra id", "tool calling", "streaming"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 1 - Planning",
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
"Programming Language :: Python :: 3.10",
|
|
14
|
+
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"swarmauri_base>=0.11.0.dev5",
|
|
19
|
+
"swarmauri_core>=0.11.0.dev5",
|
|
20
|
+
"swarmauri_standard>=0.11.0.dev5",
|
|
21
|
+
"swarmauri_transport_httpx>=0.1.0.dev4",
|
|
22
|
+
"swarmauri_transport_retry>=0.1.0.dev1",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.uv.sources]
|
|
26
|
+
swarmauri_base = { workspace = true }
|
|
27
|
+
swarmauri_core = { workspace = true }
|
|
28
|
+
swarmauri_standard = { workspace = true }
|
|
29
|
+
swarmauri_transport_httpx = { workspace = true }
|
|
30
|
+
swarmauri_transport_retry = { workspace = true }
|
|
31
|
+
|
|
32
|
+
[project.entry-points."swarmauri.llms"]
|
|
33
|
+
AzureOpenAIModel = "swarmauri_llm_azureopenai:AzureOpenAIModel"
|
|
34
|
+
|
|
35
|
+
[project.entry-points."swarmauri.tool_llms"]
|
|
36
|
+
AzureOpenAIToolModel = "swarmauri_llm_azureopenai:AzureOpenAIToolModel"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["poetry-core>=1.0.0"]
|
|
40
|
+
build-backend = "poetry.core.masonry.api"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "ruff>=0.9.9"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
markers = ["unit: Unit tests"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Retry policy for Azure OpenAI chat operations."""
|
|
2
|
+
|
|
3
|
+
from swarmauri_core.transports.i_http_response import IHttpResponse
|
|
4
|
+
from swarmauri_transport_retry import ExponentialBackoffRetryPolicy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AzureOpenAIChatRetryPolicy(ExponentialBackoffRetryPolicy):
|
|
8
|
+
"""Retry safe reads and rate-limited chat submissions."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, **kwargs) -> None:
|
|
11
|
+
kwargs.setdefault("retryable_methods", {"GET", "POST"})
|
|
12
|
+
super().__init__(**kwargs)
|
|
13
|
+
|
|
14
|
+
def should_retry(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
method: str,
|
|
18
|
+
attempt: int,
|
|
19
|
+
response: IHttpResponse | None = None,
|
|
20
|
+
error: Exception | None = None,
|
|
21
|
+
) -> bool:
|
|
22
|
+
if method.upper() == "POST":
|
|
23
|
+
return (
|
|
24
|
+
attempt < self.max_attempts
|
|
25
|
+
and error is None
|
|
26
|
+
and response is not None
|
|
27
|
+
and response.status_code == 429
|
|
28
|
+
)
|
|
29
|
+
return super().should_retry(
|
|
30
|
+
method=method,
|
|
31
|
+
attempt=attempt,
|
|
32
|
+
response=response,
|
|
33
|
+
error=error,
|
|
34
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Any, Callable, ClassVar, FrozenSet, List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, PrivateAttr
|
|
4
|
+
from swarmauri_base.ComponentBase import ComponentBase
|
|
5
|
+
from swarmauri_base.llms.LLMBase import LLMBase
|
|
6
|
+
from swarmauri_core.transports.i_http_client import IHttpClient
|
|
7
|
+
from swarmauri_core.transports.i_http_retry_policy import IHttpRetryPolicy
|
|
8
|
+
from swarmauri_core.transports.i_managed_http_client import IManagedHttpClient
|
|
9
|
+
from swarmauri_standard.utils.provider_chat_transport import (
|
|
10
|
+
abatch_chat,
|
|
11
|
+
apredict_chat,
|
|
12
|
+
astream_chat,
|
|
13
|
+
batch_chat,
|
|
14
|
+
predict_chat,
|
|
15
|
+
stream_chat,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from ._client import build_azure_openai_client
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@ComponentBase.register_type()
|
|
22
|
+
class AzureOpenAIModel(LLMBase):
|
|
23
|
+
"""Provider-native Azure OpenAI chat-completions model."""
|
|
24
|
+
|
|
25
|
+
type: Literal["AzureOpenAIModel"] = "AzureOpenAIModel"
|
|
26
|
+
endpoint: str = Field(min_length=1)
|
|
27
|
+
api_version: str = "v1"
|
|
28
|
+
name: str = Field(min_length=1)
|
|
29
|
+
discover_models: bool = False
|
|
30
|
+
capabilities: ClassVar[FrozenSet[str]] = frozenset(
|
|
31
|
+
{"chat_completions", "structured_output", "streaming", "tools"}
|
|
32
|
+
)
|
|
33
|
+
_token_provider: Optional[Callable[[], Any]] = PrivateAttr(default=None)
|
|
34
|
+
_http_client: IHttpClient = PrivateAttr()
|
|
35
|
+
_retry_owned_by_transport: bool = PrivateAttr(default=True)
|
|
36
|
+
|
|
37
|
+
predict = predict_chat
|
|
38
|
+
apredict = apredict_chat
|
|
39
|
+
stream = stream_chat
|
|
40
|
+
astream = astream_chat
|
|
41
|
+
batch = batch_chat
|
|
42
|
+
abatch = abatch_chat
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
token_provider: Optional[Callable[[], Any]] = None,
|
|
48
|
+
http_client: IHttpClient | None = None,
|
|
49
|
+
retry_policy: IHttpRetryPolicy | None = None,
|
|
50
|
+
**data: Any,
|
|
51
|
+
) -> None:
|
|
52
|
+
selected_name = data.get("name")
|
|
53
|
+
if selected_name:
|
|
54
|
+
data.setdefault("allowed_models", [selected_name])
|
|
55
|
+
super().__init__(**data)
|
|
56
|
+
self._token_provider = token_provider
|
|
57
|
+
if self.api_key is None and self._token_provider is None:
|
|
58
|
+
raise ValueError("api_key or token_provider is required")
|
|
59
|
+
self._http_client = build_azure_openai_client(
|
|
60
|
+
base_url=self._api_root(),
|
|
61
|
+
timeout=self.timeout,
|
|
62
|
+
max_attempts=self.max_retries,
|
|
63
|
+
http_client=http_client,
|
|
64
|
+
retry_policy=retry_policy,
|
|
65
|
+
)
|
|
66
|
+
if self.discover_models:
|
|
67
|
+
self.allowed_models = self._get_models()
|
|
68
|
+
elif not self.allowed_models:
|
|
69
|
+
self.allowed_models = [self.name]
|
|
70
|
+
|
|
71
|
+
def _api_root(self) -> str:
|
|
72
|
+
root = self.endpoint.rstrip("/")
|
|
73
|
+
suffix = f"/openai/{self.api_version}"
|
|
74
|
+
return root if root.endswith(suffix) else f"{root}{suffix}"
|
|
75
|
+
|
|
76
|
+
def _build_endpoint(self) -> str:
|
|
77
|
+
return f"{self._api_root()}/chat/completions"
|
|
78
|
+
|
|
79
|
+
def _build_headers(self) -> dict[str, str]:
|
|
80
|
+
headers = {"Content-Type": "application/json"}
|
|
81
|
+
if self.api_key is not None:
|
|
82
|
+
headers["api-key"] = self.api_key.get_secret_value()
|
|
83
|
+
else:
|
|
84
|
+
token = self._token_provider()
|
|
85
|
+
headers["Authorization"] = (
|
|
86
|
+
f"Bearer {getattr(token, 'token', token)}"
|
|
87
|
+
)
|
|
88
|
+
return headers
|
|
89
|
+
|
|
90
|
+
def _get_models(self) -> List[str]:
|
|
91
|
+
if not self.discover_models:
|
|
92
|
+
return [self.name]
|
|
93
|
+
response = self._http_client.request(
|
|
94
|
+
"GET",
|
|
95
|
+
f"{self._api_root()}/models",
|
|
96
|
+
headers=self._build_headers(),
|
|
97
|
+
)
|
|
98
|
+
response.raise_for_status()
|
|
99
|
+
return [
|
|
100
|
+
item["id"]
|
|
101
|
+
for item in response.json().get("data", [])
|
|
102
|
+
if item.get("id")
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
def close(self) -> None:
|
|
106
|
+
if isinstance(self._http_client, IManagedHttpClient):
|
|
107
|
+
self._http_client.close()
|
|
108
|
+
|
|
109
|
+
async def aclose(self) -> None:
|
|
110
|
+
if isinstance(self._http_client, IManagedHttpClient):
|
|
111
|
+
await self._http_client.aclose()
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from typing import Any, Callable, ClassVar, FrozenSet, List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, PrivateAttr
|
|
4
|
+
from swarmauri_base.ComponentBase import ComponentBase
|
|
5
|
+
from swarmauri_base.tool_llms.ToolLLMBase import ToolLLMBase
|
|
6
|
+
from swarmauri_core.transports.i_http_client import IHttpClient
|
|
7
|
+
from swarmauri_core.transports.i_http_retry_policy import IHttpRetryPolicy
|
|
8
|
+
from swarmauri_core.transports.i_managed_http_client import IManagedHttpClient
|
|
9
|
+
from swarmauri_standard.schema_converters.OpenAISchemaConverter import (
|
|
10
|
+
OpenAISchemaConverter,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from ._client import build_azure_openai_client
|
|
14
|
+
from swarmauri_standard.utils.provider_chat_transport import (
|
|
15
|
+
abatch_tools,
|
|
16
|
+
apredict_tools,
|
|
17
|
+
astream_tools,
|
|
18
|
+
batch_tools,
|
|
19
|
+
format_messages,
|
|
20
|
+
predict_tools,
|
|
21
|
+
stream_tools,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@ComponentBase.register_type()
|
|
26
|
+
class AzureOpenAIToolModel(ToolLLMBase):
|
|
27
|
+
"""Provider-native Azure OpenAI tool-calling model."""
|
|
28
|
+
|
|
29
|
+
type: Literal["AzureOpenAIToolModel"] = "AzureOpenAIToolModel"
|
|
30
|
+
endpoint: str = Field(min_length=1)
|
|
31
|
+
api_version: str = "v1"
|
|
32
|
+
name: str = Field(min_length=1)
|
|
33
|
+
discover_models: bool = False
|
|
34
|
+
capabilities: ClassVar[FrozenSet[str]] = frozenset(
|
|
35
|
+
{"chat_completions", "streaming", "tools"}
|
|
36
|
+
)
|
|
37
|
+
_token_provider: Optional[Callable[[], Any]] = PrivateAttr(default=None)
|
|
38
|
+
_http_client: IHttpClient = PrivateAttr()
|
|
39
|
+
_retry_owned_by_transport: bool = PrivateAttr(default=True)
|
|
40
|
+
|
|
41
|
+
predict = predict_tools
|
|
42
|
+
apredict = apredict_tools
|
|
43
|
+
stream = stream_tools
|
|
44
|
+
astream = astream_tools
|
|
45
|
+
batch = batch_tools
|
|
46
|
+
abatch = abatch_tools
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
token_provider: Optional[Callable[[], Any]] = None,
|
|
52
|
+
http_client: IHttpClient | None = None,
|
|
53
|
+
retry_policy: IHttpRetryPolicy | None = None,
|
|
54
|
+
**data: Any,
|
|
55
|
+
) -> None:
|
|
56
|
+
selected_name = data.get("name")
|
|
57
|
+
if selected_name:
|
|
58
|
+
data.setdefault("allowed_models", [selected_name])
|
|
59
|
+
super().__init__(**data)
|
|
60
|
+
self._token_provider = token_provider
|
|
61
|
+
if self.api_key is None and self._token_provider is None:
|
|
62
|
+
raise ValueError("api_key or token_provider is required")
|
|
63
|
+
self._http_client = build_azure_openai_client(
|
|
64
|
+
base_url=self._api_root(),
|
|
65
|
+
timeout=self.timeout,
|
|
66
|
+
max_attempts=self.max_retries,
|
|
67
|
+
http_client=http_client,
|
|
68
|
+
retry_policy=retry_policy,
|
|
69
|
+
)
|
|
70
|
+
if self.discover_models:
|
|
71
|
+
self.allowed_models = self._get_models()
|
|
72
|
+
elif not self.allowed_models:
|
|
73
|
+
self.allowed_models = [self.name]
|
|
74
|
+
|
|
75
|
+
def get_schema_converter(self):
|
|
76
|
+
return OpenAISchemaConverter()
|
|
77
|
+
|
|
78
|
+
def _format_messages(self, messages):
|
|
79
|
+
return format_messages(messages)
|
|
80
|
+
|
|
81
|
+
def _api_root(self) -> str:
|
|
82
|
+
root = self.endpoint.rstrip("/")
|
|
83
|
+
suffix = f"/openai/{self.api_version}"
|
|
84
|
+
return root if root.endswith(suffix) else f"{root}{suffix}"
|
|
85
|
+
|
|
86
|
+
def _build_endpoint(self) -> str:
|
|
87
|
+
return f"{self._api_root()}/chat/completions"
|
|
88
|
+
|
|
89
|
+
def _build_headers(self) -> dict[str, str]:
|
|
90
|
+
headers = {"Content-Type": "application/json"}
|
|
91
|
+
if self.api_key is not None:
|
|
92
|
+
headers["api-key"] = self.api_key.get_secret_value()
|
|
93
|
+
else:
|
|
94
|
+
token = self._token_provider()
|
|
95
|
+
headers["Authorization"] = (
|
|
96
|
+
f"Bearer {getattr(token, 'token', token)}"
|
|
97
|
+
)
|
|
98
|
+
return headers
|
|
99
|
+
|
|
100
|
+
def _get_models(self) -> List[str]:
|
|
101
|
+
if not self.discover_models:
|
|
102
|
+
return [self.name]
|
|
103
|
+
response = self._http_client.request(
|
|
104
|
+
"GET",
|
|
105
|
+
f"{self._api_root()}/models",
|
|
106
|
+
headers=self._build_headers(),
|
|
107
|
+
)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
return [
|
|
110
|
+
item["id"]
|
|
111
|
+
for item in response.json().get("data", [])
|
|
112
|
+
if item.get("id")
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
if isinstance(self._http_client, IManagedHttpClient):
|
|
117
|
+
self._http_client.close()
|
|
118
|
+
|
|
119
|
+
async def aclose(self) -> None:
|
|
120
|
+
if isinstance(self._http_client, IManagedHttpClient):
|
|
121
|
+
await self._http_client.aclose()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from .AzureOpenAIChatRetryPolicy import AzureOpenAIChatRetryPolicy
|
|
4
|
+
from .AzureOpenAIModel import AzureOpenAIModel
|
|
5
|
+
from .AzureOpenAIToolModel import AzureOpenAIToolModel
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("swarmauri_llm_azureopenai")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "0.0.0"
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AzureOpenAIChatRetryPolicy",
|
|
14
|
+
"AzureOpenAIModel",
|
|
15
|
+
"AzureOpenAIToolModel",
|
|
16
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""HTTP client composition for Azure OpenAI models."""
|
|
2
|
+
|
|
3
|
+
from swarmauri_core.transports.i_http_client import IHttpClient
|
|
4
|
+
from swarmauri_core.transports.i_http_retry_policy import IHttpRetryPolicy
|
|
5
|
+
from swarmauri_transport_httpx import HttpxClient
|
|
6
|
+
from swarmauri_transport_retry import RetryingHttpClient
|
|
7
|
+
|
|
8
|
+
from .AzureOpenAIChatRetryPolicy import AzureOpenAIChatRetryPolicy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_azure_openai_client(
|
|
12
|
+
*,
|
|
13
|
+
base_url: str,
|
|
14
|
+
timeout: float,
|
|
15
|
+
max_attempts: int,
|
|
16
|
+
http_client: IHttpClient | None,
|
|
17
|
+
retry_policy: IHttpRetryPolicy | None,
|
|
18
|
+
) -> IHttpClient:
|
|
19
|
+
"""Build a persistent client while preserving injected ownership."""
|
|
20
|
+
concrete_client = http_client or HttpxClient(
|
|
21
|
+
base_url=base_url,
|
|
22
|
+
timeout=timeout,
|
|
23
|
+
)
|
|
24
|
+
return RetryingHttpClient(
|
|
25
|
+
client=concrete_client,
|
|
26
|
+
policy=retry_policy
|
|
27
|
+
or AzureOpenAIChatRetryPolicy(max_attempts=max(max_attempts, 1)),
|
|
28
|
+
own_client=http_client is None,
|
|
29
|
+
)
|