Spaces:
Sleeping
Sleeping
Add submission story guide and training reward fix
Browse files- ENTERPRISE_CONTRACT_GUARDIAN_STORY.md +376 -0
- README.md +46 -28
- training/README.md +128 -14
- training/train.py +40 -7
ENTERPRISE_CONTRACT_GUARDIAN_STORY.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Enterprise Contract Guardian: Story + Technical Guide
|
| 2 |
+
|
| 3 |
+
This guide explains the product idea, the real-world problem, and the end-to-end solution. It is intentionally feature-first: it does not walk through every function or source file. By the end, you should understand what issue we are solving, why it matters, how the environment works, and why an RL agent can improve on it.
|
| 4 |
+
|
| 5 |
+
## 1. The Problem In One Story
|
| 6 |
+
|
| 7 |
+
It is Friday evening. A backend engineer makes what looks like a small API cleanup:
|
| 8 |
+
|
| 9 |
+
```json
|
| 10 |
+
{
|
| 11 |
+
"email": "user@example.com"
|
| 12 |
+
}
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
becomes:
|
| 16 |
+
|
| 17 |
+
```json
|
| 18 |
+
{
|
| 19 |
+
"email_address": "user@example.com"
|
| 20 |
+
}
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
The producer service deploys successfully because its own tests pass. But on Monday morning:
|
| 24 |
+
|
| 25 |
+
- Orders cannot attach customer emails to receipts.
|
| 26 |
+
- Billing cannot send invoices.
|
| 27 |
+
- Notifications cannot send transactional mail.
|
| 28 |
+
- Analytics silently drops a field and creates incomplete reports.
|
| 29 |
+
|
| 30 |
+
The real failure was not just "the API changed." The real failure was that nobody traced who depended on that field and nobody proposed a migration that old consumers could survive.
|
| 31 |
+
|
| 32 |
+
Enterprise Contract Guardian turns that workflow into an OpenEnv RL environment.
|
| 33 |
+
|
| 34 |
+
The agent learns to act like a senior platform engineer:
|
| 35 |
+
|
| 36 |
+
1. Detect the contract violation.
|
| 37 |
+
2. Trace the downstream blast radius.
|
| 38 |
+
3. Propose a backward-compatible fix.
|
| 39 |
+
4. Validate the fix against every consumer.
|
| 40 |
+
|
| 41 |
+
## 2. Why This Is A Good RL Environment
|
| 42 |
+
|
| 43 |
+
Most schema validation tasks stop at one file: "Does this payload match this schema?"
|
| 44 |
+
|
| 45 |
+
Real enterprise incidents are harder:
|
| 46 |
+
|
| 47 |
+
- The agent must reason across multiple service contracts.
|
| 48 |
+
- The answer depends on which downstream services consume which fields.
|
| 49 |
+
- A fix that works for one consumer can still break another.
|
| 50 |
+
- The reward should teach partial progress, not just pass/fail.
|
| 51 |
+
|
| 52 |
+
That makes the task a strong fit for OpenEnv Theme 3.1: world modeling for professional workflows.
|
| 53 |
+
|
| 54 |
+
## 3. End-To-End System Diagram
|
| 55 |
+
|
| 56 |
+
```mermaid
|
| 57 |
+
flowchart LR
|
| 58 |
+
A[Producer API change] --> B[Phase 1: detect violation]
|
| 59 |
+
B --> C[Phase 2: trace impacted consumers]
|
| 60 |
+
C --> D[Phase 3: propose backward-compatible fix]
|
| 61 |
+
D --> E[Validate fix against consumer specs]
|
| 62 |
+
E --> F{All consumers pass?}
|
| 63 |
+
F -->|Yes| G[High reward and episode complete]
|
| 64 |
+
F -->|No| H[Penalty plus feedback]
|
| 65 |
+
H --> D
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
The important idea: the environment is not only asking "what is wrong?" It asks "who breaks, and what migration keeps the system running?"
|
| 69 |
+
|
| 70 |
+
## 4. What The Agent Sees
|
| 71 |
+
|
| 72 |
+
At reset, the environment gives the agent a task-specific observation.
|
| 73 |
+
|
| 74 |
+
For simple detection tasks, the agent sees:
|
| 75 |
+
|
| 76 |
+
- An OpenAPI spec.
|
| 77 |
+
- A request or response payload.
|
| 78 |
+
- Current progress: violations found and violations remaining.
|
| 79 |
+
- Feedback from the previous action.
|
| 80 |
+
|
| 81 |
+
For enterprise tasks, the agent sees:
|
| 82 |
+
|
| 83 |
+
- The old and new producer API specs.
|
| 84 |
+
- The breaking change being analyzed.
|
| 85 |
+
- Consumer service declarations.
|
| 86 |
+
- Fields each consumer reads or emits.
|
| 87 |
+
- Consumer spec excerpts used later for fix validation.
|
| 88 |
+
|
| 89 |
+
The agent does not get the ground-truth affected service list. It must infer impact from the consumer declarations.
|
| 90 |
+
|
| 91 |
+
## 5. What The Agent Can Do
|
| 92 |
+
|
| 93 |
+
The action space matches the enterprise workflow.
|
| 94 |
+
|
| 95 |
+
### Phase 1: Detection
|
| 96 |
+
|
| 97 |
+
The agent reports one violation at a time:
|
| 98 |
+
|
| 99 |
+
```json
|
| 100 |
+
{
|
| 101 |
+
"action_type": "report_violation",
|
| 102 |
+
"field_path": "customer.email",
|
| 103 |
+
"violation_type": "missing_required",
|
| 104 |
+
"description": "customer.email is required by the schema but missing",
|
| 105 |
+
"suggested_fix": "Add customer.email as a valid email string"
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### Phase 2: Impact Tracing
|
| 110 |
+
|
| 111 |
+
The agent lists every affected downstream service:
|
| 112 |
+
|
| 113 |
+
```json
|
| 114 |
+
{
|
| 115 |
+
"action_type": "trace_impact",
|
| 116 |
+
"affected_services": ["OrdersService", "BillingService"],
|
| 117 |
+
"reasoning": "Both services consume the changed field from the producer API"
|
| 118 |
+
}
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
### Phase 3: Fix And Verify
|
| 122 |
+
|
| 123 |
+
The agent proposes a migration strategy and a spec patch:
|
| 124 |
+
|
| 125 |
+
```json
|
| 126 |
+
{
|
| 127 |
+
"action_type": "propose_fix",
|
| 128 |
+
"fix_strategy": "field_alias",
|
| 129 |
+
"spec_patch": {
|
| 130 |
+
"aliases": {
|
| 131 |
+
"email": "email_address"
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"rationale": "Old consumers can keep reading email while new clients use email_address"
|
| 135 |
+
}
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
## 6. Reward Diagram
|
| 139 |
+
|
| 140 |
+
```mermaid
|
| 141 |
+
flowchart TD
|
| 142 |
+
A[Agent action] --> B{Action type}
|
| 143 |
+
B -->|report_violation| C[Path and type grader]
|
| 144 |
+
B -->|trace_impact| D[Precision and recall grader]
|
| 145 |
+
B -->|propose_fix| E[Cross-consumer compatibility grader]
|
| 146 |
+
C --> F[Correct +1.0]
|
| 147 |
+
C --> G[Right path wrong type +0.3]
|
| 148 |
+
C --> H[False positive -0.3]
|
| 149 |
+
D --> I[Correct consumer +0.8]
|
| 150 |
+
D --> J[Missed consumer -0.5]
|
| 151 |
+
D --> K[False flag -0.4]
|
| 152 |
+
E --> L[All consumers pass +2.0]
|
| 153 |
+
E --> M[Consumer breaks -1.0]
|
| 154 |
+
E --> N[Malformed patch -0.5]
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
This matters because RL needs signal. A binary "pass/fail" reward would make learning slow and brittle. This environment gives a useful reward even when the agent is partially right.
|
| 158 |
+
|
| 159 |
+
## 7. Real-Time Example 1: UserService Email Rename
|
| 160 |
+
|
| 161 |
+
### Incident
|
| 162 |
+
|
| 163 |
+
UserService changes its response:
|
| 164 |
+
|
| 165 |
+
```json
|
| 166 |
+
{
|
| 167 |
+
"id": "u_123",
|
| 168 |
+
"email_address": "maya@example.com",
|
| 169 |
+
"created_at": "2026-04-25T09:30:00Z"
|
| 170 |
+
}
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
Earlier, consumers expected:
|
| 174 |
+
|
| 175 |
+
```json
|
| 176 |
+
{
|
| 177 |
+
"id": "u_123",
|
| 178 |
+
"email": "maya@example.com",
|
| 179 |
+
"created_at": "2026-04-25T09:30:00Z"
|
| 180 |
+
}
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
### Business Impact
|
| 184 |
+
|
| 185 |
+
- OrdersService needs `email` for order confirmation.
|
| 186 |
+
- BillingService needs `email` for invoices.
|
| 187 |
+
- NotificationsService needs `email` for transactional messages.
|
| 188 |
+
- AnalyticsETL only reads `id` and `created_at`, so it should not be flagged.
|
| 189 |
+
|
| 190 |
+
### How The Environment Tests The Agent
|
| 191 |
+
|
| 192 |
+
```mermaid
|
| 193 |
+
flowchart LR
|
| 194 |
+
U[UserService removes email] --> O[OrdersService consumes email]
|
| 195 |
+
U --> B[BillingService consumes email]
|
| 196 |
+
U --> N[NotificationsService consumes email]
|
| 197 |
+
U --> A[AnalyticsETL consumes id and created_at]
|
| 198 |
+
O --> X[Impacted]
|
| 199 |
+
B --> X
|
| 200 |
+
N --> X
|
| 201 |
+
A --> Y[Not impacted]
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
A weak agent may say "all consumers are impacted." That is wrong because AnalyticsETL does not depend on `email`.
|
| 205 |
+
|
| 206 |
+
A strong agent says:
|
| 207 |
+
|
| 208 |
+
```json
|
| 209 |
+
{
|
| 210 |
+
"action_type": "trace_impact",
|
| 211 |
+
"affected_services": [
|
| 212 |
+
"OrdersService",
|
| 213 |
+
"BillingService",
|
| 214 |
+
"NotificationsService"
|
| 215 |
+
],
|
| 216 |
+
"reasoning": "These services consume email, while AnalyticsETL does not"
|
| 217 |
+
}
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
Then it proposes:
|
| 221 |
+
|
| 222 |
+
```json
|
| 223 |
+
{
|
| 224 |
+
"action_type": "propose_fix",
|
| 225 |
+
"fix_strategy": "field_alias",
|
| 226 |
+
"spec_patch": {
|
| 227 |
+
"aliases": {
|
| 228 |
+
"email": "email_address"
|
| 229 |
+
}
|
| 230 |
+
},
|
| 231 |
+
"rationale": "Keep the old field contract while allowing the new field name"
|
| 232 |
+
}
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
### Why This Solves It
|
| 236 |
+
|
| 237 |
+
The fix lets old consumers keep reading `email`. New consumers can adopt `email_address`. The company gets a migration window instead of a Monday outage.
|
| 238 |
+
|
| 239 |
+
## 8. Real-Time Example 2: OrdersService Status Enum Narrowing
|
| 240 |
+
|
| 241 |
+
### Incident
|
| 242 |
+
|
| 243 |
+
OrdersService used to accept:
|
| 244 |
+
|
| 245 |
+
```json
|
| 246 |
+
{
|
| 247 |
+
"status": "refunded"
|
| 248 |
+
}
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
The new API only accepts:
|
| 252 |
+
|
| 253 |
+
```json
|
| 254 |
+
{
|
| 255 |
+
"status": "pending | confirmed | shipped | delivered"
|
| 256 |
+
}
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
The removed values are:
|
| 260 |
+
|
| 261 |
+
- `cancelled`
|
| 262 |
+
- `refunded`
|
| 263 |
+
|
| 264 |
+
### Business Impact
|
| 265 |
+
|
| 266 |
+
- ReturnsService emits `refunded`, so it breaks.
|
| 267 |
+
- SupportPortal emits `cancelled`, so it breaks.
|
| 268 |
+
- ShippingService only emits `shipped` and `delivered`, so it is safe.
|
| 269 |
+
|
| 270 |
+
### How The Environment Tests The Agent
|
| 271 |
+
|
| 272 |
+
```mermaid
|
| 273 |
+
flowchart LR
|
| 274 |
+
O[OrdersService narrows status enum] --> R[ReturnsService emits refunded]
|
| 275 |
+
O --> S[SupportPortal emits cancelled]
|
| 276 |
+
O --> H[ShippingService emits shipped or delivered]
|
| 277 |
+
R --> X[Impacted]
|
| 278 |
+
S --> X
|
| 279 |
+
H --> Y[Not impacted]
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
A strong trace action is:
|
| 283 |
+
|
| 284 |
+
```json
|
| 285 |
+
{
|
| 286 |
+
"action_type": "trace_impact",
|
| 287 |
+
"affected_services": ["ReturnsService", "SupportPortal"],
|
| 288 |
+
"reasoning": "Both emit removed enum values; ShippingService emits values that still exist"
|
| 289 |
+
}
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
The fix is different from the email rename case. A field alias does not restore removed enum values. The agent should choose a migration strategy such as versioning, deprecation window, or coordinated consumer patch:
|
| 293 |
+
|
| 294 |
+
```json
|
| 295 |
+
{
|
| 296 |
+
"action_type": "propose_fix",
|
| 297 |
+
"fix_strategy": "consumer_patch",
|
| 298 |
+
"spec_patch": {
|
| 299 |
+
"consumers_to_migrate": ["ReturnsService", "SupportPortal"]
|
| 300 |
+
},
|
| 301 |
+
"rationale": "Only the consumers emitting removed values need migration"
|
| 302 |
+
}
|
| 303 |
+
```
|
| 304 |
+
|
| 305 |
+
### Why This Solves It
|
| 306 |
+
|
| 307 |
+
The environment rewards the agent for recognizing that the safe consumer should not be touched. That is the difference between real blast-radius analysis and noisy "warn everybody" automation.
|
| 308 |
+
|
| 309 |
+
## 9. The Full Episode Lifecycle
|
| 310 |
+
|
| 311 |
+
```mermaid
|
| 312 |
+
sequenceDiagram
|
| 313 |
+
participant Agent
|
| 314 |
+
participant Env as OpenEnv Environment
|
| 315 |
+
participant Graph as Service Graph
|
| 316 |
+
participant Grader as Deterministic Grader
|
| 317 |
+
|
| 318 |
+
Agent->>Env: reset(task_name, seed)
|
| 319 |
+
Env->>Graph: load scenario
|
| 320 |
+
Env-->>Agent: observation
|
| 321 |
+
Agent->>Env: report_violation or trace_impact
|
| 322 |
+
Env->>Grader: compare against ground truth
|
| 323 |
+
Grader-->>Env: reward + feedback
|
| 324 |
+
Env-->>Agent: next observation
|
| 325 |
+
Agent->>Env: propose_fix
|
| 326 |
+
Env->>Grader: validate patch against all consumers
|
| 327 |
+
Grader-->>Env: per-consumer pass/fail
|
| 328 |
+
Env-->>Agent: final score
|
| 329 |
+
```
|
| 330 |
+
|
| 331 |
+
The key technical principle is determinism. The grader knows the planted violations and expected affected consumers. That makes the reward objective, repeatable, and suitable for training.
|
| 332 |
+
|
| 333 |
+
## 10. Why Training Can Improve The Agent
|
| 334 |
+
|
| 335 |
+
The baseline model already understands many simple schema issues, but it struggles where exact action formatting and enterprise reasoning matter.
|
| 336 |
+
|
| 337 |
+
Current baseline evidence shows:
|
| 338 |
+
|
| 339 |
+
- Strong performance on several direct validation tasks.
|
| 340 |
+
- Major headroom on `detect_breaking_changes`, where the model finds fields but often uses the wrong violation type.
|
| 341 |
+
- Moderate headroom on `trace_downstream_blast_radius`, where precision and recall can improve.
|
| 342 |
+
|
| 343 |
+
GRPO training can improve behavior because every sampled completion receives direct environment feedback:
|
| 344 |
+
|
| 345 |
+
- Correct path and type gets more reward than right path with wrong type.
|
| 346 |
+
- Correct consumer list gets more reward than over-warning every service.
|
| 347 |
+
- Fixes that preserve every consumer get more reward than fixes that only look plausible.
|
| 348 |
+
|
| 349 |
+
## 11. What Makes This Submission Strong
|
| 350 |
+
|
| 351 |
+
The project has a clear judge-facing story:
|
| 352 |
+
|
| 353 |
+
- Problem: API changes break downstream services because teams lack automated impact reasoning.
|
| 354 |
+
- Environment: OpenEnv simulation with specs, payloads, service graphs, and deterministic grading.
|
| 355 |
+
- Results: baseline scores exist, and trained reward plots should be added after the GRPO run.
|
| 356 |
+
- Why it matters: platform teams, API gateway teams, CI/CD pipelines, and microservice organizations need this.
|
| 357 |
+
|
| 358 |
+
The novelty is not "schema validation." The novelty is turning multi-service contract impact analysis into a trainable RL environment.
|
| 359 |
+
|
| 360 |
+
## 12. What Still Must Be Added Before Final Submission
|
| 361 |
+
|
| 362 |
+
The environment and tests are in good shape, but the final submission needs visible training evidence:
|
| 363 |
+
|
| 364 |
+
- Commit `results/reward_curve.png`.
|
| 365 |
+
- Commit `results/before_after.png`.
|
| 366 |
+
- Generate and commit `trained_scores.json`.
|
| 367 |
+
- Replace README trained-score placeholders.
|
| 368 |
+
- Add the public WandB run link, if WandB is used.
|
| 369 |
+
- Add the YouTube demo video or HuggingFace mini-blog link.
|
| 370 |
+
|
| 371 |
+
Without those artifacts, the project is strong on innovation and story, but weaker on the 20 percent "Improvement in Rewards" judging criterion.
|
| 372 |
+
|
| 373 |
+
## 13. Two-Minute Pitch Version
|
| 374 |
+
|
| 375 |
+
"Enterprise API breaks rarely happen because one schema is invalid. They happen because a small producer change silently breaks downstream consumers. Our environment trains an agent to handle the full platform-engineering workflow: detect the contract violation, trace the blast radius across services, propose a backward-compatible migration, and validate that migration against every consumer contract. The reward is composable: correct violations, correct consumers, missed consumers, false flags, malformed fixes, and cross-consumer compatibility are all scored independently. This teaches a model a real enterprise skill that standard LLMs do not reliably perform today."
|
| 376 |
+
|
README.md
CHANGED
|
@@ -69,7 +69,7 @@ Enterprise Service Graph
|
|
| 69 |
|
| 70 |
```
|
| 71 |
reset()
|
| 72 |
-
β Agent receives: a changed spec (producer) +
|
| 73 |
|
| 74 |
Phase 1 β Detection
|
| 75 |
step(violation_report) β Correct? +1.0 | Proximity +0.3 | False positive -0.3 | Duplicate -0.1
|
|
@@ -207,7 +207,7 @@ Multiple **independent** reward signals (per `help_guide.md Β§7`) β reduces re
|
|
| 207 |
|
| 208 |
| Signal | Reward | Rationale (`help_guide.md Β§7`) |
|
| 209 |
|---|---|---|
|
| 210 |
-
| **Step
|
| 211 |
| **Format compliance** | β0.2 for malformed actions | Enforces schema |
|
| 212 |
| **Anti-hacking (spam)** | β1.0 if > 3Γ total violations reported | Prevents "report everything" exploit |
|
| 213 |
|
|
@@ -254,45 +254,49 @@ python inference.py
|
|
| 254 |
openenv validate
|
| 255 |
```
|
| 256 |
|
| 257 |
-
## Training Results
|
| 258 |
|
| 259 |
> **Training**: GRPO via TRL + Unsloth Β· **Hardware**: HuggingFace Jobs T4 GPU
|
|
|
|
| 260 |
|
| 261 |
-
### Reward Curve
|
| 262 |
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
<!-- After training, replace with:
|
| 266 |

|
| 267 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
|
|
|
| 269 |

|
| 270 |
-
*
|
| 271 |
-->
|
| 272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
| Phase | WandB Run | Notebook |
|
| 274 |
|---|---|---|
|
| 275 |
| GRPO (Phase 1 + Phase 2/3) | *(link after training)* | [`training/grpo_colab.ipynb`](training/grpo_colab.ipynb) |
|
| 276 |
|
| 277 |
See [`training/README.md`](training/README.md) for the three ways to run the pipeline (Colab / HF Jobs / local).
|
| 278 |
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
| Task | Phase | Score | Steps | Success |
|
| 282 |
-
|---|---|---|---|---|
|
| 283 |
-
| `find_type_mismatches` | 1 | 0.75 | 4 | β
|
|
| 284 |
-
| `validate_nested_objects` | 1 | 0.99 | 12 | β
|
|
| 285 |
-
| `detect_breaking_changes` | 1 | **0.01** | 20 | β |
|
| 286 |
-
| `validate_response_schema` | 1 | 0.99 | 10 | β
|
|
| 287 |
-
| `validate_cross_field_constraints` | 1 | 0.86 | 8 | β
|
|
| 288 |
-
| `validate_auth_request` | 1 | 0.99 | 10 | β
|
|
| 289 |
-
| `trace_downstream_blast_radius` | 2 | 0.67 | 1 | β
|
|
| 290 |
-
| `propose_backward_compat_fix` | 3 | 0.99 | 1 | β
|
|
| 291 |
-
| `multi_service_cascade_fix` | 2+3 | 0.99 | 2 | β
|
|
| 292 |
-
|
| 293 |
-
Full per-step rewards in [`../baseline_scores.json`](../baseline_scores.json).
|
| 294 |
-
|
| 295 |
-
**Headroom for training**: `detect_breaking_changes` at 0.01 is the biggest opportunity β the 72B model finds the right field paths (proximity hits) but never predicts `violation_type='breaking_change'` correctly. Phase 2 trace is also under-shooting recall. After GRPO training the trained-model row will go alongside this table.
|
| 296 |
|
| 297 |
## Why This Matters
|
| 298 |
|
|
@@ -310,11 +314,25 @@ This is a genuinely underexplored domain in RL/LLM training β no prior benchma
|
|
| 310 |
|
| 311 |
| Resource | URL |
|
| 312 |
|---|---|
|
| 313 |
-
| HuggingFace Space
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
| Demo Video / HF Blog | *(add after recording)* |
|
| 316 |
| WandB Training Run | *(add after training)* |
|
| 317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
## Project Structure
|
| 319 |
|
| 320 |
```
|
|
|
|
| 69 |
|
| 70 |
```
|
| 71 |
reset()
|
| 72 |
+
β Agent receives: a changed spec (producer) + service graph with consumer declarations.
|
| 73 |
|
| 74 |
Phase 1 β Detection
|
| 75 |
step(violation_report) β Correct? +1.0 | Proximity +0.3 | False positive -0.3 | Duplicate -0.1
|
|
|
|
| 207 |
|
| 208 |
| Signal | Reward | Rationale (`help_guide.md Β§7`) |
|
| 209 |
|---|---|---|
|
| 210 |
+
| **Step budget** | Hard max-step limit per task | Discourages padding and forces concise analysis |
|
| 211 |
| **Format compliance** | β0.2 for malformed actions | Enforces schema |
|
| 212 |
| **Anti-hacking (spam)** | β1.0 if > 3Γ total violations reported | Prevents "report everything" exploit |
|
| 213 |
|
|
|
|
| 254 |
openenv validate
|
| 255 |
```
|
| 256 |
|
| 257 |
+
## Training Results β Before vs After
|
| 258 |
|
| 259 |
> **Training**: GRPO via TRL + Unsloth Β· **Hardware**: HuggingFace Jobs T4 GPU
|
| 260 |
+
> **Why we report both**: per the hackathon judging criteria, "Improvement in Rewards" (20%) requires a baseline-vs-trained comparison. The grader looks at the delta, not absolute numbers.
|
| 261 |
|
| 262 |
+
### Reward Curve (training progress)
|
| 263 |
|
| 264 |
+
<!-- Embedded after training run; uncomment + push the .png:
|
|
|
|
|
|
|
| 265 |

|
| 266 |
+
*Mean episode reward across training steps. Rising curve = GRPO is finding higher-reward completions over time.*
|
| 267 |
+
-->
|
| 268 |
+
|
| 269 |
+
*To be added after the onsite GRPO run (Apr 25β26). The plot is auto-generated by [`training/train.py`](training/train.py) and saved as `results/reward_curve.png`.*
|
| 270 |
+
|
| 271 |
+
### Before vs After β per-task comparison
|
| 272 |
|
| 273 |
+
<!-- Embedded after training run; uncomment + push the .png:
|
| 274 |

|
| 275 |
+
*Bar chart: baseline (grey) vs GRPO-trained adapter (green). Side-by-side per task.*
|
| 276 |
-->
|
| 277 |
|
| 278 |
+
| Task | Phase | Baseline (Qwen2.5-72B) | Trained (Qwen2.5-1.5B + LoRA) | Ξ |
|
| 279 |
+
|---|---|---|---|---|
|
| 280 |
+
| `find_type_mismatches` | 1 | 0.75 | _(after training)_ | _Ξ_ |
|
| 281 |
+
| `validate_nested_objects` | 1 | 0.99 | _(after training)_ | _Ξ_ |
|
| 282 |
+
| `detect_breaking_changes` | 1 | **0.01** | _(after training)_ | _largest delta expected here_ |
|
| 283 |
+
| `validate_response_schema` | 1 | 0.99 | _(after training)_ | _Ξ_ |
|
| 284 |
+
| `validate_cross_field_constraints` | 1 | 0.86 | _(after training)_ | _Ξ_ |
|
| 285 |
+
| `validate_auth_request` | 1 | 0.99 | _(after training)_ | _Ξ_ |
|
| 286 |
+
| `trace_downstream_blast_radius` | 2 | 0.67 | _(after training)_ | _Ξ_ |
|
| 287 |
+
| `propose_backward_compat_fix` | 3 | 0.99 | _(after training)_ | _Ξ_ |
|
| 288 |
+
| `multi_service_cascade_fix` | 2+3 | 0.99 | _(after training)_ | _Ξ_ |
|
| 289 |
+
| **Mean** | | **0.82** | _(after training)_ | _Ξ_ |
|
| 290 |
+
|
| 291 |
+
Full per-step rewards in [`../baseline_scores.json`](../baseline_scores.json) (before) and [`../trained_scores.json`](../trained_scores.json) (after β generated post-training).
|
| 292 |
+
|
| 293 |
| Phase | WandB Run | Notebook |
|
| 294 |
|---|---|---|
|
| 295 |
| GRPO (Phase 1 + Phase 2/3) | *(link after training)* | [`training/grpo_colab.ipynb`](training/grpo_colab.ipynb) |
|
| 296 |
|
| 297 |
See [`training/README.md`](training/README.md) for the three ways to run the pipeline (Colab / HF Jobs / local).
|
| 298 |
|
| 299 |
+
**Where to expect the biggest delta**: `detect_breaking_changes` at 0.01 β the 72B baseline finds the right field paths (proximity hits) but never predicts `violation_type='breaking_change'` correctly. GRPO should lift this substantially because the env's grader gives a clear +1.0 vs +0.3 vs -0.3 signal that the policy can directly optimise for.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
## Why This Matters
|
| 302 |
|
|
|
|
| 314 |
|
| 315 |
| Resource | URL |
|
| 316 |
|---|---|
|
| 317 |
+
| HuggingFace Space (live env) | https://huggingface.co/spaces/pushpam14/api-contract-validator |
|
| 318 |
+
| Live env endpoint | https://pushpam14-api-contract-validator.hf.space |
|
| 319 |
+
| Health check | https://pushpam14-api-contract-validator.hf.space/health |
|
| 320 |
+
| Training Notebook (Colab) | [`training/grpo_colab.ipynb`](training/grpo_colab.ipynb) |
|
| 321 |
+
| Story + Technical Guide | [`ENTERPRISE_CONTRACT_GUARDIAN_STORY.md`](ENTERPRISE_CONTRACT_GUARDIAN_STORY.md) |
|
| 322 |
+
| GitHub repo | https://github.com/kumarpushpam17-personal/Hackathon |
|
| 323 |
| Demo Video / HF Blog | *(add after recording)* |
|
| 324 |
| WandB Training Run | *(add after training)* |
|
| 325 |
|
| 326 |
+
Quick test:
|
| 327 |
+
|
| 328 |
+
```bash
|
| 329 |
+
curl https://pushpam14-api-contract-validator.hf.space/health
|
| 330 |
+
# {"status":"healthy"}
|
| 331 |
+
|
| 332 |
+
curl -X POST https://pushpam14-api-contract-validator.hf.space/reset \
|
| 333 |
+
-H "Content-Type: application/json" -d '{}'
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
## Project Structure
|
| 337 |
|
| 338 |
```
|
training/README.md
CHANGED
|
@@ -11,23 +11,99 @@ Re-runnable training pipeline for the API Contract Validator environment, using
|
|
| 11 |
| `plot.py` | Build `reward_curve.png` and `before_after.png` for the README |
|
| 12 |
| `grpo_colab.ipynb` | One-click Colab notebook (open in Colab β Runtime β Run all) |
|
| 13 |
|
| 14 |
-
##
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
|
| 20 |
-
|
| 21 |
|
| 22 |
```bash
|
| 23 |
hf jobs uv run \
|
| 24 |
-
--with trl --with unsloth --with openenv-core
|
|
|
|
| 25 |
--flavor t4-small \
|
| 26 |
-s HF_TOKEN -s WANDB_API_KEY \
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
-- python training/train.py
|
| 28 |
```
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
```bash
|
| 33 |
pip install trl unsloth wandb matplotlib datasets
|
|
@@ -59,22 +135,60 @@ python training/plot.py
|
|
| 59 |
|---|---|---|
|
| 60 |
| `HF_TOKEN` | β | Required. Used for both inference (router) and Hub push |
|
| 61 |
| `WANDB_API_KEY` | β | Optional. If set, training logs go to WandB |
|
| 62 |
-
| `BASE_MODEL` | `unsloth/Qwen2.5-
|
| 63 |
| `ENV_URL` | `http://localhost:7860` | Local server or deployed HF Space |
|
| 64 |
-
| `MAX_STEPS` | `
|
| 65 |
| `NUM_GENERATIONS` | `4` | Completions per prompt for relative ranking |
|
| 66 |
| `LORA_R` | `16` | LoRA rank |
|
| 67 |
| `PUSH_TO_HUB` | β | `<username>/<repo>` β push trained adapter |
|
| 68 |
|
| 69 |
## What the judges look at
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
```
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
```
|
| 79 |
|
| 80 |
-
|
|
|
|
| 11 |
| `plot.py` | Build `reward_curve.png` and `before_after.png` for the README |
|
| 12 |
| `grpo_colab.ipynb` | One-click Colab notebook (open in Colab β Runtime β Run all) |
|
| 13 |
|
| 14 |
+
## Recommended path β HF Jobs (best for the finale)
|
| 15 |
|
| 16 |
+
HF Jobs runs in the cloud, doesn't disconnect, and bills against your $30 hackathon credit. Three runs total cost ~$3 of $60 if you have credits across two accounts.
|
| 17 |
|
| 18 |
+
### Run 1 β Smoke test (~$0.30, 5 min)
|
| 19 |
|
| 20 |
+
Verifies the pipeline works end-to-end before committing to a long run.
|
| 21 |
|
| 22 |
```bash
|
| 23 |
hf jobs uv run \
|
| 24 |
+
--with "trl" --with "unsloth" --with "openenv-core[core]>=0.2.2" \
|
| 25 |
+
--with "wandb" --with "matplotlib" --with "datasets" --with "openai" \
|
| 26 |
--flavor t4-small \
|
| 27 |
-s HF_TOKEN -s WANDB_API_KEY \
|
| 28 |
+
-e BASE_MODEL=unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit \
|
| 29 |
+
-e ENV_URL=https://pushpam14-api-contract-validator.hf.space \
|
| 30 |
+
-e MAX_STEPS=10 \
|
| 31 |
+
-e WANDB_RUN=smoke-test \
|
| 32 |
-- python training/train.py
|
| 33 |
```
|
| 34 |
|
| 35 |
+
If this errors, **don't proceed**. Fix the error, re-run smoke test until it returns clean.
|
| 36 |
+
|
| 37 |
+
### Run 2 β Main training, **Qwen2.5-7B on L4** (~$2.40, ~2 hours)
|
| 38 |
+
|
| 39 |
+
Best balance of model size, speed, and cost for our $60 budget. L4 has 24 GB which fits Qwen2.5-7B with 4-bit quantisation + LoRA r=16.
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
hf jobs uv run \
|
| 43 |
+
--with "trl" --with "unsloth" --with "openenv-core[core]>=0.2.2" \
|
| 44 |
+
--with "wandb" --with "matplotlib" --with "datasets" --with "openai" \
|
| 45 |
+
--flavor l4x1 \
|
| 46 |
+
-s HF_TOKEN -s WANDB_API_KEY \
|
| 47 |
+
-e BASE_MODEL=unsloth/Qwen2.5-7B-Instruct-bnb-4bit \
|
| 48 |
+
-e ENV_URL=https://pushpam14-api-contract-validator.hf.space \
|
| 49 |
+
-e MAX_STEPS=300 \
|
| 50 |
+
-e NUM_GENERATIONS=4 \
|
| 51 |
+
-e LORA_R=16 \
|
| 52 |
+
-e LORA_ALPHA=32 \
|
| 53 |
+
-e WANDB_PROJECT=openenv-contract-guardian \
|
| 54 |
+
-e WANDB_RUN=grpo-7b-l4-300steps \
|
| 55 |
+
-e PUSH_TO_HUB=pushpam14/api-contract-validator-grpo-7b \
|
| 56 |
+
-- python training/train.py
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Run 3 β Insurance run on second account (~$0.40, ~45 min)
|
| 60 |
+
|
| 61 |
+
Use your second HF account in parallel as a safety net. Smaller model = faster, more dramatic improvement curve. If Run 2 produces a beautiful curve we ship that; if Run 2 has issues we ship this one.
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
# Use your SECOND HF account's token here
|
| 65 |
+
hf jobs uv run \
|
| 66 |
+
--with "trl" --with "unsloth" --with "openenv-core[core]>=0.2.2" \
|
| 67 |
+
--with "wandb" --with "matplotlib" --with "datasets" --with "openai" \
|
| 68 |
+
--flavor t4-small \
|
| 69 |
+
-s HF_TOKEN -s WANDB_API_KEY \
|
| 70 |
+
-e BASE_MODEL=unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit \
|
| 71 |
+
-e ENV_URL=https://pushpam14-api-contract-validator.hf.space \
|
| 72 |
+
-e MAX_STEPS=200 \
|
| 73 |
+
-e WANDB_RUN=grpo-1.5b-t4-200steps \
|
| 74 |
+
-e PUSH_TO_HUB=YOUR_SECOND_ACCOUNT/api-contract-validator-grpo-1.5b \
|
| 75 |
+
-- python training/train.py
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### Hardware β model size cheatsheet
|
| 79 |
+
|
| 80 |
+
| Flavor | VRAM | $/hr | Fits (4-bit + LoRA) |
|
| 81 |
+
|---|---|---|---|
|
| 82 |
+
| `t4-small` | 16 GB | $0.40 | Up to 3B |
|
| 83 |
+
| `l4x1` | 24 GB | $0.80 | Up to 8B comfortably |
|
| 84 |
+
| `a10g-large` | 24 GB | $1.50 | Up to 8B, faster than L4 |
|
| 85 |
+
| `a100-large` | 80 GB | $3.50 | 14B fp16 or 70B 4-bit |
|
| 86 |
+
| `h100x1` | 80 GB | $4.50 | Same as A100 but ~2Γ faster |
|
| 87 |
+
|
| 88 |
+
For our env, **`l4x1` + Qwen2.5-7B is the sweet spot.**
|
| 89 |
+
|
| 90 |
+
### Monitoring the run
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
# Watch the job logs in real time
|
| 94 |
+
hf jobs logs <job-id> --follow
|
| 95 |
+
|
| 96 |
+
# List recent jobs
|
| 97 |
+
hf jobs list
|
| 98 |
+
|
| 99 |
+
# WandB run will be auto-linked in the job logs β bookmark that URL for the README
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
## Alternative: Colab notebook (if HF Jobs is unavailable)
|
| 103 |
+
|
| 104 |
+
Open `grpo_colab.ipynb` in Colab. Set `HF_TOKEN` and `WANDB_API_KEY` in the secrets pane. Hit **Runtime β Run all**. Free T4, but disconnects after 3 hours and only fits the 1.5B model.
|
| 105 |
+
|
| 106 |
+
## Alternative: Local GPU
|
| 107 |
|
| 108 |
```bash
|
| 109 |
pip install trl unsloth wandb matplotlib datasets
|
|
|
|
| 135 |
|---|---|---|
|
| 136 |
| `HF_TOKEN` | β | Required. Used for both inference (router) and Hub push |
|
| 137 |
| `WANDB_API_KEY` | β | Optional. If set, training logs go to WandB |
|
| 138 |
+
| `BASE_MODEL` | `unsloth/Qwen2.5-7B-Instruct-bnb-4bit` | 7B fits on L4 (24 GB) with 4-bit |
|
| 139 |
| `ENV_URL` | `http://localhost:7860` | Local server or deployed HF Space |
|
| 140 |
+
| `MAX_STEPS` | `300` | GRPO steps. ~2 hours on L4 |
|
| 141 |
| `NUM_GENERATIONS` | `4` | Completions per prompt for relative ranking |
|
| 142 |
| `LORA_R` | `16` | LoRA rank |
|
| 143 |
| `PUSH_TO_HUB` | β | `<username>/<repo>` β push trained adapter |
|
| 144 |
|
| 145 |
## What the judges look at
|
| 146 |
|
| 147 |
+
The hackathon's "Improvement in Rewards" 20% criterion explicitly asks for a **before vs after comparison**. Per the official Q&A:
|
| 148 |
+
|
| 149 |
+
> "You're expected to show before vs after behavior. Run inference using both models and include the comparison (metrics, rewards, or outputs) in the README."
|
| 150 |
|
| 151 |
+
So after training, you must commit ALL FOUR of these:
|
| 152 |
+
|
| 153 |
+
```
|
| 154 |
+
baseline_scores.json # already committed (the "before")
|
| 155 |
+
trained_scores.json # post-training inference output
|
| 156 |
+
api_contract_validator/results/reward_curve.png # GRPO training curve
|
| 157 |
+
api_contract_validator/results/before_after.png # bar chart comparison
|
| 158 |
```
|
| 159 |
+
|
| 160 |
+
## Post-training commit checklist
|
| 161 |
+
|
| 162 |
+
Once the Colab notebook finishes, run this on your laptop:
|
| 163 |
+
|
| 164 |
+
```bash
|
| 165 |
+
cd ~/work/hackathon/hackathon-api-contract-validator
|
| 166 |
+
|
| 167 |
+
# 1. Pull the four artifacts down from Colab into local repo
|
| 168 |
+
# (Colab File pane β right-click β Download for each)
|
| 169 |
+
#
|
| 170 |
+
# Place them at:
|
| 171 |
+
# ./trained_scores.json
|
| 172 |
+
# ./api_contract_validator/results/reward_curve.png
|
| 173 |
+
# ./api_contract_validator/results/before_after.png
|
| 174 |
+
|
| 175 |
+
# 2. Edit api_contract_validator/README.md
|
| 176 |
+
# - Replace each "_(after training)_" placeholder in the Before vs After
|
| 177 |
+
# table with the score from trained_scores.json
|
| 178 |
+
# - Uncomment the two `<!--  -->` lines so the plots render
|
| 179 |
+
# - Add the WandB run URL in the Links section (and to the WandB row in
|
| 180 |
+
# the Training Results table)
|
| 181 |
+
|
| 182 |
+
# 3. Run validation
|
| 183 |
+
PYTHONPATH=api_contract_validator python3 -m pytest \
|
| 184 |
+
api_contract_validator/tests/test_environment.py -q
|
| 185 |
+
|
| 186 |
+
# 4. Commit + push
|
| 187 |
+
git add trained_scores.json \
|
| 188 |
+
api_contract_validator/results/*.png \
|
| 189 |
+
api_contract_validator/README.md
|
| 190 |
+
git commit -m "Add post-training results: trained scores + reward plots"
|
| 191 |
+
git push
|
| 192 |
```
|
| 193 |
|
| 194 |
+
That single push is the "after" half of the before-vs-after evidence judges grade.
|
training/train.py
CHANGED
|
@@ -58,8 +58,25 @@ except ImportError:
|
|
| 58 |
|
| 59 |
@dataclass
|
| 60 |
class TrainConfig:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
base_model: str = os.getenv(
|
| 62 |
-
"BASE_MODEL", "unsloth/Qwen2.5-
|
| 63 |
)
|
| 64 |
env_url: str = os.getenv("ENV_URL", "http://localhost:7860")
|
| 65 |
push_to_hub_id: str | None = os.getenv("PUSH_TO_HUB", None)
|
|
@@ -76,7 +93,7 @@ class TrainConfig:
|
|
| 76 |
# GRPO
|
| 77 |
max_seq_length: int = int(os.getenv("MAX_SEQ_LEN", "2048"))
|
| 78 |
num_generations: int = int(os.getenv("NUM_GENERATIONS", "4"))
|
| 79 |
-
max_steps: int = int(os.getenv("MAX_STEPS", "
|
| 80 |
learning_rate: float = float(os.getenv("LR", "5e-6"))
|
| 81 |
per_device_batch_size: int = int(os.getenv("BATCH_SIZE", "1"))
|
| 82 |
grad_accum: int = int(os.getenv("GRAD_ACCUM", "4"))
|
|
@@ -92,6 +109,13 @@ class TrainConfig:
|
|
| 92 |
# ββ Reward function: rolls out one step against the live env βββββββββββββ
|
| 93 |
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
def make_reward_fn(env_client, task_pool: List[str]):
|
| 96 |
"""Return a TRL-compatible reward_fn that grades each completion via env.
|
| 97 |
|
|
@@ -104,15 +128,24 @@ def make_reward_fn(env_client, task_pool: List[str]):
|
|
| 104 |
|
| 105 |
def reward_fn(prompts, completions, **kwargs): # noqa: ARG001
|
| 106 |
rewards: List[float] = []
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
text = completion if isinstance(completion, str) else completion[0]["content"]
|
|
|
|
|
|
|
| 110 |
try:
|
|
|
|
|
|
|
|
|
|
| 111 |
action_data = parse_llm_response(text)
|
| 112 |
action = _build_action(action_data)
|
| 113 |
-
# one-step roll-out: reset β step β grade β reset
|
| 114 |
-
# Each prompt is associated with a fresh episode in train_dataset,
|
| 115 |
-
# so we use the env's most recent reset state as scoring context.
|
| 116 |
step_result = loop.run_until_complete(env_client.step(action))
|
| 117 |
rewards.append(float(step_result.reward or 0.0))
|
| 118 |
except Exception as exc: # noqa: BLE001
|
|
|
|
| 58 |
|
| 59 |
@dataclass
|
| 60 |
class TrainConfig:
|
| 61 |
+
"""Training configuration. All fields read from env vars at instantiation.
|
| 62 |
+
|
| 63 |
+
Recommended HF Jobs configurations:
|
| 64 |
+
|
| 65 |
+
Smoke test ($0.30, 5 min):
|
| 66 |
+
BASE_MODEL=unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit, MAX_STEPS=10,
|
| 67 |
+
flavor t4-small
|
| 68 |
+
|
| 69 |
+
Main run on L4 ($2.40, ~2 hr):
|
| 70 |
+
BASE_MODEL=unsloth/Qwen2.5-7B-Instruct-bnb-4bit, MAX_STEPS=300,
|
| 71 |
+
flavor l4x1
|
| 72 |
+
|
| 73 |
+
Insurance run on T4 ($0.40, ~45 min):
|
| 74 |
+
BASE_MODEL=unsloth/Qwen2.5-1.5B-Instruct-bnb-4bit, MAX_STEPS=200,
|
| 75 |
+
flavor t4-small
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
base_model: str = os.getenv(
|
| 79 |
+
"BASE_MODEL", "unsloth/Qwen2.5-7B-Instruct-bnb-4bit"
|
| 80 |
)
|
| 81 |
env_url: str = os.getenv("ENV_URL", "http://localhost:7860")
|
| 82 |
push_to_hub_id: str | None = os.getenv("PUSH_TO_HUB", None)
|
|
|
|
| 93 |
# GRPO
|
| 94 |
max_seq_length: int = int(os.getenv("MAX_SEQ_LEN", "2048"))
|
| 95 |
num_generations: int = int(os.getenv("NUM_GENERATIONS", "4"))
|
| 96 |
+
max_steps: int = int(os.getenv("MAX_STEPS", "300"))
|
| 97 |
learning_rate: float = float(os.getenv("LR", "5e-6"))
|
| 98 |
per_device_batch_size: int = int(os.getenv("BATCH_SIZE", "1"))
|
| 99 |
grad_accum: int = int(os.getenv("GRAD_ACCUM", "4"))
|
|
|
|
| 109 |
# ββ Reward function: rolls out one step against the live env βββββββββββββ
|
| 110 |
|
| 111 |
|
| 112 |
+
def _list_value(values: Any, index: int, default: Any) -> Any:
|
| 113 |
+
"""Return ``values[index]`` for TRL batch kwargs, with a safe fallback."""
|
| 114 |
+
if isinstance(values, list) and index < len(values):
|
| 115 |
+
return values[index]
|
| 116 |
+
return default
|
| 117 |
+
|
| 118 |
+
|
| 119 |
def make_reward_fn(env_client, task_pool: List[str]):
|
| 120 |
"""Return a TRL-compatible reward_fn that grades each completion via env.
|
| 121 |
|
|
|
|
| 128 |
|
| 129 |
def reward_fn(prompts, completions, **kwargs): # noqa: ARG001
|
| 130 |
rewards: List[float] = []
|
| 131 |
+
try:
|
| 132 |
+
loop = asyncio.get_event_loop()
|
| 133 |
+
except RuntimeError:
|
| 134 |
+
loop = asyncio.new_event_loop()
|
| 135 |
+
asyncio.set_event_loop(loop)
|
| 136 |
+
task_names = kwargs.get("task") or []
|
| 137 |
+
seeds = kwargs.get("seed") or []
|
| 138 |
+
|
| 139 |
+
for idx, completion in enumerate(completions):
|
| 140 |
text = completion if isinstance(completion, str) else completion[0]["content"]
|
| 141 |
+
task_name = _list_value(task_names, idx, task_pool[0])
|
| 142 |
+
seed = _list_value(seeds, idx, 0)
|
| 143 |
try:
|
| 144 |
+
loop.run_until_complete(
|
| 145 |
+
env_client.reset(task_name=task_name, seed=int(seed))
|
| 146 |
+
)
|
| 147 |
action_data = parse_llm_response(text)
|
| 148 |
action = _build_action(action_data)
|
|
|
|
|
|
|
|
|
|
| 149 |
step_result = loop.run_until_complete(env_client.step(action))
|
| 150 |
rewards.append(float(step_result.reward or 0.0))
|
| 151 |
except Exception as exc: # noqa: BLE001
|