Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit194d9db

Browse files
authored
Expose M.E.AI.OpenAI input message conversions (#6601)
Internally we have helpers that convert from M.E.AI chat messages to the various OpenAI object models. To ease interop when a developer gets M.E.AI messages from another library and then wants to submit them on their own to OpenAI, this just exposes those helpers publicly.
1 parentcc0d010 commit194d9db

File tree

5 files changed

+205
-84
lines changed

5 files changed

+205
-84
lines changed

‎src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public async Task<ChatResponse> GetResponseAsync(
7070
{
7171
_=Throw.IfNull(messages);
7272

73-
varopenAIChatMessages=ToOpenAIChatMessages(messages,options,AIJsonUtilities.DefaultOptions);
73+
varopenAIChatMessages=ToOpenAIChatMessages(messages,options);
7474
varopenAIOptions=ToOpenAIOptions(options);
7575

7676
// Make the call to OpenAI.
@@ -85,7 +85,7 @@ public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
8585
{
8686
_=Throw.IfNull(messages);
8787

88-
varopenAIChatMessages=ToOpenAIChatMessages(messages,options,AIJsonUtilities.DefaultOptions);
88+
varopenAIChatMessages=ToOpenAIChatMessages(messages,options);
8989
varopenAIOptions=ToOpenAIOptions(options);
9090

9191
// Make the call to OpenAI.
@@ -115,7 +115,7 @@ internal static ChatTool ToOpenAIChatTool(AIFunction aiFunction, ChatOptions? op
115115
}
116116

117117
/// <summary>Converts an Extensions chat message enumerable to an OpenAI chat message enumerable.</summary>
118-
privatestaticIEnumerable<OpenAI.Chat.ChatMessage>ToOpenAIChatMessages(IEnumerable<ChatMessage>inputs,ChatOptions?chatOptions,JsonSerializerOptionsjsonOptions)
118+
internalstaticIEnumerable<OpenAI.Chat.ChatMessage>ToOpenAIChatMessages(IEnumerable<ChatMessage>inputs,ChatOptions?chatOptions)
119119
{
120120
// Maps all of the M.E.AI types to the corresponding OpenAI types.
121121
// Unrecognized or non-processable content is ignored.
@@ -148,7 +148,7 @@ internal static ChatTool ToOpenAIChatTool(AIFunction aiFunction, ChatOptions? op
148148
{
149149
try
150150
{
151-
result=JsonSerializer.Serialize(resultContent.Result,jsonOptions.GetTypeInfo(typeof(object)));
151+
result=JsonSerializer.Serialize(resultContent.Result,AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object)));
152152
}
153153
catch(NotSupportedException)
154154
{
@@ -176,7 +176,7 @@ internal static ChatTool ToOpenAIChatTool(AIFunction aiFunction, ChatOptions? op
176176
caseFunctionCallContentfc:
177177
(toolCalls??=[]).Add(
178178
ChatToolCall.CreateFunctionToolCall(fc.CallId,fc.Name,new(JsonSerializer.SerializeToUtf8Bytes(
179-
fc.Arguments,jsonOptions.GetTypeInfo(typeof(IDictionary<string,object?>))))));
179+
fc.Arguments,AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IDictionary<string,object?>))))));
180180
break;
181181

182182
default:

‎src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@ public static ResponseTool AsOpenAIResponseTool(this AIFunction function) =>
181181
publicstaticConversationFunctionToolAsOpenAIConversationFunctionTool(thisAIFunctionfunction)=>
182182
OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));
183183

184+
/// <summary>Creates a sequence of OpenAI <see cref="OpenAI.Chat.ChatMessage"/> instances from the specified input messages.</summary>
185+
/// <param name="messages">The input messages to convert.</param>
186+
/// <returns>A sequence of OpenAI chat messages.</returns>
187+
publicstaticIEnumerable<OpenAI.Chat.ChatMessage>AsOpenAIChatMessages(thisIEnumerable<ChatMessage>messages)=>
188+
OpenAIChatClient.ToOpenAIChatMessages(Throw.IfNull(messages),chatOptions:null);
189+
190+
/// <summary>Creates a sequence of OpenAI <see cref="OpenAI.Responses.ResponseItem"/> instances from the specified input messages.</summary>
191+
/// <param name="messages">The input messages to convert.</param>
192+
/// <returns>A sequence of OpenAI response items.</returns>
193+
publicstaticIEnumerable<OpenAI.Responses.ResponseItem>AsOpenAIResponseItems(thisIEnumerable<ChatMessage>messages)=>
194+
OpenAIResponseChatClient.ToOpenAIResponseItems(Throw.IfNull(messages));
195+
184196
// TODO: Once we're ready to rely on C# 14 features, add an extension property ChatOptions.Strict.
185197

186198
/// <summary>Gets whether the properties specify that strict schema handling is desired.</summary>

