From f558dd431cf6d1fa45d58ae570c5018a8b8935c9 Mon Sep 17 00:00:00 2001 From: Javier Martinez Date: Thu, 30 Jul 2026 15:09:03 +0200 Subject: [PATCH] fix: improve perf --- .../deduplicate_event_interceptor.py | 15 ++++---- tests/sse/test_sse.py | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py b/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py index f29615ba..4491c78b 100644 --- a/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py +++ b/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py @@ -17,16 +17,20 @@ class DeduplicateEventInterceptor(ChatResponseLoopInterceptor): ``input_json_delta`` with an unchanged ``partial_json_obj``). Those duplicates add noise for clients without changing state, so they are suppressed. Ping keepalives are always forwarded. + + Equality is checked via pydantic's structural ``__eq__`` (field-by-field + comparison) rather than ``model_dump_json``, since serializing every + streamed event just to compare it adds significant latency. """ def __init__(self) -> None: - self._last_fingerprint: str | None = None + self._last_event: Event | None = None async def on_iteration_start(self, context: ChatInterceptorContext) -> None: - self._last_fingerprint = None + self._last_event = None async def on_iteration_end(self, context: ChatInterceptorContext) -> None: - self._last_fingerprint = None + self._last_event = None async def intercept_event( self, @@ -36,11 +40,10 @@ class DeduplicateEventInterceptor(ChatResponseLoopInterceptor): if isinstance(event, PingEvent): return event - fingerprint = event.model_dump_json() - if fingerprint == self._last_fingerprint: + if event == self._last_event: return None - self._last_fingerprint = fingerprint + self._last_event = event return event def model_copy( diff --git a/tests/sse/test_sse.py b/tests/sse/test_sse.py index 3c3beda2..082b6d66 100644 --- a/tests/sse/test_sse.py +++ b/tests/sse/test_sse.py @@ -681,6 +681,42 @@ async def test_unit_duplicate_content_block_deltas_are_dropped() -> None: assert deltas[1].delta.partial_json_obj == {"object": "deals", "query": "x"} +def test_unit_content_block_delta_equality_ignores_object_identity() -> None: + """Two distinct RawContentBlockDeltaEvent objects compare equal when their + field values match, and unequal when they differ -- this is what + DeduplicateEventInterceptor relies on instead of model_dump_json().""" + from private_gpt.events.models import InputJSONDelta + + bid = _block_id() + + equal_a = RawContentBlockDeltaEvent( + index=1, + block_id=bid, + delta=InputJSONDelta( + partial_json="", partial_json_obj={"object": "deals", "query": None} + ), + ) + equal_b = RawContentBlockDeltaEvent( + index=1, + block_id=bid, + delta=InputJSONDelta( + partial_json="", partial_json_obj={"object": "deals", "query": None} + ), + ) + assert equal_a is not equal_b + assert equal_a == equal_b + + different = RawContentBlockDeltaEvent( + index=1, + block_id=bid, + delta=InputJSONDelta( + partial_json='{"object":"deals","query":"x"}', + partial_json_obj={"object": "deals", "query": "x"}, + ), + ) + assert equal_a != different + + @pytest.mark.asyncio async def test_unit_ping_events_are_never_deduplicated() -> None: """Ping keepalives always pass through even when consecutive."""