diff --git a/OllamaStudy.UseExtensionsAI/OllamaStudy.UseExtensionsAI.csproj b/OllamaStudy.UseExtensionsAI/OllamaStudy.UseExtensionsAI.csproj index c61e9f8..f39eaab 100644 --- a/OllamaStudy.UseExtensionsAI/OllamaStudy.UseExtensionsAI.csproj +++ b/OllamaStudy.UseExtensionsAI/OllamaStudy.UseExtensionsAI.csproj @@ -16,8 +16,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + diff --git a/OllamaStudy.UseExtensionsAI/OpenAIAPITest.cs b/OllamaStudy.UseExtensionsAI/OpenAIAPITest.cs index 523bf6f..811ae5e 100644 --- a/OllamaStudy.UseExtensionsAI/OpenAIAPITest.cs +++ b/OllamaStudy.UseExtensionsAI/OpenAIAPITest.cs @@ -12,17 +12,32 @@ namespace OllamaStudy.UseExtensionsAI /// public class OpenAIAPITest { - private ITestOutputHelper _output; - private IOptionsMonitor _ollamaOptionsMonitor; - private OpenAIClient _defaultOpenAIClient; - private ChatClient _chatClient; - - public OpenAIAPITest(ITestOutputHelper outputHelper, OpenAIClient defaultOpenAIClient, IOptionsMonitor ollamaOptionsMonitor) + private readonly ITestOutputHelper _output; + private readonly IOptionsMonitor _ollamaOptionsMonitor; + private readonly IHttpClientFactory _httpClientFactory; + private readonly HttpClient _defaultHttpClient; + private readonly HttpClient _ollamaHttpClient; + private readonly HttpClient _uiUiAPIHttpClient; + private readonly HttpClient _bailianHttpClient; + private readonly HttpClient _zipuHttpClient; + + public OpenAIAPITest + ( + ITestOutputHelper outputHelper, + OpenAIClient defaultOpenAIClient, + IOptionsMonitor ollamaOptionsMonitor, + IHttpClientFactory httpClientFactory + ) { _output = outputHelper; - _defaultOpenAIClient = defaultOpenAIClient; _ollamaOptionsMonitor = ollamaOptionsMonitor; - _chatClient = _defaultOpenAIClient.GetChatClient(_ollamaOptionsMonitor.CurrentValue.Model); + _httpClientFactory = httpClientFactory; + + _defaultHttpClient = _httpClientFactory.CreateClient("OpenAIHttpClient"); + _ollamaHttpClient = _httpClientFactory.CreateClient("OllamaHttpClient"); + _uiUiAPIHttpClient = _httpClientFactory.CreateClient("UiUiAPIHttpClient"); + _bailianHttpClient = _httpClientFactory.CreateClient("BailianHttpClient"); + _zipuHttpClient = _httpClientFactory.CreateClient("ZiPuHttpClient"); } #region 各种业务Client @@ -32,74 +47,52 @@ namespace OllamaStudy.UseExtensionsAI [Fact] public void GetClients_Test() { - #pragma warning disable OPENAI001 - Assert.NotNull(_defaultOpenAIClient); - - //音频客户端 - var audioClient = _defaultOpenAIClient.GetAudioClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(audioClient); - - //聊天客户端 - var chatClient = _defaultOpenAIClient.GetChatClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(chatClient); - - //嵌入客户端 - var embeddingClient = _defaultOpenAIClient.GetEmbeddingClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(embeddingClient); - - //图像客户端 - var imageClient = _defaultOpenAIClient.GetImageClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(imageClient); - - //微调客户端 - var moderationClient = _defaultOpenAIClient.GetModerationClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(moderationClient); - - //文件客户端 - var openAIFileClient = _defaultOpenAIClient.GetOpenAIFileClient(); - Assert.NotNull(openAIFileClient); + Assert.NotNull(_defaultHttpClient); + Assert.NotNull(_ollamaHttpClient); + Assert.NotNull(_uiUiAPIHttpClient); + Assert.NotNull(_bailianHttpClient); + Assert.NotNull(_zipuHttpClient); + } + #endregion - //模型客户端 - var modelClient = _defaultOpenAIClient.GetOpenAIModelClient(); - Assert.NotNull(modelClient); + #region 音频 + [Fact] + public async Task Audio_Test() + { + var requetData = new + { + //语音模型 + model = "gpt-4o-mini-tts", - //助手客户端(仅评估) - var assistantClient = _defaultOpenAIClient.GetAssistantClient(); - Assert.NotNull(assistantClient); + //要生成音频的文本。最大长度为4096个字符。 + input = "你好,上海今天的天气非常好,很适合户外游玩!", - //批量客户端(仅评估) - var batchClient = _defaultOpenAIClient.GetBatchClient(); - Assert.NotNull(batchClient); + //生成音频时使用的语音。支持的语音有:alloy、echo、fable、onyx、nova 和 shimmer。 + voice = "alloy", - //评估客户端(仅评估) - var evaluationClient = _defaultOpenAIClient.GetEvaluationClient(); - Assert.NotNull(evaluationClient); + //默认为 mp3 音频的格式。支持的格式有:mp3、opus、aac 和 flac。 + response_format = "mp3", - //微调客户端(仅评估) - var FineTuningClient = _defaultOpenAIClient.GetFineTuningClient(); - Assert.NotNull(FineTuningClient); + //默认为 1 生成的音频速度。选择0.25到4.0之间的值。1.0是默认值。 + speed = 1.0f, + }; - //响应客户端(仅评估) - var openAIResponseClient = _defaultOpenAIClient.GetOpenAIResponseClient(_ollamaOptionsMonitor.CurrentValue.Model); - Assert.NotNull(openAIResponseClient); + using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://sg.uiuiapi.com/v1/audio/speech") + { + Content = JsonContent.Create(requetData) + }; - //实时客户端(仅评估) -#pragma warning disable OPENAI002 - var realtimeClient = _defaultOpenAIClient.GetRealtimeClient(); - Assert.NotNull(realtimeClient); -#pragma warning restore OPENAI002 + var responseMessage = await _uiUiAPIHttpClient.SendAsync(requestMessage); + responseMessage.EnsureSuccessStatusCode(); - //向量存储客户端(仅评估) - var vectorStoreClient = _defaultOpenAIClient.GetVectorStoreClient(); - Assert.NotNull(vectorStoreClient); + //处理响应 + var responseObject = await responseMessage.Content.ReadAsByteArrayAsync(); -#pragma warning restore OPENAI001 + using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.mp3"); + stream.Write(responseObject); } #endregion - #region 音频 - #endregion - #region 聊天 #endregion @@ -126,11 +119,7 @@ namespace OllamaStudy.UseExtensionsAI [Fact] public void List_Models_Test() { - var modelClient = _defaultOpenAIClient.GetOpenAIModelClient(); - - OpenAI.Models.OpenAIModelCollection openAIModelCollection = modelClient.GetModels().Value; - _output.WriteLine($"Ollama服务中,共有{openAIModelCollection.Count()}个模型,包括[{string.Join(",", openAIModelCollection)}]"); } #endregion diff --git a/OllamaStudy.UseExtensionsAI/OpenAISdkTest.cs b/OllamaStudy.UseExtensionsAI/OpenAISdkTest.cs index cbadcd2..a0f32bc 100644 --- a/OllamaStudy.UseExtensionsAI/OpenAISdkTest.cs +++ b/OllamaStudy.UseExtensionsAI/OpenAISdkTest.cs @@ -573,7 +573,7 @@ public class OpenAISdkTest They can serve as accents, adding contrast and texture. """; - ImageGenerationOptions options = new() + OpenAI.Images.ImageGenerationOptions options = new() { Quality = GeneratedImageQuality.High, Size = GeneratedImageSize.W1792xH1024, diff --git a/OllamaStudy.UseExtensionsAI/Startup.cs b/OllamaStudy.UseExtensionsAI/Startup.cs index fe5bc70..191b5ae 100644 --- a/OllamaStudy.UseExtensionsAI/Startup.cs +++ b/OllamaStudy.UseExtensionsAI/Startup.cs @@ -21,7 +21,7 @@ namespace OllamaStudy.UseExtensionsAI #endregion #region Startup 风格 - + /// /// (可选) 创建IHostBuilder /// @@ -31,9 +31,9 @@ namespace OllamaStudy.UseExtensionsAI return Host.CreateDefaultBuilder(); } - public void ConfigureHost(IHostBuilder hostBuilder) + public void ConfigureHost(IHostBuilder hostBuilder) { - hostBuilder.ConfigureAppConfiguration((hostBuilder,configBuilder) => + hostBuilder.ConfigureAppConfiguration((hostBuilder, configBuilder) => { configBuilder .SetBasePath(Directory.GetCurrentDirectory()) @@ -63,12 +63,12 @@ namespace OllamaStudy.UseExtensionsAI .Configure(context.Configuration.GetRequiredSection("OllamaServer")); services - .AddScoped(provider => + .AddScoped(provider => { var options = provider.GetRequiredService>().CurrentValue; - return new OllamaApiClient(options.OllamaServerUrl,options.Model); - + return new OllamaApiClient(options.OllamaServerUrl, options.Model); + }) .AddScoped(provider => { @@ -76,10 +76,10 @@ namespace OllamaStudy.UseExtensionsAI var openAIClientOptions = new OpenAIClientOptions() { - Endpoint = new Uri(new Uri(options.OllamaServerUrl),"v1") + Endpoint = new Uri(new Uri(options.OllamaServerUrl), "v1") }; - return new OpenAIClient(new ApiKeyCredential("nokey"),openAIClientOptions); + return new OpenAIClient(new ApiKeyCredential("nokey"), openAIClientOptions); }) .AddScoped(provider => { @@ -87,14 +87,14 @@ namespace OllamaStudy.UseExtensionsAI var openAIClientOptions = new OpenAIClientOptions() { - Endpoint = new Uri(new Uri(options.OllamaServerUrl),"v1") + Endpoint = new Uri(new Uri(options.OllamaServerUrl), "v1") }; - return new ChatClient(options.Model,new ApiKeyCredential("nokey"),openAIClientOptions); + return new ChatClient(options.Model, new ApiKeyCredential("nokey"), openAIClientOptions); }) //OpenAI 客户端是线程安全的,可安全的注册为单例 - .AddKeyedSingleton("OpenAIChatClient",(provider,obj) => + .AddKeyedSingleton("OpenAIChatClient", (provider, obj) => { var options = provider.GetRequiredService>().CurrentValue; @@ -123,7 +123,7 @@ namespace OllamaStudy.UseExtensionsAI Endpoint = new Uri("https://dashscope.aliyuncs.com/compatible-mode/v1") }; - return new OpenAIClient(new ApiKeyCredential(""), openAIClientOptions); + return new OpenAIClient(new ApiKeyCredential("sk-0122f39e383546b9a0999f70b9ef99e3"), openAIClientOptions); }) //智谱 OpenAI兼容API .AddKeyedSingleton("ZipuAPIClient", (provider, obj) => @@ -135,6 +135,72 @@ namespace OllamaStudy.UseExtensionsAI return new OpenAIClient(new ApiKeyCredential("397a799102a6453282da8abb2a1b2581.8fTMHZGRkPHJya4R"), openAIClientOptions); }); + + //HttpClient 注册 + + //Ollama HttpClient + services + .AddHttpClient("OllamaHttpClient", (provider, client) => + { + var options = provider.GetRequiredService>().CurrentValue; + + client.BaseAddress = new Uri(options.OllamaServerUrl); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer nokey"); + client.Timeout = TimeSpan.FromSeconds(60); + }); + + //OpenAI HttpClient + services + .AddHttpClient("OpenAIHttpClient", (provider, client) => + { + var options = provider.GetRequiredService>().CurrentValue; + + client.BaseAddress = new Uri(options.OllamaServerUrl); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer nokey"); + client.Timeout = TimeSpan.FromSeconds(60); + }); + + //UiUiAPI HttpClient + services + .AddHttpClient("UiUiAPIHttpClient", (provider, client) => + { + client.BaseAddress = new Uri("https://sg.uiuiapi.com/v1"); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer sk-4azuOUkbzNGP22pQkND8ad1vZl7ladwBQyqGKlWWZyxYgX1L"); + //client.DefaultRequestHeaders.Add("Content-Type", "application/json"); + client.Timeout = TimeSpan.FromSeconds(60); + }) + .UseSocketsHttpHandler((socketHttpHandeler, serviceProvider) => + { + //配置请求代理:请求走 Fiddler 代理,便于调试 + socketHttpHandeler.Proxy = new System.Net.WebProxy("http://127.0.0.1:8888"); + }); + + //阿里百炼 HttpClient + services + .AddHttpClient("BailianHttpClient", (provider, client) => + { + //var options = provider.GetRequiredService>().CurrentValue; + + client.BaseAddress = new Uri("https://dashscope.aliyuncs.com/compatible-mode/v1"); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer sk-0122f39e383546b9a0999f70b9ef99e3"); + client.Timeout = TimeSpan.FromSeconds(60); + }); + + //智谱 HttpClient + services + .AddHttpClient("ZiPuHttpClient", (provider, client) => + { + //var options = provider.GetRequiredService>().CurrentValue; + + client.BaseAddress = new Uri("https://open.bigmodel.cn/api/paas/v4/"); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer 397a799102a6453282da8abb2a1b2581.8fTMHZGRkPHJya4R"); + client.Timeout = TimeSpan.FromSeconds(60); + }) + .UseSocketsHttpHandler((socketHttpHandeler, serviceProvider) => + { + //配置请求代理:请求走 Fiddler 代理,便于调试 + socketHttpHandeler.Proxy = new System.Net.WebProxy("http://127.0.0.1:8888"); + }); } #endregion } diff --git a/OllamaStudy.UseOllamaSharp/OllamaApiTest.cs b/OllamaStudy.UseOllamaSharp/OllamaApiTest.cs index cbcde3f..b9d4313 100644 --- a/OllamaStudy.UseOllamaSharp/OllamaApiTest.cs +++ b/OllamaStudy.UseOllamaSharp/OllamaApiTest.cs @@ -889,45 +889,45 @@ namespace OllamaStudy.UseOllamaSharp } /// - /// 结构化输出对话请求(没有直接实现) + /// 结构化输出对话请求 /// /// [Fact] - public void ChatRequest_StructuredOutputs_Test() + public async Task ChatRequest_StructuredOutputs_Test() { - //var chatRequest = new ChatRequest() - //{ - // Messages = new[] - // { - // new Message() - // { - // Role = ChatRole.User, - // Content = "Ollama is 22 years old and busy saving the world. Return a JSON object with the age and availability.", - // } - // }, - - // Think = false, - - // //(可选)禁用流式处理响应 - // Stream = false, - - // Format = new - // { - // type = "object", - // properties = new { age = new { type = "integer" }, available = new { type = "boolean" } }, - // required = new string[] { "age", "available" } - // }, - //}; - //var chatDoneResponse = await _ollamaApiClient.ChatAsync(chatRequest).StreamToEndAsync(); - //_output.WriteLine(chatDoneResponse?.Message.Content); - - //var jsonObject = new { age = 0, available = false }; - - //var responseObject = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(chatDoneResponse?.Message.Content ?? "{}", jsonObject); - - //Assert.NotNull(responseObject); - //Assert.Equal(22, responseObject.age); - //Assert.True(responseObject.available); + var chatRequest = new ChatRequest() + { + Messages = new Message[] + { + new Message() + { + Role = ChatRole.User, + Content = "Ollama is 22 years old and busy saving the world. Return a JSON object with the age and availability.", + }, + }, + + Think = false, + + //(可选)禁用流式处理响应 + Stream = false, + + Format = new + { + type = "object", + properties = new { age = new { type = "integer" }, available = new { type = "boolean" } }, + required = new string[] { "age", "available" } + }, + }; + var chatDoneResponse = await _ollamaApiClient.ChatAsync(chatRequest).StreamToEndAsync(); + _output.WriteLine(chatDoneResponse?.Message.Content); + + var jsonObject = new { age = 0, available = false }; + + var responseObject = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(chatDoneResponse?.Message.Content ?? "{}", jsonObject); + + Assert.NotNull(responseObject); + Assert.Equal(22, responseObject.age); + Assert.True(responseObject.available); } ///