fix: support LiteLLM Responses streaming
PR and Push Build/Test / build-and-test (push) Successful in 14m17s

This commit is contained in:
2026-07-27 14:44:32 +02:00
parent 3cadf08fa2
commit e192ae7cd8
16 changed files with 682 additions and 541 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-27
@@ -0,0 +1,40 @@
## Context
`LiteLlmResponsesChatClient` exposes a non-streaming `IChatClient` interface and currently buffers every successful `/v1/responses` body before parsing it as one JSON document. The deployed LiteLLM `chatgpt/gpt-5.5` route instead returns Responses Server-Sent Events. Microsoft.Extensions.AI.OpenAI already provides a Responses streaming adapter that maps the OpenAI SDK's typed SSE updates into `ChatResponseUpdate` values, including text, reasoning, function calls, response metadata, and usage.
That adapter uses `FunctionCallContent.CreateFromParsedArguments`, which records argument-mapping failures on the function call instead of throwing them from the response parser. The default function invoker does not itself prevent invocation when that property is populated, so Meeting Assistant must turn the recorded parse failure into a matching function result before calling the tool.
## Goals / Non-Goals
**Goals:**
- Preserve the existing non-streaming `IChatClient` contract while selecting the supported Responses streaming or non-streaming path through configuration.
- Delegate SSE framing, event deserialization, streamed function-call assembly, response metadata, and usage mapping to the OpenAI SDK and Microsoft.Extensions.AI.OpenAI adapter.
- Preserve the original encoded function arguments when forwarding conversation history.
- Return a safe, structured invalid-arguments result for malformed argument JSON without invoking the requested tool.
- Apply the guarded invocation behavior to both summary and workflow-editor agent pipelines that use this Responses client.
**Non-Goals:**
- Expose token-by-token upstream streaming to the UI.
- Replace Microsoft.Extensions.AI function invocation or implement general JSON Schema validation.
- Recover from an incomplete or malformed Responses event stream that has no usable completed output.
## Decisions
1. Add `Agent:UseStreaming`, defaulting to `true`, and send summary requests through the matching OpenAI SDK method. The streaming path uses `ResponsesClient.CreateResponseStreamingAsync` and collects the Microsoft.Extensions.AI.OpenAI typed updates into one `ChatResponse`; the non-streaming path uses `ResponsesClient.CreateResponseAsync` and its supported `AsChatResponse` adapter.
2. Keep the existing JSON payload builder because it owns Meeting Assistant-specific compaction input, tool serialization, reasoning settings, retry diagnostics, and initiator behavior. Deserialize that payload into the OpenAI SDK's `CreateResponseOptions`, set its streaming flag from configuration, and let the selected SDK method own the HTTP response protocol.
The SDK deserializes `ResponseItem` values by their `type` discriminator. Message items must therefore include `"type": "message"` before the JSON payload is converted to `CreateResponseOptions`; otherwise the SDK reserializes them as `"type": "unknown"` and the provider rejects the request.
3. Rely on the framework adapter's `FunctionCallContent.CreateFromParsedArguments` mapping for function-call argument decoding and raw response preservation. A shared function-invocation callback checks the recorded parse exception before invocation. For invalid JSON it returns a structured `invalid_tool_arguments` value that the framework associates with the original call ID; otherwise it delegates to the actual function.
4. Configure both agent pipelines with the shared guarded invoker. This avoids duplicating the safety decision and prevents zero-argument tools from accidentally running when malformed JSON would otherwise map to an empty argument set.
## Risks / Trade-offs
- **[Provider event variants]** A provider could emit events outside the OpenAI Responses schema. → Use the maintained OpenAI SDK parser and fail diagnostically for genuinely incompatible streams instead of maintaining local event variants.
- **[Buffered result]** The summary pipeline does not expose token-by-token updates to its caller. → Collect the framework updates only at the existing non-streaming boundary; the HTTP response itself remains streamed and incrementally parsed.
- **[Experimental API]** The OpenAI Responses adapter is marked experimental in the currently referenced package. → Keep it behind `LiteLlmResponsesChatClient`, which isolates future package API changes from the rest of Meeting Assistant.
- **[Error disclosure]** Raw parser exceptions may contain implementation details. → Return a stable error code and concise validation message rather than exception text or the malformed arguments.
@@ -0,0 +1,26 @@
## Why
The configured LiteLLM Responses endpoint can return successful responses as Server-Sent Events even when the request sets `stream: false`. Meeting Assistant currently treats every successful body as one JSON document, and it also lets malformed function-call argument JSON escape as a fatal parse exception, so either condition can abort an otherwise recoverable summary run.
## What Changes
- Consume the configured LiteLLM Responses endpoint through the supported OpenAI Responses streaming transport and Agent Framework adapter.
- Assemble completed response output items and usage metadata from Responses SSE events before returning them through the existing non-streaming chat-client interface.
- Add an agent setting that selects streaming or non-streaming Responses transport, with streaming enabled by default.
- Preserve valid Responses item discriminators when translating agent messages through the OpenAI SDK request model.
- Treat malformed function-call arguments as invalid tool input that is returned to the agent for correction without invoking the tool or terminating the summary run.
- Add behavior tests for streamed text, streamed function calls, and invalid function-call arguments.
## Capabilities
### New Capabilities
- None.
### Modified Capabilities
- `meeting-summary`: Make the existing Responses-based summary pipeline interoperable with SSE responses and resilient to malformed tool-call input.
## Impact
The change affects agent configuration, the custom LiteLLM Responses chat client, its summary-agent integration, and focused tests. It does not change the public HTTP API.
@@ -0,0 +1,36 @@
## ADDED Requirements
### Requirement: Summary agents tolerate Responses event streams and malformed tool arguments
Meeting Assistant SHALL consume successful summary-agent Responses results through the supported OpenAI Responses and Agent Framework Server-Sent Events adapter.
When a Responses event stream delivers completed output items separately from the final response metadata, Meeting Assistant SHALL assemble those output items into one agent response while preserving final response metadata and usage.
Meeting Assistant SHALL provide an agent setting that selects streaming or non-streaming Responses transport. Streaming SHALL be enabled by default. When streaming is disabled, Meeting Assistant SHALL use the supported non-streaming OpenAI Responses client and adapter.
When a returned function call contains arguments that are not a valid JSON object, Meeting Assistant SHALL NOT invoke the requested function and SHALL return an invalid-tool-arguments result associated with the original call ID to the agent so it can correct the call.
When Meeting Assistant translates chat messages into a Responses request, every message input item SHALL retain the `message` item discriminator required by the OpenAI SDK and Responses API.
#### Scenario: Streamed text response is assembled
- **WHEN** the configured Responses endpoint returns completed message output in Server-Sent Events followed by final response metadata
- **THEN** the summary agent receives the completed message text, response metadata, and usage without a JSON document parse failure
#### Scenario: Streamed function call is assembled
- **WHEN** the configured Responses endpoint returns a completed function-call output item in Server-Sent Events
- **THEN** the summary agent receives the function call with its call ID, function name, and parsed arguments
#### Scenario: Streaming transport can be disabled
- **WHEN** `MeetingAssistant:Agent:UseStreaming` is `false`
- **THEN** the summary agent requests a non-streaming Responses result
- **AND** converts the response through the supported OpenAI Responses adapter
#### Scenario: Chat message input remains a Responses message
- **WHEN** the summary agent sends a user or assistant chat message through the Responses client
- **THEN** the outbound Responses input item has type `message`
- **AND** the request does not contain an `unknown` input item type
#### Scenario: Malformed function arguments are returned to the agent
- **WHEN** the model returns a function call whose arguments are not a valid JSON object
- **THEN** Meeting Assistant does not invoke the requested function
- **AND** sends a function result with an invalid-tool-arguments error for the original call ID back to the agent
- **AND** allows the agent loop to continue
@@ -0,0 +1,24 @@
## 1. Responses SSE compatibility
- [x] 1.1 Add a failing client behavior test for a streamed text response with final metadata and usage.
- [x] 1.2 Add failing configuration and client behavior tests for selecting non-streaming Responses transport.
- [x] 1.3 Route Responses requests through the Agent Framework/OpenAI SDK streaming or non-streaming adapter according to configuration.
- [x] 1.4 Add behavior coverage for streamed function-call output.
## 2. Invalid tool-argument recovery
- [x] 2.1 Add a failing agent-loop behavior test proving malformed function-call JSON does not invoke the tool and is returned to the agent.
- [x] 2.2 Preserve argument parse failures on function-call content and add the shared guarded function invoker.
- [x] 2.3 Apply guarded function invocation to the summary and workflow-editor agent pipelines.
## 3. Verification
- [x] 3.1 Refactor the touched response and invocation paths for DRYness, SOLID boundaries, and simplicity while preserving behavior.
- [x] 3.2 Run focused tests, the full solution tests, and strict OpenSpec validation.
- [x] 3.3 Verify the client behavior against the deployed LiteLLM Responses endpoint and restart Meeting Assistant only after confirming it is idle.
## 4. Responses message-item compatibility
- [x] 4.1 Add a failing client behavior test proving an outbound chat message retains the `message` discriminator through SDK serialization.
- [x] 4.2 Emit the Responses `message` discriminator for chat message input items.
- [x] 4.3 Run focused and full tests, validate OpenSpec strictly, restart while idle, and retry the failed summary through the local API.