feat(llm): añade AzureOpenAIProvider y OpenAIProvider con retry exponencial

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Juan
2026-05-10 10:30:05 +02:00
co-authored by Claude Opus 4.7
parent 45b2b9367c
commit 4630fa214d
2 changed files with 138 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
"""Proveedor Azure OpenAI con retry exponencial y fallback configurable."""
from __future__ import annotations
import asyncio
import time
from typing import Any
from openai import AsyncAzureOpenAI
from agentforge_core.llm.base import CompletionResult, Message
class AzureOpenAIProvider:
"""Cliente Azure OpenAI con retry 3x backoff exponencial."""
name = "azure"
def __init__(
self,
endpoint: str,
api_key: str,
deployment: str,
api_version: str,
) -> None:
if not (endpoint and api_key and deployment):
raise ValueError("Azure OpenAI requiere endpoint, api_key y deployment.")
self._client = AsyncAzureOpenAI(
azure_endpoint=endpoint,
api_key=api_key,
api_version=api_version,
)
self._deployment = deployment
async def complete(
self,
messages: list[Message],
schema: dict[str, Any] | None = None,
temperature: float = 0.2,
max_tokens: int = 2000,
) -> CompletionResult:
delays = [1.0, 2.0, 4.0]
last_exc: Exception | None = None
for attempt, delay in enumerate([0.0, *delays]):
if delay:
await asyncio.sleep(delay)
try:
return await self._call_once(messages, temperature, max_tokens)
except Exception as exc:
last_exc = exc
if attempt >= len(delays):
break
assert last_exc is not None
raise last_exc
async def _call_once(
self,
messages: list[Message],
temperature: float,
max_tokens: int,
) -> CompletionResult:
start = time.perf_counter()
resp = await self._client.chat.completions.create( # type: ignore[call-overload]
model=self._deployment,
messages=[m.model_dump() for m in messages],
temperature=temperature,
max_tokens=max_tokens,
response_format={"type": "json_object"},
)
elapsed = int((time.perf_counter() - start) * 1000)
choice = resp.choices[0]
return CompletionResult(
content=choice.message.content or "",
model=resp.model,
tokens_in=resp.usage.prompt_tokens if resp.usage else 0,
tokens_out=resp.usage.completion_tokens if resp.usage else 0,
latency_ms=elapsed,
)
+60
View File
@@ -0,0 +1,60 @@
"""Proveedor OpenAI directo (fallback)."""
from __future__ import annotations
import asyncio
import time
from typing import Any
from openai import AsyncOpenAI
from agentforge_core.llm.base import CompletionResult, Message
class OpenAIProvider:
"""Cliente OpenAI con misma política de retry que Azure."""
name = "openai"
def __init__(self, api_key: str, model: str = "gpt-4o") -> None:
if not api_key:
raise ValueError("OpenAIProvider requiere api_key.")
self._client = AsyncOpenAI(api_key=api_key)
self._model = model
async def complete(
self,
messages: list[Message],
schema: dict[str, Any] | None = None,
temperature: float = 0.2,
max_tokens: int = 2000,
) -> CompletionResult:
delays = [1.0, 2.0, 4.0]
last_exc: Exception | None = None
for attempt, delay in enumerate([0.0, *delays]):
if delay:
await asyncio.sleep(delay)
try:
start = time.perf_counter()
resp = await self._client.chat.completions.create( # type: ignore[call-overload]
model=self._model,
messages=[m.model_dump() for m in messages],
temperature=temperature,
max_tokens=max_tokens,
response_format={"type": "json_object"},
)
elapsed = int((time.perf_counter() - start) * 1000)
choice = resp.choices[0]
return CompletionResult(
content=choice.message.content or "",
model=resp.model,
tokens_in=resp.usage.prompt_tokens if resp.usage else 0,
tokens_out=resp.usage.completion_tokens if resp.usage else 0,
latency_ms=elapsed,
)
except Exception as exc:
last_exc = exc
if attempt >= len(delays):
break
assert last_exc is not None
raise last_exc