Skip to content

[dotnet-port-api] Add Response.ModelID - #1156

Open
PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:feat/response-modelid
Open

PratikDhanave (PratikDhanave) wants to merge 5 commits into
microsoft:mainfrom
PratikDhanaveFork:feat/response-modelid

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Implements #1140.

M.E.AI ChatResponse/ChatResponseUpdate carry a ModelId that ProcessUpdate folds onto the response. Go had no canonical, provider-agnostic way to read which model produced a response — it was only surfaced ad hoc via provider AdditionalProperties.

Change

  • Add ModelID to agent.Response and agent.ResponseUpdate.
  • Fold it in Response.Update (like ResponseID/FinishReason) and propagate it in ToUpdates.
  • Populate it from the model on the response in the OpenAI (chat + responses), Anthropic, and Gemini providers.

ConversationID from the issue is intentionally omitted: Go conversation identity lives on Session.ServiceID, so a separate field would be redundant.

Tests

  • TestResponse_Update_ModelID: ModelID folds from a later update and round-trips through ToUpdates.
  • TestChatModelIDSurfaced: the OpenAI chat provider surfaces the response model on Response.ModelID.

Response and ResponseUpdate now carry ModelID, the identifier of the model
that produced the response, matching M.E.AI ChatResponse/ChatResponseUpdate
(ProcessUpdate folds ModelId). Response.Update folds it like ResponseID, and
ToUpdates round-trips it. The OpenAI (chat + responses), Anthropic, and Gemini
providers populate it from the model on the response.

ConversationID is intentionally not added: in Go the conversation identity
lives on Session.ServiceID, so a separate field would be redundant.

Implements microsoft#1140.
Copilot AI lite review requested due to automatic review settings September 22, 2026 05:22
@github-actions github-actions Bot added area:agent Changes files in the agent area area:provider Changes files in the provider area area:provider/anthropic Changes files in the provider / anthropic area area:provider/gemini Changes files in the provider / gemini area area:provider/openai Changes files in the provider / openai area size:large At most 300 changed lines across at most 10 files labels Sep 22, 2026

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

Streaming model IDs and metadata-only round-tripping remain incomplete.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 4 Medium severity

Open (4)
What changed in this PR

Adds provider-agnostic ModelID fields to agent responses and response updates, with provider mappings and folding support.

Changes:

  • Adds and propagates ModelID.
  • Surfaces model IDs across OpenAI, Anthropic, and Gemini providers.
  • Adds response and OpenAI chat tests.
File Reviewed changes
provider/​openaiprovider/​responses.go Non-streaming model ID is populated; streaming updates still omit it.
provider/​openaiprovider/​chat.go Surfaces the model ID for chat responses.
provider/​openaiprovider/​chat_test.go Tests OpenAI chat model ID exposure.
provider/​geminiprovider/​agent.go Non-streaming model ID is populated; streaming updates still omit it.
provider/​anthropicprovider/​agent.go Non-streaming model ID is populated; streaming updates still omit it.
agent/​response.go Adds and folds ModelID; metadata-only updates omit it during round-tripping.
agent/​response_test.go Tests model ID folding and round-tripping.

