고영민mandu05.com

둘러보기

  • 홈
  • 글
  • 프로젝트
  • 지식 그래프
  • 이력서
본문으로 건너뛰기
  1. 고영민
  2. /
  3. 글
고영민/글
© 2023-2026 Youngmin. All rights reserved.
rhdudals0505@naver.comGitHubLinkedIn
이 사이트는 Next.js로 만들었습니다.갱신 2026.09.26
글/프로젝트 · 해커톤 · 대회 · 사이드 프로젝트

[사이드 프로젝트] 'Jev 호환'이라는 말에 스펙이 없어서 — jevcompat을 만들며 배운 것

2026-09-24·4분 읽기
Open SourcePythonSide ProjectAPITesting
← 이전 글 [사이드 프로젝트] 에이전트의 2시간을 35초 영상으로 — sessionreel을 만들며 배운 것

댓글 불러오는 중…

← 글

목차

  • 무엇을 만들었나
  • 왜 만들었나
  • 결과: 스타 상위 8개 서버
  • 가장 어려웠던 것: 남의 프로젝트를 틀리게 채점하지 않기
  • 스펙을 쓸 때 정한 규칙 두 가지
  • 하지 않은 것

무엇을 만들었나

jevcompat은 세 가지로 되어 있다.

  1. SPEC.md: TypeSafe System One API(POST /v1/systemone)가 지켜야 할 것을 번호 붙은 요구사항 48개로 정리한 비공식 스펙이다. 요구사항마다 MUST/SHOULD 수준과 근거 출처가 붙어 있다.
  2. jevcompat test URL: 아무 서버에나 이 스펙을 돌린다. 실패하면 원인이 된 요청과 규칙을 어긴 응답 바이트를 같이 보여준다.
  3. jevcompat proxy URL: 스펙을 안 지키는 서버 앞에 두면, 고칠 수 있는 건 고치고 고칠 수 없는 건 틀린 답을 넘기지 않고 거부한다.

왜 만들었나

9월 15일 TypeSafe가 Jev를 냈다. 타입이 있는 질문(예/아니오, 선택, 점수)을 넣으면 확률이 붙은 타입 있는 답을 돌려주는 모델이다. 모델은 비공개였고 API는 대기열로만 풀렸다. 그러자 "Jev 호환"을 내건 오픈소스 서버가 9일 만에 100개 가까이 생겼다.

실제로 스타가 많은 26개의 코드를 읽어봤다.

  • /v1/systemone을 실제로 제공하는 건 14개뿐이었다.
  • confidence의 뜻이 서버마다 달랐다. 최고 확률인 곳, 1·2위 차이인 곳, 1 − 엔트로피인 곳이 있었다.
  • 선택지 상한이 26, 50, 64, 128개로 제각각이었다. 문서는 255개를 약속한다.
  • 공식 SDK의 기본값인 "model": "jev-latest"를 받는 방식도 네 가지로 갈렸다.

"호환"이 무슨 뜻인지 확인할 기준이 없었다. 기준이 될 TypeSafe의 문서, OpenAPI 파일, SDK 두 개도 서로 여덟 군데에서 달랐다.

주변을 먼저 조사했다. 리더보드(JevBench), 캘리브레이션 벤치마크(sys1bench), semantic grep(17개), SQL 연동(25개)은 이미 있었다. 비어 있는 건 API 계약 자체였다. 마크다운이 구현체만 수십 개인 상태에서 CommonMark가 스펙과 테스트로 정리한 것과 같은 구도라고 봤다.

결과: 스타 상위 8개 서버

서버★MUST판정Jev 클라이언트에서 깨지는 것
kev (0.8B)5.8k32/32적합—
decider (0.8B)33832/32적합—
von57131/32부적합객체·배열 instructions를 거부
rizzo-flow (1.7B)38931/32부적합선택지 26개 초과를 거부
Open-Jev (2B)28431/32부적합한쪽만 있는 noul criteria를 거부
laya20.1k30/32부적합선택지 128개를 거부, SDK가 못 읽는 null legend
jeff23030/32부적합64개 초과를 거부, score가 자기 확률의 기댓값이 아님
simple-jev (0.8B)49929/32부적합jev-latest를 거부, 50개 초과를 거부

