diff --git a/.claude/commands/tasks.md b/.claude/commands/tasks.md index 4067bd1..526990c 100644 --- a/.claude/commands/tasks.md +++ b/.claude/commands/tasks.md @@ -143,4 +143,3 @@ Show the task breakdown and: - Include testing tasks throughout, not just at the end think hard - diff --git a/.claude/settings.json b/.claude/settings.json index e791c6a..145c9c1 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -68,7 +68,8 @@ "Bash(git rebase:*)", "Bash(curl:*)", "Bash(wget:*)", - "Bash(uv:*)" + "Bash(uv:*)", + "Bash(gh:*view*)" ], "deny": [ "Bash(sudo:*)", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae3f1b0..cb379f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: coverage: runs-on: ubuntu-latest if: github.event_name == 'pull_request' - + steps: - uses: actions/checkout@v4 @@ -65,4 +65,4 @@ jobs: if: always() with: file: ./htmlcov/index.html - fail_ci_if_error: false \ No newline at end of file + fail_ci_if_error: false diff --git a/.github/workflows/docs.md b/.github/workflows/docs.md index ecc6c08..84e85f0 100644 --- a/.github/workflows/docs.md +++ b/.github/workflows/docs.md @@ -58,4 +58,4 @@ make lint # Check formatting uv run ruff format --check src/ tests/ -``` \ No newline at end of file +``` diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 549ed19..c17a2ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ on: jobs: test-and-build: runs-on: ubuntu-latest - + steps: - uses: actions/checkout@v4 @@ -46,4 +46,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: dist-packages - path: dist/ \ No newline at end of file + path: dist/ diff --git a/.gitignore b/.gitignore index 505a3b1..3152e68 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ wheels/ # Virtual environments .venv + +# tmp files +.tmp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b810d33 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +# Pre-commit hooks for automated code quality checks +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-ast + - id: check-merge-conflict + - id: check-toml + - id: debug-statements + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + types_or: [python, pyi] + - id: ruff-format + types_or: [python, pyi] diff --git a/.tmp/design.md b/.tmp/design.md deleted file mode 100644 index 33ad148..0000000 --- a/.tmp/design.md +++ /dev/null @@ -1,1002 +0,0 @@ -# 技術設計書 - MIP MCP Server - -## 1. システムアーキテクチャ - -### 1.1 全体構成 - -``` -┌─────────────────┐ streamable HTTP ┌─────────────────┐ -│ LLM Client │ ◄──────────────────► │ MIP MCP │ -│ (Claude/GPT) │ │ Server │ -└─────────────────┘ └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ Python Executor │ - │ (PuLP/pyomo/ │ - │ pyscipopt) │ - └─────────────────┘ - │ - ▼ - ┌─────────────────┐ - │ Solver Layer │ - │ (SCIP/Gurobi/ │ - │ CPLEX/CBC) │ - └─────────────────┘ -``` - -### 1.2 モジュール構成 - -``` -src/mip_mcp/ -├── __init__.py -├── server.py # MCPサーバーメイン -├── handlers/ # MCPリクエストハンドラー -│ ├── __init__.py -│ ├── solve.py # 最適化実行ハンドラー -│ ├── execute_code.py # Pythonコード実行ハンドラー -│ ├── validate.py # モデル検証ハンドラー -│ └── status.py # ステータス確認ハンドラー -├── solvers/ # ソルバー抽象化層 -│ ├── __init__.py -│ ├── base.py # ベースソルバークラス -│ ├── scip_solver.py # pyscipopt実装 -│ ├── gurobi_solver.py # Gurobi実装(オプション) -│ └── cplex_solver.py # CPLEX実装(オプション) -├── models/ # データモデル定義 -│ ├── __init__.py -│ ├── problem.py # 最適化問題定義 -│ ├── solution.py # ソリューション定義 -│ └── config.py # 設定データモデル -├── parsers/ # 問題形式パーサー -│ ├── __init__.py -│ ├── json_parser.py # JSON形式パーサー -│ ├── lp_parser.py # LP形式パーサー -│ └── mps_parser.py # MPS形式パーサー -├── executor/ # Pythonコード実行エンジン -│ ├── __init__.py -│ ├── code_executor.py # Pythonコード実行器 -│ ├── sandbox.py # サンドボックス環境 -│ └── libraries.py # 許可されたライブラリ管理 -├── utils/ # ユーティリティ -│ ├── __init__.py -│ ├── logger.py # ログ設定 -│ ├── config_manager.py # 設定管理 -│ └── validators.py # 入力検証 -└── config/ # 設定ファイル - ├── default.yaml # デフォルト設定 - └── solvers.yaml # ソルバー設定 -``` - -## 2. コアコンポーネント設計 - -### 2.1 MCPサーバー (server.py) - -```python -from fastmcp import FastMCP -from typing import Dict, Any, Optional - -class MIPMCPServer: - def __init__(self, config_path: Optional[str] = None): - self.config = ConfigManager(config_path) - self.solver_factory = SolverFactory(self.config) - self.app = FastMCP("mip-mcp") - self._register_handlers() - - def _register_handlers(self): - # MCPツール登録 - self.app.add_tool("solve_optimization", solve_handler) - self.app.add_tool("execute_python_code", execute_code_handler) - self.app.add_tool("get_library_examples", get_library_examples_handler) - self.app.add_tool("validate_model", validate_handler) - self.app.add_tool("get_solver_status", status_handler) - - async def run(self): - await self.app.run() -``` - -### 2.2 ソルバー抽象化層 - -#### ベースソルバークラス (solvers/base.py) - -```python -from abc import ABC, abstractmethod -from typing import Dict, Any, Optional -from ..models.problem import OptimizationProblem -from ..models.solution import OptimizationSolution - -class BaseSolver(ABC): - def __init__(self, config: Dict[str, Any]): - self.config = config - self.model = None - - @abstractmethod - async def solve(self, problem: OptimizationProblem) -> OptimizationSolution: - """最適化問題を解決する""" - pass - - @abstractmethod - def validate_problem(self, problem: OptimizationProblem) -> Dict[str, Any]: - """問題の妥当性を検証する""" - pass - - @abstractmethod - def get_solver_info(self) -> Dict[str, Any]: - """ソルバー情報を取得する""" - pass - - @abstractmethod - def set_parameters(self, params: Dict[str, Any]) -> None: - """ソルバーパラメータを設定する""" - pass -``` - -#### SCIPソルバー実装 (solvers/scip_solver.py) - -```python -import pyscipopt -from .base import BaseSolver -from ..models.problem import OptimizationProblem -from ..models.solution import OptimizationSolution - -class SCIPSolver(BaseSolver): - def __init__(self, config: Dict[str, Any]): - super().__init__(config) - self.model = pyscipopt.Model() - - async def solve(self, problem: OptimizationProblem) -> OptimizationSolution: - try: - # 変数定義 - variables = {} - for var_def in problem.variables: - variables[var_def.name] = self.model.addVar( - name=var_def.name, - vtype=var_def.type, - lb=var_def.lower_bound, - ub=var_def.upper_bound - ) - - # 制約条件追加 - for constraint in problem.constraints: - expr = self._build_expression(constraint.expression, variables) - self.model.addCons(expr <= constraint.rhs, name=constraint.name) - - # 目的関数設定 - obj_expr = self._build_expression(problem.objective.expression, variables) - self.model.setObjective(obj_expr, sense=problem.objective.sense) - - # 最適化実行 - self.model.optimize() - - return self._extract_solution(variables) - - except Exception as e: - return OptimizationSolution( - status="error", - message=str(e), - objective_value=None, - variables={} - ) - -### 2.3 Pythonコード実行エンジン - -#### コード実行器 (executor/code_executor.py) - -```python -import ast -import sys -import io -import contextlib -from typing import Dict, Any, Optional, List -from ..models.solution import OptimizationSolution -from .sandbox import SecurityChecker -from .libraries import get_allowed_imports - -class PythonCodeExecutor: - def __init__(self, config: Dict[str, Any]): - self.config = config - self.security_checker = SecurityChecker() - self.allowed_imports = get_allowed_imports() - - async def execute_optimization_code(self, code: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """ - Pythonコードを実行して最適化問題を解決する - - Args: - code: 実行するPythonコード(PuLP/pyomo/pyscipopt等を使用) - data: コードで使用するデータ - - Returns: - 最適化結果 - """ - try: - # セキュリティチェック - self.security_checker.validate_code(code) - - # 実行環境の準備 - namespace = self._prepare_namespace(data) - - # コード実行 - output = io.StringIO() - with contextlib.redirect_stdout(output): - exec(code, namespace) - - # 結果の抽出 - result = self._extract_result(namespace, output.getvalue()) - - return result - - except Exception as e: - return { - "status": "error", - "message": f"Code execution failed: {str(e)}", - "objective_value": None, - "variables": {} - } - - def _prepare_namespace(self, data: Optional[Dict[str, Any]]) -> Dict[str, Any]: - """実行環境の名前空間を準備""" - namespace = { - '__builtins__': self._get_safe_builtins(), - 'data': data or {}, - } - - # 許可されたライブラリをインポート - for lib_name, lib_module in self.allowed_imports.items(): - namespace[lib_name] = lib_module - - return namespace - - def _get_safe_builtins(self) -> Dict[str, Any]: - """安全なbuiltins関数のみを提供""" - safe_builtins = { - 'len', 'range', 'enumerate', 'zip', 'sum', 'min', 'max', - 'abs', 'round', 'int', 'float', 'str', 'list', 'dict', - 'tuple', 'set', 'bool', 'print' - } - - return {name: getattr(__builtins__, name) for name in safe_builtins if hasattr(__builtins__, name)} - - def _extract_result(self, namespace: Dict[str, Any], output: str) -> Dict[str, Any]: - """実行結果から最適化結果を抽出""" - # PuLPの場合 - if 'pulp' in output.lower() or any('pulp' in str(v) for v in namespace.values()): - return self._extract_pulp_result(namespace) - - # pyomoの場合 - if 'pyomo' in output.lower() or any('pyomo' in str(type(v)) for v in namespace.values()): - return self._extract_pyomo_result(namespace) - - # pyscipoptの場合 - if 'scip' in output.lower() or any('scip' in str(type(v)) for v in namespace.values()): - return self._extract_scip_result(namespace) - - # 汎用的な結果抽出 - return self._extract_generic_result(namespace, output) -``` - -#### セキュリティチェッカー (executor/sandbox.py) - -```python -import ast -from typing import Set, List - -class SecurityChecker: - """コードのセキュリティチェックを行う""" - - DANGEROUS_FUNCTIONS = { - 'eval', 'exec', 'compile', '__import__', 'open', 'file', - 'input', 'raw_input', 'reload', 'vars', 'globals', 'locals', - 'dir', 'getattr', 'setattr', 'delattr', 'hasattr' - } - - DANGEROUS_MODULES = { - 'os', 'sys', 'subprocess', 'shutil', 'glob', 'socket', - 'urllib', 'http', 'ftplib', 'smtplib', 'multiprocessing', - 'threading', 'pickle', 'marshal', 'shelve' - } - - def validate_code(self, code: str) -> bool: - """コードの安全性を検証""" - try: - tree = ast.parse(code) - checker = DangerousNodeVisitor() - checker.visit(tree) - - if checker.dangerous_nodes: - raise SecurityError(f"Dangerous operations detected: {checker.dangerous_nodes}") - - return True - - except SyntaxError as e: - raise SecurityError(f"Syntax error in code: {str(e)}") - -class DangerousNodeVisitor(ast.NodeVisitor): - """危険なASTノードを検出する""" - - def __init__(self): - self.dangerous_nodes = [] - - def visit_Call(self, node): - # 危険な関数呼び出しをチェック - if isinstance(node.func, ast.Name) and node.func.id in SecurityChecker.DANGEROUS_FUNCTIONS: - self.dangerous_nodes.append(f"Function call: {node.func.id}") - self.generic_visit(node) - - def visit_Import(self, node): - # 危険なモジュールのインポートをチェック - for alias in node.names: - if alias.name in SecurityChecker.DANGEROUS_MODULES: - self.dangerous_nodes.append(f"Import: {alias.name}") - self.generic_visit(node) - - def visit_ImportFrom(self, node): - # from文での危険なインポートをチェック - if node.module in SecurityChecker.DANGEROUS_MODULES: - self.dangerous_nodes.append(f"Import from: {node.module}") - self.generic_visit(node) - -class SecurityError(Exception): - """セキュリティ関連のエラー""" - pass -``` - -#### 許可ライブラリ管理 (executor/libraries.py) - -```python -from typing import Dict, Any -import importlib - -def get_allowed_imports() -> Dict[str, Any]: - """許可されたライブラリのマッピングを返す""" - allowed_libs = {} - - # 最適化ライブラリ - optimization_libs = [ - 'pulp', - 'pyomo', - 'pyscipopt', - 'cvxpy', - 'ortools' - ] - - # 数値計算ライブラリ - numeric_libs = [ - 'numpy', - 'pandas', - 'scipy', - 'math', - 'statistics' - ] - - # 全てのライブラリを試行してインポート - for lib_name in optimization_libs + numeric_libs: - try: - lib_module = importlib.import_module(lib_name) - allowed_libs[lib_name] = lib_module - except ImportError: - # ライブラリが利用できない場合はスキップ - continue - - return allowed_libs - -def get_library_info() -> Dict[str, Dict[str, Any]]: - """利用可能なライブラリの情報を返す""" - return { - 'pulp': { - 'description': 'Linear Programming library for Python', - 'example': ''' -import pulp - -# 問題定義 -prob = pulp.LpProblem("Example", pulp.LpMaximize) - -# 変数定義 -x = pulp.LpVariable("x", 0, None) -y = pulp.LpVariable("y", 0, None) - -# 目的関数 -prob += 3*x + 2*y - -# 制約条件 -prob += 2*x + y <= 100 -prob += x + y <= 80 - -# 求解 -prob.solve() - -# 結果出力 -result = { - "status": pulp.LpStatus[prob.status], - "objective": pulp.value(prob.objective), - "variables": {v.name: v.varValue for v in prob.variables()} -} -''' - }, - 'pyomo': { - 'description': 'Python Optimization Modeling Objects', - 'example': ''' -import pyomo.environ as pyo -from pyomo.opt import SolverFactory - -# モデル作成 -model = pyo.ConcreteModel() - -# 変数定義 -model.x = pyo.Var(bounds=(0, None)) -model.y = pyo.Var(bounds=(0, None)) - -# 目的関数 -model.obj = pyo.Objective(expr=3*model.x + 2*model.y, sense=pyo.maximize) - -# 制約条件 -model.constraint1 = pyo.Constraint(expr=2*model.x + model.y <= 100) -model.constraint2 = pyo.Constraint(expr=model.x + model.y <= 80) - -# 求解 -solver = SolverFactory('cbc') -results = solver.solve(model) - -# 結果出力 -result = { - "status": str(results.solver.termination_condition), - "objective": pyo.value(model.obj), - "variables": { - "x": pyo.value(model.x), - "y": pyo.value(model.y) - } -} -''' - } - } -``` - -### 2.4 Pythonコード実行ハンドラー (handlers/execute_code.py) - -```python -from fastmcp import Context -from typing import Dict, Any, Optional -from ..executor.code_executor import PythonCodeExecutor -from ..utils.logger import get_logger - -logger = get_logger(__name__) - -async def execute_code_handler( - context: Context, - code: str, - data: Optional[Dict[str, Any]] = None, - library: str = "pulp" -) -> Dict[str, Any]: - """ - Pythonコードを実行して最適化問題を解決する - - Args: - code: 実行するPythonコード - data: コードで使用するデータ - library: 使用する最適化ライブラリのヒント - - Returns: - 最適化結果 - """ - try: - executor = PythonCodeExecutor(context.config) - result = await executor.execute_optimization_code(code, data) - - logger.info(f"Code execution completed with library {library}") - return result - - except Exception as e: - logger.error(f"Code execution failed: {str(e)}") - return { - "status": "error", - "message": str(e), - "objective_value": None, - "variables": {} - } - -async def get_library_examples_handler(context: Context) -> Dict[str, Any]: - """ - 利用可能なライブラリとサンプルコードを返す - """ - from ..executor.libraries import get_library_info - - return { - "libraries": get_library_info(), - "supported_formats": ["pulp", "pyomo", "pyscipopt", "cvxpy", "ortools"] - } -``` - -### 2.5 データモデル定義 - -#### 最適化問題モデル (models/problem.py) - -```python -from pydantic import BaseModel, Field -from typing import List, Dict, Any, Optional, Literal -from enum import Enum - -class VariableType(str, Enum): - CONTINUOUS = "C" - INTEGER = "I" - BINARY = "B" - -class ObjectiveSense(str, Enum): - MINIMIZE = "minimize" - MAXIMIZE = "maximize" - -class Variable(BaseModel): - name: str - type: VariableType = VariableType.CONTINUOUS - lower_bound: Optional[float] = None - upper_bound: Optional[float] = None - -class Constraint(BaseModel): - name: str - expression: Dict[str, float] # {variable_name: coefficient} - sense: Literal["<=", ">=", "="] = "<=" - rhs: float - -class Objective(BaseModel): - sense: ObjectiveSense - expression: Dict[str, float] # {variable_name: coefficient} - -class OptimizationProblem(BaseModel): - name: str - variables: List[Variable] - constraints: List[Constraint] - objective: Objective - parameters: Optional[Dict[str, Any]] = None -``` - -#### ソリューションモデル (models/solution.py) - -```python -from pydantic import BaseModel -from typing import Dict, Any, Optional, List - -class SolutionVariable(BaseModel): - name: str - value: float - reduced_cost: Optional[float] = None - -class SolutionConstraint(BaseModel): - name: str - slack: Optional[float] = None - dual_value: Optional[float] = None - -class OptimizationSolution(BaseModel): - status: str # optimal, infeasible, unbounded, error, etc. - objective_value: Optional[float] = None - variables: Dict[str, float] = {} - constraints: List[SolutionConstraint] = [] - solve_time: Optional[float] = None - iterations: Optional[int] = None - message: Optional[str] = None - solver_info: Optional[Dict[str, Any]] = None -``` - -### 2.6 MCPハンドラー実装 - -#### 最適化実行ハンドラー (handlers/solve.py) - -```python -from fastmcp import Context -from typing import Dict, Any -from ..models.problem import OptimizationProblem -from ..parsers import get_parser -from ..utils.logger import get_logger - -logger = get_logger(__name__) - -async def solve_handler( - context: Context, - problem_data: Dict[str, Any], - solver_name: str = "scip", - solver_params: Dict[str, Any] = None -) -> Dict[str, Any]: - """ - 最適化問題を解決する - - Args: - problem_data: 最適化問題データ(JSON/LP/MPS形式) - solver_name: 使用するソルバー名 - solver_params: ソルバーパラメータ - - Returns: - 最適化結果 - """ - try: - # 問題データのパース - parser = get_parser(problem_data) - problem = parser.parse(problem_data) - - # ソルバー取得 - solver = context.solver_factory.get_solver(solver_name) - - # パラメータ設定 - if solver_params: - solver.set_parameters(solver_params) - - # 最適化実行 - solution = await solver.solve(problem) - - logger.info(f"Optimization completed: {solution.status}") - - return solution.dict() - - except Exception as e: - logger.error(f"Optimization failed: {str(e)}") - return { - "status": "error", - "message": str(e), - "objective_value": None, - "variables": {} - } -``` - -## 3. 設定管理システム - -### 3.1 設定ファイル構造 - -#### default.yaml -```yaml -server: - name: "mip-mcp" - version: "0.1.0" - -logging: - level: "INFO" - format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - -solvers: - default: "scip" - timeout: 3600 # seconds - -executor: - enabled: true - timeout: 300 # seconds for code execution - memory_limit: "1GB" - -parsers: - supported_formats: ["json", "lp", "mps", "python"] - -validation: - max_variables: 100000 - max_constraints: 100000 - max_code_length: 10000 # characters -``` - -#### solvers.yaml -```yaml -scip: - class: "SCIPSolver" - enabled: true - parameters: - limits/time: 3600 - display/verblevel: 1 - -gurobi: - class: "GurobiSolver" - enabled: false - parameters: - TimeLimit: 3600 - OutputFlag: 1 - -cplex: - class: "CPLEXSolver" - enabled: false - parameters: - timelimit: 3600 - output.clonelog: 1 -``` - -### 3.2 設定管理クラス (utils/config_manager.py) - -```python -import yaml -from pathlib import Path -from typing import Dict, Any, Optional - -class ConfigManager: - def __init__(self, config_path: Optional[str] = None): - self.config_dir = Path(config_path) if config_path else Path(__file__).parent.parent / "config" - self.config = self._load_config() - - def _load_config(self) -> Dict[str, Any]: - """設定ファイルを読み込む""" - default_config = self._load_yaml("default.yaml") - solver_config = self._load_yaml("solvers.yaml") - - default_config["solvers_config"] = solver_config - return default_config - - def _load_yaml(self, filename: str) -> Dict[str, Any]: - """YAMLファイルを読み込む""" - config_path = self.config_dir / filename - if config_path.exists(): - with open(config_path, 'r', encoding='utf-8') as f: - return yaml.safe_load(f) or {} - return {} - - def get(self, key: str, default: Any = None) -> Any: - """設定値を取得する""" - keys = key.split('.') - value = self.config - for k in keys: - if isinstance(value, dict) and k in value: - value = value[k] - else: - return default - return value -``` - -## 4. 入力形式対応 - -### 4.1 入力形式の拡張 - -MCPサーバーは以下の入力形式に対応: - -1. **Pythonコード実行** (推奨) - - PuLP, pyomo, pyscipopt等のライブラリを使用 - - 複雑なデータ処理と最適化を組み合わせ可能 - - 柔軟なモデリングが可能 - -2. **構造化データ形式** - - JSON: プログラマティックな問題定義 - - LP: 標準線形計画形式 - - MPS: 業界標準形式 - -### 4.2 パーサーシステム - -### 4.3 パーサーファクトリー (parsers/__init__.py) - -```python -from typing import Dict, Any, Union -from .json_parser import JSONParser -from .lp_parser import LPParser -from .mps_parser import MPSParser - -def get_parser(problem_data: Union[Dict[str, Any], str]): - """ - 問題データの形式に応じて適切なパーサーを返す - """ - if isinstance(problem_data, dict): - return JSONParser() - elif isinstance(problem_data, str): - if problem_data.strip().startswith("Minimize") or problem_data.strip().startswith("Maximize"): - return LPParser() - elif "ROWS" in problem_data and "COLUMNS" in problem_data: - return MPSParser() - else: - # JSON文字列として試行 - try: - import json - json.loads(problem_data) - return JSONParser() - except: - raise ValueError("Unsupported problem format") - else: - raise ValueError("Invalid problem data type") -``` - -### 4.4 JSONパーサー (parsers/json_parser.py) - -```python -from typing import Dict, Any, Union -from ..models.problem import OptimizationProblem -import json - -class JSONParser: - def parse(self, data: Union[Dict[str, Any], str]) -> OptimizationProblem: - """ - JSON形式の最適化問題をパースする - - Expected format: - { - "name": "problem_name", - "variables": [ - {"name": "x1", "type": "C", "lower_bound": 0, "upper_bound": 10} - ], - "constraints": [ - {"name": "c1", "expression": {"x1": 2, "x2": 3}, "sense": "<=", "rhs": 10} - ], - "objective": { - "sense": "minimize", - "expression": {"x1": 1, "x2": 2} - } - } - """ - if isinstance(data, str): - data = json.loads(data) - - return OptimizationProblem.parse_obj(data) -``` - -## 5. エラーハンドリング戦略 - -### 5.1 エラー分類 - -```python -class MIPMCPError(Exception): - """MIP MCP基底例外クラス""" - pass - -class SolverError(MIPMCPError): - """ソルバー関連エラー""" - pass - -class ParseError(MIPMCPError): - """パース関連エラー""" - pass - -class ValidationError(MIPMCPError): - """バリデーション関連エラー""" - pass - -class ConfigError(MIPMCPError): - """設定関連エラー""" - pass -``` - -### 5.2 エラーハンドリング戦略 - -1. **入力検証段階**: `ValidationError`でクライアントに詳細な修正指示 -2. **パース段階**: `ParseError`で形式エラーを報告 -3. **ソルバー実行段階**: `SolverError`で実行エラーを処理 -4. **内部エラー**: ログ出力と一般的なエラーメッセージ - -## 6. テスト戦略 - -### 6.1 テスト構造 - -``` -tests/ -├── unit/ -│ ├── test_solvers/ -│ ├── test_parsers/ -│ ├── test_handlers/ -│ └── test_models/ -├── integration/ -│ ├── test_mcp_communication/ -│ └── test_solver_integration/ -└── fixtures/ - ├── sample_problems/ - └── expected_solutions/ -``` - -### 6.2 テストデータ - -- 小規模線形計画問題 -- 整数計画問題 -- 実行不可能問題 -- 非有界問題 -- エラーケース用の不正データ - -## 7. パフォーマンス考慮事項 - -### 7.1 メモリ管理 - -- 大規模問題でのストリーミング処理 -- ソルバーインスタンスの適切な破棄 -- 中間結果のメモリ効率 - -### 7.2 非同期処理 - -- 長時間実行される最適化の非同期対応 -- プログレス報告機能 -- キャンセル機能 - -## 8. セキュリティ考慮事項 - -### 8.1 入力検証 - -- 問題サイズ制限 -- ファイル形式検証 -- SQLインジェクション対策(外部DB使用時) - -### 8.2 リソース制限 - -- CPU使用時間制限 -- メモリ使用量制限 -- 同時実行数制限 - -## 9. 依存関係とインストール - -### 9.1 pyproject.tomlの更新 - -```toml -[project] -name = "mip-mcp" -version = "0.1.0" -description = "MIP optimization server using Model Context Protocol" -readme = "README.md" -authors = [ - { name = "ohtaman", email = "ohtamans@gmail.com" } -] -requires-python = ">=3.12" -dependencies = [ - "fastmcp>=2.10.6", - "pyscipopt>=5.5.0", - "pulp>=2.7.0", - "pydantic>=2.0.0", - "pyyaml>=6.0", -] - -[project.optional-dependencies] -extra = [ - "pyomo>=6.0.0", - "cvxpy>=1.3.0", - "gurobipy>=10.0.0", # 商用ライセンス必要 - "cplex>=22.1.0", # 商用ライセンス必要 - "numpy>=1.21.0", - "pandas>=1.3.0", - "ortools>=9.0.0", -] - -[project.scripts] -mip-mcp = "mip_mcp:main" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" -``` - -### 9.2 使用例 - -#### Pythonコード実行による最適化 - -```python -# LLMクライアントから送信されるリクエスト例 -{ - "tool": "execute_python_code", - "arguments": { - "code": """ -import pulp - -# 生産計画問題 -prob = pulp.LpProblem("Production", pulp.LpMaximize) - -# 変数定義 -x1 = pulp.LpVariable("Product_A", 0, None) -x2 = pulp.LpVariable("Product_B", 0, None) - -# 目的関数(利益最大化) -prob += 40*x1 + 30*x2 - -# 制約条件 -prob += 2*x1 + x2 <= 100 # 労働時間制約 -prob += x1 + 2*x2 <= 80 # 原材料制約 - -# 求解 -prob.solve() - -# 結果をグローバル変数として設定 -result = { - "status": pulp.LpStatus[prob.status], - "objective": pulp.value(prob.objective), - "variables": {v.name: v.varValue for v in prob.variables()} -} -""", - "data": { - "max_labor_hours": 100, - "max_materials": 80 - } - } -} -``` - -## 10. 将来拡張性 - -### 10.1 プラグインアーキテクチャ - -- 新しいソルバーの動的追加 -- カスタムパーサーの追加 -- カスタムバリデーターの追加 -- 新しい最適化ライブラリのサポート - -### 10.2 スケーラビリティ - -- 分散処理対応(将来) -- キューイングシステム統合 -- 結果キャッシュ機能 -- Docker化とKubernetes対応 \ No newline at end of file diff --git a/.tmp/issue2-solution-summary.md b/.tmp/issue2-solution-summary.md deleted file mode 100644 index 211b741..0000000 --- a/.tmp/issue2-solution-summary.md +++ /dev/null @@ -1,94 +0,0 @@ -# Issue #2 Solution Summary: Ghost Process Prevention - -## Overview -Issue #2 identified potential ghost process (orphaned subprocess) problems in the MIP-MCP server. A comprehensive solution has been implemented that addresses all the identified concerns. - -## Problems Solved - -### ✅ 1. PyodideExecutor Process Cleanup Issues -**Location**: `src/mip_mcp/executor/pyodide_executor.py:727-775` - -**Solution Implemented**: -- **Graceful shutdown cascade**: Try exit command → SIGTERM → SIGKILL with proper timeouts -- **Proper timeout handling**: 2s for graceful exit, 5s for SIGTERM, 2s for SIGKILL -- **Idempotent cleanup**: Multiple cleanup calls are safe -- **Improved `__del__` method**: Synchronous fallback cleanup for destruction scenarios - -### ✅ 2. Process Group Management -**Location**: `src/mip_mcp/executor/pyodide_executor.py:184` - -**Solution Implemented**: -- **Process groups**: `start_new_session=True` creates new process group -- **Child process cleanup**: Entire process tree is terminated together -- **Signal isolation**: Subprocesses don't inherit parent signal handlers - -### ✅ 3. Executor Registry for Tracking -**Location**: `src/mip_mcp/utils/executor_registry.py` - -**Solution Implemented**: -- **Weak reference tracking**: Avoids circular references, automatic cleanup of dead refs -- **Thread-safe operations**: AsyncLock for concurrent access -- **Centralized cleanup**: `cleanup_all()` with 15s timeout for all active executors -- **Active count tracking**: Real-time monitoring of executor instances - -### ✅ 4. Server-Level Signal Handling -**Location**: `src/mip_mcp/server.py:57-93` - -**Solution Implemented**: -- **Atexit handlers**: Natural cleanup during process termination -- **FastMCP integration**: Works with FastMCP's signal handling without conflicts -- **Event loop management**: Creates new loop for cleanup during shutdown -- **Exception suppression**: Prevents ugly tracebacks during shutdown - -### ✅ 5. Exception Path Cleanup -**Location**: `src/mip_mcp/handlers/execute_code.py:619-628` - -**Solution Implemented**: -- **Finally blocks**: Ensure executor cleanup in all execution paths -- **Registry integration**: Automatic registration/unregistration -- **Error handling**: Graceful degradation when cleanup fails - -## Key Features - -### Security & Reliability -- **Complete process isolation**: WebAssembly + process groups prevent orphans -- **Timeout protection**: No hanging processes, all operations have timeouts -- **Resource leak prevention**: Weak references prevent memory leaks - -### Robustness -- **Multiple cleanup strategies**: Graceful → terminate → kill escalation -- **Failure tolerance**: Continues cleanup even if individual executors fail -- **Idempotent operations**: Safe to call cleanup multiple times - -### Testing Coverage -- **14 comprehensive test cases**: Cover all cleanup scenarios -- **Integration testing**: Real-world execution path testing -- **Mock-based testing**: Fast, reliable test execution -- **Edge case coverage**: Timeouts, failures, concurrent operations - -## Implementation Statistics - -- **Files Modified**: 4 core files -- **New Files Added**: 1 (executor_registry.py) -- **Test Files Added**: 1 (test_subprocess_cleanup.py) -- **Test Cases**: 14 tests covering all scenarios -- **Test Pass Rate**: 100% (88 passed, 7 skipped) - -## Acceptance Criteria Status - -- ✅ No ghost processes remain after server shutdown -- ✅ Signal handlers properly clean up all subprocesses -- ✅ Process cleanup works under all exception conditions -- ✅ Subprocess cleanup completes within reasonable timeouts -- ✅ Process groups prevent orphaned child processes -- ✅ Comprehensive logging for debugging process issues - -## Manual Testing Verified - -1. **Signal handling**: Server responds properly to SIGINT/SIGTERM -2. **Process monitoring**: No processes remain after shutdown -3. **Timeout scenarios**: Cleanup completes within expected timeframes -4. **Concurrent execution**: Multiple executors cleanup properly -5. **Error conditions**: Cleanup works even when operations fail - -The implementation successfully addresses all concerns raised in Issue #2 and provides a robust, production-ready solution for subprocess management. \ No newline at end of file diff --git a/.tmp/pyodide_architecture.md b/.tmp/pyodide_architecture.md deleted file mode 100644 index 7d85adc..0000000 --- a/.tmp/pyodide_architecture.md +++ /dev/null @@ -1,227 +0,0 @@ -# Pyodideベース MIP-MCP サーバー アーキテクチャ設計 - -## 概要 - -既存のカスタムサンドボックスをPyodideベースの安全なWebAssembly実行環境に置き換える。ツール名は汎用的なMIP対応のまま維持し、現在はPuLPのみサポートする。 - -## アーキテクチャ - -### 1. **Pyodide実行エンジン** (`PyodideExecutor`) - -```typescript -interface PyodideExecutor { - // Pyodide環境の初期化(遅延読み込み) - initialize(): Promise - - // MIPコード実行(PuLP対応) - executeMIPCode(code: string, options: ExecutionOptions): Promise - - // ライブラリ検出 - detectLibrary(code: string): MIPLibrary - - // パフォーマンス最適化 - warmup(): Promise - dispose(): void -} -``` - -### 2. **ライブラリ検出システム** (`LibraryDetector`) - -```python -class LibraryDetector: - def detect_library(self, code: str) -> MIPLibrary: - """コードからMIPライブラリを自動検出""" - if 'import pulp' in code or 'from pulp' in code: - return MIPLibrary.PULP - elif 'import mip' in code or 'from mip' in code: - return MIPLibrary.PYTHON_MIP # 将来対応 - else: - return MIPLibrary.UNKNOWN -``` - -### 3. **問題情報抽出** (`ProblemExtractor`) - -```python -class ProblemExtractor: - def extract_problem_info(self, code: str, pyodide_globals: dict) -> ProblemInfo: - """実行後のグローバル変数から問題情報を抽出""" - # 1. 変数方式(推奨) - if '__mps_content__' in pyodide_globals: - return ProblemInfo(format='mps', content=pyodide_globals['__mps_content__']) - elif '__lp_content__' in pyodide_globals: - return ProblemInfo(format='lp', content=pyodide_globals['__lp_content__']) - - # 2. 自動検出方式 - problems = self._detect_problems(pyodide_globals) - if problems: - return self._generate_format(problems[0]) - - return ProblemInfo(format=None, content=None) -``` - -### 4. **MCPツール統合** - -既存のツール名を維持しつつ、内部実装をPyodideに変更: - -```python -class MIPMCPTools: - # ツール名: execute_mip_code (汎用的) - # 実装: PuLPサポート、将来的に他ライブラリ対応可能 - async def execute_mip_code(self, code: str) -> ExecutionResult - - # ツール名: validate_mip_code (汎用的) - # 実装: 現在はPuLP構文検証 - async def validate_mip_code(self, code: str) -> ValidationResult - - # その他既存ツールも同様に汎用的な名前を維持 -``` - -## 実装詳細 - -### Phase 1: Pyodide実行エンジン - -1. **Node.js + Pyodide統合** - ```typescript - class PyodideExecutor { - private pyodide: PyodideInterface | null = null - - async initialize() { - if (!this.pyodide) { - this.pyodide = await loadPyodide() - await this.pyodide.loadPackage("micropip") - await this.installMIPLibraries() - } - } - - private async installMIPLibraries() { - await this.pyodide.runPythonAsync(` - import micropip - await micropip.install('pulp') - `) - } - } - ``` - -2. **セキュアな実行環境** - ```python - # Pyodide内部で実行される安全なコード実行ラッパー - def secure_execute_mip_code(user_code: str) -> dict: - # グローバル名前空間を制限 - safe_globals = { - '__builtins__': {}, - 'pulp': pulp, - # その他必要最小限のみ - } - - try: - exec(user_code, safe_globals) - return { - 'success': True, - 'globals': safe_globals, - 'error': None - } - except Exception as e: - return { - 'success': False, - 'globals': {}, - 'error': str(e) - } - ``` - -### Phase 2: 問題情報抽出 - -1. **変数ベース抽出(推奨)** - ```python - # ユーザーコード例 - import pulp - prob = pulp.LpProblem("example", pulp.LpMaximize) - # ... 問題定義 ... - - # LP形式を変数に設定(推奨方式) - prob.writeLP("/tmp/problem.lp") - with open("/tmp/problem.lp", "r") as f: - __lp_content__ = f.read() - ``` - -2. **自動検出(フォールバック)** - ```python - def auto_detect_problems(globals_dict: dict) -> List[ProblemInfo]: - problems = [] - for name, obj in globals_dict.items(): - if hasattr(obj, 'writeLP'): # PuLP問題オブジェクト - problems.append(extract_from_pulp_problem(obj)) - return problems - ``` - -### Phase 3: パフォーマンス最適化 - -1. **Pyodide事前ウォームアップ** - ```typescript - class PyodidePool { - private instances: PyodideExecutor[] = [] - - async warmup(count: number = 3) { - for (let i = 0; i < count; i++) { - const executor = new PyodideExecutor() - await executor.initialize() - this.instances.push(executor) - } - } - - async getExecutor(): Promise { - return this.instances.pop() || new PyodideExecutor() - } - } - ``` - -2. **メモリ効率化** - - 不要なライブラリの遅延読み込み - - 実行後のクリーンアップ - - インスタンス再利用 - -## 移行計画 - -### Step 1: 新しいPyodide実行エンジン実装 -- `src/mip_mcp/executor/pyodide_executor.py` -- Node.js統合とPyodide初期化 - -### Step 2: 既存ハンドラーの更新 -- `src/mip_mcp/handlers/execute_code.py`の内部実装変更 -- MCPツール名とインターフェースは維持 - -### Step 3: ライブラリ検出の統合 -- `src/mip_mcp/utils/library_detector.py`をPyodide対応に更新 - -### Step 4: テストとパフォーマンス調整 -- セキュリティテスト -- パフォーマンステスト -- エラーハンドリング強化 - -## 期待される効果 - -### セキュリティ -- ✅ **完全分離**: WebAssembly環境での実行 -- ✅ **ゼロ脆弱性**: ホストシステムアクセス不可 -- ✅ **検証済み**: Pyodideは本番環境で広く使用 - -### 機能性 -- ✅ **PuLP完全サポート**: LP/MPS生成確認済み -- ✅ **拡張性**: 将来的にpython-mip等追加可能 -- ✅ **互換性**: 既存MCPツール名・インターフェース維持 - -### パフォーマンス -- ✅ **高速起動**: 数百ms程度 -- ✅ **軽量**: メモリ使用量最適化 -- ✅ **スケーラブル**: Cloud Run環境での運用 - -## リスク軽減 - -### 潜在的リスク -1. **Pyodide起動時間**: ウォームアップで解決 -2. **メモリ使用量**: プールとクリーンアップで管理 -3. **ライブラリ制限**: PuLPサポート確認済み - -### 対策 -- インスタンスプール -- メモリ監視 -- グレースフルフォールバック(RestrictedPython) \ No newline at end of file diff --git a/.tmp/requirements.md b/.tmp/requirements.md deleted file mode 100644 index 5534797..0000000 --- a/.tmp/requirements.md +++ /dev/null @@ -1,173 +0,0 @@ -# 要件定義書 - MIP MCP Server - -## 1. 目的 - -LLMからのPuLPコードを受信し、MPS/LPファイルを生成してMIP最適化を実行するMCP(Model Context Protocol)サーバーを構築する。LLMクライアントはPuLPライブラリを使用してPythonコードを生成し、MCPサーバーがそのコードを実行してMPS/LPファイルを出力、pyscipoptで解決して結果を返却する。 - -## 2. 機能要件 - -### 2.1 必須機能 - -#### 基本MCP機能 -- [ ] MCP対応のサーバー実装(fastmcpベース) -- [ ] streamable HTTPトランスポート対応(fastmcp標準機能) -- [ ] LLMクライアントとの双方向通信インターフェース -- [ ] LP/MPS形式での問題ファイル受信・処理 - -#### 最適化ソルバー統合 -- [ ] pyscipopt(デフォルト)による最適化実行 -- [ ] 設定ファイルによるソルバー切り替え機能(SCIP、Gurobi、CPLEX等) -- [ ] ソルバーパラメータの動的調整 -- [ ] 最適化結果の構造化出力 - -#### Pythonコード実行機能(PuLP特化) -- [ ] PuLPライブラリを使用したPythonコード実行 -- [ ] MPS/LPファイル生成機能 -- [ ] 生成されたファイルの妥当性検証 -- [ ] PuLPモデルからソルバー対応形式への変換 - -#### 問題検証・デバッグ支援 -- [ ] 制約条件の数学的検証 -- [ ] 実行不可能モデルの診断 -- [ ] 制約緩和提案機能 -- [ ] エラーメッセージの自然言語説明 - -### 2.2 オプション機能(将来実装) - -- [ ] 複数最適化ライブラリ対応(pyomo、cvxpy等) -- [ ] 複数ソルバーでの結果比較 -- [ ] ソリューションの感度分析 -- [ ] 最適化プロセスの可視化 - -## 3. 非機能要件 - -### 3.1 パフォーマンス - -- 処理性能はソルバーとマシン性能に依存(具体的な時間制約なし) -- ソルバー実行のプログレス情報提供 -- メモリ使用量の適切な管理 -- streamable HTTPによる効率的なデータ転送 - -### 3.2 セキュリティ - -- 入力データの検証とサニタイゼーション -- ソルバー実行時のサンドボックス化 -- 機密性の高い最適化データの保護 -- ログ情報の適切な管理 - -### 3.3 保守性 - -- モジュラー設計による機能拡張の容易性 -- 設定ファイルによる動作カスタマイズ -- 詳細なログ出力とデバッグ情報 -- 包括的なユニットテストカバレージ - -### 3.4 互換性 - -- 主要MIPソルバーとの互換性維持 -- 既存のMCP実装との相互運用性 -- Python 3.12以上での動作保証 -- クロスプラットフォーム対応 - -### 3.5 クラウドデプロイメント - -- Google Cloud Run環境での実行対応 -- コンテナ化による可搬性確保 -- ステートレス設計によるスケーラビリティ -- 環境変数による設定管理 - -## 4. 制約事項 - -### 4.1 技術的制約 - -- fastmcp v2.10.6以上の使用 -- pyscipopt v5.5.0以上の使用 -- Pythonベースの実装 -- MCP仕様への準拠 - -### 4.2 ビジネス制約 - -- オープンソースライセンスでの提供 -- 商用ソルバーは別途ライセンス必要 -- 初期段階では基本機能の実装を優先 - -### 4.3 デプロイメント制約 - -- Cloud Run環境のメモリ・CPU制限への対応 -- コールドスタート時間の最小化 -- リクエストタイムアウト(最大60分)内での処理 -- 永続ストレージなしでの動作 - -## 5. 成功基準 - -### 5.1 完了の定義 - -- [ ] MCP経由でLLMクライアントがPuLPコードを送信可能 -- [ ] PuLPコードからMPS/LPファイルを生成 -- [ ] 生成されたファイルをpyscipoptで解決 -- [ ] 最適化結果(変数値、目的関数値、ステータス)を返却 -- [ ] エラー処理と適切なフィードバックの提供 -- [ ] streamable HTTPトランスポートでの通信確立 -- [ ] Cloud Run環境での正常動作確認 - -### 5.2 受け入れテスト - -- PuLPコードによるサンプル最適化問題の解決 -- 線形計画・整数計画問題でのMPS/LP生成テスト -- 生成ファイルの妥当性検証テスト -- エラーケースでの適切な診断メッセージ表示 -- MCP streamable HTTPでの通信テスト -- Cloud Run環境でのデプロイメントテスト -- コンテナ環境でのPuLP→MPS/LP→解決フローテスト - -## 6. 想定されるリスク - -### 技術リスク -- 商用ソルバーのライセンス・インストール複雑性 -- MCP仕様の進化に伴う互換性問題 -- 大規模問題でのメモリ・処理時間制約 - -### 運用リスク -- ソルバー依存関係の管理 -- LLMとの通信エラー処理 -- 不正な最適化問題入力の処理 - -### クラウド運用リスク -- Cloud Runのコールドスタート遅延 -- メモリ・CPU制限による大規模問題の制約 -- ネットワーク接続の不安定性 -- 商用ソルバーライセンスのクラウド対応 - -### 対策 -- 複数ソルバーでの代替実装 -- 詳細なエラーハンドリング -- 入力検証の強化 -- コンテナイメージの最適化 -- ヘルスチェック機能の実装 -- 適切なタイムアウト設定 - -## 7. 今後の検討事項 - -### 設計フェーズで詳細化すべき事項 -- MCPサーバーのアーキテクチャ設計 -- ソルバー抽象化層の設計 -- Pythonコード実行環境の設計 -- 設定管理システムの設計 -- エラーハンドリング戦略 -- テスト戦略とカバレージ計画 -- Cloud Run デプロイメント戦略 -- Dockerコンテナ設計 - -### 実装優先度の検討 -- Phase 1: 基本MCP機能 + PuLP + pyscipopt統合 -- Phase 2: MPS/LPファイル生成・処理機能 -- Phase 3: Cloud Run対応 + コンテナ化 -- Phase 4: 他最適化ライブラリ対応(pyomo、cvxpy等) -- Phase 5: 複数ソルバー対応 - -### 外部依存関係の調査 -- PuLPライブラリの機能とMPS/LP出力形式 -- pyscipoptとの互換性確認 -- MCPクライアントとの連携テスト -- Cloud Runの制限事項とベストプラクティス -- コンテナ環境でのPuLP + pyscipoptの動作確認 \ No newline at end of file diff --git a/.tmp/solver_selection_design.md b/.tmp/solver_selection_design.md deleted file mode 100644 index c1daeac..0000000 --- a/.tmp/solver_selection_design.md +++ /dev/null @@ -1,37 +0,0 @@ -# Solver Selection Design - -## Problem -現在、ユーザーはソルバーパラメータ(solver_params)を設定できるが、どのソルバーを使用するかを指定できない。execute_mip_codeツールはハードコードでSCIPソルバーのみを使用している。 - -## Solution Design - -### 1. APIパラメータの追加 -`execute_mip_code`ツールに`solver`パラメータを追加: -- Type: Optional[str] = None -- Default: None (設定ファイルのdefaultを使用) -- Options: "scip" (現在は1つのみサポート) - -### 2. SolverFactoryパターンの実装 -ソルバー選択のためのファクトリークラスを作成: -```python -class SolverFactory: - @staticmethod - def create_solver(solver_name: str, config: Dict[str, Any]) -> BaseSolver: - if solver_name.lower() == "scip": - return SCIPSolver(config) - else: - raise ValueError(f"Unsupported solver: {solver_name}") -``` - -### 3. 設定からのデフォルトソルバー取得 -設定ファイル(default.yaml)の`solvers.default`を使用してデフォルトソルバーを決定 - -### 4. バックワード互換性 -- 既存のコードは変更なしで動作し続ける -- solver パラメータが未指定の場合はSCIPを使用 - -## Implementation Steps -1. SolverFactoryクラスの実装 -2. execute_mip_code_handlerの更新(solverパラメータ追加) -3. MCP tool定義の更新 -4. テストの追加 \ No newline at end of file diff --git a/.tmp/tasks.md b/.tmp/tasks.md deleted file mode 100644 index 6cfd1d9..0000000 --- a/.tmp/tasks.md +++ /dev/null @@ -1,380 +0,0 @@ -# タスク分解書 - MIP MCP Server - -## 1. 実装タスク概要 - -要件・設計に基づいて、PuLP特化のMIP MCP Serverを実装する。 - -### ワークフロー -``` -LLM Client → PuLP Code → MCP Server → MPS/LP File → pyscipopt → Results → LLM Client -``` - -## 2. Phase 1: 基本MCP機能 + PuLP + pyscipopt統合 - -### 2.1 プロジェクト基盤セットアップ - -#### T001: 依存関係の更新 -- [ ] pyproject.tomlにPuLP依存関係追加 -- [ ] 必要な依存関係のインストール確認 -- [ ] 開発環境のセットアップ - -**優先度**: High -**見積時間**: 0.5h -**成果物**: 更新されたpyproject.toml - -#### T002: プロジェクト構造の作成 -- [ ] src/mip_mcp/配下のディレクトリ構造作成 -- [ ] 基本的な__init__.pyファイル作成 -- [ ] 設定ファイルディレクトリの作成 - -**優先度**: High -**見積時間**: 0.5h -**成果物**: 基本プロジェクト構造 - -### 2.2 データモデル定義 - -#### T003: 基本データモデル実装 -- [ ] models/solution.py: OptimizationSolution実装 -- [ ] models/config.py: 設定データモデル実装 -- [ ] Pydanticモデルの基本検証 - -**優先度**: High -**見積時間**: 1h -**成果物**: データモデルクラス - -### 2.3 設定管理システム - -#### T004: 設定管理実装 -- [ ] utils/config_manager.py実装 -- [ ] config/default.yaml作成 -- [ ] 環境変数サポート実装 - -**優先度**: High -**見積時間**: 1h -**成果物**: 設定管理システム - -#### T005: ログシステム実装 -- [ ] utils/logger.py実装 -- [ ] 構造化ログ出力設定 -- [ ] ログレベル設定 - -**優先度**: Medium -**見積時間**: 0.5h -**成果物**: ログシステム - -### 2.4 PuLPコード実行エンジン - -#### T006: セキュリティチェッカー実装 -- [ ] executor/sandbox.py実装 -- [ ] ASTベースの危険コード検出 -- [ ] SecurityError例外クラス定義 - -**優先度**: High -**見積時間**: 2h -**成果物**: セキュアなコード検証機能 - -#### T007: PuLP実行環境実装 -- [ ] executor/code_executor.py実装 -- [ ] PuLPライブラリの安全な実行環境 -- [ ] 実行タイムアウト制御 - -**優先度**: High -**見積時間**: 2h -**成果物**: PuLP実行エンジン - -#### T008: MPS/LP生成機能実装 -- [ ] PuLPモデルからMPS形式生成 -- [ ] PuLPモデルからLP形式生成 -- [ ] 生成ファイルの妥当性検証 - -**優先度**: High -**見積時間**: 1.5h -**成果物**: ファイル生成機能 - -### 2.5 ソルバー統合 - -#### T009: ベースソルバークラス実装 -- [ ] solvers/base.py実装 -- [ ] 抽象ソルバーインターフェース定義 -- [ ] エラーハンドリング基盤 - -**優先度**: High -**見積時間**: 1h -**成果物**: ソルバー抽象化基盤 - -#### T010: SCIPソルバー実装 -- [ ] solvers/scip_solver.py実装 -- [ ] MPS/LPファイル読み込み機能 -- [ ] pyscipoptによる最適化実行 -- [ ] 結果抽出機能 - -**優先度**: High -**見積時間**: 2h -**成果物**: SCIP統合機能 - -### 2.6 MCPサーバー実装 - -#### T011: MCPハンドラー実装 -- [ ] handlers/execute_code.py実装 -- [ ] PuLPコード実行ハンドラー -- [ ] エラーハンドリングとレスポンス形成 - -**優先度**: High -**見積時間**: 1.5h -**成果物**: MCP実行ハンドラー - -#### T012: MCPサーバーメイン実装 -- [ ] server.py実装 -- [ ] FastMCPサーバー設定 -- [ ] ハンドラー登録とルーティング - -**優先度**: High -**見積時間**: 1h -**成果物**: MCPサーバー本体 - -#### T013: アプリケーションエントリーポイント -- [ ] __init__.py実装 -- [ ] main()関数実装 -- [ ] コマンドライン起動機能 - -**優先度**: High -**見積時間**: 0.5h -**成果物**: 実行可能アプリケーション - -## 3. Phase 2: MPS/LPファイル生成・処理機能強化 - -### 3.1 ファイル処理機能拡張 - -#### T014: ファイル検証機能強化 -- [ ] 生成されたMPS/LPファイルの構文検証 -- [ ] ソルバー固有の制限事項チェック -- [ ] 詳細なエラーレポート機能 - -**優先度**: Medium -**見積時間**: 1.5h -**成果物**: 強化された検証機能 - -#### T015: ファイル最適化機能 -- [ ] MPS/LPファイルの最適化(サイズ削減) -- [ ] 冗長な制約の検出と削除 -- [ ] 変数名の正規化 - -**優先度**: Low -**見積時間**: 2h -**成果物**: ファイル最適化機能 - -### 3.2 エラーハンドリング強化 - -#### T016: 詳細エラー診断 -- [ ] PuLPコードエラーの詳細分析 -- [ ] ソルバーエラーの分類と説明 -- [ ] 修正提案機能 - -**優先度**: Medium -**見積時間**: 2h -**成果物**: 高度なエラー診断機能 - -#### T017: カスタム例外クラス -- [ ] MIPMCPError基底クラス実装 -- [ ] SolverError, ParseError等の専用例外 -- [ ] 例外階層の整理 - -**優先度**: Medium -**見積時間**: 1h -**成果物**: 例外クラス体系 - -## 4. Phase 3: Cloud Run対応 + コンテナ化 - -### 4.1 コンテナ化 - -#### T018: Dockerfile作成 -- [ ] マルチステージビルドDockerfile -- [ ] Python環境とソルバー依存関係 -- [ ] セキュリティ最適化 - -**優先度**: High -**見積時間**: 2h -**成果物**: Dockerfile - -#### T019: Docker環境テスト -- [ ] ローカルDockerビルドテスト -- [ ] コンテナ内でのPuLP + pyscipopt動作確認 -- [ ] メモリ・CPU使用量測定 - -**優先度**: High -**見積時間**: 1h -**成果物**: 動作確認済みコンテナ - -### 4.2 Cloud Run対応 - -#### T020: Cloud Run設定 -- [ ] service.yaml作成 -- [ ] 環境変数設定 -- [ ] リソース制限設定 - -**優先度**: High -**見積時間**: 1h -**成果物**: Cloud Run設定ファイル - -#### T021: ヘルスチェック実装 -- [ ] /health エンドポイント実装 -- [ ] システム状態監視機能 -- [ ] 依存関係チェック - -**優先度**: High -**見積時間**: 1h -**成果物**: ヘルスチェック機能 - -#### T022: Cloud Runデプロイメント -- [ ] gcloud CLIによるデプロイ -- [ ] 動作確認テスト -- [ ] パフォーマンス測定 - -**優先度**: High -**見積時間**: 1h -**成果物**: デプロイ済みサービス - -## 5. テスト実装 - -### 5.1 ユニットテスト - -#### T023: コアコンポーネントテスト -- [ ] PuLP実行エンジンテスト -- [ ] ソルバー統合テスト -- [ ] データモデルテスト - -**優先度**: High -**見積時間**: 3h -**成果物**: ユニットテストスイート - -#### T024: セキュリティテスト -- [ ] 危険なコードの検出テスト -- [ ] サンドボックス機能テスト -- [ ] 入力検証テスト - -**優先度**: High -**見積時間**: 2h -**成果物**: セキュリティテストスイート - -### 5.2 統合テスト - -#### T025: エンドツーエンドテスト -- [ ] PuLP → MPS/LP → 解決フローテスト -- [ ] MCPクライアント統合テスト -- [ ] エラーケーステスト - -**優先度**: High -**見積時間**: 2h -**成果物**: 統合テストスイート - -#### T026: Cloud Run統合テスト -- [ ] コンテナ環境でのテスト -- [ ] Cloud Run環境でのテスト -- [ ] パフォーマンステスト - -**優先度**: Medium -**見積時間**: 1.5h -**成果物**: クラウド統合テスト - -## 6. ドキュメント作成 - -### 6.1 開発者向けドキュメント - -#### T027: API仕様書 -- [ ] MCPツール仕様 -- [ ] リクエスト・レスポンス形式 -- [ ] エラーコード一覧 - -**優先度**: Medium -**見積時間**: 2h -**成果物**: API仕様書 - -#### T028: 開発環境セットアップガイド -- [ ] 依存関係インストール手順 -- [ ] 開発環境構築手順 -- [ ] テスト実行方法 - -**優先度**: Medium -**見積時間**: 1h -**成果物**: 開発者ガイド - -### 6.2 運用ドキュメント - -#### T029: デプロイメントガイド -- [ ] Cloud Runデプロイ手順 -- [ ] 設定オプション説明 -- [ ] トラブルシューティング - -**優先度**: Medium -**見積時間**: 1.5h -**成果物**: デプロイメントガイド - -#### T030: 使用例とチュートリアル -- [ ] PuLPコード例 -- [ ] 典型的な最適化問題の解決例 -- [ ] トラブルシューティング事例 - -**優先度**: Low -**見積時間**: 2h -**成果物**: チュートリアル - -## 7. 実装順序とマイルストーン - -### Milestone 1: 基本機能完成 (Phase 1) -**期間**: 2-3日 -**成果物**: 動作するMCP Server + PuLP + pyscipopt統合 - -**クリティカルパス**: -T001 → T002 → T003 → T006 → T007 → T008 → T010 → T011 → T012 → T013 - -### Milestone 2: 機能強化 (Phase 2) -**期間**: 1-2日 -**成果物**: エラーハンドリング強化、ファイル処理改善 - -**並行実装可能**: -- T014, T015 (ファイル処理) -- T016, T017 (エラーハンドリング) - -### Milestone 3: クラウド対応 (Phase 3) -**期間**: 1-2日 -**成果物**: Cloud Run対応完了 - -**実装順序**: -T018 → T019 → T020 → T021 → T022 - -### Milestone 4: 品質保証 -**期間**: 2-3日 -**成果物**: テスト完備、ドキュメント整備 - -**並行実装可能**: -- T023, T024, T025 (テスト) -- T027, T028, T029 (ドキュメント) - -## 8. リスク対応 - -### 高リスクタスク -- **T006**: セキュリティチェッカー - 複雑なAST解析 -- **T008**: MPS/LP生成 - PuLPの出力形式への依存 -- **T018**: Dockerfile - ソルバー依存関係の複雑性 - -### 代替案 -- セキュリティチェッカーが複雑な場合 → 基本的な文字列チェックで代替 -- MPS/LP生成に問題がある場合 → JSON形式での中間表現を使用 -- Docker化に問題がある場合 → 標準Python環境での実行を優先 - -## 9. 品質基準 - -### コード品質 -- テストカバレージ > 80% -- 型ヒント完備 -- ドキュメント文字列完備 - -### パフォーマンス -- PuLPコード実行 < 30秒 -- MPS/LP生成 < 5秒 -- MCPレスポンス < 1秒 (ソルバー実行除く) - -### セキュリティ -- 危険なコードの検出率 > 95% -- サンドボックス環境での隔離 -- 入力検証完備 \ No newline at end of file diff --git a/Makefile b/Makefile index 886b144..f9b9db9 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for MIP-MCP project -.PHONY: help install test test-unit test-integration test-basic coverage lint format clean dev-install +.PHONY: help install test test-unit test-integration test-basic coverage lint format clean dev-install pre-commit-install pre-commit-run pre-commit-update # Default target help: @@ -14,6 +14,9 @@ help: @echo " coverage Run tests with coverage report" @echo " lint Run linting with ruff" @echo " format Format code with ruff" + @echo " pre-commit-install Install pre-commit hooks" + @echo " pre-commit-run Run pre-commit on all files" + @echo " pre-commit-update Update pre-commit hook versions" @echo " clean Clean cache and build artifacts" # Installation @@ -58,6 +61,16 @@ lint-check: uv run ruff check src/ tests/ uv run ruff format --check src/ tests/ +# Pre-commit hooks +pre-commit-install: + uv run pre-commit install + +pre-commit-run: + uv run pre-commit run --all-files + +pre-commit-update: + uv run pre-commit autoupdate + # Cleanup clean: find . -type d -name __pycache__ -delete @@ -77,4 +90,4 @@ check: @echo "Running development checks..." $(MAKE) format $(MAKE) lint - $(MAKE) test-basic \ No newline at end of file + $(MAKE) test-basic diff --git a/README.md b/README.md index b315e55..dfb461a 100644 --- a/README.md +++ b/README.md @@ -224,17 +224,18 @@ uv run pytest tests/unit/test_handlers.py -v ### Code Quality ```bash -# Format code -uv run black src/ tests/ +# Format code and fix issues +make format -# Sort imports -uv run isort src/ tests/ +# Run linting checks +make lint -# Type checking -uv run mypy src/ +# Check code formatting without changes +make lint-check -# Run linting (if configured) -uv run ruff check src/ tests/ +# Or use Ruff directly +uv run ruff format src/ tests/ +uv run ruff check --fix src/ tests/ ``` ### Development Setup @@ -243,8 +244,39 @@ uv run ruff check src/ tests/ # Install development dependencies uv sync --group dev -# Install pre-commit hooks (if using) -pre-commit install +# Install pre-commit hooks (recommended) +make pre-commit-install + +# OR use pre-commit directly +uv run pre-commit install +``` + +### Pre-commit Hooks + +This project uses pre-commit hooks to automatically check code quality before commits: + +```bash +# Install pre-commit hooks +make pre-commit-install + +# Run pre-commit on all files +make pre-commit-run + +# Update hook versions +make pre-commit-update +``` + +The pre-commit hooks will automatically: +- Format code with Ruff +- Fix linting issues +- Check for trailing whitespace +- Validate YAML and TOML files +- Check for merge conflicts +- Remove debug statements + +If pre-commit hooks fail, fix the issues and commit again. To skip hooks in emergencies: +```bash +git commit --no-verify -m "Emergency commit" ``` ## Architecture diff --git a/docs/FUNCTIONAL_REQUIREMENTS.md b/docs/FUNCTIONAL_REQUIREMENTS.md index c7b14e5..8ddfe40 100644 --- a/docs/FUNCTIONAL_REQUIREMENTS.md +++ b/docs/FUNCTIONAL_REQUIREMENTS.md @@ -7,7 +7,7 @@ - **Input**: Natural language business problem description, context, constraints - **Processing**: Semantic parsing, constraint extraction, template matching - **Output**: Mathematical MIP model (variables, constraints, objective function) -- **Acceptance Criteria**: +- **Acceptance Criteria**: - Support for common business domains (supply chain, scheduling, resource allocation) - 90% accuracy for well-defined problems - Generate valid mathematical formulations @@ -212,4 +212,4 @@ ### Reliability - Graceful handling of edge cases - Fallback mechanisms for failures -- Consistent behavior across domains \ No newline at end of file +- Consistent behavior across domains diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 5590032..b59959b 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -32,7 +32,7 @@ Transform optimization from a specialist tool to an accessible business solution - Mathematical constraint extraction - Variable and objective function definition -2. **validate_and_debug_model** +2. **validate_and_debug_model** - Mathematical formulation validation - Infeasibility detection and analysis - Error interpretation and suggestions @@ -402,4 +402,4 @@ mip-mcp/ This implementation plan provides a comprehensive roadmap for building an LLM-Enhanced MIP system that addresses real-world optimization barriers. Through a phased approach focusing on core value delivery, technical excellence, and user experience, the system will transform optimization from a specialist tool to an accessible business capability. -The plan balances innovation with practical implementation considerations, ensuring deliverable value at each phase while building toward transformative AI-enhanced optimization capabilities. Success depends on disciplined execution, continuous user feedback, and maintaining focus on solving real business problems through intelligent automation. \ No newline at end of file +The plan balances innovation with practical implementation considerations, ensuring deliverable value at each phase while building toward transformative AI-enhanced optimization capabilities. Success depends on disciplined execution, continuous user feedback, and maintaining focus on solving real business problems through intelligent automation. diff --git a/docs/LLM_ENHANCED_MIP_RESEARCH.md b/docs/LLM_ENHANCED_MIP_RESEARCH.md index ba0fac9..5b596d4 100644 --- a/docs/LLM_ENHANCED_MIP_RESEARCH.md +++ b/docs/LLM_ENHANCED_MIP_RESEARCH.md @@ -255,4 +255,4 @@ Mixed Integer Programming (MIP) remains underutilized in real-world applications LLM-enhanced MIP represents a paradigm shift in optimization accessibility and effectiveness. By addressing the fundamental barriers to MIP adoption through intelligent automation, natural language interfaces, and seamless integration capabilities, this approach can unlock the value of optimization for a much broader range of organizations and applications. -The proposed MCP functions provide a comprehensive framework for implementing this vision, with a clear implementation roadmap that delivers value incrementally while building toward transformative capabilities. Success depends on careful attention to validation, user experience, and the balance between automation and human expertise. \ No newline at end of file +The proposed MCP functions provide a comprehensive framework for implementing this vision, with a clear implementation roadmap that delivers value incrementally while building toward transformative capabilities. Success depends on careful attention to validation, user experience, and the balance between automation and human expertise. diff --git a/docs/SYSTEM_REQUIREMENTS.md b/docs/SYSTEM_REQUIREMENTS.md index 8a6a7f6..376cc03 100644 --- a/docs/SYSTEM_REQUIREMENTS.md +++ b/docs/SYSTEM_REQUIREMENTS.md @@ -362,4 +362,4 @@ The LLM-Enhanced MIP system is a distributed, cloud-native application that inte - **Service Dependencies**: Third-party service risk assessment - **Single Points of Failure**: Redundancy for critical components - **Capacity Planning**: Proactive capacity management -- **Performance Degradation**: Performance monitoring and alerting \ No newline at end of file +- **Performance Degradation**: Performance monitoring and alerting diff --git a/docs/TECHNICAL_REQUIREMENTS.md b/docs/TECHNICAL_REQUIREMENTS.md index 16329cb..2cf6f0c 100644 --- a/docs/TECHNICAL_REQUIREMENTS.md +++ b/docs/TECHNICAL_REQUIREMENTS.md @@ -269,4 +269,4 @@ - Modular architecture with clear interfaces - Comprehensive unit and integration tests - Automated deployment pipelines -- Clear documentation and code comments \ No newline at end of file +- Clear documentation and code comments diff --git a/docs/USER_REQUIREMENTS.md b/docs/USER_REQUIREMENTS.md index b3ed0b4..64bbd43 100644 --- a/docs/USER_REQUIREMENTS.md +++ b/docs/USER_REQUIREMENTS.md @@ -45,8 +45,8 @@ ### Business Analyst Use Cases #### UC-001: Supply Chain Optimization -**As a** supply chain analyst -**I want to** optimize inventory distribution across warehouses +**As a** supply chain analyst +**I want to** optimize inventory distribution across warehouses **So that** I can minimize costs while meeting demand requirements **Acceptance Criteria:** @@ -56,8 +56,8 @@ - Export results to Excel for presentation #### UC-002: Resource Allocation -**As a** project manager -**I want to** allocate team members to projects optimally +**As a** project manager +**I want to** allocate team members to projects optimally **So that** I can maximize productivity while respecting skill requirements **Acceptance Criteria:** @@ -69,8 +69,8 @@ ### Operations Research Practitioner Use Cases #### UC-003: Model Development Acceleration -**As an** OR analyst -**I want to** quickly formulate complex optimization models +**As an** OR analyst +**I want to** quickly formulate complex optimization models **So that** I can focus on analysis rather than implementation **Acceptance Criteria:** @@ -80,8 +80,8 @@ - Generate code for multiple solver platforms #### UC-004: Performance Optimization -**As an** optimization engineer -**I want to** improve solver performance on large models +**As an** optimization engineer +**I want to** improve solver performance on large models **So that** I can solve problems within acceptable timeframes **Acceptance Criteria:** @@ -93,8 +93,8 @@ ### Data Scientist Use Cases #### UC-005: Workflow Integration -**As a** data scientist -**I want to** integrate optimization into my ML pipeline +**As a** data scientist +**I want to** integrate optimization into my ML pipeline **So that** I can create end-to-end decision support systems **Acceptance Criteria:** @@ -104,8 +104,8 @@ - Automate recurring optimization workflows #### UC-006: Experiment Management -**As a** research scientist -**I want to** run multiple optimization experiments +**As a** research scientist +**I want to** run multiple optimization experiments **So that** I can compare different approaches systematically **Acceptance Criteria:** @@ -117,8 +117,8 @@ ### Executive Use Cases #### UC-007: Strategic Decision Support -**As an** executive -**I want to** understand optimization recommendations +**As an** executive +**I want to** understand optimization recommendations **So that** I can make informed strategic decisions **Acceptance Criteria:** @@ -128,8 +128,8 @@ - Access results on mobile devices #### UC-008: Performance Monitoring -**As a** department head -**I want to** monitor optimization system performance +**As a** department head +**I want to** monitor optimization system performance **So that** I can ensure business objectives are met **Acceptance Criteria:** @@ -340,4 +340,4 @@ - 20-40% improvement in optimization quality - 60% reduction in model development time - 90% reduction in debugging time -- 85% accuracy in automated model generation \ No newline at end of file +- 85% accuracy in automated model generation diff --git a/pyproject.toml b/pyproject.toml index 1893aa1..8ee354d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dev = [ "pytest-asyncio>=1.1.0", "pytest-cov>=6.2.1", "ruff>=0.12.7", + "pre-commit>=4.0.0", ] [tool.ruff] @@ -83,11 +84,22 @@ ignore = [ "S101", # Use of assert detected "PLR2004", # Magic value used in comparison ] +"src/mip_mcp/utils/library_detector.py" = [ + "N802", # Function name should be lowercase (AST visitor methods exception) +] [tool.ruff.format] # コードフォーマット設定 quote-style = "double" indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" +# Package data configuration for bundled pyodide files +[tool.hatch.build.targets.wheel] +packages = ["src/mip_mcp"] + +[tool.hatch.build.targets.wheel.shared-data] +"node_modules/pyodide/pyodide.js" = "mip_mcp/pyodide/pyodide.js" +"node_modules/pyodide/pyodide.asm.js" = "mip_mcp/pyodide/pyodide.asm.js" +"node_modules/pyodide/pyodide.asm.wasm" = "mip_mcp/pyodide/pyodide.asm.wasm" +"node_modules/pyodide/pyodide-lock.json" = "mip_mcp/pyodide/pyodide-lock.json" +"node_modules/pyodide/python_stdlib.zip" = "mip_mcp/pyodide/python_stdlib.zip" diff --git a/pytest.ini b/pytest.ini index 26786e6..fd74ad5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -20,18 +20,18 @@ markers = e2e: End-to-end tests # Test discovery and execution options -addopts = +addopts = --strict-markers --strict-config --verbose --tb=short --color=yes --durations=10 - + # Coverage configuration [tool:coverage:run] source = src/mip_mcp -omit = +omit = */tests/* */test_* */__pycache__/* @@ -55,4 +55,4 @@ show_missing = true precision = 2 [tool:coverage:html] -directory = htmlcov \ No newline at end of file +directory = htmlcov diff --git a/src/mip_mcp/config/default.yaml b/src/mip_mcp/config/default.yaml index 54bf0ba..e80b1cb 100644 --- a/src/mip_mcp/config/default.yaml +++ b/src/mip_mcp/config/default.yaml @@ -1,21 +1,21 @@ server: name: "mip-mcp" version: "0.1.0" - + logging: level: "INFO" format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - + executor: enabled: true timeout: 300 # seconds for code execution memory_limit: "1GB" - + solvers: default: "scip" timeout: 3600 # seconds - + validation: max_variables: 100000 max_constraints: 100000 - max_code_length: 10000 # characters \ No newline at end of file + max_code_length: 10000 # characters diff --git a/src/mip_mcp/executor/pyodide_executor.py b/src/mip_mcp/executor/pyodide_executor.py index 0bda161..b21f07d 100644 --- a/src/mip_mcp/executor/pyodide_executor.py +++ b/src/mip_mcp/executor/pyodide_executor.py @@ -241,9 +241,36 @@ async def _wait_for_process_ready(self) -> None: f"Error waiting for Pyodide process readiness: {e}" ) from e + def _check_bundled_pyodide(self) -> str | None: + """Check for bundled pyodide installation (from wheel).""" + try: + # Check if bundled pyodide files exist (installed from wheel) + import pkg_resources + + try: + pyodide_js_path = pkg_resources.resource_filename( + "mip_mcp", "pyodide/pyodide.js" + ) + if Path(pyodide_js_path).exists(): + return pyodide_js_path + except (ImportError, FileNotFoundError): + pass + + return None + + except Exception as e: + logger.debug(f"Error checking bundled pyodide: {e}") + return None + async def _find_pyodide_path(self) -> str | None: """Find pyodide installation path.""" try: + # First check for bundled pyodide (from wheel installation) + bundled_path = self._check_bundled_pyodide() + if bundled_path: + logger.info(f"Using bundled pyodide at: {bundled_path}") + return bundled_path + # Try to find pyodide using Node.js proc = await asyncio.create_subprocess_exec( "node", diff --git a/tests/README.md b/tests/README.md index 54d5027..f270668 100644 --- a/tests/README.md +++ b/tests/README.md @@ -105,4 +105,4 @@ make coverage-all 2. 非同期テストのパフォーマンス最適化 3. 統合テスト環境の自動化 4. テストデータの管理改善 -5. セキュリティテストの強化 \ No newline at end of file +5. セキュリティテストの強化 diff --git a/uv.lock b/uv.lock index 7f2af75..f02514b 100644 --- a/uv.lock +++ b/uv.lock @@ -67,6 +67,15 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", size = 508501 } +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + [[package]] name = "click" version = "8.2.1" @@ -202,6 +211,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/1f/4b9f6986add9f6ff361c1bfffeb08fc2f2f6752f8adf8d4dcf0a988b6f28/cyclopts-3.22.3-py3-none-any.whl", hash = "sha256:771ae584868c8beeac74184a96e9fad3726c787b17e47a6f0d5f42cece1df57a", size = 84941 }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + [[package]] name = "dnspython" version = "2.7.0" @@ -275,6 +293,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/05/4958cccbe862958d862b6a15f2d10d2f5ec3c411268dcb131a433e5e7a0d/fastmcp-2.10.6-py3-none-any.whl", hash = "sha256:9782416a8848cc0f4cfcc578e5c17834da620bef8ecf4d0daabf5dd1272411a2", size = 202613 }, ] +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + [[package]] name = "h11" version = "0.16.0" @@ -321,6 +348,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054 }, ] +[[package]] +name = "identify" +version = "2.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145 }, +] + [[package]] name = "idna" version = "3.10" @@ -430,6 +466,7 @@ dev = [ [package.dev-dependencies] dev = [ + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -450,12 +487,22 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "pre-commit", specifier = ">=4.0.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=1.1.0" }, { name = "pytest-cov", specifier = ">=6.2.1" }, { name = "ruff", specifier = ">=0.12.7" }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + [[package]] name = "numpy" version = "2.3.2" @@ -540,6 +587,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, ] +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -549,6 +605,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, +] + [[package]] name = "pulp" version = "3.2.2" @@ -987,3 +1059,17 @@ sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8 wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406 }, ] + +[[package]] +name = "virtualenv" +version = "20.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/96/0834f30fa08dca3738614e6a9d42752b6420ee94e58971d702118f7cfd30/virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0", size = 6076970 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/c6/f8f28009920a736d0df434b52e9feebfb4d702ba942f15338cb4a83eafc1/virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56", size = 6057761 }, +]