Skip to content

Commit 68976a1

Browse files
committed
fix: Remove duplicate history entry creation and ad test
1 parent fe7e40c commit 68976a1

3 files changed

Lines changed: 102 additions & 94 deletions

File tree

dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowChatHistoryProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ internal void AddMessages(AgentSession session, params IEnumerable<ChatMessage>
4343
=> this._sessionState.GetOrInitializeState(session).Messages.AddRange(messages);
4444

4545
protected override ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default)
46-
=> new(this._sessionState.GetOrInitializeState(context.Session).Messages);
46+
=> new(this._sessionState.GetOrInitializeState(context.Session).Messages.AsReadOnly());
4747

4848
protected override ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default)
4949
{
@@ -65,7 +65,7 @@ public IEnumerable<ChatMessage> GetFromBookmark(AgentSession session)
6565
public IEnumerable<ChatMessage> GetAllMessages(AgentSession session)
6666
{
6767
var state = this._sessionState.GetOrInitializeState(session);
68-
return state.Messages;
68+
return state.Messages.AsReadOnly();
6969
}
7070

7171
public void UpdateBookmark(AgentSession session)

dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs

Lines changed: 74 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -110,35 +110,27 @@ public AgentResponseUpdate CreateUpdate(string responseId, object raw, params AI
110110
{
111111
Throw.IfNullOrEmpty(parts);
112112

113-
AgentResponseUpdate update = new(ChatRole.Assistant, parts)
113+
return new(ChatRole.Assistant, parts)
114114
{
115115
CreatedAt = DateTimeOffset.UtcNow,
116116
MessageId = Guid.NewGuid().ToString("N"),
117117
Role = ChatRole.Assistant,
118118
ResponseId = responseId,
119119
RawRepresentation = raw
120120
};
121-
122-
this.ChatHistoryProvider.AddMessages(this, update.ToChatMessage());
123-
124-
return update;
125121
}
126122

127123
public AgentResponseUpdate CreateUpdate(string responseId, object raw, ChatMessage message)
128124
{
129125
Throw.IfNull(message);
130126

131-
AgentResponseUpdate update = new(message.Role, message.Contents)
127+
return new(message.Role, message.Contents)
132128
{
133129
CreatedAt = message.CreatedAt ?? DateTimeOffset.UtcNow,
134130
MessageId = message.MessageId ?? Guid.NewGuid().ToString("N"),
135131
ResponseId = responseId,
136132
RawRepresentation = raw
137133
};
138-
139-
this.ChatHistoryProvider.AddMessages(this, update.ToChatMessage());
140-
141-
return update;
142134
}
143135

144136
private async ValueTask<StreamingRun> CreateOrResumeRunAsync(List<ChatMessage> messages, CancellationToken cancellationToken = default)
@@ -170,94 +162,86 @@ internal async
170162
IAsyncEnumerable<AgentResponseUpdate> InvokeStageAsync(
171163
[EnumeratorCancellation] CancellationToken cancellationToken = default)
172164
{
173-
try
174-
{
175-
this.LastResponseId = Guid.NewGuid().ToString("N");
176-
List<ChatMessage> messages = this.ChatHistoryProvider.GetFromBookmark(this).ToList();
165+
this.LastResponseId = Guid.NewGuid().ToString("N");
166+
List<ChatMessage> messages = this.ChatHistoryProvider.GetFromBookmark(this).ToList();
177167

178168
#pragma warning disable CA2007 // Analyzer misfiring and not seeing .ConfigureAwait(false) below.
179-
await using StreamingRun run =
180-
await this.CreateOrResumeRunAsync(messages, cancellationToken).ConfigureAwait(false);
169+
await using StreamingRun run =
170+
await this.CreateOrResumeRunAsync(messages, cancellationToken).ConfigureAwait(false);
181171
#pragma warning restore CA2007
182172

183-
await run.TrySendMessageAsync(new TurnToken(emitEvents: true)).ConfigureAwait(false);
184-
await foreach (WorkflowEvent evt in run.WatchStreamAsync(blockOnPendingRequest: false, cancellationToken)
185-
.ConfigureAwait(false)
186-
.WithCancellation(cancellationToken))
173+
await run.TrySendMessageAsync(new TurnToken(emitEvents: true)).ConfigureAwait(false);
174+
await foreach (WorkflowEvent evt in run.WatchStreamAsync(blockOnPendingRequest: false, cancellationToken)
175+
.ConfigureAwait(false)
176+
.WithCancellation(cancellationToken))
177+
{
178+
switch (evt)
187179
{
188-
switch (evt)
189-
{
190-
case AgentResponseUpdateEvent agentUpdate:
191-
yield return agentUpdate.Update;
192-
break;
193-
194-
case RequestInfoEvent requestInfo:
195-
FunctionCallContent fcContent = requestInfo.Request.ToFunctionCall();
196-
AgentResponseUpdate update = this.CreateUpdate(this.LastResponseId, evt, fcContent);
197-
yield return update;
198-
break;
199-
200-
case WorkflowErrorEvent workflowError:
201-
Exception? exception = workflowError.Exception;
202-
if (exception is TargetInvocationException tie && tie.InnerException != null)
203-
{
204-
exception = tie.InnerException;
205-
}
206-
207-
if (exception != null)
208-
{
209-
string message = this._includeExceptionDetails
210-
? exception.Message
211-
: "An error occurred while executing the workflow.";
212-
213-
ErrorContent errorContent = new(message);
214-
yield return this.CreateUpdate(this.LastResponseId, evt, errorContent);
215-
}
216-
217-
break;
218-
219-
case SuperStepCompletedEvent stepCompleted:
220-
this.LastCheckpoint = stepCompleted.CompletionInfo?.Checkpoint;
180+
case AgentResponseUpdateEvent agentUpdate:
181+
yield return agentUpdate.Update;
182+
break;
183+
184+
case RequestInfoEvent requestInfo:
185+
FunctionCallContent fcContent = requestInfo.Request.ToFunctionCall();
186+
AgentResponseUpdate update = this.CreateUpdate(this.LastResponseId, evt, fcContent);
187+
yield return update;
188+
break;
189+
190+
case WorkflowErrorEvent workflowError:
191+
Exception? exception = workflowError.Exception;
192+
if (exception is TargetInvocationException tie && tie.InnerException != null)
193+
{
194+
exception = tie.InnerException;
195+
}
196+
197+
if (exception != null)
198+
{
199+
string message = this._includeExceptionDetails
200+
? exception.Message
201+
: "An error occurred while executing the workflow.";
202+
203+
ErrorContent errorContent = new(message);
204+
yield return this.CreateUpdate(this.LastResponseId, evt, errorContent);
205+
}
206+
207+
break;
208+
209+
case SuperStepCompletedEvent stepCompleted:
210+
this.LastCheckpoint = stepCompleted.CompletionInfo?.Checkpoint;
211+
goto default;
212+
213+
case WorkflowOutputEvent output:
214+
IEnumerable<ChatMessage>? updateMessages = output.Data switch
215+
{
216+
IEnumerable<ChatMessage> chatMessages => chatMessages,
217+
ChatMessage chatMessage => [chatMessage],
218+
_ => null
219+
};
220+
221+
if (!this._includeWorkflowOutputsInResponse || updateMessages == null)
222+
{
221223
goto default;
222-
223-
case WorkflowOutputEvent output:
224-
IEnumerable<ChatMessage>? updateMessages = output.Data switch
225-
{
226-
IEnumerable<ChatMessage> chatMessages => chatMessages,
227-
ChatMessage chatMessage => [chatMessage],
228-
_ => null
229-
};
230-
231-
if (!this._includeWorkflowOutputsInResponse || updateMessages == null)
232-
{
233-
goto default;
234-
}
235-
236-
foreach (ChatMessage message in updateMessages)
237-
{
238-
yield return this.CreateUpdate(this.LastResponseId, evt, message);
239-
}
240-
break;
241-
242-
default:
243-
// Emit all other workflow events for observability (DevUI, logging, etc.)
244-
yield return new AgentResponseUpdate(ChatRole.Assistant, [])
245-
{
246-
CreatedAt = DateTimeOffset.UtcNow,
247-
MessageId = Guid.NewGuid().ToString("N"),
248-
Role = ChatRole.Assistant,
249-
ResponseId = this.LastResponseId,
250-
RawRepresentation = evt
251-
};
252-
break;
253-
}
224+
}
225+
226+
foreach (ChatMessage message in updateMessages)
227+
{
228+
yield return this.CreateUpdate(this.LastResponseId, evt, message);
229+
}
230+
break;
231+
232+
default:
233+
// Emit all other workflow events for observability (DevUI, logging, etc.)
234+
yield return new AgentResponseUpdate(ChatRole.Assistant, [])
235+
{
236+
CreatedAt = DateTimeOffset.UtcNow,
237+
MessageId = Guid.NewGuid().ToString("N"),
238+
Role = ChatRole.Assistant,
239+
ResponseId = this.LastResponseId,
240+
RawRepresentation = evt
241+
};
242+
break;
254243
}
255244
}
256-
finally
257-
{
258-
// Do we want to try to undo the step, and not update the bookmark?
259-
this.ChatHistoryProvider.UpdateBookmark(this);
260-
}
261245
}
262246

263247
public string? LastResponseId { get; set; }

dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,30 @@ public async Task Test_AsAgent_ErrorContentStreamedOutAsync(bool includeExceptio
114114
}
115115

116116
[Fact]
117-
public async Task Test_AsAgent_OutgoingMessagesInHistoryAsync()
117+
public async Task Test_SingleAgent_AsAgent_OutgoingMessagesInHistoryAsync()
118+
{
119+
// Arrange
120+
TestReplayAgent agent = new(TestMessages, TestAgentId, TestAgentName);
121+
Workflow singleAgentWorkflow = new WorkflowBuilder(agent).Build();
122+
AIAgent workflowAgent = singleAgentWorkflow.AsAIAgent();
123+
124+
// Act
125+
AgentSession session = await workflowAgent.CreateSessionAsync();
126+
AgentResponse response = await workflowAgent.RunAsync(session);
127+
128+
// Assert
129+
WorkflowSession workflowSession = session.Should().BeOfType<WorkflowSession>().Subject;
130+
131+
ChatMessage[] responseMessages = response.Messages.ToArray();
132+
ChatMessage[] sessionMessages = workflowSession.ChatHistoryProvider.GetAllMessages(workflowSession).ToArray();
133+
134+
// Since we never sent an incoming message, the expectation is that there should be nothing in the session
135+
// except the response
136+
responseMessages.Should().BeEquivalentTo(sessionMessages, options => options.WithStrictOrdering());
137+
}
138+
139+
[Fact]
140+
public async Task Test_Handoffs_AsAgent_OutgoingMessagesInHistoryAsync()
118141
{
119142
// Arrange
120143
TestReplayAgent agent = new(TestMessages, TestAgentId, TestAgentName);
@@ -132,6 +155,7 @@ public async Task Test_AsAgent_OutgoingMessagesInHistoryAsync()
132155
ChatMessage[] sessionMessages = workflowSession.ChatHistoryProvider.GetAllMessages(workflowSession).ToArray();
133156

134157
// Since we never sent an incoming message, the expectation is that there should be nothing in the session
135-
responseMessages.Should().BeEquivalentTo(sessionMessages);
158+
// except the response
159+
responseMessages.Should().BeEquivalentTo(sessionMessages, options => options.WithStrictOrdering());
136160
}
137161
}

0 commit comments

Comments
 (0)