친구
그림톡 만들기 3편: ‘한 번 답하는 앱’에서 ‘계속 대화하는 친구’로 바꾸기
시작하며
결과 화면을 어느 정도 정리하고 나서, 그림톡을 실제로 다시 써보면 금방 보이는 문제가 하나 있었다.
첫 만남은 꽤 괜찮았다. 그림을 보여주고, 말을 걸고, 친구가 대답하는 첫 장면까지는 감정선이 살아 있었다.
그런데 그 다음이 문제였다.
대화가 이어지지 않는 느낌이 있었다.
겉으로는 다시 말을 걸 수 있었지만, 실제 체감은 “같은 친구와 이어서 말한다”보다 “다시 한 번 결과를 만든다”에 더 가까웠다. 이건 그림톡에서는 꽤 치명적인 문제였다. 그림톡의 핵심은 첫 응답 하나를 만드는 게 아니라, 내 그림 친구와 계속 말을 섞게 만드는 것이기 때문이다.
이번 글에서는 이 문제를 어떻게 봤는지, 왜 1턴과 후속 턴을 분리하게 됐는지, 그리고 사용성 측면에서 왜 앨범 업로드까지 추가하게 됐는지 정리해보려고 한다.
1. 가장 큰 문제는 latency보다 continuity였다
처음엔 응답 속도가 느린 게 더 큰 문제처럼 보였다. 물론 그것도 맞다. 아이가 기다리는 시간이 길어지면 금방 집중이 끊긴다.
그런데 계속 보다 보니 더 본질적인 문제는 따로 있었다.
느린 건 UX로 어느 정도 완화할 수 있지만, 대화가 끊겨 보이면 핵심 경험이 무너진다.
즉, 사용자가 느끼는 문제는 단순히 “오래 걸려서 답답하다”가 아니라,
- 같은 친구가 아닌 것 같고
- 방금 한 말을 이어서 듣는 느낌이 약하고
- 대화를 쌓아가는 리듬이 잘 안 생긴다
는 쪽에 가까웠다.
그래서 우선순위를 다시 잡았다.
- 같은 친구 continuity를 강하게 만들기
- 후속 턴 latency 줄이기
- UX도 이어지는 대화처럼 보이게 만들기
2. 1턴과 후속 턴은 사실 같은 요청이 아니었다
구조를 다시 보니, 첫 턴과 후속 턴은 목적 자체가 달랐다.
첫 턴
첫 턴은 말 그대로 친구가 처음 깨어나는 순간이다.
여기서는
- 그림 정보도 필요하고
- 사용자가 처음 한 말도 필요하고
- 캐릭터의 정체, 말투, 첫 인사, 첫 질문도 같이 만들어져야 했다.
그래서 첫 턴은 여전히 image + audio가 맞았다.
후속 턴
하지만 두 번째 턴부터는 다르다.
이미 사용자는 그 친구를 만났고, 이제 필요한 건 “새 친구 생성”이 아니라 같은 친구의 다음 대답이었다.
그런데 기존 흐름은 이 둘을 충분히 구분하지 못하고 있었다. 그 결과 후속 턴도 어딘가 첫 턴처럼 느껴졌고, 캐릭터 continuity도 흔들렸다.
그래서 아예 구조를 분리했다.
first_turncontinue_turn
이렇게 나누고 나서야 무엇을 계속 보내고, 무엇을 보내지 말아야 하는지가 또렷해졌다.
3. 후속 턴에서는 그림을 다시 보내지 않기로 했다
이번 구조 변경에서 가장 중요한 결정 중 하나는 이거였다.
후속 턴에서는 이미지를 다시 보내지 않는다.
이유는 명확했다.
1) 같은 친구를 계속 유지하고 싶었기 때문
매 턴 이미지를 다시 보내면, 모델 입장에서는 같은 친구를 이어서 말하기보다 이미지를 다시 해석하는 쪽으로 흐를 수 있다.
그림톡에서 원하는 건 그게 아니었다.
2) 속도도 줄이고 싶었기 때문
후속 턴에서 이미지를 매번 다시 보내는 건 비용도 크고, 응답 시간에도 영향을 준다.
3) 이미 필요한 정보는 첫 턴에서 얻었기 때문
첫 턴이 끝난 시점에는 이미 이 친구의 기본 프로필을 알고 있다.
- 이름
- 정체
- 성격
- 목소리 톤
- 첫 인사 톤
- 질문 톤
그러면 후속 턴에서 더 중요한 건 이미지 재전송이 아니라, 고정 캐릭터 프로필 + 최근 대화 문맥이었다.
그래서 후속 턴은 이렇게 정리했다.
- 입력:
audio only - 숨은 문맥:
- character profile
- 최근 1턴(user / assistant)
이 구조가 훨씬 맞았다.
4. 캐릭터 프로필을 고정 저장했다
후속 턴 continuity를 만들기 위해선, “같은 친구”가 어떤 친구인지 앱이 잊지 않아야 했다.
그래서 첫 턴 성공 시 캐릭터 정보를 store에 고정 저장하게 만들었다.
예를 들면 이런 정보들이다.
nameidentitytraitsvoiceTonegreetingquestion
이후 후속 턴에서는 새 캐릭터를 다시 만들지 않고, 이 고정 프로필을 기준으로 응답하게 했다.
이게 중요한 이유는 단순하다. 모델이 매번 자유롭게 다시 만들게 두면, 말투나 정체가 조금씩 흔들릴 수밖에 없다.
반대로 프로필을 고정하면,
- 같은 이름으로 말하고
- 같은 정체를 유지하고
- 비슷한 말투와 질문 리듬을 유지하기 쉬워진다.
결과적으로 대화가 “새로 생성된 응답들”이 아니라, 같은 친구의 이어진 말들처럼 느껴지게 된다.
5. 그런데 실사용 중 더 치명적인 문제가 하나 나왔다
continuity를 붙이고 나서도, 직접 써보면 가끔 이상한 응답이 튀어나왔다.
예를 들어 사용자가 처음에 “너 누구야?”라고 물었을 때, 그림 속 친구가 자기 이름을 말해야 하는 대신,
- “나는 카카오에서 온 AI 카나나야”
- “나는 카나나야”
같은 식으로 대답하는 경우가 있었다.
이건 완전히 잘못된 응답이었다.
그림톡의 친구는 AI 비서가 아니라, 그림 속에서 막 깨어난 존재여야 한다. 사용자가 처음 묻는 “누구야?”에 대한 정답은 이런 쪽이어야 한다.
- 나는 루루야
- 나는 별여우야
- 아직 이름이 없다면 네가 지어줄래?
- 나는 네 그림에서 방금 깨어난 친구야
즉, 이건 단순한 문구 취향 문제가 아니라, 제품 정체성을 깨는 버그에 가까웠다.
6. route 레이어에서도 방어가 필요했다
원인을 보니 프롬프트만의 문제가 아니었다.
실제로는 서버 라우트에서 continuity 메타를 제대로 읽지 않고 있었고, 모델이 자기 정체를 이상하게 말해도 그대로 assistant text로 통과될 여지가 있었다.
그래서 route 레벨에서 두 가지를 고쳤다.
1) continuity 메타를 실제로 받기
이제 서버는 아래 정보를 실제로 읽는다.
turnModecharacterpreviousUserTextpreviousAssistantText
2) 금지 자기소개 방어
응답 안에 이런 표현이 들어오면 그대로 노출하지 않게 했다.
- 카나나
- 카카오에서 온 AI
- 카카오의 도우미
- AI 비서
- 인공지능
이런 패턴이 감지되면, 캐릭터 프로필 기반 fallback 문장으로 치환하게 만들었다.
이건 조금 투박해 보일 수 있지만, 실제 제품 관점에서는 꽤 중요한 안전장치였다. 그림톡은 모델의 정체를 설명하는 앱이 아니라, 친구를 믿게 만드는 앱이기 때문이다.
7. UX도 같이 이어지게 보여야 했다
요청 구조만 바꾼다고 해서 사용자가 continuity를 바로 느끼는 건 아니다. 화면도 같이 바뀌어야 한다.
그래서 후속 턴 쪽 UX도 조금 손봤다.
record 화면
다시 말 걸기를 누르면 완전히 새 단계로 끊기는 느낌 대신, 직전 대화가 남아 있는 상태에서 이어서 말을 걸게 했다.
- “이어서 한마디 더 해볼까?”
- “방금 만난 같은 친구가 네 말을 기다리고 있어.”
이런 식으로 문구도 조정했다.
loading 화면
기존 로딩 카피는 첫 턴에는 맞았지만, 후속 턴에는 어색했다.
그래서 후속 턴일 때는
- 친구가 방금 네 말을 듣고 있어
- 같은 친구가 이어서 대답을 생각하고 있어
- 금방 다시 말을 걸어줄 거야
처럼 더 대화적인 느낌으로 바꿨다.
result 화면
후속 턴 결과는 “처음 인사했어”가 아니라 “이어서 대답했어”라는 식으로 분기했다.
작은 차이지만, 이런 문장 하나가 전체 경험을 꽤 다르게 만든다.
8. 사용성 측면에서 앨범 업로드도 넣었다
또 하나 느낀 건, 사용자가 꼭 실시간으로 사진을 찍기만 하진 않는다는 점이었다.
오히려 이런 경우가 많다.
- 이미 찍어둔 그림 사진이 있음
- 전에 그린 그림으로도 해보고 싶음
- 부모 폰 갤러리에 저장된 그림을 쓰고 싶음
- 카메라를 바로 켜는 게 번거로움
그래서 카메라 메인 흐름은 유지하되, 보조 액션으로 **‘앨범에서 가져오기’**를 추가했다.
여기서 중요한 건 구조를 새로 만드는 게 아니라,
선택한 이미지도 기존 capturedImage 구조로 태워서 뒤 플로우를 그대로 재사용하는 것이었다.
즉,
- 찍든
- 고르든
결국은 같은 preview → record → loading → result 흐름으로 들어간다.
이 방식이 좋은 이유는 두 가지였다.
- 감정선은 그대로 유지된다
- 진입 마찰은 확실히 줄어든다
그리고 구현 후에는 object URL revoke까지 같이 넣어서 메모리 누수 가능성도 정리했다.
9. 이번에 얻은 가장 큰 감각
이번 작업을 하면서 가장 크게 배운 건, 그림톡은 “좋은 첫 응답”보다 좋은 이어짐이 더 중요하다는 점이었다.
첫 응답은 누구나 어느 정도 만들 수 있다. 하지만 두 번째, 세 번째 턴에서도 같은 친구가 살아 있는 것처럼 느끼게 만드는 건 전혀 다른 문제였다.
그리고 그건 단순히 모델 품질의 문제가 아니라,
- 요청 구조
- 캐릭터 상태 관리
- 응답 방어
- 로딩 카피
- 입력 UX
이런 것들이 같이 맞물려야 가능한 일이었다.
결국 continuity는 프롬프트 한 줄이 아니라, 제품 전체가 같은 친구를 믿게 만드는 방식에 더 가까웠다.
마무리
3편에서는 그림톡을 “한 번 대답하는 앱”에서 “같은 그림 친구와 계속 대화하는 앱” 쪽으로 바꾸는 과정과, 그 과정에서 발견한 실사용 문제들을 정리해봤다.
특히 continuity를 구조적으로 분리한 것, 카나나 정체 누수를 막는 방어를 넣은 것, 그리고 앨범 업로드를 통해 진입 방식을 넓힌 것은 모두 같은 방향을 향하고 있었다.
그 방향은 꽤 분명하다.
그림톡은 결과를 만드는 앱이 아니라, 아이가 자기 그림 친구와 계속 놀게 만드는 앱이어야 한다.
다음 단계에서는 이 흐름 위에서, 더 긴 대화에서 캐릭터가 얼마나 안정적으로 유지되는지, 그리고 실제 아이 사용 상황에서 어떤 리듬이 자연스러운지를 더 다듬게 될 것 같다.