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 31 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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 FluentAssertions;
  14. using OpenAI.Chat;
  15. using Xunit;
  16. namespace AutoGen.OpenAI.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<ChatMessage>>();
  70. var chatRequestMessage = (UserChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  71. chatRequestMessage.Content.First().Text.Should().Be("Hello");
  72. chatRequestMessage.ParticipantName.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<UserChatMessage>>();
  89. var chatRequestMessage = (UserChatMessage)((MessageEnvelope<UserChatMessage>)innerMessage!).Content;
  90. chatRequestMessage.Content.First().Text.Should().Be("hello");
  91. return await innerAgent.GenerateReplyAsync(msgs);
  92. })
  93. .RegisterMiddleware(middleware);
  94. // user message
  95. var userMessage = new UserChatMessage("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<ChatMessage>>();
  139. var chatRequestMessage = (AssistantChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  140. chatRequestMessage.Content.First().Text.Should().Be("How can I help you?");
  141. chatRequestMessage.ParticipantName.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<ChatMessage>>();
  158. var chatRequestMessage = (SystemChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  159. chatRequestMessage.Content.First().Text.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<ChatMessage>>();
  176. var chatRequestMessage = (UserChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  177. chatRequestMessage.ParticipantName.Should().Be("user");
  178. chatRequestMessage.Content.Count().Should().Be(1);
  179. chatRequestMessage.Content.First().Kind.Should().Be(ChatMessageContentPartKind.Image);
  180. return await innerAgent.GenerateReplyAsync(msgs);
  181. })
  182. .RegisterMiddleware(middleware);
  183. // user message
  184. IMessage message = new ImageMessage(Role.User, "https://example.com/image.png", "user");
  185. await agent.GenerateReplyAsync([message]);
  186. }
  187. [Fact]
  188. public async Task ItThrowExceptionWhenProcessingImageMessageFromSelfAndStrictModeIsTrueAsync()
  189. {
  190. var middleware = new OpenAIChatRequestMessageConnector(true);
  191. var agent = new EchoAgent("assistant")
  192. .RegisterMiddleware(middleware);
  193. var imageMessage = new ImageMessage(Role.Assistant, "https://example.com/image.png", "assistant");
  194. Func<Task> action = async () => await agent.GenerateReplyAsync([imageMessage]);
  195. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: ImageMessage");
  196. }
  197. [Fact]
  198. public async Task ItProcessMultiModalMessageAsync()
  199. {
  200. var middleware = new OpenAIChatRequestMessageConnector();
  201. var agent = new EchoAgent("assistant")
  202. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  203. {
  204. var innerMessage = msgs.Last();
  205. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  206. var chatRequestMessage = (UserChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  207. chatRequestMessage.ParticipantName.Should().Be("user");
  208. chatRequestMessage.Content.Count().Should().Be(2);
  209. chatRequestMessage.Content.First().Kind.Should().Be(ChatMessageContentPartKind.Text);
  210. chatRequestMessage.Content.Last().Kind.Should().Be(ChatMessageContentPartKind.Image);
  211. return await innerAgent.GenerateReplyAsync(msgs);
  212. })
  213. .RegisterMiddleware(middleware);
  214. // user message
  215. IMessage message = new MultiModalMessage(
  216. Role.User,
  217. [
  218. new TextMessage(Role.User, "Hello", "user"),
  219. new ImageMessage(Role.User, "https://example.com/image.png", "user"),
  220. ], "user");
  221. await agent.GenerateReplyAsync([message]);
  222. }
  223. [Fact]
  224. public async Task ItThrowExceptionWhenProcessingMultiModalMessageFromSelfAndStrictModeIsTrueAsync()
  225. {
  226. var middleware = new OpenAIChatRequestMessageConnector(true);
  227. var agent = new EchoAgent("assistant")
  228. .RegisterMiddleware(middleware);
  229. var multiModalMessage = new MultiModalMessage(
  230. Role.Assistant,
  231. [
  232. new TextMessage(Role.User, "Hello", "assistant"),
  233. new ImageMessage(Role.User, "https://example.com/image.png", "assistant"),
  234. ], "assistant");
  235. Func<Task> action = async () => await agent.GenerateReplyAsync([multiModalMessage]);
  236. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: MultiModalMessage");
  237. }
  238. [Fact]
  239. public async Task ItProcessToolCallMessageAsync()
  240. {
  241. var middleware = new OpenAIChatRequestMessageConnector();
  242. var agent = new EchoAgent("assistant")
  243. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  244. {
  245. var innerMessage = msgs.Last();
  246. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  247. var chatRequestMessage = (AssistantChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  248. // when the message is a tool call message
  249. // the name field should not be set
  250. // please visit OpenAIChatRequestMessageConnector class for more information
  251. chatRequestMessage.ParticipantName.Should().BeNullOrEmpty();
  252. chatRequestMessage.ToolCalls.Count().Should().Be(1);
  253. chatRequestMessage.Content.First().Text.Should().Be("textContent");
  254. chatRequestMessage.ToolCalls.First().Should().BeOfType<ChatToolCall>();
  255. var functionToolCall = (ChatToolCall)chatRequestMessage.ToolCalls.First();
  256. functionToolCall.FunctionName.Should().Be("test");
  257. functionToolCall.Id.Should().Be("test");
  258. functionToolCall.FunctionArguments.ToString().Should().Be("test");
  259. return await innerAgent.GenerateReplyAsync(msgs);
  260. })
  261. .RegisterMiddleware(middleware);
  262. // user message
  263. IMessage message = new ToolCallMessage("test", "test", "assistant")
  264. {
  265. Content = "textContent",
  266. };
  267. await agent.GenerateReplyAsync([message]);
  268. }
  269. [Fact]
  270. public async Task ItProcessParallelToolCallMessageAsync()
  271. {
  272. var middleware = new OpenAIChatRequestMessageConnector();
  273. var agent = new EchoAgent("assistant")
  274. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  275. {
  276. var innerMessage = msgs.Last();
  277. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  278. var chatRequestMessage = (AssistantChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  279. chatRequestMessage.Content.Should().BeNullOrEmpty();
  280. // when the message is a tool call message
  281. // the name field should not be set
  282. // please visit OpenAIChatRequestMessageConnector class for more information
  283. chatRequestMessage.ParticipantName.Should().BeNullOrEmpty();
  284. chatRequestMessage.ToolCalls.Count().Should().Be(2);
  285. for (int i = 0; i < chatRequestMessage.ToolCalls.Count(); i++)
  286. {
  287. chatRequestMessage.ToolCalls.ElementAt(i).Should().BeOfType<ChatToolCall>();
  288. var functionToolCall = (ChatToolCall)chatRequestMessage.ToolCalls.ElementAt(i);
  289. functionToolCall.FunctionName.Should().Be("test");
  290. functionToolCall.Id.Should().Be($"test_{i}");
  291. functionToolCall.FunctionArguments.ToString().Should().Be("test");
  292. }
  293. return await innerAgent.GenerateReplyAsync(msgs);
  294. })
  295. .RegisterMiddleware(middleware);
  296. // user message
  297. var toolCalls = new[]
  298. {
  299. new ToolCall("test", "test"),
  300. new ToolCall("test", "test"),
  301. };
  302. IMessage message = new ToolCallMessage(toolCalls, "assistant");
  303. await agent.GenerateReplyAsync([message]);
  304. }
  305. [Fact]
  306. public async Task ItThrowExceptionWhenProcessingToolCallMessageFromUserAndStrictModeIsTrueAsync()
  307. {
  308. var middleware = new OpenAIChatRequestMessageConnector(strictMode: true);
  309. var agent = new EchoAgent("assistant")
  310. .RegisterMiddleware(middleware);
  311. var toolCallMessage = new ToolCallMessage("test", "test", "user");
  312. Func<Task> action = async () => await agent.GenerateReplyAsync([toolCallMessage]);
  313. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid message type: ToolCallMessage");
  314. }
  315. [Fact]
  316. public async Task ItProcessToolCallResultMessageAsync()
  317. {
  318. var middleware = new OpenAIChatRequestMessageConnector();
  319. var agent = new EchoAgent("assistant")
  320. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  321. {
  322. var innerMessage = msgs.Last();
  323. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  324. var chatRequestMessage = (ToolChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  325. chatRequestMessage.Content.First().Text.Should().Be("result");
  326. chatRequestMessage.ToolCallId.Should().Be("test");
  327. return await innerAgent.GenerateReplyAsync(msgs);
  328. })
  329. .RegisterMiddleware(middleware);
  330. // user message
  331. IMessage message = new ToolCallResultMessage("result", "test", "test", "user");
  332. await agent.GenerateReplyAsync([message]);
  333. }
  334. [Fact]
  335. public async Task ItProcessParallelToolCallResultMessageAsync()
  336. {
  337. var middleware = new OpenAIChatRequestMessageConnector();
  338. var agent = new EchoAgent("assistant")
  339. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  340. {
  341. msgs.Count().Should().Be(2);
  342. for (int i = 0; i < msgs.Count(); i++)
  343. {
  344. var innerMessage = msgs.ElementAt(i);
  345. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  346. var chatRequestMessage = (ToolChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  347. chatRequestMessage.Content.First().Text.Should().Be("result");
  348. chatRequestMessage.ToolCallId.Should().Be($"test_{i}");
  349. }
  350. return await innerAgent.GenerateReplyAsync(msgs);
  351. })
  352. .RegisterMiddleware(middleware);
  353. // user message
  354. var toolCalls = new[]
  355. {
  356. new ToolCall("test", "test", "result"),
  357. new ToolCall("test", "test", "result"),
  358. };
  359. IMessage message = new ToolCallResultMessage(toolCalls, "user");
  360. await agent.GenerateReplyAsync([message]);
  361. }
  362. [Fact]
  363. public async Task ItProcessFunctionCallMiddlewareMessageFromUserAsync()
  364. {
  365. var middleware = new OpenAIChatRequestMessageConnector();
  366. var agent = new EchoAgent("assistant")
  367. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  368. {
  369. msgs.Count().Should().Be(1);
  370. var innerMessage = msgs.Last();
  371. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  372. var chatRequestMessage = (UserChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  373. chatRequestMessage.Content.First().Text.Should().Be("result");
  374. chatRequestMessage.ParticipantName.Should().Be("user");
  375. return await innerAgent.GenerateReplyAsync(msgs);
  376. })
  377. .RegisterMiddleware(middleware);
  378. // user message
  379. var toolCallMessage = new ToolCallMessage("test", "test", "user");
  380. var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "user");
  381. var aggregateMessage = new AggregateMessage<ToolCallMessage, ToolCallResultMessage>(toolCallMessage, toolCallResultMessage, "user");
  382. await agent.GenerateReplyAsync([aggregateMessage]);
  383. }
  384. [Fact]
  385. public async Task ItProcessFunctionCallMiddlewareMessageFromAssistantAsync()
  386. {
  387. var middleware = new OpenAIChatRequestMessageConnector();
  388. var agent = new EchoAgent("assistant")
  389. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  390. {
  391. msgs.Count().Should().Be(2);
  392. var innerMessage = msgs.Last();
  393. innerMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  394. var chatRequestMessage = (ToolChatMessage)((MessageEnvelope<ChatMessage>)innerMessage!).Content;
  395. chatRequestMessage.Content.First().Text.Should().Be("result");
  396. chatRequestMessage.ToolCallId.Should().Be("test");
  397. var toolCallMessage = msgs.First();
  398. toolCallMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  399. var toolCallRequestMessage = (AssistantChatMessage)((MessageEnvelope<ChatMessage>)toolCallMessage!).Content;
  400. toolCallRequestMessage.Content.Should().BeNullOrEmpty();
  401. toolCallRequestMessage.ToolCalls.Count().Should().Be(1);
  402. toolCallRequestMessage.ToolCalls.First().Should().BeOfType<ChatToolCall>();
  403. var functionToolCall = (ChatToolCall)toolCallRequestMessage.ToolCalls.First();
  404. functionToolCall.FunctionName.Should().Be("test");
  405. functionToolCall.Id.Should().Be("test");
  406. functionToolCall.FunctionArguments.ToString().Should().Be("test");
  407. return await innerAgent.GenerateReplyAsync(msgs);
  408. })
  409. .RegisterMiddleware(middleware);
  410. // user message
  411. var toolCallMessage = new ToolCallMessage("test", "test", "assistant");
  412. var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "assistant");
  413. var aggregateMessage = new ToolCallAggregateMessage(toolCallMessage, toolCallResultMessage, "assistant");
  414. await agent.GenerateReplyAsync([aggregateMessage]);
  415. }
  416. [Fact]
  417. public async Task ItProcessParallelFunctionCallMiddlewareMessageFromAssistantAsync()
  418. {
  419. var middleware = new OpenAIChatRequestMessageConnector();
  420. var agent = new EchoAgent("assistant")
  421. .RegisterMiddleware(async (msgs, _, innerAgent, _) =>
  422. {
  423. msgs.Count().Should().Be(3);
  424. var toolCallMessage = msgs.First();
  425. toolCallMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  426. var toolCallRequestMessage = (AssistantChatMessage)((MessageEnvelope<ChatMessage>)toolCallMessage!).Content;
  427. toolCallRequestMessage.Content.Should().BeNullOrEmpty();
  428. toolCallRequestMessage.ToolCalls.Count().Should().Be(2);
  429. for (int i = 0; i < toolCallRequestMessage.ToolCalls.Count(); i++)
  430. {
  431. toolCallRequestMessage.ToolCalls.ElementAt(i).Should().BeOfType<ChatToolCall>();
  432. var functionToolCall = (ChatToolCall)toolCallRequestMessage.ToolCalls.ElementAt(i);
  433. functionToolCall.FunctionName.Should().Be("test");
  434. functionToolCall.Id.Should().Be($"test_{i}");
  435. functionToolCall.FunctionArguments.ToString().Should().Be("test");
  436. }
  437. for (int i = 1; i < msgs.Count(); i++)
  438. {
  439. var toolCallResultMessage = msgs.ElementAt(i);
  440. toolCallResultMessage!.Should().BeOfType<MessageEnvelope<ChatMessage>>();
  441. var toolCallResultRequestMessage = (ToolChatMessage)((MessageEnvelope<ChatMessage>)toolCallResultMessage!).Content;
  442. toolCallResultRequestMessage.Content.First().Text.Should().Be("result");
  443. toolCallResultRequestMessage.ToolCallId.Should().Be($"test_{i - 1}");
  444. }
  445. return await innerAgent.GenerateReplyAsync(msgs);
  446. })
  447. .RegisterMiddleware(middleware);
  448. // user message
  449. var toolCalls = new[]
  450. {
  451. new ToolCall("test", "test", "result"),
  452. new ToolCall("test", "test", "result"),
  453. };
  454. var toolCallMessage = new ToolCallMessage(toolCalls, "assistant");
  455. var toolCallResultMessage = new ToolCallResultMessage(toolCalls, "assistant");
  456. var aggregateMessage = new AggregateMessage<ToolCallMessage, ToolCallResultMessage>(toolCallMessage, toolCallResultMessage, "assistant");
  457. await agent.GenerateReplyAsync([aggregateMessage]);
  458. }
  459. [Fact]
  460. public async Task ItReturnOriginalMessageWhenStrictModeIsFalseAsync()
  461. {
  462. var middleware = new OpenAIChatRequestMessageConnector();
  463. var agent = new EchoAgent("assistant")
  464. .RegisterMiddleware(middleware);
  465. // text message
  466. var textMessage = "hello";
  467. var messageToSend = MessageEnvelope.Create(textMessage);
  468. var message = await agent.GenerateReplyAsync([messageToSend]);
  469. message.Should().BeOfType<MessageEnvelope<string>>();
  470. }
  471. [Fact]
  472. public async Task ItThrowInvalidOperationExceptionWhenStrictModeIsTrueAsync()
  473. {
  474. var middleware = new OpenAIChatRequestMessageConnector(true);
  475. var agent = new EchoAgent("assistant")
  476. .RegisterMiddleware(middleware);
  477. // text message
  478. var textMessage = new UserChatMessage("hello");
  479. var messageToSend = MessageEnvelope.Create(textMessage);
  480. Func<Task> action = async () => await agent.GenerateReplyAsync([messageToSend]);
  481. await action.Should().ThrowAsync<InvalidOperationException>().WithMessage("Invalid return message type MessageEnvelope`1");
  482. }
  483. [Fact]
  484. public void ToOpenAIChatRequestMessageShortCircuitTest()
  485. {
  486. var agent = new EchoAgent("assistant");
  487. var middleware = new OpenAIChatRequestMessageConnector();
  488. #pragma warning disable CS0618 // Type or member is obsolete
  489. ChatMessage[] messages =
  490. [
  491. new UserChatMessage("Hello"),
  492. new AssistantChatMessage("How can I help you?"),
  493. new SystemChatMessage("You are a helpful AI assistant"),
  494. new FunctionChatMessage("functionName", "result"),
  495. new ToolChatMessage("test", "test"),
  496. ];
  497. #pragma warning restore CS0618 // Type or member is obsolete
  498. foreach (var oaiMessage in messages)
  499. {
  500. IMessage message = new MessageEnvelope<ChatMessage>(oaiMessage);
  501. var oaiMessages = middleware.ProcessIncomingMessages(agent, [message]);
  502. oaiMessages.Count().Should().Be(1);
  503. //oaiMessages.First().Should().BeOfType<IMessage<ChatRequestMessage>>();
  504. if (oaiMessages.First() is IMessage<ChatMessage> chatRequestMessage)
  505. {
  506. chatRequestMessage.Content.Should().Be(oaiMessage);
  507. }
  508. else
  509. {
  510. // fail the test
  511. Assert.True(false);
  512. }
  513. }
  514. }
  515. private void VerifyOAIMessages(IEnumerable<(IMessage, IEnumerable<IMessage>)> messages)
  516. {
  517. var jsonObjects = messages.Select(pair =>
  518. {
  519. var (originalMessage, ms) = pair;
  520. var objs = new List<object>();
  521. foreach (var m in ms)
  522. {
  523. object? obj = null;
  524. var chatRequestMessage = (m as IMessage<ChatMessage>)?.Content;
  525. if (chatRequestMessage is UserChatMessage userMessage)
  526. {
  527. obj = new
  528. {
  529. Role = "user",
  530. Content = userMessage.Content,
  531. Name = userMessage.ParticipantName,
  532. MultiModaItem = userMessage.Content?.Select(item =>
  533. {
  534. return item switch
  535. {
  536. _ when item.Kind == ChatMessageContentPartKind.Image => new
  537. {
  538. Type = "Image",
  539. ImageUrl = GetImageUrlFromContent(item),
  540. } as object,
  541. _ when item.Kind == ChatMessageContentPartKind.Text => new
  542. {
  543. Type = "Text",
  544. Text = item.Text,
  545. } as object,
  546. _ => throw new System.NotImplementedException(),
  547. };
  548. }),
  549. };
  550. }
  551. if (chatRequestMessage is AssistantChatMessage assistantMessage)
  552. {
  553. obj = new
  554. {
  555. Role = "assistant",
  556. Content = assistantMessage.Content,
  557. Name = assistantMessage.ParticipantName,
  558. TooCall = assistantMessage.ToolCalls.Select(tc =>
  559. {
  560. return tc switch
  561. {
  562. ChatToolCall functionToolCall => new
  563. {
  564. Type = "Function",
  565. Name = functionToolCall.FunctionName,
  566. Arguments = functionToolCall.FunctionArguments,
  567. Id = functionToolCall.Id,
  568. } as object,
  569. _ => throw new System.NotImplementedException(),
  570. };
  571. }),
  572. };
  573. }
  574. if (chatRequestMessage is SystemChatMessage systemMessage)
  575. {
  576. obj = new
  577. {
  578. Name = systemMessage.ParticipantName,
  579. Role = "system",
  580. Content = systemMessage.Content,
  581. };
  582. }
  583. #pragma warning disable CS0618 // Type or member is obsolete
  584. if (chatRequestMessage is FunctionChatMessage functionMessage)
  585. {
  586. obj = new
  587. {
  588. Role = "function",
  589. Content = functionMessage.Content,
  590. Name = functionMessage.FunctionName,
  591. };
  592. }
  593. #pragma warning restore CS0618 // Type or member is obsolete
  594. if (chatRequestMessage is ToolChatMessage toolCallMessage)
  595. {
  596. obj = new
  597. {
  598. Role = "tool",
  599. Content = toolCallMessage.Content.First().Text,
  600. ToolCallId = toolCallMessage.ToolCallId,
  601. };
  602. }
  603. objs.Add(obj ?? throw new System.NotImplementedException());
  604. }
  605. return new
  606. {
  607. OriginalMessage = originalMessage.ToString(),
  608. ConvertedMessages = objs,
  609. };
  610. });
  611. var json = JsonSerializer.Serialize(jsonObjects, this.jsonSerializerOptions);
  612. Approvals.Verify(json);
  613. }
  614. private object? GetImageUrlFromContent(ChatMessageContentPart content)
  615. {
  616. return content.ImageUri;
  617. }
  618. private static T CreateInstance<T>(params object[] args)
  619. {
  620. var type = typeof(T);
  621. var instance = type.Assembly.CreateInstance(
  622. type.FullName!, false,
  623. BindingFlags.Instance | BindingFlags.NonPublic,
  624. null, args, null, null);
  625. return (T)instance!;
  626. }
  627. }