
Ramblr는 안드로이드에서 화면 위 마이크를 한 번 눌러 말하고 다시 누르면, 입력 중인 칸에 다듬어진 문장을 넣는 오픈소스 받아쓰기 앱이다. 키보드를 바꾸거나 별도 전사 창에서 복사할 필요가 없다. 기기 안에서만 처리하는 오프라인 설정과, 사용자의 API 키로 클라우드 모델을 이용하는 설정을 고를 수 있다.
앱은 마이크 버튼 외에도 음성 입력 키보드, 안드로이드 접근성 버튼이나 음량 키, 빠른 설정 타일, 선택한 텍스트 정리 기능 등 다섯 가지 실행 방식을 제공한다. 일부 방식은 접근성 서비스를 요구하지 않는다.
| 실행 방식 | 접근성 필요 | 용도 |
|---|---|---|
| 화면 위 아이콘(기본값) | 예 | 어느 앱에서나 링을 눌러 녹음하고 다시 눌러 끝내는 일상적인 사용 |
| Ramblr Voice 키보드 | 아니요 | 접근성 기반 삽입이 차단된 앱이나 일반 입력기 방식이 편한 경우 |
| 시스템 제어(접근성 버튼·음량 키 길게 누르기) | 예 | 화면에 링을 띄우지 않고 직접 실행 |
| 빠른 설정 타일 | 예 | 알림 창에서 받아쓰기 시작 |
| 텍스트 선택 후 ‘Ramblr’ | 아니요 | 이미 작성한 텍스트 정리 |
실행 방식은 설정의 ‘Invocation’에서 고른다. 대부분은 동시에 사용할 수 있다.
화면 위 아이콘은 다른 앱 위에 떠 있는 마이크 링이다. 눌러 녹음을 시작하고 다시 눌러 멈추면, 현재 포커스가 있는 입력란에 정리된 텍스트를 삽입한다. 길게 누르면 처리 중에는 취소하고, 방금 삽입한 결과는 되돌리며, 대기 중에는 문체 빠른 메뉴를 연다. 대기 상태에서 아이콘은 화면 가장자리로 자동 숨김(peek) 처리할 수 있고, 숨김 시간과 크기·테두리·채움색·기호 색상·사용자 지정 아이콘 이미지를 설정할 수 있다.
키보드와 시스템 실행 방식
Ramblr Voice는 마이크와 진행 상태, 문체 선택, 간단한 편집 키 세 개, 키보드 전환 및 설정 바로가기를 갖춘 음성 전용 입력기다. 안드로이드 표준 InputConnection을 통해 텍스트를 넣으므로 접근성 서비스를 완전히 끈 상태에서도 작동한다. 마이크 옆 문체 칩에서 정리 기능을 켜거나 끄고 문체를 고를 수 있으며, 이 설정은 화면 위 아이콘의 길게 누르기 메뉴 및 설정과 동기화된다. 변경 사항은 다음 받아쓰기에 적용되고, 녹음이나 처리 중에는 칩을 사용할 수 없다.
편집 키는 백스페이스(선택 영역 또는 한 글자 삭제, 길게 누르면 반복), 스페이스, 엔터다. 여러 줄 입력란에서 엔터는 새 줄을 넣고, 한 줄 입력란에서는 검색·이동·완료·다음 등의 동작을 실행한다. 여러 줄 입력란을 제출하지 않으며, 받아쓰기가 진행 중일 때 엔터가 입력란을 벗어나게 하는 동작이면 받아쓰기 결과가 오기 전에 양식이 전송되지 않도록 막는다.
이 입력기는 완전한 키보드가 아니다. 글자·숫자·기호 배열, 스와이프 입력, 자동 교정, 추천, 클립보드, 이모지 화면은 없으며 편집 키는 받아쓰기 수정용이다. 일반 입력을 하려면 기존 키보드로 돌아가야 한다. 화면은 시스템의 밝은 테마와 어두운 테마를 따른다.
비밀번호·PIN·웹 비밀번호 입력란에서는 마이크가 비활성화된다. 안드로이드의 ‘개인화 학습 금지’ 플래그를 요청하는 편집기는 계속 사용할 수 있지만, 그 세션은 기록, 품질 로그, 어휘 제안에서 제외된다. 화면 위 아이콘도 비밀번호 입력란에 포커스가 있을 때 녹음을 시작하지 않고 ‘Ramblr는 비밀번호 입력란에 받아쓰지 않는다’고 표시한다. 받아쓰기 도중 입력란이 비밀번호 입력란으로 판명되면 결과를 삽입하거나 클립보드에 복사하거나 기록에 저장하지 않고 폐기한다.
접근성 버튼·제스처 또는 음량 버튼 두 개를 길게 눌러 실행하는 시스템 제어 방식은 화면 위 링 대신 안드로이드 시스템 제어를 사용한다. 이 방식에는 별도의 서비스 구성요소가 필요하다. 시스템 설정에서 마지막으로 연결된 Ramblr 바로 가기를 끄면 안드로이드의 INVISIBLE_TOGGLE 동작 때문에 서비스 자체도 비활성화된다. 앱 오류가 아니라 안드로이드 동작이며, Ramblr는 이를 감지해 복구 안내 배너를 표시한다. 음량 키 길게 누르기는 의도적으로 이 방식에서만 쓸 수 있다. 화면 위 아이콘 방식에 연결하면 음량 키가 받아쓰기가 아니라 서비스를 켜고 끄는 동작이 되기 때문이다.
고급 사용자는 ADB로 WRITE_SECURE_SETTINGS 권한을 부여하면 앱 안에서 시스템 연결을 즉시 바꿔 이 문제를 피할 수 있다. 이는 선택 사항이며, 권한을 주지 않으면 시스템 설정으로 이동하는 바로 가기를 제공한다.
adb shell pm grant com.trevornk.ramblr android.permission.WRITE_SECURE_SETTINGS
빠른 설정 타일을 누르면 받아쓰기를 시작하거나 멈춘다. 안드로이드 13 이상에서는 앱이 시스템에 타일 추가를 요청하고, 그보다 낮은 버전에서는 직접 추가하는 방법을 안내한다. 안드로이드는 타일이 실제로 추가됐는지 앱에 알려주지 않으므로 설정의 해당 항목은 실시간 상태 표시가 아니라 추가 방법 안내다.
텍스트를 선택한 뒤 안드로이드 선택 메뉴에서 Ramblr를 누르고 문체를 고르면 정리된 결과가 선택한 텍스트를 대체한다. 입력란이 읽기 전용이면 결과를 클립보드에 넣는다. 이 기능은 접근성 서비스를 전혀 사용하지 않으며, 화면을 읽지 않아도 작동한다.
설치와 받아쓰기 처리
앱은 Releases에서 APK를 받아 사이드로드하거나 소스에서 빌드할 수 있다. 실행 후 화면 위 아이콘을 쓰려면 접근성 서비스를 켜고, 접근성을 쓰지 않으려면 안드로이드 키보드 설정에서 Ramblr Voice를 활성화한다. 모든 방식에는 마이크 권한이 필요하다.
설정의 ‘Cloud’에서 처리 방식을 선택한다. ‘Local’은 기기 내 전사와 정리로 완전 오프라인이며, ‘Cloud’는 클라우드 전사와 정리를 이용한다. ‘Fastest’는 기기 내 전사와 클라우드 정리를 결합한다. Local이나 Fastest를 선택하면 ‘Transcription’에서 모델을 내려받고, Cloud나 Fastest를 선택하면 ‘Cloud’에서 API 키를 추가한다.
처리 흐름은 실행 후 음성 녹음, 기기 내 또는 클라우드 전사, 선택적 문장 정리, 현재 입력란에 삽입하는 순서다. 삽입에 실패하면 결과를 클립보드에 복사하고 안내 말풍선을 더 오래 표시해 누를 수 있게 한다.
전사와 정리는 각각 독립적으로 선택할 수 있어, 기기 내 전사와 클라우드 정리를 함께 쓸 수 있다.
| 제공자 | 전사 | 정리 | 설명 |
|---|---|---|---|
| 기기 내 처리 | 가능 | 가능 | sherpa-onnx STT와 llama.cpp 정리를 사용한다. API 키와 네트워크가 필요 없으며, 제공자 연결의 기본 대안으로 항상 사용할 수 있다. |
| OpenAI | 가능 | 가능 | 오디오 전사에는 gpt-transcribe, gpt-4o-transcribe, whisper-1을, 정리에는 채팅 모델을 쓴다. |
| Gemini | 가능 | 가능 | 멀티모달 오디오 입력을 이용하는 정식 전사 제공자이며 정리 전용이 아니다. |
| Anthropic | 불가 | 가능 | 오디오 입력 기능이 없어 정리만 지원한다. |
| 자체 호스팅 OpenAI 호환 게이트웨이 | 불가 | 가능 | 선택 기능이며 기본값은 꺼져 있다. 설명을 참고한다. |
기기 내 전사와 정리를 함께 쓰면 데이터가 기기 밖으로 나가지 않는다. 클라우드 모드에서도 요청은 사용자가 제공한 키를 이용해 휴대전화에서 제공자에게 직접 전송되며 중계 서버를 거치지 않는다. 앱에는 운영자가 관리하는 백엔드가 없다. 전체 정책은 PRIVACY.md에 있다.
정리 제공자는 설정의 ‘Cloud’에서 순서가 있는 체인으로 구성한다. 예를 들어 Gemini Flash를 시도하고 실패하면 OpenAI, Anthropic, 기기 내 처리 순으로 대체할 수 있다. 시간 초과, 2xx가 아닌 응답, 네트워크 오류가 나면 다음 단계로 넘어간다. 모든 단계가 실패하면 원본 전사 결과를 제공하므로 말한 내용이 조용히 사라지지 않는다.
- 같은 호스트에 연속으로 연결된 단계는 호스트가 응답하지 않을 때 각각 재시도하지 않고 한 단위로 실패 처리한다.
- 전체 체인의 제한 시간은 8초다. 34일간의 현장 데이터에서 관찰한 p99 3828ms를 바탕으로 설정했으며, 관찰된 실제 성공 사례를 중단하지 않도록 조정했다고 프로젝트는 설명한다.
- 단계별 제한 시간은 남은 전체 시간에 맞춰 줄어든다. 기기 내 처리에도 별도 시간 예산을 주어 느린 시도가 클라우드 대체 경로를 소진하지 않게 한다.
설계 근거는 docs/adr/0001-cleanup-waterfall.md에 공개돼 있다.
기기 내 모델과 문장 정리
기기 내 모델은 필요할 때 내려받으며 체크섬을 확인한 뒤 앱 전용 저장 공간에 둔다. APK에는 모델을 넣지 않는다.
| 모델 | 크기 | 설명 |
|---|---|---|
| Parakeet 0.6B (v3) | 487 MB | 권장 기본 모델, WER 2.38%, CC-BY-4.0, 25개 언어 |
| Parakeet Unified 0.6B | 501 MB | 가장 높은 품질( WER 1.67%); 비자유 NVIDIA 라이선스이며 동의가 필요하다. |
| Canary 180M Flash | 153 MB | 다국어(en/es/de/fr), 구두점 지원, WER 2.44% |
| Parakeet 110M | 104 MB | 가장 작고 빠르며 WER 3.02%; 때때로 의미 없는 토큰이 나온다. |
기기 내 정리의 기본 모델은 Mumble Cleanup 2-Stage다. 크기는 352 MB이고 Apache-2.0 라이선스이며, 대본 정리용으로 특별히 학습한 Qwen2.5-0.5B 파인튜닝 모델이다. 대안으로 219 MB 크기의 LFM2.5 350M도 쓸 수 있다. 실시간 미리보기에는 별도의 57 MB 스트리밍 모델을 사용한다.
기본 문체는 여섯 가지다. Natural은 전사기가 쉼을 기준으로 나눈 문장을 다시 잇고 군더더기를 없애며, 자기 수정 내용을 정리하되 말투·이름·숫자를 그대로 유지한다. Formal은 맞춤법·문법·구두점을 고치고 알려진 프로젝트명과 기술 용어를 바로잡으며 문장 구조를 1대1로 보존한다. 코딩과 짧은 기술 메모에 적합하다. Casual은 구두점·대문자 표기와 명백한 음성 인식 오류만 최소한으로 수정한다.
Notes & lists는 군더더기와 말의 시작 부분을 빼고 자기 수정을 최종 의도에 맞춰 정리한다. 항목을 나열하는 것이 분명하면 장황한 말을 문단이나 목록으로 재구성한다. Email은 인사말이나 맺음말을 임의로 만들지 않고 이메일 본문으로 다듬는다. Concise는 모든 사실을 유지하면서 말을 가장 짧게 줄인다.
사용자는 사용자 지정 문체를 만들거나 기본 문체를 복제해 수정할 수 있다. 기본 문체를 편집하면 원본을 바꾸지 않고 복사본이 생긴다. 길게 누르는 메뉴에 표시할 문체를 고르고 앱마다 별도 문체도 설정할 수 있다. 예를 들어 Gmail에는 Email, 메모장에는 Notes & lists를 지정할 수 있다. 앱별 문체는 기본적으로 기억되어 다음 사용 때 재사용된다.
Natural·Formal·Casual은 원래 표현을 유지하도록 설계됐다. 클라우드 정리 결과는 사용자가 말한 내용과 대조해 숫자, 이름, 길이, 새로 지어낸 표현 여부, 대화체 응답 여부를 확인한다. 검사에 실패하면 말하지 않은 문장을 입력하는 대신 원본 전사 결과를 넣고 정리를 건너뛰었다고 알린다.
개인 어휘에 프로젝트명이나 전문 용어를 등록하면 정리 과정에서 이를 반영한다. 기기 내 정리에서는 모델 처리 뒤 결정론적 후처리를 거쳐 비슷하게 잘못 인식된 말을 수정한다. 예를 들어 “clawed code”를 “Claude Code”로 바로잡는다. 작은 기기 내 모델에 긴 용어 목록을 프롬프트로 넣으면 목록을 출력으로 되풀이했기 때문에, 모델에 요청하는 대신 후처리 단계에서 고친다. 클라우드 정리는 용어 목록을 프롬프트에 넣어도 문제가 없으므로 그렇게 처리한다.
스마트 어휘 제안은 기본적으로 켜져 있다. 정리 과정에서 자주 고쳐지는 단어나 반복해서 등장하는 낯선 이름을 찾아 한 번 눌러 어휘에 추가하도록 제안한다. 단어가 서로 다른 날을 포함해 받아쓰기 세 건 이상에 나타나야 제안 대상이 된다. 후보 단어만 저장하고 전사 텍스트는 저장하지 않는다. 기능을 끄면 누적된 후보를 모두 삭제한다.
실시간 미리보기와 설정
Ramblr에는 서로 다른 두 가지 실시간 기능이 있다. 설정의 ‘Live Preview’는 기본적으로 꺼져 있으며, 대화 중 작은 기기 내 스트리밍 모델로 부분 텍스트를 보여준다. 화면 위 아이콘·접근성 방식과 키보드에서 쓸 수 있다. 최종 삽입 결과는 계속 일반 전사 파이프라인에서 나오며, 스트리밍은 말하는 동안 보이는 내용만 바꾼다. 이 기능은 항상 기기 내에서 작동하고 클라우드로 보내지 않는다. ‘Preview before inject’를 켜면 부분 결과를 실제 입력란 대신 화면 위 말풍선에 표시한다.
‘Gemini Cloud Live’는 설정에서 켤 수 있는 실험적 클라우드 스트리밍 방식이며 기본값은 꺼져 있다. 일괄 처리보다 비용이 약 1.8배 들고, 실제 기기 검증을 마치지 않았다. 현재 Ramblr Voice 키보드에만 연결돼 있다. 화면 위 아이콘 방식은 클라우드 중간 결과를 표시하지 않고 일반 일괄 처리로 전환된다. 관련 이슈는 #233과 #245다.
동작 설정에는 무음 뒤 자동 정지(기본값은 꺼짐, 기기 내 VAD, 기준 2.0초), 아이콘 자동 숨김 시간, 한 번 눌러 복원 후 녹음, ‘원본 텍스트를 쓰려면 누르기’ 되돌리기 말풍선(기본값은 켜짐), 삽입 전 미리보기, 앱별 문체 기억이 있다. 클라우드 전사용 오디오 압축 업로드는 기본적으로 꺼져 있다. 품질 영향이 아직 측정되지 않아 선택 기능으로 제공된다.
화면 위 아이콘 모양은 링 크기(44·56·76 dp 또는 사용자 지정), 테두리, 대기 상태 채움색, 투명도 슬라이더가 있는 기호 색상, 사용자 지정 아이콘 이미지로 조절할 수 있다. 녹음 및 전사 중 색상은 실제 상태를 나타내므로 고정돼 있다.
기록 설정은 최근 받아쓰기 50건을 기기에 보관한다. 기본적으로 켜져 있으며, 삽입 실패를 복구할 때 쓸 수 있다. 항목을 누르면 복사하고 길게 누르면 삭제한다. 벤치마크 로그에는 처리 시간과 모델 ID를 기록하지만 전사 텍스트는 넣지 않는다. 별도의 품질 로그는 A/B 검토를 위해 실제 사용자의 말을 저장하므로 기본값은 꺼져 있고 백업에서도 제외된다. 수동 백업·복원은 기록, 벤치마크 로그, 설정을 ZIP 파일로 내보낸다. API 키는 암호화가 기기 키 저장소에 묶여 있어 다른 기기에서 복호화할 수 없으므로 내보내지 않는다.
기기 내 정리는 소리 내어 말한 숫자도 정규화한다. 예를 들어 “four thirty”를 “4:30”으로 바꾼다. 다만 “half”나 “a dozen”처럼 뜻이 모호한 수량 표현은 그대로 두도록 보수적으로 처리한다.
호환성, 개인정보와 제한
Ramblr는 안드로이드의 표준 텍스트 입력란을 사용하는 앱에서 가장 잘 작동한다. 일부 앱은 사용자 지정 입력 화면이나 터미널 형태를 사용해 직접 삽입이 되지 않는다. 이런 경우 클립보드로 대체하고 말풍선을 계속 표시해 누를 수 있게 한다. 일부 앱은 붙여넣기나 텍스트 삽입 자체를 막는다. 테스트한 앱 목록은 없다. 각 앱을 실제 기기에서 써봐야 정확한 목록을 만들 수 있어 #5에서 추적 중이다. 목록에 앱이 없다고 해서 지원 또는 미지원이라고 판단해서는 안 된다.
- 화면 위 아이콘에는 접근성 권한이 필요하지만 Ramblr Voice 키보드와 텍스트 선택 통합에는 필요하지 않다.
- 기기 내 모델은 57~501 MB로 내려받기 용량이 크다.
- 클라우드 방식에는 사용자의 API 키가 필요하다.
- Ramblr Voice는 글자·숫자·기호 입력, 스와이프, 이모지를 제공하는 대체 키보드가 아니다.
- 녹음은 최대 10분이며 300ms보다 짧은 클립은 업로드하지 않고 폐기한다.
- Google Play 배포는 평가되지 않았다. 접근성 앱은 해당 스토어에서 실제 정책 심사를 받으므로 #99를 참고한다. 현재는 사이드로드하거나 소스에서 빌드할 수 있다.
안드로이드 11(API 30) 이상이 필요하다.
Termux의 터미널 영역은 표준 텍스트 입력란이 아니다. 추가 키 행(ESC, CTRL, 화살표)을 좌우로 밀어 Termux 자체 입력 칸에 접근해 받아쓰면, Termux가 내용을 터미널에 전달한다.
접근성 권한은 앱 사이에서 포커스가 있는 입력란에 받아쓰기 텍스트를 삽입하기 위해서만 쓴다. Ramblr의 onAccessibilityEvent 처리기는 비어 있으며 화면을 읽거나 백그라운드 자동화를 하지 않는다. 마이크를 누를 때만 동작한다. 키보드 및 텍스트 선택 방식은 접근성 서비스를 전혀 사용하지 않는다.
예외는 사용자가 직접 켜는 설정이다. ‘Behavior → Show the icon → Only while the keyboard is open’ 또는 ‘While a text field is selected’를 선택하면 텍스트 입력란이나 키보드가 표시되는지 확인하기 위해 창과 포커스 변화를 감지한다. 화면에 무엇이 표시되는지는 읽지 않는다. 기본값인 ‘Always’에서는 추가로 아무것도 감지하지 않으며, ‘Always’로 되돌리면 감지를 즉시 중단한다.
빌드와 배포
소스에서 빌드하려면 JDK 17과 Android SDK가 필요하다.
git clone --recursive https://github.com/trevornk/ramblr.git && cd ramblr
make build
--recursive 옵션이 중요하다. 기기 내 정리 빌드에 필요한 llama.cpp가 서브모듈이기 때문이다. 서브모듈 없이 이미 복제했다면 다음을 실행한다. sherpa-onnx 네이티브 라이브러리는 자동으로 내려받는다.
git submodule update --init --recursive
make build # debug APK -> app/build/outputs/apk/github/debug/
make test # JVM 단위 테스트, 기기 불필요
make adb-install # 빌드 후 ADB로 설치
make clean
배포 빌드에는 두 가지 변형이 있다. github가 기본값이며 Releases에서 제공하는 사이드로드 빌드다. 자체 업데이트 기능은 이 변형에만 있다. storefront는 Google Play와 F-Droid용이며, 자체 업데이트 코드를 실행 중 비활성화하는 것이 아니라 컴파일된 클래스에서 완전히 제외한다.
make build FLAVOR=storefront
Google Play 정책은 앱이 자체 메커니즘 밖에서 스스로 업데이트하는 것을 금지하고, F-Droid는 업데이트를 클라이언트가 관리한다고 보기 때문에 두 배포판을 분리했다. 자체 업데이트 코드를 별도 소스 세트에 두어 스토어용 빌드에서는 해당 정책을 위반할 수 없게 한다.
github 변형의 자체 업데이트는 6시간마다 Releases를 확인한다. 기본값은 알림이며, 선택적으로 자동 설치를 켤 수 있다. 자동 설치는 기본적으로 꺼져 있고 릴리스 파일의 SHA-256 확인, 설정된 조용한 시간(기본 새벽 1~5시), 서비스가 실제로 대기 상태인지 확인하는 절차를 거친다. 대기 상태는 약 30초 간격으로 두 번 확인하므로 받아쓰기 도중 설치되지 않는다.
실제 릴리스 노트에는 GitHub Releases가 안드로이드 versionCode를 기본 제공하지 않으므로 versionCode: <int> 형식의 줄이 있어야 한다.
저장소는 녹음·전사·정리 상태를 두 받아쓰기 인터페이스가 공유하는 DictationRuntime.kt, 화면 위 오버레이와 접근성 삽입을 담당하는 WhisperAccessibilityService.kt, InputConnection으로 내용을 확정하는 음성 전용 입력기 RamblrImeService.kt, 앱 간 텍스트 정리를 맡는 ProcessTextActivity.kt 등으로 구성된다. SystemControlsAccessibilityService는 WhisperAccessibilityService의 빈 하위 클래스다. flagRequestAccessibilityButton은 정적 XML에서만 설정할 수 있고 실행 중 변경할 수 없어 두 클래스가 존재한다.
제공자 목록과 실행은 ProviderChain.kt, ProviderChainRuntime.kt, ProviderChainStore.kt가 맡는다. 각 기능은 그 목록에서 기능과 설정을 갖춘 첫 제공자를 사용한다. CleanupWaterfallExecutor.kt는 호스트별 묶음 실패 처리와 마감 시간 조정을 담당한다. PostProcessor.kt는 OpenAI 호환 채팅 완성 클라이언트와 기본 문체 프롬프트를 담는다. 문체 시스템은 CleanupPersona.kt, PersonaRegistry.kt, CustomPersonaStore.kt, QuickMenuPersonaStore.kt로 구성된다. 모델 목록·다운로드·체크섬 검증은 ModelDownloader.kt, ModelCatalogStore.kt가 담당하고, 기기 내 처리 바인딩은 LocalTranscriber.kt, LlamaCppInference.kt에 있다. 아키텍처 결정 기록은 docs/adr/에 있으며 모든 설정 액티비티는 AndroidManifest.xml에서 확인할 수 있다.
테스트와 평가 도구
make test는 에뮬레이터나 기기 없이 JVM 단위 테스트 전체를 실행한다. 제공자 체인, 정리 워터폴, 모델 카탈로그, 문체 시스템, 받아쓰기 상태 머신을 다룬다. 이 커밋 기준으로 테스트는 166개 클래스에 걸쳐 1,751개가 모두 통과했다고 프로젝트는 설명한다. CI는 main 브랜치에 푸시하거나 PR을 열 때 같은 테스트와 assembleDebug를 실행한다.
PostProcessorTest.kt는 JSON 파싱을 검사할 뿐 출력 품질을 검사하지 않는다. 정리 프롬프트를 나란히 비교하는 수동 도구는 app/src/test/kotlin/com/trevornk/ramblr/tools/EvalHarness.kt에 있다. 독립 실행 main()이며 JUnit 테스트가 아니다. make test 때 컴파일되지만 실행되지는 않는다. 예시 자료는 app/src/test/resources/eval_samples/에 있으며 합성 받아쓰기 23건과 사람 기준 정리를 담은 NOTES.md가 포함된다.
이 도구는 실제 제공자 API를 호출해 비용이 발생한다. 직접 실행할 때 본인의 키를 환경 변수로 설정한다. .env 파일은 읽지 않으며 키를 저장소에 커밋해서는 안 된다.
export OPENAI_API_KEY=sk-... # never commit this; no .env is read
export ANTHROPIC_API_KEY=sk-ant-... # optional — omit to skip
export GEMINI_API_KEY=AIza... # optional — omit to skip
./gradlew runEvalHarness
./gradlew runEvalHarness --args="DEV_PROMPT,SIMPLE_PROMPT,STRUCTURED_PROMPT"
제공자별 모델 목록은 OPENAI_EVAL_MODELS, ANTHROPIC_EVAL_MODELS, GEMINI_EVAL_MODELS로 바꿀 수 있다. 실행할 때마다 사람이 결과를 비교할 수 있도록 git에서 제외된 eval-reports/에 마크다운 보고서를 쓴다. 도구는 결과를 자동 채점하지 않는다. 새 변형은 EvalHarness.kt의 PROMPT_REGISTRY에 추가한다.
별도 수동 벤치마크인 app/src/test/kotlin/com/trevornk/ramblr/tools/GeminiTranscriptionBenchmark.kt는 Gemini가 오디오를 텍스트로 바꾸는 전사 단계를 측정하는 독립 실행 main()이다. 점수 계산, 매니페스트 검증, WAV에서 PCM으로 변환하는 TranscriptionMetrics.kt, TranscriptionEvalManifest.kt, WavPcm.kt는 CI에서 실제 단위 테스트를 실행한다.
테스트 자료 디렉터리 app/src/test/resources/transcription_eval/은 비어 있는 상태로 배포된다. 자료를 추가하기 전에 그 안의 README.md를 읽어야 한다. 이 벤치마크는 각 자료·대상·반복마다 실제 Gemini API를 호출한다. 클라이언트 계약에 사용량 메타데이터가 없으므로 비용을 추정하지 않는다.
export GEMINI_API_KEY=AIza...
./gradlew runGeminiTranscriptionBenchmark
GEMINI_TRANSCRIPTION_ENGINES=generateContent,interactions ./gradlew runGeminiTranscriptionBenchmark
GEMINI_TRANSCRIPTION_DELAY_MS=7500 ./gradlew runGeminiTranscriptionBenchmark # pace under quota
GEMINI_TRANSCRIPTION_REPETITIONS=3 ./gradlew runGeminiTranscriptionBenchmark # expose variance
GEMINI_TRANSCRIBE_MODES에는 verbatim과 smart를 지정할 수 있다. 원시 음성 인식의 A/B 결과에는 verbatim을 쓴다. smart는 군더더기를 빼고 문장을 다듬으므로 별도 축으로 보고하며 원시 음성 인식 결과처럼 비교해서는 안 된다.
벤치마크는 가능한 범위에서 실제 제품의 동작을 맞춘다. 실제 받아쓰기와 같은 프롬프트를 보내며, 기본 어휘도 포함한다. GEMINI_TRANSCRIPTION_VOCABULARY로 어휘를 바꿀 수 있고, 인라인 오디오 크기 제한도 동일하게 적용한다. 다만 실제 기기가 아니라 JVM에서 WAV 자료를 사용하고, 압축 업로드 경로를 실행하지 않으며 비용도 보고하지 않는다. 보고서(Markdown과 JSON)는 커밋 SHA, 시각, WER/CER, 성공률, 지연시간 백분위와 함께 git에서 제외된 eval-reports/에 저장된다. 키가 없거나 모델 목록이 비었거나 자료가 잘못되면 기본값으로 조용히 대체하지 않고 실행을 거부한다.
선택형 게이트웨이와 기여
Ramblr는 자체 호스팅 OpenAI 호환 게이트웨이로 정리를 보낼 수 있다. 이 기능은 선택 사항이며 저장소에 기본 엔드포인트가 포함돼 있지 않다. 사용하려면 커밋하지 않는 local.properties에 주소를 설정한다.
# local.properties (never committed)
OMNIROUTE_BASE_URL=https://your-gateway.example/v1
설정하지 않으면 제공자 선택 화면에 해당 옵션이 나타나지 않는다. 구현은 app/src/main/kotlin/com/trevornk/ramblr/OmniRoute.kt에 있다.
이슈와 PR을 환영한다. 기여 전에는 JDK 17을 지정하고 테스트와 빌드를 실행한다.
export JAVA_HOME=/path/to/jdk-17
./gradlew testGithubDebugUnitTest
./gradlew assembleGithubDebug
변경 범위를 좁게 유지하고 새 로직에는 테스트를 추가하거나 기존 테스트를 갱신해야 한다. 테스트 관례는 app/src/test/kotlin/com/trevornk/ramblr/에서 확인할 수 있다. 각 동작을 하나씩 다루는 이름이 분명한 작은 @Test 함수를 쓰고, 순수 로직은 Robolectric 없이 시험할 수 있도록 안드로이드 클래스에서 분리한다.
전사에는 Apache-2.0 라이선스의 sherpa-onnx, 기기 내 정리에는 MIT 라이선스의 llama.cpp를 서브모듈로 사용한다. 제3자 저작물 전체 목록은 NOTICE에 있다. Ramblr는 GPLv3 라이선스이며 LICENSE와 NOTICE를 참고할 수 있다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요