rnow 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rnow/__init__.py +5 -0
- rnow/__main__.py +7 -0
- rnow/cli/__init__.py +6 -0
- rnow/cli/auth.py +67 -0
- rnow/cli/blob.py +98 -0
- rnow/cli/commands.py +2311 -0
- rnow/cli/common.py +28 -0
- rnow/cli/cube.py +255 -0
- rnow/cli/main.py +49 -0
- rnow/cli/test.py +728 -0
- rnow/cli/token_count.py +295 -0
- rnow/core/__init__.py +33 -0
- rnow/core/reward.py +333 -0
- rnow/core/tool.py +494 -0
- rnow/models.py +295 -0
- rnow/templates/deepseek-aha/config.yml +26 -0
- rnow/templates/deepseek-aha/rewards.py +36 -0
- rnow/templates/deepseek-aha/train.jsonl +1000 -0
- rnow/templates/mcp-tavily/config.yml +29 -0
- rnow/templates/mcp-tavily/requirements.txt +1 -0
- rnow/templates/mcp-tavily/rewards.py +25 -0
- rnow/templates/mcp-tavily/train.jsonl +500 -0
- rnow/templates/new/config.yml +26 -0
- rnow/templates/new/requirements.txt +1 -0
- rnow/templates/new/rewards.py +0 -0
- rnow/templates/new/train.jsonl +0 -0
- rnow/templates/rl-nextjs/config.yml +27 -0
- rnow/templates/rl-nextjs/requirements.txt +2 -0
- rnow/templates/rl-nextjs/rewards.py +446 -0
- rnow/templates/rl-nextjs/train.jsonl +1000 -0
- rnow/templates/rl-single/config.yml +27 -0
- rnow/templates/rl-single/requirements.txt +1 -0
- rnow/templates/rl-single/rewards.py +14 -0
- rnow/templates/rl-single/train.jsonl +1000 -0
- rnow/templates/rl-tools/config.yml +27 -0
- rnow/templates/rl-tools/env.py +38 -0
- rnow/templates/rl-tools/requirements.txt +3 -0
- rnow/templates/rl-tools/rewards.py +25 -0
- rnow/templates/rl-tools/train.jsonl +500 -0
- rnow/templates/sft/config.yml +20 -0
- rnow/templates/sft/train.jsonl +100 -0
- rnow/templates/tutorial-reward/config.yml +27 -0
- rnow/templates/tutorial-reward/requirements.txt +1 -0
- rnow/templates/tutorial-reward/rewards.py +15 -0
- rnow/templates/tutorial-reward/train.jsonl +1000 -0
- rnow/templates/tutorial-tool/config.yml +27 -0
- rnow/templates/tutorial-tool/env.py +7 -0
- rnow/templates/tutorial-tool/requirements.txt +3 -0
- rnow/templates/tutorial-tool/rewards.py +7 -0
- rnow/templates/tutorial-tool/train.jsonl +1266 -0
- rnow-0.2.4.dist-info/METADATA +135 -0
- rnow-0.2.4.dist-info/RECORD +56 -0
- rnow-0.2.4.dist-info/WHEEL +5 -0
- rnow-0.2.4.dist-info/entry_points.txt +2 -0
- rnow-0.2.4.dist-info/licenses/LICENSE +21 -0
- rnow-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
project_id: ""
|
|
2
|
+
project_name: "rlvr-project"
|
|
3
|
+
dataset_id: ""
|
|
4
|
+
dataset_name: "train"
|
|
5
|
+
dataset_type: rl
|
|
6
|
+
organization_id: ""
|
|
7
|
+
data:
|
|
8
|
+
train_file: train.jsonl
|
|
9
|
+
batch_size: 32
|
|
10
|
+
group_size: 16
|
|
11
|
+
model:
|
|
12
|
+
path: Qwen/Qwen3-8B
|
|
13
|
+
qlora_rank: 32
|
|
14
|
+
name: "My RL Model"
|
|
15
|
+
description: "Reinforcement learning fine-tuned model"
|
|
16
|
+
algorithm:
|
|
17
|
+
loss_fn: ppo
|
|
18
|
+
adv_estimator: grpo
|
|
19
|
+
kl_penalty_coef: 0.01
|
|
20
|
+
rollout:
|
|
21
|
+
max_turns: 1
|
|
22
|
+
max_tokens: 16384
|
|
23
|
+
trainer:
|
|
24
|
+
num_epochs: 30
|
|
25
|
+
learning_rate: 0.0001
|
|
26
|
+
save_step: 20
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Add your project dependencies here
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
project_id: ""
|
|
2
|
+
project_name: "Next.js Code Generation"
|
|
3
|
+
dataset_id: ""
|
|
4
|
+
dataset_name: "nextjs-components"
|
|
5
|
+
dataset_type: rl
|
|
6
|
+
organization_id: ""
|
|
7
|
+
data:
|
|
8
|
+
train_file: train.jsonl
|
|
9
|
+
batch_size: 32
|
|
10
|
+
group_size: 16
|
|
11
|
+
model:
|
|
12
|
+
path: Qwen/Qwen3-8B
|
|
13
|
+
qlora_rank: 32
|
|
14
|
+
name: "Next.js Code Generator"
|
|
15
|
+
description: "RL model for Next.js code generation"
|
|
16
|
+
algorithm:
|
|
17
|
+
loss_fn: ppo
|
|
18
|
+
adv_estimator: grpo
|
|
19
|
+
kl_penalty_coef: 0.01
|
|
20
|
+
rollout:
|
|
21
|
+
max_turns: 1
|
|
22
|
+
max_tokens: 16384
|
|
23
|
+
termination_policy: max_turns
|
|
24
|
+
trainer:
|
|
25
|
+
num_epochs: 10
|
|
26
|
+
learning_rate: 0.0001
|
|
27
|
+
save_step: 833
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reward functions for Next.js ast-grep rules using ReinforceNow framework.
|
|
3
|
+
Each reward function checks if the generated code matches the expected ast-grep pattern.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from ast_grep_py import Config, SgRoot
|
|
9
|
+
|
|
10
|
+
from rnow.core import RewardArgs, reward
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@reward(precondition=True)
|
|
14
|
+
def code_block(args: RewardArgs, messages: list) -> float:
|
|
15
|
+
"""Precondition: Response must contain a ```typescript/tsx code block."""
|
|
16
|
+
response = messages[-1].get("content", "")
|
|
17
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
18
|
+
return 1.0 if match else 0.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@reward
|
|
22
|
+
def layout_syntax_1(args: RewardArgs, messages: list) -> float:
|
|
23
|
+
"""Reward for correct Next.js layout syntax with children prop."""
|
|
24
|
+
response = messages[-1].get("content", "")
|
|
25
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
26
|
+
if not match:
|
|
27
|
+
return 0.0
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
31
|
+
matches = root.find_all(
|
|
32
|
+
Config(
|
|
33
|
+
rule={
|
|
34
|
+
"all": [
|
|
35
|
+
{
|
|
36
|
+
"pattern": "function $NAME({ children }: { children: React.ReactNode }) { $$$BODY }"
|
|
37
|
+
},
|
|
38
|
+
{"kind": "function_declaration"},
|
|
39
|
+
{"has": {"pattern": "children", "stopBy": "end"}},
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return 1.0 if matches else 0.0
|
|
45
|
+
except Exception:
|
|
46
|
+
return 0.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@reward
|
|
50
|
+
def server_dynamic_segment_1(args: RewardArgs, messages: list) -> float:
|
|
51
|
+
"""Reward for correct async param extraction in dynamic segment pages."""
|
|
52
|
+
response = messages[-1].get("content", "")
|
|
53
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
54
|
+
if not match:
|
|
55
|
+
return 0.0
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
59
|
+
matches = root.find_all(
|
|
60
|
+
Config(
|
|
61
|
+
rule={
|
|
62
|
+
"all": [
|
|
63
|
+
{"pattern": "function $FUNC($$$ARGS) { $$$BODY }"},
|
|
64
|
+
{"kind": "function_declaration"},
|
|
65
|
+
{"has": {"pattern": "const { $VAR2 } = await params", "stopBy": "end"}},
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return 1.0 if matches else 0.0
|
|
71
|
+
except Exception:
|
|
72
|
+
return 0.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@reward
|
|
76
|
+
def server_dynamic_segment_2(args: RewardArgs, messages: list) -> float:
|
|
77
|
+
"""Reward for generateStaticParams pattern."""
|
|
78
|
+
response = messages[-1].get("content", "")
|
|
79
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
80
|
+
if not match:
|
|
81
|
+
return 0.0
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
85
|
+
matches = root.find_all(
|
|
86
|
+
Config(
|
|
87
|
+
rule={
|
|
88
|
+
"kind": "program",
|
|
89
|
+
"all": [
|
|
90
|
+
{
|
|
91
|
+
"has": {
|
|
92
|
+
"pattern": "function generateStaticParams() { $$$BODY }",
|
|
93
|
+
"has": {
|
|
94
|
+
"pattern": "return posts.map((post) => ({ slug: post.slug, }))",
|
|
95
|
+
"stopBy": "end",
|
|
96
|
+
},
|
|
97
|
+
"stopBy": "end",
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
return 1.0 if matches else 0.0
|
|
105
|
+
except Exception:
|
|
106
|
+
return 0.0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@reward
|
|
110
|
+
def server_search_params(args: RewardArgs, messages: list) -> float:
|
|
111
|
+
"""Reward for correct server searchParams handling."""
|
|
112
|
+
response = messages[-1].get("content", "")
|
|
113
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
114
|
+
if not match:
|
|
115
|
+
return 0.0
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
119
|
+
matches = root.find_all(
|
|
120
|
+
Config(
|
|
121
|
+
rule={
|
|
122
|
+
"all": [
|
|
123
|
+
{
|
|
124
|
+
"pattern": "async function $FUNC({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) { $$$BODY }"
|
|
125
|
+
},
|
|
126
|
+
{"kind": "function_declaration"},
|
|
127
|
+
{
|
|
128
|
+
"has": {
|
|
129
|
+
"pattern": "const $VAR = (await searchParams).$VAR",
|
|
130
|
+
"stopBy": "end",
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return 1.0 if matches else 0.0
|
|
138
|
+
except Exception:
|
|
139
|
+
return 0.0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@reward
|
|
143
|
+
def use_client_directive(args: RewardArgs, messages: list) -> float:
|
|
144
|
+
"""Reward for correct 'use client' directive placement."""
|
|
145
|
+
response = messages[-1].get("content", "")
|
|
146
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
147
|
+
if not match:
|
|
148
|
+
return 0.0
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
152
|
+
matches = root.find_all(Config(rule={"kind": "string", "pattern": '"use client"'}))
|
|
153
|
+
return 1.0 if matches else 0.0
|
|
154
|
+
except Exception:
|
|
155
|
+
return 0.0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@reward
|
|
159
|
+
def metadata_export(args: RewardArgs, messages: list) -> float:
|
|
160
|
+
"""Reward for valid metadata export."""
|
|
161
|
+
response = messages[-1].get("content", "")
|
|
162
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
163
|
+
if not match:
|
|
164
|
+
return 0.0
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
168
|
+
matches = root.find_all(Config(rule={"pattern": "export const metadata = { $$$BODY }"}))
|
|
169
|
+
return 1.0 if matches else 0.0
|
|
170
|
+
except Exception:
|
|
171
|
+
return 0.0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@reward
|
|
175
|
+
def error_boundary(args: RewardArgs, messages: list) -> float:
|
|
176
|
+
"""Reward for valid error boundary component."""
|
|
177
|
+
response = messages[-1].get("content", "")
|
|
178
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
179
|
+
if not match:
|
|
180
|
+
return 0.0
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
184
|
+
matches = root.find_all(
|
|
185
|
+
Config(
|
|
186
|
+
rule={
|
|
187
|
+
"all": [
|
|
188
|
+
{
|
|
189
|
+
"pattern": """export default function Error({
|
|
190
|
+
error,
|
|
191
|
+
reset,
|
|
192
|
+
}: {
|
|
193
|
+
error: Error
|
|
194
|
+
reset: () => void
|
|
195
|
+
}) {
|
|
196
|
+
$$$BODY
|
|
197
|
+
}"""
|
|
198
|
+
},
|
|
199
|
+
{"has": {"pattern": "reset()", "stopBy": "end"}},
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
return 1.0 if matches else 0.0
|
|
205
|
+
except Exception:
|
|
206
|
+
return 0.0
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@reward
|
|
210
|
+
def not_found_boundary(args: RewardArgs, messages: list) -> float:
|
|
211
|
+
"""Reward for not-found boundary component."""
|
|
212
|
+
response = messages[-1].get("content", "")
|
|
213
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
214
|
+
if not match:
|
|
215
|
+
return 0.0
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
219
|
+
matches = root.find_all(
|
|
220
|
+
Config(rule={"pattern": "export default function NotFound() { $$$BODY }"})
|
|
221
|
+
)
|
|
222
|
+
return 1.0 if matches else 0.0
|
|
223
|
+
except Exception:
|
|
224
|
+
return 0.0
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@reward
|
|
228
|
+
def loading_boundary(args: RewardArgs, messages: list) -> float:
|
|
229
|
+
"""Reward for loading boundary component."""
|
|
230
|
+
response = messages[-1].get("content", "")
|
|
231
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
232
|
+
if not match:
|
|
233
|
+
return 0.0
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
237
|
+
matches = root.find_all(
|
|
238
|
+
Config(rule={"pattern": "export default function Loading() { $$$BODY }"})
|
|
239
|
+
)
|
|
240
|
+
return 1.0 if matches else 0.0
|
|
241
|
+
except Exception:
|
|
242
|
+
return 0.0
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@reward
|
|
246
|
+
def template_component(args: RewardArgs, messages: list) -> float:
|
|
247
|
+
"""Reward for template component."""
|
|
248
|
+
response = messages[-1].get("content", "")
|
|
249
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
250
|
+
if not match:
|
|
251
|
+
return 0.0
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
255
|
+
matches = root.find_all(
|
|
256
|
+
Config(
|
|
257
|
+
rule={
|
|
258
|
+
"all": [
|
|
259
|
+
{
|
|
260
|
+
"pattern": "export default function Template({ children }: { children: React.ReactNode }) { $$$BODY }"
|
|
261
|
+
},
|
|
262
|
+
{"has": {"pattern": "children", "stopBy": "end"}},
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
return 1.0 if matches else 0.0
|
|
268
|
+
except Exception:
|
|
269
|
+
return 0.0
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@reward
|
|
273
|
+
def redirect_usage(args: RewardArgs, messages: list) -> float:
|
|
274
|
+
"""Reward for usage of Next.js redirect() helper."""
|
|
275
|
+
response = messages[-1].get("content", "")
|
|
276
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
277
|
+
if not match:
|
|
278
|
+
return 0.0
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
282
|
+
matches = root.find_all(
|
|
283
|
+
Config(rule={"kind": "program", "has": {"pattern": "redirect(", "stopBy": "end"}})
|
|
284
|
+
)
|
|
285
|
+
return 1.0 if matches else 0.0
|
|
286
|
+
except Exception:
|
|
287
|
+
return 0.0
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@reward
|
|
291
|
+
def notfound_function_usage(args: RewardArgs, messages: list) -> float:
|
|
292
|
+
"""Reward for usage of Next.js notFound() function."""
|
|
293
|
+
response = messages[-1].get("content", "")
|
|
294
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
295
|
+
if not match:
|
|
296
|
+
return 0.0
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
300
|
+
matches = root.find_all(
|
|
301
|
+
Config(rule={"kind": "program", "has": {"pattern": "notFound(", "stopBy": "end"}})
|
|
302
|
+
)
|
|
303
|
+
return 1.0 if matches else 0.0
|
|
304
|
+
except Exception:
|
|
305
|
+
return 0.0
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@reward
|
|
309
|
+
def generate_metadata_function(args: RewardArgs, messages: list) -> float:
|
|
310
|
+
"""Reward for dynamic generateMetadata() function."""
|
|
311
|
+
response = messages[-1].get("content", "")
|
|
312
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
313
|
+
if not match:
|
|
314
|
+
return 0.0
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
318
|
+
matches = root.find_all(
|
|
319
|
+
Config(rule={"pattern": "export async function generateMetadata() { $$$BODY }"})
|
|
320
|
+
)
|
|
321
|
+
return 1.0 if matches else 0.0
|
|
322
|
+
except Exception:
|
|
323
|
+
return 0.0
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@reward
|
|
327
|
+
def generate_metadata_object(args: RewardArgs, messages: list) -> float:
|
|
328
|
+
"""Reward for static metadata object export."""
|
|
329
|
+
response = messages[-1].get("content", "")
|
|
330
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
331
|
+
if not match:
|
|
332
|
+
return 0.0
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
336
|
+
matches = root.find_all(Config(rule={"pattern": "export const metadata = { $$$BODY }"}))
|
|
337
|
+
return 1.0 if matches else 0.0
|
|
338
|
+
except Exception:
|
|
339
|
+
return 0.0
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@reward
|
|
343
|
+
def route_handler_get(args: RewardArgs, messages: list) -> float:
|
|
344
|
+
"""Reward for GET route handler in Next.js Route Handlers."""
|
|
345
|
+
response = messages[-1].get("content", "")
|
|
346
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
347
|
+
if not match:
|
|
348
|
+
return 0.0
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
352
|
+
matches = root.find_all(
|
|
353
|
+
Config(rule={"pattern": "export async function GET(request: Request) { $$$BODY }"})
|
|
354
|
+
)
|
|
355
|
+
return 1.0 if matches else 0.0
|
|
356
|
+
except Exception:
|
|
357
|
+
return 0.0
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@reward
|
|
361
|
+
def route_handler_post(args: RewardArgs, messages: list) -> float:
|
|
362
|
+
"""Reward for POST route handler in Next.js Route Handlers."""
|
|
363
|
+
response = messages[-1].get("content", "")
|
|
364
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
365
|
+
if not match:
|
|
366
|
+
return 0.0
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
370
|
+
matches = root.find_all(
|
|
371
|
+
Config(rule={"pattern": "export async function POST(request: Request) { $$$BODY }"})
|
|
372
|
+
)
|
|
373
|
+
return 1.0 if matches else 0.0
|
|
374
|
+
except Exception:
|
|
375
|
+
return 0.0
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@reward
|
|
379
|
+
def default_page_component(args: RewardArgs, messages: list) -> float:
|
|
380
|
+
"""Reward for default page component."""
|
|
381
|
+
response = messages[-1].get("content", "")
|
|
382
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
383
|
+
if not match:
|
|
384
|
+
return 0.0
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
388
|
+
matches = root.find_all(
|
|
389
|
+
Config(rule={"pattern": "export default function Page() { $$$BODY }"})
|
|
390
|
+
)
|
|
391
|
+
return 1.0 if matches else 0.0
|
|
392
|
+
except Exception:
|
|
393
|
+
return 0.0
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@reward
|
|
397
|
+
def client_component_detection(args: RewardArgs, messages: list) -> float:
|
|
398
|
+
"""Reward for client components using 'use client' directive."""
|
|
399
|
+
response = messages[-1].get("content", "")
|
|
400
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
401
|
+
if not match:
|
|
402
|
+
return 0.0
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
406
|
+
matches = root.find_all(
|
|
407
|
+
Config(rule={"all": [{"kind": "string"}, {"pattern": '"use client"'}]})
|
|
408
|
+
)
|
|
409
|
+
return 1.0 if matches else 0.0
|
|
410
|
+
except Exception:
|
|
411
|
+
return 0.0
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@reward
|
|
415
|
+
def server_component_detection(args: RewardArgs, messages: list) -> float:
|
|
416
|
+
"""Reward for server components (without 'use client')."""
|
|
417
|
+
response = messages[-1].get("content", "")
|
|
418
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
419
|
+
if not match:
|
|
420
|
+
return 0.0
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
424
|
+
has_function = root.find_all(Config(rule={"kind": "function_declaration"}))
|
|
425
|
+
has_use_client = root.find_all(Config(rule={"kind": "string", "pattern": '"use client"'}))
|
|
426
|
+
return 1.0 if has_function and not has_use_client else 0.0
|
|
427
|
+
except Exception:
|
|
428
|
+
return 0.0
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@reward
|
|
432
|
+
def parallel_route_segment(args: RewardArgs, messages: list) -> float:
|
|
433
|
+
"""Reward for parallel route segments (e.g., @modal)."""
|
|
434
|
+
response = messages[-1].get("content", "")
|
|
435
|
+
match = re.search(r"```(?:typescript|tsx|ts)\n(.*?)```", response, re.DOTALL)
|
|
436
|
+
if not match:
|
|
437
|
+
return 0.0
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
root = SgRoot(match.group(1).strip(), "tsx").root()
|
|
441
|
+
matches = root.find_all(
|
|
442
|
+
Config(rule={"kind": "program", "has": {"pattern": "@", "stopBy": "end"}})
|
|
443
|
+
)
|
|
444
|
+
return 1.0 if matches else 0.0
|
|
445
|
+
except Exception:
|
|
446
|
+
return 0.0
|