Skip to content

Commit 8427ffe

Browse files
authored
Merge pull request #2 from RomeCore/develop
Release v1.2.0
2 parents 3d4eb8c + 69c8235 commit 8427ffe

18 files changed

Lines changed: 223 additions & 97 deletions

src/LLTSharp/ITemplate.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
using System.Collections.Generic;
42
using LLTSharp.Metadata;
5-
using Microsoft.Extensions.AI;
63

74
namespace LLTSharp
85
{
96
/// <summary>
10-
/// Interface for a template that can be used to generate prompts.
7+
/// Interface for a template that can be used to generate contents.
118
/// </summary>
129
public interface ITemplate
1310
{
@@ -25,7 +22,7 @@ public interface ITemplate
2522
}
2623

2724
/// <summary>
28-
/// Interface for a template that can be used to generate prompts.
25+
/// Interface for a template that can be used to generate content.
2926
/// </summary>
3027
/// <typeparam name="TResult">The type of result produced by the template.</typeparam>
3128
public interface ITemplate<TResult> : ITemplate
@@ -35,16 +32,16 @@ public interface ITemplate<TResult> : ITemplate
3532
}
3633

3734
/// <summary>
38-
/// Represents a prompt template that produces a string result.
35+
/// Represents a text template that produces a string result.
3936
/// </summary>
40-
public interface IPromptTemplate : ITemplate<string>
37+
public interface ITextTemplate : ITemplate<string>
4138
{
4239
}
4340

4441
/// <summary>
4542
/// Represents a messages template that produces a collection of messages.
4643
/// </summary>
47-
public interface IMessagesTemplate : ITemplate<IEnumerable<ChatMessage>>
44+
public interface IMessagesTemplate : ITemplate<IEnumerable<Message>>
4845
{
4946
}
5047
}

src/LLTSharp/LLTParser.cs

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,14 @@ private static void DeclareValues(ParserBuilder builder)
8686
builder.CreateRule("constant_array")
8787
.Literal("[")
8888
.ZeroOrMoreSeparated(b => b.Rule("constant"), b => b.Literal(","), allowTrailingSeparator: true)
89-
.ConfigureLast(c => c.SkippingStrategy(ParserSkippingStrategy.SkipBeforeParsingGreedy))
89+
.ConfigureLast(c => c.Skip(b => b.Rule("skip"), ParserSkippingStrategy.SkipBeforeParsingGreedy))
9090
.Literal("]")
9191
.Transform(v => new TemplateArrayAccessor(v.Children[1].SelectValues<TemplateDataAccessor>()));
9292

9393
builder.CreateRule("constant_object")
9494
.Literal("{")
9595
.ZeroOrMoreSeparated(b => b.Rule("constant_pair"), b => b.Literal(","), allowTrailingSeparator: true)
96-
.ConfigureLast(c => c.SkippingStrategy(ParserSkippingStrategy.SkipBeforeParsingGreedy))
96+
.ConfigureLast(c => c.Skip(b => b.Rule("skip"), ParserSkippingStrategy.SkipBeforeParsingGreedy))
9797
.Literal("}")
9898
.Transform(v =>
9999
{
@@ -526,18 +526,39 @@ private static void DeclareTextTemplates(ParserBuilder builder)
526526
node.Refine(depth: 1);
527527

528528
var library = v.GetParsingParameter<LLTParsingContext>().LocalLibrary;
529-
var template = new PromptTemplate(node, new MetadataCollection(metadata), library);
529+
var template = new TextTemplate(node, new MetadataCollection(metadata), library);
530530
library.Add(template);
531531
return template;
532532
});
533533

534+
var textEscapes = new Dictionary<string, string>
535+
{
536+
["@@"] = "@",
537+
["{{"] = "{",
538+
["}}"] = "}"
539+
};
540+
541+
var textForbidden = new string[]
542+
{
543+
"@", "{", "}", "`````"
544+
};
545+
534546
builder.CreateRule("text_content")
535-
.EscapedTextDoubleChars("@{}", allowsEmpty: false)
547+
.EscapedText(textEscapes, textForbidden, allowsEmpty: false)
548+
.Transform(v => new TextTemplatePlainTextNode(v.GetIntermediateValue<string>()));
549+
550+
builder.CreateRule("text_multiline_content")
551+
.Token(b => b.Between(
552+
b => b.Literal("`````"),
553+
b => b.TextUntil("`````"),
554+
b => b.Literal("`````")
555+
))
536556
.Transform(v => new TextTemplatePlainTextNode(v.GetIntermediateValue<string>()));
537557

