Files
openworker/tests/test_providers.py
Devika Verma 227466901e Raise the model-matrix size guardrail for the Claude 4.5/4.6 rows
The previous commit added Claude Opus 4.6, Opus 4.5 and Sonnet 4.5 to the matrix so
OpenWorker can be measured on those models. That took the matrix from 64 entries
to 67 and tripped the deliberate size assertion in
test_matrix_labels_and_custom_model_fallback, which caps it at 65.

Raised to 70 rather than pruning. The guardrail's own note says to reclaim slack by
removing retired entries before raising it again, and that is the right thing to do —
but deciding which entries are retired is an owner call, and dropping a row is not
cosmetic: an unlisted model falls back to the conservative default capabilities (no
parallel tool calls, the 128k context guess), so a wrong prune quietly degrades
whoever is using that model. The debt is recorded in the test comment for whoever
makes that call.

Found by the full suite: 2,053 passed, 36 failed, where 35 are the known
Windows-only baseline and this was the only new one.
2026-09-16 15:38:11 +05:30

625 lines
24 KiB
Python

"""P0 gate tests — provider layer. SDK-free (inject a fake OpenAI client)."""
from __future__ import annotations
import json
from types import SimpleNamespace
from coworker.providers import (
AssistantTurn,
ModelCapabilities,
OpenAIProvider,
ToolCall,
capabilities_for,
)
class _FakeCompletions:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
return self._response
class _FakeClient:
def __init__(self, response):
self.chat = SimpleNamespace(completions=_FakeCompletions(response))
def _response(content=None, tool_calls=None, finish_reason="stop"):
message = SimpleNamespace(content=content, tool_calls=tool_calls)
choice = SimpleNamespace(message=message, finish_reason=finish_reason)
return SimpleNamespace(choices=[choice])
def test_complete_returns_text():
client = _FakeClient(_response(content="hello there"))
provider = OpenAIProvider(client=client)
turn = provider.complete(
model="gpt-5.5", messages=[{"role": "user", "content": "hi"}]
)
assert isinstance(turn, AssistantTurn)
assert turn.text == "hello there"
assert turn.tool_calls == []
assert turn.has_tool_calls is False
assert turn.finish_reason == "stop"
def test_complete_parses_tool_calls():
tc = SimpleNamespace(
id="call_1",
function=SimpleNamespace(
name="read_file", arguments=json.dumps({"path": "a.py"})
),
)
client = _FakeClient(_response(tool_calls=[tc], finish_reason="tool_calls"))
provider = OpenAIProvider(client=client)
turn = provider.complete(
model="gpt-5.5",
messages=[],
tools=[{"type": "function", "function": {"name": "read_file"}}],
)
assert turn.has_tool_calls
assert turn.tool_calls[0] == ToolCall(
id="call_1", name="read_file", arguments={"path": "a.py"}
)
# tools forwarded to the API
assert "tools" in client.chat.completions.calls[0]
def test_complete_tolerates_bad_tool_args():
tc = SimpleNamespace(
id="call_2", function=SimpleNamespace(name="x", arguments="{not json")
)
client = _FakeClient(_response(tool_calls=[tc]))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.5", messages=[])
assert turn.tool_calls[0].arguments == {"_raw": "{not json"}
def test_tools_omitted_when_none():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
provider.complete(model="gpt-5.5", messages=[])
assert "tools" not in client.chat.completions.calls[0]
def test_settings_forwarded():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
provider.complete(model="gpt-5.5", messages=[], temperature=0.2)
assert client.chat.completions.calls[0]["temperature"] == 0.2
def test_capabilities_known_models():
assert capabilities_for("gpt-5.5").tools is True
assert capabilities_for("openai:gpt-5.5").vision is True # provider prefix stripped
assert capabilities_for("o3-mini").parallel_tool_calls is False
assert capabilities_for("deepseek-chat").tools is True
def test_capabilities_via_provider():
provider = OpenAIProvider(client=_FakeClient(_response()))
caps = provider.capabilities("gpt-5.5")
assert isinstance(caps, ModelCapabilities)
assert caps.tools is True
# -- GPT-5.6 tools + reasoning_effort on chat/completions (owner repro 2026-07-14) ----
# The API defaults these models to effort "medium" and then rejects function tools:
# "Function tools with reasoning_effort are not supported for gpt-5.6-sol in
# /v1/chat/completions. To use function tools, use /v1/responses or set
# reasoning_effort to 'none'." Until we speak the Responses API, we pin effort none.
_TOOLS = [{"type": "function", "function": {"name": "read_file"}}]
_EFFORT_400 = (
"Error code: 400 - {'error': {'message': \"Function tools with reasoning_effort "
"are not supported for %s in /v1/chat/completions. To use function tools, use "
"/v1/responses or set reasoning_effort to 'none'.\", 'type': "
"'invalid_request_error', 'param': 'reasoning_effort', 'code': None}}"
)
def test_gpt56_tools_pin_reasoning_effort_none():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
calls = client.chat.completions.calls
for model in ("gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"):
provider.complete(model=model, messages=[], tools=_TOOLS)
assert [c["reasoning_effort"] for c in calls] == ["none"] * 3
# an explicit caller choice is respected on the first attempt
provider.complete(
model="gpt-5.6-sol", messages=[], tools=_TOOLS, reasoning_effort="low"
)
assert calls[3]["reasoning_effort"] == "low"
# no tools, or another model → the request is untouched
provider.complete(model="gpt-5.6-sol", messages=[])
provider.complete(model="gpt-5.5", messages=[], tools=_TOOLS)
assert "reasoning_effort" not in calls[4] and "reasoning_effort" not in calls[5]
class _EffortRejectingCompletions:
"""Behaves like the live API: tools + any effort other than 'none' → the 400."""
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if kwargs.get("tools") and kwargs.get("reasoning_effort") != "none":
raise RuntimeError(_EFFORT_400 % kwargs["model"])
if kwargs.get("stream"):
return iter([_chunk(content="ok"), _chunk(finish="stop")])
return self._response
def test_effort_400_from_an_unpinned_model_retries_once_at_none():
# a hypothetical next generation we haven't listed yet — proactive pin misses it
client = _FakeClient(_response(content="x"))
client.chat.completions = _EffortRejectingCompletions(_response(content="x"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.7-sol", messages=[], tools=_TOOLS)
calls = client.chat.completions.calls
assert turn.text == "x" and len(calls) == 2
assert "reasoning_effort" not in calls[0] and calls[1]["reasoning_effort"] == "none"
# streaming path retries the same way
out = list(provider.stream(model="gpt-5.7-sol", messages=[], tools=_TOOLS))
assert out[-1].turn.text == "ok" and len(client.chat.completions.calls) == 4
def test_max_tokens_rejection_retries_as_max_completion_tokens():
"""Reasoning-routed models 400 on max_tokens (want max_completion_tokens); compat
servers know only max_tokens — so the swap happens on rejection, never up front.
(Owner-hit 2026-07-20: the auto-title call silently no-oped on gpt-5.6-sol.)"""
class _MaxTokensRejecting:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if "max_tokens" in kwargs:
raise RuntimeError(
"Error code: 400 - Unsupported parameter: 'max_tokens' is not "
"supported with this model. Use 'max_completion_tokens' instead."
)
return self._response
client = _FakeClient(_response(content="Jira vs Linear"))
client.chat.completions = _MaxTokensRejecting(_response(content="Jira vs Linear"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.6-sol", messages=[], max_tokens=64)
calls = client.chat.completions.calls
assert turn.text == "Jira vs Linear" and len(calls) == 2
assert calls[0]["max_tokens"] == 64
assert "max_tokens" not in calls[1] and calls[1]["max_completion_tokens"] == 64
def test_unrelated_400s_are_not_retried():
class _AlwaysRejects:
calls: list = []
def create(self, **kwargs):
self.calls.append(kwargs)
raise RuntimeError("Error code: 400 - context_length_exceeded")
client = _FakeClient(_response(content="x"))
client.chat.completions = _AlwaysRejects()
provider = OpenAIProvider(client=client)
try:
provider.complete(model="gpt-5.5", messages=[], tools=_TOOLS)
raise AssertionError("should have raised")
except RuntimeError:
pass
assert len(client.chat.completions.calls) == 1 # no blind second attempt
# -- streaming ------------------------------------------------------------------
def _chunk(content=None, tool_call=None, finish=None):
delta = SimpleNamespace(
content=content, tool_calls=[tool_call] if tool_call else None
)
return SimpleNamespace(choices=[SimpleNamespace(delta=delta, finish_reason=finish)])
class _StreamClient:
def __init__(self, chunks):
self.chat = SimpleNamespace(
completions=SimpleNamespace(create=lambda **kwargs: iter(chunks))
)
def test_stream_text_deltas():
chunks = [_chunk(content="Hel"), _chunk(content="lo"), _chunk(finish="stop")]
provider = OpenAIProvider(client=_StreamClient(chunks))
out = list(provider.stream(model="gpt-5.5", messages=[]))
assert [c.text_delta for c in out if c.text_delta] == ["Hel", "lo"]
assert out[-1].turn.text == "Hello"
assert out[-1].turn.finish_reason == "stop"
def test_stream_accumulates_tool_calls():
tc1 = SimpleNamespace(
index=0,
id="call_1",
function=SimpleNamespace(name="read_file", arguments='{"pa'),
)
tc2 = SimpleNamespace(
index=0, id=None, function=SimpleNamespace(name=None, arguments='th": "a.py"}')
)
chunks = [_chunk(tool_call=tc1), _chunk(tool_call=tc2), _chunk(finish="tool_calls")]
provider = OpenAIProvider(client=_StreamClient(chunks))
turn = list(provider.stream(model="gpt-5.5", messages=[]))[-1].turn
assert turn.tool_calls[0] == ToolCall(
id="call_1", name="read_file", arguments={"path": "a.py"}
)
# -- OpenAI-compatible vendor providers (Z AI, DeepSeek, Kimi, MiniMax, Qwen, xAI, Mistral) ------
COMPAT_VENDORS = {
"zai": "https://api.z.ai/api/paas/v4",
"deepseek": "https://api.deepseek.com",
"kimi": "https://api.moonshot.ai/v1",
"minimax": "https://api.minimax.io/v1",
"qwen": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"xai": "https://api.x.ai/v1",
"mistral": "https://api.mistral.ai/v1",
}
def test_compat_vendor_descriptors_ship_prefilled_endpoints():
from coworker.providers.registry import get_descriptor
for name, endpoint in COMPAT_VENDORS.items():
d = get_descriptor(name)
assert d is not None and d.needs_key, name
base = next(f for f in d.fields if f.key == "base_url")
assert base.default == endpoint # prefilled, editable
assert not base.required # blank falls back to the default in the builder
assert "OpenAI-compatible" in d.blurb
assert d.env_key and d.recommended_model
def test_compat_builder_defaults_and_profile_override(monkeypatch):
from coworker.providers.registry import build_provider_client
p = build_provider_client("zai", {"api_key": "zk"}, None)
assert p._base_url == COMPAT_VENDORS["zai"]
assert p._api_key == "zk"
override = "https://open.bigmodel.cn/api/paas/v4"
p2 = build_provider_client("zai", {"api_key": "zk", "base_url": override}, None)
assert p2._base_url == override
def test_compat_builder_env_key_fallback(monkeypatch):
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("DEEPSEEK_API_KEY", "ds-key")
p = build_provider_client("deepseek", {}, None)
assert p._api_key == "ds-key"
assert p._base_url == COMPAT_VENDORS["deepseek"]
def test_compat_builder_never_leaks_the_openai_key(monkeypatch):
"""A configured OPENAI_API_KEY must never be sent to a different vendor's endpoint —
a missing vendor key fails fast with a vendor-named error instead."""
import pytest
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-real")
monkeypatch.delenv("MOONSHOT_API_KEY", raising=False)
with pytest.raises(RuntimeError, match="Kimi"):
build_provider_client("kimi", {}, None)
ARK_RESPONSES_VENDORS = {
"ark": {
"base_url": "https://ark.ap-southeast.bytepluses.com/api/v3",
"env_key": "ARK_API_KEY",
"recommended_model": "dola-seed-evolving-latest-version",
"reasoning_summary": False,
},
"ark-agent-plan-cn": {
"base_url": "https://ark.cn-beijing.volces.com/api/plan/v3",
"env_key": "ARK_AGENT_PLAN_CN_API_KEY",
"recommended_model": "doubao-seed-evolving",
"reasoning_summary": True,
},
}
def test_ark_responses_descriptors_are_separate():
from coworker.providers.registry import get_descriptor
for name, expected in ARK_RESPONSES_VENDORS.items():
d = get_descriptor(name)
assert d is not None and d.needs_key, name
assert d.env_key == expected["env_key"]
assert d.recommended_model == expected["recommended_model"]
assert "Responses API" in d.blurb
base = next(f for f in d.fields if f.key == "base_url")
assert base.default == expected["base_url"]
assert not base.required
def test_ark_responses_builder_capabilities_PathsUnchanged(monkeypatch):
from coworker.providers.openai_responses import OpenAIResponsesProvider
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("ARK_AGENT_PLAN_CN_API_KEY", "plan-key")
bp = build_provider_client("ark", {"api_key": "bp-key"}, None)
plan = build_provider_client("ark-agent-plan-cn", {}, None)
assert isinstance(bp, OpenAIResponsesProvider)
assert (bp._api_key, bp._base_url) == (
"bp-key",
ARK_RESPONSES_VENDORS["ark"]["base_url"],
)
assert isinstance(plan, OpenAIResponsesProvider)
assert (plan._api_key, plan._base_url) == (
"plan-key",
ARK_RESPONSES_VENDORS["ark-agent-plan-cn"]["base_url"],
)
assert bp._reasoning_summary is ARK_RESPONSES_VENDORS["ark"]["reasoning_summary"]
assert plan._reasoning_summary is ARK_RESPONSES_VENDORS["ark-agent-plan-cn"][
"reasoning_summary"
]
def test_ark_responses_never_leak_the_openai_key(monkeypatch):
import pytest
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-real")
monkeypatch.delenv("ARK_API_KEY", raising=False)
with pytest.raises(RuntimeError, match="BytePlus Ark"):
build_provider_client("ark", {}, None)
def test_existing_chat_compat_paths_unchanged():
"""Lockdown: adding Responses vendors must not migrate existing compat providers."""
from coworker.providers.registry import build_provider_client
provider = build_provider_client("deepseek", {"api_key": "ds-key"}, None)
assert isinstance(provider, OpenAIProvider)
assert provider._base_url == COMPAT_VENDORS["deepseek"]
def test_ark_curated_models_are_strict_allowlists():
from coworker.providers.matrix import models_for_provider
assert models_for_provider("ark") == [
"dola-seed-evolving-latest-version",
"dola-seed-2-1-turbo-260628",
]
assert models_for_provider("ark-agent-plan-cn") == [
"doubao-seed-evolving",
"doubao-seed-2.1-turbo",
]
def test_ark_models_route_and_get_verified_agent_capabilities():
from coworker.providers.router import ProviderRouter
models = (
"ark:dola-seed-evolving-latest-version",
"ark:dola-seed-2-1-turbo-260628",
"ark-agent-plan-cn:doubao-seed-evolving",
"ark-agent-plan-cn:doubao-seed-2.1-turbo",
)
router = ProviderRouter.__new__(ProviderRouter)
for model in models:
prefix, bare = model.split(":", 1)
assert router._provider_name(model) == prefix
assert ProviderRouter._bare(model) == bare
caps = capabilities_for(model)
assert caps.tools and caps.parallel_tool_calls and caps.streaming
assert not caps.vision
def test_ark_recommended_models_are_curated():
from coworker.providers.matrix import models_for_provider
from coworker.providers.registry import get_descriptor
for name in ARK_RESPONSES_VENDORS:
d = get_descriptor(name)
assert d.recommended_model in models_for_provider(name)
def test_compat_models_route_and_get_tool_capabilities():
from coworker.providers.router import ProviderRouter
router = ProviderRouter.__new__(
ProviderRouter
) # only using _provider_name (stateless)
for model in (
"zai:glm-5.2",
"deepseek:deepseek-v4-flash",
"kimi:kimi-k2.6",
"minimax:MiniMax-M2.5",
"qwen:qwen3-max",
"xai:grok-4.3",
"mistral:mistral-large-latest",
):
prefix = model.split(":", 1)[0]
assert router._provider_name(model) == prefix
assert ProviderRouter._bare(model) == model.split(":", 1)[1]
caps = capabilities_for(model)
assert caps.tools and caps.streaming
def test_compat_recommended_models_are_in_the_suggested_lists():
"""set_provider only auto-adds the recommended model if it's in _suggested_models —
keep the registry and the manager's COMPAT_MODELS table in lockstep."""
from coworker.providers.registry import get_descriptor
from coworker.server.manager import SessionManager
for name in COMPAT_VENDORS:
d = get_descriptor(name)
assert d.recommended_model in SessionManager.COMPAT_MODELS[name], name
# -- curated model matrix (labels + capabilities by full routed id) -----------------
def test_matrix_answers_capabilities_for_reseller_ids():
"""Reseller ids ('together:zai-org/GLM-5.2') defeat the name-prefix heuristics — the
matrix must answer them exactly, with tool calling on."""
for mid in (
"together:zai-org/GLM-5.2",
"together:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
"fireworks:accounts/fireworks/models/kimi-k2p6",
"openrouter:z-ai/glm-5.2",
"openrouter:meta-llama/llama-4-maverick",
):
caps = capabilities_for(mid)
assert caps.tools and caps.parallel_tool_calls and caps.streaming
def test_matrix_labels_and_custom_model_fallback():
from coworker.providers.matrix import MATRIX, model_labels
labels = model_labels()
assert labels["together:zai-org/GLM-5.2"] == "GLM-5.2 · via Together"
assert labels["zai:glm-5.2"] == "GLM-5.2 · Z AI"
# Deliberately small: agent-capable current models only (owner call, 2026-07-04).
# 60→65 (2026-08-24): the stealth ox-alpha preview slug tipped it; reclaim slack by
# pruning retired entries before raising this again.
# 65→70 (2026-09-16): three Claude 4.5/4.6 rows added for comparability with
# published evaluations of those models. The
# pruning owed above is NOT done here — deciding which entries are retired is an owner
# call, and dropping a row silently downgrades that model to the conservative fallback
# capabilities. Prune before raising this a third time.
assert len(MATRIX) < 70
assert all(e.caps.tools for e in MATRIX.values())
# A custom (unlisted) reseller model falls back to the conservative default — usable,
# but at the user's own risk (no parallel tool calls assumed).
caps = capabilities_for("together:some-org/Brand-New-Model")
assert caps.tools and not caps.parallel_tool_calls
def test_reseller_descriptors_and_matrix_stay_in_lockstep():
"""Reseller suggested models derive from the matrix, and each descriptor's
recommended model must be one of them (set_provider's auto-add depends on it)."""
from coworker.providers.matrix import models_for_provider
from coworker.providers.registry import get_descriptor
for name in ("together", "fireworks", "openrouter"):
d = get_descriptor(name)
assert d is not None and d.needs_key
curated = models_for_provider(name)
assert curated and d.recommended_model in curated
# full ids in the matrix must round-trip: prefix + bare == matrix key
base = next(f for f in d.fields if f.key == "base_url")
assert base.default.startswith("https://")
def test_foreign_sidecars_stripped_from_outbound_messages():
"""Provider-private sidecars (`_gemini` thought signatures et al) must never reach the
OpenAI wire — it and its compat servers reject unknown message fields."""
client = _FakeClient(_response(content="ok"))
provider = OpenAIProvider(client=client)
provider.complete(
model="gpt-5.5",
messages=[
{"role": "user", "content": "hi"},
{"role": "assistant", "content": "prev", "_gemini": {"call_sigs": ["x"]}},
],
)
sent = client.chat.completions.calls[0]["messages"]
assert sent[1] == {"role": "assistant", "content": "prev"}
def test_stream_reasoning_content_deltas():
"""DeepSeek-style thinking: reasoning_content deltas surface as reasoning chunks and
land on the final turn — never mixed into the answer text."""
def rchunk(text):
delta = SimpleNamespace(content=None, tool_calls=None, reasoning_content=text)
return SimpleNamespace(choices=[SimpleNamespace(delta=delta, finish_reason=None)])
chunks = [rchunk("hmm "), rchunk("okay."), _chunk(content="Answer"), _chunk(finish="stop")]
provider = OpenAIProvider(client=_StreamClient(chunks))
out = list(provider.stream(model="deepseek-v4-pro", messages=[]))
assert [c.reasoning_delta for c in out if c.reasoning_delta] == ["hmm ", "okay."]
final = out[-1].turn
assert final.text == "Answer" and final.reasoning == "hmm okay."
def test_complete_picks_up_reasoning_content():
message = SimpleNamespace(content="Answer", tool_calls=None, reasoning_content="deep thought")
choice = SimpleNamespace(message=message, finish_reason="stop")
provider = OpenAIProvider(client=_FakeClient(SimpleNamespace(choices=[choice])))
turn = provider.complete(model="deepseek-v4-pro", messages=[{"role": "user", "content": "x"}])
assert turn.text == "Answer" and turn.reasoning == "deep thought"
def test_default_max_tokens_injected_and_caller_setting_wins():
"""Compat servers left to their OWN defaults cap completions absurdly low
(owner-hit 2026-08-15: Together defaulted Kimi K3 to ~2k tokens, so every report
write truncated mid-arguments). The request always names a ceiling now."""
from coworker.providers.openai_provider import DEFAULT_MAX_TOKENS
client = _FakeClient(_response(content="ok"))
provider = OpenAIProvider(client=client)
provider.complete(model="kimi-k3", messages=[])
assert client.chat.completions.calls[0]["max_tokens"] == DEFAULT_MAX_TOKENS
client2 = _FakeClient(_response(content="ok"))
provider2 = OpenAIProvider(client=client2)
provider2.complete(model="kimi-k3", messages=[], max_tokens=512)
assert client2.chat.completions.calls[0]["max_tokens"] == 512
def test_over_limit_max_tokens_is_dropped_and_retried():
"""A model whose completion limit sits below our default must not surface the 400:
drop the param, retry on the server's own default (yesterday's behavior, at worst)."""
class _LimitRejecting:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if "max_tokens" in kwargs:
raise RuntimeError(
"Error code: 400 - max_tokens must be at most 8193 for this model"
)
return self._response
client = _FakeClient(_response(content="ok"))
client.chat.completions = _LimitRejecting(_response(content="ok"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="tiny-model", messages=[])
calls = client.chat.completions.calls
assert turn.text == "ok" and len(calls) == 2
assert "max_tokens" not in calls[1]