Mini Project 4 05

BookShelf 자동 태그: 장식에서 구조적 데이터로

태그 자동 생성은 표지 생성 옆의 작은 부가 기능처럼 보이지만, BookShelf에서는 검색과 추천이 책을 이해하는 기준을 얻는 과정이다.

근거 · 4차 tagService/추천 태그 구현

BookShelf 자동 태그: 장식에서 구조적 데이터로 대표 이미지

1장: 자동 태그: 단순한 장식인가, 시스템의 재료인가?

솔라는 자신의 BookShelf 프로젝트 화면을 만족스럽게 바라보고 있었다. 사용자가 책 정보를 입력하면 AI가 그에 어울리는 표지 이미지를 즉석에서 생성해주는 기능. 며칠을 고생한 보람이 있었다. 추상적인 책 설명이 다채로운 색과 이미지로 변환되는 순간은 볼 때마다 흐뭇했다.

자동 생성 태그도 마찬가지였다. ‘#미래’, ‘#AI’, ‘#성장’ 같은 태그들이 책 카드 아래에 예쁘게 붙어 있었다. 사용자가 일일이 태그를 고민할 필요 없이, 책의 핵심 키워드를 한눈에 보여주는 편리한 기능. 솔라에게 이 태그들은 잘 만들어진 표지 이미지처럼, 서비스를 더 풍성하고 보기 좋게 만드는 ‘장식’에 가까웠다.

“흠… 이제 프로젝트 회고만 정리하면 되겠네.”

솔라는 프로젝트의 기술적인 부분을 요약한 메모를 열었다. 여러 파일과 기능에 대한 간략한 설명들이 나열되어 있었다. 그중 태그 생성에 관한 한 문장이 눈에 들어왔다.

4차 tagService.js는 OpenAI Responses API와 gpt-4o-mini를 사용해 후보 태그 중 정확히 3개를 뽑고, 그 태그는 추천과 탐색의 재료가 된다.

솔라는 잠시 멈칫했다. 문장을 소리 내어 다시 읽어 보았다. 다른 부분은 모두 익숙한 기술 스택과 구현 방식에 대한 설명이었다. 하지만 마지막 구절이 어딘가 생경하게 느껴졌다.

‘…추천과 탐색의 재료가 된다.’

‘재료?’ 솔라는 고개를 갸웃했다. ‘장식’이나 ‘라벨’이 아니라 ‘재료’라니. 단어 하나 차이지만, 어감은 완전히 달랐다. 마치 주방에서 장식용 파슬리 가루와 빵을 만드는 밀가루를 구분하는 것 같은 이질감이었다. 자신의 생각과 이 문장 사이에 미묘한 틈이 벌어져 있는 것을 느꼈다.

“언니, 잠깐만.”

솔라는 거실에서 책을 읽고 있던 루나에게 다가가 노트북 화면을 보여주었다.

“이 문장 좀 이상하지 않아? 우리 BookShelf 프로젝트 태그 기능 설명인데, 마지막에 태그가 추천의 ‘재료’가 된대.”

루나는 잠시 문장을 들여다보더니 시선을 들어 솔라를 바라보았다.

“뭐가 이상하게 들리는데?”

“재료라니? 그냥 책 밑에 붙는 해시태그잖아. 책 표지 이미지처럼 예쁘게 보여주는 장식 아니었어? 사용자가 ‘아, 이 책은 이런 내용이구나’ 하고 쉽게 파악하게 도와주는 역할. 그게 전부인 줄 알았지.”

솔라의 말에는 이 기능의 역할은 이미 명확하게 정리되었다는 확신이 섞여 있었다. 루나는 고개를 끄덕였다. 질문을 가로막는 대신, 솔라가 짚어낸 단어에 집중했다.

“재료… 확실히 재미있는 표현이네. 그럼 솔라 네가 생각하는 ‘장식’과 ‘재료’는 어떻게 다른데?”

“음… 장식은 그냥 겉에 붙여서 보기 좋게 하는 거잖아. 사람이 보는 거지. 없어도 핵심 기능은 그대로 돌아가고. 근데 재료는… 그걸로 뭔가를 만드는 거잖아. 시스템이 사용하는 입력값 같은 느낌? 집을 지을 때 쓰는 벽돌이나, 빵을 만드는 밀가루처럼.”

