Skip to content

Commit 59b2c59

Browse files
Add AITool -> OpenAI.Responses.ResponseTool conversion utility (#6958)
* Add utility for ResponseTool * Adding UT for Utility * Update internals to utility * Update src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> * Update src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> * Update src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> * Update src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> * Update test to use the AITool extension directly * Improve UT * Add MCP missing UT * Update src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> --------- Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent dbf497f commit 59b2c59

File tree

3 files changed

+400
-89
lines changed

3 files changed

+400
-89
lines changed

src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ public static class MicrosoftExtensionsAIResponsesExtensions
2121
public static FunctionTool AsOpenAIResponseTool(this AIFunctionDeclaration function) =>
2222
OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(function));
2323

24+
/// <summary>Creates an OpenAI <see cref="ResponseTool"/> from an <see cref="AITool"/>.</summary>
25+
/// <param name="tool">The tool to convert.</param>
26+
/// <returns>An OpenAI <see cref="ResponseTool"/> representing <paramref name="tool"/> or <see langword="null"/> if there is no mapping.</returns>
27+
/// <exception cref="ArgumentNullException"><paramref name="tool"/> is <see langword="null"/>.</exception>
28+
/// <remarks>
29+
/// This method is only able to create <see cref="ResponseTool"/>s for <see cref="AITool"/> types
30+
/// it's aware of, namely all of those available from the Microsoft.Extensions.AI.Abstractions library.
31+
/// </remarks>
32+
public static ResponseTool? AsOpenAIResponseTool(this AITool tool) =>
33+
OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(tool));
34+
2435
/// <summary>
2536
/// Creates an OpenAI <see cref="ResponseTextFormat"/> from a <see cref="ChatResponseFormat"/>.
2637
/// </summary>

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs

Lines changed: 93 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,97 @@ void IDisposable.Dispose()
431431
// Nothing to dispose. Implementation required for the IChatClient interface.
432432
}
433433