538558
builder.CreateRule("text_statements")
539559
.ZeroOrMore(b => b.Choice(
540560
c => c.Rule("text_content"),
561+
c => c.Rule("text_multiline_content"),
541562
c => c.Rule("text_statement")))
542563
.Transform(v =>
543564
{
@@ -567,7 +588,7 @@ private static void DeclareTextTemplates(ParserBuilder builder)
567588
)
568589
.Transform(v => v.GetValue(1));
569590

570-
builder.CreateRule("text_expression_inner")
591+
builder.CreateRule("text_expression")
571592
.Rule("simple_expression") // We don't want to use binary expressions in text statements
572593
.Optional(b => b
573594
.Literal(':')
@@ -578,21 +599,6 @@ private static void DeclareTextTemplates(ParserBuilder builder)
578599
return new TextTemplateExpressionNode(v.GetValue<TemplateExpressionNode>(0), format);
579600
});
580601

581-
builder.CreateRule("text_expression")
582-
.Custom(
583-
(self, ctx, sett, childSett, children, childrenIds) =>
584-
{
585-
var result = self.ParseRule(childrenIds[0], ctx, childSett);
586-
if (!result.success)
587-
return result;
588-
var modifierChild = result.children[1];
589-
if (modifierChild.length == 0)
590-
result.length = result.children[0].length;
591-
return result;
592-
},
593-
b => b.Rule("text_expression_inner")
594-
);
595-
596602
builder.CreateRule("text_if")
597603
.Keyword("if")
598604
.Rule("expression")
@@ -729,15 +735,18 @@ static LLTParser()
729735
{
730736
var builder = new ParserBuilder();
731737

732-
// Settings //
733-
builder.Settings
734-
.Skip(s => s.Choice(
738+
// Skip rule //
739+
builder.CreateRule("skip")
740+
.Choice(
735741
c => c.Whitespaces(),
736742
c => c.Literal("@/").TextUntil('\n', '\r'), // @/ C#-like comments
737743
c => c.Literal("@*").TextUntil("*@").Literal("*@")) // @*...*@ comments
738-
.ConfigureForSkip(), // Ignore all errors when parsing comments and unnecessary whitespace
739-
ParserSkippingStrategy.TryParseThenSkipLazy) // Allows rules to capture skip-rules contents if can, such as whitespaces
740-
.UseCaching().RecordWalkTrace(); // If caching is disabled, prepare to wait for a long time (seconds) when encountering an error :P (you will also get a million of errors, seriously)
744+
.ConfigureForSkip(); // Ignore all errors when parsing comments and unnecessary whitespace
745+
746+
// Settings //
747+
builder.Settings
748+
.Skip(b => b.Rule("skip"), ParserSkippingStrategy.TryParseThenSkipLazy) // Allows rules to capture skip-rules contents if can, such as whitespaces
749+
.UseCaching(); // If caching is disabled, prepare to wait for a long time (seconds) when encountering an error :P (you will also get a million of errors, seriously)
741750

742751
// ---- Values ---- //
743752
DeclareValues(builder);

src/LLTSharp/LLTSharp.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
<PropertyGroup>
1010
<PackageId>LLTSharp</PackageId>
11-
<Version>1.1.0</Version>
11+
<Version>1.2.0</Version>
1212
<Authors>Roman K.</Authors>
1313
<Company>RomeCore</Company>
1414
<Product>LLTSharp</Product>
15-
<Description>A lightweight .NET template engine for LLM with messages and prompt templates support.</Description>
15+
<Description>A lightweight .NET template engine for LLM with messages and text templates support.</Description>
1616
<PackageReadmeFile>README.md</PackageReadmeFile>
1717
<PackageTags>template, llm, template-engine, language-models</PackageTags>
1818
<RepositoryUrl>https://github.com/RomeCore/LLTSharp</RepositoryUrl>
@@ -25,8 +25,7 @@
2525
</ItemGroup>
2626

2727
<ItemGroup>
28-
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" Version="9.10.0" />
29-
<PackageReference Include="RCParsing" Version="4.7.1" />
28+
<PackageReference Include="RCParsing" Version="5.1.0" />
3029
</ItemGroup>
3130

3231
</Project>

src/LLTSharp/Message.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace LLTSharp
6+
{
7+
/// <summary>
8+
/// Represents a role in a conversation.
9+
/// </summary>
10+
public enum Role
11+
{
12+
/// <summary>
13+
/// System role, typically used for system instructions or context.
14+
/// </summary>
15+
System,
16+
17+
/// <summary>
18+
/// User role, typically used for user input or questions.
19+
/// </summary>
20+
User,
21+
22+
/// <summary>
23+
/// Assistant role, typically used for responses or actions.
24+
/// </summary>
25+
Assistant,
26+
27+
/// <summary>
28+
/// Tool role, typically used for responses from tool calls.
29+
/// </summary>
30+
Tool
31+
}
32+
33+
/// <summary>
34+
/// Represents a single message in a conversation with LLM.
35+
/// </summary>
36+
public class Message
37+
{
38+
/// <summary>
39+
/// Gets or sets the role of the message sender. Typically "user" or "assistant".
40+
/// </summary>
41+
public Role Role { get; }
42+
43+
/// <summary>
44+
/// Gets or sets the content of the message.
45+
/// </summary>
46+
public string Content { get; }
47+
48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="Message"/> class.
50+
/// </summary>
51+
public Message()
52+
{
53+
Role = Role.System;
54+
Content = string.Empty;
55+
}
56+
57+
/// <summary>
58+
/// Initializes a new instance of the <see cref="Message"/> class.
59+
/// </summary>
60+
/// <param name="role">The role of the message sender.</param>
61+
/// <param name="content">The content of the message.</param>
62+
public Message(Role role, string content)
63+
{
64+
Role = role;
65+
Content = content;
66+
}
67+
}
68+
}

src/LLTSharp/MessagesTemplate.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.Text;
44
using LLTSharp.Metadata;
5-
using Microsoft.Extensions.AI;
65

76
namespace LLTSharp
87
{
@@ -39,7 +38,7 @@ public MessagesTemplate(MessagesTemplateNode mainNode, IMetadataCollection metad
3938
/// </summary>
4039
/// <param name="context">The context accessor to use for rendering.</param>
4140
/// <returns>The rendered prompt as a collection of messages.</returns>
42-
public IEnumerable<ChatMessage> Render(object? context = null)
41+
public IEnumerable<Message> Render(object? context = null)
4342
{
4443
var ctx = new TemplateContextAccessor(TemplateDataAccessor.Create(context), Metadata, library: LocalLibrary);
4544
return _node.Render(ctx);

src/LLTSharp/MessagesTemplateNode.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Text;
4-
using Microsoft.Extensions.AI;
53

64
namespace LLTSharp
75
{
@@ -15,7 +13,7 @@ public abstract class MessagesTemplateNode
1513
/// </summary>
1614
/// <param name="context">The context accessor containing the data to render.</param>
1715
/// <returns>A collection of messages representing the rendered template node.</returns>
18-
public abstract IEnumerable<ChatMessage> Render(TemplateContextAccessor context);
16+
public abstract IEnumerable<Message> Render(TemplateContextAccessor context);
1917

2018
/// <summary>
2119
/// Refines the template after parsing an AST to remove indents and unnecessary leading/trailing whitespaces.

src/LLTSharp/TemplateContextAccessor.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Text;
66
using LLTSharp.DataAccessors;
77
using LLTSharp.Metadata;
8-
using Microsoft.Extensions.AI;
98

109
namespace LLTSharp
1110
{
@@ -223,7 +222,7 @@ public string RenderTemplate(string identifier, TemplateDataAccessor? newContext
223222
if (template == null)
224223
throw new TemplateRuntimeException($"Template '{identifier}' not found.");
225224

226-
if (template is not PromptTemplate && template is not PlaintextTemplate)
225+
if (template is not TextTemplate && template is not PlaintextTemplate)
227226
throw new TemplateRuntimeException($"Template '{identifier}' is not a text template.");
228227

229228
var context = newContext ?? this;
@@ -239,7 +238,7 @@ public string RenderTemplate(string identifier, TemplateDataAccessor? newContext
239238
/// <param name="identifier">The identifier of the messages template to render.</param>
240239
/// <param name="newContext">The new context to use for rendering the template. If null, uses the current context.</param>
241240
/// <returns>The rendered template as a collection of messages.</returns>
242-
public IEnumerable<ChatMessage> RenderMessagesTemplate(string identifier, TemplateDataAccessor? newContext)
241+
public IEnumerable<Message> RenderMessagesTemplate(string identifier, TemplateDataAccessor? newContext)
243242
{
244243
var template = Library.TryRetrieve(identifier);
245244
if (template == null)

src/LLTSharp/TemplateNodes/MessagesTemplateEntryNode.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Text;
4-
using Microsoft.Extensions.AI;
54

65
namespace LLTSharp.TemplateNodes
76
{
@@ -32,21 +31,21 @@ public MessagesTemplateEntryNode(TemplateExpressionNode role, TextTemplateNode c
3231
Child = child ?? throw new ArgumentNullException(nameof(child));
3332
}
3433

35-
public override IEnumerable<ChatMessage> Render(TemplateContextAccessor context)
34+
public override IEnumerable<Message> Render(TemplateContextAccessor context)
3635
{
3736
var role = Role.Evaluate(context);
3837
var content = Child.Render(context);
3938

40-
ChatMessage message = role.ToString() switch
39+
Message message = role.ToString() switch
4140
{
42-
"system" => new ChatMessage(ChatRole.System, content),
43-
"user" => new ChatMessage(ChatRole.User, content),
44-
"assistant" => new ChatMessage(ChatRole.Assistant, content),
41+
"system" => new Message(LLTSharp.Role.System, content),
42+
"user" => new Message(LLTSharp.Role.User, content),
43+
"assistant" => new Message(LLTSharp.Role.Assistant, content),
4544
// "tool" => new ToolMessage(content), // TODO: Add support for tool call ids and tool names
4645
"tool" => throw new TemplateRuntimeException($"Tool messages are not yet supported.", dataAccessor: context, messagesTemplateNode: this),
4746
_ => throw new TemplateRuntimeException($"Invalid role '{role}'.", dataAccessor: context, messagesTemplateNode: this),
4847
};
49-
return new ChatMessage[] { message };
48+
return new Message[] { message };
5049
}
5150

5251
public override void Refine(int depth)

src/LLTSharp/TemplateNodes/MessagesTemplateForeachNode.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Text;
43
using LLTSharp.DataAccessors;
5-
using Microsoft.Extensions.AI;
64

75
namespace LLTSharp.TemplateNodes
86
{
@@ -41,15 +39,15 @@ public MessagesTemplateForeachNode(TemplateExpressionNode source, MessagesTempla
4139
IterableName = string.IsNullOrEmpty(iterableName) ? throw new ArgumentException("The iterable name cannot be null or empty.", nameof(iterableName)) : iterableName;
4240
}
4341

44-
public override IEnumerable<ChatMessage> Render(TemplateContextAccessor context)
42+
public override IEnumerable<Message> Render(TemplateContextAccessor context)
4543
{
4644
var source = Source.Evaluate(context);
4745

4846
if (source is not IEnumerableTemplateDataAccessor enumerableSource)
4947
throw new TemplateRuntimeException($"The source expression does not provide an enumerable data source.",
5048
dataAccessor: context, expressionNode: Source);
5149

52-
List<ChatMessage> messages = new List<ChatMessage>();
50+
List<Message> messages = new List<Message>();
5351

5452
context.PushFrame();
5553
foreach (var item in enumerableSource)

0 commit comments

Comments
 (0)