8개 중 2개가 적합했다. 가장 눈에 띈 건 jeff였다. 요청 안의 질문 순서만 뒤집었는데 team 답이 Billing 0.768에서 0.351로 바뀌었다. 질문들이 인코더 패스 하나를 공유하기 때문이다.

가장 어려웠던 것: 남의 프로젝트를 틀리게 채점하지 않기

검사를 만드는 건 금방이었다. 그다음 일이 훨씬 오래 걸렸다. 이 도구는 남의 오픈소스를 공개적으로 채점한다. 그래서 가장 나쁜 버그는 기능 누락이 아니라 오탐, 즉 멀쩡한 서버에 "MUST 위반"을 붙이는 것이다.

그래서 세 겹으로 검증했다.

1. 모든 검사는 실패할 수 있어야 한다. 레퍼런스 서버(jevcompat mock)는 스펙을 정확히 구현하고, 요구사항마다 결함을 일부러 넣을 수 있다(47종). CI는 세 가지를 확인한다. 깨끗한 서버에서는 전부 통과하는지, 결함마다 해당 요구사항이 실패하는지, 결함 하나가 관계없는 MUST 여러 개로 번지지 않는지. 절대 실패하지 않는 검사는 없는 검사와 같다.

2. 독립 리뷰를 세 번 받았다. 첫 리뷰에서 치명적인 문제가 나왔다. "질문 id를 바꾸면 답이 달라지는가"를 요청 두 번의 차이로 판정했는데, 확률에 노이즈가 있는 정상 서버가 약 25% 확률로 떨어졌다. 테스트는 고정 시드 하나에서 우연히 통과하고 있었다. 지금은 요청을 여러 번 보내 평균을 비교하고, 합동 표준편차로 한계를 잡고, 새로 보낸 두 번째 라운드에서도 같은 방향으로 차이가 나야 실패로 친다. 오탐률은 여러 시드로 테스트한다. 그 밖에 이런 문제들이 있었다.

  • 첫 요청에서 서버가 잠깐 502를 내면 "모델 이름을 안 받는다"로 오판했다.
  • float32로 반올림한 값을 반올림으로 알아보지 못했다.
  • 속도 제한(429)에 걸린 걸 서버 결함으로 셌다.

3. 실서버 결과를 하나씩 사람이 봤다. 8개 서버의 실패를 전부 요청·응답 증거와 대조했다. 여기서 도구 자체의 버그 5개가 더 나왔다.

  • 구조화된 score 레벨을 문자열로 돌려주는 걸 위반으로 봤다. 그런데 API 문서는 legend 값을 문자열로 정의한다.
  • 공식 문서의 예시를 모두 재현하는 confidence 공식이 두 개 있었는데, 하나만 인정하고 있었다.
  • 새로 넣은 검사가 등록 순서 때문에 한 번도 실행되지 않았다.

전부 공개 전에 고쳤다. 최종 보고서에 남은 실패 42개는 모두 실제 위반이다(REVIEW.md).

스펙을 쓸 때 정한 규칙 두 가지

  • MUST와 SHOULD는 규칙 하나로 나눈다. 공식 문서나 SDK대로 짠 클라이언트가 깨지면(예외, 크래시, 맞는 척하는 틀린 답) MUST다. 공식 서버와 동작만 다르면 SHOULD다.
  • 공식 출처끼리 충돌하면 견고성 규칙을 따른다. 공식 출처 중 하나라도 유효하다고 한 요청은 받는다. 모든 공식 클라이언트가 파싱할 수 있는 응답만 낸다. 이 규칙 하나로 여덟 군데 충돌이 정리됐다.

하지 않은 것

  • 정확도나 캘리브레이션 채점은 하지 않는다. "똑똑한가"가 아니라 "내 클라이언트 코드가 돌아가는가"를 본다.
  • TypeSafe 호스티드 API는 테스트하지 않는다. 도구가 일부러 잘못된 요청을 보내는데, TypeSafe 약관이 보안 테스트를 금지한다.