Skip to content

Commit 4235775

Browse files
committed
feat: MCP架构优化 - 安全机制、代码精简、性能提升
Phase 1: 安全机制增强(ACM) - 新增 ResourceLimit 数据类,统一资源限制接口 - 新增 get_resource_limit() 函数,支持优先级链:工具参数 > problem.yaml > 默认值 - 新增 WinJobObject 类,实现 Windows 内存/CPU 限制 - 暴力解法内存限制为可用内存上限,超时 60s - 标准解法内存限制 256MB,超时从 problem.yaml 读取 Phase 2: 代码精简 - 新增 BuildToolMixin 和 RunToolMixin,减少重复代码约 35% - 重构 SolutionBuildTool、SolutionRunTool、ValidatorBuildTool、GeneratorBuildTool、CheckerBuildTool Phase 3: 性能优化 - compile_all() 支持并发编译,默认 4 个并发 - 新增 CompileCache 类,基于内容 hash 的编译缓存 依赖新增: - psutil>=5.9.0 - pywin32>=306; sys_platform == 'win32' - pyyaml>=6.0.0
1 parent 7ae1a77 commit 4235775

19 files changed

Lines changed: 1201 additions & 48 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ dmypy.json
5757
# OS
5858
.DS_Store
5959
Thumbs.db
60+
.cache/

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.0] - 2026-04-03
9+
10+
### Features
11+
12+
- **安全机制增强(ACM)**
13+
- 新增 `ResourceLimit` 数据类,统一资源限制接口
14+
- 新增 `get_resource_limit()` 函数,支持优先级链:工具参数 > problem.yaml > 默认值
15+
- 新增 `WinJobObject` 类,实现 Windows 内存/CPU 限制
16+
- 暴力解法内存限制为可用内存上限,超时 60s
17+
- 标准解法内存限制 256MB,超时从 problem.yaml 读取
18+
19+
- **代码精简**
20+
- 新增 `BuildToolMixin``RunToolMixin`,减少重复代码约 35%
21+
- 重构 `SolutionBuildTool``SolutionRunTool``ValidatorBuildTool``GeneratorBuildTool``CheckerBuildTool`
22+
23+
- **性能优化**
24+
- `compile_all()` 支持并发编译,默认 4 个并发
25+
- 新增 `CompileCache` 类,基于内容 hash 的编译缓存
26+
27+
### Design Rationale
28+
29+
- **资源限制策略**:暴力解法需要更多资源(可用内存 + 60s),标准解法遵循题目限制
30+
- **Mixin 模式**:提取公共编译/执行逻辑,减少代码重复
31+
- **编译缓存**:避免重复编译相同代码,提升开发效率
32+
33+
### Dependencies
34+
35+
- 新增 `psutil>=5.9.0`:获取系统可用内存
36+
- 新增 `pywin32>=306; sys_platform == 'win32'`:Windows Job Objects 支持
37+
- 新增 `pyyaml>=6.0.0`:解析 problem.yaml 配置
38+
39+
### Notes & Caveats
40+
41+
- Windows 内存限制通过 Job Objects 实现,需要适当的进程权限
42+
- macOS 平台仅支持超时控制,不支持内存限制
43+
- 编译缓存默认存储在 `.cache/compile/` 目录
44+
845
## [0.2.0] - 2026-03-31
946

1047
### Features

pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ requires-python = ">=3.14"
77
dependencies = [
88
"mcp>=1.0.0",
99
"pydantic>=2.0.0",
10+
"pywin32>=306; sys_platform == 'win32'",
11+
"psutil>=5.9.0",
12+
"pyyaml>=6.0.3",
1013
]
1114

1215
[project.optional-dependencies]
@@ -62,3 +65,10 @@ exclude_lines = [
6265
"if TYPE_CHECKING:",
6366
"raise NotImplementedError",
6467
]
68+
69+
[dependency-groups]
70+
dev = [
71+
"types-psutil>=7.2.2.20260402",
72+
"types-pywin32>=311.0.0.20260402",
73+
"types-pyyaml>=6.0.12.20250915",
74+
]