‎src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma warning disableS1067// Expressions should not be too complex
1818
#pragma warning disableS3011// Reflection should not be used to increase accessibility of classes, methods, or fields
1919
#pragma warning disableS3604// Member initializer values should not be redundant
20+
#pragma warning disableSA1202// Elements should be ordered by access
2021
#pragma warning disableSA1204// Static elements should appear before instance elements
2122

2223
namespaceMicrosoft.Extensions.AI;
@@ -466,8 +467,7 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
466467
}
467468

468469
/// <summary>Convert a sequence of <see cref="ChatMessage"/>s to <see cref="ResponseItem"/>s.</summary>
469-
privatestaticIEnumerable<ResponseItem>ToOpenAIResponseItems(
470-
IEnumerable<ChatMessage>inputs)
470+
internalstaticIEnumerable<ResponseItem>ToOpenAIResponseItems(IEnumerable<ChatMessage>inputs)
471471
{
472472
foreach(ChatMessageinputininputs)
473473
{

‎test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAIFunctionConversionTests.cs‎

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
usingSystem;
5+
usingSystem.Collections.Generic;
6+
usingSystem.ComponentModel;
7+
usingSystem.Linq;
8+
usingSystem.Text.Json;
9+
usingOpenAI.Assistants;
10+
usingOpenAI.Chat;
11+
usingOpenAI.Realtime;
12+
usingOpenAI.Responses;
13+
usingXunit;
14+
15+
namespaceMicrosoft.Extensions.AI;
16+
17+
publicclassOpenAIConversionTests
18+
{
19+
privatestaticreadonlyAIFunction_testFunction=AIFunctionFactory.Create(
20+
([Description("The name parameter")]stringname)=>name,
21+
"test_function",
22+
"A test function for conversion");
23+
24+
[Fact]
25+
publicvoidAsOpenAIChatTool_ProducesValidInstance()
26+
{
27+
vartool=_testFunction.AsOpenAIChatTool();
28+
29+
Assert.NotNull(tool);
30+
Assert.Equal("test_function",tool.FunctionName);
31+
Assert.Equal("A test function for conversion",tool.FunctionDescription);
32+
ValidateSchemaParameters(tool.FunctionParameters);
33+
}
34+
35+
[Fact]
36+
publicvoidAsOpenAIResponseTool_ProducesValidInstance()
37+
{
38+
vartool=_testFunction.AsOpenAIResponseTool();
39+
40+
Assert.NotNull(tool);
41+
}
42+
43+
[Fact]
44+
publicvoidAsOpenAIConversationFunctionTool_ProducesValidInstance()
45+
{
46+
vartool=_testFunction.AsOpenAIConversationFunctionTool();
47+
48+
Assert.NotNull(tool);
49+
Assert.Equal("test_function",tool.Name);
50+
Assert.Equal("A test function for conversion",tool.Description);
51+
ValidateSchemaParameters(tool.Parameters);
52+
}
53+
54+
[Fact]
55+
publicvoidAsOpenAIAssistantsFunctionToolDefinition_ProducesValidInstance()
56+
{
57+
vartool=_testFunction.AsOpenAIAssistantsFunctionToolDefinition();
58+
59+
Assert.NotNull(tool);
60+
Assert.Equal("test_function",tool.FunctionName);
61+
Assert.Equal("A test function for conversion",tool.Description);
62+
ValidateSchemaParameters(tool.Parameters);
63+
}
64+
65+
/// <summary>Helper method to validate function parameters match our schema.</summary>
66+
privatestaticvoidValidateSchemaParameters(BinaryDataparameters)
67+
{
68+
Assert.NotNull(parameters);
69+
70+
usingvarjsonDoc=JsonDocument.Parse(parameters);
71+
varroot=jsonDoc.RootElement;
72+
73+
Assert.Equal("object",root.GetProperty("type").GetString());
74+
Assert.True(root.TryGetProperty("properties",outvarproperties));
75+
Assert.True(properties.TryGetProperty("name",outvarnameProperty));
76+
Assert.Equal("string",nameProperty.GetProperty("type").GetString());
77+
Assert.Equal("The name parameter",nameProperty.GetProperty("description").GetString());
78+
}
79+
80+
[Fact]
81+
publicvoidAsOpenAIChatMessages_ProducesExpectedOutput()
82+
{
83+
Assert.Throws<ArgumentNullException>("messages",()=>((IEnumerable<ChatMessage>)null!).AsOpenAIChatMessages());
84+
85+
List<ChatMessage>messages=
86+
[
87+
new(ChatRole.System,"You are a helpful assistant."),
88+
new(ChatRole.User,"Hello"),
89+
new(ChatRole.Assistant,
90+
[
91+
newTextContent("Hi there!"),
92+
newFunctionCallContent("callid123","SomeFunction",newDictionary<string,object?>
93+
{
94+
["param1"]="value1",
95+
["param2"]=42
96+
}),
97+
]),
98+
new(ChatRole.Tool,[newFunctionResultContent("callid123","theresult")]),
99+
new(ChatRole.Assistant,"The answer is 42."),
100+
];
101+
102+
varconvertedMessages=messages.AsOpenAIChatMessages().ToArray();
103+
104+
Assert.Equal(5,convertedMessages.Length);
105+
106+
SystemChatMessagem0=Assert.IsType<SystemChatMessage>(convertedMessages[0]);
107+
Assert.Equal("You are a helpful assistant.",Assert.Single(m0.Content).Text);
108+
109+
UserChatMessagem1=Assert.IsType<UserChatMessage>(convertedMessages[1]);
110+
Assert.Equal("Hello",Assert.Single(m1.Content).Text);
111+
112+
AssistantChatMessagem2=Assert.IsType<AssistantChatMessage>(convertedMessages[2]);
113+
Assert.Single(m2.Content);
114+
Assert.Equal("Hi there!",m2.Content[0].Text);
115+
vartc=Assert.Single(m2.ToolCalls);
116+
Assert.Equal("callid123",tc.Id);
117+
Assert.Equal("SomeFunction",tc.FunctionName);
118+
Assert.True(JsonElement.DeepEquals(JsonSerializer.SerializeToElement(newDictionary<string,object?>
119+
{
120+
["param1"]="value1",
121+
["param2"]=42
122+
}),JsonSerializer.Deserialize<JsonElement>(tc.FunctionArguments.ToMemory().Span)));
123+
124+
ToolChatMessagem3=Assert.IsType<ToolChatMessage>(convertedMessages[3]);
125+
Assert.Equal("callid123",m3.ToolCallId);
126+
Assert.Equal("theresult",Assert.Single(m3.Content).Text);
127+
128+
AssistantChatMessagem4=Assert.IsType<AssistantChatMessage>(convertedMessages[4]);
129+
Assert.Equal("The answer is 42.",Assert.Single(m4.Content).Text);
130+
}
131+
132+
[Fact]
133+
publicvoidAsOpenAIResponseItems_ProducesExpectedOutput()
134+
{
135+
Assert.Throws<ArgumentNullException>("messages",()=>((IEnumerable<ChatMessage>)null!).AsOpenAIResponseItems());
136+
137+
List<ChatMessage>messages=
138+
[
139+
new(ChatRole.System,"You are a helpful assistant."),
140+
new(ChatRole.User,"Hello"),
141+
new(ChatRole.Assistant,
142+
[
143+
newTextContent("Hi there!"),
144+
newFunctionCallContent("callid123","SomeFunction",newDictionary<string,object?>
145+
{
146+
["param1"]="value1",
147+
["param2"]=42
148+
}),
149+
]),
150+
new(ChatRole.Tool,[newFunctionResultContent("callid123","theresult")]),
151+
new(ChatRole.Assistant,"The answer is 42."),
152+
];
153+
154+
varconvertedItems=messages.AsOpenAIResponseItems().ToArray();
155+
156+
Assert.Equal(6,convertedItems.Length);
157+
158+
MessageResponseItemm0=Assert.IsAssignableFrom<MessageResponseItem>(convertedItems[0]);
159+
Assert.Equal("You are a helpful assistant.",Assert.Single(m0.Content).Text);
160+
161+
MessageResponseItemm1=Assert.IsAssignableFrom<MessageResponseItem>(convertedItems[1]);
162+
Assert.Equal(OpenAI.Responses.MessageRole.User,m1.Role);
163+
Assert.Equal("Hello",Assert.Single(m1.Content).Text);
164+
165+
MessageResponseItemm2=Assert.IsAssignableFrom<MessageResponseItem>(convertedItems[2]);
166+
Assert.Equal(OpenAI.Responses.MessageRole.Assistant,m2.Role);
167+
Assert.Equal("Hi there!",Assert.Single(m2.Content).Text);
168+
169+
FunctionCallResponseItemm3=Assert.IsAssignableFrom<FunctionCallResponseItem>(convertedItems[3]);
170+
Assert.Equal("callid123",m3.CallId);
171+
Assert.Equal("SomeFunction",m3.FunctionName);
172+
Assert.True(JsonElement.DeepEquals(JsonSerializer.SerializeToElement(newDictionary<string,object?>
173+
{
174+
["param1"]="value1",
175+
["param2"]=42
176+
}),JsonSerializer.Deserialize<JsonElement>(m3.FunctionArguments.ToMemory().Span)));
177+
178+
FunctionCallOutputResponseItemm4=Assert.IsAssignableFrom<FunctionCallOutputResponseItem>(convertedItems[4]);
179+
Assert.Equal("callid123",m4.CallId);
180+
Assert.Equal("theresult",m4.FunctionOutput);
181+
182+
MessageResponseItemm5=Assert.IsAssignableFrom<MessageResponseItem>(convertedItems[5]);
183+
Assert.Equal(OpenAI.Responses.MessageRole.Assistant,m5.Role);
184+
Assert.Equal("The answer is 42.",Assert.Single(m5.Content).Text);
185+
}
186+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp