Skip to content

Commit 997a5bd

Browse files
committed
fix(examples): robust fenced-JSON parsing and runner cleanup in llm review
1 parent e4c02fd commit 997a5bd

2 files changed

Lines changed: 23 additions & 12 deletions

File tree

examples/skills_code_review_agent/review/llm_review.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
from .findings import Finding
1818
from .redaction import redact_text
1919

20-
_FENCE_RE = re.compile(r"```(?:json)?\s*(\{.*?\})\s*```", re.DOTALL)
20+
_FENCE_RE = re.compile(r"```(?:json)?\s*(\{.*\})\s*```", re.DOTALL)
2121

2222

23-
def parse_llm_output(text: str):
23+
def parse_llm_output(text: str) -> tuple[list[dict], str]:
2424
"""Extract (findings_dicts, summary) from model text; ([], "") on failure."""
2525
candidates = [text.strip()]
2626
fence = _FENCE_RE.search(text)
@@ -36,24 +36,27 @@ def parse_llm_output(text: str):
3636
return [], ""
3737

3838

39-
async def run_llm_review(agent, diff_text: str, static_findings):
39+
async def run_llm_review(agent, diff_text: str, static_findings) -> tuple[list[Finding], str, list[str]]:
4040
"""Run one review turn. Returns (llm_findings, summary, warnings)."""
41-
warnings = []
41+
warnings: list[str] = []
4242
runner = Runner(app_name="code_review_agent", agent=agent,
4343
session_service=InMemorySessionService())
4444
prompt = REVIEW_REQUEST_TEMPLATE.format(
4545
findings_json=json.dumps([f.model_dump() for f in static_findings]),
4646
diff=redact_text(diff_text))
4747
message = Content(role="user", parts=[Part.from_text(text=prompt)])
4848
final_text = ""
49-
async for event in runner.run_async(user_id="cr_user",
50-
session_id=uuid.uuid4().hex,
51-
new_message=message):
52-
if event.partial or not event.content or not event.content.parts:
53-
continue
54-
for part in event.content.parts:
55-
if getattr(part, "text", None) and not getattr(part, "thought", None):
56-
final_text += part.text
49+
try:
50+
async for event in runner.run_async(user_id="cr_user",
51+
session_id=uuid.uuid4().hex,
52+
new_message=message):
53+
if event.partial or not event.content or not event.content.parts:
54+
continue
55+
for part in event.content.parts:
56+
if getattr(part, "text", None) and not getattr(part, "thought", None):
57+
final_text += part.text
58+
finally:
59+
await runner.close()
5760
raw_findings, summary = parse_llm_output(final_text)
5861
if not summary and final_text:
5962
warnings.append("llm output was not valid JSON; ignored")

examples/skills_code_review_agent/tests/test_llm_review.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ def test_parse_llm_output_fenced_json():
3131
assert summary == "s" and findings == []
3232

3333

34+
def test_parse_llm_output_fenced_nested_json():
35+
text = ('```json\n{"summary": "s", "findings": [{"severity": "high", "category": "security",'
36+
' "file": "a.py", "line": 3, "title": "x", "confidence": 0.9}]}\n```')
37+
findings, summary = parse_llm_output(text)
38+
assert summary == "s"
39+
assert findings[0]["severity"] == "high"
40+
41+
3442
def test_parse_llm_output_garbage_returns_empty():
3543
findings, summary = parse_llm_output("I could not review this.")
3644
assert findings == [] and summary == ""

0 commit comments

Comments
 (0)