면지 (Myunzy)
Tool-Call Harness over a Small Korean Model
내 이력서와 실제 채용공고로 AI 면접관을 자동으로 만들고, 작은 한국어 오픈 모델(EXAONE-4.5)을 음성·도구호출 하네스 위에서 굴려 끝까지 일관되게 압박하고 평가하며 세션 안에서 약점까지 학습하는 모의면접 플랫폼입니다. OBA Weekendthon S1 전체 Top 6, LG U+ Voice AI 트랙(EXAONE).
개요
OBA Weekendthon은 1박 2일 동안 후원사 Open API와 오픈소스로 제품을 만드는 빌드 캠프였습니다. 심사 기준을 뜯어보니 "API·오픈소스 활용도"가 공통심사와 피어리뷰 양쪽에서 25%씩, 사실상 이중으로 걸려 있었습니다. 그래서 우리가 내린 결론은 분명했습니다. 직접 모델을 만들기보다, 오픈소스와 오픈모델을 잘 활용해 제품을 만드는 쪽에 점수가 몰려 있다는 것이었습니다. 우리가 고른 건 LG U+ 트랙이었습니다. 자격요건이 두 가지였는데 둘 다 선택이 아니라 강제였습니다. 하나는 EXAONE를 실제로 쓰는 것, 다른 하나는 음성(Voice AI)으로 동작하는 것이었습니다. GPT-4o로 자유롭게 연기시키는 길은 처음부터 막혀 있었고, 작은 한국어 오픈 모델인 EXAONE-4.5로, 그것도 목소리로 굴러가는 제품을 만들어야 했습니다. 마침 팀원들이 실제로 취업과 이직 면접을 앞두고 있던 시기였습니다. 그래서 우리가 당장 쓰고 싶은 것이 그대로 주제가 됐습니다. 그리고 EXAONE는 트랙이 강제한 모델이긴 했지만, 면접이라는 주제에는 오히려 유리한 카드였습니다. EXAONE는 LG가 만든 한국어 특화 오픈 모델이라 한국어 답변의 뉘앙스를 잘 잡는데, 면접은 한국어로 진행되고 우리가 하려던 일도 결국 지원자의 한국어 답변을 이해하고 머뭇거림이나 표현까지 분석하는 거였으니까요. 작은 모델이라는 점이 한국어 안에서는 약점이 아니었던 셈입니다. 진짜 질문은 여기서 나왔습니다. 한국어에 강한 작은 오픈 모델을, 그것도 음성으로 굴려서, 끝까지 무너지지 않는 면접관을 만들 수 있을까?
도전
작은 오픈 모델한테 프롬프트로 그냥 "면접관이 되라"고 시키면 세 군데에서 무너졌습니다. 첫째는 페르소나 드리프트입니다. 몇 턴만 지나면 압박하던 면접관이 친절한 챗봇으로 풀어졌습니다. 둘째는 tool-call이 자꾸 깨지는 문제였습니다. GPT-4o급의 안정적인 함수호출이 안 나오니까, 꼬리질문을 발사하거나 점수를 매기는 구조화된 도구호출이 비결정적으로 흔들렸습니다. 셋째는 평가 일관성이었습니다. 심사위원이 같은 답변을 두 번 넣었는데 점수가 달라지면 그 순간 신뢰를 잃습니다. 게다가 해커톤이라 후원사 API의 키와 명세가 행사 당일까지 도착하지 않았습니다. 외부 8종(OCR·공고·평판·STT·TTS 등)에 의존한 채로 짜두면, 키가 안 오는 순간 데모 시연 자체가 불가능해질 위험이 있었습니다.
- 페르소나 드리프트: 몇 턴 지나면 압박 면접관이 친절한 챗봇으로 풀어짐
- tool-call 무효: GPT급 안정 함수호출이 없어 꼬리질문·점수 산출이 비결정적으로 흔들림
- 평가 일관성: 같은 답변에 점수가 달라지면 신뢰 상실, 게다가 키·명세가 당일까지 미도착
접근
모델을 더 키우는 대신, 모델 바깥에 결정론 하네스를 두면 작은 모델로도 일관성을 살 수 있다고 봤습니다. 베팅은 세 가지였습니다. 첫째, 턴 루프는 LangGraph식 결정론 컨트롤러가 잡고 모델은 발화만 담당하게 한다. 둘째, tool-call은 wrap_tool_call 미들웨어가 스키마를 검증하고 자동으로 다시 물어보면서 유효율을 끌어올린다. 셋째, 점수와 합격확률, 타이밍 같은 평가는 모델이 아니라 순수함수가 계산한다(랜덤 0). 이 세 가지가 맞으면, 작은 모델에 하네스를 얹은 것이 큰 모델의 자유연기를 대체할 수 있다는 가설이었습니다.
구조
3-tier 듀얼 백엔드(mock-first) 구조를 잡고, 각 모듈을 DeepAgents 컴포넌트에 1:1로 매핑했습니다. harness.py는 wrap_tool_call 미들웨어(스키마 검증과 재시도), skills.py는 SkillsMiddleware(SKILL.md 한 장에 페르소나·STAR·플레이북), state.py는 StateBackend(세션이 끝나면 사라지는 weakness_profile 자가진화), engine.py는 LangGraph 결정론 컨트롤러(턴 루프와 verdict 순수집계), llm/base.py는 provider-agnostic 포트(mock과 EXAONE 토글)입니다. 그리고 BFF가 mock 엔진과 Python 프록시 사이를 분기하도록 만들어서, 키가 하나도 없어도 전 기능이 mock으로 완주하고 env만 켜면 무중단으로 실연동으로 승격됩니다. HARNESS=deepagents로 켜면 부트스트랩이 실제 deepagents·langchain 에이전트(wrap_tool_call·write_todos) 위에서 돌아갑니다.
실행
전체 흐름은 이렇습니다. 먼저 부트스트랩이 이력서 OCR과 채용공고, 회사 평판을 합쳐 Fit-Gap 공격포인트와 4개 페르소나(기술·컬처핏·임원·HR)를 만듭니다. 사용자가 면접 단계를 고르면 그 순서가 곧 라운드가 되고, 직무 키워드를 감지해 backend·frontend·pm 플레이북이 자동으로 스왑됩니다. 턴 루프에서는 답변의 약점 신호를 잡으면 라운드당 최대 2번 꼬리질문을 던지고, 그 약점을 weakness_profile에 쌓아 단계가 바뀌어도 직전 약점을 이어서 파고듭니다. 음성 면접은 webm으로 녹음한 뒤 Qwen3 ASR로 전사하고(단어 타임스탬프 포함) 머뭇거림과 필러를 분석하며, 면접관 목소리도 골라 들을 수 있습니다. 끝까지 지킨 원칙은 정직성입니다. mock으로 동작하는 부분은 면접관 발화에서 실제 인용인 척 꾸미지 않고, 작업 로그에 [mock] 배지를 그대로 노출했습니다.
결과 & 성과
OBA Weekendthon S1에서 전체 메인 프라이즈 Top 6에 들었습니다(전 트랙 종합). 키가 하나도 없이 mock으로 전 기능이 완주하는 단독 데모에, EXAONE-4.5 실연동과 실제 DeepAgents 부트스트랩(wrap_tool_call·write_todos), Qwen3 STT/TTS까지 묶어서 1박 2일 안에 동작하는 제품을 완성했습니다. 작업 로그 패널이 ocr → job → fitgap → persona → playbook → harness.validate/retry → followup.fire → evolve.diff를 실시간으로 흘려보내서, "agentic"과 "하네스"가 말이 아니라 화면에서 그대로 보이게 만들었습니다. 점수는 순수함수로 계산하니까 심사위원이 같은 답변을 다시 넣어도 똑같이 재현됐습니다. LG U+ Voice AI(EXAONE)와 GS네오텍 MISO 트랙 요건에도 정면으로 맞췄습니다.
다른 프로젝트