스스로 비유를 들고 나니, 솔라는 자신이 처음 느꼈던 이질감의 정체를 조금 더 선명하게 알 수 있었다. 장식은 사람을 향하지만, 재료는 시스템을 향한다.

루나가 다시 화면의 문장을 가리켰다.

“그럼 그 문장을 다시 봐봐. ‘추천과 탐색의 재료가 된다’는 건, 그 태그를 누가, 혹은 무엇이 사용한다는 뜻일까?”

“아…”

짧은 탄식과 함께 솔라의 머릿속이 환해졌다. 사용자가 아니었다. 문장의 주어는 명백히 ‘추천과 탐색’이라는 시스템의 기능이었다.

“사람이 아니라, 추천 시스템이 사용한다는 거구나. 그럼 이 태그들은 그냥 보여주기용 라벨이 아니었네. 추천 알고리즘이 이 태그들을 ‘먹고’ 일한다는 거구나.”

솔라는 자신이 만든 기능의 역할을 완전히 잘못 이해하고 있었다는 사실을 깨달았다. 태그는 단순히 책을 꾸미는 액세서리가 아니었다. BookShelf의 핵심 기능인 ‘추천’을 떠받치는 구조적인 부품, 즉 ‘데이터’였던 것이다. 장식인 줄 알았던 것이, 사실은 기계의 톱니바퀴였다.

생각이 바뀌자 새로운 질문이 꼬리를 물고 이어졌다.

“그런데… 이상하네. 그냥 AI한테 ‘이 책에 어울리는 태그를 적당히 만들어줘’라고 하면 되는 거 아닌가? 왜 굳이 ‘후보 태그 중에서’ 고르고, 그것도 ‘태그 3개’로 개수까지 제한하는 까다로운 규칙을 만든 걸까? 이 제약들이 태그를 ‘장식’이 아닌 ‘재료’로 만드는 열쇠인 건가?”

2장: 태그 생성의 규칙: 왜 ‘정확히 3개’와 ‘Strict Schema’인가?

솔라는 더 이상 BookShelf 서비스의 화려한 화면을 보고 있지 않았다. 그녀의 시선은 코드 에디터의 한 파일, src/tagService.js에 고정되어 있었다. 마우스 커서가 AI를 호출하는 함수 주변을 맴돌았다. 화면에는 책 표지 이미지도, 예쁘게 정렬된 태그 목록도 없었다. 오직 시스템의 뼈대를 이루는 건조한 코드와 주석만이 가득했다. ‘재료’라는 단어 하나가 솔라를 화려한 결과물 뒤편의 설계도로 이끈 것이다.

솔라는 프로젝트 문서를 뒤져 태그 생성 규칙을 더 자세히 정리한 부분을 찾아냈다. 그리고는 그것을 거실 테이블 위에 있던 언니의 노트 옆에 나란히 펼쳐 놓았다. 노트에는 지난번 대화의 흔적인 ‘장식 -> 재료’라는 단어가 적혀 있었다. 그 옆에 솔라는 방금 찾은 규칙을 옮겨 적었다.

  • 정해진 후보 태그 목록 안에서만 선택
  • 정확히 3개의 태그만 반환
  • 반드시 Strict JSON Schema 형식 준수

규칙들을 손으로 직접 쓰고 나니, 지난밤의 의문이 더욱 선명해졌다. 이 빡빡한 제약들은 마치 창의적인 AI의 손발을 묶는 족쇄처럼 느껴졌다.

“언니, 역시 이상해. 태그가 추천 시스템의 ‘재료’라는 건 이제 알겠어. 그런데 재료를 만드는 방식이 너무 경직되어 있잖아.”

솔라는 자신이 정리한 메모를 손가락으로 툭툭 쳤다.

“AI한테 책 내용을 주고 자유롭게 키워드를 뽑으라고 하면 훨씬 더 풍부하고 정확한 태그가 나올 수도 있잖아? ‘#우주비행사의_고독’이라든지, ‘#기술_발전의_양면성’처럼. 그런데 왜 굳이 정해진 목록에서, 그것도 ‘태그 3개’라는 제약을 두는 거지? 이건 AI의 능력을 제한하는 거 아니야?”

