70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
"""Regression tests for nvidia_openai_embed AsyncOpenAI cleanup.
|
|
|
|
Mirrors the cleanup contract established for the Anthropic backend
|
|
(``tests/llm/anthropic_impl/test_anthropic_client_cleanup.py``, PR #3261):
|
|
the per-call ``AsyncOpenAI`` client must be released on both the success and
|
|
error paths so its httpx connection pool does not leak one pool per call.
|
|
|
|
``nvidia_openai_embed`` now holds the client in an ``async with`` (matching
|
|
``openai_embed``); these tests pin that behavior. The raw function is invoked
|
|
via ``.func.__wrapped__`` to bypass the ``EmbeddingFunc`` and tenacity
|
|
``@retry`` wrappers.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from lightrag.llm.nvidia_openai import nvidia_openai_embed
|
|
|
|
|
|
class _FakeAsyncOpenAI:
|
|
"""Mimics ``AsyncOpenAI`` as an async context manager.
|
|
|
|
The real SDK's ``__aexit__`` closes the client; this fake does the same so
|
|
the test can assert on ``close()``.
|
|
"""
|
|
|
|
def __init__(self, *, create):
|
|
self.embeddings = SimpleNamespace(create=create)
|
|
self.close = AsyncMock()
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
await self.close()
|
|
|
|
|
|
def _make_response(embedding=(0.1, 0.2, 0.3)):
|
|
return SimpleNamespace(data=[SimpleNamespace(embedding=list(embedding))])
|
|
|
|
|
|
@pytest.mark.offline
|
|
@pytest.mark.asyncio
|
|
async def test_client_closed_after_success():
|
|
"""Successful embed: client.close() runs after the result is returned."""
|
|
fake = _FakeAsyncOpenAI(create=AsyncMock(return_value=_make_response()))
|
|
|
|
with patch("lightrag.llm.nvidia_openai.AsyncOpenAI", return_value=fake):
|
|
result = await nvidia_openai_embed.func.__wrapped__(texts=["hello"])
|
|
|
|
assert result.tolist() == [[0.1, 0.2, 0.3]]
|
|
fake.embeddings.create.assert_awaited_once()
|
|
fake.close.assert_awaited()
|
|
|
|
|
|
@pytest.mark.offline
|
|
@pytest.mark.asyncio
|
|
async def test_client_closed_on_error():
|
|
"""API error: client.close() runs before the error propagates."""
|
|
fake = _FakeAsyncOpenAI(create=AsyncMock(side_effect=RuntimeError("boom")))
|
|
|
|
with patch("lightrag.llm.nvidia_openai.AsyncOpenAI", return_value=fake):
|
|
with pytest.raises(RuntimeError, match="boom"):
|
|
await nvidia_openai_embed.func.__wrapped__(texts=["hello"])
|
|
|
|
fake.embeddings.create.assert_awaited_once()
|
|
fake.close.assert_awaited()
|