mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 05:31:46 +02:00
fix(hermes): retain reported charges from interrupted requests
Keep authenticated verified OpenRouter usage frames in the known subtotal even when transport fails, without certifying complete cost or token totals. Cover the real pinned SDK interruption plus a successful retry and update both reviewed runtime closure pins. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ecec024b12
commit
f0d8417801
3 files changed
+50
-5
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/** Reviewed native closures. Setup cannot adopt a digest from downloaded files. */
|
||||
export const HERMES_CLOSURES: Readonly<Record<string, string>> = Object.freeze({
|
||||
"darwin-arm64": "26371563768eb6578b80019f86d4a49cb55e36fd1593021cf9eac2cba945180c",
|
||||
"linux-x64": "06a4e2d0005f0600125b8b3032cb200d7d5ff21280ecd6c3b80f29d29a3491a8",
|
||||
"darwin-arm64": "fdf1369e1713fcf6dbaf43bb4a08e2cb819120bfef47d6047adfe6f7f47778a1",
|
||||
"linux-x64": "62f0071c5be0b5d07088b0d1f9d280db64390f7aeab395a526059afaa3573135",
|
||||
});
|
||||
@@ -80,13 +80,15 @@ class TurnBilling:
|
||||
with self._lock:
|
||||
self._closed = True
|
||||
completed = [r for r in self._requests if r.valid and r.done]
|
||||
priced = [r for r in completed if r.cost is not None]
|
||||
# A verified usage frame can precede a transport interruption.
|
||||
# Keep that known charge without certifying the request's totals.
|
||||
priced = [r for r in self._requests if r.cost_verified]
|
||||
tokenized = [r for r in completed if r.tokens is not None]
|
||||
scope_complete = owned_work_complete and not self._overflow and not self._children
|
||||
cost = sum(r.cost for r in priced)
|
||||
if cost > 1000000000000000:
|
||||
raise ValueError("Hermes reported turn cost exceeds its receipt bound")
|
||||
complete = scope_complete and len(priced) == len(self._requests)
|
||||
complete = scope_complete and len(completed) == len(self._requests) and len(priced) == len(self._requests)
|
||||
token_complete = scope_complete and len(tokenized) == len(self._requests)
|
||||
totals = tuple(sum(r.tokens[i] for r in tokenized) for i in range(4))
|
||||
if any(value > 9007199254740991 for value in totals):
|
||||
@@ -107,6 +109,7 @@ class WireReceipt:
|
||||
self.done = False
|
||||
self.tokens = None
|
||||
self.cost = None
|
||||
self.cost_verified = False
|
||||
self._seen_usage = False
|
||||
self._buffer = bytearray()
|
||||
self._data = []
|
||||
@@ -117,9 +120,11 @@ class WireReceipt:
|
||||
value = json.loads(data, parse_float=Decimal)
|
||||
except (ValueError, UnicodeError):
|
||||
self.valid = False
|
||||
self.cost_verified = False
|
||||
return
|
||||
if not isinstance(value, dict):
|
||||
self.valid = False
|
||||
self.cost_verified = False
|
||||
return
|
||||
usage = value.get("usage")
|
||||
if usage is not None:
|
||||
@@ -128,13 +133,16 @@ class WireReceipt:
|
||||
self.valid = False
|
||||
self._seen_usage = True
|
||||
self.tokens, self.cost = tokens, cost
|
||||
self.cost_verified = self.valid and cost is not None
|
||||
if value.get("error") is not None:
|
||||
self.valid = False
|
||||
self.cost_verified = False
|
||||
|
||||
def json(self, body):
|
||||
with self.owner._lock:
|
||||
if len(body) > MAX_BODY:
|
||||
self.valid = False
|
||||
self.cost_verified = False
|
||||
else:
|
||||
self._document(body)
|
||||
self.done = True
|
||||
|
||||
@@ -80,7 +80,19 @@ class Accounting(unittest.TestCase):
|
||||
self.assertFalse(ledger.finish()[0]["complete"])
|
||||
ledger = TurnBilling(); receipt = ledger.begin()
|
||||
receipt.json(document()); receipt.json(document("0.01"))
|
||||
self.assertFalse(ledger.finish()[0]["complete"])
|
||||
billed, _ = ledger.finish()
|
||||
self.assertFalse(billed["complete"])
|
||||
self.assertEqual(billed["reportedRequestCount"], 0)
|
||||
self.assertEqual(billed["amountUsdExact"], "0.000000000")
|
||||
|
||||
def test_verified_frame_before_an_unfinished_response_is_a_known_subtotal(self):
|
||||
ledger = TurnBilling(); receipt = ledger.begin()
|
||||
receipt.feed(b"data: " + document() + b"\n\n")
|
||||
billed, tokens = ledger.finish()
|
||||
self.assertEqual(billed["reportedRequestCount"], 1)
|
||||
self.assertEqual(billed["amountUsdExact"], "0.004200000")
|
||||
self.assertFalse(billed["complete"])
|
||||
self.assertIsNone(tokens)
|
||||
|
||||
def test_truncation_and_oversized_frames_are_unpriced(self):
|
||||
for wire in (b"data: " + document() + b"\n", b"data: " + b"x" * (MAX_FRAME + 1)):
|
||||
@@ -130,6 +142,31 @@ class Transport(unittest.TestCase):
|
||||
self.assertFalse(billed["complete"])
|
||||
self.assertIsNone(tokens)
|
||||
|
||||
def test_interrupted_stream_keeps_its_verified_charge_before_a_successful_retry(self):
|
||||
class InterruptedStream(httpx.SyncByteStream):
|
||||
def __iter__(self):
|
||||
yield b"data: " + document() + b"\n\n"
|
||||
raise httpx.ReadError("synthetic interrupted stream")
|
||||
|
||||
calls = []
|
||||
def reply(request):
|
||||
calls.append(request)
|
||||
if len(calls) == 1:
|
||||
return httpx.Response(200, headers={"content-type": "text/event-stream"}, stream=InterruptedStream())
|
||||
return httpx.Response(200, content=document())
|
||||
|
||||
with httpx.Client(transport=httpx.MockTransport(reply)) as client, OpenAI(http_client=client, api_key="synthetic-fixture-only", base_url="https://openrouter.ai/api/v1") as sdk:
|
||||
with self.assertRaises(httpx.ReadError):
|
||||
with sdk.chat.completions.create(model="fixture-model", messages=[], stream=True) as stream:
|
||||
list(stream)
|
||||
sdk.chat.completions.create(model="fixture-model", messages=[])
|
||||
billed, tokens = self.ledger.finish()
|
||||
self.assertEqual(billed["requestCount"], 2)
|
||||
self.assertEqual(billed["reportedRequestCount"], 2)
|
||||
self.assertEqual(billed["amountUsdExact"], "0.008400000")
|
||||
self.assertFalse(billed["complete"])
|
||||
self.assertIsNone(tokens)
|
||||
|
||||
def test_account_or_endpoint_changes_cannot_mint_billing_for_the_selected_account(self):
|
||||
for url, key in [("https://other.test/v1/chat/completions", "synthetic-fixture-only"),
|
||||
("https://openrouter.ai/api/v1/chat/completions", "another-fixture-account"),
|
||||
|
||||
Reference in new issue
Block a user