Skip to content

Commit 178ee9f

Browse files
authored
Use compiled regex objects instead of raw pattern strings. (#48)
1 parent d722e38 commit 178ee9f

File tree

9 files changed

+56
-69
lines changed

9 files changed

+56
-69
lines changed

openjudge/graders/agent/tool/tool_call_accuracy.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ def __init__(
220220
language=language,
221221
)
222222

223+
# Pattern to match tool calls in JSON format
224+
self._tool_call_pattern = re.compile(
225+
r'\{\s*"name"\s*:\s*"[^"]*"\s*,\s*"arguments"\s*:\s*\{.*?\}\s*\}', flags=re.DOTALL
226+
)
227+
223228
def _parse_tools_from_response(
224229
self,
225230
response: str,
@@ -233,10 +238,7 @@ def _parse_tools_from_response(
233238
List of parsed tool calls.
234239
"""
235240
tool_calls = []
236-
237-
# Pattern to match tool calls in JSON format
238-
tool_call_pattern = r'\{\s*"name"\s*:\s*"[^"]*"\s*,\s*"arguments"\s*:\s*\{.*?\}\s*\}'
239-
matches = re.findall(tool_call_pattern, response, re.DOTALL)
241+
matches = self._tool_call_pattern.findall(response)
240242

241243
for match in matches:
242244
try:

openjudge/graders/agent/tool/tool_call_success.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"""
77

88
import json
9-
import re
109
import textwrap
1110
from typing import Any, Dict, List, Optional, Union
1211

@@ -244,34 +243,6 @@ def __init__(
244243
)
245244
self.template = template or DEFAULT_TOOL_CALL_SUCCESS_TEMPLATE
246245

247-
def _parse_tools_from_response(
248-
self,
249-
response: str,
250-
) -> List[Dict[str, Any]]:
251-
"""Extract tool calls from the response.
252-
253-
Args:
254-
response: The response string to extract tool calls from.
255-
256-
Returns:
257-
List of parsed tool calls.
258-
"""
259-
tool_calls = []
260-
261-
# Pattern to match tool calls in JSON format
262-
tool_call_pattern = r'\{\s*"name"\s*:\s*"[^"]*"\s*,\s*"arguments"\s*:\s*\{.*?\}\s*\}'
263-
matches = re.findall(tool_call_pattern, response, re.DOTALL)
264-
265-
for match in matches:
266-
try:
267-
tool_call = json.loads(match)
268-
tool_calls.append(tool_call)
269-
except json.JSONDecodeError:
270-
# Skip invalid JSON
271-
continue
272-
273-
return tool_calls
274-
275246
async def aevaluate(
276247
self,
277248
tool_definitions: Union[Dict[str, Any], List[Dict[str, Any]]],

openjudge/graders/code/code_excution.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ def __init__(
6060
)
6161
self.test_framework_available = False
6262

63+
# Python code pattern in various formats
64+
self._python_code_pattern = re.compile(r"```python\n(.*?)\n```", flags=re.DOTALL)
65+
# generic code formats
66+
self._generic_code_pattern = re.compile(r"```\n(.*?)\n```", flags=re.DOTALL)
67+
6368
def _extract_code(self, content: str) -> str:
6469
"""
6570
Extract code from content
@@ -71,12 +76,12 @@ def _extract_code(self, content: str) -> str:
7176
Extracted code
7277
"""
7378
# Try to find Python code in various formats
74-
code_match = re.search(r"```python\n(.*?)\n```", content, re.DOTALL)
79+
code_match = self._python_code_pattern.search(content)
7580
if code_match:
7681
return code_match.group(1)
7782

7883
# Try other formats
79-
code_match = re.search(r"```\n(.*?)\n```", content, re.DOTALL)
84+
code_match = self._generic_code_pattern.search(content)
8085
if code_match:
8186
return code_match.group(1)
8287

openjudge/graders/code/code_style.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def __init__(self):
2727
description="Basic code style checking including indentation consistency and naming conventions.",
2828
)
2929

30+
self._function_pattern = re.compile(r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(")
31+
self._variable_pattern = re.compile(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*=")
32+
self._snake_case_pattern = re.compile(r"^[a-z_][a-z0-9_]*$")
33+
self._code_pattern = re.compile(r"```(?:python)?\s*\n(.*?)\n\s*```", re.DOTALL)
34+
3035
def _check_indentation(self, code: str) -> tuple[bool, str]:
3136
"""Check indentation consistency"""
3237
lines = code.split("\n")
@@ -58,11 +63,8 @@ def _check_indentation(self, code: str) -> tuple[bool, str]:
5863
def _check_naming(self, code: str) -> tuple[float, str]:
5964
"""Check naming conventions"""
6065
# Simple naming check
61-
function_pattern = r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\("
62-
variable_pattern = r"([a-zA-Z_][a-zA-Z0-9_]*)\s*="
63-
64-
functions = re.findall(function_pattern, code)
65-
variables = re.findall(variable_pattern, code)
66+
functions = self._function_pattern.findall(code)
67+
variables = self._variable_pattern.findall(code)
6668

6769
total_names = len(functions) + len(variables)
6870
if total_names == 0:
@@ -72,12 +74,12 @@ def _check_naming(self, code: str) -> tuple[float, str]:
7274

7375
# Check function names (should be snake_case)
7476
for func in functions:
75-
if re.match(r"^[a-z_][a-z0-9_]*$", func):
77+
if self._snake_case_pattern.match(func):
7678
good_names += 1
7779

7880
# Check variable names (should be snake_case)
7981
for var in variables:
80-
if re.match(r"^[a-z_][a-z0-9_]*$", var):
82+
if self._snake_case_pattern.match(var):
8183
good_names += 1
8284

8385
score = good_names / total_names
@@ -122,8 +124,7 @@ async def aevaluate(self, response: str) -> GraderScore:
122124
0.5 Code style score: 0.500; Consistent indentation; Naming convention: 1/2 names follow snake_case
123125
"""
124126
# Extract code blocks
125-
code_pattern = r"```(?:python)?\s*\n(.*?)\n\s*```"
126-
code_blocks = re.findall(code_pattern, response, re.DOTALL)
127+
code_blocks = self._code_pattern.findall(response)
127128

128129
if not code_blocks:
129130
return GraderScore(

openjudge/graders/code/syntax_checker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ def __init__(self):
3131
description="Check code syntax using Abstract Syntax Tree to validate Python code blocks.",
3232
)
3333

34+
self._code_pattern = re.compile(r"```(?:python)?\s*\n(.*?)\n\s*```", re.DOTALL)
35+
3436
async def aevaluate(self, response: str) -> GraderScore:
3537
"""Check code syntax in the provided response.
3638
@@ -68,8 +70,7 @@ async def aevaluate(self, response: str) -> GraderScore:
6870
"""
6971

7072
# Extract code blocks
71-
code_pattern = r"```(?:python)?\s*\n(.*?)\n\s*```"
72-
code_blocks = re.findall(code_pattern, response, re.DOTALL)
73+
code_blocks = self._code_pattern.findall(response)
7374

7475
if not code_blocks:
7576
# No code blocks, return neutral score

openjudge/graders/format/ngram_repetition_penalty.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ def __init__(
6767
chinese_only=chinese_only,
6868
)
6969

70+
self._think_pattern = re.compile(r"(.*?)", flags=re.DOTALL)
71+
7072
def _extract_thought_process(self, content: str) -> str:
7173
"""Extract thought process"""
72-
think_pattern = r"(.*?)"
73-
matches = re.findall(think_pattern, content, re.DOTALL)
74+
matches = self._think_pattern.findall(content)
7475
return " ".join(matches) if matches else ""
7576

7677
def _generate_ngrams(self, tokens: List[str]) -> List[tuple]:

openjudge/graders/format/reasoning_format.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ def __init__(self, think_token: str = "think", answer_token: str = "answer"):
3434
description="Check format reward for thinking format and answer format with proper tags.",
3535
)
3636
self.think_token = think_token
37+
self.think_pattern = re.compile(f"<{self.think_token}>.*?</{self.think_token}>", flags=re.DOTALL)
38+
3739
self.answer_token = answer_token
40+
self.answer_pattern = re.compile(f"<{self.answer_token}>.*?</{self.answer_token}>", flags=re.DOTALL)
3841

3942
# pylint: disable=unused-argument
4043
async def aevaluate(self, response: str, *args: Any, **kwargs: Any) -> GraderScore:
@@ -73,12 +76,10 @@ async def aevaluate(self, response: str, *args: Any, **kwargs: Any) -> GraderSco
7376
"""
7477

7578
# Check thinking format tags
76-
think_pattern = f"<{self.think_token}>.*?</{self.think_token}>"
77-
has_think_tag = bool(re.search(think_pattern, response, re.DOTALL))
79+
has_think_tag = bool(self.think_pattern.search(response))
7880

7981
# Check answer format tags
80-
answer_pattern = f"<{self.answer_token}>.*?</{self.answer_token}>"
81-
has_answer_tag = bool(re.search(answer_pattern, response, re.DOTALL))
82+
has_answer_tag = bool(self.answer_pattern.search(response))
8283

8384
# Calculate reward
8485
reward = 1.0 if has_think_tag and has_answer_tag else 0.0

openjudge/graders/format/reasoning_tool_format.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ def __init__(self) -> None:
2626
description="Check tool call format including think, answer and tool_call tags with JSON validation.",
2727
)
2828

29+
# patterns for identifiying tags
30+
self._think_pattern = re.compile(r"<think>(.*?)</think>", re.DOTALL)
31+
self._answer_pattern = re.compile(r"<answer>(.*?)</answer>", re.DOTALL)
32+
self._tool_call_pattern = re.compile(r"<tool_call>(.*?)</tool_call>", re.DOTALL)
33+
34+
self._think_answer_pattern = re.compile(r"^\s*<think>.*?</think>\s*<answer>.*?</answer>\s*$", re.DOTALL)
35+
self._think_tool_call_pattern = re.compile(
36+
r"^\s*<think>.*?</think>\s*(?:<tool_call>.*?</tool_call>\s*)+$", re.DOTALL
37+
)
38+
39+
self._consecutive_start_tool_call_tag_pattern = re.compile(r"<tool_call>\s*<tool_call>")
40+
self._consecutive_end_tool_call_tag_pattern = re.compile(r"</tool_call>\s*</tool_call>")
41+
2942
# pylint: disable=too-many-statements
3043
async def aevaluate(self, response: str, **kwargs: Any) -> GraderScore:
3144
"""
@@ -69,13 +82,9 @@ async def aevaluate(self, response: str, **kwargs: Any) -> GraderScore:
6982
"""
7083

7184
# Extract tag contents
72-
think_pattern = r"<think>(.*?)</think>"
73-
answer_pattern = r"<answer>(.*?)</answer>"
74-
tool_call_pattern = r"<tool_call>(.*?)</tool_call>"
75-
76-
think_matches = re.search(think_pattern, response, re.DOTALL)
77-
answer_matches = re.search(answer_pattern, response, re.DOTALL)
78-
tool_call_matches = re.findall(tool_call_pattern, response, re.DOTALL)
85+
think_matches = self._think_pattern.search(response)
86+
answer_matches = self._answer_pattern.search(response)
87+
tool_call_matches = self._tool_call_pattern.findall(response)
7988

8089
has_think_tag = think_matches is not None
8190
has_answer_tag = answer_matches is not None
@@ -89,9 +98,8 @@ async def aevaluate(self, response: str, **kwargs: Any) -> GraderScore:
8998
# Case 1: <think></think> + <answer></answer>
9099
if has_answer_tag and not has_tool_call_tag:
91100
# Check overall format
92-
format_pattern = r"^\s*<think>.*?</think>\s*<answer>.*?</answer>\s*$"
93101
valid_format = bool(
94-
re.match(format_pattern, response, re.DOTALL),
102+
self._think_answer_pattern.match(response),
95103
)
96104

97105
# Check tag occurrence count
@@ -115,9 +123,8 @@ async def aevaluate(self, response: str, **kwargs: Any) -> GraderScore:
115123
# Case 2: <think></think> + <tool_call></tool_call>
116124
elif has_tool_call_tag and not has_answer_tag:
117125
# Check overall format
118-
format_pattern = r"^\s*<think>.*?</think>\s*(?:<tool_call>.*?</tool_call>\s*)+$"
119126
valid_format = bool(
120-
re.match(format_pattern, response, re.DOTALL),
127+
self._think_tool_call_pattern.match(response),
121128
)
122129

123130
# Check <think> tag occurrence count
@@ -133,11 +140,9 @@ async def aevaluate(self, response: str, **kwargs: Any) -> GraderScore:
133140

134141
# Check for consecutive duplicate tags
135142
if valid_format:
136-
if re.search(
137-
r"</tool_call>\s*</tool_call>",
143+
if self._consecutive_end_tool_call_tag_pattern.search(
138144
response,
139-
) or re.search(
140-
r"<tool_call>\s*<tool_call>",
145+
) or self._consecutive_start_tool_call_tag_pattern.search(
141146
response,
142147
):
143148
valid_format = False

openjudge/graders/text/number_accuracy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def __init__(self, tolerance: float = 1e-6, **kwargs: Any) -> None:
5252
**kwargs,
5353
)
5454
self.tolerance = tolerance
55+
self._number_pattern = re.compile(r"-?\d+\.?\d*")
5556

5657
def _extract_numbers(self, text: str) -> List[float]:
5758
"""Extract numbers from text"""
5859
# Match integers and floating point numbers
59-
number_pattern = r"-?\d+\.?\d*"
60-
numbers = re.findall(number_pattern, text)
60+
numbers = self._number_pattern.findall(text)
6161
return [float(n) for n in numbers if n]
6262

6363
async def aevaluate(self, response: str, reference_response: str) -> GraderScore:

0 commit comments

Comments
 (0)