From 7088fa4f94f136c558014a701809d9bbf20ece98 Mon Sep 17 00:00:00 2001 From: Jacek Zimonski <39839016+jacekzimonski@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:04:36 +0200 Subject: [PATCH 1/5] GitHub-216 Completion detail view was streamed --- backend/core/domain/agent_completion.py | 2 ++ .../core/runners/agent_completion_builder.py | 3 +++ backend/core/runners/runner.py | 2 ++ backend/core/services/completion_runner.py | 2 ++ .../clickhouse/_models/_ch_completion.py | 5 +++++ .../clickhouse/_models/_ch_completion_test.py | 1 + backend/protocol/api/_api_models.py | 5 +++++ backend/protocol/api/_services/conversions.py | 2 ++ .../api/_services/playground_service.py | 1 + .../protocol/api/_services/run/run_service.py | 1 + .../cells/CompletionTableVersionCell.tsx | 5 ----- .../CompletionDetailsView.tsx | 1 + .../version-details/VersionDetailsView.tsx | 20 ++++++++++++------- web/src/types/models.ts | 1 + 14 files changed, 39 insertions(+), 12 deletions(-) diff --git a/backend/core/domain/agent_completion.py b/backend/core/domain/agent_completion.py index 542fbd39..b94f1c1b 100644 --- a/backend/core/domain/agent_completion.py +++ b/backend/core/domain/agent_completion.py @@ -39,6 +39,8 @@ class AgentCompletion(BaseModel): from_cache: bool = False + stream: bool = False + source: Literal["web", "api", "mcp"] = "api" metadata: dict[str, Any] | None = None diff --git a/backend/core/runners/agent_completion_builder.py b/backend/core/runners/agent_completion_builder.py index 7e39c349..b9b85e61 100644 --- a/backend/core/runners/agent_completion_builder.py +++ b/backend/core/runners/agent_completion_builder.py @@ -26,6 +26,8 @@ class AgentCompletionBuilder(BaseModel): metadata: dict[str, Any] + stream: bool = False + llm_completions: list[LLMCompletion] = Field(default_factory=list) file_download_seconds: float | None = None @@ -62,6 +64,7 @@ def build(self, output: RunnerOutput, error: Error | None = None, force: bool = messages=self.messages, cost_usd=sum(trace.cost_usd for trace in traces), duration_seconds=sum(trace.duration_seconds for trace in traces), + stream=self.stream, metadata=self.metadata, ) return self._built_completion diff --git a/backend/core/runners/runner.py b/backend/core/runners/runner.py index 23e746f4..f5bf3074 100644 --- a/backend/core/runners/runner.py +++ b/backend/core/runners/runner.py @@ -108,6 +108,7 @@ async def prepare_completion( completion_id: UUID, metadata: dict[str, Any] | None = None, conversation_id: str | None = None, + stream: bool = False, ) -> AgentCompletionBuilder: """Construct a task run builder for the given input and properties""" @@ -122,6 +123,7 @@ async def prepare_completion( start_time=start_time, conversation_id=conversation_id, messages=messages, + stream=stream, ) def _should_use_cache(self, cache: CacheUsage) -> bool: diff --git a/backend/core/services/completion_runner.py b/backend/core/services/completion_runner.py index 4b720d77..bfa6e36f 100644 --- a/backend/core/services/completion_runner.py +++ b/backend/core/services/completion_runner.py @@ -111,6 +111,7 @@ async def prepare( use_fallback: FallbackOption, completion_id: UUID, conversation_id: str | None, + stream: bool = False, ): runner = Runner( tenant_slug=self._tenant.slug, @@ -129,6 +130,7 @@ async def prepare( completion_id=completion_id, metadata=metadata, conversation_id=conversation_id, + stream=stream, ) return runner, builder diff --git a/backend/core/storage/clickhouse/_models/_ch_completion.py b/backend/core/storage/clickhouse/_models/_ch_completion.py index 0b68844d..ca77499b 100644 --- a/backend/core/storage/clickhouse/_models/_ch_completion.py +++ b/backend/core/storage/clickhouse/_models/_ch_completion.py @@ -129,6 +129,9 @@ class ClickhouseCompletion(BaseModel): # Origin of the run source: Literal["web", "api", "mcp"] = "api" + # Whether the completion was streamed + stream: bool = False + # Traces as array of strings traces: list[_Trace] = Field(default_factory=list) @@ -174,6 +177,7 @@ def from_domain(cls, tenant: int, completion: AgentCompletion): cost_millionth_usd=_cost_millionth_usd(completion.cost_usd), metadata=_sanitize_metadata(completion.metadata), source=completion.source, + stream=completion.stream, # Traces traces=[_Trace.from_domain(trace) for trace in completion.traces], ) @@ -207,6 +211,7 @@ def to_domain(self, agent: Agent | None = None) -> AgentCompletion: traces=[_Trace.to_domain(trace) for trace in self.traces], metadata=from_sanitized_metadata(self.metadata), source=self.source, + stream=self.stream, from_cache=False, ) diff --git a/backend/core/storage/clickhouse/_models/_ch_completion_test.py b/backend/core/storage/clickhouse/_models/_ch_completion_test.py index 8bc58263..88f49acc 100644 --- a/backend/core/storage/clickhouse/_models/_ch_completion_test.py +++ b/backend/core/storage/clickhouse/_models/_ch_completion_test.py @@ -94,6 +94,7 @@ def test_sanity(self): }, "source": "api", "status": "success", + "stream": False, "traces": [ { "cost_usd": 3.0, diff --git a/backend/protocol/api/_api_models.py b/backend/protocol/api/_api_models.py index 927b7eeb..31abde19 100644 --- a/backend/protocol/api/_api_models.py +++ b/backend/protocol/api/_api_models.py @@ -432,6 +432,11 @@ class Completion(BaseModel): description="Metadata associated with the completion. Can be used to store additional information about the completion.", ) + stream: bool = Field( + default=False, + description="Whether the completion was generated using streaming mode.", + ) + cost_usd: float = Field(description="The cost of the inference in USD.") duration_seconds: float | None = Field( default=None, diff --git a/backend/protocol/api/_services/conversions.py b/backend/protocol/api/_services/conversions.py index f4a7499c..f4160806 100644 --- a/backend/protocol/api/_services/conversions.py +++ b/backend/protocol/api/_services/conversions.py @@ -473,6 +473,7 @@ def completion_from_domain(completion: DomainCompletion) -> Completion: messages=[message_from_domain(m) for m in completion.messages] if completion.messages else [], annotations=None, # TODO: metadata=completion.metadata or None, + stream=completion.stream, cost_usd=completion.cost_usd or 0.0, duration_seconds=completion.duration_seconds or 0.0, traces=[trace_from_domain(t) for t in completion.traces] if completion.traces else None, @@ -490,6 +491,7 @@ def completion_to_domain(completion: Completion) -> DomainCompletion: agent_output=output_to_domain(completion.output), messages=[message_to_domain(m) for m in completion.messages] if completion.messages else [], metadata=completion.metadata or None, + stream=completion.stream, cost_usd=completion.cost_usd or 0.0, duration_seconds=completion.duration_seconds or 0.0, traces=[trace_to_domain(t) for t in completion.traces] if completion.traces else [], diff --git a/backend/protocol/api/_services/playground_service.py b/backend/protocol/api/_services/playground_service.py index f14ebac5..da890830 100644 --- a/backend/protocol/api/_services/playground_service.py +++ b/backend/protocol/api/_services/playground_service.py @@ -217,6 +217,7 @@ async def _run_version( use_fallback="never", conversation_id=None, completion_id=completion_id, + stream=False, ) completion = await self._completion_runner.run(runner, builder) diff --git a/backend/protocol/api/_services/run/run_service.py b/backend/protocol/api/_services/run/run_service.py index 8f904efe..9cf4dc9e 100644 --- a/backend/protocol/api/_services/run/run_service.py +++ b/backend/protocol/api/_services/run/run_service.py @@ -248,6 +248,7 @@ async def run(self, request: OpenAIProxyChatCompletionRequest, start_time: float use_fallback=use_fallback, conversation_id=request.conversation_id, completion_id=completion_id, + stream=stream, ) if stream: return await self._stream(runner, builder, request) diff --git a/web/src/app/completions/sections/table/cells/CompletionTableVersionCell.tsx b/web/src/app/completions/sections/table/cells/CompletionTableVersionCell.tsx index 0f1ef924..2d2926e8 100644 --- a/web/src/app/completions/sections/table/cells/CompletionTableVersionCell.tsx +++ b/web/src/app/completions/sections/table/cells/CompletionTableVersionCell.tsx @@ -50,11 +50,6 @@ function CompletionTableVersionCell({ value }: CompletionTableVersionCellProps) }); } - // Check stream (default: false) - if (obj.stream !== undefined && obj.stream !== false) { - nonDefaultEntries.push({ key: "stream", value: String(obj.stream) }); - } - // Check include_usage (default: false) if (obj.include_usage !== undefined && obj.include_usage !== false) { nonDefaultEntries.push({ diff --git a/web/src/components/completion-modal/CompletionDetailsView.tsx b/web/src/components/completion-modal/CompletionDetailsView.tsx index a8d4dab6..f2743134 100644 --- a/web/src/components/completion-modal/CompletionDetailsView.tsx +++ b/web/src/components/completion-modal/CompletionDetailsView.tsx @@ -52,6 +52,7 @@ export function CompletionDetailsView(props: Props) { + {/* Stream - only show when completion data is available */} + {completion && ( +
+
+ Stream + {completion.stream ? "true" : "false"} +
+
+ )} + {/* Advanced Settings */}
- {/* Stream */} -
- Stream - {extendedVersion.stream ? "true" : "false"} -
- {/* Include Usage */}
Include Usage diff --git a/web/src/types/models.ts b/web/src/types/models.ts index cbd6698c..27cdfee7 100644 --- a/web/src/types/models.ts +++ b/web/src/types/models.ts @@ -212,6 +212,7 @@ export interface Completion { messages: Message[]; annotations?: Annotation[]; metadata: Record; + stream: boolean; cost_usd: number; duration_seconds?: number; traces?: Trace[]; From 53a69f47892b3dff38670f6d74ba9400c95d6240 Mon Sep 17 00:00:00 2001 From: Jacek Zimonski <39839016+jacekzimonski@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:02:13 +0200 Subject: [PATCH 2/5] Fixes after QA --- backend/core/domain/agent_completion.py | 2 -- backend/core/runners/agent_completion_builder.py | 3 --- backend/core/runners/runner.py | 2 -- backend/core/services/completion_runner.py | 8 +++++--- .../storage/clickhouse/_models/_ch_completion.py | 4 ---- .../clickhouse/_models/_ch_completion_test.py | 1 - backend/protocol/api/_api_models.py | 5 ----- backend/protocol/api/_services/conversions.py | 2 -- .../completion-modal/CompletionDetailsView.tsx | 1 - .../version-details/VersionDetailsView.tsx | 15 +-------------- 10 files changed, 6 insertions(+), 37 deletions(-) diff --git a/backend/core/domain/agent_completion.py b/backend/core/domain/agent_completion.py index b94f1c1b..542fbd39 100644 --- a/backend/core/domain/agent_completion.py +++ b/backend/core/domain/agent_completion.py @@ -39,8 +39,6 @@ class AgentCompletion(BaseModel): from_cache: bool = False - stream: bool = False - source: Literal["web", "api", "mcp"] = "api" metadata: dict[str, Any] | None = None diff --git a/backend/core/runners/agent_completion_builder.py b/backend/core/runners/agent_completion_builder.py index b9b85e61..7e39c349 100644 --- a/backend/core/runners/agent_completion_builder.py +++ b/backend/core/runners/agent_completion_builder.py @@ -26,8 +26,6 @@ class AgentCompletionBuilder(BaseModel): metadata: dict[str, Any] - stream: bool = False - llm_completions: list[LLMCompletion] = Field(default_factory=list) file_download_seconds: float | None = None @@ -64,7 +62,6 @@ def build(self, output: RunnerOutput, error: Error | None = None, force: bool = messages=self.messages, cost_usd=sum(trace.cost_usd for trace in traces), duration_seconds=sum(trace.duration_seconds for trace in traces), - stream=self.stream, metadata=self.metadata, ) return self._built_completion diff --git a/backend/core/runners/runner.py b/backend/core/runners/runner.py index f5bf3074..23e746f4 100644 --- a/backend/core/runners/runner.py +++ b/backend/core/runners/runner.py @@ -108,7 +108,6 @@ async def prepare_completion( completion_id: UUID, metadata: dict[str, Any] | None = None, conversation_id: str | None = None, - stream: bool = False, ) -> AgentCompletionBuilder: """Construct a task run builder for the given input and properties""" @@ -123,7 +122,6 @@ async def prepare_completion( start_time=start_time, conversation_id=conversation_id, messages=messages, - stream=stream, ) def _should_use_cache(self, cache: CacheUsage) -> bool: diff --git a/backend/core/services/completion_runner.py b/backend/core/services/completion_runner.py index bfa6e36f..857005a2 100644 --- a/backend/core/services/completion_runner.py +++ b/backend/core/services/completion_runner.py @@ -113,12 +113,15 @@ async def prepare( conversation_id: str | None, stream: bool = False, ): + # Add stream information to metadata + metadata_with_stream = {**metadata, "stream": stream} + runner = Runner( tenant_slug=self._tenant.slug, custom_configs=self._tenant.providers, agent=agent, version=version, - metadata=metadata, + metadata=metadata_with_stream, metric_tags={}, provider_factory=self._provider_factory, timeout=timeout or 240, @@ -128,9 +131,8 @@ async def prepare( agent_input=input, start_time=start_time, completion_id=completion_id, - metadata=metadata, + metadata=metadata_with_stream, conversation_id=conversation_id, - stream=stream, ) return runner, builder diff --git a/backend/core/storage/clickhouse/_models/_ch_completion.py b/backend/core/storage/clickhouse/_models/_ch_completion.py index ca77499b..9744e52f 100644 --- a/backend/core/storage/clickhouse/_models/_ch_completion.py +++ b/backend/core/storage/clickhouse/_models/_ch_completion.py @@ -129,8 +129,6 @@ class ClickhouseCompletion(BaseModel): # Origin of the run source: Literal["web", "api", "mcp"] = "api" - # Whether the completion was streamed - stream: bool = False # Traces as array of strings traces: list[_Trace] = Field(default_factory=list) @@ -177,7 +175,6 @@ def from_domain(cls, tenant: int, completion: AgentCompletion): cost_millionth_usd=_cost_millionth_usd(completion.cost_usd), metadata=_sanitize_metadata(completion.metadata), source=completion.source, - stream=completion.stream, # Traces traces=[_Trace.from_domain(trace) for trace in completion.traces], ) @@ -211,7 +208,6 @@ def to_domain(self, agent: Agent | None = None) -> AgentCompletion: traces=[_Trace.to_domain(trace) for trace in self.traces], metadata=from_sanitized_metadata(self.metadata), source=self.source, - stream=self.stream, from_cache=False, ) diff --git a/backend/core/storage/clickhouse/_models/_ch_completion_test.py b/backend/core/storage/clickhouse/_models/_ch_completion_test.py index 88f49acc..8bc58263 100644 --- a/backend/core/storage/clickhouse/_models/_ch_completion_test.py +++ b/backend/core/storage/clickhouse/_models/_ch_completion_test.py @@ -94,7 +94,6 @@ def test_sanity(self): }, "source": "api", "status": "success", - "stream": False, "traces": [ { "cost_usd": 3.0, diff --git a/backend/protocol/api/_api_models.py b/backend/protocol/api/_api_models.py index 31abde19..927b7eeb 100644 --- a/backend/protocol/api/_api_models.py +++ b/backend/protocol/api/_api_models.py @@ -432,11 +432,6 @@ class Completion(BaseModel): description="Metadata associated with the completion. Can be used to store additional information about the completion.", ) - stream: bool = Field( - default=False, - description="Whether the completion was generated using streaming mode.", - ) - cost_usd: float = Field(description="The cost of the inference in USD.") duration_seconds: float | None = Field( default=None, diff --git a/backend/protocol/api/_services/conversions.py b/backend/protocol/api/_services/conversions.py index f4160806..f4a7499c 100644 --- a/backend/protocol/api/_services/conversions.py +++ b/backend/protocol/api/_services/conversions.py @@ -473,7 +473,6 @@ def completion_from_domain(completion: DomainCompletion) -> Completion: messages=[message_from_domain(m) for m in completion.messages] if completion.messages else [], annotations=None, # TODO: metadata=completion.metadata or None, - stream=completion.stream, cost_usd=completion.cost_usd or 0.0, duration_seconds=completion.duration_seconds or 0.0, traces=[trace_from_domain(t) for t in completion.traces] if completion.traces else None, @@ -491,7 +490,6 @@ def completion_to_domain(completion: Completion) -> DomainCompletion: agent_output=output_to_domain(completion.output), messages=[message_to_domain(m) for m in completion.messages] if completion.messages else [], metadata=completion.metadata or None, - stream=completion.stream, cost_usd=completion.cost_usd or 0.0, duration_seconds=completion.duration_seconds or 0.0, traces=[trace_to_domain(t) for t in completion.traces] if completion.traces else [], diff --git a/web/src/components/completion-modal/CompletionDetailsView.tsx b/web/src/components/completion-modal/CompletionDetailsView.tsx index f2743134..a8d4dab6 100644 --- a/web/src/components/completion-modal/CompletionDetailsView.tsx +++ b/web/src/components/completion-modal/CompletionDetailsView.tsx @@ -52,7 +52,6 @@ export function CompletionDetailsView(props: Props) {
- {/* Stream - only show when completion data is available */} - {completion && ( -
-
- Stream - {completion.stream ? "true" : "false"} -
-
- )} - {/* Advanced Settings */}