On-device text retrieval

Semantic Text Search 구현 구조

Sprint 2 Flutter 앱의 자연어 검색은 keyword-only 검색에서 MobileCLIP2-S3 text encoder embedding + SQLite keyword score를 결합하는 semantic_text_fusion 구조로 확장한다.

현재 결론

앱은 사용자 query를 OpenCLIP tokenizer와 text encoder ONNX로 512차원 embedding으로 바꾼다. 이 embedding을 미리 생성해 둔 text_index.json의 catalog embedding과 cosine similarity로 비교하고, SQLite LIKE 기반 keyword score를 함께 반영해 Top-3를 만든다.

final_text_score = 0.75 * semantic_score + 0.25 * keyword_score

App main 확인 기준: lpcvc-2026-CNU/App@598da589에서 SemanticTextSearchService, TokenizerService, TextIndexRepository, LocalApiClientImpl 연결이 반영되어 있다. 이전의 keyword-only 설명은 구현 전 계획 기록으로만 남긴다.

Runtime Flow

사용자 query
  -> KeywordSearchService(SQLite LIKE)
  -> TokenizerService(tokenizer_bundle.json)
  -> OnnxInferenceService.extractTextEmbedding(text_tokens)
  -> TextIndexRepository(text_index.json)
  -> SemanticTextSearchService(cosine + fusion policy)
  -> LocalApiClientImpl top3/decision/log
  -> TextSearchScreen result/detail link

App Services

파일 역할
lib/services/tokenizer_service.dart tokenizer_bundle.json을 읽어 Python OpenCLIP tokenizer와 같은 token ids를 만든다.
lib/services/onnx_inference_service.dart image encoder와 text encoder session을 모두 로드하고, text_tokens: [1,77] int64 입력을 실행한다.
lib/data/text_index_repository.dart 검색 대상 catalog text embedding을 읽고 L2 normalize한다.
lib/services/keyword_search_service.dart SQLite LIKE로 name/alias/candidate_text keyword score를 계산한다.
lib/services/semantic_text_search_service.dart semantic score, keyword score, policy threshold를 결합해 Top-3와 decision을 만든다.
lib/api/local_api_client_impl.dart 텍스트 검색 request를 semantic service로 보내고 로그에 backend, score, margin, decision을 남긴다.

Required Artifacts

assets/mobile_artifacts_fp16/
  tokenizer_bundle.json
  text_index.json
  text_search_policy.json
  text_query_regression_set.json
  text_search_eval_report.json

위 파일들은 대용량 ONNX와 별개로 Git에서 추적 가능한 semantic search metadata다. 대용량 .onnx, .onnx.data는 Git에 넣지 않고 Google Drive 또는 D드라이브 source에서 sync한다.

Main Branch Evaluation Snapshot

아래 수치는 assets/mobile_artifacts_fp16/text_search_eval_report.json 기준이다. regression set은 전체 query 동작 확인용이며, 실제 사용자 로그가 쌓이면 threshold와 query set을 다시 보정한다.

항목
전체 query 184개
감독 query / OOS query 179개 / 5개
Top-1 accuracy 98.88%
Top-3 recall 100.00%
Out-of-scope accuracy 100.00%
한국어 query 125개, Top-1 98.40%, Top-3 100.00%
영어 query 54개, Top-1 100.00%, Top-3 100.00%
query type alias 75개 Top-1 97.33%, description 52개 Top-1 100.00%, official_name 52개 Top-1 100.00%

Decision Policy

조건 decision reason
Top-1 점수가 out_of_scope_threshold 미만 out_of_scope text_top1_below_oos
keyword hit가 없고 Top-1 점수가 no_keyword_match_threshold 미만 out_of_scope no_keyword_and_score_below_match
keyword hit가 없고 margin이 no_keyword_oos_margin 미만 out_of_scope no_keyword_and_margin_low
margin이 ambiguous_margin 미만 ambiguous text_margin_low
Top-1 점수가 matched_threshold 이상 matched text_score_high

Validation

Known Limits

PC/Flutter test에서는 tokenizer, asset, service wiring을 검증했다. Android 실기기에서 ONNX Runtime text encoder가 실제로 추론되는지는 별도 QA 단계로 남긴다. 또한 GitHub main에는 대용량 ONNX 파일이 들어 있지 않으므로, binary artifact를 배치하지 않은 checkout에서는 contract script가 missing file을 보고하는 것이 정상이다. 또한 OOS threshold는 현재 regression set 기준으로 보정했으므로, 실제 사용자 query 로그가 쌓이면 다시 calibration해야 한다.