Skip to content

Commit e2bad31

Browse files
.Net: [Error handling] [Part5] Replacing TemplateException exception by SKException (#2468)
### Motivation and Context This PR is one of the follow-up PRs aimed at gradually replacing SK custom exceptions with SKException to provide a simple, consistent, and extensible exception model. You can find more details in the [ADR](https://github.com/microsoft/semantic-kernel/blob/main/docs/decisions/0004-error-handling.md) discussing error handling. ### Description This PR updates the template engine functionality to throw an SKException instead of a TemplateException and removes the TemplateException one. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent 11f010b commit e2bad31

File tree

13 files changed

+38
-199
lines changed

13 files changed

+38
-199
lines changed

dotnet/src/IntegrationTests/TemplateLanguage/PromptTemplateEngineTests.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Threading.Tasks;
88
using Microsoft.SemanticKernel;
9+
using Microsoft.SemanticKernel.Diagnostics;
910
using Microsoft.SemanticKernel.SkillDefinition;
1011
using Microsoft.SemanticKernel.TemplateEngine;
1112
using Xunit;
@@ -144,7 +145,7 @@ public async Task ItHandleEdgeCasesAsync(string template, string expectedResult)
144145
this._logger.WriteLine("expected: " + expectedResult);
145146
if (expectedResult.StartsWith("ERROR", StringComparison.OrdinalIgnoreCase))
146147
{
147-
await Assert.ThrowsAsync<TemplateException>(
148+
await Assert.ThrowsAsync<SKException>(
148149
async () => await this._target.RenderAsync(template, kernel.CreateNewContext()));
149150
}
150151
else

dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs

+5-11
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
using Microsoft.Extensions.Logging;
88
using Microsoft.Extensions.Logging.Abstractions;
99
using Microsoft.SemanticKernel.AI.TextCompletion;
10+
using Microsoft.SemanticKernel.Diagnostics;
1011
using Microsoft.SemanticKernel.Orchestration;
1112
using Microsoft.SemanticKernel.SkillDefinition;
12-
using Microsoft.SemanticKernel.TemplateEngine;
1313
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
1414
using Moq;
1515
using Xunit;
@@ -35,11 +35,8 @@ public async Task ItThrowsIfAFunctionDoesntExistAsync()
3535
this._skills.Setup(x => x.TryGetFunction("functionName", out It.Ref<ISKFunction?>.IsAny)).Returns(false);
3636
var target = new CodeBlock("functionName", this._logger.Object);
3737

38-
// Act
39-
var exception = await Assert.ThrowsAsync<TemplateException>(async () => await target.RenderCodeAsync(context));
40-
41-
// Assert
42-
Assert.Equal(TemplateException.ErrorCodes.FunctionNotFound, exception.ErrorCode);
38+
// Act & Assert
39+
await Assert.ThrowsAsync<SKException>(async () => await target.RenderCodeAsync(context));
4340
}
4441

4542
[Fact]
@@ -56,11 +53,8 @@ public async Task ItThrowsIfAFunctionCallThrowsAsync()
5653
this._skills.Setup(x => x.GetFunction("functionName")).Returns(function.Object);
5754
var target = new CodeBlock("functionName", this._logger.Object);
5855

59-
// Act
60-
var exception = await Assert.ThrowsAsync<TemplateException>(async () => await target.RenderCodeAsync(context));
61-
62-
// Assert
63-
Assert.Equal(TemplateException.ErrorCodes.RuntimeError, exception.ErrorCode);
56+
// Act & Assert
57+
await Assert.ThrowsAsync<SKException>(async () => await target.RenderCodeAsync(context));
6458
}
6559

6660
[Fact]

dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/FunctionIdBlockTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

33
using Microsoft.Extensions.Logging.Abstractions;
4-
using Microsoft.SemanticKernel.TemplateEngine;
4+
using Microsoft.SemanticKernel.Diagnostics;
55
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
66
using Xunit;
77

@@ -86,7 +86,7 @@ public void ItAllowsOnlyOneDot()
8686
// Arrange
8787
var target1 = new FunctionIdBlock("functionName");
8888
var target2 = new FunctionIdBlock("skillName.functionName");
89-
Assert.Throws<TemplateException>(() => new FunctionIdBlock("foo.skillName.functionName"));
89+
Assert.Throws<SKException>(() => new FunctionIdBlock("foo.skillName.functionName"));
9090