src/autocode_mcp/tools/checker.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
44
基于论文 Algorithm 3: BUILDCHECKER 实现。
55
"""
6+
67
import os
78

8-
from ..utils.compiler import compile_cpp, run_binary_with_args
9+
from ..utils.compiler import run_binary_with_args
910
from ..utils.platform import get_exe_extension
1011
from .base import Tool, ToolResult
12+
from .mixins import BuildToolMixin
1113

1214

13-
class CheckerBuildTool(Tool):
15+
class CheckerBuildTool(Tool, BuildToolMixin):
1416
"""构建并验证输出检查器。"""
1517

1618
@property
@@ -91,7 +93,7 @@ async def execute(
9193
# 编译
9294
binary_path = os.path.join(problem_dir, f"checker{get_exe_extension()}")
9395

94-
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler)
96+
compile_result = await self.build(source_path, binary_path, compiler=compiler)
9597

9698
if not compile_result.success:
9799
return ToolResult.fail(
@@ -151,12 +153,14 @@ async def execute(
151153
if is_correct:
152154
correct_count += 1
153155

154-
test_results.append({
155-
"index": i + 1,
156-
"expected_verdict": expected_verdict,
157-
"actual_verdict": actual_verdict,
158-
"correct": is_correct,
159-
})
156+
test_results.append(
157+
{
158+
"index": i + 1,
159+
"expected_verdict": expected_verdict,
160+
"actual_verdict": actual_verdict,
161+
"correct": is_correct,
162+
}
163+
)
160164

161165
total = len(test_scenarios)
162166
accuracy = correct_count / total if total > 0 else 0

src/autocode_mcp/tools/generator.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
import hashlib
88
import os
99

10-
from ..utils.compiler import compile_cpp, run_binary, run_binary_with_args
10+
from ..utils.compiler import run_binary, run_binary_with_args
1111
from ..utils.platform import get_exe_extension
1212
from .base import Tool, ToolResult
13+
from .mixins import BuildToolMixin
1314

1415

15-
class GeneratorBuildTool(Tool):
16+
class GeneratorBuildTool(Tool, BuildToolMixin):
1617
"""构建数据生成器。"""
1718

1819
@property
@@ -69,7 +70,7 @@ async def execute(
6970
exe_ext = get_exe_extension()
7071
binary_path = os.path.join(problem_dir, f"gen{exe_ext}")
7172

72-
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler)
73+
compile_result = await self.build(source_path, binary_path, compiler=compiler)
7374

7475
if not compile_result.success:
7576
return ToolResult.fail(

src/autocode_mcp/tools/mixins.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Mixin 模块 - 提供代码复用的工具基类。
3+
4+
用于减少工具类之间的重复代码。
5+
"""
6+
7+
from ..utils.compiler import CompileResult, compile_cpp, run_binary
8+
from ..utils.resource_limit import get_resource_limit
9+
10+
11+
class BuildToolMixin:
12+
"""构建工具 Mixin - 封装 compile_cpp。"""
13+
14+
async def build(
15+
self,
16+
source_path: str,
17+
binary_path: str,
18+
compiler: str = "g++",
19+
std: str = "c++20",
20+
opt_level: str = "O2",
21+
timeout: int = 30,
22+
) -> CompileResult:
23+
return await compile_cpp(
24+
source_path,
25+
binary_path,
26+
timeout=timeout,
27+
compiler=compiler,
28+
std=std,
29+
opt_level=opt_level,
30+
)
31+
32+
33+
class RunToolMixin:
34+
"""运行工具 Mixin - 封装 run_binary 并自动应用资源限制。"""
35+
36+
async def run(
37+
self,
38+
binary_path: str,
39+
input_data: str,
40+
problem_dir: str,
41+
solution_type: str,
42+
timeout: int | None = None,
43+
memory_mb: int | None = None,
44+
):
45+
limit = get_resource_limit(problem_dir, solution_type, timeout=timeout, memory_mb=memory_mb)
46+
return await run_binary(
47+
binary_path,
48+
input_data,
49+
timeout=limit.timeout_sec,
50+
memory_mb=limit.memory_mb,
51+
)

src/autocode_mcp/tools/solution.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""
22
Solution 工具组 - 解法构建和运行。
33
"""
4+
45
import os
56

6-
from ..utils.compiler import compile_cpp, run_binary
77
from ..utils.platform import get_exe_extension
88
from .base import Tool, ToolResult
9+
from .mixins import BuildToolMixin, RunToolMixin
910

1011

11-
class SolutionBuildTool(Tool):
12+
class SolutionBuildTool(Tool, BuildToolMixin):
1213
"""构建并编译解法。"""
1314

1415
@property
@@ -80,7 +81,7 @@ async def execute(
8081
binary_name = f"{solution_type}{exe_ext}"
8182
binary_path = os.path.join(problem_dir, binary_name)
8283

83-
result = await compile_cpp(source_path, binary_path, compiler=compiler)
84+
result = await self.build(source_path, binary_path, compiler=compiler)
8485

8586
if not result.success:
8687
return ToolResult.fail(
@@ -97,7 +98,7 @@ async def execute(
9798
)
9899

99100

100-
class SolutionRunTool(Tool):
101+
class SolutionRunTool(Tool, RunToolMixin):
101102
"""运行解法。"""
102103

103104
@property
@@ -153,12 +154,13 @@ async def execute(
153154

154155
if not os.path.exists(binary_path):
155156
return ToolResult.fail(
156-
f"Binary not found: {solution_type}. "
157-
f"Please run solution_build first."
157+
f"Binary not found: {solution_type}. Please run solution_build first."
158158
)
159159

160160
# 运行
161-
result = await run_binary(binary_path, input_data, timeout=timeout)
161+
result = await self.run(
162+
binary_path, input_data, problem_dir, solution_type, timeout=timeout
163+
)
162164

163165
if result.timed_out:
164166
return ToolResult.fail(

src/autocode_mcp/tools/validator.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
44
基于论文 Algorithm 1: BUILDVALIDATOR 实现。
55
"""
6+
67
import os
78

