| """ |
| API Contract Validator Environment Client. |
| |
| Maintains a persistent WebSocket connection to the environment server. |
| Each client instance has its own isolated session. |
| """ |
|
|
| from typing import Any, Dict |
|
|
| from openenv.core import EnvClient |
| from openenv.core.client_types import StepResult |
|
|
| try: |
| from .models import ValidatorAction, ValidatorObservation, ValidatorState |
| except (ImportError, ModuleNotFoundError): |
| from models import ValidatorAction, ValidatorObservation, ValidatorState |
|
|
|
|
| class ValidatorEnv( |
| EnvClient[ValidatorAction, ValidatorObservation, ValidatorState] |
| ): |
| """Client for the API Contract Validator Environment. |
| |
| Example:: |
| |
| with ValidatorEnv(base_url="http://localhost:7860").sync() as env: |
| result = env.reset() |
| print(result.observation.task_name) |
| result = env.step(ValidatorAction( |
| field_path="email", |
| violation_type="missing_required", |
| description="Required field 'email' is missing.", |
| )) |
| print(result.observation.feedback) |
| |
| Example with Docker:: |
| |
| env = ValidatorEnv.from_docker_image("api-contract-validator:latest") |
| result = env.reset() |
| ... |
| env.close() |
| """ |
|
|
| def _step_payload(self, action: ValidatorAction) -> Dict[str, Any]: |
| """Convert action to JSON payload for the step message.""" |
| return { |
| "action_type": action.action_type, |
| |
| "field_path": action.field_path, |
| "violation_type": action.violation_type, |
| "description": action.description, |
| "suggested_fix": action.suggested_fix, |
| |
| "affected_services": list(action.affected_services), |
| "reasoning": action.reasoning, |
| |
| "fix_strategy": action.fix_strategy, |
| "spec_patch": dict(action.spec_patch), |
| "rationale": action.rationale, |
| } |
|
|
| def _parse_result( |
| self, payload: Dict[str, Any] |
| ) -> StepResult[ValidatorObservation]: |
| """Parse the server response into a typed StepResult.""" |
| obs_data = payload.get("observation", {}) |
| observation = ValidatorObservation( |
| done=payload.get("done", False), |
| reward=payload.get("reward"), |
| task_name=obs_data.get("task_name", ""), |
| task_description=obs_data.get("task_description", ""), |
| phase=obs_data.get("phase", "detection"), |
| api_spec=obs_data.get("api_spec", {}), |
| payload=obs_data.get("payload", {}), |
| violations_found=obs_data.get("violations_found", []), |
| violations_remaining=obs_data.get("violations_remaining", 0), |
| service_graph=obs_data.get("service_graph", {}), |
| consumers_traced=obs_data.get("consumers_traced", []), |
| total_consumers=obs_data.get("total_consumers", 0), |
| detected_violation=obs_data.get("detected_violation", {}), |
| consumer_specs=obs_data.get("consumer_specs", {}), |
| fix_validation_results=obs_data.get("fix_validation_results", {}), |
| feedback=obs_data.get("feedback", ""), |
| max_steps=obs_data.get("max_steps", 0), |
| ) |
| return StepResult( |
| observation=observation, |
| reward=payload.get("reward"), |
| done=payload.get("done", False), |
| ) |
|
|
| def _parse_state(self, payload: Dict[str, Any]) -> ValidatorState: |
| """Parse the state response into a typed ValidatorState.""" |
| return ValidatorState( |
| episode_id=payload.get("episode_id"), |
| step_count=payload.get("step_count", 0), |
| task_name=payload.get("task_name", ""), |
| phase=payload.get("phase", "detection"), |
| total_violations=payload.get("total_violations", 0), |
| correct_reports=payload.get("correct_reports", 0), |
| false_positives=payload.get("false_positives", 0), |
| duplicate_reports=payload.get("duplicate_reports", 0), |
| total_consumers=payload.get("total_consumers", 0), |
| consumers_correctly_traced=payload.get("consumers_correctly_traced", 0), |
| consumers_missed=payload.get("consumers_missed", 0), |
| consumers_false_flagged=payload.get("consumers_false_flagged", 0), |
| fix_attempts=payload.get("fix_attempts", 0), |
| fix_validated=payload.get("fix_validated", False), |
| fix_breaks_consumers=payload.get("fix_breaks_consumers", 0), |
| score=payload.get("score", 0.0), |
| ) |
|
|