9191
// Act + Assert
9292
Assert.True(target1.IsValid(out _));

dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/VarBlockTests.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using Microsoft.SemanticKernel.Diagnostics;
34
using Microsoft.SemanticKernel.Orchestration;
4-
using Microsoft.SemanticKernel.TemplateEngine;
55
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
66
using Xunit;
77

@@ -96,8 +96,7 @@ public void ItThrowsIfTheVarNameIsEmpty()
9696
var target = new VarBlock(" $ ");
9797

9898
// Act + Assert
99-
var ex = Assert.Throws<TemplateException>(() => target.Render(variables));
100-
Assert.Equal(TemplateException.ErrorCodes.SyntaxError, ex.ErrorCode);
99+
Assert.Throws<SKException>(() => target.Render(variables));
101100
}
102101

103102
[Theory]

dotnet/src/SemanticKernel.UnitTests/TemplateEngine/CodeTokenizerTests.cs

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using Microsoft.SemanticKernel.Diagnostics;
34
using Microsoft.SemanticKernel.TemplateEngine;
45
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
56
using Xunit;
@@ -129,15 +130,12 @@ public void ItSupportsEscaping()
129130
Assert.Equal("'f\'oo'", blocks[1].Content);
130131
}
131132

132-
[Fact]
133-
public void ItThrowsWhenSeparatorsAreMissing()
133+
[Theory]
134+
[InlineData(@"call 'f\\'xy'")]
135+
[InlineData(@"call 'f\\'x")]
136+
public void ItThrowsWhenSeparatorsAreMissing(string template)
134137
{
135-
// Arrange
136-
var template1 = @"call 'f\\'xy'";
137-
var template2 = @"call 'f\\'x";
138-
139-
// Act
140-
Assert.Throws<TemplateException>(() => this._target.Tokenize(template1));
141-
Assert.Throws<TemplateException>(() => this._target.Tokenize(template2));
138+
// Act & Assert
139+
Assert.Throws<SKException>(() => this._target.Tokenize(template));
142140
}
143141
}

dotnet/src/SemanticKernel.UnitTests/TemplateEngine/TemplateExceptionTests.cs

-63
This file was deleted.

dotnet/src/SemanticKernel/TemplateEngine/Blocks/CodeBlock.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public async Task<string> RenderCodeAsync(SKContext context, CancellationToken c
8888
{
8989
if (!this._validated && !this.IsValid(out var error))
9090
{
91-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, error);
91+
throw new SKException(error);
9292
}
9393

9494
this.Logger.LogTrace("Rendering code: `{0}`", this.Content);
@@ -103,8 +103,7 @@ public async Task<string> RenderCodeAsync(SKContext context, CancellationToken c
103103
return await this.RenderFunctionCallAsync((FunctionIdBlock)this._tokens[0], context).ConfigureAwait(false);
104104
}
105105

106-
throw new TemplateException(TemplateException.ErrorCodes.UnexpectedBlockType,
107-
$"Unexpected first token type: {this._tokens[0].Type:G}");
106+
throw new SKException($"Unexpected first token type: {this._tokens[0].Type:G}");
108107
}
109108

