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
6 changes: 5 additions & 1 deletion src/Cellm/AddIn/CellmAddInConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Cellm.AddIn;
using Cellm.Models.Prompts;

namespace Cellm.AddIn;

public class CellmAddInConfiguration
{
Expand All @@ -19,4 +21,6 @@ public class CellmAddInConfiguration
public int CacheTimeoutInSeconds { get; init; } = 3600;

public List<string> Models { get; init; } = [];

public StructuredOutputShape StructuredOutputShape { get; init; } = StructuredOutputShape.Table;
}
9 changes: 9 additions & 0 deletions src/Cellm/AddIn/CellmFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ internal static async Task<object> GetResponseAsync(Arguments arguments, Stopwat
.SetModel(arguments.Model)
.SetTemperature(arguments.Temperature)
.SetMaxOutputTokens(cellmAddInConfiguration.CurrentValue.MaxOutputTokens)
.SetOutputShape(cellmAddInConfiguration.CurrentValue.StructuredOutputShape)
.AddSystemMessage(SystemMessages.SystemMessage)
.AddUserMessage(userMessage)
.Build();
Expand All @@ -172,8 +173,16 @@ internal static async Task<object> GetResponseAsync(Arguments arguments, Stopwat
var response = await client.GetResponseAsync(prompt, arguments.Provider, cancellationToken).ConfigureAwait(false);
var assistantMessage = response.Messages.LastOrDefault()?.Text ?? throw new InvalidOperationException("No text response");

// Check for cancellation before returning response
cancellationToken.ThrowIfCancellationRequested();

logger.LogInformation("Sending prompt to {}/{} ({}) ... Done (elapsed time: {}ms, request time: {}ms)", arguments.Provider, arguments.Model, callerCoordinates, wallClock.ElapsedMilliseconds, requestClock.ElapsedMilliseconds);

if (StructuredOutput.TryParse(assistantMessage, response.OutputShape, out var array2d) && array2d is not null)
{
return array2d;
}

return assistantMessage;
}
// Short-circuit if any cells were found to be #GETTING_DATA or contain other errors during cell parsing.
Expand Down
119 changes: 117 additions & 2 deletions src/Cellm/AddIn/UserInterface/Ribbon/RibbonModelGroup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text;
using Cellm.AddIn.UserInterface.Forms;
using Cellm.Models.Prompts;
using Cellm.Models.Providers;
using Cellm.Models.Providers.Anthropic;
using Cellm.Models.Providers.Aws;
Expand All @@ -25,6 +26,7 @@ public partial class RibbonMain
private enum ModelGroupControlIds
{
VerticalContainer,
HorizontalContainer,

ModelProviderGroup,
ProviderModelBox,
Expand All @@ -34,6 +36,12 @@ private enum ModelGroupControlIds

ModelComboBox,
TemperatureComboBox,

OutputCell,
OutputRow,
OutputTable,
OutputColumn,

CacheToggleButton,

ProviderSettingsButton
Expand Down Expand Up @@ -155,11 +163,41 @@ public string ModelGroup()
getItemLabel="{nameof(GetTemperatureItemLabel)}"
/>
</box>
<box id="{nameof(ModelGroupControlIds.HorizontalContainer)}" boxStyle="horizontal">
<buttonGroup id="SelectionButtonGroup">
<toggleButton id="{nameof(ModelGroupControlIds.OutputCell)}"
imageMso="TableSelectCell"
getPressed="{nameof(GetOutputCellPressed)}"
onAction="{nameof(OnOutputCellClicked)}"
screentip="Output response in a single cell (default)" />
<toggleButton id="{nameof(ModelGroupControlIds.OutputRow)}"
imageMso="TableRowSelect"
getPressed="{nameof(GetOutputRowPressed)}"
onAction="{nameof(OnOutputRowClicked)}"
screentip="Respond with row"
supertip="Spill multiple response values (if any) across cells to the right." />
<toggleButton id="{nameof(ModelGroupControlIds.OutputTable)}"
imageMso="TableSelect"
getPressed="{nameof(GetOutputTablePressed)}"
onAction="{nameof(OnOutputTableClicked)}"
screentip="Respond with table"
supertip="Let model decide how to output multiple values (as single cell, row, column, or table, just tell it what you want)" />
<toggleButton id="{nameof(ModelGroupControlIds.OutputColumn)}"
imageMso="TableColumnSelect"
getPressed="{nameof(GetOutputColumnPressed)}"
onAction="{nameof(OnOutputColumnClicked)}"
screentip="Respond with column"
supertip="Spill multiple response values (if any) across cells below" />
</buttonGroup>
</box>
</box>
<separator id="cacheSeparator" />
<toggleButton id="{nameof(ModelGroupControlIds.CacheToggleButton)}" label="Cache" size="large" imageMso="SourceControlRefreshStatus"
<toggleButton id="{nameof(ModelGroupControlIds.CacheToggleButton)}"
label="Memory On" size="large"
imageMso="SourceControlRefreshStatus"
screentip="Enable/disable local caching of model responses. Enabled: Return cached responses for identical prompts. Disabled: Always request new responses. Disabling cache will clear entries."
onAction="{nameof(OnCacheToggled)}" getPressed="{nameof(GetCachePressed)}" />
onAction="{nameof(OnCacheToggled)}"
getPressed="{nameof(GetCachePressed)}" />
</group>
""";
}
Expand Down Expand Up @@ -809,4 +847,81 @@ public string GetTemperatureItemLabel(IRibbonControl control, int index)
_logger.LogWarning("Invalid index {index} requested for GetTemperatureItemLabel", index);
return string.Empty;
}

public void OnOutputCellClicked(IRibbonControl control, bool isPressed)
{
// Default, cannot toggle off via this button
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.None.ToString());
InvalidateOutputToggleButtons();
}

public void OnOutputRowClicked(IRibbonControl control, bool isPressed)
{
if (isPressed)
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.Row.ToString());
InvalidateOutputToggleButtons();
}
else
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.None.ToString());
InvalidateOutputToggleButtons();
}
}

public void OnOutputTableClicked(IRibbonControl control, bool isPressed)
{
if (isPressed)
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.Table.ToString());
InvalidateOutputToggleButtons();
}
else
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.None.ToString());
InvalidateOutputToggleButtons();
}
}

public void OnOutputColumnClicked(IRibbonControl control, bool isPressed)
{
if (isPressed)
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.Column.ToString());
InvalidateOutputToggleButtons();
}
else
{
SetValue($"{nameof(CellmAddInConfiguration)}:{nameof(StructuredOutputShape)}", StructuredOutputShape.None.ToString());
InvalidateOutputToggleButtons();
}
}

public bool GetOutputCellPressed(IRibbonControl control)
{
return GetValue($"{nameof(CellmAddInConfiguration)}:{nameof(CellmAddInConfiguration.StructuredOutputShape)}") == StructuredOutputShape.None.ToString();
}

public bool GetOutputRowPressed(IRibbonControl control)
{
return GetValue($"{nameof(CellmAddInConfiguration)}:{nameof(CellmAddInConfiguration.StructuredOutputShape)}") == StructuredOutputShape.Row.ToString();
}

public bool GetOutputTablePressed(IRibbonControl control)
{
return GetValue($"{nameof(CellmAddInConfiguration)}:{nameof(CellmAddInConfiguration.StructuredOutputShape)}") == StructuredOutputShape.Table.ToString();
}

public bool GetOutputColumnPressed(IRibbonControl control)
{
return GetValue($"{nameof(CellmAddInConfiguration)}:{nameof(CellmAddInConfiguration.StructuredOutputShape)}") == StructuredOutputShape.Column.ToString();
}

private void InvalidateOutputToggleButtons()
{
_ribbonUi?.InvalidateControl(nameof(ModelGroupControlIds.OutputCell));
_ribbonUi?.InvalidateControl(nameof(ModelGroupControlIds.OutputRow));
_ribbonUi?.InvalidateControl(nameof(ModelGroupControlIds.OutputTable));
_ribbonUi?.InvalidateControl(nameof(ModelGroupControlIds.OutputColumn));
}
}
1 change: 0 additions & 1 deletion src/Cellm/Cellm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.5" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.56.0-alpha" />
<PackageReference Include="Mistral.SDK" Version="2.2.0" />
<PackageReference Include="ModelContextProtocol" Version="0.2.0-preview.3" />
<PackageReference Include="OllamaSharp" Version="5.2.2" />
Expand Down
2 changes: 1 addition & 1 deletion src/Cellm/Models/Prompts/Prompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace Cellm.Models.Prompts;

public record Prompt(IList<ChatMessage> Messages, ChatOptions Options);
internal record Prompt(IList<ChatMessage> Messages, ChatOptions Options, StructuredOutputShape OutputShape);
36 changes: 22 additions & 14 deletions src/Cellm/Models/Prompts/PromptBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,86 @@

namespace Cellm.Models.Prompts;

public class PromptBuilder
internal class PromptBuilder
{
private readonly List<ChatMessage> _messages = [];
private readonly ChatOptions _options = new();
private StructuredOutputShape _outputShape = StructuredOutputShape.None;

public PromptBuilder()
internal PromptBuilder()
{
}

public PromptBuilder(Prompt prompt)
internal PromptBuilder(Prompt prompt)
{
// Do not mutate prompt
_messages = new List<ChatMessage>(prompt.Messages);
_options = prompt.Options.Clone();
_outputShape = prompt.OutputShape;
}

public PromptBuilder SetModel(string model)
internal PromptBuilder SetModel(string model)
{
_options.ModelId = model;
return this;
}

public PromptBuilder SetTemperature(double temperature)
internal PromptBuilder SetTemperature(double temperature)
{
_options.Temperature = (float)temperature;
return this;
}

public PromptBuilder SetMaxOutputTokens(int maxOutputTokens)
internal PromptBuilder SetMaxOutputTokens(int maxOutputTokens)
{
_options.MaxOutputTokens = maxOutputTokens;
return this;
}

public PromptBuilder AddSystemMessage(string content)
internal PromptBuilder SetOutputShape(StructuredOutputShape outputShape)
{
_outputShape = outputShape;
return this;
}

internal PromptBuilder AddSystemMessage(string content)
{
_messages.Add(new ChatMessage(ChatRole.System, content));
return this;
}

public PromptBuilder AddUserMessage(string content)
internal PromptBuilder AddUserMessage(string content)
{
_messages.Add(new ChatMessage(ChatRole.User, content));
return this;
}

public PromptBuilder AddAssistantMessage(string content)
internal PromptBuilder AddAssistantMessage(string content)
{
_messages.Add(new ChatMessage(ChatRole.User, content));
return this;
}

public PromptBuilder AddMessage(ChatMessage message)
internal PromptBuilder AddMessage(ChatMessage message)
{
_messages.Add(message);
return this;
}

public PromptBuilder AddMessages(IList<ChatMessage> messages)
internal PromptBuilder AddMessages(IList<ChatMessage> messages)
{
_messages.AddRange(messages);
return this;
}

public PromptBuilder SetTools(IList<AITool> tools)
internal PromptBuilder SetTools(IList<AITool> tools)
{
_options.Tools = tools;
return this;
}

public Prompt Build()
internal Prompt Build()
{
return new Prompt(_messages, _options);
return new Prompt(_messages, _options, _outputShape);
}
}
Loading
Loading