From de53b98e15af1b85f7dd448b5347b1f060fb4ca1 Mon Sep 17 00:00:00 2001 From: Krzysztof Cwalina Date: Wed, 6 Nov 2024 11:39:23 -0800 Subject: [PATCH] updates for demo (#46997) * updates for demo * PR feedback * updated api file * changed from global to local memory.data 8.0 * fixed bugs * more bug fixes --- .../api/Azure.CloudMachine.netstandard2.0.cs | 41 ++--- .../src/Azure.CloudMachine.csproj | 1 + .../src/CoreServices/MessagingServices.cs | 17 +- .../src/CoreServices/StorageFile.cs | 13 +- .../src/CoreServices/StorageServices.cs | 153 ++++++++++++------ .../src/extensions/AzureOpenAIExtensions.cs | 23 +++ .../src/extensions/ChatTools.cs | 2 +- .../src/extensions/EmbeddingsVectorbase.cs | 13 +- .../src/extensions/MemoryVectorbaseStore.cs | 2 +- .../src/extensions/VectorbaseStore.cs | 2 +- .../tests/CloudMachineTests.cs | 15 +- 11 files changed, 193 insertions(+), 89 deletions(-) diff --git a/sdk/cloudmachine/Azure.CloudMachine/api/Azure.CloudMachine.netstandard2.0.cs b/sdk/cloudmachine/Azure.CloudMachine/api/Azure.CloudMachine.netstandard2.0.cs index 493ce7a3ef2ca..733b707ca1d38 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/api/Azure.CloudMachine.netstandard2.0.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/api/Azure.CloudMachine.netstandard2.0.cs @@ -26,7 +26,8 @@ public readonly partial struct MessagingServices { private readonly object _dummy; private readonly int _dummyPrimitive; - public void SendMessage(object serializable) { } + public void SendJson(object serializable) { } + public System.Threading.Tasks.Task SendJsonAsync(object serializable) { throw null; } public void WhenMessageReceived(System.Action received) { } } public partial class StorageFile @@ -36,6 +37,7 @@ internal StorageFile() { } public string Path { get { throw null; } } public string RequestId { get { throw null; } } public void Delete() { } + public System.Threading.Tasks.Task DeleteAsync() { throw null; } public System.BinaryData Download() { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public override bool Equals(object obj) { throw null; } @@ -50,14 +52,18 @@ public readonly partial struct StorageServices { private readonly object _dummy; private readonly int _dummyPrimitive; - public void DeleteBlob(string path) { } - public System.BinaryData DownloadBlob(string path) { throw null; } - public string UploadBinaryData(System.BinaryData data, string name = null, bool overwrite = false) { throw null; } - public string UploadBytes(byte[] bytes, string name = null, bool overwrite = false) { throw null; } - public string UploadBytes(System.ReadOnlyMemory bytes, string name = null, bool overwrite = false) { throw null; } + public void Delete(string path) { } + public System.Threading.Tasks.Task DeleteAsync(string path) { throw null; } + public System.BinaryData Download(string path) { throw null; } + public System.Threading.Tasks.Task DownloadAsync(string path) { throw null; } + public string Upload(System.BinaryData data, string name = null, bool overwrite = false) { throw null; } + public string Upload(System.IO.Stream fileStream, string name = null, string contentType = null, bool overwrite = false) { throw null; } + public System.Threading.Tasks.Task UploadAsync(System.BinaryData data, string name = null, bool overwrite = false) { throw null; } + public System.Threading.Tasks.Task UploadAsync(System.IO.Stream fileStream, string name = null, string contentType = null, bool overwrite = false) { throw null; } public string UploadJson(object json, string name = null, bool overwrite = false) { throw null; } - public string UploadStream(System.IO.Stream fileStream, string name = null, bool overwrite = false) { throw null; } - public void WhenBlobUploaded(System.Action function) { } + public System.Threading.Tasks.Task UploadJsonAsync(object json, string name = null, bool overwrite = false) { throw null; } + public void WhenUploaded(System.Action function) { } + public void WhenUploaded(System.Action function) { } } } namespace Azure.CloudMachine.KeyVault @@ -71,12 +77,11 @@ namespace Azure.CloudMachine.OpenAI { public static partial class AzureOpenAIExtensions { + public static void Add(this System.Collections.Generic.List messages, System.Collections.Generic.IEnumerable entries) { } public static OpenAI.Chat.ChatClient GetOpenAIChatClient(this Azure.Core.ClientWorkspace workspace) { throw null; } public static OpenAI.Embeddings.EmbeddingClient GetOpenAIEmbeddingsClient(this Azure.Core.ClientWorkspace workspace) { throw null; } + public static void Trim(this System.Collections.Generic.List messages) { } } -} -namespace Azure.CloudMachine.OpenAI.Chat -{ public partial class ChatTools { public ChatTools(params System.Type[] tools) { } @@ -92,14 +97,12 @@ public void Add(System.Type functions) { } protected virtual string GetMethodInfoToName(System.Reflection.MethodInfo function) { throw null; } protected virtual string GetParameterInfoToDescription(System.Reflection.ParameterInfo parameter) { throw null; } } -} -namespace Azure.CloudMachine.OpenAI.Embeddings -{ public partial class EmbeddingsVectorbase { - public EmbeddingsVectorbase(OpenAI.Embeddings.EmbeddingClient client, Azure.CloudMachine.OpenAI.Embeddings.VectorbaseStore store = null, int factChunkSize = 0) { } + public EmbeddingsVectorbase(OpenAI.Embeddings.EmbeddingClient client, Azure.CloudMachine.OpenAI.VectorbaseStore store = null, int factChunkSize = 0) { } + public void Add(System.BinaryData data) { } public void Add(string text) { } - public System.Collections.Generic.IEnumerable Find(string text, Azure.CloudMachine.OpenAI.Embeddings.FindOptions options = null) { throw null; } + public System.Collections.Generic.IEnumerable Find(string text, Azure.CloudMachine.OpenAI.FindOptions options = null) { throw null; } } public partial class FindOptions { @@ -120,10 +123,10 @@ public readonly partial struct VectorbaseEntry public abstract partial class VectorbaseStore { protected VectorbaseStore() { } - public abstract int Add(Azure.CloudMachine.OpenAI.Embeddings.VectorbaseEntry entry); - public abstract void Add(System.Collections.Generic.IReadOnlyList entry); + public abstract int Add(Azure.CloudMachine.OpenAI.VectorbaseEntry entry); + public abstract void Add(System.Collections.Generic.IReadOnlyList entry); public static float CosineSimilarity(System.ReadOnlySpan x, System.ReadOnlySpan y) { throw null; } - public abstract System.Collections.Generic.IEnumerable Find(System.ReadOnlyMemory vector, Azure.CloudMachine.OpenAI.Embeddings.FindOptions options); + public abstract System.Collections.Generic.IEnumerable Find(System.ReadOnlyMemory vector, Azure.CloudMachine.OpenAI.FindOptions options); } } namespace Azure.Core diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/Azure.CloudMachine.csproj b/sdk/cloudmachine/Azure.CloudMachine/src/Azure.CloudMachine.csproj index 59d3be831d9ec..5b22c0e7c7fd7 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/Azure.CloudMachine.csproj +++ b/sdk/cloudmachine/Azure.CloudMachine/src/Azure.CloudMachine.csproj @@ -21,6 +21,7 @@ + diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/MessagingServices.cs b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/MessagingServices.cs index 237080a5c77da..7b2b146da7421 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/MessagingServices.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/MessagingServices.cs @@ -20,15 +20,24 @@ public readonly struct MessagingServices /// Sends a message to the service bus. /// /// - public void SendMessage(object serializable) + public void SendJson(object serializable) + { +#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). + SendJsonAsync(serializable).GetAwaiter().GetResult(); +#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + } + + /// + /// Sends a message to the service bus. + /// + /// + public async Task SendJsonAsync(object serializable) { ServiceBusSender sender = GetServiceBusSender(); BinaryData serialized = BinaryData.FromObjectAsJson(serializable); ServiceBusMessage message = new(serialized); -#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). - sender.SendMessageAsync(message).GetAwaiter().GetResult(); -#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). + await sender.SendMessageAsync(message).ConfigureAwait(false); } /// diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageFile.cs b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageFile.cs index 9e16977e22a32..e3c27ee57c986 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageFile.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageFile.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; using System.Threading; +using System.Threading.Tasks; namespace Azure.CloudMachine; @@ -43,7 +44,7 @@ public class StorageFile /// /// public BinaryData Download() - => _storage.DownloadBlob(Path); + => _storage.Download(Path); // public async Task DownloadAsync() // => await _storage.DownloadBlobAsync(Path).ConfigureAwait(false); @@ -52,10 +53,14 @@ public BinaryData Download() /// Deletes the file from the storage account. /// public void Delete() - => _storage.DeleteBlob(Path); + => _storage.Delete(Path); - // public async Task DeleteAsync() - // => await _storage.DeleteBlobAsync(Path).ConfigureAwait(false); + /// + /// Deletes the file from the storage account. + /// + /// + public async Task DeleteAsync() + => await _storage.DeleteAsync(Path).ConfigureAwait(false); // public Uri ShareFolder(AccessPermissions permissions, TimeSpan expiresAfter) // => _storage.ShareFolder(Path, permissions, expiresAfter); diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageServices.cs b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageServices.cs index 35f2766581e6e..06aa0df639a1a 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageServices.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/CoreServices/StorageServices.cs @@ -11,6 +11,9 @@ using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Azure.Messaging.ServiceBus; +using System.Net.Mime; +using ContentType = Azure.Core.ContentType; +using System.Collections.Generic; namespace Azure.CloudMachine; @@ -57,20 +60,21 @@ private BlobContainerClient GetContainer(string containerName) /// public string UploadJson(object json, string name = default, bool overwrite = false) { - BlobContainerClient container = GetDefaultContainer(); - - if (name == default) - name = $"b{Guid.NewGuid()}"; - - var client = container.GetBlockBlobClient(name); - var options = new BlobUploadOptions - { - Conditions = overwrite ? null : new BlobRequestConditions { IfNoneMatch = new ETag("*") }, - HttpHeaders = new BlobHttpHeaders { ContentType = ContentType.ApplicationJson.ToString() } - }; + BinaryData data = BinaryData.FromObjectAsJson(json); + return Upload(data, name, overwrite); + } - client.Upload(BinaryData.FromObjectAsJson(json).ToStream(), options); - return name; + /// + /// Uploads a JSON object to the storage account. + /// + /// + /// + /// + /// + public async Task UploadJsonAsync(object json, string name = default, bool overwrite = false) + { + BinaryData data = BinaryData.FromObjectAsJson(json); + return await UploadAsync(data, name, overwrite).ConfigureAwait(false); } /// @@ -78,92 +82,130 @@ public string UploadJson(object json, string name = default, bool overwrite = fa /// /// /// + /// /// /// - public string UploadStream(Stream fileStream, string name = default, bool overwrite = false) + public string Upload(Stream fileStream, string name = default, string contentType = default, bool overwrite = false) { - BlobContainerClient container = GetDefaultContainer(); - - if (name == default) - name = $"b{Guid.NewGuid()}"; - - var client = container.GetBlockBlobClient(name); - var options = new BlobUploadOptions - { - Conditions = overwrite ? null : new BlobRequestConditions { IfNoneMatch = new ETag("*") }, - HttpHeaders = new BlobHttpHeaders { ContentType = ContentType.ApplicationOctetStream.ToString() } - }; + BlockBlobClient client = GetBlobClient(ref name); + BlobUploadOptions options = CreateUploadOptions(overwrite, contentType); client.Upload(fileStream, options); return name; } /// - /// Uploads a binary data object to the storage account. + /// Uploads a file to the storage account. /// - /// + /// /// + /// /// /// - public string UploadBinaryData(BinaryData data, string name = default, bool overwrite = false) + public async Task UploadAsync(Stream fileStream, string name = default, string contentType = default, bool overwrite = false) + { + BlockBlobClient client = GetBlobClient(ref name); + BlobUploadOptions options = CreateUploadOptions(overwrite, contentType); + + await client.UploadAsync(fileStream, options).ConfigureAwait(false); + return name; + } + + private BlockBlobClient GetBlobClient(ref string name) { BlobContainerClient container = GetDefaultContainer(); - if (name == default) - name = $"b{Guid.NewGuid()}"; + if (name == default) name = $"b{Guid.NewGuid()}"; + BlockBlobClient client = container.GetBlockBlobClient(name); + return client; + } - var client = container.GetBlockBlobClient(name); - var options = new BlobUploadOptions + private BlobUploadOptions CreateUploadOptions(bool overwrite, string contentType) + { + if (contentType == null) contentType = ContentType.ApplicationOctetStream.ToString(); + BlobUploadOptions options = new() { Conditions = overwrite ? null : new BlobRequestConditions { IfNoneMatch = new ETag("*") }, - HttpHeaders = new BlobHttpHeaders { ContentType = ContentType.ApplicationOctetStream.ToString() } + HttpHeaders = new BlobHttpHeaders { ContentType = contentType }, }; - - client.Upload(data.ToStream(), options); - return name; + return options; } /// - /// Uploads a byte array to the storage account. + /// Uploads a binary data object to the storage account. /// - /// + /// /// /// /// - public string UploadBytes(byte[] bytes, string name = default, bool overwrite = false) - => UploadBinaryData(BinaryData.FromBytes(bytes), name, overwrite); + public string Upload(BinaryData data, string name = default, bool overwrite = false) + => Upload(data.ToStream(), name, data.MediaType, overwrite); /// - /// Uploads a byte array to the storage account. + /// Uploads a binary data object to the storage account. /// - /// + /// /// /// /// - public string UploadBytes(ReadOnlyMemory bytes, string name = default, bool overwrite = false) - => UploadBinaryData(BinaryData.FromBytes(bytes), name, overwrite); + public async Task UploadAsync(BinaryData data, string name = default, bool overwrite = false) + => await UploadAsync(data.ToStream(), name, data.MediaType, overwrite).ConfigureAwait(false); /// /// Uploads a file to the storage account. /// /// /// - public BinaryData DownloadBlob(string path) + public BinaryData Download(string path) { - BlobClient blob = GetBlobClientFromPath(path, null); + BlobClient blob = GetBlobClientFromPath(path, containerName: default); BlobDownloadResult result = blob.DownloadContent(); - return result.Content; + BinaryData content = result.Content; + + string contentType = result.Details.ContentType; + if (contentType != default) + content = content.WithMediaType(contentType); + + return content; + } + + /// + /// Uploads a file to the storage account. + /// + /// + /// + public async Task DownloadAsync(string path) + { + BlobClient blob = GetBlobClientFromPath(path, containerName: default); + BlobDownloadResult result = await blob.DownloadContentAsync().ConfigureAwait(false); + BinaryData content = result.Content; + + string contentType = result.Details.ContentType; + if (contentType!=default) content = content.WithMediaType(contentType); + + return content; } /// /// Deletes a blob from the storage account. /// /// - public void DeleteBlob(string path) + public void Delete(string path) { BlobClient blob = GetBlobClientFromPath(path, null); blob.DeleteIfExists(); } + /// + /// Deletes a blob from the storage account. + /// + /// + /// + public async Task DeleteAsync(string path) + { + BlobClient blob = GetBlobClientFromPath(path, null); + await blob.DeleteIfExistsAsync().ConfigureAwait(false); + } + private BlobClient GetBlobClientFromPath(string path, string containerName) { var _blobContainer = GetDefaultContainer(); @@ -198,7 +240,7 @@ private static string ConvertPathToBlobPath(string path, BlobContainerClient con /// Adds a function to be called when a blob is uploaded. /// /// - public void WhenBlobUploaded(Action function) + public void WhenUploaded(Action function) { CloudMachineClient cm = _cm; // TODO (Pri 0): once the cache gets GCed, we will stop receiving events @@ -231,4 +273,17 @@ public void WhenBlobUploaded(Action function) #pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). } + + /// + /// Adds a function to be called when a blob is uploaded. + /// + /// + public void WhenUploaded(Action function) + { + WhenUploaded((StorageFile file) => + { + BinaryData data = file.Download(); + function(data); + }); + } } diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/AzureOpenAIExtensions.cs b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/AzureOpenAIExtensions.cs index 1e813457dba9d..a764cbc41ac4f 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/AzureOpenAIExtensions.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/AzureOpenAIExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.ClientModel; +using System.Collections.Generic; using Azure.AI.OpenAI; using Azure.Core; using OpenAI.Chat; @@ -72,4 +73,26 @@ private static EmbeddingClient CreateEmbeddingsClient(this ClientWorkspace works EmbeddingClient embeddings = client.GetEmbeddingClient(connection.Id); return embeddings; } + + /// + /// Trims list of chat messages. + /// + /// + public static void Trim(this List messages) + { + messages.RemoveRange(0, messages.Count / 2); + } + + /// + /// Adds a list of vectorbase entries to the list of chat messages. + /// + /// + /// + public static void Add(this List messages, IEnumerable entries) + { + foreach (VectorbaseEntry entry in entries) + { + messages.Add(ChatMessage.CreateSystemMessage(entry.Data.ToString())); + } + } } diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/ChatTools.cs b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/ChatTools.cs index 7a298fd9bb299..a5cd50372c312 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/ChatTools.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/ChatTools.cs @@ -10,7 +10,7 @@ using System.Text.Json; using OpenAI.Chat; -namespace Azure.CloudMachine.OpenAI.Chat; +namespace Azure.CloudMachine.OpenAI; /// The service client for the OpenAI Chat Completions endpoint. public class ChatTools diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/EmbeddingsVectorbase.cs b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/EmbeddingsVectorbase.cs index 8fbc3855de2dc..5bd2c2225b274 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/EmbeddingsVectorbase.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/EmbeddingsVectorbase.cs @@ -5,7 +5,7 @@ using System; using OpenAI.Embeddings; -namespace Azure.CloudMachine.OpenAI.Embeddings; +namespace Azure.CloudMachine.OpenAI; /// /// The vectorbase for storing embeddings. @@ -42,6 +42,17 @@ public void Add(string text) } } + /// + /// Adds an entry to the vectorbase. The media type must be "text/plain". + /// + /// + /// + public void Add(BinaryData data) + { + if (data.MediaType != "text/plain") throw new InvalidOperationException("Only text/plain media type is supported."); + Add(data.ToString()); + } + /// /// Finds entries in the vectorbase. /// diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/MemoryVectorbaseStore.cs b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/MemoryVectorbaseStore.cs index 4c03dc9bd8827..e0e0428766fd8 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/MemoryVectorbaseStore.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/MemoryVectorbaseStore.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System; -namespace Azure.CloudMachine.OpenAI.Embeddings; +namespace Azure.CloudMachine.OpenAI; internal class MemoryVectorbaseStore : VectorbaseStore { diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/VectorbaseStore.cs b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/VectorbaseStore.cs index bd5d58cd79120..6944fe2cf76fb 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/src/extensions/VectorbaseStore.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/src/extensions/VectorbaseStore.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System; -namespace Azure.CloudMachine.OpenAI.Embeddings; +namespace Azure.CloudMachine.OpenAI; /// /// The base class for a vectorbase store. diff --git a/sdk/cloudmachine/Azure.CloudMachine/tests/CloudMachineTests.cs b/sdk/cloudmachine/Azure.CloudMachine/tests/CloudMachineTests.cs index c70d714105703..881efc8d0e5e7 100644 --- a/sdk/cloudmachine/Azure.CloudMachine/tests/CloudMachineTests.cs +++ b/sdk/cloudmachine/Azure.CloudMachine/tests/CloudMachineTests.cs @@ -16,11 +16,8 @@ using Microsoft.Extensions.Configuration; using System.Collections.Generic; using Microsoft.Extensions.Primitives; -using OpenAI.Embeddings; using System.Linq; using System.IO; -using Azure.CloudMachine.OpenAI.Chat; -using Azure.CloudMachine.OpenAI.Embeddings; namespace Azure.CloudMachine.Tests; @@ -61,7 +58,7 @@ public void Storage(string[] args) CloudMachineClient cm = new(); - cm.Storage.WhenBlobUploaded((StorageFile file) => + cm.Storage.WhenUploaded((StorageFile file) => { var data = file.Download(); Console.WriteLine(data.ToString()); @@ -73,7 +70,7 @@ public void Storage(string[] args) Foo = 5, Bar = true }); - BinaryData downloaded = cm.Storage.DownloadBlob(uploaded); + BinaryData downloaded = cm.Storage.Download(uploaded); Console.WriteLine(downloaded.ToString()); eventSlim.Wait(); } @@ -136,7 +133,7 @@ public void Messaging(string[] args) Console.WriteLine(message); Assert.True(message != null); }); - cm.Messaging.SendMessage(new + cm.Messaging.SendJson(new { Foo = 5, Bar = true @@ -155,8 +152,8 @@ public void Demo(string[] args) CloudMachineClient cm = new(); // setup - cm.Messaging.WhenMessageReceived((string message) => cm.Storage.UploadBinaryData(BinaryData.FromString(message))); - cm.Storage.WhenBlobUploaded((StorageFile file) => + cm.Messaging.WhenMessageReceived((string message) => cm.Storage.Upload(BinaryData.FromString(message))); + cm.Storage.WhenUploaded((StorageFile file) => { var content = file.Download(); ChatCompletion completion = cm.GetOpenAIChatClient().CompleteChat(content.ToString()); @@ -164,7 +161,7 @@ public void Demo(string[] args) }); // go! - cm.Messaging.SendMessage("Tell me something about Redmond, WA."); + cm.Messaging.SendJson("Tell me something about Redmond, WA."); } [Ignore("no recordings yet")]