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
144 changes: 144 additions & 0 deletions plugins/model-providers/infersia/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"""Infersia provider profile.

Infersia serves open-weight models on dedicated GPUs through an
OpenAI-compatible chat-completions endpoint, and publishes the exact
quantisation, hardware and measured latency behind every model.

Address models by their catalogue ID, e.g. ``deepseek/deepseek-v4-flash-0731``
or ``qwen/qwen3.6-35b-a3b``. Appending ``:free`` selects a rate-limited free
variant where one is published.

DeepSeek V4 Flash is served at its full 1,048,576-token window rather than a
reduced slice, which is the main reason to reach for this provider on
long-context work.
"""

import json
import logging
import urllib.request

from providers import register_provider
from providers.base import ProviderProfile, _profile_user_agent

logger = logging.getLogger(__name__)


def _is_chat_model(entry: dict) -> bool:
"""Return True when *entry* is a model the chat_completions transport can use.

An OpenAI-shaped ``/v1/models`` list describes an account's whole catalogue,
not one endpoint's worth of it, and the model object carries no capability
field — so an entry alone cannot say which route answers it. Infersia
publishes an ``architecture`` block for exactly this, and the test is
``text`` on BOTH sides: a chat model takes text in and emits text out.

Checking the output side alone is not sufficient. Speech-to-text is
``audio->text``, so its output modality *is* ``["text"]`` and it passes a
one-sided test while answering ``/v1/audio/transcriptions`` rather than
``/v1/chat/completions``.

This is an allow-list, so a modality we have not accounted for is excluded
by default: a future non-text model drops out of the picker until this
profile is taught about it, instead of appearing and failing on first use.

An entry with no ``architecture`` block is kept. Absence is no information
rather than evidence of a non-chat model, and ``fallback_models`` is empty
here, so failing closed on it would leave an empty picker with nothing to
explain it.
"""
architecture = entry.get("architecture")
if not isinstance(architecture, dict):
return True

def _has_text(key: str) -> bool:
modalities = architecture.get(key)
if not isinstance(modalities, list):
return True
return any(str(m).strip().lower() == "text" for m in modalities)

return _has_text("input_modalities") and _has_text("output_modalities")


class InfersiaProfile(ProviderProfile):
"""Infersia — live catalogue, narrowed to the chat-capable models."""

def fetch_models(
self,
*,
api_key: str | None = None,
base_url: str | None = None,
timeout: float = 8.0,
) -> list[str] | None:
"""Fetch the live catalogue and keep only its chat models.

Same request as the base implementation; it is repeated here rather
than delegated because the base returns bare ID strings, and the
capability signal being filtered on lives in the fields those strings
are read out of.

``fallback_models`` is empty, so whatever this returns *is* the picker.
"""
effective_base = base_url or self.base_url
url = (self.models_url or "").strip()
if not url:
if not effective_base:
return None
url = effective_base.rstrip("/") + "/models"

from hermes_cli.urllib_security import open_credentialed_url

req = urllib.request.Request(url)
if api_key:
req.add_header("Authorization", f"Bearer {api_key}")
req.add_header("Accept", "application/json")
req.add_header("User-Agent", _profile_user_agent())
for k, v in self.default_headers.items():
req.add_header(k, v)

try:
with open_credentialed_url(req, timeout=timeout) as resp:
data = json.loads(resp.read().decode())
items = data if isinstance(data, list) else data.get("data", [])
return [
m["id"]
for m in items
if isinstance(m, dict) and "id" in m and _is_chat_model(m)
]
except Exception as exc:
logger.debug("fetch_models(infersia): %s", exc)
return None


infersia = InfersiaProfile(
name="infersia",
aliases=("infersia-ai",),
display_name="Infersia",
description="Infersia — open-weight models with published quantisation",
signup_url="https://infersia.com/dashboard/keys",
env_vars=("INFERSIA_API_KEY", "INFERSIA_BASE_URL"),
base_url="https://api.infersia.com/v1",
auth_type="api_key",
# Images are accepted inside tool-result messages. Re-verified against
# qwen/qwen3.6-35b-a3b on 2026-08-06, after Step 3.7 Flash (the model this
# was originally checked against) was retired: a two-pixel test image sent
# as a base64 data URI came back correctly described.
supports_vision=True,
# Prefix caching is automatic and unkeyed here. The endpoint tolerates a
# ``prompt_cache_key`` field rather than honouring it, and this flag is
# documented as opt-in for endpoints that explicitly accept it, so
# claiming it would advertise behaviour that does not exist.
supports_prompt_cache_key=False,
# Auxiliary model for cheap side tasks. Qwen3 8B is the smallest thing in
# the catalogue and the only hardcoded model id here; everything else is
# discovered live.
default_aux_model="qwen/qwen3-8b",
# Deliberately empty, following the DeepInfra profile's reasoning: the
# live catalogue at ``{base_url}/models`` is the source of truth, and it
# returns OpenAI-shaped entries with pricing and context length. When the
# fetch fails the picker shows nothing, which is better than routing
# someone to a model id that has since been retired — this catalogue is
# still changing week to week.
fallback_models=(),
)

register_provider(infersia)
5 changes: 5 additions & 0 deletions plugins/model-providers/infersia/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: infersia-provider
kind: model-provider
version: 1.0.0
description: Infersia — open-weight inference with published quantisation
author: Infersia (@infersia)
187 changes: 187 additions & 0 deletions tests/plugins/model_providers/test_infersia_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Unit tests for the Infersia provider profile's model discovery.

