Files
byeongwook.choiandClaude Fable 5.1 921b8ce229 Stage 0: SAP(ZAA_ICF)에서 패키지 단위로 프로그램 소스 수집 (ingest/from_sap.py)
- sap/: abap-mcp 의 catalog/sap_client/mcp_server 복사. import 와 .env 탐색만 이 저장소에 맞춤
- ingest/from_sap.py: 패키지명 → GET_PROGRAM_LIST → GET_PROGRAM_SOURCE(+Include) → data/raw/*.txt
  (normalize 가 읽는 수집 JSON 형식 그대로). 받은 파일은 건너뛰고 --force 로 재수집
- normalize: 응답의 TCODE_LIST 를 tcodes.jsonl 로 (from_dir 와 같은 모양, 로더가 적재)
- .env.example 에 SAP_URL/SAP_USER/SAP_PASS, tests/test_from_sap.py

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 14:13:14 +09:00

166 lines
11 KiB
Markdown

# abap-indexing
ABAP 소스코드 인덱싱 서버. 약 1만 본의 ABAP 프로그램 소스와 프로그램 메타정보를
정규화 → 구조 파싱 → (LLM 로직 조각 추출) → 인덱싱하고, 질의 API 를 제공한다.
계획서: `ABAP_INDEXING_PLAN.md` (Downloads), 진행 가정: `ASSUMPTIONS.md`.
**조각의 코드 원문은 로직만이고, 붙여넣는 데 필요한 선언(정의부)은 따로 수집한다** — ABAP 은
내부테이블·스트럭처를 TOP 에 선언하므로 로직만 복사하면 컴파일되지 않는다.
쓸지 말지는 붙여넣는 쪽 LLM 이 정한다. → `docs/definition-block-design.md`
**인덱스의 1차 단위는 로직 조각(logic chunk)** 이다. 파서가 자른 FORM/METHOD 는 LLM 에게 코드를
나눠 보여주는 창이고, LLM 이 그 안에서 업무적으로 의미 있는 조각(특정 SQL, DB 갱신, BAPI 호출,
집계 LOOP …)을 골라내 설명을 붙인다. 의미 없는 코드는 인덱스에서 버린다. → `docs/logic-chunk-design.md`
**소비자**: `opencode-be` 레포의 OpenCode 커스텀 툴(`.opencode/tool/_index_client.ts`)이
이 서버의 HTTP API 를 호출한다 (기본 `http://127.0.0.1:8100`).
## 파이프라인
```
python -m ingest.from_sap ZFI01 # Stage 0: SAP(ZAA_ICF)에서 패키지의 프로그램 소스 → data/raw (sap/ = abap-mcp 복사본)
data/raw/*.txt (수집 JSON)
→ python -m ingest.normalize # Stage 1: 줄바꿈 아티팩트 제거, 프로그램/인클루드 파일화
→ python -m parser.run # Stage 2: unit·참조·데이터플로우·호출그래프·정의부 (LLM 없이)
→ python -m index.loader # Stage 4: SQLite(FTS5) 적재
→ python -m summarize.runner # Stage 3: 프로그램 요약 → unit 별 로직 조각 추출
→ python -m wiki_out.run --all # OKF v0.2 위키 (프로그램 문서 + tables/functions 엔티티)
→ python -m query.api # Stage 5: 질의 API :8100
→ python -m eval.run_eval # Stage 6: recall / 조각 적중 / 추적 평가
```
## 설치 / 실행
```bash
python -m venv .venv && .venv/Scripts/python -m pip install -e ".[dev]" # Linux/mac: .venv/bin/python
cp .env.example .env
# data/raw/ 에 수집 파일(ZFI01.txt, ZFIR10070.txt 형식) 배치
python -m ingest.normalize && python -m parser.run && python -m index.loader
python -m query.api # 127.0.0.1:8100
```
테스트: `python -m pytest` (샘플 데이터가 있으면 §4.6 실측 검증 포함)
파서나 색인 로직을 바꿨으면 소스가 그대로라도 재적재가 필요하다:
`python -m index.loader --force`
(정의부를 처음 붙이는 기존 DB 도 `python -m parser.run && python -m index.loader --force` 한 번이 필요하다 —
`declaration` 테이블은 빈 채로 만들어지고 재적재 때 채워진다.)
### 서버가 코드 변경을 반영하지 않을 때
`/dashboard``/wiki-viewer`**템플릿(html)을 요청마다 다시 읽지만**, 파이썬 모듈은
프로세스에 캐시된다(`sys.modules`). 그래서 CSS·JS 를 고치면 새로고침만으로 보이지만
`wiki_out/viewer.py` 같은 **파이썬 코드를 고치면 서버를 다시 띄워야** 한다.
(실제로 조각 내비게이션을 추가했는데 사이드바가 계속 0 으로 보인 적이 있다.)
- 개발 중에는 자동 재시작: `python -m query.api --reload`
- 낡았는지 확인: `GET /health``started_at` 이 코드 수정 시각보다 이전인지 본다
## Stage 3 을 LLM 키 없이 돌리기
`--llm file` 은 프롬프트를 `data/llm_jobs/` 에 파일로 내놓고 응답 파일을 기다린다.
사람이 채워도 되고, 코딩 에이전트가 프롬프트를 읽고 채워도 된다.
```bash
python -m summarize.runner --llm file --program ZFIR10070 --limit 3 # 1) 프롬프트 내놓기
python -m summarize.jobs list --pending # 2) 대기 목록
python -m summarize.jobs show <job_id> # 프롬프트 읽기
python -m summarize.jobs answer <job_id> --file answer.json # 3) 응답 저장(스키마 검증)
python -m summarize.runner --llm file --program ZFIR10070 --limit 3 # 4) 같은 명령 → 적재
```
프로그램 요약은 unit 프롬프트의 **문맥**이므로 먼저 답해야 한다 — 요약이 대기 중이면 그 프로그램의
unit 프롬프트는 나오지 않는다(요약이 채워진 뒤 프롬프트 내용이 달라져 두 번 답하게 되는 것을 막는다).
백엔드 3종: `--llm api`(환경변수 키) / `--llm file`(키 불필요) / `--llm fake`(배선 검증용 더미).
`/ingest` 가 뒤이어 돌리는 요약의 백엔드는 `SUMMARIZE_BACKEND=api|file|off` 로 고른다.
큐 현황은 `GET /summaries/jobs` 또는 `python -m summarize.jobs stats`.
## 결과를 다른 PC 로 가져가기
### 방법 A — 단일 HTML 스냅샷 (파이썬·서버 불필요)
```bash
python -m wiki_out.viewer # → data/wiki-viewer.html (약 1.2 MB)
python -m query.dashboard # → data/dashboard.html (약 1.4 MB)
```
데이터가 파일 안에 박히므로 **그 파일 하나만 복사해 브라우저로 열면** 된다.
로직 조각의 소스 원문과 자연어 설명, 테이블·펑션 문서, 관측소 수치가 모두 들어 있다.
외부 스크립트·이미지를 받지 않으며, 웹폰트는 못 받아도 시스템 한글 글꼴로 폴백한다(폐쇄망 가능).
안 되는 것: 조각 **검색**(`/search/logic`)과 `trace_variable`·`get_call_graph`·`get_table_usage`
같은 구조 도구. 이건 DB 질의라 서버가 필요하다 (뷰어 사이드바의 문서 검색은 동작).
### 방법 B — 인덱스째로 옮기기 (전체 기능)
가져갈 것은 **소스 + `data/index.db` + `wiki/`** 뿐이다 (합쳐 약 14 MB).
```
config/ ingest/ parser/ index/ summarize/ query/ wiki_out/ eval/ docs/
pyproject.toml
data/index.db ← 조각·요약·심볼·호출그래프 + 인클루드 소스 원문까지 전부 들어있다
wiki/ ← 사람 교정본이 있으면 함께 (없으면 재생성 가능)
```
`data/normalized/`·`data/parsed/`·`data/llm_jobs/`**가져갈 필요 없다** — 재생성 가능하고,
조각의 소스 원문은 `include.code` 로 DB 안에 있다. 새 PC 에서:
```bash
python -m venv .venv && .venv/Scripts/python -m pip install -e ".[dev]"
python -m query.api # 관측소·뷰어·검색 API 전부 동작
python -m wiki_out.run --all # (선택) 위키 재생성 — DB 만으로 된다
```
실측 검증: 위 최소 세트만 복사한 디렉토리에서 위키 재생성(283건 검증 통과)·스냅샷 생성·
`search_logic`·`get_table_usage`·`trace_variable`·서버 기동이 모두 정상 동작했다.
## API
| Method | Path | 설명 |
|---|---|---|
| GET | `/health` | 상태 + 적재 통계 |
| GET | `/search/logic?q=&top_k=&program=&kind=` | **로직 조각 검색** — 자연어 로직 질의의 1차 진입점. 프로그램 단위로 묶어 반환 |
| GET | `/summaries/jobs` | LLM 키 없이 돌릴 때의 프롬프트 대기 큐 |
| GET | `/chunks/{chunk_id}` | 조각 메타 + 코드 원문 + **정의부**(붙여넣기용 선언, `?decls=false` 로 끔). chunk_id 의 `#``%23` 으로 인코딩 |
| GET | `/programs/{name}/declarations?scope=` | 프로그램의 선언 카탈로그 (정의부) |
| GET | `/programs/{name}/chunks?unit_id=` | 프로그램(또는 unit)의 조각 목록 |
| GET | `/search/programs?q=&top_k=` | 프로그램 검색 — 이름·타이틀 기반 얇은 색인 (FTS + 한글 2-gram) |
| GET | `/search/units?q=&program=` | unit 검색 — 이름·주석·한 줄 요약 기반 얇은 색인 |
| GET | `/programs/{name}/summary` | 프로그램 요약(LLM) + 구조 요약(파서, 조각 목록 포함) |
| GET | `/programs/{name}/source` | 정규화된 전체 소스 (opencode-be 의 fetch_abap 용) |
| GET | `/programs/{name}/units/{unit}/code` | unit 코드 원문(400줄 상한) + 요약 + 정의부 |
| GET | `/programs/{name}/trace/{symbol}` | 변수 선언 + 쓰기 지점 (via_perform 재귀 전개, 깊이 5) |
| GET | `/programs/{name}/call-graph?unit=` | 호출 그래프 |
| GET | `/programs/{name}/who-calls/{unit}` | 역참조 |
| GET | `/tables/{name}/usage` | 테이블 읽기/쓰기 프로그램·unit |
오류: 404(존재하지 않는 프로그램/심볼 — 유사 후보를 detail 에 포함), 503(DB 오류).
## 원칙 (계획서)
- **사실은 파서가, 해석은 LLM이** — tables/calls/writes 는 파서 결과가 항상 우선. 조각의 줄 번호도
first_line 앵커로 검증하고, 조각의 테이블·호출은 파서로 다시 뽑는다.
- **코드 원문은 로직, 선언은 정의부로 분리.** 조각에는 로직만 담아 검색 품질을 지키고, 붙여넣을 때
필요한 선언은 `declaration` 테이블에서 의존까지 묶어 따로 조립한다 (index/decls.py).
인덱스는 재료와 분류만 넘기고, 쓸지 말지는 붙여넣는 쪽이 정한다.
- **인덱스 단위는 LLM 이 골라낸 로직 조각.** 파서의 문법 단위(FORM/METHOD/FUNCTION/MODULE/이벤트 블록)는
LLM 에게 보여주는 창이자 조각의 컨테이너다. 프로그램 요약을 먼저 만들어 문맥으로 붙인다.
- 모든 산출물은 코드 해시에 묶여 증분 처리 (unit code_hash 가 같으면 조각 보존).
- 요약·태그·키워드는 한국어 우선 + SAP 영어 용어·객체명 병기.
## 현재 상태 / 남은 일
- Stage 1·2·4·5·6 동작. 샘플 14본(문장 24,578) 실측: **미인식 문장 30건(0.12%)**, 프로그램별 최대 0.46%
(프로그램 하나만 보면 안 된다 — `tests/test_parser_fieldsymbol.py` 가 전체를 잰다)
- Stage 3(로직 조각 추출): 구조 완료. **LLM 키 없이 `--llm file` 로 실행 가능** (위 참고)
- 임베딩/벡터 검색: 모델 미정으로 보류 — FTS(+2-gram, 동의어 확장)만 사용 중
- DB: SQLite 기본. PostgreSQL 전환 스키마는 `index/schema_postgres.sql`
- **보강 과제 10건 중 9건 반영 완료** — 판정·적용 내역은 `docs/수정사항-적용.md`
(1 색인 반영 / 3 텍스트 심볼 FTS / 4 용어 사전 질의 확장 / 6 중복 조각 복제 /
8 엔티티 위키·계층 index / 10 병렬 실행 + 429 백오프. 2·5·9 는 이미 완료·무효)
- **남은 일: 7번 정답셋 확대.** 개발자 질의 50~100개를 받아 recall@10/MRR 을 재고 베이스라인과
비교해야 한다. 이게 없으면 위 검색 변경들의 정밀도 효과를 검증할 수 없다
(튜닝 대상 값: `query/expand.py``EXPANDED_WEIGHT`, `MAX_SYNONYMS`, 2단 검색 보충 조건)
- 프로세스 위키 페이지("입고 처리는 세 경로")는 reduce 단계가 필요해 조각 데이터가 쌓인 뒤로 보류