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 · modecode

1using 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]
18 public async Task Example01_AudioFromFileWithToolsAsync()
19 {
20 RealtimeConversationClient client = new(
21 model: "gpt-4o-realtime-preview",
22 credential: new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")));
23 using 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.
27 ConversationSessionOptions sessionOptions = new()
28 {
29 Instructions = "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.",
32 Voice = ConversationVoice.Alloy,
33 Tools = { CreateSampleWeatherTool() },
34 InputAudioFormat = ConversationAudioFormat.Pcm16,
35 OutputAudioFormat = ConversationAudioFormat.Pcm16,
36 // Input transcription options must be provided to enable transcribed feedback for input audio
37 InputTranscriptionOptions = new()
38 {
39 Model = "whisper-1",
40 },
41 };
42
43 await 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.
47 await session.AddItemAsync(
48 ConversationItem.CreateUserMessage(["I'm trying to decide what to wear on my trip."]));
49
50 string inputAudioPath = FindFile("Assets\\realtime_whats_the_weather_pcm16_24khz_mono.wav");
51 using Stream inputAudioStream = File.OpenRead(inputAudioPath);
52 _ = session.SendInputAudioAsync(inputAudioStream);
53
54 Dictionary<string, Stream> outputAudioStreamsById = [];
55
56 await foreach (ConversationUpdate update in session.ReceiveUpdatesAsync())
57 {
58 if (update is ConversationSessionStartedUpdate sessionStartedUpdate)
59 {
60 Console.WriteLine($"<<< Session started. ID: {sessionStartedUpdate.SessionId}");
61 Console.WriteLine();
62 }
63
64 if (update is ConversationInputSpeechStartedUpdate speechStartedUpdate)
65 {
66 Console.WriteLine(
67 $" -- Voice activity detection started at {speechStartedUpdate.AudioStartTime}");
68 }
69
70 if (update is ConversationInputSpeechFinishedUpdate speechFinishedUpdate)
71 {
72 Console.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.
78 if (update is ConversationItemStreamingStartedUpdate itemStreamingStartedUpdate)
79 {
80 Console.WriteLine($" -- Begin streaming of new item");
81 if (!string.IsNullOrEmpty(itemStreamingStartedUpdate.FunctionName))
82 {
83 Console.Write($" {itemStreamingStartedUpdate.FunctionName}: ");
84 }
85 }
86
87 if (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.
92 Console.Write(deltaUpdate.AudioTranscript);
93 Console.Write(deltaUpdate.Text);
94 Console.Write(deltaUpdate.FunctionArguments);
95 if (deltaUpdate.AudioBytes is not null)
96 {
97 if (!outputAudioStreamsById.TryGetValue(deltaUpdate.ItemId, out Stream value))
98 {
99 string filename = $"output_{sessionOptions.OutputAudioFormat}_{deltaUpdate.ItemId}.raw";
100 value = File.OpenWrite(filename);
101 outputAudioStreamsById[deltaUpdate.ItemId] = value;
102 }
103
104 value.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.
111 if (update is ConversationItemStreamingFinishedUpdate itemStreamingFinishedUpdate)
112 {
113 Console.WriteLine();
114 Console.WriteLine($" -- Item streaming finished, item_id={itemStreamingFinishedUpdate.ItemId}");
115
116 if (itemStreamingFinishedUpdate.FunctionCallId is not null)
117 {
118 Console.WriteLine($" + Responding to tool invoked by item: {itemStreamingFinishedUpdate.FunctionName}");
119 ConversationItem functionOutputItem = ConversationItem.CreateFunctionCallOutput(
120 callId: itemStreamingFinishedUpdate.FunctionCallId,
121 output: "70 degrees Fahrenheit and sunny");
122 await session.AddItemAsync(functionOutputItem);
123 }
124 else if (itemStreamingFinishedUpdate.MessageContentParts?.Count > 0)
125 {
126 Console.Write($" + [{itemStreamingFinishedUpdate.MessageRole}]: ");
127 foreach (ConversationContentPart contentPart in itemStreamingFinishedUpdate.MessageContentParts)
128 {
129 Console.Write(contentPart.AudioTranscript);
130 }
131 Console.WriteLine();
132 }
133 }
134
135 if (update is ConversationInputTranscriptionFinishedUpdate transcriptionCompletedUpdate)
136 {
137 Console.WriteLine();
138 Console.WriteLine($" -- User audio transcript: {transcriptionCompletedUpdate.Transcript}");
139 Console.WriteLine();
140 }
141
142 if (update is ConversationResponseFinishedUpdate turnFinishedUpdate)
143 {
144 Console.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.
149 if (turnFinishedUpdate.CreatedItems.Any(item => item.FunctionName?.Length > 0))
150 {
151 Console.WriteLine($" -- Ending client turn for pending tool responses");
152 await session.StartResponseAsync();
153 }
154 else
155 {
156 break;
157 }
158 }
159
160 if (update is ConversationErrorUpdate errorUpdate)
161 {
162 Console.WriteLine();
163 Console.WriteLine($"ERROR: {errorUpdate.Message}");
164 break;
165 }
166 }
167
168 foreach ((string itemId, Stream outputAudioStream) in outputAudioStreamsById)
169 {
170 Console.WriteLine($"Raw audio output for {itemId}: {outputAudioStream.Length} bytes");
171 outputAudioStream.Dispose();
172 }
173 }
174
175 private static ConversationFunctionTool CreateSampleWeatherTool()
176 {
177 return new ConversationFunctionTool("get_weather_for_location")
178 {
179 Description = "gets the weather for a location",
180 Parameters = 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
199 private static string FindFile(string fileName)
200 {
201 for (string currentDirectory = Directory.GetCurrentDirectory();
202 currentDirectory != null && currentDirectory != Path.GetPathRoot(currentDirectory);
203 currentDirectory = Directory.GetParent(currentDirectory)?.FullName!)
204 {
205 string filePath = Path.Combine(currentDirectory, fileName);
206 if (File.Exists(filePath))
207 {
208 return filePath;
209 }
210 }
211
212 throw new FileNotFoundException($"File '{fileName}' not found.");
213 }
214}
215