Skip to content

Commit 16da89a

Browse files
.Net: Add sample showing how to use web and file search (#12611)
### Motivation and Context Partial fix for #12562 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄
1 parent b78307c commit 16da89a

File tree

9 files changed

+250
-16
lines changed

9 files changed

+250
-16
lines changed

dotnet/samples/GettingStartedWithAgents/OpenAIResponse/Step03_OpenAIResponseAgent_ReasoningModel.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
using Microsoft.SemanticKernel;
33
using Microsoft.SemanticKernel.Agents.OpenAI;
4+
using OpenAI.Responses;
45

56
namespace GettingStarted.OpenAIResponseAgents;
67

@@ -26,4 +27,57 @@ public async Task UseOpenAIResponseAgentWithAReasoningModelAsync()
2627
WriteAgentChatMessage(responseItem);
2728
}
2829
}
30+
31+
[Fact]
32+
public async Task UseOpenAIResponseAgentWithAReasoningModelAndSummariesAsync()
33+
{
34+
// Define the agent
35+
OpenAIResponseAgent agent = new(this.Client);
36+
37+
// ResponseCreationOptions allows you to specify tools for the agent.
38+
OpenAIResponseAgentInvokeOptions invokeOptions = new()
39+
{
40+
ResponseCreationOptions = new()
41+
{
42+
ReasoningOptions = new()
43+
{
44+
ReasoningEffortLevel = ResponseReasoningEffortLevel.High,
45+
// This parameter cannot be used due to a known issue in the OpenAI .NET SDK.
46+
// https://github.com/openai/openai-dotnet/issues/457
47+
// ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed,
48+
},
49+
},
50+
};
51+
52+
// Invoke the agent and output the response
53+
var responseItems = agent.InvokeAsync(
54+
"""
55+
Instructions:
56+
- Given the React component below, change it so that nonfiction books have red
57+
text.
58+
- Return only the code in your reply
59+
- Do not include any additional formatting, such as markdown code blocks
60+
- For formatting, use four space tabs, and do not allow any lines of code to
61+
exceed 80 columns
62+
const books = [
63+
{ title: 'Dune', category: 'fiction', id: 1 },
64+
{ title: 'Frankenstein', category: 'fiction', id: 2 },
65+
{ title: 'Moneyball', category: 'nonfiction', id: 3 },
66+
];
67+
export default function BookList() {
68+
const listItems = books.map(book =>
69+
<li>
70+
{book.title}
71+
</li>
72+
);
73+
return (
74+
<ul>{listItems}</ul>
75+
);
76+
}
77+
""", options: invokeOptions);
78+
await foreach (ChatMessageContent responseItem in responseItems)
79+
{
80+
WriteAgentChatMessage(responseItem);
81+
}
82+
}
2983
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ClientModel.Primitives;
4+
using Microsoft.SemanticKernel;
5+
using Microsoft.SemanticKernel.Agents.OpenAI;
6+
using Microsoft.SemanticKernel.ChatCompletion;
7+
using OpenAI.Files;
8+
using OpenAI.Responses;
9+
using OpenAI.VectorStores;
10+
using Plugins;
11+
using Resources;
12+
13+
namespace GettingStarted.OpenAIResponseAgents;
14+
15+
/// <summary>
16+
/// This example demonstrates how to use tools during a model interaction using <see cref="OpenAIResponseAgent"/>.
17+
/// </summary>
18+
public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseResponsesAgentTest(output)
19+
{
20+
[Fact]
21+
public async Task InvokeAgentWithFunctionToolsAsync()
22+
{
23+
// Define the agent
24+
OpenAIResponseAgent agent = new(this.Client)
25+
{
26+
StoreEnabled = false,
27+
};
28+
29+
// Create a plugin that defines the tools to be used by the agent.
30+
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
31+
var tools = plugin.Select(f => f.ToToolDefinition(plugin.Name));
32+
agent.Kernel.Plugins.Add(plugin);
33+
34+
ICollection<ChatMessageContent> messages =
35+
[
36+
new ChatMessageContent(AuthorRole.User, "What is the special soup and its price?"),
37+
new ChatMessageContent(AuthorRole.User, "What is the special drink and its price?"),
38+
];
39+
foreach (ChatMessageContent message in messages)
40+
{
41+
WriteAgentChatMessage(message);
42+
}
43+
44+
// Invoke the agent and output the response
45+
var responseItems = agent.InvokeAsync(messages);
46+
await foreach (ChatMessageContent responseItem in responseItems)
47+
{
48+
WriteAgentChatMessage(responseItem);
49+
}
50+
}
51+
52+
[Fact]
53+
public async Task InvokeAgentWithWebSearchAsync()
54+
{
55+
// Define the agent
56+
OpenAIResponseAgent agent = new(this.Client)
57+
{
58+
StoreEnabled = false,
59+
};
60+
61+
// ResponseCreationOptions allows you to specify tools for the agent.
62+
ResponseCreationOptions creationOptions = new();
63+
creationOptions.Tools.Add(ResponseTool.CreateWebSearchTool());
64+
OpenAIResponseAgentInvokeOptions invokeOptions = new()
65+
{
66+
ResponseCreationOptions = creationOptions,
67+
};
68+
69+
// Invoke the agent and output the response
70+
var responseItems = agent.InvokeAsync("What was a positive news story from today?", options: invokeOptions);
71+
await foreach (ChatMessageContent responseItem in responseItems)
72+
{
73+
WriteAgentChatMessage(responseItem);
74+
}
75+
}
76+
77+
[Fact]
78+
public async Task InvokeAgentWithFileSearchAsync()
79+
{
80+
// Upload a file to the OpenAI File API
81+
await using Stream stream = EmbeddedResource.ReadStream("employees.pdf")!;
82+
OpenAIFile file = await this.FileClient.UploadFileAsync(stream, filename: "employees.pdf", purpose: FileUploadPurpose.UserData);
83+
84+
// Create a vector store for the file
85+
CreateVectorStoreOperation createStoreOp = await this.VectorStoreClient.CreateVectorStoreAsync(
86+
waitUntilCompleted: true,
87+
new VectorStoreCreationOptions()
88+
{
89+
FileIds = { file.Id },
90+
});
91+
92+
// Define the agent
93+
OpenAIResponseAgent agent = new(this.Client)
94+
{
95+
StoreEnabled = false,
96+
};
97+
98+
// ResponseCreationOptions allows you to specify tools for the agent.
99+
ResponseCreationOptions creationOptions = new();
100+
creationOptions.Tools.Add(ResponseTool.CreateFileSearchTool([createStoreOp.VectorStoreId], null));
101+
OpenAIResponseAgentInvokeOptions invokeOptions = new()
102+
{
103+
ResponseCreationOptions = creationOptions,
104+
};
105+
106+
// Invoke the agent and output the response
107+
ICollection<ChatMessageContent> messages =
108+
[
109+
new ChatMessageContent(AuthorRole.User, "Who is the youngest employee?"),
110+
new ChatMessageContent(AuthorRole.User, "Who works in sales?"),
111+
new ChatMessageContent(AuthorRole.User, "I have a customer request, who can help me?"),
112+
];
113+
foreach (ChatMessageContent message in messages)
114+
{
115+
WriteAgentChatMessage(message);
116+
}
117+
118+
// Invoke the agent and output the response
119+
var responseItems = agent.InvokeAsync(messages, options: invokeOptions);
120+
await foreach (ChatMessageContent responseItem in responseItems)
121+
{
122+
WriteAgentChatMessage(responseItem);
123+
}
124+
125+
// Clean up resources
126+
RequestOptions noThrowOptions = new() { ErrorOptions = ClientErrorBehaviors.NoThrow };
127+
this.FileClient.DeleteFile(file.Id, noThrowOptions);
128+
this.VectorStoreClient.DeleteVectorStore(createStoreOp.VectorStoreId, noThrowOptions);
129+
}
130+
}

