openai/openai-dotnet

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
achandmsft-patch-1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/Custom/Embeddings/OpenAIEmbedding.cs

153lines · modecode

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