fix(engine): convert apple_fm_shim cumulative snapshots to deltas (#464)

Apple FM's stream_response yields cumulative text snapshots, but
OpenAI-compatible clients concatenate delta.content — so streamed
responses were duplicated/stuttered (#378). Diff each snapshot against
the last and emit only the incremental suffix; fall back to the full
snapshot if the model revises earlier text, and skip empty deltas.

Rebased on #377 (apple_fm_sdk migration): uses the options= streaming
API. Adds a stubbed-SDK regression test asserting deltas are
incremental, not cumulative.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-06-01 10:11:53 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 5270149ea5
commit bd1ab115ec
2 changed files with 51 additions and 2 deletions
+15 -2
View File
@@ -128,10 +128,23 @@ async def chat_completions(
async def generate():
cid = f"chatcmpl-{uuid.uuid4().hex[:12]}"
async for token in session.stream_response(
# Apple FM yields cumulative snapshots, OpenAI clients expect
# incremental deltas — diff against the last snapshot to convert
# (see #378).
sent = ""
async for snapshot in session.stream_response(
prompt,
options=options,
):
if not snapshot.startswith(sent):
# Snapshot diverged (model revised earlier text);
# fall back to resending the full snapshot.
delta = snapshot
else:
delta = snapshot[len(sent) :]
sent = snapshot
if not delta:
continue
chunk = {
"id": cid,
"object": "chat.completion.chunk",
@@ -140,7 +153,7 @@ async def chat_completions(
"choices": [
{
"index": 0,
"delta": {"content": token},
"delta": {"content": delta},
"finish_reason": None,
}
],
+36
View File
@@ -118,6 +118,42 @@ class TestAppleFmShimSdkMigration:
assert rec["stream_calls"][-1]["options"] is rec["options"][-1]
assert "data: [DONE]" in body
def test_stream_emits_incremental_deltas_from_cumulative_snapshots(self, shim):
"""Regression for #378.
Apple FM's stream_response yields CUMULATIVE text snapshots, but
OpenAI clients concatenate delta.content. The shim must emit the
incremental suffix per chunk, so concatenating the deltas
reconstructs the final snapshot exactly — with no duplicated or
dropped characters.
"""
import json as _json
mod, rec = shim # fixture streams ["Sure! ", "Sure! The ", "Sure! The answer."]
client = TestClient(mod.app)
with client.stream(
"POST",
"/v1/chat/completions",
json={"messages": [{"role": "user", "content": "hi"}], "stream": True},
) as resp:
assert resp.status_code == 200
body = "".join(resp.iter_text())
# Collect content deltas from the SSE chunks.
deltas: list[str] = []
for line in body.splitlines():
if not line.startswith("data:") or "[DONE]" in line:
continue
payload = _json.loads(line[len("data:") :].strip())
content = payload["choices"][0]["delta"].get("content")
if content:
deltas.append(content)
# Incremental, not cumulative: concatenation equals the final
# snapshot, and no single delta repeats the whole prefix.
assert "".join(deltas) == "Sure! The answer."
assert deltas == ["Sure! ", "The ", "answer."]
def test_health_unpacks_is_available_tuple(self, monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Darwin")
rec = _install_stub_sdk(available=(False, "Apple Intelligence disabled"))