* add cache option

* app ! to global

* apply reexecute

* add more test and renaming

* revert the schema file

* fix typo

* rename template -> locale
This commit is contained in:
Hongyang Du (hond) 2020-09-11 06:42:03 +08:00 коммит произвёл GitHub
Родитель a69996b46f
Коммит dff5dc6611
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 103 добавлений и 22 удалений

Просмотреть файл

@ -24,6 +24,27 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
Markdown,
}
/// <summary>
/// LG cache scope options.
/// </summary>
public enum LGCacheScope
{
/// <summary>
/// Global template cache scope.
/// </summary>
Global,
/// <summary>
/// Only cache result in the same layer of children in template.
/// </summary>
Local,
/// <summary>
/// Without cache.
/// </summary>
None
}
/// <summary>
/// Options for evaluating LG templates.
/// </summary>
@ -43,6 +64,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
NullSubstitution = null;
LineBreakStyle = null;
Locale = null;
CacheScope = null;
}
/// <summary>
@ -55,6 +77,7 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
NullSubstitution = opt.NullSubstitution;
LineBreakStyle = opt.LineBreakStyle;
Locale = opt.Locale ?? Thread.CurrentThread.CurrentCulture.Name;
CacheScope = opt.CacheScope;
}
/// <summary>
@ -125,6 +148,14 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
/// </value>
public Func<string, object> NullSubstitution { get; set; } = null;
/// <summary>
/// Gets or sets cache scope of the evaluation result.
/// </summary>
/// <value>
/// Cache scope of the evaluation result.
/// </value>
public LGCacheScope? CacheScope { get; set; } = null;
/// <summary>
/// Merge a incoming option to current option. If a property in incoming option is not null while it is null in current
/// option, then the value of this property will be overwritten.

Просмотреть файл

@ -23,12 +23,12 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
}
/// <summary>
/// Gets or sets the children template that this template has evaluated currently.
/// Gets or sets the children template that this template has evaluated and cached currently.
/// </summary>
/// <value>
/// The children template that this template has evaluated currently.
/// </value>
public Dictionary<string, object> EvaluatedChildren { get; set; } = new Dictionary<string, object>();
public Dictionary<string, object> CachedEvaluatedChildren { get; set; } = new Dictionary<string, object>();
/// <summary>
/// Gets or sets template name.

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -23,6 +24,8 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
internal const string LGType = "lgType";
private const string ReExecuteSuffix = "!";
private static readonly ConcurrentDictionary<string, object> _cachedResult = new ConcurrentDictionary<string, object>();
private readonly Stack<EvaluationTarget> _evaluationTargetStack = new Stack<EvaluationTarget>();
private readonly EvaluationOptions _lgOptions;
@ -85,7 +88,6 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
}
var templateTarget = new EvaluationTarget(templateName, memory);
var currentEvaluateId = templateTarget.GetId();
if (_evaluationTargetStack.Any(e => e.GetId() == currentEvaluateId))
@ -93,26 +95,55 @@ namespace Microsoft.Bot.Builder.LanguageGeneration
throw new Exception($"{TemplateErrors.LoopDetected} {string.Join(" => ", _evaluationTargetStack.Reverse().Select(e => e.TemplateName))} => {templateName}");
}
EvaluationTarget previousEvaluateTarget = null;
if (_evaluationTargetStack.Count != 0)
object result = null;
var hasResult = false;
if (!reExecute)
{
previousEvaluateTarget = _evaluationTargetStack.Peek();
if (!reExecute && previousEvaluateTarget.EvaluatedChildren.ContainsKey(currentEvaluateId))
if (_lgOptions.CacheScope == LGCacheScope.Global)
{
return previousEvaluateTarget.EvaluatedChildren[currentEvaluateId];
if (_cachedResult.ContainsKey(currentEvaluateId))
{
result = _cachedResult[currentEvaluateId];
hasResult = true;
}
}
else if (_lgOptions.CacheScope == null || _lgOptions.CacheScope == LGCacheScope.Local)
{
EvaluationTarget previousEvaluateTarget = null;
if (_evaluationTargetStack.Count != 0)
{
previousEvaluateTarget = _evaluationTargetStack.Peek();
if (previousEvaluateTarget.CachedEvaluatedChildren.ContainsKey(currentEvaluateId))
{
result = previousEvaluateTarget.CachedEvaluatedChildren[currentEvaluateId];
hasResult = true;
}
}
}
}
// Using a stack to track the evaluation trace
_evaluationTargetStack.Push(templateTarget);
var result = Visit(TemplateMap[templateName].TemplateBodyParseTree);
if (previousEvaluateTarget != null)
if (!hasResult)
{
previousEvaluateTarget.EvaluatedChildren[currentEvaluateId] = result;
}
_evaluationTargetStack.Push(templateTarget);
result = Visit(TemplateMap[templateName].TemplateBodyParseTree);
_evaluationTargetStack.Pop();
_evaluationTargetStack.Pop();
if (!reExecute)
{
if (_lgOptions.CacheScope == LGCacheScope.Global)
{
_cachedResult[currentEvaluateId] = result;
}
else if (_lgOptions.CacheScope == null || _lgOptions.CacheScope == LGCacheScope.Local)
{
if (_evaluationTargetStack.Count > 0)
{
_evaluationTargetStack.Peek().CachedEvaluatedChildren[currentEvaluateId] = result;
}
}
}
}
return result;
}