💡 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
AgentID: resp.AgentID,
MessageID: msg.ID,
ResponseID: resp.ID,
ModelID: resp.ModelID,
Role: message.RoleAssistant,
MessageID: resp.ID,
ResponseID: resp.ID,
ModelID: string(resp.Model),
yield(&agent.ResponseUpdate{
Contents: responseContents,
Role: message.RoleAssistant,
ModelID: resp.ModelVersion,
Comment thread provider/openaiprovider/responses.go Outdated

currentUpdate := &agent.ResponseUpdate{
ResponseID: resp.ID,
ModelID: resp.Model,
@github-actions github-actions Bot added kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure labels Sep 22, 2026
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the public-api-change Pull Request changes public APIs label Sep 22, 2026

@github-actions github-actions Bot 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.

Generated by Go API Consistency Review Agent · copilot · auto · 98.2 AIC · ⌖ 5.96 AIC · ⊞ 9.2K

Comment thread agent/response.go

// ModelID is the identifier of the model that produced this response, when
// the provider supplies it. It is empty otherwise.
ModelID string `json:",omitzero"`

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.

This PR adds ModelID to agent.Response/agent.ResponseUpdate (the Go equivalent of upstream AgentResponse/AgentResponseUpdate, not ChatResponse/ChatResponseUpdate). Upstream, ModelId/model exists only on the lower-level ChatResponse/ChatResponseUpdate types:

  • .NET: Microsoft.Agents.AI.Abstractions/AgentResponse.cs and AgentResponseUpdate.cs have no ModelId property (properties are AgentId, ResponseId, ContinuationToken, CreatedAt, FinishReason, Usage, RawRepresentation, AdditionalProperties). ModelId is folded only in AIAgentChatClient.CloneWithConversationId on ChatResponse (dotnet/src/Microsoft.Agents.AI/ChatClient/AIAgentChatClient.cs:368), and referenced in AgentResponseUpdateTests.ConstructorWithChatResponseUpdateRoundtrips (dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs:42) as a ChatResponseUpdate field that is not asserted to roundtrip onto AgentResponseUpdate.
  • Python: AgentResponse.__init__/AgentResponseUpdate.__init__ (python/packages/core/agent_framework/_types.py:2848 and :3134) take no model/model_id parameter, while the sibling ChatResponse.__init__/ChatResponseUpdate.__init__ (same file, :2449 and :2735) do.

So both upstream implementations deliberately keep model identity at the chat-client layer and omit it from the agent-level response contract. Adding ModelID unconditionally to Go's agent.Response/ResponseUpdate (the AgentResponse analog) is a divergence from that design, not merely an idiomatic Go difference — it's a new agent-level field neither upstream implementation exposes.

Suggested resolution: either (a) confirm with maintainers this is an intentional Go-specific enhancement over the agent-level contract (and document that divergence), or (b) if a ModelID surface is wanted, scope it to a chat-level type analogous to ChatResponse/ChatResponseUpdate if/when Go introduces one, rather than agent.Response.

@qmuntal Quim Muntal (qmuntal) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fix review comments.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot 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.

Warning

Firewall blocked 3 domains

The following domains were blocked by the firewall during workflow execution:

  • proxy.golang.org
  • storage.googleapis.com
  • sum.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"
    - "storage.googleapis.com"
    - "sum.golang.org"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent for #1156 · copilot · auto · 125.5 AIC · ⌖ 7.69 AIC · ⊞ 13.9K

Comment thread agent/response.go

// ModelID is the identifier of the model that produced this response, when
// the provider supplies it. It is empty otherwise.
ModelID string `json:",omitzero"`

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.

Parity concern (unresolved from prior review): ModelID added at the agent-response layer, not the chat-response layer

Upstream .NET and Python deliberately keep model identity on the lower-level chat-response contract and omit it from the agent-level response contract that Go's agent.Response/ResponseUpdate mirror:

  • .NET: Microsoft.Agents.AI.Abstractions/AgentResponse.cs and AgentResponseUpdate.cs have no ModelId property (members are AgentId, ResponseId, ContinuationToken, CreatedAt, FinishReason, Usage, RawRepresentation, AdditionalProperties). ModelId is folded only onto ChatResponse in AIAgentChatClient.CloneWithConversationId (dotnet/src/Microsoft.Agents.AI/ChatClient/AIAgentChatClient.cs:368). AgentResponseUpdateTests.ConstructorWithChatResponseUpdateRoundtrips (dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs:42) exercises ModelId as a ChatResponseUpdate fixture field but never asserts it round-trips onto AgentResponseUpdate.
  • Python: AgentResponse.__init__/AgentResponseUpdate.__init__ (python/packages/core/agent_framework/_types.py:2848, :3134) take no model/model_id parameter, while sibling ChatResponse.__init__/ChatResponseUpdate.__init__ (same file, :2449, :2735) do.

Go has no chat-level response type distinct from agent.Response, so this PR's choice to add ModelID at the agent layer is a real API-shape divergence from both upstream implementations, not just an idiomatic naming difference. Suggest either explicitly confirming with maintainers that this is an intentional Go-specific widening of the agent-level contract (and documenting the divergence in the doc comment), or deferring the field to a future chat-level type if/when Go introduces one.


currentUpdate := &agent.ResponseUpdate{
ResponseID: resp.ID,
ModelID: resp.Model,

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.

ModelID is only propagated on non-streaming responses across three providers

This PR populates ResponseUpdate.ModelID from the final/non-streaming response object in responsesProcessResponse (here), provider/anthropicprovider/agent.go (non-streaming branch, resp.Model), and provider/geminiprovider/agent.go (non-streaming branch, resp.ModelVersion). None of the corresponding streaming paths set it:

  • responsesProcessStreamingUpdate (provider/openaiprovider/responses.go) builds every streaming agent.ResponseUpdate via createUpdate, which never sets ModelID, even though ResponseCreatedEvent/ResponseCompletedEvent carry event.Response.Model.
  • The Anthropic streaming loop (provider/anthropicprovider/agent.go, around the stream.Next() loop) never reads accumulated.Model or event.Message.Model into the yielded updates.
  • The Gemini streaming loop (provider/geminiprovider/agent.go, around the per-chunk yield) never reads resp.ModelVersion into the yielded updates.

Upstream M.E.AI sets ChatResponseUpdate.ModelId consistently for both streaming and non-streaming chat responses (the same ModelId property backs both ChatResponse and ChatResponseUpdate, and provider adapters populate it per-chunk). As written, collecting a streaming run via ResponseStream.Collect on any of these three providers silently produces an empty Response.ModelID, even though the PR's non-streaming tests pass. Suggest populating ModelID in each streaming update from the per-chunk/per-event model field, matching the non-streaming branches added in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed — ModelID is now set on the streaming updates for all three providers: OpenAI Responses threads the model through responsesStreamState from the lifecycle events' Response.Model; Anthropic reads it from the message_start Message.Model; Gemini reads each chunk's ModelVersion (and the final usage update). Added TestStreamingModelIDSurfaced to lock in the Collect() round-trip.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

API consistency review

Result Scope
aligned public API

Changed Go contract

  • agent.Response.ModelID (new exported field, json:",omitzero"): identifier of the model that produced the response, empty when the provider doesn't supply one.
  • agent.ResponseUpdate.ModelID (new exported field, same tag): per-update model identifier.
  • Response.ToUpdates() now carries ModelID onto per-message updates and triggers/extends the metadata-only trailing update when ModelID != "".
  • Response.Update() folds ModelID using cmp.Or(update.ModelID, resp.ModelID) — last non-empty value wins, matching how ResponseID/FinishReason are folded.
  • Provider wiring: anthropicprovider, geminiprovider, and openaiprovider (chat + responses, streaming and non-streaming) now populate ModelID from the respective SDK response/event fields (resp.Model, event.Message.Model, resp.ModelVersion, lifecycle lifecycle lifecycle events' event.Response.Model).

Upstream evidence reviewed

Implementation Source Contract checked
.NET / MEAI Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponse.cs — ModelId property and ToChatResponseUpdates() ChatResponse.ModelId is an optional string surfaced from the provider; ToChatResponseUpdates() copies ModelId onto every per-message ChatResponseUpdate alongside ResponseId/ConversationId/FinishReason
.NET / MEAI Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseUpdate.cs — ModelId property and doc remark Doc explicitly notes that when multiple updates carry different ModelId values, only one is used to populate ChatResponse.ModelId when converting back via ToChatResponseAsync — i.e., a last-wins/aggregation fold, consistent with Go's cmp.Or fold in Response.Update

Assessment

The new ModelID field and its fold/round-trip behavior are a direct, faithful port of MEAI's ChatResponse.ModelId / ChatResponseUpdate.ModelId design: same optional/empty-when-unsupported semantics, same placement on both the response and update types, and the same approach of surfacing it on every per-message update plus the metadata-only trailing update. Provider-side population (Anthropic message_start/message_delta, Gemini ModelVersion, OpenAI Chat/Responses model/lifecycle events) is plumbing that maps directly onto each SDK's own exposed model field and introduces no behavioral divergence from .NET. No gating or default-value concerns apply since this is purely additive, backward-compatible metadata (zero value omitted via omitzero, matching string? nullable-equivalent semantics in C#). No examples were changed by this PR.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · copilot · auto · 36.2 AIC · ⌖ 5.84 AIC · ⊞ 13.9K · ◷

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Oct 8, 2026
@qmuntal
Quim Muntal (qmuntal) requested a balanced review from Copilot October 8, 2026 07:56

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.

🟡 Changes recommended

A model-only response round-trip creates an empty assistant message instead of preserving an empty message list.

6 open findings

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread agent/response.go
Comment on lines +48 to +50
// ModelID is the identifier of the model that produced this response, when
// the provider supplies it. It is empty otherwise.
ModelID string `json:",omitzero"`
Comment thread agent/response.go
}

if hasAdditionalProperties || resp.ContinuationToken != "" {
if hasAdditionalProperties || resp.ContinuationToken != "" || resp.ModelID != "" {
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 area:provider/anthropic Changes files in the provider / anthropic area area:provider/gemini Changes files in the provider / gemini area area:provider/openai Changes files in the provider / openai area area:provider Changes files in the provider area kind:code Changes production behavior or code kind:tests Changes tests, fixtures, or test infrastructure parity-approved Go API consistency review found no parity issues public-api-change Pull Request changes public APIs 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