openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
jsquire-patch-1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

examples/Realtime/Example01_AudioFromFileWithToolsAsync.cs

214lines · modeblame

b0f9e5c3Jose Arriaga Maldonado1 years ago1using NUnit.Framework;
2using OpenAI.Images;
3using OpenAI.RealtimeConversation;
4using System;
5using System.ClientModel;
6using System.Collections.Generic;
7using System.IO;
8using System.Linq;
9using System.Threading.Tasks;
10
11namespace OpenAI.Examples;
12
13#pragma warning disable OPENAI002
14
15public partial class RealtimeExamples
16{
17[Test]
18public async Task Example01_AudioFromFileWithToolsAsync()
19{
20RealtimeConversationClient client = new(
21model: "gpt-4o-realtime-preview",
22credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")));
23using RealtimeConversationSession session = await client.StartConversationSessionAsync();
24
25// Session options control connection-wide behavior shared across all conversations,
26// including audio input format and voice activity detection settings.
27ConversationSessionOptions sessionOptions = new()
28{
29Instructions = "You are a cheerful assistant that talks like a pirate. "
30+ "Always inform the user when you are about to call a tool. "
31+ "Prefer to call tools whenever applicable.",
32Voice = ConversationVoice.Alloy,
33Tools = { CreateSampleWeatherTool() },
34InputAudioFormat = ConversationAudioFormat.Pcm16,
35OutputAudioFormat = ConversationAudioFormat.Pcm16,
36// Input transcription options must be provided to enable transcribed feedback for input audio
37InputTranscriptionOptions = new()
38{
39Model = "whisper-1",
40},
41};
42
43await session.ConfigureSessionAsync(sessionOptions);
44
45// Conversation history or text input are provided by adding messages to the conversation.
46// Adding a message will not automatically begin a response turn.
47await session.AddItemAsync(
48ConversationItem.CreateUserMessage(["I'm trying to decide what to wear on my trip."]));
49
50string inputAudioPath = FindFile("Assets\\realtime_whats_the_weather_pcm16_24khz_mono.wav");
51using Stream inputAudioStream = File.OpenRead(inputAudioPath);
52_ = session.SendInputAudioAsync(inputAudioStream);
53
54Dictionary<string, Stream> outputAudioStreamsById = [];
55
56await foreach (ConversationUpdate update in session.ReceiveUpdatesAsync())
57{
58if (update is ConversationSessionStartedUpdate sessionStartedUpdate)
59{
60Console.WriteLine($"<<< Session started. ID: {sessionStartedUpdate.SessionId}");
61Console.WriteLine();
62}
63
64if (update is ConversationInputSpeechStartedUpdate speechStartedUpdate)
65{
66Console.WriteLine(
67$" -- Voice activity detection started at {speechStartedUpdate.AudioStartTime}");
68}
69
70if (update is ConversationInputSpeechFinishedUpdate speechFinishedUpdate)
71{
72Console.WriteLine(
73$" -- Voice activity detection ended at {speechFinishedUpdate.AudioEndTime}");
74}
75
76// Item started updates notify that the model generation process will insert a new item into
77// the conversation and begin streaming its content via content updates.
78if (update is ConversationItemStreamingStartedUpdate itemStreamingStartedUpdate)
79{
80Console.WriteLine($" -- Begin streaming of new item");
81if (!string.IsNullOrEmpty(itemStreamingStartedUpdate.FunctionName))
82{
83Console.Write($" {itemStreamingStartedUpdate.FunctionName}: ");
84}
85}
86
87if (update is ConversationItemStreamingPartDeltaUpdate deltaUpdate)
88{
89// With audio output enabled, the audio transcript of the delta update contains an approximation of
90// the words spoken by the model. Without audio output, the text of the delta update will contain
91// the segments making up the text content of a message.
92Console.Write(deltaUpdate.AudioTranscript);
93Console.Write(deltaUpdate.Text);
94Console.Write(deltaUpdate.FunctionArguments);
95if (deltaUpdate.AudioBytes is not null)
96{
97if (!outputAudioStreamsById.TryGetValue(deltaUpdate.ItemId, out Stream value))
98{
99string filename = $"output_{sessionOptions.OutputAudioFormat}_{deltaUpdate.ItemId}.raw";
100value = File.OpenWrite(filename);
101outputAudioStreamsById[deltaUpdate.ItemId] = value;
102}
103
104value.Write(deltaUpdate.AudioBytes);
105}
106}
107
108// Item finished updates arrive when all streamed data for an item has arrived and the
109// accumulated results are available. In the case of function calls, this is the point
110// where all arguments are expected to be present.
111if (update is ConversationItemStreamingFinishedUpdate itemStreamingFinishedUpdate)
112{
113Console.WriteLine();
114Console.WriteLine($" -- Item streaming finished, item_id={itemStreamingFinishedUpdate.ItemId}");
115
116if (itemStreamingFinishedUpdate.FunctionCallId is not null)
117{
118Console.WriteLine($" + Responding to tool invoked by item: {itemStreamingFinishedUpdate.FunctionName}");
119ConversationItem functionOutputItem = ConversationItem.CreateFunctionCallOutput(
120callId: itemStreamingFinishedUpdate.FunctionCallId,
121output: "70 degrees Fahrenheit and sunny");
122await session.AddItemAsync(functionOutputItem);
123}
124else if (itemStreamingFinishedUpdate.MessageContentParts?.Count > 0)
125{
126Console.Write($" + [{itemStreamingFinishedUpdate.MessageRole}]: ");
127foreach (ConversationContentPart contentPart in itemStreamingFinishedUpdate.MessageContentParts)
128{
129Console.Write(contentPart.AudioTranscript);
130}
131Console.WriteLine();
132}
133}
134
135if (update is ConversationInputTranscriptionFinishedUpdate transcriptionCompletedUpdate)
136{
137Console.WriteLine();
138Console.WriteLine($" -- User audio transcript: {transcriptionCompletedUpdate.Transcript}");
139Console.WriteLine();
140}
141
142if (update is ConversationResponseFinishedUpdate turnFinishedUpdate)
143{
144Console.WriteLine($" -- Model turn generation finished. Status: {turnFinishedUpdate.Status}");
145
146// Here, if we processed tool calls in the course of the model turn, we finish the
147// client turn to resume model generation. The next model turn will reflect the tool
148// responses that were already provided.
149if (turnFinishedUpdate.CreatedItems.Any(item => item.FunctionName?.Length > 0))
150{
151Console.WriteLine($" -- Ending client turn for pending tool responses");
152await session.StartResponseAsync();
153}
154else
155{
156break;
157}
158}
159
160if (update is ConversationErrorUpdate errorUpdate)
161{
162Console.WriteLine();
163Console.WriteLine($"ERROR: {errorUpdate.Message}");
164break;
165}
166}
167
168foreach ((string itemId, Stream outputAudioStream) in outputAudioStreamsById)
169{
170Console.WriteLine($"Raw audio output for {itemId}: {outputAudioStream.Length} bytes");
171outputAudioStream.Dispose();
172}
173}
174
175private static ConversationFunctionTool CreateSampleWeatherTool()
176{
86407c80Jose Arriaga Maldonado1 years ago177return new ConversationFunctionTool("get_weather_for_location")
b0f9e5c3Jose Arriaga Maldonado1 years ago178{
179Description = "gets the weather for a location",
180Parameters = BinaryData.FromString("""
181{
182"type": "object",
183"properties": {
184"location": {
185"type": "string",
186"description": "The city and state, e.g. San Francisco, CA"
187},
188"unit": {
189"type": "string",
190"enum": ["c","f"]
191}
192},
193"required": ["location","unit"]
194}
195""")
196};
197}
198
199private static string FindFile(string fileName)
200{
201for (string currentDirectory = Directory.GetCurrentDirectory();
202currentDirectory != null && currentDirectory != Path.GetPathRoot(currentDirectory);
203currentDirectory = Directory.GetParent(currentDirectory)?.FullName!)
204{
205string filePath = Path.Combine(currentDirectory, fileName);
206if (File.Exists(filePath))
207{
208return filePath;
209}
210}
211
212throw new FileNotFoundException($"File '{fileName}' not found.");
213}
214}