Skip to content

feat(customs): 新增 ExpressionRecognition 表达式识别 custom 教程 - #8

Merged
kqcoxn merged 1 commit into
MaaXYZ:mainfrom
overflow65537:feat/custom
Jun 28, 2026
Merged

feat(customs): 新增 ExpressionRecognition 表达式识别 custom 教程#8
kqcoxn merged 1 commit into
MaaXYZ:mainfrom
overflow65537:feat/custom

Conversation

@overflow65537

@overflow65537 overflow65537 commented Jun 28, 2026

Copy link
Copy Markdown
Member

Summary by Sourcery

添加一个新的 ExpressionRecognition 自定义识别器,用于评估基于 OCR 的数值表达式,并提供相应的使用文档以及示例管线/元数据文件。

New Features:

  • 引入 ExpressionRecognition 自定义识别类,用于在管线中对来自 OCR 的数值进行算术和逻辑表达式评估。

Documentation:

  • 新增 README,记录 ExpressionRecognition 的使用方法、表达式语法、管线集成方式以及调试细节。

Chores:

  • 为 ExpressionRecognition 自定义识别器集成添加示例管线和 MaaHub 元数据文件。
Original summary in English

Summary by Sourcery

Add a new ExpressionRecognition custom recognizer that evaluates OCR-based numeric expressions and provide accompanying usage documentation and sample pipeline/metadata files.

New Features:

  • Introduce ExpressionRecognition custom recognition class to evaluate arithmetic and logical expressions over OCR-derived numeric values within pipelines.

Documentation:

  • Add README documenting ExpressionRecognition usage, expression syntax, pipeline integration, and debugging details.

Chores:

  • Add sample pipeline and MaaHub metadata files for ExpressionRecognition custom recognizer integration.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 2 个问题

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Storage/customs/overflow65537/expression-recognition/ExpressionRecognition.py" line_range="183-168" />
<code_context>
+            return box_index
+        return None
+
+    def _extract_text(self, result: Any, box_index: int | None = None) -> str | None:
+        if box_index is not None:
+            indexed_text = self._extract_text_by_index(result, box_index)
+            if indexed_text is not None:
+                return indexed_text
+
+        if isinstance(result, dict):
+            direct_text = result.get("text")
+            if isinstance(direct_text, str):
+                return direct_text
+
+            for key in ("best", "best_result", "detail", "filtered", "filtered_results", "all"):
+                if key in result:
+                    nested_text = self._extract_text(result[key])
+                    if nested_text is not None:
+                        return nested_text
+            return None
+
+        if isinstance(result, Iterable) and not isinstance(result, (str, bytes, bytearray)):
</code_context>
<issue_to_address>
**issue:** Guard against potential cycles in recognition objects during text extraction

这些辅助函数会在多个属性之间递归(`detail``raw_detail``best_result``filtered_results``sub_results` 等)。如果识别对象图出现循环,就可能导致无限递归并造成栈溢出。建议在递归调用过程中传递一个包含对象 ID 的 `visited` 集合,并跳过已经访问过的对象,以此在保持当前无环图行为不变的前提下,对可能的循环进行防护。
</issue_to_address>

### Comment 2
<location path="Storage/customs/overflow65537/expression-recognition/ExpressionRecognition.py" line_range="406-409" />
<code_context>
+            return text
+        return f"{text[:limit]}...<truncated>"
+
+    def _normalize_expression(self, expression: str) -> str:
+        normalized = expression.replace("&&", " and ").replace("||", " or ")
+        normalized = re.sub(r"!(?!=)", " not ", normalized)
+        return normalized
+
+    def _validate_ast(self, node: ast.AST, allowed_names: set[str]) -> None:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Clarify or tighten normalization around logical operators and negation

使用 `re.sub(r"!(?!=)", " not ", normalized)` 也会把 `a!b` 重写为 `a not b`,这样任何出现在非布尔或紧邻标识符场景中的 `!` 都会被默默接受,而不是被拒绝。建议将其限制为单独的 `!` 记号(例如要求前后有空白)或者在解析前增加预验证步骤,对意外的 `!` 用法抛出更清晰的错误。

