From a8fb26b8c59bfb73a9aa0fc112a77d21efbeb8b2 Mon Sep 17 00:00:00 2001 From: Roman Kondrat'ev <62770895+RomeCore@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:03:34 +0500 Subject: [PATCH 1/2] Improve metadata creation, add advanced language fallback system --- .../TemplateDictionaryAccessor.cs | 5 ++ .../DataAccessors/TemplateObjectAccessor.cs | 5 ++ src/LLTSharp/ITemplate.cs | 7 +- src/LLTSharp/ITemplateParser.cs | 6 +- src/LLTSharp/LLTParser.cs | 48 +++++-------- .../HierarchicalLanguageFallbackScheme.cs | 71 +++++++++++++++++++ .../Locale/MajorLanguageFallbackScheme.cs | 2 +- .../Factories/LanguageMetadataFactory.cs | 21 ++++++ .../TargetModelFamilyMetadataFactory.cs | 18 +++++ .../Factories/TargetModelMetadataFactory.cs | 18 +++++ .../Factories/VersionMetadataFactory.cs | 19 +++++ .../LanguageMetadataFallbackScheme.cs | 3 +- src/LLTSharp/Metadata/MetadataFactory.cs | 18 +++++ .../Metadata/{ => Types}/LanguageMetadata.cs | 3 +- .../{ => Types}/TargetModelFamilyMetadata.cs | 4 +- .../{ => Types}/TargetModelMetadata.cs | 4 +- .../Metadata/{ => Types}/VersionMetadata.cs | 15 +++- src/LLTSharp/TemplateContextAccessor.cs | 18 +++-- src/LLTSharp/TemplateDataAccessor.cs | 7 ++ src/LLTSharp/TemplateLibrary.cs | 42 ++++++++--- .../BasicTemplateParsingTests.cs | 6 +- tests/LLTSharp.Tests/TemplateLibraryTests.cs | 20 ++++-- 22 files changed, 283 insertions(+), 77 deletions(-) create mode 100644 src/LLTSharp/Locale/HierarchicalLanguageFallbackScheme.cs create mode 100644 src/LLTSharp/Metadata/Factories/LanguageMetadataFactory.cs create mode 100644 src/LLTSharp/Metadata/Factories/TargetModelFamilyMetadataFactory.cs create mode 100644 src/LLTSharp/Metadata/Factories/TargetModelMetadataFactory.cs create mode 100644 src/LLTSharp/Metadata/Factories/VersionMetadataFactory.cs rename src/LLTSharp/Metadata/{ => FallbackSchemes}/LanguageMetadataFallbackScheme.cs (94%) create mode 100644 src/LLTSharp/Metadata/MetadataFactory.cs rename src/LLTSharp/Metadata/{ => Types}/LanguageMetadata.cs (96%) rename src/LLTSharp/Metadata/{ => Types}/TargetModelFamilyMetadata.cs (94%) rename src/LLTSharp/Metadata/{ => Types}/TargetModelMetadata.cs (94%) rename src/LLTSharp/Metadata/{ => Types}/VersionMetadata.cs (65%) diff --git a/src/LLTSharp/DataAccessors/TemplateDictionaryAccessor.cs b/src/LLTSharp/DataAccessors/TemplateDictionaryAccessor.cs index 0e5124a..418bd41 100644 --- a/src/LLTSharp/DataAccessors/TemplateDictionaryAccessor.cs +++ b/src/LLTSharp/DataAccessors/TemplateDictionaryAccessor.cs @@ -46,6 +46,11 @@ public override TemplateDataAccessor Index(TemplateDataAccessor index) return Property(index.ToString()); } + public override bool HasProperty(string name) + { + return _dictionary.ContainsKey(name); + } + public override TemplateDataAccessor Property(string key) { if (_dictionary.TryGetValue(key, out var accessor)) diff --git a/src/LLTSharp/DataAccessors/TemplateObjectAccessor.cs b/src/LLTSharp/DataAccessors/TemplateObjectAccessor.cs index 3a06b6f..94f0c8f 100644 --- a/src/LLTSharp/DataAccessors/TemplateObjectAccessor.cs +++ b/src/LLTSharp/DataAccessors/TemplateObjectAccessor.cs @@ -54,6 +54,11 @@ public TemplateObjectAccessor(object target, DataAccessorCreationOptions options return accessors; } + public override bool HasProperty(string name) + { + return _propertyAccessors.ContainsKey(name); + } + public override TemplateDataAccessor Property(string key) { if (_propertyAccessors.TryGetValue(key, out var accessor)) diff --git a/src/LLTSharp/ITemplate.cs b/src/LLTSharp/ITemplate.cs index d0ad4e5..60acee6 100644 --- a/src/LLTSharp/ITemplate.cs +++ b/src/LLTSharp/ITemplate.cs @@ -6,13 +6,8 @@ namespace LLTSharp /// /// Interface for a template that can be used to generate contents. /// - public interface ITemplate + public interface ITemplate : IMetadataProvider { - /// - /// Gets the metadata associated with this template. - /// - public IMetadataCollection Metadata { get; } - /// /// Renders the template with the given context. /// diff --git a/src/LLTSharp/ITemplateParser.cs b/src/LLTSharp/ITemplateParser.cs index f26c3c0..2e74444 100644 --- a/src/LLTSharp/ITemplateParser.cs +++ b/src/LLTSharp/ITemplateParser.cs @@ -1,4 +1,5 @@ -using System; +using LLTSharp.Metadata; +using System; using System.Collections.Generic; using System.Text; @@ -13,7 +14,8 @@ public interface ITemplateParser /// Parses a prompt template string into a objects. /// /// The prompt template string to parse. + /// Optional metadata factories to use for parsing. /// A collection of objects representing the parsed templates. - IEnumerable Parse(string templateString); + IEnumerable Parse(string templateString, IEnumerable? metadataFactories = null); } } \ No newline at end of file diff --git a/src/LLTSharp/LLTParser.cs b/src/LLTSharp/LLTParser.cs index cbd76f5..435c7fc 100644 --- a/src/LLTSharp/LLTParser.cs +++ b/src/LLTSharp/LLTParser.cs @@ -20,6 +20,7 @@ public class LLTParser : ITemplateParser private class LLTParsingContext { public TemplateLibrary LocalLibrary { get; set; } + public IEnumerable MetadataFactories { get; set; } } private static readonly Parser _parser; @@ -697,30 +698,23 @@ private static void DeclareMainRules(ParserBuilder builder) .Rule("constant_object") .Transform(v => { + var ctx = v.GetParsingParameter(); + var factories = ctx.MetadataFactories; + var obj = v.GetValue(2); var metadata = new List(); var additionalMetadata = new AdditionalMetadata(); foreach (var pair in obj.Dictionary) { - switch (pair.Key) - { - case "lang": - metadata.Add(new LanguageMetadata(new Locale.LanguageCode(pair.Value.ToString()))); - break; - case "model": - metadata.Add(new TargetModelMetadata(pair.Value.ToString())); - break; - case "model_family": - metadata.Add(new TargetModelFamilyMetadata(pair.Value.ToString())); + IMetadata? result = null; + foreach (var factory in factories) + if (factory.TryCreateMetadata(pair.Key, pair.Value, out result)) break; - case "version": - metadata.Add(new VersionMetadata((int)((double)pair.Value.GetValue()))); - break; - default: - additionalMetadata.Set(pair.Key, pair.Value.GetValue()); - break; - } + if (result != null) + metadata.Add(result); + else + additionalMetadata.Set(pair.Key, pair.Value.GetValue()); } if (additionalMetadata.Count > 0) @@ -770,21 +764,13 @@ static LLTParser() _parser = builder.Build(); } - public ParsedRuleResultBase ParseAST(string templateString) + public IEnumerable Parse(string templateString, IEnumerable? metadataFactories = null) { - var ctx = new LLTParsingContext { LocalLibrary = new TemplateLibrary() }; - return _parser.Parse(templateString, ctx); - } - - public ParsedRuleResultBase ParseOptimizedAST(string templateString) - { - var ctx = new LLTParsingContext { LocalLibrary = new TemplateLibrary() }; - return _parser.Parse(templateString, ctx).Optimized(ParseTreeOptimization.Default); - } - - public IEnumerable Parse(string templateString) - { - var ctx = new LLTParsingContext { LocalLibrary = new TemplateLibrary() }; + var ctx = new LLTParsingContext + { + LocalLibrary = new TemplateLibrary(), + MetadataFactories = metadataFactories?.ToList() ?? Enumerable.Empty() + }; return _parser.Parse(templateString, ctx).GetValue>(); } } diff --git a/src/LLTSharp/Locale/HierarchicalLanguageFallbackScheme.cs b/src/LLTSharp/Locale/HierarchicalLanguageFallbackScheme.cs new file mode 100644 index 0000000..bdf8e79 --- /dev/null +++ b/src/LLTSharp/Locale/HierarchicalLanguageFallbackScheme.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LLTSharp.Locale +{ + /// + /// Provides a hierarchical language fallback scheme that walks up the language tag hierarchy (e.g., en-US → en) + /// and then tries any available sibling variant of the same language root before falling back to a default or the first available language. + /// + public class HierarchicalLanguageFallbackScheme : ILanguageFallbackScheme + { + // Да, да, это нейрослоп + + private readonly LanguageCode? _defaultLanguage; + + /// + /// Creates a new instance of the scheme. + /// + /// + /// Optional default language code to be used if the target language and none of its linguistic relatives are available. + /// If , the first available language from the collection will be returned as a last resort. + /// + public HierarchicalLanguageFallbackScheme(LanguageCode? defaultLanguage = null) + { + _defaultLanguage = defaultLanguage; + } + + public LanguageCode GetFallbackLanguage(LanguageCode targetLanguage, IEnumerable availableLanguages) + { + if (availableLanguages == null) + throw new ArgumentNullException(nameof(availableLanguages)); + + // Materialise to a list to avoid multiple enumeration. + List availableList = availableLanguages as List ?? availableLanguages.ToList(); + + if (availableList.Count == 0) + throw new ArgumentException("Available languages collection is empty.", nameof(availableLanguages)); + + // 1. Exact match is already the best choice. + if (availableList.Any(l => l == targetLanguage)) + return targetLanguage; + + // 2. Walk up the parent chain: e.g., zh-Hans-CN → zh-Hans → zh + LanguageCode current = targetLanguage; + while (true) + { + LanguageCode parent = current.GetSuperLanguage(); + if (parent == current) + break; + + if (availableList.Any(l => l == parent)) + return parent; + + current = parent; + } + + // 3. Look for any sibling belonging to the same root language (e.g., fr-FR when fr-CA was requested) + var sibling = availableList.FirstOrDefault(l => l.IsSubLanguageOf(current)); + if (sibling.FullCode != null) + return sibling; + + // 4. Fall back to the explicitly configured default language (if available). + if (_defaultLanguage.HasValue && availableList.Any(l => l == _defaultLanguage.Value)) + return _defaultLanguage.Value; + + // 5. Ultimate fallback – return the first language from the available set. + return availableList[0]; + } + } +} \ No newline at end of file diff --git a/src/LLTSharp/Locale/MajorLanguageFallbackScheme.cs b/src/LLTSharp/Locale/MajorLanguageFallbackScheme.cs index b3bb6c2..2628573 100644 --- a/src/LLTSharp/Locale/MajorLanguageFallbackScheme.cs +++ b/src/LLTSharp/Locale/MajorLanguageFallbackScheme.cs @@ -22,7 +22,7 @@ public LanguageCode GetFallbackLanguage(LanguageCode targetLanguage, IEnumerable var hashSet = new HashSet(availableLanguages); hashSet.IntersectWith(LanguageGroup.MajorWorldLanguages); - if (hashSet.Any()) + if (hashSet.Count > 0) return hashSet.First(); return availableLanguages.First(); diff --git a/src/LLTSharp/Metadata/Factories/LanguageMetadataFactory.cs b/src/LLTSharp/Metadata/Factories/LanguageMetadataFactory.cs new file mode 100644 index 0000000..65370e0 --- /dev/null +++ b/src/LLTSharp/Metadata/Factories/LanguageMetadataFactory.cs @@ -0,0 +1,21 @@ +using LLTSharp.Metadata.Types; +using System; +using System.Collections.Generic; +using System.Text; + +namespace LLTSharp.Metadata.Factories +{ + public class LanguageMetadataFactory : MetadataFactory + { + public override bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata) + { + if (key == "lang") + { + metadata = new LanguageMetadata(value.ToString()); + return true; + } + metadata = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/LLTSharp/Metadata/Factories/TargetModelFamilyMetadataFactory.cs b/src/LLTSharp/Metadata/Factories/TargetModelFamilyMetadataFactory.cs new file mode 100644 index 0000000..cde1c9a --- /dev/null +++ b/src/LLTSharp/Metadata/Factories/TargetModelFamilyMetadataFactory.cs @@ -0,0 +1,18 @@ +using LLTSharp.Metadata.Types; + +namespace LLTSharp.Metadata.Factories +{ + public class TargetModelFamilyMetadataFactory : MetadataFactory + { + public override bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata) + { + if (key == "model_family") + { + metadata = new TargetModelFamilyMetadata(value.ToString()); + return true; + } + metadata = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/LLTSharp/Metadata/Factories/TargetModelMetadataFactory.cs b/src/LLTSharp/Metadata/Factories/TargetModelMetadataFactory.cs new file mode 100644 index 0000000..f8111fb --- /dev/null +++ b/src/LLTSharp/Metadata/Factories/TargetModelMetadataFactory.cs @@ -0,0 +1,18 @@ +using LLTSharp.Metadata.Types; + +namespace LLTSharp.Metadata.Factories +{ + public class TargetModelMetadataFactory : MetadataFactory + { + public override bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata) + { + if (key == "model") + { + metadata = new TargetModelMetadata(value.ToString()); + return true; + } + metadata = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/LLTSharp/Metadata/Factories/VersionMetadataFactory.cs b/src/LLTSharp/Metadata/Factories/VersionMetadataFactory.cs new file mode 100644 index 0000000..0b75535 --- /dev/null +++ b/src/LLTSharp/Metadata/Factories/VersionMetadataFactory.cs @@ -0,0 +1,19 @@ +using LLTSharp.Metadata.Types; +using System; + +namespace LLTSharp.Metadata.Factories +{ + public class VersionMetadataFactory : MetadataFactory + { + public override bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata) + { + if (key == "version") + { + metadata = new VersionMetadata(Version.Parse(value.ToString())); + return true; + } + metadata = null; + return false; + } + } +} \ No newline at end of file diff --git a/src/LLTSharp/Metadata/LanguageMetadataFallbackScheme.cs b/src/LLTSharp/Metadata/FallbackSchemes/LanguageMetadataFallbackScheme.cs similarity index 94% rename from src/LLTSharp/Metadata/LanguageMetadataFallbackScheme.cs rename to src/LLTSharp/Metadata/FallbackSchemes/LanguageMetadataFallbackScheme.cs index a09af0c..79ad261 100644 --- a/src/LLTSharp/Metadata/LanguageMetadataFallbackScheme.cs +++ b/src/LLTSharp/Metadata/FallbackSchemes/LanguageMetadataFallbackScheme.cs @@ -3,8 +3,9 @@ using System.Linq; using System.Text; using LLTSharp.Locale; +using LLTSharp.Metadata.Types; -namespace LLTSharp.Metadata +namespace LLTSharp.Metadata.FallbackSchemes { /// /// A fallback scheme for handling language metadata when no specific information is available. diff --git a/src/LLTSharp/Metadata/MetadataFactory.cs b/src/LLTSharp/Metadata/MetadataFactory.cs new file mode 100644 index 0000000..9455182 --- /dev/null +++ b/src/LLTSharp/Metadata/MetadataFactory.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LLTSharp.Metadata +{ + public abstract class MetadataFactory + { + /// + /// Tries to create a new instance of based on the provided key and value. + /// + /// The key associated with the metadata. + /// The value associated with the metadata. + /// When this method returns, contains the newly created instance of if successful; otherwise, null. This parameter is passed uninitialized. + /// true if a new instance of was created successfully; otherwise, false. + public abstract bool TryCreateMetadata(string key, TemplateDataAccessor value, out IMetadata metadata); + } +} \ No newline at end of file diff --git a/src/LLTSharp/Metadata/LanguageMetadata.cs b/src/LLTSharp/Metadata/Types/LanguageMetadata.cs similarity index 96% rename from src/LLTSharp/Metadata/LanguageMetadata.cs rename to src/LLTSharp/Metadata/Types/LanguageMetadata.cs index a5568d2..f765822 100644 --- a/src/LLTSharp/Metadata/LanguageMetadata.cs +++ b/src/LLTSharp/Metadata/Types/LanguageMetadata.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Text; using LLTSharp.Locale; -using LLTSharp.Metadata; -namespace LLTSharp.Metadata +namespace LLTSharp.Metadata.Types { /// /// The metadata for language-related information. diff --git a/src/LLTSharp/Metadata/TargetModelFamilyMetadata.cs b/src/LLTSharp/Metadata/Types/TargetModelFamilyMetadata.cs similarity index 94% rename from src/LLTSharp/Metadata/TargetModelFamilyMetadata.cs rename to src/LLTSharp/Metadata/Types/TargetModelFamilyMetadata.cs index d3e094b..63fe221 100644 --- a/src/LLTSharp/Metadata/TargetModelFamilyMetadata.cs +++ b/src/LLTSharp/Metadata/Types/TargetModelFamilyMetadata.cs @@ -1,6 +1,4 @@ -using LLTSharp.Metadata; - -namespace LLTSharp.Metadata +namespace LLTSharp.Metadata.Types { /// /// The metadata for target model family-related information. diff --git a/src/LLTSharp/Metadata/TargetModelMetadata.cs b/src/LLTSharp/Metadata/Types/TargetModelMetadata.cs similarity index 94% rename from src/LLTSharp/Metadata/TargetModelMetadata.cs rename to src/LLTSharp/Metadata/Types/TargetModelMetadata.cs index 670e45b..6a8f769 100644 --- a/src/LLTSharp/Metadata/TargetModelMetadata.cs +++ b/src/LLTSharp/Metadata/Types/TargetModelMetadata.cs @@ -1,6 +1,4 @@ -using LLTSharp.Metadata; - -namespace LLTSharp.Metadata +namespace LLTSharp.Metadata.Types { /// /// The metadata for target model-related information. diff --git a/src/LLTSharp/Metadata/VersionMetadata.cs b/src/LLTSharp/Metadata/Types/VersionMetadata.cs similarity index 65% rename from src/LLTSharp/Metadata/VersionMetadata.cs rename to src/LLTSharp/Metadata/Types/VersionMetadata.cs index 8db8cab..e6145b0 100644 --- a/src/LLTSharp/Metadata/VersionMetadata.cs +++ b/src/LLTSharp/Metadata/Types/VersionMetadata.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace LLTSharp.Metadata +namespace LLTSharp.Metadata.Types { /// /// Represents the version metadata. @@ -12,7 +12,7 @@ public class VersionMetadata : IMetadata /// /// Gets the version code associated with this metadata. /// - public int Version { get; } + public Version Version { get; } /// /// Initializes a new instance of the class with the specified version code. @@ -20,7 +20,16 @@ public class VersionMetadata : IMetadata /// The version code associated with this metadata. public VersionMetadata(int version) { - Version = version; + Version = new Version(version, 0); + } + + /// + /// Initializes a new instance of the class with the specified version code. + /// + /// The version code associated with this metadata. + public VersionMetadata(Version version) + { + Version = version ?? throw new ArgumentNullException(nameof(version)); } public override string ToString() diff --git a/src/LLTSharp/TemplateContextAccessor.cs b/src/LLTSharp/TemplateContextAccessor.cs index dc9110c..093aa01 100644 --- a/src/LLTSharp/TemplateContextAccessor.cs +++ b/src/LLTSharp/TemplateContextAccessor.cs @@ -209,16 +209,20 @@ public override string ToString(string? format = null) /// Renders a template with the specified identifier and optional new context. /// /// - /// Tries to find the specified template in the local library. If not found, tries to find it in the shared library. + /// Tries to find the specified template in the context variables, then local library. If not found, tries to find it in the shared library. /// /// The identifier of the template to render. /// The new context to use for rendering the template. If null, uses the current context. /// The rendered template as a string. public string RenderTemplate(string identifier, TemplateDataAccessor? newContext) { - var template = Library.TryRetrieve(identifier); + ITemplate? template = null; + if (Context.HasProperty(identifier) && Context.Property(identifier).GetValue() is ITemplate ctxValueTemplate) + template = ctxValueTemplate; if (template == null) - template = TemplateLibrary.Shared.TryRetrieve(identifier); // try to get from shared library + template = Library.TryRetrieve(identifier); + if (template == null) + template = TemplateLibrary.Shared.TryRetrieve(identifier); if (template == null) throw new TemplateRuntimeException($"Template '{identifier}' not found."); @@ -233,14 +237,18 @@ public string RenderTemplate(string identifier, TemplateDataAccessor? newContext /// Renders a messages template with the specified identifier and optional new context. /// /// - /// Tries to find the specified template in the local library. If not found, tries to find it in the shared library. + /// Tries to find the specified template in the context variables, then local library. If not found, tries to find it in the shared library. /// /// The identifier of the messages template to render. /// The new context to use for rendering the template. If null, uses the current context. /// The rendered template as a collection of messages. public IEnumerable RenderMessagesTemplate(string identifier, TemplateDataAccessor? newContext) { - var template = Library.TryRetrieve(identifier); + ITemplate? template = null; + if (Context.HasProperty(identifier) && Context.Property(identifier).GetValue() is ITemplate ctxValueTemplate) + template = ctxValueTemplate; + if (template == null) + template = Library.TryRetrieve(identifier); if (template == null) template = TemplateLibrary.Shared.TryRetrieve(identifier); if (template == null) diff --git a/src/LLTSharp/TemplateDataAccessor.cs b/src/LLTSharp/TemplateDataAccessor.cs index d8ddf91..f0e7afd 100644 --- a/src/LLTSharp/TemplateDataAccessor.cs +++ b/src/LLTSharp/TemplateDataAccessor.cs @@ -18,6 +18,13 @@ public abstract class TemplateDataAccessor : IDisposable /// public virtual int Length => 0; + /// + /// Determines if a property exists in the data. + /// + /// The name of the property to check. + /// True if the property exists; otherwise, false. + public virtual bool HasProperty(string name) => false; + /// /// Gets the template data property associated with the specified property name. /// diff --git a/src/LLTSharp/TemplateLibrary.cs b/src/LLTSharp/TemplateLibrary.cs index eb921ea..57e5fae 100644 --- a/src/LLTSharp/TemplateLibrary.cs +++ b/src/LLTSharp/TemplateLibrary.cs @@ -9,6 +9,9 @@ using System.Threading.Tasks; using LLTSharp.Locale; using LLTSharp.Metadata; +using LLTSharp.Metadata.Factories; +using LLTSharp.Metadata.FallbackSchemes; +using LLTSharp.Metadata.Types; using RCParsing; namespace LLTSharp @@ -28,7 +31,7 @@ static TemplateLibrary() /// /// Registers a template parser for the specified language. /// - /// The language code of the template language. + /// The language code of the template language, e.g., "llt". /// The template parser to register. /// Thrown when a parser is already registered for the specified language code. public static void RegisterParser(string languageCode, ITemplateParser parser) @@ -46,14 +49,35 @@ public static void RegisterParser(string languageCode, ITemplateParser parser) private readonly Dictionary _fallbackSchemes = new(); private readonly Dictionary> _fallbackMetadatas = new(); + private readonly List _metadataFactories = new() + { + new VersionMetadataFactory(), + new LanguageMetadataFactory(), + new TargetModelMetadataFactory(), + new TargetModelFamilyMetadataFactory() + }; - private readonly object _lockObject = new object(); + private readonly object _lockObject = new(); /// /// Gets the shared template library instance. /// public static TemplateLibrary Shared { get; } = new(); + /// + /// Gets the collection of metadata factories used by this template library. + /// These are used to construct metadata objects inside metadata collections. + /// + /// The default metadata factories are: + /// + /// + /// + /// + /// + /// + /// + public List MetadataFactories => _metadataFactories; + /// /// Initializes a new instance of the class. /// @@ -276,7 +300,7 @@ public void ImportFromString(string templateContents, string languageCode = "llt if (!_templateParsers.TryGetValue(languageCode, out var parser)) throw new ArgumentException($"No parser registered for language: '{languageCode}'."); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); foreach (var template in templates) Add(template); } @@ -294,7 +318,7 @@ public void ImportFromReader(TextReader reader, string languageCode = "llt") throw new ArgumentException($"No parser registered for language: '{languageCode}'."); var templateContents = reader?.ReadToEnd() ?? throw new ArgumentNullException(nameof(reader)); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); foreach (var template in templates) Add(template); } @@ -313,7 +337,7 @@ public void ImportFromStream(Stream stream, string languageCode = "llt") using var reader = new StreamReader(stream); var templateContents = reader.ReadToEnd(); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); foreach (var template in templates) Add(template); } @@ -340,7 +364,7 @@ public void ImportFromFile(string filename) throw new ArgumentException($"No parser registered for language: '{languageCode}'."); var templateContents = File.ReadAllText(filename); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); AddRange(templates); } @@ -357,7 +381,7 @@ public void ImportFromFile(string filename, string languageCode) throw new ArgumentException($"No parser registered for language: '{languageCode}'."); var templateContents = File.ReadAllText(filename); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); AddRange(templates); } @@ -393,7 +417,7 @@ public IEnumerable ImportFromFolder(string folderPath, SearchO try { - var templates = parser.Parse(File.ReadAllText(file)); + var templates = parser.Parse(File.ReadAllText(file), MetadataFactories); AddRange(templates); } catch (ParsingException ex) @@ -438,7 +462,7 @@ public List ImportFromAssembly(Assembly assembly, string? fold var stream = assembly.GetManifestResourceStream(resource); using var reader = new StreamReader(stream); var templateContents = reader.ReadToEnd(); - var templates = parser.Parse(templateContents); + var templates = parser.Parse(templateContents, MetadataFactories); AddRange(templates); } catch (ParsingException ex) diff --git a/tests/LLTSharp.Tests/BasicTemplateParsingTests.cs b/tests/LLTSharp.Tests/BasicTemplateParsingTests.cs index 5c10d58..fc90775 100644 --- a/tests/LLTSharp.Tests/BasicTemplateParsingTests.cs +++ b/tests/LLTSharp.Tests/BasicTemplateParsingTests.cs @@ -174,11 +174,7 @@ public void MultipleInlineVariables() """; var parser = new LLTParser(); - var ast = parser.ParseAST(templateStr); - var ctx = ast.Context; - var value = ast.GetValue>(); - - output.WriteLine(ctx.walkTrace.Render(400)); + parser.Parse(templateStr); } } } \ No newline at end of file diff --git a/tests/LLTSharp.Tests/TemplateLibraryTests.cs b/tests/LLTSharp.Tests/TemplateLibraryTests.cs index de9b1b1..3f80c83 100644 --- a/tests/LLTSharp.Tests/TemplateLibraryTests.cs +++ b/tests/LLTSharp.Tests/TemplateLibraryTests.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using LLTSharp.Locale; using LLTSharp.Metadata; +using LLTSharp.Metadata.Types; namespace LLTSharp.Tests { @@ -33,7 +35,7 @@ @template sample_template_another { @metadata { - lang: 'en_US', + lang: 'en-US', model: 'gpt-3.5-turbo' } This is another template for GPT-3.5-Turbo model. @@ -43,7 +45,7 @@ @template sample_template { @metadata { - lang: 'en_US', + lang: 'en-US', model: 'gpt-3.5-turbo' } This is template for GPT-3.5-Turbo model. @@ -53,22 +55,22 @@ @template sample_template { @metadata { - lang: 'en_US', + lang: 'en-US', model: 'gpt-4' } This is template for GPT-4 model. } """); - var template = lib.Retrieve("sample_template", new LanguageMetadata("en_US"), new TargetModelMetadata("gpt-3.5-turbo")); + var template = lib.Retrieve("sample_template", new LanguageMetadata("en-US"), new TargetModelMetadata("gpt-3.5-turbo")); var rendered = template.Render(); Assert.Equal("This is template for GPT-3.5-Turbo model.", rendered); - template = lib.Retrieve("sample_template", new LanguageMetadata("en_US"), new TargetModelMetadata("gpt-4")); + template = lib.Retrieve("sample_template", new LanguageMetadata("en-US"), new TargetModelMetadata("gpt-4")); rendered = template.Render(); Assert.Equal("This is template for GPT-4 model.", rendered); - Assert.Throws(() => lib.Retrieve("sample_template", new LanguageMetadata("fr_FR"))); + Assert.Throws(() => lib.Retrieve("sample_template", new LanguageMetadata("fr-FR"))); } [Fact] @@ -76,6 +78,8 @@ public void TemplatePriorityByMetadataSpecificity() { var lib = new TemplateLibrary(); + lib.SetLanguageFallbackScheme(new HierarchicalLanguageFallbackScheme()); + lib.ImportFromString( """ @template greeting @@ -112,6 +116,10 @@ @template greeting var lessSpecific = lib.Retrieve("greeting", new LanguageMetadata("en")); Assert.Equal("Hello!", lessSpecific.Render().ToString()); + // Fallback to the most specific language-specific template + var lessSpecific2 = lib.RetrieveWithFallback("greeting", new LanguageMetadata("en-US")); + Assert.Equal("Hello!", lessSpecific2.Render().ToString()); + // Most general template. var generic = lib.Retrieve("greeting"); Assert.Equal("Generic greeting", generic.Render().ToString()); From 94633d6593d4b5ce194df8a652cc58b0a9544ce6 Mon Sep 17 00:00:00 2001 From: Roman Kondrat'ev <62770895+RomeCore@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:08:50 +0500 Subject: [PATCH 2/2] Change version to 1.2.3 --- src/LLTSharp/LLTSharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LLTSharp/LLTSharp.csproj b/src/LLTSharp/LLTSharp.csproj index 2bee7a1..85fdda4 100644 --- a/src/LLTSharp/LLTSharp.csproj +++ b/src/LLTSharp/LLTSharp.csproj @@ -8,7 +8,7 @@ LLTSharp - 1.2.2 + 1.2.3 Roman K. RomeCore LLTSharp