LLM 응답의 수식을 터미널에서 읽을 수 있게 렌더링합니다. 구현은 core/ui/latex.py 한 모듈이고, 진입점은 render_latex(src, block=...)와 스트리밍 본문에서 수식 구간을 찾아내는 extract_and_render_inline입니다. 호출자는 대화형 루프(core/cli/interactive_loop.py)입니다.
2단 구조
Tier 1 pylatexenc LatexNodes2Text 인라인/블록 공통. LaTeX → 평탄한 Unicode 한 줄 Tier 2 latex2sympy2 + sympy.pretty 블록 전용. 분수·적분을 2D Unicode 블록으로 폴백 원문 그대로 양쪽 모두 실패 시. 절대 raise하지 않음
인라인(block=False)은 Tier 2를 아예 건너뜁니다. 한 줄 흐름이 예측 가능해야 하기 때문입니다. 블록은 _has_tier2_construct가 분수 같은 2D 가치가 있는 토큰을 발견했을 때만 Tier 2를 시도하고, 파싱이나 pretty가 실패하면 조용히 Tier 1로 내려갑니다. Tier 1마저 실패하면 입력 원문을 그대로 반환합니다.
감지 휴리스틱
extract_and_render_inline은 구분자 있는 수식 ($...$, $$...$$)과 구분자 없는 후보를 모두 다루면서 오탐을 막는 가드를 둡니다. 마크다운 코드 스팬은 건너뛰고, /가 파일 경로 문맥인지 판별하고 (_looks_like_path_context), 숫자 밑 첨자와 중첩 윗 첨자도 처리합니다. 수식 출력 계약 자체는 프롬프트 쪽에서 with_math_output_formatting(core/llm/prompt_assembler.py)이 모델에 지시합니다.
실패 모드
| 증상 | 원인 | 해법 |
|---|---|---|
| 수식이 원문 LaTeX로 보임 | pylatexenc 미설치 또는 Tier 1 예외 | 의존성은 pyproject에 선언되어 있습니다. 재설치 후에도 같으면 입력이 LaTeX가 아닌 경우입니다. |
| 블록 수식이 한 줄로 나옴 | Tier 2 파싱 실패 후 Tier 1 폴백 | 의도된 동작입니다. latex2sympy2가 다루지 못하는 구문은 평탄화됩니다. |
| 경로가 수식으로 렌더링 | 구분자 없는 후보 오탐 | 경로 문맥 가드가 회귀 테스트로 고정되어 있습니다. 사례를 발견하면 테스트에 추가합니다. |
회귀 테스트
tests/core/ui/test_ui_latex.py, tests/core/ui/test_cli_latex_uiux.py, tests/core/cli/test_interactive_loop_latex.py가 멀티라인 collapse, 구분자 없는 휴리스틱, 경로 문맥, 숫자 밑 첨자 케이스를 고정합니다.