```suggestion
    def _normalize_expression(self, expression: str) -> str:
        # Reject clearly invalid uses of "!" (e.g. in the middle of identifiers or operands),
        # instead of silently treating them as logical negation.
        #
        # Example invalid patterns: "a!b", "foo!bar"
        # Valid patterns that are still supported:
        #   "!foo", "a && !b", "x != y"
        if re.search(r"\w!\w", expression):
            raise ValueError(
                "Invalid '!' usage in expression: '!' may only be used as boolean "
                "negation (e.g. '!x') or as part of '!=' comparison."
            )

        normalized = expression.replace("&&", " and ").replace("||", " or ")
        # Normalize standalone logical negation, but leave '!=' untouched.
        normalized = re.sub(r"!(?!=)", " not ", normalized)
        return normalized
```
</issue_to_address>

Sourcery 对开源项目是免费的 - 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进对你代码的评审。
Original comment in English

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Storage/customs/overflow65537/expression-recognition/ExpressionRecognition.py" line_range="183-168" />
<code_context>
+            return box_index
+        return None
+
+    def _extract_text(self, result: Any, box_index: int | None = None) -> str | None:
+        if box_index is not None:
+            indexed_text = self._extract_text_by_index(result, box_index)
+            if indexed_text is not None:
+                return indexed_text
+
+        if isinstance(result, dict):
+            direct_text = result.get("text")
+            if isinstance(direct_text, str):
+                return direct_text
+
+            for key in ("best", "best_result", "detail", "filtered", "filtered_results", "all"):
+                if key in result:
+                    nested_text = self._extract_text(result[key])
+                    if nested_text is not None:
+                        return nested_text
+            return None
+
+        if isinstance(result, Iterable) and not isinstance(result, (str, bytes, bytearray)):
</code_context>
<issue_to_address>
**issue:** Guard against potential cycles in recognition objects during text extraction

These helpers recurse through several attributes (`detail`, `raw_detail`, `best_result`, `filtered_results`, `sub_results`, etc.). If the recognition object graph ever becomes cyclic, this can lead to unbounded recursion and a stack overflow. Consider passing a `visited` set of object ids through the recursive calls and skipping already‑seen objects to guard against cycles while preserving current behavior for acyclic graphs.
</issue_to_address>

### Comment 2
<location path="Storage/customs/overflow65537/expression-recognition/ExpressionRecognition.py" line_range="406-409" />
<code_context>
+            return text
+        return f"{text[:limit]}...<truncated>"
+
+    def _normalize_expression(self, expression: str) -> str:
+        normalized = expression.replace("&&", " and ").replace("||", " or ")
+        normalized = re.sub(r"!(?!=)", " not ", normalized)
+        return normalized
+
+    def _validate_ast(self, node: ast.AST, allowed_names: set[str]) -> None:
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Clarify or tighten normalization around logical operators and negation

Using `re.sub(r"!(?!=)", " not ", normalized)` will also rewrite `a!b` to `a not b`, so any stray `!` in non‑boolean or adjacent‑identifier contexts is silently accepted instead of rejected. Consider restricting this to `!` as a separate token (e.g., with surrounding whitespace) or adding a pre‑validation step that raises a clearer error for unexpected `!` usage before parsing.

