Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ PromptLens runs golden test sets against multiple models, scores outputs using L
- **Multiple Export Formats** - HTML, JSON, CSV, and Markdown outputs
- **Parallel Execution** - Async execution with configurable concurrency and retry logic
- **Portable & Local** - No cloud backend, all data stays on your machine
- **Safer HTTP Integrations** - Unparseable HTTP provider responses fail fast with clear error context
- **Easy to Extend** - Plugin architecture for custom providers, judges, and exporters

---
Expand Down
5 changes: 5 additions & 0 deletions promptlens/providers/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ async def _make_request() -> ModelResponse:

# Extract content (try common response formats)
content = self._extract_content(data)
if not content.strip():
raise ValueError(
"HTTP provider response did not contain parseable text content. "
f"Top-level keys: {sorted(data.keys()) if isinstance(data, dict) else type(data).__name__}"
)

# Local models typically don't provide token counts or cost
return ModelResponse(
Expand Down
51 changes: 51 additions & 0 deletions tests/test_http_provider_empty_content_guard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from unittest.mock import patch

import pytest

from promptlens.models.config import ProviderConfig
from promptlens.providers.http import HTTPProvider


def _provider() -> HTTPProvider:
return HTTPProvider(
ProviderConfig(
name="http",
model="test-model",
endpoint="http://localhost:11434/api/generate",
)
)


@pytest.mark.asyncio
async def test_generate_returns_error_when_content_unparseable() -> None:
provider = _provider()

class _FakeResponse:
def raise_for_status(self) -> None:
return None

async def json(self):
return {"foo": "bar"}

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc, tb):
return None

class _FakeSession:
def post(self, *args, **kwargs):
return _FakeResponse()

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc, tb):
return None

with patch("promptlens.providers.http.aiohttp.ClientSession", return_value=_FakeSession()):
result = await provider.generate("hello")

assert result.error is not None
assert "did not contain parseable text content" in result.error
assert result.content == ""