mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 11:19:56 +00:00
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> The existing Exceptions structure was very much inspired by dotnet, this is now replaced with a pythonic implementations. This means all Exceptions derive from KernelException and then specialise for different purposes. Closes #2194 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Added folder for all exception types, all can be imported through `from semantic_kernel.exceptions import ...` no need for a user to know which file the relevant one is in, but keeps things tidy for developers. Removed old KernelException, added back subtypes for the errorcodes. Went through everything to make sure the `raise ... from exc` pattern is used as much as possible as that returns a better stacktrace. ### 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 😄
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
import logging
|
|
from typing import List
|
|
|
|
from semantic_kernel.exceptions import CodeBlockSyntaxError
|
|
from semantic_kernel.template_engine.blocks.block import Block
|
|
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
|
|
from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock
|
|
from semantic_kernel.template_engine.blocks.named_arg_block import NamedArgBlock
|
|
from semantic_kernel.template_engine.blocks.symbols import Symbols
|
|
from semantic_kernel.template_engine.blocks.val_block import ValBlock
|
|
from semantic_kernel.template_engine.blocks.var_block import VarBlock
|
|
|
|
logger: logging.Logger = logging.getLogger(__name__)
|
|
|
|
|
|
# BNF parsed by CodeTokenizer:
|
|
# [template] ::= "" | [variable] " " [template]
|
|
# | [value] " " [template]
|
|
# | [function-call] " " [template]
|
|
# [variable] ::= "$" [valid-name]
|
|
# [value] ::= "'" [text] "'" | '"' [text] '"'
|
|
# [function-call] ::= [function-id] | [function-id] [parameter]
|
|
# [parameter] ::= [variable] | [value]
|
|
class CodeTokenizer:
|
|
@staticmethod
|
|
def tokenize(text: str) -> List[Block]:
|
|
# Remove spaces, which are ignored anyway
|
|
text = text.strip() if text else ""
|
|
# Render None/empty to []
|
|
if not text:
|
|
return []
|
|
# 1 char only edge case, var and val blocks are invalid with one char, so it must be a function id block
|
|
if len(text) == 1:
|
|
return [FunctionIdBlock(content=text)]
|
|
|
|
# Track what type of token we're reading
|
|
current_token_type = None
|
|
|
|
# Track the content of the current token
|
|
current_token_content: List[str] = []
|
|
|
|
# Other state we need to track
|
|
text_value_delimiter = None
|
|
space_separator_found = False
|
|
skip_next_char = False
|
|
next_char = ""
|
|
blocks: List[Block] = []
|
|
|
|
for index, current_char in enumerate(text[:-1]):
|
|
next_char = text[index + 1]
|
|
|
|
if skip_next_char:
|
|
skip_next_char = False
|
|
continue
|
|
|
|
# First char is easy
|
|
if index == 0:
|
|
if current_char == Symbols.VAR_PREFIX:
|
|
current_token_type = BlockTypes.VARIABLE
|
|
elif current_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE):
|
|
current_token_type = BlockTypes.VALUE
|
|
text_value_delimiter = current_char
|
|
else:
|
|
current_token_type = BlockTypes.FUNCTION_ID
|
|
|
|
current_token_content.append(current_char)
|
|
continue
|
|
|
|
# While reading values between quotes
|
|
if current_token_type in (BlockTypes.VALUE, BlockTypes.NAMED_ARG):
|
|
# If the current char is escaping the next special char we:
|
|
# - skip the current char (escape char)
|
|
# - add the next char (special char)
|
|
# - jump to the one after (to handle "\\" properly)
|
|
if current_char == Symbols.ESCAPE_CHAR and next_char in (
|
|
Symbols.DBL_QUOTE,
|
|
Symbols.SGL_QUOTE,
|
|
Symbols.ESCAPE_CHAR,
|
|
):
|
|
current_token_content.append(next_char)
|
|
skip_next_char = True
|
|
continue
|
|
|
|
current_token_content.append(current_char)
|
|
|
|
# When we reach the end of the value, we add the block
|
|
if current_char == text_value_delimiter:
|
|
blocks.append(ValBlock(content="".join(current_token_content)))
|
|
current_token_content.clear()
|
|
current_token_type = None
|
|
space_separator_found = False
|
|
|
|
continue
|
|
|
|
# If we're not between quotes, a space signals the end of the current token
|
|
# Note: there might be multiple consecutive spaces
|
|
if current_char in (
|
|
Symbols.SPACE,
|
|
Symbols.NEW_LINE,
|
|
Symbols.CARRIAGE_RETURN,
|
|
Symbols.TAB,
|
|
):
|
|
if current_token_type == BlockTypes.VARIABLE:
|
|
blocks.append(VarBlock(content="".join(current_token_content)))
|
|
current_token_content.clear()
|
|
elif current_token_type == BlockTypes.FUNCTION_ID:
|
|
if Symbols.NAMED_ARG_BLOCK_SEPARATOR.value in current_token_content:
|
|
blocks.append(NamedArgBlock(content="".join(current_token_content)))
|
|
else:
|
|
blocks.append(FunctionIdBlock(content="".join(current_token_content)))
|
|
current_token_content.clear()
|
|
|
|
space_separator_found = True
|
|
current_token_type = None
|
|
|
|
continue
|
|
|
|
# If we're not inside a quoted value and we're not processing a space
|
|
current_token_content.append(current_char)
|
|
|
|
if current_token_type is None:
|
|
if not space_separator_found:
|
|
raise CodeBlockSyntaxError("Tokens must be separated by one space least")
|
|
|
|
if current_char in (Symbols.DBL_QUOTE, Symbols.SGL_QUOTE):
|
|
# A quoted value starts here
|
|
current_token_type = BlockTypes.VALUE
|
|
text_value_delimiter = current_char
|
|
elif current_char == Symbols.VAR_PREFIX:
|
|
# A variable starts here
|
|
current_token_type = BlockTypes.VARIABLE
|
|
else:
|
|
# A function id starts here
|
|
current_token_type = BlockTypes.FUNCTION_ID
|
|
|
|
# end of main for loop
|
|
|
|
# Capture last token
|
|
current_token_content.append(next_char)
|
|
|
|
if current_token_type == BlockTypes.VALUE:
|
|
blocks.append(ValBlock(content="".join(current_token_content)))
|
|
elif current_token_type == BlockTypes.VARIABLE:
|
|
blocks.append(VarBlock(content="".join(current_token_content)))
|
|
elif current_token_type == BlockTypes.FUNCTION_ID:
|
|
if Symbols.NAMED_ARG_BLOCK_SEPARATOR.value in current_token_content:
|
|
blocks.append(NamedArgBlock(content="".join(current_token_content)))
|
|
else:
|
|
blocks.append(FunctionIdBlock(content="".join(current_token_content)))
|
|
else:
|
|
raise CodeBlockSyntaxError("Tokens must be separated by one space least")
|
|
|
|
return blocks
|