Bluetape4k Text Part 1: 텍스트 처리의 시작점과 품질 기준

이 글은 bluetape4k-text 시리즈의 1편입니다. 이번 글에서는 전체 지도를 먼저 봅니다. 한국어 토큰화, 일본어 토큰화,
언어 감지, Aho-Corasick 검색이 각각 어디에 쓰이는지, 그리고 이 라이브러리가 품질을 어떤 방식으로 확인하는지 정리합니다.
텍스트 처리는 처음에는 단순합니다. trim(), lowercase(), split(" "), contains()만으로도 제법 많은 일을
처리합니다. 문제는 그 코드가 실제 서비스의 입력 경계로 들어오는 순간부터입니다. 한국어와 일본어가 섞이고, 이모지만 들어오며,
금칙어 사전이 커지고, 사용자가 매우 긴 문자열을 보내면 단순한 문자열 처리가 아니라 운영 안정성 문제가 됩니다.
bluetape4k-text는 그 지점을 다룹니다. 거대한 NLP 플랫폼을 만들자는 이야기가 아닙니다. Kotlin/JVM 서비스에서 자주
필요한 텍스트 처리 기본 도구를 모듈로 나누고, 예제와 품질 게이트로 최소한의 기준선을 잡아두자는 쪽에 가깝습니다.
무엇을 해결하나
섹션 제목: “무엇을 해결하나”서비스에서 텍스트 처리가 필요한 상황은 대체로 아래처럼 나뉩니다.
| 상황 | 필요한 처리 | bluetape4k-text 모듈 |
|---|---|---|
| 한국어 검색어/문장 처리 | 정규화, 형태소 분석, 구 추출, 어간 추출, 문장 분리 | tokenizer-korean |
| 일본어 검색어/문장 처리 | Kuromoji 기반 형태소 분석, 명사 필터링, 금칙어 처리 | tokenizer-japanese |
| 다국어 입력 라우팅 | 입력 언어 감지, 혼합 언어 감지, 감지기 재사용 | lingua |
| 금칙어·키워드 다중 검색 | 사전 기반 다중 키워드 검색, 마스킹, Flow 기반 검색 | text-search |
| HTTP/API 입력 경계 | 공백·과도하게 큰 입력 거부, 원문 노출 방지 | 전송 계층과 tokenizer-core |
여기서 기준은 “모든 텍스트 문제를 한 모듈로 해결한다”가 아닙니다. 서비스가 지금 해결해야 하는 경계를 먼저 보고,
그 경계에 맞는 모듈만 고르면 됩니다. 한국어 입력만 토큰화하면 tokenizer-korean이면 충분합니다. 금칙어 사전 기반
검색만 필요하면 text-search부터 보면 됩니다. 언어 감지만 필요하면 토크나이저 모듈을 추가할 이유가 없습니다.
처리 흐름으로 보면 더 쉽다
섹션 제목: “처리 흐름으로 보면 더 쉽다”실제 서비스에서는 이 기능들이 보통 한 흐름으로 붙습니다.

