You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

OpenAIMessageTests.cs 33 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // OpenAIMessageTests.cs
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Text.Json;
  8. using System.Threading.Tasks;
  9. using ApprovalTests;
  10. using ApprovalTests.Namers;
  11. using ApprovalTests.Reporters;
  12. using AutoGen.Tests;
  13. using Azure.AI.OpenAI;
  14. using FluentAssertions;
  15. using Xunit;
  16. namespace AutoGen.OpenAI.V1.Tests;
  17. [Trait("Category", "UnitV1")]
  18. public class OpenAIMessageTests
  19. {
  20. private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions
  21. {
  22. WriteIndented = true,
  23. IgnoreReadOnlyProperties = false,
  24. };
  25. [Fact]
  26. [UseReporter(typeof(DiffReporter))]
  27. [UseApprovalSubdirectory("ApprovalTests")]
  28. public void BasicMessageTest()
  29. {
  30. IMessage[] messages = [
  31. new TextMessage(Role.System, "You are a helpful AI assistant"),
  32. new TextMessage(Role.User, "Hello", "user"),
  33. new TextMessage(Role.Assistant, "How can I help you?", from: "assistant"),
  34. new ImageMessage(Role.User, "https://example.com/image.png", "user"),
  35. new MultiModalMessage(Role.Assistant,
  36. [
  37. new TextMessage(Role.User, "Hello", "user"),
  38. new ImageMessage(Role.User, "https://example.com/image.png", "user"),
  39. ], "user"),
  40. new ToolCallMessage("test", "test", "assistant"),
  41. new ToolCallResultMessage("result", "test", "test", "user"),
  42. new ToolCallResultMessage(
  43. [
  44. new ToolCall("result", "test", "test"),
  45. new ToolCall("result", "test", "test"),
  46. ], "user"),
  47. new ToolCallMessage(
  48. [
  49. new ToolCall("test", "test"),
  50. new ToolCall("test", "test"),
  51. ], "assistant"),
  52. new AggregateMessage<ToolCallMessage, ToolCallResultMessage>(
  53. message1: new ToolCallMessage("test", "test", "assistant"),
  54. message2: new ToolCallResultMessage("result", "test", "test", "assistant"), "assistant"),
  55. ];
  56. var openaiMessageConnectorMiddleware = new OpenAIChatRequestMessageConnector();
  57. var agent = new EchoAgent("assistant");
  58. var oaiMessages = messages.Select(m => (m, openaiMessageConnectorMiddleware.ProcessIncomingMessages(agent, [m])));
  59. VerifyOAIMessages(oaiMessages);
  60. }
  61. [Fact]
  62. public async Task ItProcessUserTextMessageAsync()
  63. {
  64. var middleware = new OpenAIChatRequestMessageConnector();
  65. var agent = new EchoAgent("assistant")
  66. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  67. {
  68. var innerMessage = msgs.Last();
  69. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  70. var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  71. chatRequestMessage.Content.Should().Be("Hello");
  72. chatRequestMessage.Name.Should().Be("user");
  73. return await innerAgent.GenerateReplyAsync(msgs);
  74. })
  75. .RegisterMiddleware(middleware);
  76. // user message
  77. IMessage message = new TextMessage(Role.User, "Hello", "user");
  78. await agent.GenerateReplyAsync([message]);
  79. }
  80. [Fact]
  81. public async Task ItShortcutChatRequestMessageAsync()
  82. {
  83. var middleware = new OpenAIChatRequestMessageConnector();
  84. var agent = new EchoAgent("assistant")
  85. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  86. {
  87. var innerMessage = msgs.Last();
  88. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestUserMessage>>();
  89. var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope<ChatRequestUserMessage>)innerMessage!).Content;
  90. chatRequestMessage.Content.Should().Be("hello");
  91. return await innerAgent.GenerateReplyAsync(msgs);
  92. })
  93. .RegisterMiddleware(middleware);
  94. // user message
  95. var userMessage = new ChatRequestUserMessage("hello");
  96. var chatRequestMessage = MessageEnvelope.Create(userMessage);
  97. await agent.GenerateReplyAsync([chatRequestMessage]);
  98. }
  99. [Fact]
  100. public async Task ItShortcutMessageWhenStrictModelIsFalseAsync()
  101. {
  102. var middleware = new OpenAIChatRequestMessageConnector();
  103. var agent = new EchoAgent("assistant")
  104. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  105. {
  106. var innerMessage = msgs.Last();
  107. innerMessage!.Should().BeOfType<MessageEnvelope<string>>();
  108. var chatRequestMessage = ((MessageEnvelope<string>)innerMessage!).Content;
  109. chatRequestMessage.Should().Be("hello");
  110. return await innerAgent.GenerateReplyAsync(msgs);
  111. })
  112. .RegisterMiddleware(middleware);
  113. // user message
  114. var userMessage = "hello";
  115. var chatRequestMessage = MessageEnvelope.Create(userMessage);
  116. await agent.GenerateReplyAsync([chatRequestMessage]);
  117. }
  118. [Fact]
  119. public async Task ItThrowExceptionWhenStrictModeIsTrueAsync()
  120. {
  121. var middleware = new OpenAIChatRequestMessageConnector(true);
  122. var agent = new EchoAgent("assistant")
  123. .RegisterMiddleware(middleware);
  124. // user message
  125. var userMessage = "hello";
  126. var chatRequestMessage = MessageEnvelope.Create(userMessage);
  127. Func<Task> action = async () => await agent.GenerateReplyAsync([chatRequestMessage]);
  128. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: MessageEnvelope`1");
  129. }
  130. [Fact]
  131. public async Task ItProcessAssistantTextMessageAsync()
  132. {
  133. var middleware = new OpenAIChatRequestMessageConnector();
  134. var agent = new EchoAgent("assistant")
  135. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  136. {
  137. var innerMessage = msgs.Last();
  138. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  139. var chatRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  140. chatRequestMessage.Content.Should().Be("How can I help you?");
  141. chatRequestMessage.Name.Should().Be("assistant");
  142. return await innerAgent.GenerateReplyAsync(msgs);
  143. })
  144. .RegisterMiddleware(middleware);
  145. // assistant message
  146. IMessage message = new TextMessage(Role.Assistant, "How can I help you?", "assistant");
  147. await agent.GenerateReplyAsync([message]);
  148. }
  149. [Fact]
  150. public async Task ItProcessSystemTextMessageAsync()
  151. {
  152. var middleware = new OpenAIChatRequestMessageConnector();
  153. var agent = new EchoAgent("assistant")
  154. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  155. {
  156. var innerMessage = msgs.Last();
  157. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  158. var chatRequestMessage = (ChatRequestSystemMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  159. chatRequestMessage.Content.Should().Be("You are a helpful AI assistant");
  160. return await innerAgent.GenerateReplyAsync(msgs);
  161. })
  162. .RegisterMiddleware(middleware);
  163. // system message
  164. IMessage message = new TextMessage(Role.System, "You are a helpful AI assistant");
  165. await agent.GenerateReplyAsync([message]);
  166. }
  167. [Fact]
  168. public async Task ItProcessImageMessageAsync()
  169. {
  170. var middleware = new OpenAIChatRequestMessageConnector();
  171. var agent = new EchoAgent("assistant")
  172. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  173. {
  174. var innerMessage = msgs.Last();
  175. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  176. var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  177. chatRequestMessage.Content.Should().BeNullOrEmpty();
  178. chatRequestMessage.Name.Should().Be("user");
  179. chatRequestMessage.MultimodalContentItems.Count().Should().Be(1);
  180. chatRequestMessage.MultimodalContentItems.First().Should().BeOfType<ChatMessageImageContentItem>();
  181. return await innerAgent.GenerateReplyAsync(msgs);
  182. })
  183. .RegisterMiddleware(middleware);
  184. // user message
  185. IMessage message = new ImageMessage(Role.User, "https://example.com/image.png", "user");
  186. await agent.GenerateReplyAsync([message]);
  187. }
  188. [Fact]
  189. public async Task ItThrowExceptionWhenProcessingImageMessageFromSelfAndStrictModeIsTrueAsync()
  190. {
  191. var middleware = new OpenAIChatRequestMessageConnector(true);
  192. var agent = new EchoAgent("assistant")
  193. .RegisterMiddleware(middleware);
  194. var imageMessage = new ImageMessage(Role.Assistant, "https://example.com/image.png", "assistant");
  195. Func<Task> action = async () => await agent.GenerateReplyAsync([imageMessage]);
  196. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: ImageMessage");
  197. }
  198. [Fact]
  199. public async Task ItProcessMultiModalMessageAsync()
  200. {
  201. var middleware = new OpenAIChatRequestMessageConnector();
  202. var agent = new EchoAgent("assistant")
  203. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  204. {
  205. var innerMessage = msgs.Last();
  206. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  207. var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  208. chatRequestMessage.Content.Should().BeNullOrEmpty();
  209. chatRequestMessage.Name.Should().Be("user");
  210. chatRequestMessage.MultimodalContentItems.Count().Should().Be(2);
  211. chatRequestMessage.MultimodalContentItems.First().Should().BeOfType<ChatMessageTextContentItem>();
  212. chatRequestMessage.MultimodalContentItems.Last().Should().BeOfType<ChatMessageImageContentItem>();
  213. return await innerAgent.GenerateReplyAsync(msgs);
  214. })
  215. .RegisterMiddleware(middleware);
  216. // user message
  217. IMessage message = new MultiModalMessage(
  218. Role.User,
  219. [
  220. new TextMessage(Role.User, "Hello", "user"),
  221. new ImageMessage(Role.User, "https://example.com/image.png", "user"),
  222. ], "user");
  223. await agent.GenerateReplyAsync([message]);
  224. }
  225. [Fact]
  226. public async Task ItThrowExceptionWhenProcessingMultiModalMessageFromSelfAndStrictModeIsTrueAsync()
  227. {
  228. var middleware = new OpenAIChatRequestMessageConnector(true);
  229. var agent = new EchoAgent("assistant")
  230. .RegisterMiddleware(middleware);
  231. var multiModalMessage = new MultiModalMessage(
  232. Role.Assistant,
  233. [
  234. new TextMessage(Role.User, "Hello", "assistant"),
  235. new ImageMessage(Role.User, "https://example.com/image.png", "assistant"),
  236. ], "assistant");
  237. Func<Task> action = async () => await agent.GenerateReplyAsync([multiModalMessage]);
  238. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: MultiModalMessage");
  239. }
  240. [Fact]
  241. public async Task ItProcessToolCallMessageAsync()
  242. {
  243. var middleware = new OpenAIChatRequestMessageConnector();
  244. var agent = new EchoAgent("assistant")
  245. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  246. {
  247. var innerMessage = msgs.Last();
  248. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  249. var chatRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  250. // when the message is a tool call message
  251. // the name field should not be set
  252. // please visit OpenAIChatRequestMessageConnector class for more information
  253. chatRequestMessage.Name.Should().BeNullOrEmpty();
  254. chatRequestMessage.ToolCalls.Count().Should().Be(1);
  255. chatRequestMessage.Content.Should().Be("textContent");
  256. chatRequestMessage.ToolCalls.First().Should().BeOfType<ChatCompletionsFunctionToolCall>();
  257. var functionToolCall = (ChatCompletionsFunctionToolCall)chatRequestMessage.ToolCalls.First();
  258. functionToolCall.Name.Should().Be("test");
  259. functionToolCall.Id.Should().Be("test");
  260. functionToolCall.Arguments.Should().Be("test");
  261. return await innerAgent.GenerateReplyAsync(msgs);
  262. })
  263. .RegisterMiddleware(middleware);
  264. // user message
  265. IMessage message = new ToolCallMessage("test", "test", "assistant")
  266. {
  267. Content = "textContent",
  268. };
  269. await agent.GenerateReplyAsync([message]);
  270. }
  271. [Fact]
  272. public async Task ItProcessParallelToolCallMessageAsync()
  273. {
  274. var middleware = new OpenAIChatRequestMessageConnector();
  275. var agent = new EchoAgent("assistant")
  276. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  277. {
  278. var innerMessage = msgs.Last();
  279. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  280. var chatRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  281. chatRequestMessage.Content.Should().BeNullOrEmpty();
  282. // when the message is a tool call message
  283. // the name field should not be set
  284. // please visit OpenAIChatRequestMessageConnector class for more information
  285. chatRequestMessage.Name.Should().BeNullOrEmpty();
  286. chatRequestMessage.ToolCalls.Count().Should().Be(2);
  287. for (int i = 0; i < chatRequestMessage.ToolCalls.Count(); i++)
  288. {
  289. chatRequestMessage.ToolCalls.ElementAt(i).Should().BeOfType<ChatCompletionsFunctionToolCall>();
  290. var functionToolCall = (ChatCompletionsFunctionToolCall)chatRequestMessage.ToolCalls.ElementAt(i);
  291. functionToolCall.Name.Should().Be("test");
  292. functionToolCall.Id.Should().Be($"test_{i}");
  293. functionToolCall.Arguments.Should().Be("test");
  294. }
  295. return await innerAgent.GenerateReplyAsync(msgs);
  296. })
  297. .RegisterMiddleware(middleware);
  298. // user message
  299. var toolCalls = new[]
  300. {
  301. new ToolCall("test", "test"),
  302. new ToolCall("test", "test"),
  303. };
  304. IMessage message = new ToolCallMessage(toolCalls, "assistant");
  305. await agent.GenerateReplyAsync([message]);
  306. }
  307. [Fact]
  308. public async Task ItThrowExceptionWhenProcessingToolCallMessageFromUserAndStrictModeIsTrueAsync()
  309. {
  310. var middleware = new OpenAIChatRequestMessageConnector(strictMode: true);
  311. var agent = new EchoAgent("assistant")
  312. .RegisterMiddleware(middleware);
  313. var toolCallMessage = new ToolCallMessage("test", "test", "user");
  314. Func<Task> action = async () => await agent.GenerateReplyAsync([toolCallMessage]);
  315. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: ToolCallMessage");
  316. }
  317. [Fact]
  318. public async Task ItProcessToolCallResultMessageAsync()
  319. {
  320. var middleware = new OpenAIChatRequestMessageConnector();
  321. var agent = new EchoAgent("assistant")
  322. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  323. {
  324. var innerMessage = msgs.Last();
  325. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  326. var chatRequestMessage = (ChatRequestToolMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  327. chatRequestMessage.Content.Should().Be("result");
  328. chatRequestMessage.ToolCallId.Should().Be("test");
  329. return await innerAgent.GenerateReplyAsync(msgs);
  330. })
  331. .RegisterMiddleware(middleware);
  332. // user message
  333. IMessage message = new ToolCallResultMessage("result", "test", "test", "user");
  334. await agent.GenerateReplyAsync([message]);
  335. }
  336. [Fact]
  337. public async Task ItProcessParallelToolCallResultMessageAsync()
  338. {
  339. var middleware = new OpenAIChatRequestMessageConnector();
  340. var agent = new EchoAgent("assistant")
  341. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  342. {
  343. msgs.Count().Should().Be(2);
  344. for (int i = 0; i < msgs.Count(); i++)
  345. {
  346. var innerMessage = msgs.ElementAt(i);
  347. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  348. var chatRequestMessage = (ChatRequestToolMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  349. chatRequestMessage.Content.Should().Be("result");
  350. chatRequestMessage.ToolCallId.Should().Be($"test_{i}");
  351. }
  352. return await innerAgent.GenerateReplyAsync(msgs);
  353. })
  354. .RegisterMiddleware(middleware);
  355. // user message
  356. var toolCalls = new[]
  357. {
  358. new ToolCall("test", "test", "result"),
  359. new ToolCall("test", "test", "result"),
  360. };
  361. IMessage message = new ToolCallResultMessage(toolCalls, "user");
  362. await agent.GenerateReplyAsync([message]);
  363. }
  364. [Fact]
  365. public async Task ItProcessFunctionCallMiddlewareMessageFromUserAsync()
  366. {
  367. var middleware = new OpenAIChatRequestMessageConnector();
  368. var agent = new EchoAgent("assistant")
  369. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  370. {
  371. msgs.Count().Should().Be(1);
  372. var innerMessage = msgs.Last();
  373. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  374. var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  375. chatRequestMessage.Content.Should().Be("result");
  376. chatRequestMessage.Name.Should().Be("user");
  377. return await innerAgent.GenerateReplyAsync(msgs);
  378. })
  379. .RegisterMiddleware(middleware);
  380. // user message
  381. var toolCallMessage = new ToolCallMessage("test", "test", "user");
  382. var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "user");
  383. var aggregateMessage = new AggregateMessage<ToolCallMessage, ToolCallResultMessage>(toolCallMessage, toolCallResultMessage, "user");
  384. await agent.GenerateReplyAsync([aggregateMessage]);
  385. }
  386. [Fact]
  387. public async Task ItProcessFunctionCallMiddlewareMessageFromAssistantAsync()
  388. {
  389. var middleware = new OpenAIChatRequestMessageConnector();
  390. var agent = new EchoAgent("assistant")
  391. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  392. {
  393. msgs.Count().Should().Be(2);
  394. var innerMessage = msgs.Last();
  395. innerMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  396. var chatRequestMessage = (ChatRequestToolMessage)((MessageEnvelope<ChatRequestMessage>)innerMessage!).Content;
  397. chatRequestMessage.Content.Should().Be("result");
  398. chatRequestMessage.ToolCallId.Should().Be("test");
  399. var toolCallMessage = msgs.First();
  400. toolCallMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  401. var toolCallRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope<ChatRequestMessage>)toolCallMessage!).Content;
  402. toolCallRequestMessage.Content.Should().BeNullOrEmpty();
  403. toolCallRequestMessage.ToolCalls.Count().Should().Be(1);
  404. toolCallRequestMessage.ToolCalls.First().Should().BeOfType<ChatCompletionsFunctionToolCall>();
  405. var functionToolCall = (ChatCompletionsFunctionToolCall)toolCallRequestMessage.ToolCalls.First();
  406. functionToolCall.Name.Should().Be("test");
  407. functionToolCall.Id.Should().Be("test");
  408. functionToolCall.Arguments.Should().Be("test");
  409. return await innerAgent.GenerateReplyAsync(msgs);
  410. })
  411. .RegisterMiddleware(middleware);
  412. // user message
  413. var toolCallMessage = new ToolCallMessage("test", "test", "assistant");
  414. var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "assistant");
  415. var aggregateMessage = new ToolCallAggregateMessage(toolCallMessage, toolCallResultMessage, "assistant");
  416. await agent.GenerateReplyAsync([aggregateMessage]);
  417. }
  418. [Fact]
  419. public async Task ItProcessParallelFunctionCallMiddlewareMessageFromAssistantAsync()
  420. {
  421. var middleware = new OpenAIChatRequestMessageConnector();
  422. var agent = new EchoAgent("assistant")
  423. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  424. {
  425. msgs.Count().Should().Be(3);
  426. var toolCallMessage = msgs.First();
  427. toolCallMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  428. var toolCallRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope<ChatRequestMessage>)toolCallMessage!).Content;
  429. toolCallRequestMessage.Content.Should().BeNullOrEmpty();
  430. toolCallRequestMessage.ToolCalls.Count().Should().Be(2);
  431. for (int i = 0; i < toolCallRequestMessage.ToolCalls.Count(); i++)
  432. {
  433. toolCallRequestMessage.ToolCalls.ElementAt(i).Should().BeOfType<ChatCompletionsFunctionToolCall>();
  434. var functionToolCall = (ChatCompletionsFunctionToolCall)toolCallRequestMessage.ToolCalls.ElementAt(i);
  435. functionToolCall.Name.Should().Be("test");
  436. functionToolCall.Id.Should().Be($"test_{i}");
  437. functionToolCall.Arguments.Should().Be("test");
  438. }
  439. for (int i = 1; i < msgs.Count(); i++)
  440. {
  441. var toolCallResultMessage = msgs.ElementAt(i);
  442. toolCallResultMessage!.Should().BeOfType<MessageEnvelope<ChatRequestMessage>>();
  443. var toolCallResultRequestMessage = (ChatRequestToolMessage)((MessageEnvelope<ChatRequestMessage>)toolCallResultMessage!).Content;
  444. toolCallResultRequestMessage.Content.Should().Be("result");
  445. toolCallResultRequestMessage.ToolCallId.Should().Be($"test_{i - 1}");
  446. }
  447. return await innerAgent.GenerateReplyAsync(msgs);
  448. })
  449. .RegisterMiddleware(middleware);
  450. // user message
  451. var toolCalls = new[]
  452. {
  453. new ToolCall("test", "test", "result"),
  454. new ToolCall("test", "test", "result"),
  455. };
  456. var toolCallMessage = new ToolCallMessage(toolCalls, "assistant");
  457. var toolCallResultMessage = new ToolCallResultMessage(toolCalls, "assistant");
  458. var aggregateMessage = new AggregateMessage<ToolCallMessage, ToolCallResultMessage>(toolCallMessage, toolCallResultMessage, "assistant");
  459. await agent.GenerateReplyAsync([aggregateMessage]);
  460. }
  461. [Fact]
  462. public async Task ItConvertChatResponseMessageToTextMessageAsync()
  463. {
  464. var middleware = new OpenAIChatRequestMessageConnector();
  465. var agent = new EchoAgent("assistant")
  466. .RegisterMiddleware(middleware);
  467. // text message
  468. var textMessage = CreateInstance<ChatResponseMessage>(ChatRole.Assistant, "hello");
  469. var chatRequestMessage = MessageEnvelope.Create(textMessage);
  470. var message = await agent.GenerateReplyAsync([chatRequestMessage]);
  471. message.Should().BeOfType<TextMessage>();
  472. message.GetContent().Should().Be("hello");
  473. message.GetRole().Should().Be(Role.Assistant);
  474. }
  475. [Fact]
  476. public async Task ItConvertChatResponseMessageToToolCallMessageAsync()
  477. {
  478. var middleware = new OpenAIChatRequestMessageConnector();
  479. var agent = new EchoAgent("assistant")
  480. .RegisterMiddleware(middleware);
  481. // tool call message
  482. var toolCallMessage = CreateInstance<ChatResponseMessage>(ChatRole.Assistant, "textContent", new[] { new ChatCompletionsFunctionToolCall("test", "test", "test") }, new FunctionCall("test", "test"), CreateInstance<AzureChatExtensionsMessageContext>(), new Dictionary<string, BinaryData>());
  483. var chatRequestMessage = MessageEnvelope.Create(toolCallMessage);
  484. var message = await agent.GenerateReplyAsync([chatRequestMessage]);
  485. message.Should().BeOfType<ToolCallMessage>();
  486. message.GetToolCalls()!.Count().Should().Be(1);
  487. message.GetToolCalls()!.First().FunctionName.Should().Be("test");
  488. message.GetToolCalls()!.First().FunctionArguments.Should().Be("test");
  489. message.GetContent().Should().Be("textContent");
  490. }
  491. [Fact]
  492. public async Task ItReturnOriginalMessageWhenStrictModeIsFalseAsync()
  493. {
  494. var middleware = new OpenAIChatRequestMessageConnector();
  495. var agent = new EchoAgent("assistant")
  496. .RegisterMiddleware(middleware);
  497. // text message
  498. var textMessage = "hello";
  499. var messageToSend = MessageEnvelope.Create(textMessage);
  500. var message = await agent.GenerateReplyAsync([messageToSend]);
  501. message.Should().BeOfType<MessageEnvelope<string>>();
  502. }
  503. [Fact]
  504. public async Task ItThrowInvalidOperationExceptionWhenStrictModeIsTrueAsync()
  505. {
  506. var middleware = new OpenAIChatRequestMessageConnector(true);
  507. var agent = new EchoAgent("assistant")
  508. .RegisterMiddleware(middleware);
  509. // text message
  510. var textMessage = new ChatRequestUserMessage("hello");
  511. var messageToSend = MessageEnvelope.Create(textMessage);
  512. Func<Task> action = async () => await agent.GenerateReplyAsync([messageToSend]);
  513. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid return message type MessageEnvelope`1");
  514. }
  515. [Fact]
  516. public void ToOpenAIChatRequestMessageShortCircuitTest()
  517. {
  518. var agent = new EchoAgent("assistant");
  519. var middleware = new OpenAIChatRequestMessageConnector();
  520. ChatRequestMessage[] messages =
  521. [
  522. new ChatRequestUserMessage("Hello"),
  523. new ChatRequestAssistantMessage("How can I help you?"),
  524. new ChatRequestSystemMessage("You are a helpful AI assistant"),
  525. new ChatRequestFunctionMessage("result", "functionName"),
  526. new ChatRequestToolMessage("test", "test"),
  527. ];
  528. foreach (var oaiMessage in messages)
  529. {
  530. IMessage message = new MessageEnvelope<ChatRequestMessage>(oaiMessage);
  531. var oaiMessages = middleware.ProcessIncomingMessages(agent, [message]);
  532. oaiMessages.Count().Should().Be(1);
  533. //oaiMessages.First().Should().BeOfType<IMessage<ChatRequestMessage>>();
  534. if (oaiMessages.First() is IMessage<ChatRequestMessage> chatRequestMessage)
  535. {
  536. chatRequestMessage.Content.Should().Be(oaiMessage);
  537. }
  538. else
  539. {
  540. // fail the test
  541. Assert.True(false);
  542. }
  543. }
  544. }
  545. private void VerifyOAIMessages(IEnumerable<(IMessage, IEnumerable<IMessage>)> messages)
  546. {
  547. var jsonObjects = messages.Select(pair =>
  548. {
  549. var (originalMessage, ms) = pair;
  550. var objs = new List<object>();
  551. foreach (var m in ms)
  552. {
  553. object? obj = null;
  554. var chatRequestMessage = (m as IMessage<ChatRequestMessage>)?.Content;
  555. if (chatRequestMessage is ChatRequestUserMessage userMessage)
  556. {
  557. obj = new
  558. {
  559. Role = userMessage.Role.ToString(),
  560. Content = userMessage.Content,
  561. Name = userMessage.Name,
  562. MultiModaItem = userMessage.MultimodalContentItems?.Select(item =>
  563. {
  564. return item switch
  565. {
  566. ChatMessageImageContentItem imageContentItem => new
  567. {
  568. Type = "Image",
  569. ImageUrl = GetImageUrlFromContent(imageContentItem),
  570. } as object,
  571. ChatMessageTextContentItem textContentItem => new
  572. {
  573. Type = "Text",
  574. Text = textContentItem.Text,
  575. } as object,
  576. _ => throw new System.NotImplementedException(),
  577. };
  578. }),
  579. };
  580. }
  581. if (chatRequestMessage is ChatRequestAssistantMessage assistantMessage)
  582. {
  583. obj = new
  584. {
  585. Role = assistantMessage.Role.ToString(),
  586. Content = assistantMessage.Content,
  587. Name = assistantMessage.Name,
  588. TooCall = assistantMessage.ToolCalls.Select(tc =>
  589. {
  590. return tc switch
  591. {
  592. ChatCompletionsFunctionToolCall functionToolCall => new
  593. {
  594. Type = "Function",
  595. Name = functionToolCall.Name,
  596. Arguments = functionToolCall.Arguments,
  597. Id = functionToolCall.Id,
  598. } as object,
  599. _ => throw new System.NotImplementedException(),
  600. };
  601. }),
  602. FunctionCallName = assistantMessage.FunctionCall?.Name,
  603. FunctionCallArguments = assistantMessage.FunctionCall?.Arguments,
  604. };
  605. }
  606. if (chatRequestMessage is ChatRequestSystemMessage systemMessage)
  607. {
  608. obj = new
  609. {
  610. Name = systemMessage.Name,
  611. Role = systemMessage.Role.ToString(),
  612. Content = systemMessage.Content,
  613. };
  614. }
  615. if (chatRequestMessage is ChatRequestFunctionMessage functionMessage)
  616. {
  617. obj = new
  618. {
  619. Role = functionMessage.Role.ToString(),
  620. Content = functionMessage.Content,
  621. Name = functionMessage.Name,
  622. };
  623. }
  624. if (chatRequestMessage is ChatRequestToolMessage toolCallMessage)
  625. {
  626. obj = new
  627. {
  628. Role = toolCallMessage.Role.ToString(),
  629. Content = toolCallMessage.Content,
  630. ToolCallId = toolCallMessage.ToolCallId,
  631. };
  632. }
  633. objs.Add(obj ?? throw new System.NotImplementedException());
  634. }
  635. return new
  636. {
  637. OriginalMessage = originalMessage.ToString(),
  638. ConvertedMessages = objs,
  639. };
  640. });
  641. var json = JsonSerializer.Serialize(jsonObjects, this.jsonSerializerOptions);
  642. Approvals.Verify(json);
  643. }
  644. private object? GetImageUrlFromContent(ChatMessageImageContentItem content)
  645. {
  646. return content.GetType().GetProperty("ImageUrl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(content);
  647. }
  648. private static T CreateInstance<T>(params object[] args)
  649. {
  650. var type = typeof(T);
  651. var instance = type.Assembly.CreateInstance(
  652. type.FullName!, false,
  653. BindingFlags.Instance | BindingFlags.NonPublic,
  654. null, args, null, null);
  655. return (T)instance!;
  656. }
  657. }