File size: 5,486 Bytes
c532e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b3340c
 
c532e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""Pydantic request/response models for the FlakeForge REST API."""

from __future__ import annotations

from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field


# ── Health & Info ────────────────────────────────────────────────────────────

class HealthResponse(BaseModel):
    status: str = "ok"
    version: str = "0.1.0"
    uptime_seconds: float = 0.0
    environment_ready: bool = True
    # True if HF token is present in env (HUGGING_FACE_TOKEN / HF_TOKEN / …); used for challenge LLM
    challenge_llm_configured: bool = False


class ProjectInfo(BaseModel):
    name: str = "FlakeForge"
    version: str = "0.1.0"
    description: str = "RL Agent for Flaky Test Repair"
    root_cause_categories: List[str] = Field(default_factory=list)
    total_test_repos: int = 0
    max_steps_per_episode: int = 8
    reward_signals: List[str] = Field(default_factory=list)


# ── Repos ────────────────────────────────────────────────────────────────────

class RepoInfo(BaseModel):
    name: str
    path: str
    category: str = "unknown"
    test_identifier: str = ""
    has_manifest: bool = False
    manifest: Optional[Dict[str, Any]] = None


class RepoListResponse(BaseModel):
    repos: List[RepoInfo]
    total: int


# ── Episode ──────────────────────────────────────────────────────────────────

class EpisodeStartRequest(BaseModel):
    repo_path: str = ""
    test_identifier: str = ""
    max_steps: int = 8
    num_runs: int = 10


class EpisodeStartResponse(BaseModel):
    episode_id: str
    status: str = "initialized"
    observation: Dict[str, Any] = Field(default_factory=dict)
    baseline_pass_rate: float = 0.0
    env_type: str = "unknown"
    should_train: bool = True


class EpisodeStepRequest(BaseModel):
    raw_response: str = ""
    think_text: str = ""
    patch_text: str = ""
    predicted_category: str = "unknown"
    predicted_confidence: float = 0.5


class StepResult(BaseModel):
    step: int
    action: str = ""
    category: str = "unknown"
    confidence: float = 0.0
    reward: float = 0.0
    reward_breakdown: Dict[str, float] = Field(default_factory=dict)
    pass_rate_before: float = 0.0
    pass_rate_after: float = 0.0
    patch_applied: bool = False
    patch_files: List[str] = Field(default_factory=list)
    think_summary: str = ""
    done: bool = False
    done_reason: str = ""


class EpisodeStepResponse(BaseModel):
    step_result: StepResult
    observation: Dict[str, Any] = Field(default_factory=dict)


class RunEpisodeRequest(BaseModel):
    repo_path: str = ""
    test_identifier: str = ""
    max_steps: int = 8
    num_runs: int = 10
    backend: str = "nvidia"


class RunEpisodeResponse(BaseModel):
    episode_id: str
    status: str = "completed"
    steps: List[StepResult] = Field(default_factory=list)
    total_reward: float = 0.0
    final_pass_rate: float = 0.0
    baseline_pass_rate: float = 0.0
    done_reason: str = ""
    causal_graph: Optional[Dict[str, Any]] = None


class EpisodeStatusResponse(BaseModel):
    episode_id: str
    status: str = "idle"
    current_step: int = 0
    max_steps: int = 8
    pass_rate: float = 0.0
    total_reward: float = 0.0
    done: bool = False


# ── Challenge ────────────────────────────────────────────────────────────────

class ChallengeRequest(BaseModel):
    code: str
    test_code: str = ""
    preset: str = ""


class ChallengeAnalysis(BaseModel):
    detected_category: str = "unknown"
    confidence: float = 0.0
    root_cause_file: str = ""
    root_cause_function: str = ""
    causal_chain: List[str] = Field(default_factory=list)
    infrastructure_sensitive: bool = False
    suggested_fix: str = ""
    patch_diff: str = ""
    explanation: str = ""
    estimated_reward: float = 0.0


class ChallengeResponse(BaseModel):
    status: str = "analyzed"
    analysis: ChallengeAnalysis


# ── Training ─────────────────────────────────────────────────────────────────

class TrainingStats(BaseModel):
    total_episodes: int = 0
    avg_reward: float = 0.0
    fix_rate: float = 0.0
    avg_steps_to_fix: float = 0.0
    category_breakdown: Dict[str, int] = Field(default_factory=dict)
    reward_history: List[float] = Field(default_factory=list)
    baseline_history: List[float] = Field(default_factory=list)


class TrainingStatsResponse(BaseModel):
    stats: TrainingStats


# ── WebSocket messages ───────────────────────────────────────────────────────

class WSMessage(BaseModel):
    type: str
    data: Dict[str, Any] = Field(default_factory=dict)