diff --git a/private_gpt/server/chat/interceptors/chat_interceptor_service.py b/private_gpt/server/chat/interceptors/chat_interceptor_service.py index abe1bd27..36437839 100644 --- a/private_gpt/server/chat/interceptors/chat_interceptor_service.py +++ b/private_gpt/server/chat/interceptors/chat_interceptor_service.py @@ -19,9 +19,6 @@ from private_gpt.server.chat.interceptors.configure_tool_execution_interceptor i from private_gpt.server.chat.interceptors.configure_tool_interceptor import ( ConfigureToolRequestInterceptor, ) -from private_gpt.server.chat.interceptors.deduplicate_event_interceptor import ( - DeduplicateEventInterceptor, -) from private_gpt.server.chat.interceptors.default_values_interceptor import ( DefaultValuesRequestInterceptor, ) @@ -108,7 +105,6 @@ class ChatInterceptorService: # --- response interceptors (run each iteration, order matters) --- extract_citation_response_interceptor: ExtractCitationInterceptor, filter_event_by_type_interceptor: FilterZylonInterceptor, - deduplicate_event_interceptor: DeduplicateEventInterceptor, ) -> None: self._prompt_builder_service = prompt_builder_service @@ -202,7 +198,6 @@ class ChatInterceptorService: "sanity", responses=[ filter_event_by_type_interceptor, - deduplicate_event_interceptor, ], ) ) diff --git a/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py b/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py deleted file mode 100644 index f29615ba..00000000 --- a/private_gpt/server/chat/interceptors/deduplicate_event_interceptor.py +++ /dev/null @@ -1,49 +0,0 @@ -from collections.abc import Mapping -from typing import Any - -from private_gpt.components.engines.chat.interceptors.chat_interceptor import ( - ChatResponseLoopInterceptor, -) -from private_gpt.components.engines.chat.models.chat_interceptor_context import ( - ChatInterceptorContext, -) -from private_gpt.events.models import Event, PingEvent - - -class DeduplicateEventInterceptor(ChatResponseLoopInterceptor): - """Drop consecutive duplicate streamed events (except pings). - - Some providers re-emit identical ``content_block_delta`` payloads (e.g. - ``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. - """ - - def __init__(self) -> None: - self._last_fingerprint: str | None = None - - async def on_iteration_start(self, context: ChatInterceptorContext) -> None: - self._last_fingerprint = None - - async def on_iteration_end(self, context: ChatInterceptorContext) -> None: - self._last_fingerprint = None - - async def intercept_event( - self, - event: Event, - context: ChatInterceptorContext, - ) -> Event | None: - if isinstance(event, PingEvent): - return event - - fingerprint = event.model_dump_json() - if fingerprint == self._last_fingerprint: - return None - - self._last_fingerprint = fingerprint - return event - - def model_copy( - self, *, update: Mapping[str, Any] | None | None = None, deep: bool = False - ) -> "DeduplicateEventInterceptor": - return DeduplicateEventInterceptor() diff --git a/tests/sse/test_sse.py b/tests/sse/test_sse.py index 3c3beda2..0c83e158 100644 --- a/tests/sse/test_sse.py +++ b/tests/sse/test_sse.py @@ -55,9 +55,6 @@ from private_gpt.events.models import ( ToolUseBlock, Usage, ) -from private_gpt.server.chat.interceptors.deduplicate_event_interceptor import ( - DeduplicateEventInterceptor, -) from private_gpt.server.chat.interceptors.filter_event_by_type_interceptor import ( FilterZylonInterceptor, ) @@ -84,10 +81,7 @@ async def _collect_from_gen( ping_interval: float | None = None, ) -> list: """Pipe a hand-crafted event generator through the interceptor pipeline.""" - interceptors: list[ChatResponseLoopInterceptor] = [ - FilterZylonInterceptor(), - DeduplicateEventInterceptor(), - ] + interceptors: list[ChatResponseLoopInterceptor] = [FilterZylonInterceptor()] if ping_interval: gen = await PingEventInterceptor(ping_interval).intercept(gen) @@ -146,10 +140,7 @@ def _make_engine( ) -> ChatLoopEngine: llm_component = MagicMock(spec=LLMComponent) llm_component.get_llm.return_value = mock_llm - interceptors: list[ChatResponseLoopInterceptor] = [ - FilterZylonInterceptor(), - DeduplicateEventInterceptor(), - ] + interceptors: list[ChatResponseLoopInterceptor] = [FilterZylonInterceptor()] return ChatLoopEngine( llm_component=llm_component, response_interceptors=interceptors, @@ -639,62 +630,6 @@ async def test_integration_ping_injected_between_slow_llm_chunks() -> None: assert sum(1 for e in events if isinstance(e, PingEvent)) >= 1 -@pytest.mark.asyncio -async def test_unit_duplicate_content_block_deltas_are_dropped() -> None: - """Identical consecutive content_block_delta events are suppressed.""" - from private_gpt.events.models import InputJSONDelta - - bid = _block_id() - delta = InputJSONDelta( - partial_json="", - partial_json_obj={"object": "deals", "query": None}, - ) - - async def gen() -> AsyncGenerator: - yield RawMessageStartEvent.from_defaults() - yield RawContentBlockStartEvent( - index=1, - block_id=bid, - content_block=ToolUseBlock(id="tool_1", name="search", input={}), - ) - for _ in range(5): - yield RawContentBlockDeltaEvent( - index=1, - block_id=bid, - delta=delta.model_copy(deep=True), - ) - yield RawContentBlockDeltaEvent( - index=1, - block_id=bid, - delta=InputJSONDelta( - partial_json='{"object":"deals","query":"x"}', - partial_json_obj={"object": "deals", "query": "x"}, - ), - ) - yield RawContentBlockStopEvent(index=1, block_id=bid) - yield RawMessageStopEvent() - - events = await _collect_from_gen(gen()) - deltas = [e for e in events if isinstance(e, RawContentBlockDeltaEvent)] - assert len(deltas) == 2 - assert deltas[0].delta.partial_json_obj == {"object": "deals", "query": None} - assert deltas[1].delta.partial_json_obj == {"object": "deals", "query": "x"} - - -@pytest.mark.asyncio -async def test_unit_ping_events_are_never_deduplicated() -> None: - """Ping keepalives always pass through even when consecutive.""" - - async def gen() -> AsyncGenerator: - yield PingEvent() - yield PingEvent() - yield PingEvent() - - events = await _collect_from_gen(gen()) - assert len(events) == 3 - assert all(isinstance(e, PingEvent) for e in events) - - # --------------------------------------------------------------------------- # Private factory helpers # ---------------------------------------------------------------------------