Initial commit: Original Python LLMTester project
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Iterable, List, Tuple
|
||||
|
||||
__all__ = ["infer_language_from_prompt", "lint_response_markdown"]
|
||||
|
||||
|
||||
_LANGUAGE_HINTS: List[Tuple[str, str]] = [
|
||||
("swiftui", "swift"),
|
||||
("swift", "swift"),
|
||||
("objective-c", "objective-c"),
|
||||
("objc", "objective-c"),
|
||||
("kotlin", "kotlin"),
|
||||
("typescript", "typescript"),
|
||||
("javascript", "javascript"),
|
||||
("node.js", "javascript"),
|
||||
("python", "python"),
|
||||
("rust", "rust"),
|
||||
("go", "go"),
|
||||
("c#", "csharp"),
|
||||
("c++", "cpp"),
|
||||
("java", "java"),
|
||||
("sql", "sql"),
|
||||
]
|
||||
|
||||
_CODE_SYMBOLS = set("{}();[]=<>+-*/.&|%!:@")
|
||||
_CODE_PREFIXES = (
|
||||
"func ",
|
||||
"class ",
|
||||
"struct ",
|
||||
"enum ",
|
||||
"protocol ",
|
||||
"extension ",
|
||||
"import ",
|
||||
"let ",
|
||||
"var ",
|
||||
"guard ",
|
||||
"if ",
|
||||
"for ",
|
||||
"while ",
|
||||
"switch ",
|
||||
"case ",
|
||||
"return ",
|
||||
"init(",
|
||||
"init ",
|
||||
"deinit",
|
||||
"public ",
|
||||
"private ",
|
||||
"internal ",
|
||||
"fileprivate ",
|
||||
"override ",
|
||||
"@",
|
||||
"#if",
|
||||
"#endif",
|
||||
"#warning",
|
||||
"#error",
|
||||
)
|
||||
|
||||
_COMMENT_PREFIXES = ("///", "//", "/*", "*/", "* ")
|
||||
_BULLET_PREFIXES = ("- ", "* ", "+ ", "• ")
|
||||
|
||||
|
||||
def infer_language_from_prompt(prompt_text: str) -> str:
|
||||
"""Infer a reasonable language hint from the prompt text."""
|
||||
lowered = prompt_text.lower()
|
||||
for token, language in _LANGUAGE_HINTS:
|
||||
if token in lowered:
|
||||
return language
|
||||
return "text"
|
||||
|
||||
|
||||
def lint_response_markdown(raw_text: str, *, language_hint: str = "text") -> str:
|
||||
"""Normalize markdown so that only code segments are fenced and text stays plain."""
|
||||
if not raw_text:
|
||||
return ""
|
||||
|
||||
text = raw_text.replace("\r\n", "\n").replace("\r", "\n").strip("\n")
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
if "```" in text:
|
||||
return _normalize_existing_fences(text, language_hint)
|
||||
|
||||
return _auto_fence_code_segments(text, language_hint)
|
||||
|
||||
|
||||
def _normalize_existing_fences(text: str, language_hint: str) -> str:
|
||||
lines = text.split("\n")
|
||||
output: List[str] = []
|
||||
in_fence = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("```"):
|
||||
fence_marker = stripped[3:].strip()
|
||||
if in_fence:
|
||||
output.append("```")
|
||||
in_fence = False
|
||||
else:
|
||||
language = fence_marker or (language_hint if language_hint != "text" else "")
|
||||
output.append(f"```{language}" if language else "```")
|
||||
in_fence = True
|
||||
else:
|
||||
output.append(line)
|
||||
|
||||
if in_fence:
|
||||
output.append("```")
|
||||
|
||||
result = "\n".join(output).strip()
|
||||
return result
|
||||
|
||||
|
||||
def _auto_fence_code_segments(text: str, language_hint: str) -> str:
|
||||
lines = text.split("\n")
|
||||
segments: List[Tuple[str, List[str]]] = []
|
||||
current_lines: List[str] = []
|
||||
current_mode: str | None = None
|
||||
|
||||
for line in lines:
|
||||
if _is_blank(line):
|
||||
if current_lines:
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
classification = "code" if _is_likely_code_line(line) else "text"
|
||||
|
||||
if current_mode is None:
|
||||
current_mode = classification
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
if classification == current_mode:
|
||||
current_lines.append(line)
|
||||
continue
|
||||
|
||||
segments.append((current_mode, current_lines))
|
||||
current_mode = classification
|
||||
current_lines = [line]
|
||||
|
||||
if current_lines:
|
||||
segments.append((current_mode or "text", current_lines))
|
||||
|
||||
cleaned_parts: List[str] = []
|
||||
for mode, block_lines in segments:
|
||||
block_text = "\n".join(block_lines).strip("\n")
|
||||
if not block_text:
|
||||
continue
|
||||
if mode == "code":
|
||||
cleaned_parts.append(_format_code_block(block_text, language_hint))
|
||||
else:
|
||||
cleaned_parts.append(block_text)
|
||||
|
||||
return "\n\n".join(part for part in cleaned_parts if part).strip()
|
||||
|
||||
|
||||
def _format_code_block(content: str, language_hint: str) -> str:
|
||||
language = language_hint if language_hint and language_hint != "text" else ""
|
||||
inner = content.strip("\n")
|
||||
return f"```{language}\n{inner}\n```"
|
||||
|
||||
|
||||
def _is_blank(line: str) -> bool:
|
||||
return not line.strip()
|
||||
|
||||
|
||||
def _is_likely_code_line(line: str) -> bool:
|
||||
if line.startswith((" ", "\t")):
|
||||
return True
|
||||
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
return False
|
||||
|
||||
if stripped.startswith(_BULLET_PREFIXES):
|
||||
return False
|
||||
|
||||
if stripped.startswith(_COMMENT_PREFIXES):
|
||||
return True
|
||||
|
||||
if any(stripped.startswith(prefix) for prefix in _CODE_PREFIXES):
|
||||
return True
|
||||
|
||||
if stripped.endswith(("{", "}", ";")):
|
||||
return True
|
||||
|
||||
if stripped.startswith(("}", "{", "case ", "default:")):
|
||||
return True
|
||||
|
||||
if "(" in stripped and ")" in stripped:
|
||||
return True
|
||||
|
||||
if "=" in stripped:
|
||||
return True
|
||||
|
||||
symbol_count = sum(1 for ch in stripped if ch in _CODE_SYMBOLS)
|
||||
letter_count = sum(1 for ch in stripped if ch.isalpha())
|
||||
if symbol_count >= 2 and symbol_count >= max(1, letter_count * 0.3):
|
||||
return True
|
||||
|
||||
if re.search(r"\)\s*->", stripped):
|
||||
return True
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user