mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-11 14:50:14 +00:00
OpenWorker: initial import
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
"""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)
|
||||
|
||||
|
||||
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",
|
||||
):
|
||||
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).
|
||||
assert len(MATRIX) < 30
|
||||
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():
|
||||
"""Together/Fireworks 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"):
|
||||
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://")
|
||||
Reference in New Issue
Block a user