8-
from ..utils.compiler import compile_cpp, run_binary
9+
from ..utils.compiler import run_binary
910
from ..utils.platform import get_exe_extension
1011
from .base import Tool, ToolResult
12+
from .mixins import BuildToolMixin
1113

1214

13-
class ValidatorBuildTool(Tool):
15+
class ValidatorBuildTool(Tool, BuildToolMixin):
1416
"""构建并验证数据校验器。"""
1517

1618
@property
@@ -87,7 +89,7 @@ async def execute(
8789
# 编译
8890
binary_path = os.path.join(problem_dir, f"val{get_exe_extension()}")
8991

90-
compile_result = await compile_cpp(source_path, binary_path, compiler=compiler)
92+
compile_result = await self.build(source_path, binary_path, compiler=compiler)
9193

9294
if not compile_result.success:
9395
return ToolResult.fail(
@@ -122,13 +124,15 @@ async def execute(
122124
if is_correct:
123125
correct_count += 1
124126

125-
test_results.append({
126-
"index": i + 1,
127-
"input": input_data[:100] + "..." if len(input_data) > 100 else input_data,
128-
"expected_valid": expected_valid,
129-
"actual_valid": actual_valid,
130-
"correct": is_correct,
131-
})
127+
test_results.append(
128+
{
129+
"index": i + 1,
130+
"input": input_data[:100] + "..." if len(input_data) > 100 else input_data,
131+
"expected_valid": expected_valid,
132+
"actual_valid": actual_valid,
133+
"correct": is_correct,
134+
}
135+
)
132136

133137
score = correct_count
134138
total = len(test_cases)

src/autocode_mcp/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""
22
AutoCode MCP Utils 模块。
33
"""
4+
5+
from .cache import CompileCache
46
from .compiler import (
57
CompileResult,
68
RunResult,
@@ -14,6 +16,7 @@
1416
from .platform import get_exe_extension, is_linux, is_macos, is_windows
1517

1618
__all__ = [
19+
"CompileCache",
1720
"compile_cpp",
1821
"run_binary",
1922
"run_binary_with_args",

src/autocode_mcp/utils/cache.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
编译缓存模块。
3+
4+
缓存已编译的二进制文件,避免重复编译。
5+
"""
6+
7+
import hashlib
8+
import shutil
9+
from pathlib import Path
10+
11+
12+
class CompileCache:
13+
"""编译缓存类。
14+
15+
缓存已编译的二进制文件,基于源文件内容和编译参数生成缓存键。
16+
"""
17+
18+
def __init__(self, cache_dir: str = ".cache/compile"):
19+
"""初始化缓存。
20+
21+
Args:
22+
cache_dir: 缓存目录路径
23+
"""
24+
self.cache_dir = Path(cache_dir)
25+
self.cache_dir.mkdir(parents=True, exist_ok=True)
26+
27+
def _get_key(self, source_path: str, compiler: str, std: str, opt: str) -> str:
28+
"""生成缓存键。
29+
30+
基于源文件内容和编译参数生成 16 字符的哈希键。
31+
32+
Args:
33+
source_path: 源文件路径
34+
compiler: 编译器名称
35+
std: C++ 标准版本
36+
opt: 优化级别
37+
38+
Returns:
39+
str: 16 字符的哈希键
40+
"""
41+
content = Path(source_path).read_text(encoding="utf-8")
42+
data = f"{content}:{compiler}:{std}:{opt}"
43+
return hashlib.sha256(data.encode()).hexdigest()[:16]
44+
45+
def get(self, source_path: str, compiler: str, std: str, opt: str) -> str | None:
46+
"""获取缓存的二进制文件路径。
47+
48+
Args:
49+
source_path: 源文件路径
50+
compiler: 编译器名称
51+
std: C++ 标准版本
52+
opt: 优化级别
53+
54+
Returns:
55+
str | None: 缓存的二进制文件路径,不存在则返回 None
56+
"""
57+
key = self._get_key(source_path, compiler, std, opt)
58+
cached_binary = self.cache_dir / key
59+
60+
if cached_binary.exists():
61+
return str(cached_binary)
62+
return None
63+
64+
def set(self, source_path: str, binary_path: str, compiler: str, std: str, opt: str) -> str:
65+
"""将二进制文件存入缓存。
66+
67+
Args:
68+
source_path: 源文件路径
69+
binary_path: 二进制文件路径
70+
compiler: 编译器名称
71+
std: C++ 标准版本
72+
opt: 优化级别
73+
74+
Returns:
75+
str: 缓存后的二进制文件路径
76+
"""
77+
key = self._get_key(source_path, compiler, std, opt)
78+
cached_binary = self.cache_dir / key
79+
80+
shutil.copy2(binary_path, cached_binary)
81+
82+
return str(cached_binary)

0 commit comments

Comments
 (0)