openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/Custom/Embeddings/OpenAIEmbedding.cs

191lines · modecode

1using System;
2using System.Buffers;
3using System.Buffers.Binary;
4using System.Buffers.Text;
5using System.Collections.Generic;
6using System.Runtime.InteropServices;
7using System.Text.Json;
8
9namespace OpenAI.Embeddings;
10
11/// <summary>
12/// Represents an embedding vector returned by embedding endpoint.
13/// </summary>
14[CodeGenType("Embedding")]
15[CodeGenSuppress("OpenAIEmbedding", typeof(int), typeof(BinaryData))]
16public partial class OpenAIEmbedding
17{
18 // CUSTOM: Made private. The value of the embedding is publicly exposed as ReadOnlyMemory<float> instead of BinaryData.
19 /// <summary>
20 /// The embedding vector, which is a list of floats. The length of vector depends on the model as
21 /// listed in the [embedding guide](/docs/guides/embeddings).
22 /// <para>
23 /// To assign an object to this property use <see cref="BinaryData.FromObjectAsJson{T}(T, System.Text.Json.JsonSerializerOptions?)"/>.
24 /// </para>
25 /// <para>
26 /// To assign an already formatted json string to this property use <see cref="BinaryData.FromString(string)"/>.
27 /// </para>
28 /// <para>
29 /// <remarks>
30 /// Supported types:
31 /// <list type="bullet">
32 /// <item>
33 /// <description><see cref="IList{T}"/> where <c>T</c> is of type <see cref="double"/></description>
34 /// </item>
35 /// <item>
36 /// <description><see cref="string"/></description>
37 /// </item>
38 /// </list>
39 /// </remarks>
40 /// Examples:
41 /// <list type="bullet">
42 /// <item>
43 /// <term>BinaryData.FromObjectAsJson("foo")</term>
44 /// <description>Creates a payload of "foo".</description>
45 /// </item>
46 /// <item>
47 /// <term>BinaryData.FromString("\"foo\"")</term>
48 /// <description>Creates a payload of "foo".</description>
49 /// </item>
50 /// <item>
51 /// <term>BinaryData.FromObjectAsJson(new { key = "value" })</term>
52 /// <description>Creates a payload of { "key": "value" }.</description>
53 /// </item>
54 /// <item>
55 /// <term>BinaryData.FromString("{\"key\": \"value\"}")</term>
56 /// <description>Creates a payload of { "key": "value" }.</description>
57 /// </item>
58 /// </list>
59 /// </para>
60 /// </summary>
61 [CodeGenMember("Embedding")]
62 private BinaryData EmbeddingProperty { get; }
63
64 // CUSTOM: Made private. This property does not add value in the context of a strongly-typed class.
65 /// <summary> The object type, which is always "embedding". </summary>
66 private string Object { get; } = "embedding";
67
68 // CUSTOM: Added logic to handle additional custom properties.
69 /// <summary> Initializes a new instance of <see cref="OpenAIEmbedding"/>. </summary>
70 /// <param name="index"> The index of the embedding in the list of embeddings. </param>
71 /// <param name="embeddingProperty">
72 /// The embedding vector, which is a list of floats. The length of vector depends on the model as
73 /// listed in the [embedding guide](/docs/guides/embeddings).
74 /// </param>
75 /// <param name="object"> The object type, which is always "embedding". </param>
76 /// <param name="serializedAdditionalRawData"> Keeps track of any properties unknown to the library. </param>
77 internal OpenAIEmbedding(int index, BinaryData embeddingProperty, string @object, IDictionary<string, BinaryData> serializedAdditionalRawData)
78 {
79 Index = index;
80 EmbeddingProperty = embeddingProperty;
81 Object = @object;
82 _additionalBinaryDataProperties = serializedAdditionalRawData;
83
84 // Handle additional custom properties.
85 _vector = ConvertToVectorOfFloats(embeddingProperty);
86 }
87
88 // CUSTOM: Entirely custom constructor used by the Model Factory.
89 /// <summary> Initializes a new instance of <see cref="OpenAIEmbedding"/>. </summary>
90 /// <param name="index"> The index of the embedding in the list of embeddings. </param>
91 /// <param name="vector"> The embedding vector, which is a list of floats. </param>
92 internal OpenAIEmbedding(int index, ReadOnlyMemory<float> vector)
93 {
94 Index = index;
95 _vector = vector;
96 }
97
98 private ReadOnlyMemory<float> _vector;
99
100 // CUSTOM: Added as a public, custom method. For slightly better performance, the embedding is always requested as a base64-encoded
101 // string and then manually transformed into a more user-friendly ReadOnlyMemory<float>.
102 /// <summary>
103 /// Gets the embedding vector as a list of floats.
104 /// </summary>
105 /// <returns>A read-only memory segment of floats representing the embedding vector.</returns>
106 public ReadOnlyMemory<float> ToFloats() => _vector;
107
108 // CUSTOM: Implemented custom logic to transform from BinaryData to ReadOnlyMemory<float>.
109 private static ReadOnlyMemory<float> ConvertToVectorOfFloats(BinaryData binaryData)
110 {
111 ReadOnlySpan<byte> bytes = binaryData.ToMemory().Span;
112
113 // Remove quotes around base64 string.
114 if (bytes.Length > 2 && bytes[0] == (byte)'"' && bytes[bytes.Length - 1] == (byte)'"')
115 {
116 return ConvertFromBase64(bytes);
117 }
118 return ConvertFromJsonArray(binaryData);
119 }
120
121 private static ReadOnlyMemory<float> ConvertFromBase64(ReadOnlySpan<byte> base64)
122 {
123 base64 = base64.Slice(1, base64.Length - 2);
124
125 // Decode base64 string to bytes.
126 byte[] bytes = null;
127 try
128 {
129 bytes = ArrayPool<byte>.Shared.Rent(Base64.GetMaxDecodedFromUtf8Length(base64.Length));
130 OperationStatus status = Base64.DecodeFromUtf8(base64, bytes.AsSpan(), out int bytesConsumed, out int bytesWritten);
131 if (status != OperationStatus.Done || bytesWritten % sizeof(float) != 0)
132 {
133 ThrowInvalidData();
134 }
135
136 // Interpret bytes as floats
137 float[] vector = new float[bytesWritten / sizeof(float)];
138 bytes.AsSpan(0, bytesWritten).CopyTo(MemoryMarshal.AsBytes(vector.AsSpan()));
139 if (!BitConverter.IsLittleEndian)
140 {
141 Span<int> ints = MemoryMarshal.Cast<float, int>(vector.AsSpan());
142#if NET8_0_OR_GREATER
143 BinaryPrimitives.ReverseEndianness(ints, ints);
144#else
145 for (int i = 0; i < ints.Length; i++)
146 {
147 ints[i] = BinaryPrimitives.ReverseEndianness(ints[i]);
148 }
149#endif
150 }
151
152 return new ReadOnlyMemory<float>(vector);
153 }
154 finally
155 {
156 if (bytes is not null)
157 {
158 ArrayPool<byte>.Shared.Return(bytes);
159 }
160 }
161
162 static void ThrowInvalidData()
163 => throw new FormatException("The input is not a valid Base64 string of encoded floats.");
164 }
165
166 private static ReadOnlyMemory<float> ConvertFromJsonArray(BinaryData jsonArray)
167 {
168 using JsonDocument document = JsonDocument.Parse(jsonArray);
169 JsonElement array = document.RootElement;
170 if (array.ValueKind != JsonValueKind.Array)
171 {
172 throw new FormatException("The input is not a valid JSON array");
173 }
174
175 int arrayLength = array.GetArrayLength();
176 float[] vector = new float[arrayLength];
177 int index = 0;
178 try
179 {
180 foreach (JsonElement value in array.EnumerateArray())
181 {
182 vector[index++] = value.GetSingle();
183 }
184 return vector.AsMemory();
185 }
186 catch
187 {
188 throw new FormatException("The input is not a valid JSON array of float values");
189 }
190 }
191}
192