Skip to content

Commit 8755600

Browse files
committed
Make draft-release-notes classify.py robust to multi-object LLM responses and slow agent runs
1 parent a9a3c4b commit 8755600

1 file changed

Lines changed: 29 additions & 8 deletions

File tree

.github/scripts/draft-release-notes/classify.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,34 @@ def parse_response(s: str) -> dict:
244244
s = s.strip()
245245
s = re.sub(r"^```(?:json)?\s*", "", s, flags=re.I)
246246
s = re.sub(r"\s*```$", "", s)
247-
# Some CLIs prefix a status line; try to locate the first { and last }.
248-
if not s.startswith("{"):
249-
start = s.find("{")
250-
end = s.rfind("}")
251-
if start != -1 and end != -1:
252-
s = s[start : end + 1]
253-
return json.loads(s)
247+
# The model sometimes emits scratchpad objects (e.g. {"intent": "..."})
248+
# before the real decision object. Walk all top-level JSON objects in
249+
# the string and return the last one that has a "decision" key, falling
250+
# back to the last object if none match.
251+
decoder = json.JSONDecoder()
252+
objects: list[dict] = []
253+
i = 0
254+
n = len(s)
255+
while i < n:
256+
# Skip to the next object start.
257+
j = s.find("{", i)
258+
if j == -1:
259+
break
260+
try:
261+
obj, end = decoder.raw_decode(s, j)
262+
except json.JSONDecodeError:
263+
i = j + 1
264+
continue
265+
if isinstance(obj, dict):
266+
objects.append(obj)
267+
i = end
268+
if not objects:
269+
# Force the original error path for callers that expect JSONDecodeError.
270+
return json.loads(s)
271+
for obj in reversed(objects):
272+
if "decision" in obj:
273+
return obj
274+
return objects[-1]
254275

255276

256277
def validate(decision: dict) -> list[str]:
@@ -356,7 +377,7 @@ def process_one(bundle: PrBundle, args) -> tuple[str, str | None, dict | None]:
356377
def main() -> int:
357378
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
358379
ap.add_argument("--jobs", type=int, default=4, help="parallel CLI invocations (default 4)")
359-
ap.add_argument("--timeout", type=int, default=300, help="per-PR CLI timeout seconds")
380+
ap.add_argument("--timeout", type=int, default=900, help="per-PR CLI timeout seconds")
360381
ap.add_argument("--force", action="store_true", help="re-classify PRs with existing decision.json")
361382
ap.add_argument("--only", type=int, nargs="*", help="restrict to these PR numbers")
362383
ap.add_argument(

0 commit comments

Comments
 (0)