dotnet/src/Agents/OpenAI/Extensions/OpenAIResponseExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ public static ChatMessageContent ToChatMessageContent(this OpenAIResponse respon
5050
var role = messageResponseItem.Role.ToAuthorRole();
5151
return new ChatMessageContent(role, item.ToChatMessageContentItemCollection(), innerContent: messageResponseItem);
5252
}
53+
else if (item is ReasoningResponseItem reasoningResponseItem)
54+
{
55+
if (reasoningResponseItem.SummaryTextParts is not null && reasoningResponseItem.SummaryTextParts.Count > 0)
56+
{
57+
return new ChatMessageContent(AuthorRole.Assistant, item.ToChatMessageContentItemCollection(), innerContent: reasoningResponseItem);
58+
}
59+
}
5360
else if (item is FunctionCallResponseItem functionCallResponseItem)
5461
{
5562
return new ChatMessageContent(AuthorRole.Assistant, item.ToChatMessageContentItemCollection(), innerContent: functionCallResponseItem);
@@ -68,6 +75,10 @@ public static ChatMessageContentItemCollection ToChatMessageContentItemCollectio
6875
{
6976
return messageResponseItem.Content.ToChatMessageContentItemCollection();
7077
}
78+
else if (item is ReasoningResponseItem reasoningResponseItem)
79+
{
80+
return reasoningResponseItem.SummaryTextParts.ToChatMessageContentItemCollection();
81+
}
7182
else if (item is FunctionCallResponseItem functionCallResponseItem)
7283
{
7384
Exception? exception = null;
@@ -183,6 +194,16 @@ private static ChatMessageContentItemCollection ToChatMessageContentItemCollecti
183194
}
184195
return collection;
185196
}
197+
198+
private static ChatMessageContentItemCollection ToChatMessageContentItemCollection(this IReadOnlyList<string> texts)
199+
{
200+
var collection = new ChatMessageContentItemCollection();
201+
foreach (var text in texts)
202+
{
203+
collection.Add(new TextContent(text, innerContent: null));
204+
}
205+
return collection;
206+
}
186207
#endregion
187208