Просмотреть файл

@ -1,5 +1,5 @@
# templateWithSameParams(param)
- ${listTemplate1(param)} ${listTemplate1!(param)}
- ${listTemplate1(param)} ${listTemplate1!(param)} ${listTemplate1(param)}
# listTemplate1(param)
- item1

Просмотреть файл

@ -9,4 +9,11 @@
- item2
- item3
- item4
- item5
- item5
# globalCache(param)
- ${subTemplate(param)} ${listTemplate1(param)}
# subTemplate(param)
- ${listTemplate1(param)}

Просмотреть файл

@ -51,13 +51,13 @@
<None Remove="Examples\NonAdaptiveCardActivity.lg" />
<None Remove="Examples\StrictModeFalse.lg" />
<None Remove="Examples\switchcase.lg" />
<None Remove="Examples\TemplateCache.lg" />
<None Remove="Examples\TemplateNameWithDot.lg" />
<None Remove="Examples\TemplateRef.lg" />
<None Remove="Examples\Regex.lg" />
<None Remove="Examples\EvalExpression.lg" />
<None Remove="Examples\StructuredTemplate.lg" />
<None Remove="Examples\ConditionExpression.lg" />
<None Remove="Examples\EvaluateOnce.lg" />
<None Remove="Examples\LoopScope.lg" />
<None Remove="Examples\ExpressionExtract.lg" />
<None Remove="Examples\StringInterpolation.lg" />
@ -233,7 +233,7 @@
<Content Include="Examples\StructuredTemplate.lg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Examples\EvaluateOnce.lg">
<Content Include="Examples\TemplateCache.lg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Examples\ReExecute.lg">

Просмотреть файл

@ -1107,9 +1107,9 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
}
[Fact]
public void TestEvaluateOnce()
public void TestTemplateCache()
{
var templates = Templates.ParseFile(GetExampleFilePath("EvaluateOnce.lg"));
var templates = Templates.ParseFile(GetExampleFilePath("TemplateCache.lg"));
var evaled = templates.Evaluate("templateWithSameParams", new { param = "ms" });
Assert.NotNull(evaled);
@ -1120,6 +1120,13 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
// may be has different values
evaled = templates.Evaluate("templateWithDifferentParams", new { param1 = "ms", param2 = "newms" });
// global cache test
evaled = templates.Evaluate("globalCache", new { param = "ms" }, new EvaluationOptions { CacheScope = LGCacheScope.Global });
resultList = evaled.ToString().Split(" ");
Assert.True(resultList.Length == 2);
Assert.True(resultList[0] == resultList[1]);
}
[Fact]
@ -1129,6 +1136,11 @@ namespace Microsoft.Bot.Builder.AI.LanguageGeneration.Tests
// may be has different values
var evaled = templates.Evaluate("templateWithSameParams", new { param1 = "ms", param2 = "newms" });
// the third one should be the same with the first one
var resultList = evaled.ToString().Split(" ");
Assert.True(resultList.Length == 3);
Assert.True(resultList[0] == resultList[2]);
}
[Fact]