refactor: consolidate LLM JSON parsing into shared llm_utils module

Extracted duplicate _strip_markdown_fences / _parse_llm_json functions
from 7 files into app/services/llm_utils.py. Two shared functions:
- strip_markdown_fences(): fence stripping only
- parse_llm_json(): fence stripping + JSON parse + error logging

Files updated: flowpilot_engine, knowledge_flywheel, session_to_flow_service,
ai_tree_generator_service, ai_fix_service, ai_chat_service, kb_conversion_service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 03:25:25 +00:00
parent 0a77215fac
commit 10cf5f45eb
8 changed files with 53 additions and 72 deletions

View File

@@ -275,13 +275,7 @@ def _build_system_prompt(flow_type: str) -> str:
return f"{ROLE_PERSONA}\n\n{flow_context}\n\n{SCHEMA_CONTEXT}\n\n{INTERVIEW_PROTOCOL}\n\n{RESPONSE_FORMAT}"
def _strip_markdown_fences(text: str) -> str:
"""Strip markdown code fences if the model wrapped its JSON response."""
text = text.strip()
match = re.match(r"^```(?:json)?\s*([\s\S]*?)```$", text)
if match:
return match.group(1).strip()
return text
from app.services.llm_utils import strip_markdown_fences as _strip_markdown_fences
def _parse_delta(response: str) -> dict | None:

View File

@@ -86,11 +86,7 @@ def _serialize_tree_outline(
return "\n".join(lines)
def _strip_markdown_fences(text: str) -> str:
"""Strip ```json...``` fences from AI response."""
return re.sub(r"^```(?:json)?\s*\n?", "", text.strip(), flags=re.MULTILINE).rstrip(
"`"
).strip()
from app.services.llm_utils import strip_markdown_fences as _strip_markdown_fences
def _replace_node_in_tree(

View File

@@ -13,6 +13,8 @@ import re
import uuid
from typing import Any
from app.services.llm_utils import strip_markdown_fences as _strip_markdown_fences
from app.core.ai_provider import get_ai_provider
from app.core.config import settings
from app.core.ai_tree_validator import validate_generated_tree, count_tree_stats
@@ -111,14 +113,6 @@ Return a corrected full JSON object only. No markdown, no prose, no code fences.
Fix ALL listed errors while maintaining the same troubleshooting/procedural logic."""
def _strip_markdown_fences(text: str) -> str:
"""Strip markdown code fences if the model wrapped its JSON response."""
text = text.strip()
match = re.match(r"^```(?:json)?\s*([\s\S]*?)```$", text)
if match:
return match.group(1).strip()
return text
def _estimate_cost(input_tokens: int, output_tokens: int) -> float:

View File

@@ -24,13 +24,7 @@ COST_PER_INPUT_TOKEN = 3.0 / 1_000_000
COST_PER_OUTPUT_TOKEN = 15.0 / 1_000_000
def _strip_markdown_fences(text: str) -> str:
"""Strip markdown code fences if the model wrapped its JSON response."""
text = text.strip()
match = re.match(r"^```(?:json)?\s*([\s\S]*?)```$", text)
if match:
return match.group(1).strip()
return text
from app.services.llm_utils import strip_markdown_fences as _strip_markdown_fences
def _try_repair_json(text: str) -> dict | None: