leader review of writer draft: refusal 판정은 citation marker(`[#번호]`) 유무 기반이며 `<REFUSE>` 특수 마커가 아님. O-2 문구 예시도 실제 rag-v3 규칙으로 정정. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
185 lines
9.5 KiB
Markdown
185 lines
9.5 KiB
Markdown
---
|
|
title: kebab v0.20.2 release notes (draft)
|
|
created: 2026-05-29
|
|
status: draft
|
|
release_trigger:
|
|
- 사용자 도그푸딩 필요 (8-finding dogfood 라운드 완료)
|
|
- RAG prompt_template_version default 변경 (rag-v2 → rag-v3 응답언어 매칭)
|
|
- eval --config facade 패치 (검색 품질 eval dogfood KB 평가 enabler)
|
|
---
|
|
|
|
# kebab v0.20.2 — Ask 응답언어 자동 매칭 + 검색 품질 eval 인프라
|
|
|
|
v0.20.1 (한국어 형태소 검색, 2026-05-28) 후속 patch release. 도그푸딩 8-finding 라운드에서 발견·수정된 문서/스키마/UX 표면 정비 + `rag-v3` 응답언어 규칙 + eval `--config` facade 패치를 한 번에 묶어 cut.
|
|
|
|
---
|
|
|
|
## 핵심 변경
|
|
|
|
### 1. Ask 응답언어 자동 매칭 (rag-v3, Finding #1 + O-2)
|
|
|
|
**변경 사실**: `SYSTEM_PROMPT_RAG_V3` 신설. `rag-v2` 의 7규칙 위에 "답변 언어 = 질문 언어" 규칙 추가. config `[rag] prompt_template_version` 의 default 가 `"rag-v2"` → `"rag-v3"` 로 변경.
|
|
|
|
```bash
|
|
# v0.20.2 기본 동작
|
|
kebab ask "What is the tokenizer?" # → 영어 답변
|
|
kebab ask "토크나이저가 뭐야?" # → 한국어 답변
|
|
|
|
# 이전 v3 고정하려면 (이미 default, 명시 생략 가능)
|
|
# [rag] prompt_template_version = "rag-v3"
|
|
|
|
# rag-v2 로 pin 하면 legacy 동작 (query 언어 무시)
|
|
# [rag] prompt_template_version = "rag-v2"
|
|
```
|
|
|
|
도그푸딩 확인: 영어 query → 영어 답변, 한국어 corpus 를 영어로 물으면 근거를 영어로 번역해 답함 (trade-off — 원문 보존이 필요하면 큰따옴표 직접 인용 규칙이 적용됨).
|
|
|
|
**trade-off**: 한국어 corpus 를 영어로 물을 때 LLM 이 근거를 영어로 번역해 답하므로 원문 표현이 일부 달라질 수 있다. 원문 그대로 필요하면 큰따옴표 직접 인용 (`[#번호]` 앞에 chunk 속 원문) 이 v3 에서도 유지된다.
|
|
|
|
**Finding O-2 — refusal 언어중립화**: rag-v3 system prompt 의 refusal/hedge 규칙에서 한국어 리터럴 (`근거가 부족하면 "근거가 부족하다"고 답한다`) 을 언어중립 표현 (`근거가 부족하면 답변 언어로 근거가 부족함을 밝히고 [#번호] 인용 없이 답한다`) 으로 변경.
|
|
|
|
**Known limitation**: gemma4:e4b 같은 소형 모델은 refusal 메시지의 언어가 query 언어와 불일치할 수 있음 (영어 query → 한국어 refusal 가능). refusal 판정 자체는 답변의 citation marker (`[#번호]`) 유무 기반 — 유효 marker 가 없으면 `LlmSelfJudge` 로 refuse 판정 — 이라 문구 언어와 무관하게 정확함 (refusal 문구 텍스트는 판정에 쓰이지 않음).
|
|
|
|
**mitigation**:
|
|
- refusal 판정은 LLM 출력 내 marker 기반으로 항상 정상 동작.
|
|
- 언어 불일치가 허용 불가한 경우 `"rag-v2"` 로 pin (이전 동작 보존).
|
|
- 소형 모델 대신 gemma4:26b 등 대형 모델은 불일치 빈도가 현저히 낮음.
|
|
|
|
**upgrade 절차**: 기존 config 에 `prompt_template_version` 미명시 시 자동으로 v3 적용. v2 를 명시적으로 유지하려면 `[rag] prompt_template_version = "rag-v2"` 추가.
|
|
|
|
---
|
|
|
|
### 2. Bulk search input schema 확정 (Finding #2)
|
|
|
|
**변경 사실**: `docs/wire-schema/v1/bulk_search_input.schema.json` 이 15필드 완전 명세로 확정. `query` 누락 시 error shape hint 가 error.v1.message 에 포함됨.
|
|
|
|
```bash
|
|
# query 필수, 나머지 optional (15필드 중 1필수 + 14선택)
|
|
printf '%s\n' \
|
|
'{"query":"한국","mode":"lexical","k":3}' \
|
|
'{"query":"tokenizer","mode":"hybrid"}' \
|
|
'{}' \
|
|
| kebab search --bulk --json
|
|
# 세 번째 줄 → error.v1 (code: invalid_input, message 에 schema hint 포함)
|
|
```
|
|
|
|
**trade-off**: `query` 만 required, 나머지는 전부 optional + 스키마 검증은 agent 측 부담. error shape hint 로 agent 가 retry 없이 즉시 수정 가능.
|
|
|
|
**mitigation**: bulk cap 100 건 초과 시 즉시 `error.v1 (code: too_many_queries)` 반환.
|
|
|
|
**upgrade**: 기존 `{"query":"..."}` 형태는 완전 호환. 신규 optional 필드는 점진 추가 가능.
|
|
|
|
---
|
|
|
|
### 3. List docs human-readable path 보강 (Finding #3)
|
|
|
|
**변경 사실**: `kebab list docs` human-readable 출력이 `title \t doc_path` 에서 `doc_id \t title \t doc_path` 로 변경. title 중복(예: `README.md` + `README.md`) 시 doc_id 로 구분 가능.
|
|
|
|
```bash
|
|
kebab list docs
|
|
# 출력 예:
|
|
# abc123 README.md /path/to/java/README.md
|
|
# def456 README.md /path/to/kotlin/README.md
|
|
```
|
|
|
|
`--json` 출력 (`doc_summary.v1` array) 은 wire shape 변경 없음.
|
|
|
|
**trade-off**: human-readable 컬럼이 늘어나 좁은 터미널에서 wrap 가능. `--json` 으로 programmatic 처리 권장.
|
|
|
|
---
|
|
|
|
### 4. schema `index_version` 두 곳 구분 (Finding #7)
|
|
|
|
**변경 사실**: `schema.v1.models.index_version` 과 `search_hit.v1.index_version` 이 서로 다른 축임을 README 및 schema description 에 명시.
|
|
|
|
- `schema.v1.models.index_version` = **vector store (LanceDB)** version.
|
|
- `search_hit.v1.index_version` = **lexical (FTS5)** version.
|
|
|
|
cascade 에서 별도 추적 — 둘 중 하나가 변경돼도 나머지에 영향 없음.
|
|
|
|
**upgrade**: 기존 consumer 가 `index_version` 을 단일값으로 읽는 경우 fieldpath 확인 필요.
|
|
|
|
---
|
|
|
|
### 5. eval `--config` facade 패치 + 검색 품질 baseline 인프라
|
|
|
|
**변경 사실**: `kebab eval run / aggregate / compare` 가 `--config <path>` 를 honor. 이전에는 eval 이 XDG default config 만 읽어 dogfood KB (`/build/dogfood/config.toml`) 를 직접 평가할 수 없었음.
|
|
|
|
```bash
|
|
# v0.20.2: dogfood KB 에서 직접 eval
|
|
KEBAB_EVAL_GOLDEN=/build/dogfood/golden_queries.yaml \
|
|
kebab --config /build/dogfood/config.toml eval run --mode hybrid --k 10
|
|
```
|
|
|
|
**검색 품질 baseline** (v0.20.2, `/build/dogfood/golden_queries.yaml` 10 query):
|
|
|
|
| Mode | hit@1 | hit@3 | hit@10 | MRR | recall@10 | empty |
|
|
|------|-------|-------|--------|-----|-----------|-------|
|
|
| hybrid | 0.7 | **1.0** | 1.0 | **0.833** | 1.0 | 0 |
|
|
| lexical | 0.4 | 1.0 | 1.0 | 0.7 | 1.0 | 0 |
|
|
|
|
hybrid 가 vector 덕분에 top-1 정확도 우위. hit@3 이후는 두 모드 모두 완벽. 현재 ranking 조정 없이 달성 (`[[project_ranking_deferred]]` 결정 유효).
|
|
|
|
**golden 큐레이션 교훈**: 초기 dispatch.py 정답을 note 로만 한정한 것이 오류. eval 분해 시 vector 가 영어 docstring dispatch.py 를 top-1 으로 반환함을 발견, 정답에 추가 후 hit@3 0.9→1.0 개선. **교훈**: golden answer 는 "note 의 intent" 뿐 아니라 "합리적으로 관련된 모든 doc" 을 포함해야 함.
|
|
|
|
**trade-off**: `--config` 패치가 facade rule (kebab-cli → kebab-app `*_with_config`) 의 eval 적용. 누락 시 dogfood KB 와 XDG KB 의 결과가 섞여 eval 결과 오염.
|
|
|
|
**upgrade**: 기존 `kebab eval` 호출은 동작 변경 없음 (config 미명시 → XDG default 그대로).
|
|
|
|
---
|
|
|
|
### 6. 기타 docs/schema 정비 (Finding #4 · #5/#6 · #8)
|
|
|
|
**Finding #4 — doc.lang semantic 명시**: `lang = "und"` 는 소스코드 doc 의 정상 상태임을 README + schema description 에 명시. lingua 가 자연어 감지 대상 아님을 `lang_breakdown` 해석 가이드에 추가.
|
|
|
|
**Finding #5/#6 — fusion_score / score_kind**: `search_hit.v1` 의 `fusion_score` / `lexical_score` / `vector_score` / `lexical_rank` / `vector_rank` 가 모두 `retrieval` object 내부에 있음 (top-level 아님) 을 README 및 schema description 에 명시. 단일 mode (lexical/vector) 에서 `score == fusion_score == (lexical|vector)_score` 가 같은 값인 이유도 설명.
|
|
|
|
**Finding #8 — kebab init Ollama endpoint hint**: `kebab init` 이 생성하는 config.toml 에 `[models.llm] endpoint` 의 default 주석에 remote Ollama host 예시 추가.
|
|
|
|
---
|
|
|
|
## Version cascade 주의
|
|
|
|
| Version field | v0.20.1 | v0.20.2 |
|
|
|---|---|---|
|
|
| `prompt_template_version` default | `"rag-v2"` | **`"rag-v3"`** |
|
|
| wire schema shape | unchanged | unchanged (additive description only) |
|
|
| `eval config_snapshot_json` | `prompt_template_version: "rag-v2"` | `prompt_template_version: "rag-v3"` |
|
|
|
|
**eval compare 주의**: v0.20.1 eval run 과 v0.20.2 eval run 을 `eval compare` 할 때 `prompt_template_version` 이 다르므로 config_snapshot 이 다름. eval runner 가 snapshot 불일치를 경고하지 않으면 metric 변화의 원인이 rag 버전인지 corpus 변화인지 구분 불가. 정확한 비교는 `[rag] prompt_template_version = "rag-v2"` 로 pin 후 re-run.
|
|
|
|
---
|
|
|
|
## Breaking changes / 사용자 영향
|
|
|
|
- **prompt_template_version default 변경 (rag-v2 → rag-v3)**: 영어로 묻는 경우 영어로 답함. config 에 `prompt_template_version` 미명시한 사용자는 자동 적용. 이전 동작 유지는 `"rag-v2"` 명시.
|
|
- **소형 모델 refusal 언어 불일치 known limitation**: gemma4:e4b 사용자는 refusal 메시지 언어가 가끔 틀릴 수 있음. 정확도(판정)는 영향 없음.
|
|
|
|
---
|
|
|
|
## Upgrade 절차
|
|
|
|
```bash
|
|
# 1. binary 교체
|
|
git fetch && git checkout v0.20.2
|
|
cargo build --release -p kebab-cli -j 4
|
|
|
|
# 2. rag-v3 동작 확인
|
|
kebab ask "What is the tokenizer?" --hide-citations # 영어 응답 기대
|
|
kebab ask "토크나이저가 뭐야?" --hide-citations # 한국어 응답 기대
|
|
|
|
# 3. eval baseline 측정 (선택)
|
|
KEBAB_EVAL_GOLDEN=/build/dogfood/golden_queries.yaml \
|
|
kebab --config /build/dogfood/config.toml eval run --mode hybrid --k 10
|
|
```
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- HOTFIXES entry: [`tasks/HOTFIXES.md`](../../tasks/HOTFIXES.md) 2026-05-29
|
|
- DOGFOOD scenarios: [`docs/DOGFOOD.md`](../DOGFOOD.md) §3.6 + §10.2
|
|
- eval `--config` facade: CLAUDE.md "The facade rule"
|
|
- Golden queries: `/build/dogfood/golden_queries.yaml`
|
|
- Eval logs: `/build/dogfood/logs/eval-{hybrid,lexical}-v0.20.2.json`
|