Files
ABAP-Indexing/docs/수정사항-적용.md
T

13 KiB
Raw Blame History

수정사항 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_PROMPTprogram_purpose · main_flow · key_internal_tables 를 문맥으로 주입한다. DB 에 요약이 0건이었던 것은 미구현이 아니라 OpenRouter 무료 티어의 429 Too Many Requests 로 실행이 실패해서였다 (unit.summary_error 6건 전부 429).
  • 5번schemas.LogicChunkpurpose_en · keywords_ko · keywords_en · sap_objects 가 모두 있고 프롬프트가 명시적으로 요구한다.
  • 9번units/ 문서 생성은 중단됐다. 로직 조각은 프로그램 문서 안의 H3/H4 섹션이다. 원본 파일의 "units/ 를 git 제외로 둔 것은 좋은 완화책" 서술은 현재 상태와 맞지 않는다 (.gitignoreunits 항목이 없고 디렉토리도 없다).

적용 내용

1번 — 요약을 검색 색인에 반영

구조 변경으로 chunk_fts 가 신설돼 조각의 purpose/keywords/objects 는 이미 색인되고 있었고, extract_unit 도 요약 후 unit_fts 를 갱신하고 있었다. 남아 있던 두 구멍만 막았다.

  • 적재 시점 unit_fts.purpose 의 장식 문자index/loader.pyheader_comment 를 날것으로 넣어 1,177행 중 867행이 '----------------' 이었다. clean_comment() 를 적용해 0행으로.
    • clean_commentquery/tools.pyindex/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.py2단 검색. 원질의로 먼저 찾고, 결과가 top_k 미만일 때만 동의어 식으로 보충하며 점수에 EXPANDED_WEIGHT=0.35 감쇠를 걸고 matched_by='동의어 확장' 으로 표시한다.
    • 동의어를 원질의와 같은 OR 버킷에 섞지 않은 이유: fts_or 는 토큰과 한글 2-gram 을 전부 OR 로 이어 이미 재현율 편향이다. 여기에 동의어까지 같은 가중치로 넣으면 "총계정원장" 질의가 "원장" 2-gram 하나로 걸린 문서와 동일 점수가 된다.
  • 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.pytables/ 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 ] = 형태를 모두 인식한다. dataflowrun(미인식 리포트)이 같은 판정을 쓴다.

  • 효과 (샘플 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개 값을 감으로 정한 상태가 현재의 가장 큰 미확정 요소다.