feat: preserve governed OpenAI tool messages in 1.4.0

Add faithful single-choice chat results, tool stream activity, error-frame retries, and isolated protocol caching.

Reject empty cached completions with the same value predicate as transport, add regression coverage, and adopt Apache-2.0.
This commit is contained in:
2026-10-01 14:32:19 -04:00
parent 0e29deac1a
commit 9185eab8c9
42 changed files with 2227 additions and 78 deletions
+111
View File
@@ -15,6 +15,7 @@ from polygateway.types import (
ChatRequest,
Effort,
LLMResponse,
OpenAIChatCompletion,
SourceConfig,
ThinkingObservation,
)
@@ -891,3 +892,113 @@ class TestCallStatsNotPoisoned:
without = ChatRequest(messages=_MSGS)
assert with_ctx.cache_namespace == without.cache_namespace
assert digest_messages(with_ctx.messages) == digest_messages(without.messages)
class TestOpenAICompletionCache:
"""协议缓存不得混模式或把字典冒充公共值对象。"""
def test_mode_isolation_preserves_text_key(self):
assert build_cache_key("m", _MSGS, "proj", None) == build_cache_key(
"m", _MSGS, "proj", None, response_mode="text"
)
assert build_cache_key("m", _MSGS, "proj", None) != build_cache_key(
"m", _MSGS, "proj", None, response_mode="openai"
)
@pytest.mark.parametrize(
"completion",
[
None,
{},
{
"message": {"content": "ok"},
"finish_reason": "stop",
"response_id": None,
"usage": None,
},
],
)
async def test_missing_or_bad_completion_misses(self, completion):
backend = InMemoryCache()
mw = CacheMW(backend=backend, model_fingerprint="m", default_namespace="proj", ttl_s=60)
raw = json.loads(mw._serialize(_resp()))
raw["completion"] = completion
assert (
mw._rehydrate(json.dumps(raw), ChatRequest(messages=_MSGS, response_mode="openai"))
is None
)
async def test_serialization_failure_does_not_fail_call(self):
mw = CacheMW(
backend=InMemoryCache(), model_fingerprint="m", default_namespace="proj", ttl_s=60
)
response = _resp(cost=object())
async def terminal(_):
return response
assert await mw(ChatRequest(messages=_MSGS), terminal) is response
@pytest.mark.parametrize(
"message",
[
{"role": "assistant"},
{"role": "assistant", "content": None, "tool_calls": []},
{"role": "assistant", "content": "", "tool_calls": []},
{"role": "assistant", "content": " \n", "tool_calls": []},
{"role": "assistant", "reasoning_content": "reason only"},
],
)
@pytest.mark.parametrize("cached_content", ["", "stale projection"])
async def test_openai_cache_without_output_misses_and_refills(message, cached_content):
"""空协议产物不能靠旧投影正文伪装成功;miss 后回源并覆写。"""
backend = InMemoryCache()
mw = _mw(backend)
request = ChatRequest(messages=_MSGS, response_mode="openai")
key = build_cache_key("m", _MSGS, "proj", None, response_mode="openai")
bad = _resp(
content=cached_content,
completion=OpenAIChatCompletion(message, "stop", "bad", None),
)
await backend.set(key, mw._serialize(bad), ttl_s=60)
fresh = _resp(
completion=OpenAIChatCompletion(
{"role": "assistant", "content": "cached"}, "stop", "fresh", None
)
)
terminal = _Terminal(fresh)
result = await mw(request, terminal)
assert terminal.calls == 1 and result is fresh and not result.cache_hit
hit = await mw(request, terminal)
assert terminal.calls == 1 and hit.cache_hit
assert hit.completion.response_id == "fresh"
async def test_openai_tool_only_cache_hits_without_origin():
"""有效工具本身是产物,空文本投影不妨碍命中。"""
backend = InMemoryCache()
mw = _mw(backend)
completion = OpenAIChatCompletion(
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"id": "call-1",
"type": "function",
"function": {"name": "echo", "arguments": "{}"},
}
],
},
"tool_calls",
"tool-response",
None,
)
request = ChatRequest(messages=_MSGS, response_mode="openai")
key = build_cache_key("m", _MSGS, "proj", None, response_mode="openai")
await backend.set(key, mw._serialize(_resp(content="", completion=completion)), ttl_s=60)
terminal = _Terminal(_resp())
hit = await mw(request, terminal)
assert hit.cache_hit and terminal.calls == 0
assert hit.completion == completion