Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
13 KiB
수정사항 10건 검토·적용 기록 (2026-09-16)
원본: 수정사항.txt. 이 파일은 2026-09-16 구조 변경(로직 조각 도입) 이전 기준으로 작성돼
일부 항목은 이미 해소된 상태였다. 항목별 판정과 적용 내용을 남긴다.
| # | 항목 | 판정 | 적용 |
|---|---|---|---|
| 1 | LLM 요약이 검색 색인에 미반영 | 부분 유효 | ✅ 적용 |
| 2 | 프로그램 단위 요약 미생성 | 이미 완료 | 코드 변경 없음 |
| 3 | 텍스트 심볼 치환 | 프롬프트 완료 / FTS 미반영 | ✅ FTS 반영 |
| 4 | 용어 사전이 질의 확장에 미사용 | 유효 | ✅ 적용 |
| 5 | keywords_en · sap_objects 필드 없음 | 이미 완료 | 코드 변경 없음 |
| 6 | 중복 프로그램 처리 없음 | 유효 | ✅ 적용 |
| 7 | 정답셋 4문항 | 유효 | ⏸ 범위 제외 (사용자 지시) |
| 8 | 엔티티·프로세스 위키 페이지 없음 | 유효 | ✅ 엔티티 적용 / 프로세스 보류 |
| 9 | unit 문서 70만 파일 문제 | 구조 변경으로 해소 | 무효 |
| 10 | 요약 병렬 실행 없음 | 유효 | ✅ 적용 |
이미 완료였던 항목 (2·5·9)
- 2번 —
summarize/runner.py:ensure_program_summary()가ProgramSummary를 만들어program.summary_json에 저장하고,UNIT_PROMPT에program_purpose·main_flow·key_internal_tables를 문맥으로 주입한다. DB 에 요약이 0건이었던 것은 미구현이 아니라 OpenRouter 무료 티어의 429 Too Many Requests 로 실행이 실패해서였다 (unit.summary_error6건 전부 429). - 5번 —
schemas.LogicChunk에purpose_en·keywords_ko·keywords_en·sap_objects가 모두 있고 프롬프트가 명시적으로 요구한다. - 9번 —
units/문서 생성은 중단됐다. 로직 조각은 프로그램 문서 안의 H3/H4 섹션이다. 원본 파일의 "units/ 를 git 제외로 둔 것은 좋은 완화책" 서술은 현재 상태와 맞지 않는다 (.gitignore에units항목이 없고 디렉토리도 없다).
적용 내용
1번 — 요약을 검색 색인에 반영
구조 변경으로 chunk_fts 가 신설돼 조각의 purpose/keywords/objects 는 이미 색인되고 있었고,
extract_unit 도 요약 후 unit_fts 를 갱신하고 있었다. 남아 있던 두 구멍만 막았다.
- 적재 시점
unit_fts.purpose의 장식 문자 —index/loader.py가header_comment를 날것으로 넣어 1,177행 중 867행이'----------------'이었다.clean_comment()를 적용해 0행으로.clean_comment를query/tools.py→index/db.py로 이동 (loader 가 써야 하는데index → query는 의존 역방향).query.tools.clean_comment이름은 하위호환으로 유지.
program_fts가 타이틀·패키지명뿐 —business_purpose_ko/main_flow/keywords_ko/keywords_en/business_tags/sap_module/related_tcodes/ 조각 키워드 / 조각 SAP 객체 / 테이블명 / 텍스트 심볼을 색인에 넣었다.- 색인을 두 테이블로 나눴다:
program_fts(이름·타이틀·패키지) +program_desc_fts(서술). 한 행에 몰아넣었더니 회귀가 났다 — bm25 는 행 전체 길이로 정규화하므로 요약이 붙어 길어진 행이 타이틀만 있는 짧은 행보다 낮게 나온다. 실측으로 타이틀이 정확히"총계정원장 조회"인 ZFIR10070 이"총계정원장 조회하는 프로그램"질의에서 1위 → 33위로 밀렸다 (행 길이 1,872자 vs 40자). 컬럼 가중치로는 해결되지 않는다(정규화는 행 단위). 나누면 각 테이블 안 행 길이가 비슷해지고, 서술 히트는_DESC_WEIGHT=0.6으로 더해질 뿐 이름·타이틀 일치를 밀어내지 않는다. 회귀 테스트:tests/test_dedupe_parallel.py::test_long_summary_does_not_outrank_title_match - 세 FTS 모두
rank(모든 컬럼 가중치 1) 대신 가중 bm25(index/db.py:bm25_rank)를 쓴다. - FTS5 는 컬럼 추가가 불가하므로
index/db.py:_migrate_fts()가 컬럼 구성 변화를 감지해 DROP→재생성한다 (전부 파생 데이터라 안전). refresh_program_fts(con, program)단건 경로를 추가했다. 전량 재구축을/ingest마다 돌리면 1만 본에서 O(N²) 가 된다.- 검색 결과의
reason이 근거를 구분한다:이름/타이틀 일치>키워드 일치>요약 일치>동의어 확장.
- 색인을 두 테이블로 나눴다:
3번 — 텍스트 심볼을 FTS 에 반영
index/db.py:text_symbol_phrases()— 코드 범위에 등장하는TEXT-nnn을 한국어 원문으로 치환.- 조각 저장 시
chunk_fts.keywords/bigrams에, 프로그램 색인에는program_fts.purpose에 넣는다. - 결과:
"계정코드를 입력하세요","회계단위 간편선택"같은 화면 문구로도 검색된다 (LLM 무관).
4번 — 용어 사전 질의 확장
config/glossary.py— 사전 로딩을 한 곳으로 모았다. 기존에는wiki_out/run.py안에 인라인 정규식으로만 있어 검색 쪽에서 쓸 수 없었다. 양방향 동의어 맵을 만든다 (입고→GR/MSEG/101뿐 아니라GR→입고/MSEG도 성립).query/expand.py— 2단 검색. 원질의로 먼저 찾고, 결과가top_k미만일 때만 동의어 식으로 보충하며 점수에EXPANDED_WEIGHT=0.35감쇠를 걸고matched_by='동의어 확장'으로 표시한다.- 동의어를 원질의와 같은 OR 버킷에 섞지 않은 이유:
fts_or는 토큰과 한글 2-gram 을 전부 OR 로 이어 이미 재현율 편향이다. 여기에 동의어까지 같은 가중치로 넣으면"총계정원장"질의가"원장"2-gram 하나로 걸린 문서와 동일 점수가 된다.
- 동의어를 원질의와 같은 OR 버킷에 섞지 않은 이유:
config/domain_glossary.yaml에 MM 섹션(입고·출고·자재·구매오더·이동유형 …)을 시드했다. 기존 사전이 FI 전용이라 원본 파일이 예로 든 "입고 → GR/101" 이 실제로는 동작하지 않았다.
6번 — 중복 unit 조각 복제
summarize/runner.py:_dedupe_plan()+clone_unit_chunks().code_hash가 같은 unit 은 대표 1건만 LLM 에 보내고 나머지는 조각을 복제한다. 이번 배치 밖에서 이미 추출된 동일 코드 unit 이 있으면 LLM 호출 0회로 복제한다.- 줄 번호 평행이동이 핵심이다 — 코드는 같아도 include 안 위치가 달라
target.line_start - rep.line_start만큼 밀어야 한다. 테스트가 복제된 줄 범위의 코드 원문이 대표와 동일한지 확인한다. - 실측(샘플 14본): 중복 그룹 108개 / unit 287개 → LLM 호출 179회(62%) 절감 가능.
--no-dedupe로 끌 수 있다.
8번 — 엔티티 위키 페이지
wiki_out/entities.py—tables/functions/tcodes/를table_ref·call_edge에서 LLM 없이 결정론적으로 생성. 실측 산출: 테이블 146건, 펑션 94건.tables/<T>.md— 읽는 프로그램 / 쓰는 프로그램 / unit 단위 사용 지점functions/<FM>.md— 호출 프로그램 / 호출 지점 / RFC 여부tcodes/—tcode테이블 미확보로 현재 0건 (데이터가 들어오면 그대로 생성됨)- 프로그램당 파일이 아니라 엔티티당 파일이라 9번의 파일 수 폭발이 재발하지 않는다.
index.md를 계층 진입점으로 재작성 — SAP 모듈 → 패키지 → 프로그램. 모듈은 요약의sap_module, 요약 전에는Z<모듈><번호>관행에서 추정한다.- 프로세스 페이지는 보류. 프로그램을 가로지르는 reduce 단계가 필요하고, 조각 데이터가 실제로 쌓인 뒤에 판단하는 게 맞다 (원본 파일도 "별도 작업"으로 인정).
10번 — 병렬 실행
llm_concurrency가 선언만 있고 읽는 코드가 없는 죽은 설정이었다. 실제로 연결했다.- sqlite 커넥션은 스레드 간 공유가 안 되므로 3단으로 갈랐다:
build_unit_prompts()(DB 읽기, 메인) →_run_calls_parallel()(LLM 호출, 워커) →finish_unit()(검증·저장, 메인). - 동시성만 올리면 429 가 늘어난다.
OpenAICompatClient.complete_json에 429/5xx 지수 백오프 재시도(Retry-After존중, jitter 포함)를 같이 넣었다. 기존에는 재시도가 없어 429 한 번에 unit 이failed로 굳었다.
함께 고친 것 (직전 분석에서 보고한 버그)
파서 토큰화 — 필드심볼·테이블본문 대입
<ls_fcat>-fieldname = 'X' 가 ['<ls_fcat>', '-', 'fieldname', '=', ...] 로 쪼개져
일반 대입 판정(up[1] == "=")이 깨졌다. 결과로 쓰기 지점이 누락되고 미인식 문장으로 잡혔다.
-
parser/tokenizer.py— 필드심볼이-컴포넌트/->메서드까지 한 토큰.&1매크로 파라미터도 토큰화. -
parser/statements.py:assignment_eq_index()— 대입 판정을 한 곳으로.X =,X[] =,X[ key = v ] =형태를 모두 인식한다.dataflow와run(미인식 리포트)이 같은 판정을 쓴다. -
효과 (샘플 14본 / 문장 24,578건. 베이스라인 커밋
35a15aa를 worktree 로 꺼내 직접 측정):지표 이전 이후 차이 미인식 문장 1,055건 (4.29%) 30건 (0.12%) −1,025 최악 프로그램 ZMMR71110_API 12.03% ZBACKUP_FI 0.46% 쓰기 지점 총계 7,327 8,251 +924 └ 필드심볼 대상 207 1,067 +860 └ itab[]형태230 307 +77 itab[] = ...와<fs>-comp = ...는 내부테이블을 채우는 흔한 관용구라trace_variable("gt_head 가 어디서 채워지나")의 정확도에 직접 영향이 있었다. 이전에도 필드심볼 쓰기가 207건 잡혔던 것은READ TABLE ... ASSIGNING <fs>/LOOP AT ... ASSIGNING <fs>경로는 별도 분기라 영향을 받지 않았기 때문이다.
외부 호출 목록 오염
CL_GUI_ALV_GRID=>MC_FC_AUF 같은 정적 상수 읽기가 method_ref 로 기록돼 호출로 취급됐다.
query/tools.py:_structure_summary 는 걸러냈지만 wiki_out/okf_writer.py 는 걸르지 않아
x-calls 40건이 거의 전부 ALV 상수와 CALL SCREEN 의 화면번호("100")였다.
parser/refs.py:EXTERNAL_CALL_KINDS/FUNCTION_CALL_KINDS로 기준을 한 곳에 두고 요약·위키·엔티티 페이지가 공유한다.- ZFIR10070 의
x-calls: ALV 상수 40건 → 실제 FM·외부 PERFORM·SUBMIT 14건.
설치 명령이 동작하지 않았음
README 가 안내하는 pip install -e ".[dev]" 가 실패했다 — flat-layout 자동 탐색이
data/ wiki/ 까지 패키지로 오인한다. pyproject.toml 에 [tool.setuptools] packages 를 명시했다.
로더 강제 재적재
파서·색인 로직이 바뀌면 소스가 그대로라도 산출물이 달라지는데, source_hash 가 같으면 스킵돼
반영할 방법이 없었다. python -m index.loader --force 를 추가했다.
7번 (정답셋)을 제외한 결과
1·4번의 효과를 숫자로 확인할 수단이 없다. 확인한 것은 기능이 동작한다는 사실뿐이다:
ACDOCT/FAGLL03로 질의 → 총계정원장 프로그램이동의어 확장으로 나온다"회계 담당자가 월 단위로 확인"(요약 본문) → 프로그램이 나온다"계정코드를 입력하세요"(텍스트 심볼) → 프로그램이 나온다
정밀도가 좋아졌는지, 아래 계수들이 적절한지, 2단 검색이 베이스라인보다 나은지는 측정하지 않았다. 현 정답셋 5문항으로는 개선도 회귀도 보이지 않는다.
정답셋 50~100문항이 생긴 뒤에 튜닝할 값:
| 값 | 위치 | 현재 | 뜻 |
|---|---|---|---|
EXPANDED_WEIGHT |
query/expand.py |
0.35 | 동의어 히트 점수 감쇠 |
MAX_SYNONYMS |
query/expand.py |
24 | 질의당 동의어 상한 |
_DESC_WEIGHT |
query/tools.py |
0.6 | 요약·키워드 색인 히트 가중 |
_BM25_WEIGHTS |
index/db.py |
표 참조 | FTS 컬럼별 bm25 가중치 |
| 2단 검색 보충 조건 | query/tools.py:_fts_rows |
len(결과) < limit |
동의어 단계 발동 시점 |
이번 회귀가 이 과제의 필요성을 그대로 보여준다. find recall@5 가 1.0 → 0.833 으로 떨어진 것을
5문항 정답셋이 잡아냈지만, 원인 파악은 수동 디버깅으로 했다. 문항이 50개면 어떤 유형의 질의가
깨졌는지가 바로 드러난다. 위 5개 값을 감으로 정한 상태가 현재의 가장 큰 미확정 요소다.