텔레그램으로 사진을 보내면 위험 요소나 점검 포인트에 번호를 매겨 표시해주는 헤르메스 에이전트 스킬을 만들어봤다. 로컬 vision 모델의 좌표 인식 정확도부터 파고들었는데, 예상 못한 곳에서 두 번이나 막혔다.
좌표계 실험: 픽셀이 아니라 정규화였다
먼저 합성 도형 이미지로 로컬 두뇌 모델(qwen35-hermes)의 바운딩박스 정확도를 실측했다. "이미지는 800x600, 픽셀 좌표로 답해라"고 물었더니 IoU 0.00~0.09로 사실상 랜덤이었고, 이미지 크기를 벗어나는 좌표까지 나왔다. 회사에서 gemma4:31b로는 잘 됐다는 얘기를 듣고 모델 교체 대신 프롬프트를 먼저 의심했다. 가로/세로를 각각 0~1000으로 정규화한 좌표로 다시 물으니 IoU 0.85~0.89까지 뛰었다. Qwen 계열 VLM이 애초에 정규화 좌표로 그라운딩하도록 학습돼 있었던 것이다. 실제 사무실 사진으로 마우스 찾기, 6개 물체 동시 넘버링까지 재현 테스트를 거쳐 이 방식을 확정했다.
실전 테스트에서 드러난 한계
다중 물체 넘버링 테스트에서 두 가지 한계가 보였다. 나란히 있는 모니터 3대처럼 인접한 유사 물체는 박스 경계가 살짝 겹쳤고, 동일한 레드불 캔이 2개 있을 때는 "각각 따로 번호를 매겨라"라고 명시하지 않으면 하나만 잡았다. 이 두 가지를 스킬 프롬프트에 규칙으로 못박았다.
첫 번째 스킬 제작과 좌절
산업안전보건법 점검, 설비 점검처럼 판단 근거가 필요한 요청을 위해 "근거 모드"(risk_level, reasoning 필드)를 넣어 스킬명 "image"로 제작했다. 그런데 실제 텔레그램에서 "/image"를 입력하니 Unknown command가 떴다. 확인해보니 헤르메스 내장 명령 중에 이미 CLI 전용 `/image`(로컬 파일 첨부)가 있어서 이름이 겹쳤고, 애초에 개인 스킬은 텔레그램의 공식 슬래시 명령 메뉴에 등록되지도 않는다는 것도 함께 알게 됐다.
슬래시 대신 자연어로 다시 요청했지만 이번엔 사진 자체가 안 돌아왔다. 로그를 뜯어보니 원인은 따로 있었다 — 모델이 스킬 파일을 읽는 `skill_view`를 한 번도 호출하지 않고 `vision_analyze`만 반복하다가 그냥 텍스트로 답을 끝내버렸다. 9B급 로컬 모델이 "분석 → JSON 추출 → 파일 저장 → 스크립트 실행"의 다단계 절차를 스스로 조립하지 못한 것이다.
재설계: 스크립트 하나로 몰아넣기
해법은 모델의 판단에 맡기는 단계를 최소화하는 것이었다. Ollama API 직접 호출부터 JSON 검증, 좌표 변환, 이미지 주석까지 전부 처리하는 스크립트 하나로 통합해서, 모델은 `terminal` 호출 한 번만 하면 되도록 바꿨다. 스킬명도 core 명령과 충돌하지 않게 "photo-mark"로 개명했다. 사무실 사진으로 다시 테스트하니 118초 만에 레드불 캔 2개를 포함해 5개 항목을 전부 정확히 잡아냈다.
디테일 개선과 한자 혼입
사용자가 "이전보다 설명이 부실해졌다"고 지적했다. 확인해보니 컨텍스트 한계가 아니라 JSON 스키마에서 "한 줄 설명"이라고 못박아둔 게 원인이었다. reasoning 필드를 "2~4문장으로 상세히"로 풀어주니 설명이 훨씬 풍부해졌는데, 그 과정에서 답변에 한자가 한두 글자 섞이는 현상(略显 같은)이 발견됐다. 중국어 베이스 모델의 특성으로 보였다.
Trouble Shooting
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| 바운딩박스 좌표가 사실상 랜덤(IoU 0.05), 이미지 밖 좌표까지 발생 | 픽셀 좌표로 요청 — 모델이 내부적으로 여전히 0~1000 스케일로 계산해놓고 픽셀이라 우김 | 0~1000 정규화 좌표로 프롬프트 변경 → IoU 0.85~0.89 |
| 텔레그램에서 "/image" 입력 시 Unknown command | 헤르메스 내장 CLI 전용 명령 `/image`(로컬 파일 첨부)와 이름 충돌 | 스킬명을 "photo-mark"로 개명, 자연어 트리거로 안내 |
| 자연어로 요청해도 사진이 텔레그램에 리턴 안 됨(2회 연속) | 모델이 skill_view를 호출하지 않고 vision_analyze만 반복하다 프로즈로 답변 종료 — 다단계 절차를 스스로 조립 못함 | 절차를 단일 스크립트로 통합해 terminal 호출 1번으로 압축 |
| 한자 혼입 필터 추가 후 응답이 8분까지 지연 | 필터가 최대 3회 재생성하는데 SKILL.md의 terminal timeout이 200초로 너무 타이트 — 첫 시도가 강제 종료되고 재호출까지 겹침 | 재시도 3회→2회로 축소, timeout 200→420으로 상향 |
느낀 점 / 다음에 할 일
이번 작업에서 가장 컸던 배움은 "모델이 못 한다"고 단정하기 전에 프롬프트 포맷부터 의심해야 한다는 것과, 소형 로컬 모델에는 멀티턴 오케스트레이션을 맡기지 말고 절차를 스크립트로 뭉쳐야 한다는 것이었다. 다음에는 사내 8계통+이물 기준처럼 외부에 안 알려진 고정 점검 기준을 참고 문서로 붙이는 작업을 이어갈 계획이다.
댓글
댓글 쓰기