회기 연속성과 멀티 케이스 계약을 영속화

This commit is contained in:
Yun Chan 2026-09-01 11:45:16 +09:00
parent be08c0b573
commit 72353ecd82
26 changed files with 2170 additions and 127 deletions

View file

@ -19,8 +19,30 @@ class _FakeStreamReader:
return self.body
class _FakeStreamWriter:
def __init__(self):
self.writes: list[bytes] = []
self.closed = False
def write(self, data: bytes) -> None:
self.writes.append(data)
async def drain(self) -> None:
return None
def is_closing(self) -> bool:
return self.closed
def close(self) -> None:
self.closed = True
async def wait_closed(self) -> None:
return None
class _FakeAgyProcess:
def __init__(self, events: list[dict]):
self.stdin = _FakeStreamWriter()
self.stdout = _FakeStreamReader(
[(json.dumps(event, ensure_ascii=False) + "\n").encode("utf-8") for event in events]
)
@ -102,6 +124,7 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
},
]
}
long_payload = "x" * 24_001
with (
patch.object(provider_registry, "_binary", return_value="codex.exe"),
patch.object(
@ -321,6 +344,7 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
captured.append(args)
return process
long_payload = "x" * 24_001
with (
patch.object(provider_registry, "_binary", return_value="agy"),
patch.object(
@ -342,7 +366,7 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
result = await provider_registry.generate_with_provider(
request,
system_prompt="system",
user_payload="hello",
user_payload=long_payload,
)
self.assertEqual(result.text, "OK")
@ -350,10 +374,16 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(result.tokens_out, 2)
self.assertEqual(result.cost_usd, 0.00001515)
args = captured[0]
print_index = args.index("--print")
self.assertEqual(print_index, len(args) - 2)
self.assertIn("[시스템 지침]", args[-1])
self.assertIn("--input-format", args)
self.assertEqual(args[args.index("--input-format") + 1], "stream-json")
self.assertEqual(args[args.index("--output-format") + 1], "stream-json")
self.assertNotIn("--print", args)
self.assertTrue(all(long_payload not in str(arg) for arg in args))
self.assertTrue(process.stdin.closed)
sent = json.loads(b"".join(process.stdin.writes).decode("utf-8"))
self.assertEqual(sent["event"], "user")
self.assertIn("[시스템 지침]", sent["message"]["content"])
self.assertIn(long_payload, sent["message"]["content"])
async def test_agy_stream_forwards_live_deltas_without_repeating_final_response(self):
capabilities = provider_registry.EngineCapabilitiesResponse(
@ -451,9 +481,52 @@ class ProviderRegistryTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(events[-1].result.tokens_out, 2)
self.assertEqual(events[-1].result.cost_usd, 0.00001515)
args = captured[0]
self.assertIn("--input-format", args)
self.assertEqual(args[args.index("--input-format") + 1], "stream-json")
self.assertIn("--output-format", args)
self.assertEqual(args[args.index("--output-format") + 1], "stream-json")
self.assertEqual(args.index("--print"), len(args) - 2)
self.assertNotIn("--print", args)
async def test_agy_stdin_rejection_reaps_child_process(self):
request = GenerateRequest(
provider="agy_cli",
model="gemini-3.6-flash-high",
reasoning_effort="high",
messages=[EngineMessage(role="user", content="hello")],
)
process = _FakeAgyProcess([])
async def broken_drain() -> None:
raise BrokenPipeError()
process.stdin.drain = broken_drain # type: ignore[method-assign]
async def fake_create_subprocess_exec(*args, **kwargs):
return process
with (
patch.object(provider_registry, "_binary", return_value="agy.exe"),
patch.object(
provider_registry,
"_resolve_selection",
AsyncMock(return_value=("gemini-3.6-flash-high", "high")),
),
patch.object(
provider_registry.asyncio,
"create_subprocess_exec",
fake_create_subprocess_exec,
),
):
with self.assertRaisesRegex(provider_registry.ProviderError, "stdin 평가 입력"):
async for _event in provider_registry._stream_agy(
request,
system_prompt="system",
user_payload="hello",
):
pass
self.assertTrue(process.stdin.closed)
self.assertEqual(process.returncode, -9)
async def test_generation_rejects_model_effort_not_returned_by_provider(self):
capabilities = provider_registry.EngineCapabilitiesResponse(