From bea7668995b490ae0fe82a41f34558910c9eca7f Mon Sep 17 00:00:00 2001
From: markwallace-microsoft
<127216156+markwallace-microsoft@users.noreply.github.com>
Date: Tue, 25 Mar 2025 20:08:07 +0000
Subject: [PATCH 1/3] Tavily image search and integration tests
---
.../Data/BaseTextSearchTests.cs | 14 +--
.../Web/Tavily/TavilyTextSearchTests.cs | 48 +++++++++
.../TestSettings/TavilyConfiguration.cs | 13 +++
.../Web/Tavily/TavilyTextSearchTests.cs | 100 ++++++++++++++++++
.../Plugins.Web/Tavily/TavilyTextSearch.cs | 55 ++++++++--
.../Tavily/TavilyTextSearchOptions.cs | 19 +++-
6 files changed, 232 insertions(+), 17 deletions(-)
create mode 100644 dotnet/src/IntegrationTests/Plugins/Web/Tavily/TavilyTextSearchTests.cs
create mode 100644 dotnet/src/IntegrationTests/TestSettings/TavilyConfiguration.cs
diff --git a/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs b/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs
index d1f5c2e518ae..bd2821a532cf 100644
--- a/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs
+++ b/dotnet/src/IntegrationTests/Data/BaseTextSearchTests.cs
@@ -18,7 +18,7 @@ namespace SemanticKernel.IntegrationTests.Data;
///
public abstract class BaseTextSearchTests : BaseIntegrationTest
{
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task CanSearchAsync()
{
// Arrange
@@ -42,7 +42,7 @@ public virtual async Task CanSearchAsync()
}
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task CanGetTextSearchResultsAsync()
{
// Arrange
@@ -72,7 +72,7 @@ public virtual async Task CanGetTextSearchResultsAsync()
}
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task CanGetSearchResultsAsync()
{
// Arrange
@@ -92,7 +92,7 @@ public virtual async Task CanGetSearchResultsAsync()
Assert.True(this.VerifySearchResults(results, query));
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task UsingTextSearchWithAFilterAsync()
{
// Arrange
@@ -113,7 +113,7 @@ public virtual async Task UsingTextSearchWithAFilterAsync()
Assert.True(this.VerifySearchResults(results, query, filter));
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task FunctionCallingUsingCreateWithSearchAsync()
{
// Arrange
@@ -142,7 +142,7 @@ public virtual async Task FunctionCallingUsingCreateWithSearchAsync()
Assert.NotEmpty(results);
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task FunctionCallingUsingCreateWithGetSearchResultsAsync()
{
// Arrange
@@ -171,7 +171,7 @@ public virtual async Task FunctionCallingUsingCreateWithGetSearchResultsAsync()
Assert.NotEmpty(results);
}
- [Fact(Skip = "Failing in integration tests pipeline with - HTTP 429 (insufficient_quota) error.")]
+ [Fact(Skip = "For manual verification only.")]
public virtual async Task FunctionCallingUsingGetTextSearchResultsAsync()
{
// Arrange
diff --git a/dotnet/src/IntegrationTests/Plugins/Web/Tavily/TavilyTextSearchTests.cs b/dotnet/src/IntegrationTests/Plugins/Web/Tavily/TavilyTextSearchTests.cs
new file mode 100644
index 000000000000..ffc0e066b8d4
--- /dev/null
+++ b/dotnet/src/IntegrationTests/Plugins/Web/Tavily/TavilyTextSearchTests.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.Configuration;
+using Microsoft.SemanticKernel.Data;
+using Microsoft.SemanticKernel.Plugins.Web.Tavily;
+using SemanticKernel.IntegrationTests.Data;
+using SemanticKernel.IntegrationTests.TestSettings;
+using Xunit;
+
+namespace SemanticKernel.IntegrationTests.Plugins.Web.Tavily;
+
+///
+/// Integration tests for .
+///
+public class TavilyTextSearchTests : BaseTextSearchTests
+{
+ ///
+ public override Task CreateTextSearchAsync()
+ {
+ var configuration = this.Configuration.GetSection("Tavily").Get();
+ Assert.NotNull(configuration);
+ Assert.NotNull(configuration.ApiKey);
+
+ return Task.FromResult(new TavilyTextSearch(apiKey: configuration.ApiKey));
+ }
+
+ ///
+ public override string GetQuery() => "What is the Semantic Kernel?";
+
+ ///
+ public override TextSearchFilter GetTextSearchFilter() => new TextSearchFilter().Equality("include_domain", "devblogs.microsoft.com");
+
+ ///
+ public override bool VerifySearchResults(object[] results, string query, TextSearchFilter? filter = null)
+ {
+ Assert.NotNull(results);
+ Assert.NotEmpty(results);
+ Assert.Equal(4, results.Length);
+ foreach (var result in results)
+ {
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ return true;
+ }
+}
diff --git a/dotnet/src/IntegrationTests/TestSettings/TavilyConfiguration.cs b/dotnet/src/IntegrationTests/TestSettings/TavilyConfiguration.cs
new file mode 100644
index 000000000000..9a0cdca8bbde
--- /dev/null
+++ b/dotnet/src/IntegrationTests/TestSettings/TavilyConfiguration.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace SemanticKernel.IntegrationTests.TestSettings;
+
+[SuppressMessage("Performance", "CA1812:Internal class that is apparently never instantiated",
+ Justification = "Configuration classes are instantiated through IConfiguration.")]
+
+internal sealed class TavilyConfiguration(string apiKey)
+{
+ public string ApiKey { get; init; } = apiKey;
+}
diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs
index ac20d80e9aaf..923fb8357d32 100644
--- a/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs
+++ b/dotnet/src/Plugins/Plugins.UnitTests/Web/Tavily/TavilyTextSearchTests.cs
@@ -172,6 +172,106 @@ public async Task GetTextSearchResultsWithCustomResultMapperReturnsSuccessfullyA
}
}
+ [Fact]
+ public async Task SearchWithAnswerReturnsSuccessfullyAsync()
+ {
+ // Arrange
+ this._messageHandlerStub.AddJsonResponse(File.ReadAllText(WhatIsTheSKResponseJson));
+
+ // Create an ITextSearch instance using Tavily search
+ var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeAnswer = true });
+
+ // Act
+ KernelSearchResults result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 });
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Results);
+ var resultList = await result.Results.ToListAsync();
+ Assert.NotNull(resultList);
+ Assert.Equal(5, resultList.Count);
+ foreach (var stringResult in resultList)
+ {
+ Assert.NotEmpty(stringResult);
+ }
+ }
+
+ [Fact]
+ public async Task SearchWithImagesReturnsSuccessfullyAsync()
+ {
+ // Arrange
+ this._messageHandlerStub.AddJsonResponse(File.ReadAllText(WhatIsTheSKResponseJson));
+
+ // Create an ITextSearch instance using Tavily search
+ var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeImages = true });
+
+ // Act
+ KernelSearchResults result = await textSearch.SearchAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 });
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Results);
+ var resultList = await result.Results.ToListAsync();
+ Assert.NotNull(resultList);
+ Assert.Equal(9, resultList.Count);
+ foreach (var stringResult in resultList)
+ {
+ Assert.NotEmpty(stringResult);
+ }
+ }
+
+ [Fact]
+ public async Task GetTextSearchResultsWithAnswerReturnsSuccessfullyAsync()
+ {
+ // Arrange
+ this._messageHandlerStub.AddJsonResponse(File.ReadAllText(WhatIsTheSKResponseJson));
+
+ // Create an ITextSearch instance using Tavily search
+ var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeAnswer = true });
+
+ // Act
+ KernelSearchResults result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 });
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Results);
+ var resultList = await result.Results.ToListAsync();
+ Assert.NotNull(resultList);
+ Assert.Equal(4, resultList.Count);
+ foreach (var textSearchResult in resultList)
+ {
+ Assert.NotNull(textSearchResult.Name);
+ Assert.NotNull(textSearchResult.Value);
+ Assert.NotNull(textSearchResult.Link);
+ }
+ }
+
+ [Fact]
+ public async Task GetTextSearchResultsWithImagesReturnsSuccessfullyAsync()
+ {
+ // Arrange
+ this._messageHandlerStub.AddJsonResponse(File.ReadAllText(WhatIsTheSKResponseJson));
+
+ // Create an ITextSearch instance using Tavily search
+ var textSearch = new TavilyTextSearch(apiKey: "ApiKey", options: new() { HttpClient = this._httpClient, IncludeImages = true, IncludeImageDescriptions = true });
+
+ // Act
+ KernelSearchResults result = await textSearch.GetTextSearchResultsAsync("What is the Semantic Kernel?", new() { Top = 4, Skip = 0 });
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotNull(result.Results);
+ var resultList = await result.Results.ToListAsync();
+ Assert.NotNull(resultList);
+ Assert.Equal(9, resultList.Count);
+ foreach (var textSearchResult in resultList)
+ {
+ Assert.NotNull(textSearchResult.Name);
+ Assert.NotNull(textSearchResult.Value);
+ Assert.NotNull(textSearchResult.Link);
+ }
+ }
+
[Theory]
[InlineData("topic", "general", "{\"query\":\"What is the Semantic Kernel?\",\"topic\":\"general\",\"max_results\":4}")]
[InlineData("topic", "news", "{\"query\":\"What is the Semantic Kernel?\",\"topic\":\"news\",\"max_results\":4}")]
diff --git a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs
index 858199523bcd..4e01d0ffb88b 100644
--- a/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs
+++ b/dotnet/src/Plugins/Plugins.Web/Tavily/TavilyTextSearch.cs
@@ -164,6 +164,15 @@ private async IAsyncEnumerable