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:
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user