Skip to content

Commit 5a8ba5b

Browse files
committed
DevUI: Use metadata.entity_id for agent/workflow name instead of model field
1 parent 820c6af commit 5a8ba5b

14 files changed

Lines changed: 86 additions & 77 deletions

File tree

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentRunResponseExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public static Response ToResponse(
5656
MaxOutputTokens = request.MaxOutputTokens,
5757
MaxToolCalls = request.MaxToolCalls,
5858
Metadata = request.Metadata is IReadOnlyDictionary<string, string> metadata ? new Dictionary<string, string>(metadata) : [],
59-
Model = request.Agent?.Name ?? request.Model,
59+
Model = request.Model,
6060
Output = output,
6161
ParallelToolCalls = request.ParallelToolCalls ?? true,
6262
PreviousResponseId = request.PreviousResponseId,

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentRunResponseUpdateExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ Response CreateResponse(ResponseStatus status = ResponseStatus.Completed, IEnume
165165
MaxOutputTokens = request.MaxOutputTokens,
166166
MaxToolCalls = request.MaxToolCalls,
167167
Metadata = request.Metadata != null ? new Dictionary<string, string>(request.Metadata) : [],
168-
Model = request.Agent?.Name ?? request.Model,
168+
Model = request.Model,
169169
Output = outputs?.ToList() ?? [],
170170
ParallelToolCalls = request.ParallelToolCalls ?? true,
171171
PreviousResponseId = request.PreviousResponseId,

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/HostedAgentResponseExecutor.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
1414

1515
/// <summary>
16-
/// Response executor that routes requests to hosted AIAgent services based on the model or agent.name parameter.
16+
/// Response executor that routes requests to hosted AIAgent services based on agent.name or metadata["entity_id"].
1717
/// This executor resolves agents from keyed services registered via AddAIAgent().
18+
/// The model field is reserved for actual model names and is never used for entity/agent identification.
1819
/// </summary>
1920
internal sealed class HostedAgentResponseExecutor : IResponseExecutor
2021
{
@@ -76,17 +77,26 @@ public async IAsyncEnumerable<StreamingResponseEvent> ExecuteAsync(
7677

7778
/// <summary>
7879
/// Resolves an agent from the service provider based on the request.
80+
/// Checks agent.name first, then metadata["entity_id"].
7981
/// </summary>
8082
/// <param name="request">The create response request.</param>
8183
/// <returns>The resolved AIAgent instance.</returns>
8284
/// <exception cref="InvalidOperationException">Thrown when the agent cannot be resolved.</exception>
8385
private AIAgent ResolveAgent(CreateResponse request)
8486
{
85-
// Extract agent name from agent.name or model parameter
86-
var agentName = request.Agent?.Name ?? request.Model;
87+
// Extract agent name from agent.name first (highest priority)
88+
string? agentName = request.Agent?.Name;
89+
90+
// Fall back to metadata["entity_id"] if agent.name is not present
91+
if (string.IsNullOrEmpty(agentName) && request.Metadata?.TryGetValue("entity_id", out string? entityId) == true)
92+
{
93+
agentName = entityId;
94+
}
95+
96+
// Never use model field for entity ID - it's for the actual model name
8797
if (string.IsNullOrEmpty(agentName))
8898
{
89-
throw new InvalidOperationException("No 'agent.name' or 'model' specified in the request.");
99+
throw new InvalidOperationException("No 'agent.name' or 'metadata.entity_id' specified in the request.");
90100
}
91101

92102
// Resolve the keyed agent service

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/InMemoryResponsesService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ private ResponseState InitializeResponse(string responseId, CreateResponse reque
371371
MaxOutputTokens = request.MaxOutputTokens,
372372
MaxToolCalls = request.MaxToolCalls,
373373
Metadata = metadata,
374-
Model = request.Model ?? "default",
374+
Model = request.Model,
375375
Output = [],
376376
ParallelToolCalls = request.ParallelToolCalls ?? true,
377377
PreviousResponseId = request.PreviousResponseId,

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/ResponsesHttpHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public async Task<IResult> CreateResponseAsync(
8282
}
8383
});
8484
}
85-
catch (InvalidOperationException ex) when (ex.Message.Contains("No 'agent.name' or 'model' specified"))
85+
catch (InvalidOperationException ex) when (ex.Message.Contains("No 'agent.name'"))
8686
{
8787
// Return OpenAI-style error for missing required parameters
8888
return Results.BadRequest(new ErrorResponse

dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIHttpApiIntegrationTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public async Task CreateConversationAndResponse_NonStreaming_NonBackground_Updat
4545
// Act - Create response (non-streaming, non-background)
4646
var createResponseRequest = new
4747
{
48-
model = AgentName,
48+
metadata = new { entity_id = AgentName },
4949
conversation = conversationId,
5050
input = UserMessage,
5151
stream = false
@@ -122,7 +122,7 @@ public async Task CreateConversationAndResponse_Streaming_NonBackground_UpdatesC
122122
// Act - Create response (streaming, non-background)
123123
var createResponseRequest = new
124124
{
125-
model = AgentName,
125+
metadata = new { entity_id = AgentName },
126126
conversation = conversationId,
127127
input = UserMessage,
128128
stream = true
@@ -196,7 +196,7 @@ public async Task CreateConversationAndResponse_NonStreaming_Background_UpdatesC
196196
// Act - Create response (non-streaming, background)
197197
var createResponseRequest = new
198198
{
199-
model = AgentName,
199+
metadata = new { entity_id = AgentName },
200200
conversation = conversationId,
201201
input = UserMessage,
202202
stream = false,

dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIResponsesAgentResolutionIntegrationTests.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,23 +168,23 @@ public async Task CreateResponse_WithMultipleAgents_ResolvesCorrectAgentAsync()
168168
}
169169

170170
/// <summary>
171-
/// Verifies that agent resolution using the model property works correctly.
171+
/// Verifies that agent resolution using the metadata.entity_id property works correctly.
172172
/// </summary>
173173
[Fact]
174-
public async Task CreateResponse_WithModelProperty_ResolvesCorrectAgentAsync()
174+
public async Task CreateResponse_WithMetadataEntityId_ResolvesCorrectAgentAsync()
175175
{
176176
// Arrange
177-
const string AgentName = "model-agent";
177+
const string AgentName = "metadata-agent";
178178
const string Instructions = "You are a helpful assistant.";
179-
const string ExpectedResponse = "Response via model property";
179+
const string ExpectedResponse = "Response via metadata.entity_id";
180180

181181
this._httpClient = await this.CreateTestServerWithAgentResolutionAsync(
182182
(AgentName, Instructions, ExpectedResponse));
183183

184-
// Act - Use raw HTTP request to control the model property
184+
// Act - Use raw HTTP request with metadata.entity_id
185185
using StringContent requestContent = new(JsonSerializer.Serialize(new
186186
{
187-
model = AgentName,
187+
metadata = new { entity_id = AgentName },
188188
input = new[]
189189
{
190190
new { type = "message", role = "user", content = "Test message" }
@@ -268,7 +268,6 @@ public async Task CreateResponse_WithoutAgentOrModel_ReturnsBadRequestAsync()
268268

269269
string responseJson = await httpResponse.Content.ReadAsStringAsync();
270270
Assert.Contains("agent.name", responseJson, StringComparison.OrdinalIgnoreCase);
271-
Assert.Contains("model", responseJson, StringComparison.OrdinalIgnoreCase);
272271
}
273272

274273
/// <summary>

python/packages/devui/agent_framework_devui/models/_openai_custom.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ class AgentFrameworkRequest(BaseModel):
139139
"""OpenAI ResponseCreateParams with Agent Framework routing.
140140
141141
This properly extends the real OpenAI API request format.
142-
- Uses 'model' field as entity_id (agent/workflow name)
142+
- Uses 'metadata.entity_id' field for entity/agent/workflow identification
143143
- Uses 'conversation' field for conversation context (OpenAI standard)
144144
"""
145145

146146
# All OpenAI fields from ResponseCreateParams
147-
model: str # Used as entity_id in DevUI!
147+
model: str | None = None
148148
input: str | list[Any] | dict[str, Any] # ResponseInputParam + dict for workflow structured input
149149
stream: bool | None = False
150150

@@ -164,12 +164,13 @@ class AgentFrameworkRequest(BaseModel):
164164
model_config = ConfigDict(extra="allow")
165165

166166
def get_entity_id(self) -> str:
167-
"""Get entity_id from model field.
167+
"""Get entity_id from metadata dictionary.
168168
169-
In DevUI, model IS the entity_id (agent/workflow name).
170-
Simple and clean!
169+
In DevUI, entity_id is stored in metadata["entity_id"].
171170
"""
172-
return self.model
171+
if self.metadata and "entity_id" in self.metadata:
172+
return str(self.metadata["entity_id"])
173+
return ""
173174

174175
def get_conversation_id(self) -> str | None:
175176
"""Extract conversation_id from conversation parameter.

python/packages/devui/agent_framework_devui/ui/assets/index.js

Lines changed: 39 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/packages/devui/frontend/src/services/api.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ class ApiClient {
549549
resumeResponseId?: string
550550
): AsyncGenerator<ExtendedResponseStreamEvent, void, unknown> {
551551
const openAIRequest: AgentFrameworkRequest = {
552-
model: agentId, // Model IS the entity_id (simplified routing!)
552+
metadata: { entity_id: agentId },
553553
input: request.input, // Direct OpenAI ResponseInputParam
554554
stream: true,
555555
conversation: request.conversation_id, // OpenAI standard conversation param
@@ -573,9 +573,9 @@ class ApiClient {
573573
workflowId: string,
574574
request: RunWorkflowRequest
575575
): AsyncGenerator<ExtendedResponseStreamEvent, void, unknown> {
576-
// Convert to OpenAI format - use model field for entity_id (same as agents)
576+
// Convert to OpenAI format - use metadata.entity_id for workflow ID
577577
const openAIRequest: AgentFrameworkRequest = {
578-
model: workflowId, // Use workflow ID in model field (matches agent pattern)
578+
metadata: { entity_id: workflowId },
579579
input: request.input_data || "", // Send dict directly, no stringification needed
580580
stream: true,
581581
conversation: request.conversation_id, // Include conversation if present

0 commit comments

Comments
 (0)