|
|
|
@ -1,4 +1,10 @@
|
|
|
|
|
namespace OllamaStudy.UseExtensionsAI;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
using OpenAI;
|
|
|
|
|
using OpenAI.Responses;
|
|
|
|
|
|
|
|
|
|
namespace OllamaStudy.UseExtensionsAI;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Ollama兼容OpenAI接口,可以直接使用OpenAI的SDK调用
|
|
|
|
@ -8,12 +14,22 @@ public class OpenAISdkTest
|
|
|
|
|
private ITestOutputHelper _output;
|
|
|
|
|
private IOptionsMonitor<OllamaServerOption> _ollamaOptionsMonitor;
|
|
|
|
|
private OpenAIClient _defaultOpenAIClient;
|
|
|
|
|
private ChatClient _singtonChatClient;
|
|
|
|
|
|
|
|
|
|
public OpenAISdkTest
|
|
|
|
|
(
|
|
|
|
|
ITestOutputHelper outputHelper,
|
|
|
|
|
OpenAIClient defaultOpenAIClient,
|
|
|
|
|
IOptionsMonitor<OllamaServerOption> ollamaOptionsMonitor,
|
|
|
|
|
|
|
|
|
|
public OpenAISdkTest(ITestOutputHelper outputHelper, OpenAIClient defaultOpenAIClient, IOptionsMonitor<OllamaServerOption> ollamaOptionsMonitor)
|
|
|
|
|
//使用了FromKeyedServices特性,所以需要使用IKeyedServiceCollection注册服务
|
|
|
|
|
[FromKeyedServices("OpenAIChatClient")]ChatClient singtonChatClient
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
_output = outputHelper;
|
|
|
|
|
_defaultOpenAIClient = defaultOpenAIClient;
|
|
|
|
|
_ollamaOptionsMonitor = ollamaOptionsMonitor;
|
|
|
|
|
_singtonChatClient = singtonChatClient;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region 使用客户端库
|
|
|
|
@ -87,31 +103,435 @@ public class OpenAISdkTest
|
|
|
|
|
#pragma warning restore OPENAI001
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 自定义URL和API密钥
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Custom_OpenAIClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var option = new OpenAIClientOptions()
|
|
|
|
|
{
|
|
|
|
|
OrganizationId = "TianyiJituan",
|
|
|
|
|
ProjectId = "StudyProject",
|
|
|
|
|
Endpoint = new Uri("http://localhost:11434/v1")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//本地Ollama服务,不需要API密钥(随便填写)
|
|
|
|
|
var openAIClient = new OpenAIClient(new ApiKeyCredential("nokey"), option);
|
|
|
|
|
var chatClient = openAIClient.GetChatClient(_ollamaOptionsMonitor.CurrentValue.Model);
|
|
|
|
|
|
|
|
|
|
Assert.NotNull(openAIClient);
|
|
|
|
|
Assert.NotNull(chatClient);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 自定义URL和API密钥
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Custom_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var option = new OpenAIClientOptions()
|
|
|
|
|
{
|
|
|
|
|
OrganizationId = "TianyiJituan",
|
|
|
|
|
ProjectId = "StudyProject",
|
|
|
|
|
UserAgentApplicationId = "StudyAgentApp",
|
|
|
|
|
Endpoint = new Uri("http://localhost:11434/v1"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var chatClient = new ChatClient(_ollamaOptionsMonitor.CurrentValue.Model,new ApiKeyCredential("nokey"),option);
|
|
|
|
|
Assert.NotNull(chatClient);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 使用异步API
|
|
|
|
|
/// 每个客户端方法在同一客户端类中都有一个异步变体
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task UseAsyncAPI_Test()
|
|
|
|
|
{
|
|
|
|
|
ChatClient chatClient = _defaultOpenAIClient.GetChatClient(_ollamaOptionsMonitor.CurrentValue.Model);
|
|
|
|
|
|
|
|
|
|
ClientResult<ChatCompletion> result = await chatClient.CompleteChatAsync("你好,请问河南的省会是什么?");
|
|
|
|
|
var responseText = result.Value.Content.First().Text;
|
|
|
|
|
_output.WriteLine(responseText);
|
|
|
|
|
|
|
|
|
|
Assert.NotNull(result);
|
|
|
|
|
Assert.Contains("郑州",responseText);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何使用依赖注入
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// OpenAI 客户端是线程安全的。可以在DI中安全地注册为单例.
|
|
|
|
|
/// 这最大限度地提高了资源效率和 HTTP 连接重用。
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Singleton_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var result = _singtonChatClient.CompleteChat("你好");
|
|
|
|
|
|
|
|
|
|
var responseText = result.Value.Content.First().Text;
|
|
|
|
|
_output.WriteLine(responseText);
|
|
|
|
|
Assert.NotNull(result);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将聊天完成与流式处理一起使用
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 使用同步流式处理API,可以立即收到响应,而无需等待模型完成。
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Streamimg_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
CollectionResult<StreamingChatCompletionUpdate> result = _singtonChatClient.CompleteChatStreaming("你好");
|
|
|
|
|
|
|
|
|
|
var stringBuilder = new StringBuilder(500);
|
|
|
|
|
|
|
|
|
|
foreach (StreamingChatCompletionUpdate completionUpdate in result)
|
|
|
|
|
{
|
|
|
|
|
if (completionUpdate.ContentUpdate.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append(completionUpdate.ContentUpdate[0].Text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_output.WriteLine(stringBuilder.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 使用异步流式处理API
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task Singleton_Async_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var result = _singtonChatClient.CompleteChatStreamingAsync("你好");
|
|
|
|
|
|
|
|
|
|
var stringBuilder = new StringBuilder(500);
|
|
|
|
|
|
|
|
|
|
await foreach (StreamingChatCompletionUpdate completionUpdate in result)
|
|
|
|
|
{
|
|
|
|
|
if (completionUpdate.ContentUpdate.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
stringBuilder.Append(completionUpdate.ContentUpdate[0].Text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_output.WriteLine(stringBuilder.ToString());
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将聊天完成与工具和函数调用一起使用
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 调用工具和函数
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Use_FunctionCalling_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
ChatTool getCurrentLocationTool = ChatTool.CreateFunctionTool
|
|
|
|
|
(
|
|
|
|
|
functionName: nameof(GetCurrentLocation),
|
|
|
|
|
functionDescription: "Get the user's current location"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ChatTool getCurrentWeatherTool = ChatTool.CreateFunctionTool
|
|
|
|
|
(
|
|
|
|
|
functionName: nameof(GetCurrentWeather),
|
|
|
|
|
functionDescription: "Get the current weather in a given location",
|
|
|
|
|
functionParameters: BinaryData.FromBytes("""
|
|
|
|
|
{
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"location": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"description": "The city and state, e.g. Boston, MA"
|
|
|
|
|
},
|
|
|
|
|
"unit": {
|
|
|
|
|
"type": "string",
|
|
|
|
|
"enum": [ "celsius", "fahrenheit" ],
|
|
|
|
|
"description": "The temperature unit to use. Infer this from the specified location."
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": [ "location" ]
|
|
|
|
|
}
|
|
|
|
|
"""u8.ToArray())
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
List<OpenAI.Chat.ChatMessage> messages = [new UserChatMessage("What's the weather like beijing today?"),];
|
|
|
|
|
|
|
|
|
|
ChatCompletionOptions options = new()
|
|
|
|
|
{
|
|
|
|
|
Tools = { getCurrentLocationTool, getCurrentWeatherTool },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool requiresAction = false;
|
|
|
|
|
|
|
|
|
|
do //实质上是手动调用函数
|
|
|
|
|
{
|
|
|
|
|
requiresAction = false;
|
|
|
|
|
ChatCompletion completion = _singtonChatClient.CompleteChat(messages, options);
|
|
|
|
|
|
|
|
|
|
switch (completion.FinishReason)
|
|
|
|
|
{
|
|
|
|
|
case OpenAI.Chat.ChatFinishReason.Stop:
|
|
|
|
|
{
|
|
|
|
|
// Add the assistant message to the conversation history.
|
|
|
|
|
messages.Add(new AssistantChatMessage(completion));
|
|
|
|
|
|
|
|
|
|
//输出
|
|
|
|
|
foreach (var message in messages)
|
|
|
|
|
{
|
|
|
|
|
_output.WriteLine(message.Content.First().Text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OpenAI.Chat.ChatFinishReason.ToolCalls:
|
|
|
|
|
{
|
|
|
|
|
// First, add the assistant message with tool calls to the conversation history.
|
|
|
|
|
messages.Add(new AssistantChatMessage(completion));
|
|
|
|
|
|
|
|
|
|
// Then, add a new tool message for each tool call that is resolved.
|
|
|
|
|
foreach (ChatToolCall toolCall in completion.ToolCalls)
|
|
|
|
|
{
|
|
|
|
|
switch (toolCall.FunctionName)
|
|
|
|
|
{
|
|
|
|
|
case nameof(GetCurrentLocation):
|
|
|
|
|
{
|
|
|
|
|
string toolResult = GetCurrentLocation();
|
|
|
|
|
messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case nameof(GetCurrentWeather):
|
|
|
|
|
{
|
|
|
|
|
// The arguments that the model wants to use to call the function are specified as a
|
|
|
|
|
// stringified JSON object based on the schema defined in the tool definition. Note that
|
|
|
|
|
// the model may hallucinate arguments too. Consequently, it is important to do the
|
|
|
|
|
// appropriate parsing and validation before calling the function.
|
|
|
|
|
using JsonDocument argumentsJson = JsonDocument.Parse(toolCall.FunctionArguments);
|
|
|
|
|
bool hasLocation = argumentsJson.RootElement.TryGetProperty("location", out JsonElement location);
|
|
|
|
|
bool hasUnit = argumentsJson.RootElement.TryGetProperty("unit", out JsonElement unit);
|
|
|
|
|
|
|
|
|
|
if (!hasLocation)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(location), "The location argument is required.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string toolResult = hasUnit
|
|
|
|
|
? GetCurrentWeather(location.GetString() ?? "", unit.GetString() ?? "")
|
|
|
|
|
: GetCurrentWeather(location.GetString() ?? "");
|
|
|
|
|
messages.Add(new ToolChatMessage(toolCall.Id, toolResult));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
// Handle other unexpected calls.
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requiresAction = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case OpenAI.Chat.ChatFinishReason.Length:
|
|
|
|
|
throw new NotImplementedException("Incomplete model output due to MaxTokens parameter or token limit exceeded.");
|
|
|
|
|
|
|
|
|
|
case OpenAI.Chat.ChatFinishReason.ContentFilter:
|
|
|
|
|
throw new NotImplementedException("Omitted content due to a content filter flag.");
|
|
|
|
|
|
|
|
|
|
case OpenAI.Chat.ChatFinishReason.FunctionCall:
|
|
|
|
|
throw new NotImplementedException("Deprecated in favor of tool calls.");
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new NotImplementedException(completion.FinishReason.ToString());
|
|
|
|
|
}
|
|
|
|
|
} while (requiresAction);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将聊天完成与结构化输出一起使用
|
|
|
|
|
[Fact]
|
|
|
|
|
public void StructuredOutputs_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
List<OpenAI.Chat.ChatMessage> messages =[new UserChatMessage("How can I solve 8x + 7 = -23?"),];
|
|
|
|
|
|
|
|
|
|
ChatCompletionOptions options = new()
|
|
|
|
|
{
|
|
|
|
|
ResponseFormat = OpenAI.Chat.ChatResponseFormat.CreateJsonSchemaFormat(
|
|
|
|
|
jsonSchemaFormatName: "math_reasoning",
|
|
|
|
|
jsonSchema: BinaryData.FromBytes("""
|
|
|
|
|
{
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"steps": {
|
|
|
|
|
"type": "array",
|
|
|
|
|
"items": {
|
|
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"explanation": { "type": "string" },
|
|
|
|
|
"output": { "type": "string" }
|
|
|
|
|
},
|
|
|
|
|
"required": ["explanation", "output"],
|
|
|
|
|
"additionalProperties": false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"final_answer": { "type": "string" }
|
|
|
|
|
},
|
|
|
|
|
"required": ["steps", "final_answer"],
|
|
|
|
|
"additionalProperties": false
|
|
|
|
|
}
|
|
|
|
|
"""u8.ToArray()),
|
|
|
|
|
jsonSchemaIsStrict: true)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ChatCompletion completion = _singtonChatClient.CompleteChat(messages, options);
|
|
|
|
|
|
|
|
|
|
using JsonDocument structuredJson = JsonDocument.Parse(completion.Content[0].Text);
|
|
|
|
|
|
|
|
|
|
_output.WriteLine($"Final answer: {structuredJson.RootElement.GetProperty("final_answer")}");
|
|
|
|
|
_output.WriteLine("Reasoning steps:");
|
|
|
|
|
|
|
|
|
|
foreach (JsonElement stepElement in structuredJson.RootElement.GetProperty("steps").EnumerateArray())
|
|
|
|
|
{
|
|
|
|
|
_output.WriteLine($" - Explanation: {stepElement.GetProperty("explanation")}");
|
|
|
|
|
_output.WriteLine($" Output: {stepElement.GetProperty("output")}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将聊天完成与音频一起使用
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 生成语音
|
|
|
|
|
/// </summary>
|
|
|
|
|
//[Fact]
|
|
|
|
|
[Fact(Skip ="因本地Ollama测试环境,不支持OpenAI音频接口,忽略测试")]
|
|
|
|
|
//[Fact]
|
|
|
|
|
public void GenerateSpeech_AudioClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var aiClientOption = new OpenAIClientOptions()
|
|
|
|
|
{
|
|
|
|
|
Endpoint = new Uri("https://sg.uiuiapi.com/v1")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AudioClient client = new("tts-1-1106", new ApiKeyCredential("sk-4azuOUkbzNGP22pQkND8ad1vZl7ladwBQyqGKlWWZyxYgX1L"), aiClientOption);
|
|
|
|
|
|
|
|
|
|
string input = """
|
|
|
|
|
对于那些照顾室内植物的人来说,过度浇水是一个常见的问题。
|
|
|
|
|
为了防止这种情况,让土壤在两次浇水之间变干至关重要。
|
|
|
|
|
与其按照固定的时间表浇水,不如考虑使用水分计来准确测量土壤的湿度。
|
|
|
|
|
如果土壤保持水分,明智的做法是再推迟几天浇水。
|
|
|
|
|
如有疑问,“节约用水,保持少即是多”的方法通常更安全。
|
|
|
|
|
""";
|
|
|
|
|
|
|
|
|
|
BinaryData speech = client.GenerateSpeech(input, GeneratedSpeechVoice.Alloy);
|
|
|
|
|
|
|
|
|
|
using FileStream stream = File.OpenWrite($"{Guid.NewGuid()}.mp3");
|
|
|
|
|
speech.ToStream().CopyTo(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 语音转文本
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Fact(Skip ="因本地Ollama测试环境,不支持OpenAI音频接口,忽略测试")]
|
|
|
|
|
//[Fact]
|
|
|
|
|
public void AudioToText_AudioClient_Test()
|
|
|
|
|
{
|
|
|
|
|
var aiClientOption = new OpenAIClientOptions()
|
|
|
|
|
{
|
|
|
|
|
Endpoint = new Uri("https://sg.uiuiapi.com/v1")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AudioClient client = new("whisper-1", new ApiKeyCredential("sk-4azuOUkbzNGP22pQkND8ad1vZl7ladwBQyqGKlWWZyxYgX1L"), aiClientOption);
|
|
|
|
|
|
|
|
|
|
string audioFilePath = Path.Combine(Environment.CurrentDirectory, "Assets", "yuxia.mp3");
|
|
|
|
|
|
|
|
|
|
AudioTranscription transcription = client.TranscribeAudio(audioFilePath);
|
|
|
|
|
|
|
|
|
|
_output.WriteLine($"{transcription.Text}");
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将响应与流式处理和推理结合使用
|
|
|
|
|
[Fact(Skip ="因本地Ollama测试环境不支持,忽略测试")]
|
|
|
|
|
public void Responses_With_Streaming_Reasoning_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
#region 如何将响应与文件搜索一起使用
|
|
|
|
|
public async Task Respones_With_FileSearch_Test()
|
|
|
|
|
{
|
|
|
|
|
#pragma warning disable OPENAI001
|
|
|
|
|
OpenAIResponseClient client = new(
|
|
|
|
|
model: "gpt-4o-mini",
|
|
|
|
|
apiKey: Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
|
|
|
|
|
|
|
|
|
|
ResponseTool fileSearchTool = ResponseTool.CreateFileSearchTool(vectorStoreIds: ["sssssssss"]);
|
|
|
|
|
OpenAIResponse response = await client.CreateResponseAsync
|
|
|
|
|
(
|
|
|
|
|
userInputText: "According to available files, what's the secret number?",
|
|
|
|
|
new ResponseCreationOptions()
|
|
|
|
|
{
|
|
|
|
|
Tools = { fileSearchTool }
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach (ResponseItem outputItem in response.OutputItems)
|
|
|
|
|
{
|
|
|
|
|
if (outputItem is FileSearchCallResponseItem fileSearchCall)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"[file_search] ({fileSearchCall.Status}): {fileSearchCall.Id}");
|
|
|
|
|
foreach (string query in fileSearchCall.Queries)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" - {query}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (outputItem is MessageResponseItem message)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"[{message.Role}] {message.Content.FirstOrDefault()?.Text}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#pragma warning restore OPENAI001
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何将响应与网络搜索结合使用
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task WebSearch_ChatClient_Test()
|
|
|
|
|
{
|
|
|
|
|
#pragma warning disable OPENAI001
|
|
|
|
|
OpenAIResponseClient client = _defaultOpenAIClient.GetOpenAIResponseClient(ModelSelecter.ModelWithRawmodel);
|
|
|
|
|
|
|
|
|
|
OpenAIResponse response = await client.CreateResponseAsync
|
|
|
|
|
(
|
|
|
|
|
userInputText: "What's a happy news headline from today?",
|
|
|
|
|
new ResponseCreationOptions()
|
|
|
|
|
{
|
|
|
|
|
Tools = { ResponseTool.CreateWebSearchTool() },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach (ResponseItem item in response.OutputItems)
|
|
|
|
|
{
|
|
|
|
|
if (item is WebSearchCallResponseItem webSearchCall)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"[Web search invoked]({webSearchCall.Status}) {webSearchCall.Id}");
|
|
|
|
|
}
|
|
|
|
|
else if (item is MessageResponseItem message)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"[{message.Role}] {message.Content?.FirstOrDefault()?.Text}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#pragma warning restore OPENAI001
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 如何生成文本嵌入
|
|
|
|
@ -131,4 +551,18 @@ public class OpenAISdkTest
|
|
|
|
|
|
|
|
|
|
#region 高级方案
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 私有方法
|
|
|
|
|
private static string GetCurrentLocation()
|
|
|
|
|
{
|
|
|
|
|
// Call the location API here.
|
|
|
|
|
return "San Francisco";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetCurrentWeather(string location, string unit = "celsius")
|
|
|
|
|
{
|
|
|
|
|
// Call the weather API here.
|
|
|
|
|
return $"31 {unit}";
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|