예를 들어 사용자 입력을 검색 색인에 넣거나 금칙어를 필터링하는 서비스를 생각해 봅시다. 먼저 HTTP 서버에서 본문 크기를
제한해야 합니다. String이 만들어진 뒤에는 서비스가 공백 여부와 문자 수를 확인합니다. 그다음 입력을 정규화하고, 필요한
경우 언어를 감지합니다. 한국어와 일본어 입력은 토크나이저로 검색에 사용할 토큰을 만들고, 금칙어 사전은 text-search로
한 번에 검색합니다.
이 경계를 정하지 않으면 처리가 여러 계층으로 흩어집니다. 컨트롤러에서 String.contains를 호출하고, 서비스에서 다시
소문자로 변환하며, 데이터 접근 계층 근처에서 또 정규화하는 식입니다. 그러면 같은 입력이 경로에 따라 다르게 처리되고,
정책 변경과 장애 분석도 어려워집니다.
모듈은 이렇게 나뉜다
섹션 제목: “모듈은 이렇게 나뉜다”bluetape4k-text 저장소는 아래 모듈로 구성됩니다.
| 모듈 | 역할 |
|---|---|
tokenizer-core | TokenizeRequest, BlockwordRequest, Severity, 사전 유틸리티, 길이 제한 등 공통 모델 |
tokenizer-korean | KoreanProcessor 퍼사드: 정규화, 토큰화, 구 추출, 어간 추출, 문장 분리, 금칙어 마스킹 |
tokenizer-japanese | JapaneseProcessor 퍼사드: Kuromoji IPAdic 토큰화, 명사 필터링, 금칙어 감지와 마스킹 |
lingua | Lingua 기반 감지기 팩터리와 detectAllLanguagesOf 등 Kotlin 확장 함수 |
text-search | AhoCorasickAutomaton, DSL 빌더, 치환, 단어 경계, Kotlin Flow 기반 검색 |
bluetape4k-text-bom | 여러 텍스트 아티팩트의 버전을 일치시키는 BOM |
의존성도 모듈 단위로 가져갑니다.
dependencies { implementation("io.github.bluetape4k.text:tokenizer-korean:<version>") implementation("io.github.bluetape4k.text:tokenizer-japanese:<version>") implementation("io.github.bluetape4k.text:lingua:<version>") implementation("io.github.bluetape4k.text:text-search:<version>")}처음부터 전부 넣을 필요는 없습니다. 텍스트 처리는 라이브러리보다 사전과 입력 특성의 영향을 많이 받습니다. 작게 붙이고, 실제 입력으로 확인하면서 넓혀가는 편이 낫습니다.
코드로 보는 작은 시작점
섹션 제목: “코드로 보는 작은 시작점”한국어 처리는 KoreanProcessor 퍼사드에서 시작합니다.
val normalized = KoreanProcessor.normalize("안됔ㅋㅋㅋㅋㅋ")// "안돼ㅋㅋㅋ"
val tokens = KoreanProcessor.tokenize("주말특가 쇼핑몰")val surfaces = KoreanProcessor.tokensToStrings(tokens)// ["주말", "특가", "쇼핑몰"]일본어 모듈은 Kuromoji 기반 토큰화와 명사 필터링을 제공합니다.
val tokens = JapaneseProcessor.tokenize("私は、日本語の勉強をしています。")val nouns = JapaneseProcessor.filterNoun(tokens).map { it.surface }// ["私", "日本語", "勉強"]언어 감지기는 매번 만들지 않고 재사용하는 것이 기본입니다. 모델을 불러오는 비용이 있기 때문입니다.
val detector = languageDetectorOf( languages = setOf(Language.ENGLISH, Language.KOREAN, Language.JAPANESE), minimumRelativeDistance = 0.0, isEveryLanguageModelPreloaded = true,)
val languages = detector.detectAllLanguagesOf("Hello service. 안녕하세요. こんにちは。")// setOf(Language.ENGLISH, Language.KOREAN, Language.JAPANESE)금칙어·위험 키워드가 많아지면 contains()를 키워드 수만큼 반복하기보다 오토마톤을 한 번 만들고 입력을 한 번 훑는 편이
낫습니다. 이 주제는 Part 3에서 더 자세히 다룹니다.
val automaton = ahoCorasick<String> { ignoreCase = true wordBoundary = WordBoundary.WHITESPACE_SEPARATED keyword("password reset", "ACCOUNT_TAKEOVER") keyword("card declined", "PAYMENT_RISK")}
val matches = automaton.parseText("user requested password reset before card declined")입력 경계가 먼저다
섹션 제목: “입력 경계가 먼저다”텍스트 처리 라이브러리를 적용할 때 자주 놓치는 부분이 입력 경계입니다. 토크나이저나 언어 감지기는 처리 비용이 클 수 있습니다. 그러므로 고비용 처리에 진입하기 전에 입력 크기와 공백 여부를 검증해야 합니다.
tokenizer-safety-examples는 이 경계를 작게 보여줍니다.
private fun validate(text: String, maxLength: Int): SafetyResponse? { if (text.length > maxLength) { return SafetyResponse(413, "text too long: ${text.length} chars (max $maxLength)") } if (text.isBlank()) { return SafetyResponse(400, "text is blank") } return null}다만 이 검사는 이미 메모리에 만들어진 String을 대상으로 합니다. 과도하게 큰 HTTP 본문이 문자열로 변환되는 과정까지
막지는 못하므로, 서버의 요청 본문 제한을 전송 계층에 먼저 설정해야 합니다. 이후 TokenizeRequest와
BlockwordRequest, 또는 위 예제의 서비스 검사가 문자 수와 공백 여부를 다시 확인합니다.
오류 메시지도 원문을 포함하지 않아야 합니다. 응답에는 실제 길이와 최대값만 담습니다. 검색어, 채팅 메시지, 신고 본문에는 민감한 내용이 포함될 수 있으므로, 원문을 반환하면 오류 응답과 로그·모니터링 시스템으로 노출 범위가 확대됩니다.
품질 기준은 벤치마크가 아니라 릴리스 게이트다
섹션 제목: “품질 기준은 벤치마크가 아니라 릴리스 게이트다”bluetape4k-text 0.2.0의 품질 문서는 의도적으로 과장하지 않습니다. 외부 NLP 시스템과 정확도를 겨루는 벤치마크가
아닙니다. 저장소 안에서 재현 가능한 fixture를 두고, 릴리스 전에 반드시 깨지지 않아야 할 동작을 고정한 게이트입니다.
| 영역 | 확인하는 내용 |
|---|---|
| 한국어 토크나이저 | 한국어/일본어 혼합 문장에서 기대한 한국어 표층형 토큰이 유지되는지 |
| 일본어 토크나이저 | 한국어/일본어 혼합 문장에서 기대한 일본어 표층형 토큰이 유지되는지 |
| 언어 감지 | 영어/한국어/일본어 혼합 입력과 이모지만 있는 미확정 입력을 구분하는지 |
| 입력 안전성 | 과도하게 큰 요청의 오류 메시지에 사용자 원문이 포함되지 않는지 |
예를 들어 한국어 fixture는 이런 입력을 다룹니다.
"서울에서 東京까지 주말 특가 티켓" to listOf("서울", "에서", "까지", "주말", "특가", "티켓")일본어 fixture도 같은 종류의 혼합 입력을 봅니다.
"서울から東京まで週末チケット" to listOf("東京", "週末", "チケット")언어 감지 픽스처는 "Hello 안녕 こんにちは"를 영어, 한국어, 일본어 집합으로 검증합니다. 이모지만 있는 입력은 빈 집합으로
처리합니다. 이 검증은 릴리스 과정에서 실제 서비스가 자주 접하는 경계 동작을 놓치지 않도록 합니다.
품질 문서의 표현이 중요한 이유도 여기에 있습니다. 이 게이트는 “대규모 말뭉치에서 최고 정확도”를 주장하지 않습니다. “Kotlin 서비스에서 채택할 때 최소한 이 입력들은 안정적으로 처리한다”를 확인합니다. 둘은 다릅니다. 전자를 주장하려면 완전히 다른 벤치마크와 데이터셋이 필요합니다.
사전 업데이트도 운영 작업이다
섹션 제목: “사전 업데이트도 운영 작업이다”텍스트 처리는 코드만으로 끝나지 않습니다. 사전이 바뀌면 결과가 바뀝니다. 금칙어가 추가되면 사용자 경험도 바뀝니다. 따라서 사전 갱신 계획은 아래 절차를 정의합니다.
| 단계 | 내용 |
|---|---|
| 1 | 담당 모듈의 리소스 루트에 있는 UTF-8 텍스트 파일 수정 |
| 2 | 각 줄의 공백 정리와 빈 줄 제거 |
| 3 | 원본 순서가 의미 없으면 결정적인 순서로 정렬 |
| 4 | 중복 제거 |
| 5 | 해당 모듈 테스트 실행 |
| 6 | 릴리스 노트에 변경 영역과 검증 명령 기록 |
사전 변경은 단순한 데이터 수정으로 끝나지 않습니다. 토큰화 결과와 금칙어 마스킹이 바뀌므로, 운영에서는 코드 변경과 같은 수준으로 검증하고 기록해야 합니다.
어디에 쓰나
섹션 제목: “어디에 쓰나”Part 1의 결론은 단순합니다.
| 서비스 문제 | 시작 모듈 |
|---|---|
| 한국어 검색 전처리 | tokenizer-korean |
| 일본어 검색 전처리 | tokenizer-japanese |
| 다국어 라우팅 | lingua |
| 금칙어 필터링 | text-search 또는 토크나이저의 금칙어 처리 기능 |
| API 입력 방어 | 전송 계층의 본문 제한, tokenizer-core 요청 모델, 안전성 예제 |
| 여러 텍스트 아티팩트 버전 정렬 | bluetape4k-text-bom |
String.contains와 split이 항상 나쁜 것은 아닙니다. 작은 내부 도구라면 충분할 때도 많습니다. 다만 그 코드가 사용자 입력,
다국어 문장, 금칙어 사전, 검색 색인, 로그·모니터링 경계로 들어오기 시작하면 기준이 필요합니다.
bluetape4k-text는 그 기준을 모듈과 예제로 나눠 제공합니다. 다음 글에서는 한국어·일본어 토크나이저와 Lingua 언어 감지를
조금 더 구체적으로 보겠습니다.
- bluetape4k-text README
- 한국어 README
- kotlin/text-processing 워크숍
- TokenizerSafetyExamples
- LinguaExamples
- TextSearchExamples
시리즈
섹션 제목: “시리즈”- Part 1: 텍스트 처리의 시작점과 품질 기준
- Part 2: 토크나이저와 언어 감지로 입력을 라우팅하기
- Part 3: Aho-Corasick 검색과 워크숍 시나리오
- Part 4: 사전과 금칙어 변경을 테스트로 다루기
댓글
GitHub 계정으로 의견을 남기거나 reaction을 남길 수 있습니다.