| import re |
| from abc import ABC, abstractmethod |
| from typing import Dict, Any, List, Optional, Union, Tuple, Set |
|
|
| class BaseValidator: |
| """すべての検証クラスの基底クラス""" |
| |
| def __init__(self, client=None): |
| """検証クラスの初期化""" |
| self.client = client |
| |
| async def _make_async_api_call(self, model: str, messages: List[Dict[str, str]], **kwargs) -> Any: |
| """非同期APIコールを行う共通メソッド""" |
| try: |
| response = self.client.chat.completions.create( |
| model=model, |
| messages=messages, |
| **kwargs |
| ) |
| return response |
| except Exception as e: |
| print(f"[Error] API call failed: {str(e)}") |
| raise |
| |
| def _extract_code_blocks(self, content: str) -> List[Dict[str, Any]]: |
| """マークダウン形式のコードブロックを抽出する共通メソッド""" |
| code_blocks = [] |
| |
| |
| pattern = r'```(\w+)?\s*\n([\s\S]*?)\n```' |
| matches = list(re.finditer(pattern, content)) |
| |
| |
| if matches: |
| for i, match in enumerate(matches): |
| language = match.group(1) or "python" |
| language = language.strip().lower() |
| code = match.group(2) |
| if code.strip(): |
| code_blocks.append({ |
| "language": language, |
| "code": code, |
| "block_index": i |
| }) |
| |
| else: |
| |
| python_patterns = [ |
| r'import\s+\w+', |
| r'from\s+\w+\s+import', |
| r'class\s+\w+\s*\(?\w*\)?:', |
| r'def\s+\w+\s*\(.*\):', |
| ] |
| |
| python_code = False |
| for pattern in python_patterns: |
| if re.search(pattern, content): |
| python_code = True |
| break |
| |
| if python_code: |
| |
| code_blocks.append({ |
| "language": "python", |
| "code": content, |
| "block_index": 0 |
| }) |
| |
| return code_blocks |
| |
| def _update_code_in_content(self, content: str, block_index: int, new_code: str) -> str: |
| """マークダウン形式のコンテンツ内のコードブロックを更新する""" |
| pattern = r'```(\w+)?\s*\n([\s\S]*?)\n```' |
| matches = list(re.finditer(pattern, content)) |
| |
| if 0 <= block_index < len(matches): |
| match = matches[block_index] |
| language = match.group(1) or "unknown" |
| start, end = match.span() |
| |
| new_block = f"```{language}\n{new_code}\n```" |
| content = content[:start] + new_block + content[end:] |
| |
| return content |
| |
| def _extract_nested_code_blocks(self, data: Dict[str, Any]) -> List[Dict[str, Any]]: |
| """辞書/JSONから入れ子になったコードブロックを抽出する""" |
| code_blocks = [] |
| |
| |
| def extract_from_dict(data, current_path=[]): |
| if isinstance(data, dict): |
| |
| if "code" in data and isinstance(data["code"], str) and len(data["code"].strip()) > 0: |
| language = data.get("language", "unknown") |
| code_blocks.append({ |
| "language": language, |
| "code": data["code"], |
| "location": current_path + ["code"] |
| }) |
| |
| |
| for key, value in data.items(): |
| extract_from_dict(value, current_path + [key]) |
| elif isinstance(data, list): |
| for i, item in enumerate(data): |
| extract_from_dict(item, current_path + [i]) |
| |
| |
| extract_from_dict(data) |
| return code_blocks |
|
|
|
|
| class Validator(ABC): |
| """検証クラスのインターフェース""" |
| |
| @abstractmethod |
| async def validate(self, content, context=None): |
| """検証を実行する抽象メソッド""" |
| pass |
| |
| @abstractmethod |
| def get_result_summary(self): |
| """検証結果の要約を返す抽象メソッド""" |
| pass |