솔라의 목소리에는 똑똑한 도구를 제대로 활용하지 못하는 답답함이 묻어났다. 루나는 솔라가 가리키는 메모를 잠시 들여다보더니, 테이블 위에 있던 빈 종이와 펜을 가져왔다. 그리고는 종이 가운데에 세로로 선을 그어 두 칸으로 나눴다.

“네 말이 맞아. 그 제약이 없으면 AI는 훨씬 더 창의적인 결과물을 낼 수 있겠지.”

루나는 왼쪽 칸 위에 ‘제약 없이’라고 적었다.

“그럼 한번 실험해보자. 여기 ‘인공지능 로봇과 인간의 우정을 다룬 성장 소설’이 한 권 있다고 상상해봐. 그리고 솔라 네가 제약 없는 자유로운 AI가 되는 거야. 이 책에 어울리는 태그를 마음껏 만들어볼래? 개수도, 형식도 상관없이.”

솔라는 신이 나서 대답했다. “음… ‘#인공지능’, ‘#로봇’, ‘우정’, ‘성장 소설’, ‘AI and Human’, ‘마음이란 무엇인가?’, ‘#미래사회’… 한 7개 정도면 되려나?”

루나는 솔라가 말한 태그들을 왼쪽 칸에 그대로 적었다. 결과는 제법 그럴듯했다. 한글과 영어가 섞여 있고, ‘#’이 붙은 것과 안 붙은 것이 혼재했으며, 단어와 질문 형태의 문장이 뒤섞여 있었다. 사람이 보기엔 아무 문제가 없었다. 오히려 풍부한 정보를 담고 있는 것처럼 보였다.

“좋아. 이번엔 오른쪽.” 루나는 오른쪽 칸 위에 ‘우리 시스템 규칙대로’라고 썼다. “규칙은 아까 네가 정리한 그대로야. 후보 목록은 #SF, #성장, #우정, #기술, #드라마, #미스터리 이렇게 여섯 개만 있다고 가정하고, 이 중에서 정확히 세 개를 골라야 해.”

솔라는 잠시 생각하더니 대답했다. “그럼… #SF, #성장, #우정. 이렇게 세 개가 제일 적절하겠네.”