``fallback_models`` is empty, so ``fetch_models`` *is* the picker. The
catalogue served at ``/v1/models`` describes the whole account, so it also
carries models answering ``/v1/rerank``, ``/v1/audio/transcriptions`` and
``/v1/audio/speech``. Only the chat models may reach the picker.

The regression these tests exist for is the one-sided filter: speech-to-text
is ``audio->text``, so its ``output_modalities`` is ``["text"]`` and any test
written on the output side alone waves it through.
"""

from __future__ import annotations

import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
from typing import Any

import pytest

# Verbatim ``/v1/models`` entries, trimmed to the fields under test. Keeping
# the real shapes means a change to what the catalogue publishes shows up here
# as a failure rather than as a model appearing in someone's picker.
_CATALOGUE: list[dict[str, Any]] = [
{
"id": "deepseek/deepseek-v4-flash-0731",
"architecture": {
"modality": "text->text",
"input_modalities": ["text"],
"output_modalities": ["text"],
},
},
{
"id": "hexgrad/kokoro-82m",
"architecture": {
"modality": "text->audio",
"input_modalities": ["text"],
"output_modalities": ["audio"],
},
},
{
"id": "openai/whisper-large-v3-turbo",
"architecture": {
"modality": "audio->text",
"input_modalities": ["audio"],
"output_modalities": ["text"],
},
},
{
"id": "zeroentropy/zerank-2",
"architecture": {
"modality": "text->embedding",
"input_modalities": ["text"],
"output_modalities": ["embedding"],
},
},
{
"id": "qwen/qwen3.6-35b-a3b",
"architecture": {
"modality": "text+image->text",
"input_modalities": ["text", "image"],
"output_modalities": ["text"],
},
},
{
"id": "qwen/qwen3-8b",
"architecture": {
"modality": "text->text",
"input_modalities": ["text"],
"output_modalities": ["text"],
},
},
]


class _CatalogueHandler(BaseHTTPRequestHandler):
"""Serves ``/models`` with a configurable catalogue."""

models: list = _CATALOGUE

def do_GET(self):
if self.path.rstrip("/") == "/models":
body = json.dumps({"data": type(self).models}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()

def log_message(self, format, *args):
pass # suppress noise


def _serve(models):
_CatalogueHandler.models = models
server = HTTPServer(("127.0.0.1", 0), _CatalogueHandler)
Thread(target=server.serve_forever, daemon=True).start()
return server, server.server_address[1]


@pytest.fixture
def infersia_profile():
"""Resolve the registered Infersia profile via the provider registry.

Going through ``get_provider_profile`` keeps the test honest: if the
registered object is ever swapped back for a plain ``ProviderProfile`` the
filter disappears and these assertions collapse.
"""
import model_tools # noqa: F401 (importing triggers plugin discovery)
import providers

profile = providers.get_provider_profile("infersia")
assert profile is not None, "infersia provider profile must be registered"
return profile


def _fetch(profile, models):
server, port = _serve(models)
try:
return profile.fetch_models(
api_key="test-key", base_url=f"http://127.0.0.1:{port}"
)
finally:
server.shutdown()


class TestInfersiaModelDiscovery:
def test_only_chat_models_reach_the_picker(self, infersia_profile):
assert _fetch(infersia_profile, _CATALOGUE) == [
"deepseek/deepseek-v4-flash-0731",
"qwen/qwen3.6-35b-a3b",
"qwen/qwen3-8b",
]

def test_speech_to_text_is_excluded_despite_text_output(self, infersia_profile):
"""The regression guard: ``audio->text`` outputs text but is not chat."""
entry = [m for m in _CATALOGUE if m["id"] == "openai/whisper-large-v3-turbo"]
assert entry[0]["architecture"]["output_modalities"] == ["text"]
assert _fetch(infersia_profile, entry) == []

@pytest.mark.parametrize(
"model_id",
["hexgrad/kokoro-82m", "zeroentropy/zerank-2"],
)
def test_non_text_output_is_excluded(self, infersia_profile, model_id):
entry = [m for m in _CATALOGUE if m["id"] == model_id]
assert _fetch(infersia_profile, entry) == []

def test_unknown_modality_is_excluded_by_default(self, infersia_profile):
"""The filter is an allow-list, so a modality it has never seen drops."""
assert (
_fetch(
infersia_profile,
[
{
"id": "example/video-model",
"architecture": {
"input_modalities": ["video"],
"output_modalities": ["video"],
},
}
],
)
== []
)

def test_default_aux_model_survives_the_filter(self, infersia_profile):
"""``default_aux_model`` is the one hardcoded id; it must still be live."""
assert infersia_profile.default_aux_model in _fetch(
infersia_profile, _CATALOGUE
)

def test_entry_without_architecture_is_kept(self, infersia_profile):
"""Absence is no information, and an empty picker explains nothing."""
assert _fetch(infersia_profile, [{"id": "some/model"}]) == ["some/model"]

def test_unreachable_endpoint_returns_none(self, infersia_profile):
"""None means "no catalogue", which is distinct from "no chat models"."""
assert (
infersia_profile.fetch_models(
api_key="test-key", base_url="http://127.0.0.1:1", timeout=1.0
)
is None
)