Skip to content

Avoid messages for response-only updates - #1127

Open
Kirill (llirik0) wants to merge 2 commits into
microsoft:mainfrom
llirik0:fix/metadata-only-response-updates
Open

Kirill (llirik0) wants to merge 2 commits into
microsoft:mainfrom
llirik0:fix/metadata-only-response-updates

Conversation

@llirik0

Copy link
Copy Markdown

Summary

Prevent response-level metadata updates from creating an empty assistant message when a response is converted with ToUpdates and collected again.

Response.Update now creates or mutates message state only when an update carries message data. Continuation tokens and response-level additional properties still round-trip normally.

Testing

  • go test ./...
  • go test -race ./...
  • go vet ./...
  • go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run

@github-actions github-actions Bot added area:agent Changes files in the agent area size:medium At most 100 changed lines across at most 5 files kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure labels Sep 20, 2026
@github-actions

This comment has been minimized.

@llirik0

Copy link
Copy Markdown
Author

Kirill (Kirill (@llirik0)) please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree

@llirik0
Kirill (llirik0) marked this pull request as ready for review September 20, 2026 20:41
@llirik0
Kirill (llirik0) requested a review from a team as a code owner September 20, 2026 20:41
Copilot AI lite review requested due to automatic review settings September 20, 2026 20:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Message-only metadata can be lost during response round trips.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Prevents response-only metadata updates from creating empty assistant messages during response round trips.

Changes:

  • Gates message mutation on message-related update fields.
  • Adds metadata round-trip tests.
File Description
agent/​response.go Updates response aggregation behavior.
agent/​response_test.go Tests metadata-only round trips.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agent/response.go
@github-actions github-actions Bot added size:large At most 300 changed lines across at most 10 files and removed size:medium At most 100 changed lines across at most 5 files labels Sep 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Scope: user-visible behavior

Changed Go contract: agent.Response.Update (skips creating/mutating a message when a ResponseUpdate carries no message-related data — no MessageID, AuthorName, Role, Contents, or RawRepresentation) and agent.Response.ToUpdates (defaults an empty per-message Role to message.RoleAssistant when converting to updates). No exported types/signatures were added, removed, or changed — same public methods, same shapes.

Upstream evidence reviewed:

  • Python python/packages/core/agent_framework/_types.py, _process_update (~line 2038): creates a new Message("assistant", []) whenever response.messages is empty or the update signals a new message boundary, without checking whether the update actually carries message data.
  • Python _finalize_response (~line 2278) → _coalesce_function_call_occurrences (~line 2287): after all updates are applied, filters response.messages to [m for m in response.messages if m.contents], discarding any message left empty — this is how Python avoids surfacing an empty message from metadata-only updates (AgentResponse.from_updates / from_update_generator always call _finalize_response before returning).
  • .NET dotnet/src/Microsoft.Agents.AI.Abstractions/AgentResponse.cs, ToAgentResponseUpdates() (~line 265): emits one update per message, using Role = message.Role (no default-to-assistant substitution), plus a single "extra" metadata update only when AdditionalProperties or Usage is set. AgentResponseExtensions.ToAgentResponse delegates message reconstruction to Microsoft.Extensions.AI's ToChatResponse(), whose empty-message handling isn't vendored in this repo, so its exact behavior couldn't be directly inspected here.

Result: aligned. Go's new guard in Update achieves the same end state as Python's post-hoc empty-message filter in _finalize_response — a response built purely from response-level metadata (continuation token, additional properties) round-trips through ToUpdates/Update without producing a spurious empty assistant message. The mechanism differs (guard-on-write vs. filter-on-finalize) but that's an implementation detail, not an observable divergence, and no new exported API surface was introduced. The Role default-to-assistant addition in ToUpdates is a minor Go-specific normalization (guards against emitting an update with an empty Role for an existing message) and doesn't conflict with either upstream implementation. No parity concerns found; public-api-change label not applicable since no exported API surface changed.

Generated by Go API Consistency Review Agent · copilot · auto · 82.2 AIC · ⌖ 7.41 AIC · ⊞ 9.2K ·

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Metadata-only updates can still produce empty assistant messages during collection; the agent-level path needs correction and regression coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

Comment thread agent/response.go
if msg.AdditionalProperties == nil {
msg.AdditionalProperties = make(map[string]any)
// A response-level metadata update must not create an empty message.
if update.MessageID != "" || update.AuthorName != "" || update.Role != "" || len(update.Contents) > 0 || update.RawRepresentation != nil {
@PratikDhanave

Copy link
Copy Markdown
Contributor

Reviewed this closely since it touches the same Response.Update/ToUpdates seam I was in for #1133. The core change is right and matches M.E.AI ChatResponseExtensions.ProcessUpdate: a response-metadata-only update should not materialize an empty message. Tests are a good round-trip shape. A few notes:

Composition with #1133 (worth the maintainers knowing before merge order is decided). #1133 scopes AdditionalProperties in Response.Update to the message when the update carries a MessageID and to the response otherwise. These two PRs are complementary and both needed:

So they dont conflict, but whichever merges second will want a trivial rebase in the same block. No action needed here beyond awareness.

Question — RawRepresentation != nil in the guard. Including it means a provider update that carries only a raw representation (no id/role/contents) will still create an empty-content message. Within ToUpdates this cant happen (the extra response-level update has RawRepresentation == nil), so the round-trip tests pass — but for a live streaming provider that emits a final raw-only event, is materializing a message the intended behavior, or should raw-only also be treated as response-level? Might be worth a comment on the guard either way.

Question — the ToUpdates role default (msg.Role == "" -> RoleAssistant). This is a separate behavior tweak bundled into the same PR. It looks reasonable, but does it mirror what .NET does for an unroled message on ToChatResponseUpdates, and is it needed for the metadata-message case specifically? A one-line rationale in the code or PR body would help future readers see why it rides along.

Nothing blocking from my perspective — the direction is correct and well-tested. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Changes files in the agent area kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure size:large At most 300 changed lines across at most 10 files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants