From fa91fb1a8dcd8b1a54d7f44e7273439eb54efbb7 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Sat, 17 Jan 2026 18:58:33 -0300 Subject: [PATCH] Refactor WebSearchTool and move to OpenAI namespace - Moved ReasoningEffort, Verbosity, and WebSearchTool to Devlooped.Extensions.AI.OpenAI namespace for clarity. - Refactored WebSearchTool to support explicit properties for Country, Region, City, TimeZone, and AllowedDomains. - Integrated location and domain filtering logic directly into WebSearchTool. - Removed WebSearchToolExtensions as its functionality is now in the main class. - Updated tests to use the new WebSearchTool API and removed obsolete ContextSize usage. --- .../{ => OpenAI}/ReasoningEffort.cs | 2 +- src/Extensions/{ => OpenAI}/Verbosity.cs | 2 +- src/Extensions/OpenAI/WebSearchTool.cs | 104 ++++++++++++++++++ .../OpenAI/WebSearchToolExtensions.cs | 67 ----------- src/Extensions/WebSearchTool.cs | 45 -------- src/Tests/OpenAITests.cs | 3 +- 6 files changed, 107 insertions(+), 116 deletions(-) rename src/Extensions/{ => OpenAI}/ReasoningEffort.cs (96%) rename src/Extensions/{ => OpenAI}/Verbosity.cs (88%) create mode 100644 src/Extensions/OpenAI/WebSearchTool.cs delete mode 100644 src/Extensions/OpenAI/WebSearchToolExtensions.cs delete mode 100644 src/Extensions/WebSearchTool.cs diff --git a/src/Extensions/ReasoningEffort.cs b/src/Extensions/OpenAI/ReasoningEffort.cs similarity index 96% rename from src/Extensions/ReasoningEffort.cs rename to src/Extensions/OpenAI/ReasoningEffort.cs index 8b7438c..d15ee61 100644 --- a/src/Extensions/ReasoningEffort.cs +++ b/src/Extensions/OpenAI/ReasoningEffort.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace Devlooped.Extensions.AI; +namespace Devlooped.Extensions.AI.OpenAI; /// /// Effort a reasoning model should apply when generating a response. diff --git a/src/Extensions/Verbosity.cs b/src/Extensions/OpenAI/Verbosity.cs similarity index 88% rename from src/Extensions/Verbosity.cs rename to src/Extensions/OpenAI/Verbosity.cs index 275e619..9a2c09b 100644 --- a/src/Extensions/Verbosity.cs +++ b/src/Extensions/OpenAI/Verbosity.cs @@ -1,4 +1,4 @@ -namespace Devlooped.Extensions.AI; +namespace Devlooped.Extensions.AI.OpenAI; /// /// Verbosity determines how many output tokens are generated for models that support it, such as GPT-5. diff --git a/src/Extensions/OpenAI/WebSearchTool.cs b/src/Extensions/OpenAI/WebSearchTool.cs new file mode 100644 index 0000000..7585d50 --- /dev/null +++ b/src/Extensions/OpenAI/WebSearchTool.cs @@ -0,0 +1,104 @@ +using Microsoft.Extensions.AI; +using OpenAI.Responses; +using WebSearch = OpenAI.Responses.WebSearchTool; + +namespace Devlooped.Extensions.AI.OpenAI; + +/// +/// Basic web search tool that can limit the search to a specific country. +/// +public class WebSearchTool : HostedWebSearchTool +{ + readonly Dictionary additionalProperties = new(); + string? country; + string? region; + string? city; + string? timeZone; + string[]? allowedDomains; + + /// + /// Initializes a new instance of the class with the specified country. + /// + /// ISO alpha-2 country code. + public WebSearchTool(string? country = null) => Country = country; + + /// + /// Sets the user's country for web search results, using the ISO alpha-2 code. + /// + public string? Country + { + get => country; + set + { + country = value; + UpdateUserLocation(); + } + } + + /// + /// Optional free text additional information about the region to be used in the search. + /// + public string? Region + { + get => region; + set + { + region = value; + UpdateUserLocation(); + } + } + + /// + /// Optional free text additional information about the city to be used in the search. + /// + public string? City + { + get => city; + set + { + city = value; + UpdateUserLocation(); + } + } + + /// + /// Optional IANA timezone name to be used in the search. + /// + public string? TimeZone + { + get => timeZone; + set + { + timeZone = value; + UpdateUserLocation(); + } + } + + /// + /// Optional list of allowed domains to restrict the web search. + /// + public string[]? AllowedDomains + { + get => allowedDomains; + set + { + allowedDomains = value; + if (value is { Length: > 0 }) + additionalProperties[nameof(WebSearch.Filters)] = new WebSearchToolFilters { AllowedDomains = value }; + else + additionalProperties.Remove(nameof(WebSearch.Filters)); + } + } + + /// + public override IReadOnlyDictionary AdditionalProperties => additionalProperties; + + void UpdateUserLocation() + { + if (country != null || region != null || city != null || timeZone != null) + additionalProperties[nameof(WebSearch.UserLocation)] = + WebSearchToolLocation.CreateApproximateLocation(country, region, city, timeZone); + else + additionalProperties.Remove(nameof(WebSearch.UserLocation)); + } +} diff --git a/src/Extensions/OpenAI/WebSearchToolExtensions.cs b/src/Extensions/OpenAI/WebSearchToolExtensions.cs deleted file mode 100644 index 42431d5..0000000 --- a/src/Extensions/OpenAI/WebSearchToolExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using OpenAI.Responses; - -namespace Devlooped.Extensions.AI.OpenAI; - -public static class OpenAIWebSearchToolExtensions -{ - extension(WebSearchTool web) - { - /// - /// Optional free text additional information about the region to be used in the search. - /// - /// - public string? Region - { - get => web.Properties.TryGetValue("Region", out var region) ? (string?)region : null; - set - { - web.Properties["Region"] = value; - web.Location = WebSearchToolLocation.CreateApproximateLocation(web.Country, value, web.City, web.TimeZone); - } - } - - /// - /// Optional free text additional information about the city to be used in the search. - /// - /// - public string? City - { - get => web.Properties.TryGetValue("City", out var city) ? (string?)city : null; - set - { - web.Properties["City"] = value; - web.Location = WebSearchToolLocation.CreateApproximateLocation(web.Country, web.Region, value, web.TimeZone); - } - } - - /// - /// Optional IANA timezone name to be used in the search. - /// - /// - public string? TimeZone - { - get => web.Properties.TryGetValue("TimeZone", out var timeZone) ? (string?)timeZone : null; - set - { - web.Properties["TimeZone"] = value; - web.Location = WebSearchToolLocation.CreateApproximateLocation(web.Country, web.Region, web.City, value); - } - } - - /// - /// Controls how much context is retrieved from the web to help the tool formulate a response. - /// - public WebSearchToolContextSize? ContextSize - { - get => web.Properties.TryGetValue(nameof(WebSearchToolContextSize), out var size) && size is WebSearchToolContextSize contextSize - ? contextSize : null; - set - { - if (value.HasValue) - web.ContextSize = value.Value; - else - web.Properties.Remove(nameof(WebSearchToolContextSize)); - } - } - } -} diff --git a/src/Extensions/WebSearchTool.cs b/src/Extensions/WebSearchTool.cs deleted file mode 100644 index 404e233..0000000 --- a/src/Extensions/WebSearchTool.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.Extensions.AI; -using OpenAI.Responses; - -namespace Devlooped.Extensions.AI; - -/// -/// Basic web search tool that can limit the search to a specific country. -/// -public class WebSearchTool : HostedWebSearchTool -{ - Dictionary additionalProperties; - - /// - /// Initializes a new instance of the class with the specified country. - /// - /// ISO alpha-2 country code. - public WebSearchTool(string country) - { - Country = country; - additionalProperties = new Dictionary - { - { nameof(WebSearchToolLocation), WebSearchToolLocation.CreateApproximateLocation(country) } - }; - } - - /// - /// Sets the user's country for web search results, using the ISO alpha-2 code. - /// - public string Country { get; } - - internal WebSearchToolLocation Location - { - set => additionalProperties[nameof(WebSearchToolLocation)] = value; - } - - internal WebSearchToolContextSize ContextSize - { - set => additionalProperties[nameof(WebSearchToolContextSize)] = value; - } - - internal IDictionary Properties => additionalProperties; - - /// - public override IReadOnlyDictionary AdditionalProperties => additionalProperties; -} diff --git a/src/Tests/OpenAITests.cs b/src/Tests/OpenAITests.cs index a5ac732..514c550 100644 --- a/src/Tests/OpenAITests.cs +++ b/src/Tests/OpenAITests.cs @@ -276,11 +276,10 @@ public async Task WebSearchCountryHighContext() var options = new ChatOptions { - Tools = [new WebSearchTool("AR") + Tools = [new Devlooped.Extensions.AI.OpenAI.WebSearchTool("AR") { Region = "Bariloche", TimeZone = "America/Argentina/Buenos_Aires", - ContextSize = WebSearchToolContextSize.High }] };