```suggestion
    def _normalize_expression(self, expression: str) -> str:
        # Reject clearly invalid uses of "!" (e.g. in the middle of identifiers or operands),
        # instead of silently treating them as logical negation.
        #
        # Example invalid patterns: "a!b", "foo!bar"
        # Valid patterns that are still supported:
        #   "!foo", "a && !b", "x != y"
        if re.search(r"\w!\w", expression):
            raise ValueError(
                "Invalid '!' usage in expression: '!' may only be used as boolean "
                "negation (e.g. '!x') or as part of '!=' comparison."
            )

        normalized = expression.replace("&&", " and ").replace("||", " or ")
        # Normalize standalone logical negation, but leave '!=' untouched.
        normalized = re.sub(r"!(?!=)", " not ", normalized)
        return normalized
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.


def _get_box_index(self, node_data: Any) -> int | None:
if not isinstance(node_data, dict):
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Guard against potential cycles in recognition objects during text extraction

这些辅助函数会在多个属性之间递归(detailraw_detailbest_resultfiltered_resultssub_results 等)。如果识别对象图出现循环,就可能导致无限递归并造成栈溢出。建议在递归调用过程中传递一个包含对象 ID 的 visited 集合,并跳过已经访问过的对象,以此在保持当前无环图行为不变的前提下,对可能的循环进行防护。

Original comment in English

issue: Guard against potential cycles in recognition objects during text extraction

These helpers recurse through several attributes (detail, raw_detail, best_result, filtered_results, sub_results, etc.). If the recognition object graph ever becomes cyclic, this can lead to unbounded recursion and a stack overflow. Consider passing a visited set of object ids through the recursive calls and skipping already‑seen objects to guard against cycles while preserving current behavior for acyclic graphs.

Comment on lines +406 to +409
def _normalize_expression(self, expression: str) -> str:
normalized = expression.replace("&&", " and ").replace("||", " or ")
normalized = re.sub(r"!(?!=)", " not ", normalized)
return normalized

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Clarify or tighten normalization around logical operators and negation

使用 re.sub(r"!(?!=)", " not ", normalized) 也会把 a!b 重写为 a not b,这样任何出现在非布尔或紧邻标识符场景中的 ! 都会被默默接受,而不是被拒绝。建议将其限制为单独的 ! 记号(例如要求前后有空白)或者在解析前增加预验证步骤,对意外的 ! 用法抛出更清晰的错误。

Suggested change
def _normalize_expression(self, expression: str) -> str:
normalized = expression.replace("&&", " and ").replace("||", " or ")
normalized = re.sub(r"!(?!=)", " not ", normalized)
return normalized
def _normalize_expression(self, expression: str) -> str:
# Reject clearly invalid uses of "!" (e.g. in the middle of identifiers or operands),
# instead of silently treating them as logical negation.
#
# Example invalid patterns: "a!b", "foo!bar"
# Valid patterns that are still supported:
# "!foo", "a && !b", "x != y"
if re.search(r"\w!\w", expression):
raise ValueError(
"Invalid '!' usage in expression: '!' may only be used as boolean "
"negation (e.g. '!x') or as part of '!=' comparison."
)
normalized = expression.replace("&&", " and ").replace("||", " or ")
# Normalize standalone logical negation, but leave '!=' untouched.
normalized = re.sub(r"!(?!=)", " not ", normalized)
return normalized
Original comment in English

suggestion (bug_risk): Clarify or tighten normalization around logical operators and negation

Using re.sub(r"!(?!=)", " not ", normalized) will also rewrite a!b to a not b, so any stray ! in non‑boolean or adjacent‑identifier contexts is silently accepted instead of rejected. Consider restricting this to ! as a separate token (e.g., with surrounding whitespace) or adding a pre‑validation step that raises a clearer error for unexpected ! usage before parsing.

Suggested change
def _normalize_expression(self, expression: str) -> str:
normalized = expression.replace("&&", " and ").replace("||", " or ")
normalized = re.sub(r"!(?!=)", " not ", normalized)
return normalized
def _normalize_expression(self, expression: str) -> str:
# Reject clearly invalid uses of "!" (e.g. in the middle of identifiers or operands),
# instead of silently treating them as logical negation.
#
# Example invalid patterns: "a!b", "foo!bar"
# Valid patterns that are still supported:
# "!foo", "a && !b", "x != y"
if re.search(r"\w!\w", expression):
raise ValueError(
"Invalid '!' usage in expression: '!' may only be used as boolean "
"negation (e.g. '!x') or as part of '!=' comparison."
)
normalized = expression.replace("&&", " and ").replace("||", " or ")
# Normalize standalone logical negation, but leave '!=' untouched.
normalized = re.sub(r"!(?!=)", " not ", normalized)
return normalized

@kqcoxn
kqcoxn merged commit 7c6f179 into MaaXYZ:main Jun 28, 2026
3 checks passed
@overflow65537
overflow65537 deleted the feat/custom branch June 28, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants