Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
wip
  • Loading branch information
christothes committed Apr 28, 2025
commit a0ec8a3f1ee976e10112d24bd33d23e142df56d6
1 change: 1 addition & 0 deletions src/OpenAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="System.ClientModel" Version="1.2.1" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="6.0.1" />
<PackageReference Update="Microsoft.Bcl.Numerics" Version="8.0.0" />
</ItemGroup>
</Project>
110 changes: 110 additions & 0 deletions src/Utility/ChatTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using OpenAI.Chat;
using OpenAI.Embeddings;

namespace OpenAI;

/// <summary>
/// The service client for OpenAI Chat Completions endpoint tools.
/// </summary>
public class ChatTools : ToolsBase<ChatTool>
{
public ChatTools(EmbeddingClient client = null) : base(client) { }

public ChatTools(Type tool, params Type[] additionalTools) : this((EmbeddingClient)null)
{
Add(tool);
if (additionalTools != null)
foreach (var t in additionalTools)
Add(t);
}

internal override ChatTool MethodInfoToTool(MethodInfo methodInfo) =>
ChatTool.CreateFunctionTool(methodInfo.Name, GetMethodDescription(methodInfo), BuildParametersJson(methodInfo.GetParameters()));

protected override async Task Add(BinaryData toolDefinitions, McpClient client)
{
using var document = JsonDocument.Parse(toolDefinitions);
if (!document.RootElement.TryGetProperty("tools", out JsonElement toolsElement))
throw new JsonException("The JSON document must contain a 'tools' array.");

var serverKey = client.ServerEndpoint.Host + client.ServerEndpoint.Port.ToString();
List<ChatTool> toolsToVectorize = new();

foreach (var tool in toolsElement.EnumerateArray())
{
var name = $"{serverKey}{_mcpToolSeparator}{tool.GetProperty("name").GetString()!}";
var description = tool.GetProperty("description").GetString()!;
#pragma warning disable IL2026, IL3050
var inputSchema = JsonSerializer.Serialize(
JsonSerializer.Deserialize<JsonElement>(tool.GetProperty("inputSchema").GetRawText()),
new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
#pragma warning restore IL2026, IL3050

var chatTool = ChatTool.CreateFunctionTool(name, description, BinaryData.FromString(inputSchema));
_definitions.Add(chatTool);
toolsToVectorize.Add(chatTool);
_mcpMethods[name] = client.CallToolAsync;
}

await AddToolsToVectorBaseAsync(toolsToVectorize).ConfigureAwait(false);
}

protected override string GetDescription(ChatTool tool) => tool.FunctionDescription;

protected override BinaryData SerializeTool(ChatTool tool)
{
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });

writer.WriteStartObject();
writer.WriteString("name", tool.FunctionName);
writer.WriteString("description", tool.FunctionDescription);
writer.WritePropertyName("inputSchema");
using (var doc = JsonDocument.Parse(tool.FunctionParameters))
doc.RootElement.WriteTo(writer);
writer.WriteEndObject();
writer.Flush();

stream.Position = 0;
return BinaryData.FromStream(stream);
}

protected override ChatTool ParseToolDefinition(BinaryData data)
{
using var document = JsonDocument.Parse(data);
var root = document.RootElement;

return ChatTool.CreateFunctionTool(
root.GetProperty("name").GetString()!,
root.GetProperty("description").GetString()!,
BinaryData.FromString(root.GetProperty("inputSchema").GetRawText()));
}

public ChatCompletionOptions ToOptions()
{
var options = new ChatCompletionOptions();
foreach (var tool in _definitions)
options.Tools.Add(tool);
return options;
}

public ChatCompletionOptions ToOptions(string prompt, ToolFindOptions options = null)
{
if (!CanFilterTools)
return ToOptions();

var completionOptions = new ChatCompletionOptions();
foreach (var tool in RelatedTo(prompt, options?.MaxEntries ?? 5))
completionOptions.Tools.Add(tool);
return completionOptions;
}

public static implicit operator ChatCompletionOptions(ChatTools tools) => tools.ToOptions();
}

41 changes: 41 additions & 0 deletions src/Utility/MCP/McpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.ClientModel.Primitives;
using System.Threading.Tasks;

namespace OpenAI;

public class McpClient
{
private readonly McpSession _session;
private readonly ClientPipeline _pipeline = ClientPipeline.Create();

public virtual Uri ServerEndpoint { get; }

public McpClient(Uri endpoint)
{
_session = new McpSession(endpoint, _pipeline);
ServerEndpoint = endpoint;
}

public virtual async Task StartAsync()
{
await _session.EnsureInitializedAsync().ConfigureAwait(false);
}

public virtual async Task<BinaryData> ListToolsAsync()
{
if (_session == null)
throw new InvalidOperationException("Session is not initialized. Call StartAsync() first.");

return await _session.SendMethod("tools/list").ConfigureAwait(false);
}

public virtual async Task<BinaryData> CallToolAsync(string toolName, BinaryData parameters)
{
if (_session == null)
throw new InvalidOperationException("Session is not initialized. Call StartAsync() first.");

Console.WriteLine($"Calling tool {toolName}...");
return await _session.CallTool(toolName, parameters).ConfigureAwait(false);
}
}
Loading