188209
}

dotnet/src/Agents/OpenAI/OpenAIResponseAgent.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public OpenAIResponseAgent(OpenAIResponseClient client)
3737
/// <summary>
3838
/// Storing of messages is enabled.
3939
/// </summary>
40-
public bool StoreEnabled { get; init; } = true;
40+
public bool StoreEnabled { get; init; } = false;
4141

4242
/// <inheritdoc/>
4343
public override async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> InvokeAsync(ICollection<ChatMessageContent> messages, AgentThread? thread = null, AgentInvokeOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
@@ -47,7 +47,7 @@ public override async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> In
4747
AgentThread agentThread = await this.EnsureThreadExistsWithMessagesAsync(messages, thread, cancellationToken).ConfigureAwait(false);
4848

4949
// Get the context contributions from the AIContextProviders.
50-
OpenAIAssistantAgentInvokeOptions extensionsContextOptions = await this.FinalizeInvokeOptionsAsync(messages, options, agentThread, cancellationToken).ConfigureAwait(false);
50+
OpenAIResponseAgentInvokeOptions extensionsContextOptions = await this.FinalizeInvokeOptionsAsync(messages, options, agentThread, cancellationToken).ConfigureAwait(false);
5151

5252
// Invoke responses with the updated chat history.
5353
ChatHistory chatHistory = [.. messages];
@@ -74,7 +74,7 @@ public override async IAsyncEnumerable<AgentResponseItem<StreamingChatMessageCon
7474
AgentThread agentThread = await this.EnsureThreadExistsWithMessagesAsync(messages, thread, cancellationToken).ConfigureAwait(false);
7575

7676
// Get the context contributions from the AIContextProviders.
77-
OpenAIAssistantAgentInvokeOptions extensionsContextOptions = await this.FinalizeInvokeOptionsAsync(messages, options, agentThread, cancellationToken).ConfigureAwait(false);
77+
OpenAIResponseAgentInvokeOptions extensionsContextOptions = await this.FinalizeInvokeOptionsAsync(messages, options, agentThread, cancellationToken).ConfigureAwait(false);
7878

7979
// Invoke responses with the updated chat history.
8080
ChatHistory chatHistory = [.. messages];
@@ -138,7 +138,7 @@ private async Task<AgentThread> EnsureThreadExistsWithMessagesAsync(ICollection<
138138
return await this.EnsureThreadExistsWithMessagesAsync(messages, thread, () => new ChatHistoryAgentThread(), cancellationToken).ConfigureAwait(false);
139139
}
140140

141-
private async Task<OpenAIAssistantAgentInvokeOptions> FinalizeInvokeOptionsAsync(ICollection<ChatMessageContent> messages, AgentInvokeOptions? options, AgentThread agentThread, CancellationToken cancellationToken)
141+
private async Task<OpenAIResponseAgentInvokeOptions> FinalizeInvokeOptionsAsync(ICollection<ChatMessageContent> messages, AgentInvokeOptions? options, AgentThread agentThread, CancellationToken cancellationToken)
142142
{
143143
Kernel kernel = this.GetKernel(options);
144144
#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
@@ -160,7 +160,7 @@ private async Task<OpenAIAssistantAgentInvokeOptions> FinalizeInvokeOptionsAsync
160160
#pragma warning restore SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
161161

162162
string mergedAdditionalInstructions = FormatAdditionalInstructions(providersContext, options);
163-
OpenAIAssistantAgentInvokeOptions extensionsContextOptions =
163+
OpenAIResponseAgentInvokeOptions extensionsContextOptions =
164164
options is null ?
165165
new()
166166
{

dotnet/src/Agents/OpenAI/OpenAIResponseAgentInvokeOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public OpenAIResponseAgentInvokeOptions(AgentInvokeOptions options)
2424
: base(options)
2525
{
2626
Verify.NotNull(options);
27+
28+
if (options is OpenAIResponseAgentInvokeOptions responseAgentInvokeOptions)
29+
{
30+
this.ResponseCreationOptions = responseAgentInvokeOptions.ResponseCreationOptions;
31+
}
2732
}
2833

2934
/// <summary>
@@ -34,6 +39,8 @@ public OpenAIResponseAgentInvokeOptions(OpenAIResponseAgentInvokeOptions options
3439
: base(options)
3540
{
3641
Verify.NotNull(options);
42+
43+
this.ResponseCreationOptions = options.ResponseCreationOptions;
3744
}
3845

3946
/// <summary>

dotnet/src/Agents/UnitTests/Extensions/ResponseItemExtensionsTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public void VerifyToChatMessageContentFromReasoning()
109109
var messageContent = responseItem.ToChatMessageContent();
110110

111111
// Assert
112-
Assert.Null(messageContent);
112+
Assert.NotNull(messageContent);
113+
Assert.Equal("Foo", messageContent.Content);
113114
}
114115
}

dotnet/src/Agents/UnitTests/OpenAI/OpenAIResponseAgentTests.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ public async Task VerifyOpenAIResponseAgentThrowsWhenUseImmutableKernelFalseWith
154154
// Arrange
155155
var agent = new OpenAIResponseAgent(this.Client)
156156
{
157-
UseImmutableKernel = false // Explicitly set to false
157+
UseImmutableKernel = false, // Explicitly set to false
158+
StoreEnabled = true,
158159
};
159160

160161
var mockAIContextProvider = new Mock<AIContextProvider>();
@@ -182,7 +183,10 @@ public async Task VerifyOpenAIResponseAgentThrowsWhenUseImmutableKernelFalseWith
182183
public async Task VerifyOpenAIResponseAgentThrowsWhenUseImmutableKernelDefaultWithAIFunctionsAsync()
183184
{
184185
// Arrange
185-
var agent = new OpenAIResponseAgent(this.Client);
186+
var agent = new OpenAIResponseAgent(this.Client)
187+
{
188+
StoreEnabled = true,
189+
};
186190
// UseImmutableKernel not set, should default to false
187191

188192
var mockAIContextProvider = new Mock<AIContextProvider>();
@@ -216,7 +220,8 @@ public async Task VerifyOpenAIResponseAgentKernelImmutabilityWhenUseImmutableKer
216220

217221
var agent = new OpenAIResponseAgent(this.Client)
218222
{
219-
UseImmutableKernel = true
223+
UseImmutableKernel = true,
224+
StoreEnabled = true,
220225
};
221226

222227
var originalKernel = agent.Kernel;
@@ -259,7 +264,8 @@ public async Task VerifyOpenAIResponseAgentMutableKernelWhenUseImmutableKernelFa
259264

260265
var agent = new OpenAIResponseAgent(this.Client)
261266
{
262-
UseImmutableKernel = false
267+
UseImmutableKernel = false,
268+
StoreEnabled = true,
263269
};
264270

265271
var originalKernel = agent.Kernel;
@@ -295,7 +301,8 @@ public async Task VerifyOpenAIResponseAgentStreamingThrowsWhenUseImmutableKernel
295301
// Arrange
296302
var agent = new OpenAIResponseAgent(this.Client)
297303
{
298-
UseImmutableKernel = false // Explicitly set to false
304+
UseImmutableKernel = false, // Explicitly set to false
305+
StoreEnabled = true,
299306
};
300307

301308
var mockAIContextProvider = new Mock<AIContextProvider>();
@@ -323,7 +330,10 @@ public async Task VerifyOpenAIResponseAgentStreamingThrowsWhenUseImmutableKernel
323330
public async Task VerifyOpenAIResponseAgentStreamingThrowsWhenUseImmutableKernelDefaultWithAIFunctionsAsync()
324331
{
325332
// Arrange
326-
var agent = new OpenAIResponseAgent(this.Client);
333+
var agent = new OpenAIResponseAgent(this.Client)
334+
{
335+
StoreEnabled = true,
336+
};
327337
// UseImmutableKernel not set, should default to false
328338

329339
var mockAIContextProvider = new Mock<AIContextProvider>();
@@ -357,7 +367,8 @@ public async Task VerifyOpenAIResponseAgentStreamingKernelImmutabilityWhenUseImm
357367

358368
var agent = new OpenAIResponseAgent(this.Client)
359369
{
360-
UseImmutableKernel = true
370+
UseImmutableKernel = true,
371+
StoreEnabled = true,
361372
};
362373

363374
var originalKernel = agent.Kernel;
@@ -400,7 +411,8 @@ public async Task VerifyOpenAIResponseAgentStreamingMutableKernelWhenUseImmutabl
400411

401412
var agent = new OpenAIResponseAgent(this.Client)
402413
{
403-
UseImmutableKernel = false
414+
UseImmutableKernel = false,
415+
StoreEnabled = true,
404416
};
405417

406418
var originalKernel = agent.Kernel;

0 commit comments

Comments
 (0)