Skip to content

Commit 14dabe0

Browse files
increase base tokens/ add pattern recognization on model thinking for error handling
1 parent a1d7114 commit 14dabe0

2 files changed

Lines changed: 30 additions & 16 deletions

File tree

src/codilay/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class CodiLayConfig:
1616
llm_model: Optional[str] = None # None = use provider default
1717
llm_provider: str = "anthropic"
1818
llm_base_url: Optional[str] = None # Override provider's default base URL
19-
max_tokens_per_call: int = 4096
19+
max_tokens_per_call: int = 8192
2020
max_file_size: int = 50000
2121
skip_binary: bool = True
2222
skip_generated: List[str] = field(

src/codilay/llm_client.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import os
5+
import re
56
import sys
67
import time
78
from typing import Any, Dict
@@ -339,6 +340,10 @@ def _call_openai(self, system_prompt: str, user_prompt: str, json_mode: bool = F
339340
# ── JSON parsing ───────────────────────────────────────────────
340341

341342
def _parse_json(self, text: str) -> Dict[str, Any]:
343+
# Strip thinking blocks which often contain invalid JSON or brackets
344+
pattern = r"(?is)<(?:think|thinking|thought|reasoning)>.*?</(?:think|thinking|thought|reasoning)>"
345+
text = re.sub(pattern, "", text).strip()
346+
342347
# Handle markdown fences that might not be at the very start/end
343348
text = text.strip()
344349

@@ -374,44 +379,53 @@ def _parse_json(self, text: str) -> Dict[str, Any]:
374379
return {"error": "LLM returned non-object JSON", "raw_value": parsed}
375380

376381
def _salvage_json(self, text: str) -> Dict[str, Any]:
382+
pattern = r"(?is)<(?:think|thinking|thought|reasoning)>.*?</(?:think|thinking|thought|reasoning)>"
383+
text = re.sub(pattern, "", text).strip()
377384
text = text.strip()
378-
start = text.find("{")
379-
if start == -1:
380-
return {"error": "Failed to parse LLM response (no start brace)", "raw_response": text[:1000]}
381385

382-
end = text.rfind("}")
386+
brace_starts = [m.start() for m in re.finditer(r"\{", text)]
387+
if not brace_starts:
388+
return {"error": "Failed to parse LLM response (no start brace)", "raw_response": text[:1000]}
383389

384-
# Strategy 1: Classic substring or take all if no end brace
385-
if end != -1 and end > start:
386-
candidates = [text[start : end + 1], text[start:]]
387-
else:
388-
candidates = [text[start:]]
390+
candidates_parsed = []
389391

390-
for candidate in candidates:
392+
# Parse starting from every brace. Save valid outputs along with string block length.
393+
for start_idx in brace_starts:
394+
candidate = text[start_idx:]
391395
try:
392396
parsed = json.loads(candidate)
393397
if isinstance(parsed, dict):
394-
return parsed
398+
candidates_parsed.append((len(candidate), parsed))
399+
continue
395400
except json.JSONDecodeError as e:
396401
# Strategy 2: Handle extra data after valid object
397402
if "Extra data" in str(e):
398403
try:
399-
parsed = json.loads(candidate[: e.pos].strip())
404+
valid_str = candidate[: e.pos].strip()
405+
parsed = json.loads(valid_str)
400406
if isinstance(parsed, dict):
401-
return parsed
407+
candidates_parsed.append((len(valid_str), parsed))
408+
continue
402409
except Exception:
403410
pass
404411

405412
# Strategy 3: Truncated JSON repair
406413
for suffix in ["}", '"', '"}', '"}]}', '"}}', "}}", "]}", "]}"]:
407414
try:
408-
parsed = json.loads(candidate + suffix)
415+
valid_str = candidate + suffix
416+
parsed = json.loads(valid_str)
409417
if isinstance(parsed, dict):
410418
parsed["_repaired"] = True
411-
return parsed
419+
candidates_parsed.append((len(valid_str), parsed))
420+
break
412421
except Exception:
413422
continue
414423

424+
if candidates_parsed:
425+
# Sort by the length of the matching JSON string to prefer the largest top-level object
426+
candidates_parsed.sort(key=lambda x: x[0], reverse=True)
427+
return candidates_parsed[0][1]
428+
415429
return {"error": "Failed to parse LLM response", "raw_response": text[:1000]}
416430

417431
def get_usage_stats(self) -> Dict[str, int]:

0 commit comments

Comments
 (0)