루나는 오른쪽 칸에 깔끔하게 [ “#SF”, “#성장”, “#우정” ] 이라고 적었다. 이제 테이블 위 종이에는 극명하게 다른 두 결과가 나란히 놓였다.

제약 없이우리 시스템 규칙대로
#인공지능[ “#SF”, “#성장”, “#우정” ]
#로봇
우정
성장 소설
AI and Human
마음이란 무엇인가?
#미래사회

솔라의 시선이 두 칸을 오갔다. 처음에는 왼쪽의 자유로운 결과물이 더 똑똑하고 유용해 보였다. 하지만 ‘시스템의 재료’라는 관점을 대입하자, 상황이 완전히 다르게 보이기 시작했다.

“아…”

왼쪽 칸의 태그들은 모양도, 개수도, 언어도, 길이도 모두 제각각이었다. ‘우정’과 ‘성장 소설’을 같은 기준으로 분류할 수 있을까? ‘AI and Human’이라는 태그가 붙은 책과 ‘#인공지능’ 태그가 붙은 책을 시스템은 어떻게 같은 그룹으로 묶을 수 있을까? 불가능했다. 시스템이 이 데이터를 처리하려면 수많은 예외 처리와 복잡한 파싱 규칙이 필요할 것이다. 데이터의 품질이 엉망진창이었다.

반면 오른쪽 칸의 결과는 지루할 정도로 규칙적이었다. 언제나 3개의 태그가, 정해진 형식의 배열 안에, 미리 약속된 단어들로만 채워진다. 어떤 책이 들어오든 결과물의 ‘구조’는 절대 변하지 않는다.

“알겠다… 왼쪽은 사람이 볼 때는 좋지만, 기계한테는 그냥 뒤죽박죽 소음에 가깝구나. 뭘 기준으로 비교하고, 뭘 기준으로 추천해야 할지 알 수가 없으니까. 하지만 오른쪽은… 모든 책이 똑같은 형식의 데이터, 즉 동일한 구조를 갖게 되네. 데이터 품질이 보장되는 거야.”

솔라는 고개를 끄덕였다. 제약의 진짜 목적을 깨달은 것이다. 이 규칙들은 AI의 창의성을 억누르기 위한 것이 아니었다. 오히려 AI라는 강력하지만 예측 불가능한 도구를 길들여, 추천 시스템이라는 기계가 완벽하게 이해하고 사용할 수 있는 ‘구조화된 데이터’를 생산하게 만드는 영리한 장치였다. 장식이 아닌 ‘재료’로 쓰이기 위한 최소한의 조건, 그것은 바로 ‘구조’와 ‘규칙’이었던 것이다.

자신이 답답하게만 여겼던 제약 조건들이 사실은 데이터의 품질을 지키는 가장 중요한 방어선이었다는 것을 깨닫자, tagService.js의 코드가 다르게 보였다. 그저 기능 구현을 위한 몇 줄의 코드가 아니라, 시스템 전체의 데이터 품질을 책임지는 단단한 설계 의지가 담긴 선언처럼 느껴졌다.

“좋아, 이제 태그가 왜 이렇게 반듯하고 예측 가능한 형태로 만들어져야 하는지는 알겠어. 깨끗하고 구조화된 재료가 준비된 거지.”

솔라의 눈빛이 다시 빛났다. 하나의 의문이 풀리자, 그 자리에 새로운 질문이 고개를 들었다.

“그런데… 추천 시스템은 이 잘 닦인 벽돌 같은 태그들로 정확히 뭘 하는 걸까? #SF 태그가 붙은 책을 읽은 사람에게 또 다른 #SF 책을 보여주는 것, 그게 전부일까? 그렇게 단순한 일을 하려고 이렇게까지 공을 들인 건 아닐 것 같은데.”

3장: 태그는 어떻게 ‘짧은 신호’가 되어 추천을 돕는가?

지난번 실험의 결과가 적힌 종이는 여전히 거실 테이블 위에 놓여 있었다. 왼쪽 칸의 자유분방하지만 뒤죽박죽인 태그들과, 오른쪽 칸의 단정하지만 어딘가 답답해 보였던 [ “#SF”, “#성장”, “#우정” ]. 솔라는 이제 오른쪽의 제약 조건들이 왜 ‘데이터 품질’을 위한 방어선이었는지 이해했다. 깨끗하게 규격화된 벽돌을 만드는 과정이었으니까. 하지만 솔라의 마지막 질문, “그래서 이 벽돌로 고작 똑같은 벽돌을 찾아주는 게 전부일까?”는 여전히 공중에 떠 있었다.

대답 대신, 루나는 책장에서 책 한 권을 꺼내 들었다. 제법 두꺼운 과학 소설이었다. 루나는 책을 뒤집어 뒷면에 인쇄된 줄거리 소개를 솔라에게 보여주었다. 수백 자에 달하는 빽빽한 글자들이 책의 세계관과 주인공의 여정, 그리고 그 안에 담긴 철학적 질문까지 함축하고 있었다.

“솔라 네가 추천 시스템이라고 생각해봐.”

루나가 말했다.

“사용자가 이 책을 막 다 읽었어. 이제 뭘 추천해줘야 할까? 네가 가진 정보는 오직 이 뒷면의 긴 글뿐이야.”

솔라는 잠시 빽빽한 글자들을 노려보았다. 막막했다. 이 글을 분석하려면 ‘인공지능’, ‘관계’, ‘정체성’, ‘반전’ 같은 핵심 단어들을 먼저 추출해야 한다. 하지만 다른 책의 소개 글은 완전히 다른 단어와 문장 구조를 사용할 것이다. 어떤 책은 줄거리 요약 대신 작가의 말이나 추천사만 있을 수도 있다. 수천, 수만 권의 책을 이런 식으로 비교하는 것은 거의 불가능에 가까웠다.

“비교할 기준이 없어. 이 긴 글과 다른 책의 긴 글을 어떻게 일대일로 비교하겠어? 단어가 몇 개나 겹치는지 세어볼 수도 있겠지만… 그게 정말 좋은 추천일지는 확신할 수 없지.”

바로 그 지점이었다. 솔라는 깨달았다. 추천 시스템에게 비정형적인 긴 글은 그 자체로 거대한 ‘소음’이었다. 인간에게는 풍부한 정보지만, 기계에게는 계산 불가능한 영역에 가까웠다.

“맞아. 그래서 이 제약들이 필요한 거야.”

루나가 테이블 위의 오른쪽 칸, 즉 [ “#SF”, “#성장”, “#우정” ]을 가리켰다.

“이 태그들의 진짜 목적은, 저 빽빽하고 긴 책 소개글을 시스템이 이해할 수 있는 아주 짧고 명료한 ‘신호(signal)’로 바꾸는 데 있어. 수백 자의 문장을, 단 세 개의 좌표로 압축하는 거지.”

‘신호’. ‘좌표’. 그 단어들이 솔라의 머릿속에 박혔다. 이제야 모든 조각이 맞춰지는 기분이었다. 후보 태그 목록, ‘태그 3개’라는 개수 제한, 엄격한 JSON 스키마. 이 모든 제약 조건은 AI의 창의성을 억누르는 족쇄가 아니었다. 오히려 자유로운 AI가 만들어낼 예측 불가능한 결과물로부터 시스템을 보호하고, ‘긴 문장’이라는 노이즈 가득한 원석을 ‘짧은 신호’라는 보석으로 제련하는 정교한 도구였던 것이다.

“아! 그럼 #SF 책을 읽은 사람에게 #SF 책을 추천하는 단순한 일이 아니었네!”

솔라의 목소리에 활기가 돌았다.

“어떤 책이 #SF, #성장, #우정이라는 세 가지 신호를 가지고 있다면, 시스템은 이 세 가지 신호를 모두 가진 다른 책을 찾아줄 수도 있고, #SF#성장이라는 두 가지 신호만 공유하는 책을 보여주면서 새로운 관심사를 제안할 수도 있겠구나. #우정 대신 #기술 태그가 붙은 책을 보여주면서 ‘이런 건 어때요?’ 하고 물어보는 거지. 신호들이 조합되면서 훨씬 더 똑똑한 추천이 가능해지는 거야.”

장식인 줄 알았던 태그가 시스템의 재료가 되고, 그 재료는 엄격한 규칙 속에서 구조화된 데이터로 만들어지며, 마침내 그 데이터는 추천 알고리즘을 위한 명료한 ‘신호’가 된다. 길고 긴 여정의 끝에, 솔라는 자신이 만들었던 기능의 진짜 ‘설계 의도’를 온전히 이해하게 되었다.

솔라는 다시 자신의 노트북을 열었다. 프로젝트 회고 문서를 수정할 차례였다. 처음에는 그저 기술 요약이라고 생각했던 문장이 눈에 들어왔다.

4차 tagService.js는 OpenAI Responses API와 gpt-4o-mini를 사용해 후보 태그 중 정확히 3개를 뽑고, 그 태그는 추천과 탐색의 재료가 된다.

솔라는 잠시 그 문장을 바라보다, 그 아래에 새로운 분석을 추가하기 시작했다. 이것은 단순히 한 줄짜리 사실 기록이 아니었다. 시스템의 핵심 설계 사상을 꿰뚫는 분석 보고서의 첫 문장이었다.

자동 태그 기능(tagService.js)의 핵심은 AI를 활용한 데이터 ‘정제’ 및 ‘구조화’에 있다. 이 기능은 비정형적인 긴 글(책 소개)을 정해진 후보군 중에서 선택된 3개의 태그 배열, 즉 예측 가능한 ‘구조적 신호’로 변환한다. 이 엄격한 제약은 추천 및 검색 알고리즘이 효율적으로 비교하고 연산할 수 있는 고품질의 데이터를 확보하기 위한 의도적인 설계다. 태그는 장식이 아니라, 추천 시스템을 작동시키는 가장 기본적인 신호 체계인 것이다.