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
5 changes: 3 additions & 2 deletions DevProxy.Abstractions/DevProxy.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Markdig" Version="0.41.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.6.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.24" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageReference Include="Prompty.Core" Version="0.2.2-beta" />
<PackageReference Include="Scriban" Version="6.2.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
<PackageReference Include="Unobtanium.Web.Proxy" Version="0.1.5" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>

</Project>
55 changes: 55 additions & 0 deletions DevProxy.Abstractions/Extensions/MarkdownExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// from: https://khalidabuhakmeh.com/parse-markdown-front-matter-with-csharp

using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

#pragma warning disable IDE0130
namespace System;
#pragma warning restore IDE0130

public static class MarkdownExtensions
{
private static readonly IDeserializer YamlDeserializer =
new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();

private static readonly MarkdownPipeline Pipeline
= new MarkdownPipelineBuilder()
.UseYamlFrontMatter()
.Build();

public static (TFrontmatter? frontmatter, string? content) ParseMarkdown<TFrontmatter>(this string markdown) where TFrontmatter : new()
{
var document = Markdown.Parse(markdown, Pipeline);
var block = document
.Descendants<YamlFrontMatterBlock>()
.FirstOrDefault();

if (block == null)
{
return (default, markdown);
}

var yaml =
block
// this is not a mistake
// we have to call .Lines 2x
.Lines // StringLineGroup[]
.Lines // StringLine[]
.OrderByDescending(x => x.Line)
.Select(x => $"{x}\n")
.ToList()
.Select(x => x.Replace("---", string.Empty, StringComparison.Ordinal))
.Where(x => !string.IsNullOrWhiteSpace(x))
.Aggregate((s, agg) => agg + s);

var t = YamlDeserializer.Deserialize<TFrontmatter>(yaml);
var content = markdown[(block.Span.End + 1)..];
return (t, content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Abstractions.Prompty;
using DevProxy.Abstractions.Utils;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using PromptyCore = Prompty.Core;
using System.Collections.Concurrent;

namespace DevProxy.Abstractions.LanguageModel;
Expand Down Expand Up @@ -105,7 +104,7 @@ public async Task<bool> IsEnabledAsync(CancellationToken cancellationToken)
Logger.LogDebug("Loading prompt file: {FilePath}", filePath);
var promptContents = File.ReadAllText(filePath);

var prompty = PromptyCore.Prompty.Load(promptContents, []);
var prompty = Prompt.FromMarkdown(promptContents);
if (prompty.Prepare(parameters) is not ChatMessage[] promptyMessages ||
promptyMessages.Length == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Prompty.Core;

namespace DevProxy.Abstractions.LanguageModel;

Expand All @@ -18,8 +17,6 @@ public static ILanguageModelClient Create(IServiceProvider serviceProvider, ICon
var lmSection = configuration.GetSection("LanguageModel");
var config = lmSection?.Get<LanguageModelConfiguration>() ?? new();

InvokerFactory.AutoDiscovery();

return config.Client switch
{
LanguageModelClient.Ollama => ActivatorUtilities.CreateInstance<OllamaLanguageModelClient>(serviceProvider, config),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

using System.Diagnostics;
using System.Net.Http.Json;
using Microsoft.Extensions.AI;
using DevProxy.Abstractions.Prompty;
using Microsoft.Extensions.Logging;

namespace DevProxy.Abstractions.LanguageModel;
Expand Down Expand Up @@ -83,8 +83,8 @@ protected override IEnumerable<ILanguageModelChatCompletionMessage> ConvertMessa
{
return messages.Select(m => new OllamaLanguageModelChatCompletionMessage
{
Role = m.Role.Value,
Content = m.Text
Role = m.Role ?? "user",
Content = m.Text ?? string.Empty
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using DevProxy.Abstractions.Prompty;
using DevProxy.Abstractions.Utils;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Net.Http.Json;
Expand Down Expand Up @@ -91,8 +91,8 @@ protected override IEnumerable<ILanguageModelChatCompletionMessage> ConvertMessa
{
return messages.Select(m => new OpenAIChatCompletionMessage
{
Role = m.Role.Value,
Content = m.Text
Role = m.Role ?? "user",
Content = m.Text ?? string.Empty
});
}

Expand Down
115 changes: 115 additions & 0 deletions DevProxy.Abstractions/Prompty/Prompt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using Scriban;
using YamlDotNet.Serialization;

namespace DevProxy.Abstractions.Prompty;

public class ChatMessage
{
public string? Role { get; set; }
public string? Text { get; set; }
}

public class ModelConfiguration
{
public string? Api { get; set; }
[YamlMember(Alias = "parameters")]
#pragma warning disable CA2227 // we need this for deserialization
public Dictionary<string, object>? Options { get; set; }
#pragma warning restore CA2227
}

public class Prompt
{
public IEnumerable<string>? Authors { get; set; }
public string? Description { get; set; }
public IEnumerable<ChatMessage>? Messages { get; set; }
public ModelConfiguration? Model { get; set; }
public string? Name { get; set; }
#pragma warning disable CA2227 // we need this for deserialization
public Dictionary<string, object>? Sample { get; set; }
#pragma warning restore CA2227

public static Prompt FromMarkdown(string markdown)
{
var (prompt, content) = markdown.ParseMarkdown<Prompt>();
prompt ??= new();

if (content is not null)
{
prompt.Messages = GetMessages(content);
}

return prompt;
}

public IEnumerable<ChatMessage> Prepare(Dictionary<string, object>? inputs, bool mergeSample = false)
{
inputs ??= [];

if (mergeSample && Sample is not null)
{
foreach (var kvp in Sample)
{
inputs[kvp.Key] = kvp.Value;
}
}

var messages = new List<ChatMessage>();

foreach (var message in Messages ?? [])
{
if (message.Text is null)
{
continue;
}

var template = Template.Parse(message.Text);
messages.Add(new()
{
Role = message.Role,
Text = template.Render(inputs)
});
}

return messages;
}

private static List<ChatMessage> GetMessages(string markdown)
{
var messageTypes = new[] { "system", "user", "assistant" };
var messages = new List<ChatMessage>();
var lines = markdown.Split('\n', StringSplitOptions.RemoveEmptyEntries);
ChatMessage? currentMessage = null;

foreach (var line in lines)
{
var trimmedLine = line.Trim();
if (messageTypes.Any(type => trimmedLine.StartsWith($"{type}:", StringComparison.OrdinalIgnoreCase)))
{
if (currentMessage is not null)
{
messages.Add(currentMessage);
}

var role = trimmedLine.Split(':')[0].Trim().ToLowerInvariant();
currentMessage = new ChatMessage
{
Role = role,
Text = string.Empty
};
continue;
}

if (currentMessage is not null)
{
currentMessage.Text += line;
}
}
if (currentMessage is not null)
{
messages.Add(currentMessage);
}

return messages;
}
}
69 changes: 16 additions & 53 deletions DevProxy.Abstractions/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"version": 1,
"dependencies": {
"net9.0": {
"Markdig": {
"type": "Direct",
"requested": "[0.41.3, )",
"resolved": "0.41.3",
"contentHash": "i3vSTyGpBGWbJB04aJ3cPJs0T3BV2e1nduW3EUHK/i+xUupYbym75iZPss/XjqhS5JlBErwQYnx7ofK3Zcsozg=="
},
"Microsoft.EntityFrameworkCore.Sqlite": {
"type": "Direct",
"requested": "[9.0.4, )",
Expand All @@ -18,12 +24,6 @@
"System.Text.Json": "9.0.4"
}
},
"Microsoft.Extensions.AI.Abstractions": {
"type": "Direct",
"requested": "[9.6.0, )",
"resolved": "9.6.0",
"contentHash": "xGO7rHg3qK8jRdriAxIrsH4voNemCf8GVmgdcPXI5gpZ6lZWqOEM4ZO8yfYxUmg7+URw2AY1h7Uc/H17g7X1Kw=="
},
"Microsoft.Extensions.Configuration": {
"type": "Direct",
"requested": "[9.0.4, )",
Expand Down Expand Up @@ -83,19 +83,11 @@
"Newtonsoft.Json": "13.0.3"
}
},
"Prompty.Core": {
"Scriban": {
"type": "Direct",
"requested": "[0.2.2-beta, )",
"resolved": "0.2.2-beta",
"contentHash": "OMAzLsdmrlBaw19lhZLe8VM9xULekA68sRhNZYnlRU/tMnnkhp6U8y3WZ/81yM4mLEUCHEMdy3BGE/bpfFVE/g==",
"dependencies": {
"Microsoft.Extensions.AI.Abstractions": "9.4.0-preview.1.25207.5",
"Microsoft.Extensions.Configuration": "8.0.0",
"Microsoft.Extensions.Configuration.Json": "8.0.0",
"Scriban": "5.12.1",
"Stubble.Core": "1.10.8",
"YamlDotNet": "15.3.0"
}
"requested": "[6.2.1, )",
"resolved": "6.2.1",
"contentHash": "jauX7gvreKvlD1+tkQ9D1i0kNg2p3P1ZqkDftJeTB7JAF7zKtafpyKTtr3m5Kr6d4GYw0CDfRcm2P07/efwdqQ=="
},
"System.CommandLine": {
"type": "Direct",
Expand All @@ -114,16 +106,17 @@
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"YamlDotNet": {
"type": "Direct",
"requested": "[16.3.0, )",
"resolved": "16.3.0",
"contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA=="
},
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.4.0",
"contentHash": "SwXsAV3sMvAU/Nn31pbjhWurYSjJ+/giI/0n6tCrYoupEK34iIHCuk3STAd9fx8yudM85KkLSVdn951vTng/vQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "9.0.4",
Expand Down Expand Up @@ -294,11 +287,6 @@
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"Scriban": {
"type": "Transitive",
"resolved": "5.12.1",
"contentHash": "zezB4VyYSALWvveki0IAdqxrx2r0wHqwQqP5LldFSHZ3U12YZCvt1nwJb6TZVLkerHZLP2FJIkemubLfOihdBQ=="
},
"SharpYaml": {
"type": "Transitive",
"resolved": "2.1.1",
Expand Down Expand Up @@ -334,21 +322,6 @@
"SQLitePCLRaw.core": "2.1.10"
}
},
"Stubble.Core": {
"type": "Transitive",
"resolved": "1.10.8",
"contentHash": "M7pXv3xz3TwhR8PJwieVncotjdC0w8AhviKPpGn2/DHlSNuTKTQdA5Ngmu3datOoeI2jXYEi3fhgncM7UueTWw==",
"dependencies": {
"Microsoft.CSharp": "4.7.0",
"System.Collections.Immutable": "5.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
Expand All @@ -363,16 +336,6 @@
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg=="
},
"YamlDotNet": {
"type": "Transitive",
"resolved": "15.3.0",
"contentHash": "F93japYa9YrJ59AZGhgdaUGHN7ITJ55FBBg/D/8C0BDgahv/rQD6MOSwHxOJJpon1kYyslVbeBrQ2wcJhox01w=="
}
}
}
Expand Down
Loading