chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from opensquilla.provider import openai as openai_module
|
||||
from opensquilla.provider.anthropic import AnthropicProvider
|
||||
from opensquilla.provider.openai import OpenAIProvider
|
||||
from opensquilla.provider.types import (
|
||||
ChatConfig,
|
||||
ContentBlockImage,
|
||||
ContentBlockText,
|
||||
ContentBlockToolResult,
|
||||
DoneEvent,
|
||||
ErrorEvent,
|
||||
Message,
|
||||
ToolDefinition,
|
||||
ToolInputSchema,
|
||||
)
|
||||
|
||||
|
||||
def _openai_sse_body(model: str = "test-model") -> bytes:
|
||||
chunks = [
|
||||
{
|
||||
"model": model,
|
||||
"choices": [{"delta": {"content": "ok"}, "finish_reason": None}],
|
||||
},
|
||||
{
|
||||
"model": model,
|
||||
"choices": [{"delta": {}, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 2, "completion_tokens": 1},
|
||||
},
|
||||
]
|
||||
return b"".join(
|
||||
f"data: {json.dumps(chunk)}\n\n".encode() for chunk in chunks
|
||||
) + b"data: [DONE]\n\n"
|
||||
|
||||
|
||||
def _anthropic_sse_body(events: list[dict[str, Any]]) -> bytes:
|
||||
parts: list[bytes] = []
|
||||
for event in events:
|
||||
parts.append(f"event: {event['type']}\n".encode())
|
||||
parts.append(f"data: {json.dumps(event)}\n\n".encode())
|
||||
return b"".join(parts)
|
||||
|
||||
|
||||
def test_openai_final_request_proof_blocks_oversized_send(monkeypatch: Any) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
return httpx.Response(500)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.openai.httpx.AsyncClient", patched_async_client)
|
||||
provider = OpenAIProvider(api_key="test", model="gpt-test")
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
[Message(role="user", content="x" * 5000)],
|
||||
config=ChatConfig(provider_request_max_chars=1000),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert requests == []
|
||||
assert isinstance(events[0], ErrorEvent)
|
||||
assert events[0].code == "provider_request_budget_exhausted"
|
||||
proof = json.loads(events[0].message)
|
||||
assert proof["fits"] is False
|
||||
assert proof["retry_count"] == 2
|
||||
assert proof["top_contributors"][0]["chars"] == 5000
|
||||
|
||||
|
||||
def test_openai_env_request_proof_blocks_as_controlled_error(monkeypatch: Any) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
return httpx.Response(500)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setenv("OPENSQUILLA_PROVIDER_REQUEST_PROOF_MAX_CHARS", "1")
|
||||
monkeypatch.setattr("opensquilla.provider.openai.httpx.AsyncClient", patched_async_client)
|
||||
provider = OpenAIProvider(api_key="test", model="gpt-test")
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
[Message(role="user", content="hello")],
|
||||
config=ChatConfig(provider_request_max_chars=0),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert requests == []
|
||||
assert isinstance(events[0], ErrorEvent)
|
||||
assert events[0].code == "provider_request_budget_exhausted"
|
||||
|
||||
|
||||
def test_openai_final_request_proof_allows_native_image_payload(
|
||||
monkeypatch: Any,
|
||||
) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
payloads: list[dict[str, Any]] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
payloads.append(json.loads(request.content.decode("utf-8")))
|
||||
return httpx.Response(
|
||||
200,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=_openai_sse_body(),
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.openai.httpx.AsyncClient", patched_async_client)
|
||||
provider = OpenAIProvider(
|
||||
api_key="test",
|
||||
model="vision-test",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
provider_kind="openrouter",
|
||||
)
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
content=[
|
||||
ContentBlockText(text="describe this image"),
|
||||
ContentBlockImage(
|
||||
source_type="base64",
|
||||
media_type="image/png",
|
||||
data="a" * 5000,
|
||||
),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
messages,
|
||||
config=ChatConfig(provider_request_max_chars=1000),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert len(requests) == 1
|
||||
assert any(isinstance(event, DoneEvent) for event in events)
|
||||
media_url = payloads[0]["messages"][0]["content"][1]["image_url"]["url"]
|
||||
assert media_url.startswith("data:image/png;base64,")
|
||||
assert len(media_url) > 5000
|
||||
|
||||
|
||||
def test_anthropic_final_request_proof_blocks_oversized_send(monkeypatch: Any) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
return httpx.Response(500)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.anthropic.httpx.AsyncClient", patched_async_client)
|
||||
provider = AnthropicProvider(api_key="test", model="claude-test")
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
[Message(role="user", content="x" * 5000)],
|
||||
config=ChatConfig(provider_request_max_chars=1000),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert requests == []
|
||||
assert isinstance(events[0], ErrorEvent)
|
||||
assert events[0].code == "provider_request_budget_exhausted"
|
||||
proof = json.loads(events[0].message)
|
||||
assert proof["fits"] is False
|
||||
assert proof["retry_count"] == 2
|
||||
|
||||
|
||||
def test_anthropic_env_request_proof_blocks_as_controlled_error(monkeypatch: Any) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
return httpx.Response(500)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setenv("OPENSQUILLA_PROVIDER_REQUEST_PROOF_MAX_CHARS", "1")
|
||||
monkeypatch.setattr("opensquilla.provider.anthropic.httpx.AsyncClient", patched_async_client)
|
||||
provider = AnthropicProvider(api_key="test", model="claude-test")
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
[Message(role="user", content="hello")],
|
||||
config=ChatConfig(provider_request_max_chars=0),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert requests == []
|
||||
assert isinstance(events[0], ErrorEvent)
|
||||
assert events[0].code == "provider_request_budget_exhausted"
|
||||
|
||||
|
||||
def test_anthropic_final_request_proof_allows_native_image_payload(
|
||||
monkeypatch: Any,
|
||||
) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
payloads: list[dict[str, Any]] = []
|
||||
|
||||
body = _anthropic_sse_body(
|
||||
[
|
||||
{
|
||||
"type": "message_start",
|
||||
"message": {"id": "msg_1", "model": "claude-test", "usage": {}},
|
||||
},
|
||||
{
|
||||
"type": "content_block_delta",
|
||||
"delta": {"type": "text_delta", "text": "ok"},
|
||||
},
|
||||
{"type": "message_delta", "usage": {"output_tokens": 1}},
|
||||
{"type": "message_stop"},
|
||||
]
|
||||
)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
payloads.append(json.loads(request.content.decode("utf-8")))
|
||||
return httpx.Response(
|
||||
200,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=body,
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.anthropic.httpx.AsyncClient", patched_async_client)
|
||||
provider = AnthropicProvider(api_key="test", model="claude-test")
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
content=[
|
||||
ContentBlockText(text="describe this image"),
|
||||
ContentBlockImage(
|
||||
source_type="base64",
|
||||
media_type="image/png",
|
||||
data="a" * 5000,
|
||||
),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
messages,
|
||||
config=ChatConfig(provider_request_max_chars=1000),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert len(requests) == 1
|
||||
assert any(isinstance(event, DoneEvent) for event in events)
|
||||
media_source = payloads[0]["messages"][0]["content"][1]["source"]
|
||||
assert media_source == {
|
||||
"type": "base64",
|
||||
"media_type": "image/png",
|
||||
"data": "a" * 5000,
|
||||
}
|
||||
|
||||
|
||||
def _large_schema_tool() -> ToolDefinition:
|
||||
return ToolDefinition(
|
||||
name="large_schema_tool",
|
||||
description="Tool with enough schema text to prove final adapter payload accounting.",
|
||||
input_schema=ToolInputSchema(
|
||||
properties={
|
||||
"payload": {
|
||||
"type": "string",
|
||||
"description": "schema details " * 120,
|
||||
}
|
||||
},
|
||||
required=["payload"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_openai_final_request_proof_compacts_adapter_payload_with_tools(
|
||||
monkeypatch: Any,
|
||||
) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
payloads: list[dict[str, Any]] = []
|
||||
proofs: list[dict[str, Any]] = []
|
||||
budget = 3600
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
payloads.append(json.loads(request.content.decode("utf-8")))
|
||||
return httpx.Response(
|
||||
200,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=_openai_sse_body(),
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.openai.httpx.AsyncClient", patched_async_client)
|
||||
real_log = openai_module.log
|
||||
|
||||
class _ProofLogShim:
|
||||
# Swap the module's ``log`` attribute wholesale instead of patching
|
||||
# ``log.info``: getattr on the BoundLoggerLazyProxy (which monkeypatch's
|
||||
# save step performs) binds an ephemeral logger to the processor list of
|
||||
# that moment, and teardown would install that frozen bound method on
|
||||
# the proxy permanently, breaking capture_logs for later tests.
|
||||
def info(self, event: str, **kwargs: Any) -> None:
|
||||
if event == "provider.request_proof":
|
||||
proofs.append(kwargs)
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return getattr(real_log, name)
|
||||
|
||||
monkeypatch.setattr(openai_module, "log", _ProofLogShim())
|
||||
provider = OpenAIProvider(api_key="test", model="gpt-test")
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
content=[
|
||||
ContentBlockToolResult(
|
||||
tool_use_id="call_1",
|
||||
content="tool output " * 500,
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
messages,
|
||||
tools=[_large_schema_tool()],
|
||||
config=ChatConfig(
|
||||
system="system prompt " * 20,
|
||||
provider_request_max_chars=budget,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert len(requests) == 1
|
||||
assert any(isinstance(event, DoneEvent) for event in events)
|
||||
assert len(requests[0].content) <= budget
|
||||
assert any(
|
||||
"provider_request" in message.get("content", "")
|
||||
for message in payloads[0]["messages"]
|
||||
)
|
||||
proof = proofs[-1]
|
||||
assert proof["fits"] is True
|
||||
assert proof["retry_count"] >= 1
|
||||
assert proof["messages_chars"] > 0
|
||||
assert proof["tools_chars"] > 0
|
||||
assert proof["system_chars"] > 0
|
||||
assert proof["top_level_chars"] > 0
|
||||
|
||||
|
||||
def test_anthropic_final_request_proof_compacts_adapter_payload_with_tools(
|
||||
monkeypatch: Any,
|
||||
) -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
payloads: list[dict[str, Any]] = []
|
||||
proofs: list[dict[str, Any]] = []
|
||||
budget = 3600
|
||||
body = _anthropic_sse_body(
|
||||
[
|
||||
{
|
||||
"type": "message_start",
|
||||
"message": {"id": "msg_1", "model": "claude-test", "usage": {}},
|
||||
},
|
||||
{
|
||||
"type": "content_block_delta",
|
||||
"delta": {"type": "text_delta", "text": "ok"},
|
||||
},
|
||||
{"type": "message_delta", "usage": {"output_tokens": 1}},
|
||||
{"type": "message_stop"},
|
||||
]
|
||||
)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
payloads.append(json.loads(request.content.decode("utf-8")))
|
||||
return httpx.Response(
|
||||
200,
|
||||
headers={"content-type": "text/event-stream"},
|
||||
content=body,
|
||||
)
|
||||
|
||||
transport = httpx.MockTransport(handler)
|
||||
real_async_client = httpx.AsyncClient
|
||||
|
||||
def patched_async_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient:
|
||||
kwargs["transport"] = transport
|
||||
return real_async_client(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr("opensquilla.provider.anthropic.httpx.AsyncClient", patched_async_client)
|
||||
monkeypatch.setattr(
|
||||
"opensquilla.provider.anthropic.log.info",
|
||||
lambda event, **kwargs: (
|
||||
proofs.append(kwargs) if event == "provider.request_proof" else None
|
||||
),
|
||||
)
|
||||
provider = AnthropicProvider(api_key="test", model="claude-test")
|
||||
messages = [
|
||||
Message(
|
||||
role="user",
|
||||
content=[
|
||||
ContentBlockToolResult(
|
||||
tool_use_id="call_1",
|
||||
content="tool output " * 500,
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
async def run() -> list[Any]:
|
||||
return [
|
||||
event
|
||||
async for event in provider.chat(
|
||||
messages,
|
||||
tools=[_large_schema_tool()],
|
||||
config=ChatConfig(
|
||||
system="system prompt " * 20,
|
||||
provider_request_max_chars=budget,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
events = asyncio.run(run())
|
||||
|
||||
assert len(requests) == 1
|
||||
assert any(isinstance(event, DoneEvent) for event in events)
|
||||
assert len(requests[0].content) <= budget
|
||||
assert "provider_request" in payloads[0]["messages"][0]["content"][0]["content"]
|
||||
proof = proofs[-1]
|
||||
assert proof["fits"] is True
|
||||
assert proof["retry_count"] >= 1
|
||||
assert proof["messages_chars"] > 0
|
||||
assert proof["tools_chars"] > 0
|
||||
assert proof["system_chars"] > 0
|
||||
assert proof["top_level_chars"] > 0
|
||||
Reference in New Issue
Block a user