110109
#region private ================================================================================
@@ -123,7 +122,7 @@ private async Task<string> RenderFunctionCallAsync(FunctionIdBlock fBlock, SKCon
123122
{
124123
var errorMsg = $"Function `{fBlock.Content}` not found";
125124
this.Logger.LogError(errorMsg);
126-
throw new TemplateException(TemplateException.ErrorCodes.FunctionNotFound, errorMsg);
125+
throw new SKException(errorMsg);
127126
}
128127

129128
SKContext contextClone = context.Clone();
@@ -155,7 +154,7 @@ private async Task<string> RenderFunctionCallAsync(FunctionIdBlock fBlock, SKCon
155154
{
156155
var errorMsg = $"Function `{fBlock.Content}` execution failed. {contextClone.LastException?.GetType().FullName}: {contextClone.LastException?.Message}";
157156
this.Logger.LogError(errorMsg);
158-
throw new TemplateException(TemplateException.ErrorCodes.RuntimeError, errorMsg, contextClone.LastException);
157+
throw new SKException(errorMsg, contextClone.LastException);
159158
}
160159

161160
return contextClone.Result;

dotnet/src/SemanticKernel/TemplateEngine/Blocks/FunctionIdBlock.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Text.RegularExpressions;
55
using Microsoft.Extensions.Logging;
6+
using Microsoft.SemanticKernel.Diagnostics;
67
using Microsoft.SemanticKernel.Orchestration;
78

89
namespace Microsoft.SemanticKernel.TemplateEngine.Blocks;
@@ -21,9 +22,8 @@ public FunctionIdBlock(string? text, ILogger? logger = null)
2122
var functionNameParts = this.Content.Split('.');
2223
if (functionNameParts.Length > 2)
2324
{
24-
this.Logger.LogError("Invalid function name `{0}`", this.Content);
25-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError,
26-
"A function name can contain at most one dot separating the skill name from the function name");
25+
this.Logger.LogError("Invalid function name `{FunctionName}`.", this.Content);
26+
throw new SKException($"Invalid function name `{this.Content}`. A function name can contain at most one dot separating the skill name from the function name");
2727
}
2828

2929
if (functionNameParts.Length == 2)

dotnet/src/SemanticKernel/TemplateEngine/Blocks/VarBlock.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System.Text.RegularExpressions;
44
using Microsoft.Extensions.Logging;
5+
using Microsoft.SemanticKernel.Diagnostics;
56
using Microsoft.SemanticKernel.Orchestration;
67

78
namespace Microsoft.SemanticKernel.TemplateEngine.Blocks;
@@ -70,7 +71,7 @@ public string Render(ContextVariables? variables)
7071
{
7172
const string ErrMsg = "Variable rendering failed, the variable name is empty";
7273
this.Logger.LogError(ErrMsg);
73-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, ErrMsg);
74+
throw new SKException(ErrMsg);
7475
}
7576

7677
if (variables.TryGetValue(this.Name, out string? value))

dotnet/src/SemanticKernel/TemplateEngine/CodeTokenizer.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using Microsoft.Extensions.Logging;
66
using Microsoft.Extensions.Logging.Abstractions;
7+
using Microsoft.SemanticKernel.Diagnostics;
78
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
89
using Microsoft.SemanticKernel.Text;
910

@@ -188,8 +189,7 @@ public List<Block> Tokenize(string? text)
188189
{
189190
if (!spaceSeparatorFound)
190191
{
191-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError,
192-
"Tokens must be separated by one space least");
192+
throw new SKException("Tokens must be separated by one space least");
193193
}
194194

195195
if (IsQuote(currentChar))
@@ -228,8 +228,7 @@ public List<Block> Tokenize(string? text)
228228
break;
229229

230230
case TokenTypes.None:
231-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError,
232-
"Tokens must be separated by one space least");
231+
throw new SKException("Tokens must be separated by one space least");
233232
}
234233

235234
return blocks;

dotnet/src/SemanticKernel/TemplateEngine/PromptTemplateEngine.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using Microsoft.Extensions.Logging;
99
using Microsoft.Extensions.Logging.Abstractions;
10+
using Microsoft.SemanticKernel.Diagnostics;
1011
using Microsoft.SemanticKernel.Orchestration;
1112
using Microsoft.SemanticKernel.TemplateEngine.Blocks;
1213

@@ -46,7 +47,7 @@ public IList<Block> ExtractBlocks(string? templateText, bool validate = true)
4647
{
4748
if (!block.IsValid(out var error))
4849
{
49-
throw new TemplateException(TemplateException.ErrorCodes.SyntaxError, error);
50+
throw new SKException(error);
5051
}
5152
}
5253
}
@@ -88,7 +89,7 @@ internal async Task<string> RenderAsync(IList<Block> blocks, SKContext context,
8889
default:
8990
const string Error = "Unexpected block type, the block doesn't have a rendering method";
9091
this._logger.LogError(Error);
91-
throw new TemplateException(TemplateException.ErrorCodes.UnexpectedBlockType, Error);
92+
throw new SKException(Error);
9293
}
9394
}
9495

0 commit comments

Comments
 (0)