434+
internal static ResponseTool? ToResponseTool(AITool tool, ChatOptions? options = null)
435+
{
436+
switch (tool)
437+
{
438+
case ResponseToolAITool rtat:
439+
return rtat.Tool;
440+
441+
case AIFunctionDeclaration aiFunction:
442+
return ToResponseTool(aiFunction, options);
443+
444+
case HostedWebSearchTool webSearchTool:
445+
WebSearchToolLocation? location = null;
446+
if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
447+
{
448+
location = objLocation as WebSearchToolLocation;
449+
}
450+
451+
WebSearchToolContextSize? size = null;
452+
if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
453+
objSize is WebSearchToolContextSize)
454+
{
455+
size = (WebSearchToolContextSize)objSize;
456+
}
457+
458+
return ResponseTool.CreateWebSearchTool(location, size);
459+
460+
case HostedFileSearchTool fileSearchTool:
461+
return ResponseTool.CreateFileSearchTool(
462+
fileSearchTool.Inputs?.OfType<HostedVectorStoreContent>().Select(c => c.VectorStoreId) ?? [],
463+
fileSearchTool.MaximumResultCount);
464+
465+
case HostedCodeInterpreterTool codeTool:
466+
return ResponseTool.CreateCodeInterpreterTool(
467+
new CodeInterpreterToolContainer(codeTool.Inputs?.OfType<HostedFileContent>().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
468+
CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
469+
new()));
470+
471+
case HostedMcpServerTool mcpTool:
472+
McpTool responsesMcpTool = Uri.TryCreate(mcpTool.ServerAddress, UriKind.Absolute, out Uri? url) ?
473+
ResponseTool.CreateMcpTool(
474+
mcpTool.ServerName,
475+
url,
476+
mcpTool.AuthorizationToken,
477+
mcpTool.ServerDescription) :
478+
ResponseTool.CreateMcpTool(
479+
mcpTool.ServerName,
480+
new McpToolConnectorId(mcpTool.ServerAddress),
481+
mcpTool.AuthorizationToken,
482+
mcpTool.ServerDescription);
483+
484+
if (mcpTool.AllowedTools is not null)
485+
{
486+
responsesMcpTool.AllowedTools = new();
487+
AddAllMcpFilters(mcpTool.AllowedTools, responsesMcpTool.AllowedTools);
488+
}
489+
490+
switch (mcpTool.ApprovalMode)
491+
{
492+
case HostedMcpServerToolAlwaysRequireApprovalMode:
493+
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval);
494+
break;
495+
496+
case HostedMcpServerToolNeverRequireApprovalMode:
497+
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval);
498+
break;
499+
500+
case HostedMcpServerToolRequireSpecificApprovalMode specificMode:
501+
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(new CustomMcpToolCallApprovalPolicy());
502+
503+
if (specificMode.AlwaysRequireApprovalToolNames is { Count: > 0 } alwaysRequireToolNames)
504+
{
505+
responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval = new();
506+
AddAllMcpFilters(alwaysRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval);
507+
}
508+
509+
if (specificMode.NeverRequireApprovalToolNames is { Count: > 0 } neverRequireToolNames)
510+
{
511+
responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval = new();
512+
AddAllMcpFilters(neverRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval);
513+
}
514+
515+
break;
516+
}
517+
518+
return responsesMcpTool;
519+
520+
default:
521+
return null;
522+
}
523+
}
524+
434525
internal static FunctionTool ToResponseTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
435526
{
436527
bool? strict =
@@ -492,96 +583,9 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
492583
{
493584
foreach (AITool tool in tools)
494585
{
495-
switch (tool)
586+
if (ToResponseTool(tool, options) is { } responseTool)
496587
{
497-
case ResponseToolAITool rtat:
498-
result.Tools.Add(rtat.Tool);
499-
break;
500-
501-
case AIFunctionDeclaration aiFunction:
502-
result.Tools.Add(ToResponseTool(aiFunction, options));
503-
break;
504-
505-
case HostedWebSearchTool webSearchTool:
506-
WebSearchToolLocation? location = null;
507-
if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
508-
{
509-
location = objLocation as WebSearchToolLocation;
510-
}
511-
512-
WebSearchToolContextSize? size = null;
513-
if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
514-
objSize is WebSearchToolContextSize)
515-
{
516-
size = (WebSearchToolContextSize)objSize;
517-
}
518-
519-
result.Tools.Add(ResponseTool.CreateWebSearchTool(location, size));
520-
break;
521-
522-
case HostedFileSearchTool fileSearchTool:
523-
result.Tools.Add(ResponseTool.CreateFileSearchTool(
524-
fileSearchTool.Inputs?.OfType<HostedVectorStoreContent>().Select(c => c.VectorStoreId) ?? [],
525-
fileSearchTool.MaximumResultCount));
526-
break;
527-
528-
case HostedCodeInterpreterTool codeTool:
529-
result.Tools.Add(
530-
ResponseTool.CreateCodeInterpreterTool(
531-
new CodeInterpreterToolContainer(codeTool.Inputs?.OfType<HostedFileContent>().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
532-
CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
533-
new())));
534-
break;
535-
536-
case HostedMcpServerTool mcpTool:
537-
McpTool responsesMcpTool = Uri.TryCreate(mcpTool.ServerAddress, UriKind.Absolute, out Uri? url) ?
538-
ResponseTool.CreateMcpTool(
539-
mcpTool.ServerName,
540-
url,
541-
mcpTool.AuthorizationToken,
542-
mcpTool.ServerDescription) :
543-
ResponseTool.CreateMcpTool(
544-
mcpTool.ServerName,
545-
new McpToolConnectorId(mcpTool.ServerAddress),
546-
mcpTool.AuthorizationToken,
547-
mcpTool.ServerDescription);
548-
549-
if (mcpTool.AllowedTools is not null)
550-
{
551-
responsesMcpTool.AllowedTools = new();
552-
AddAllMcpFilters(mcpTool.AllowedTools, responsesMcpTool.AllowedTools);
553-
}
554-
555-
switch (mcpTool.ApprovalMode)
556-
{
557-
case HostedMcpServerToolAlwaysRequireApprovalMode:
558-
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.AlwaysRequireApproval);
559-
break;
560-
561-
case HostedMcpServerToolNeverRequireApprovalMode:
562-
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval);
563-
break;
564-
565-
case HostedMcpServerToolRequireSpecificApprovalMode specificMode:
566-
responsesMcpTool.ToolCallApprovalPolicy = new McpToolCallApprovalPolicy(new CustomMcpToolCallApprovalPolicy());
567-
568-
if (specificMode.AlwaysRequireApprovalToolNames is { Count: > 0 } alwaysRequireToolNames)
569-
{
570-
responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval = new();
571-
AddAllMcpFilters(alwaysRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsAlwaysRequiringApproval);
572-
}
573-
574-
if (specificMode.NeverRequireApprovalToolNames is { Count: > 0 } neverRequireToolNames)
575-
{
576-
responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval = new();
577-
AddAllMcpFilters(neverRequireToolNames, responsesMcpTool.ToolCallApprovalPolicy.CustomPolicy.ToolsNeverRequiringApproval);
578-
}
579-
580-
break;
581-
}
582-
583-
result.Tools.Add(responsesMcpTool);
584-
break;
588+
result.Tools.Add(responseTool);
585589
}
586590
}
587591

0 commit comments

Comments
 (0)