Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions src/Cellm/AddIn/CellmFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static object PromptWith(
[ExcelArgument(AllowReference = true, Name = "InstructionsOrTemperature", Description = "A cell or range of cells with instructions or a temperature")] object instructionsOrTemperature,
[ExcelArgument(Name = "Temperature", Description = "Temperature")] object temperature)
{
// Short-circuit if any of the inputs is #GETTING_DATA. This function will be re-triggered when inputs are updated with realized values.
// Short-circuit if any inputs are #GETTING_DATA. This function will be re-triggered when inputs are updated with realized values.
if (IsCellReferenceGettingData(providerAndModel) ||
IsCellReferenceGettingData(instructionsOrContext) ||
IsCellReferenceGettingData(instructionsOrTemperature))
Expand All @@ -84,6 +84,7 @@ public static object PromptWith(
var argumentParser = CellmAddIn.Services.GetRequiredService<ArgumentParser>();
var providerConfiguration = CellmAddIn.Services.GetRequiredService<IOptionsMonitor<ProviderConfiguration>>();

// We must parse arguments on the main thread
var arguments = argumentParser
.AddProvider(providerAndModel)
.AddModel(providerAndModel)
Expand All @@ -92,23 +93,11 @@ public static object PromptWith(
.AddTemperature(temperature)
.Parse();

var userMessage = new StringBuilder()
.AppendLine(arguments.Instructions)
.AppendLine(arguments.Context)
.ToString();

var prompt = new PromptBuilder()
.SetModel(arguments.Model)
.SetTemperature(arguments.Temperature)
.SetMaxOutputTokens(providerConfiguration.CurrentValue.MaxOutputTokens)
.AddSystemMessage(SystemMessages.SystemMessage)
.AddUserMessage(userMessage)
.Build();

// ObserveResponse will send request on another thread and update the cell value on the main thread
return ExcelAsyncUtil.Observe(
nameof(PromptWith),
new object[] { providerAndModel, instructionsOrContext, instructionsOrTemperature, temperature },
() => new ObserveResponse(prompt, arguments.Provider));
() => new ObserveResponse(arguments));
}
catch (CellmException ex)
{
Expand Down
56 changes: 45 additions & 11 deletions src/Cellm/AddIn/ObserveResponse.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using Cellm.Models;
using System.Text;
using Cellm.Models;
using Cellm.Models.Prompts;
using Cellm.Models.Providers;
using ExcelDna.Integration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Cellm.AddIn;

internal class ObserveResponse(Prompt prompt, Provider provider) : IExcelObservable
internal class ObserveResponse(Arguments arguments) : IExcelObservable
{
private IExcelObserver? _observer;
private Task? _task;
Expand Down Expand Up @@ -37,33 +39,65 @@ public IDisposable Subscribe(IExcelObserver observer)
{
try
{
var userMessage = new StringBuilder()
.AppendLine(arguments.Instructions)
.AppendLine(arguments.Context)
.ToString();

var providerConfiguration = CellmAddIn.Services.GetRequiredService<IOptionsMonitor<ProviderConfiguration>>();

var prompt = new PromptBuilder()
.SetModel(arguments.Model)
.SetTemperature(arguments.Temperature)
.SetMaxOutputTokens(providerConfiguration.CurrentValue.MaxOutputTokens)
.AddSystemMessage(SystemMessages.SystemMessage)
.AddUserMessage(userMessage)
.Build();

// Check for cancellation before sending request
_cancellationTokenSource.Token.ThrowIfCancellationRequested();

var client = CellmAddIn.Services.GetRequiredService<Client>();
var response = await client.GetResponseAsync(prompt, provider, _cancellationTokenSource.Token);
var response = await client.GetResponseAsync(prompt, arguments.Provider, _cancellationTokenSource.Token);
var assistantMessage = response.Messages.LastOrDefault()?.Text ?? throw new InvalidOperationException("No text response");

// Check for cancellation before notifying observer
_cancellationTokenSource.Token.ThrowIfCancellationRequested();

_observer.OnNext(assistantMessage);
_observer.OnCompleted();
// Notify observer on the main thread
ExcelAsyncUtil.QueueAsMacro(() =>
{
_observer?.OnNext(assistantMessage);
_observer?.OnCompleted();
});

_logger.LogDebug("Getting response ... Done");
_logger.LogDebug("Getting response {id} ... Done", _task?.Id);
}
catch (OperationCanceledException ex)
{
_logger.LogDebug(ex, "Getting response ... Cancelled");
_observer.OnError(ex);
// Notify observer on the main thread
ExcelAsyncUtil.QueueAsMacro(() =>
{
_observer.OnError(ex);
});

_logger.LogDebug(ex, "Getting response {id} ... Cancelled", _task?.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Getting response ... Failed: {message}", ex.Message);
observer.OnError(ex);
// Notify observer on the main thread
ExcelAsyncUtil.QueueAsMacro(() =>
{
observer.OnError(ex);
});

_logger.LogError(ex, "Getting response {id} ... Failed: {message}", _task?.Id, ex.Message);
}
}, _cancellationTokenSource.Token);

return new ActionDisposable(() =>
{
_logger.LogDebug("Getting response ... Disposing");
_logger.LogDebug("Getting response {id} ... Disposing ({status})", _task.Id, _task.IsCompleted ? "done" : "cancelled");
_cancellationTokenSource.Cancel();
});
}
Expand Down