Code › tail-villain
프롬프트 리팩터링으로 사라진 엔진 규칙
근거 강화를 위한 프롬프트 리팩터링에서 사라진 인터뷰 엔진 규칙을 복구한 과정
인터뷰를 더 잘하게 만들려고 프롬프트를 줄였는데, 줄이면 안 되는 것까지 같이 사라졌다.
처음 목표는 단순했다. 빌런이 사용자의 이력과 프로젝트를 더 정확히 붙잡고 질문하게 만들고 싶었다. 답변을 듣고 그럴듯한 follow-up을 던지는 수준을 넘어서, 사용자가 실제로 겪은 근거 안에서만 물고 늘어지게 하는 쪽이었다.
그래서 evidence hook 구조를 손봤다. 그런데 그 과정에서 프롬프트 최적화까지 같이 들어가면서, 인터뷰 엔진의 행동 자체가 무너졌다.
tail-villain의 인터뷰는 프롬프트 하나로만 돌아가지 않고, 사용자의 답변을 받은 뒤 빌런 페르소나를 유지하면서 다음 질문을 만들고 평가 신호를 해석한다. 필요하면 JSON 형태의 결과도 파싱해야 해서, 겉으로는 AI가 말하는 것처럼 보여도 안쪽에는 꽤 많은 엔진 규칙이 붙어 있다.
이날 먼저 정리한 건 grounding이었다. 기존 evidence hook은 사용자의 배경을 참고하게 만드는 역할을 했지만, 프로젝트처럼 구체적인 맥락이 필요한 정보와 경력 전반의 요약처럼 넓게 쓰이는 정보가 한 구조 안에 섞여 있었다. 둘을 같은 무게로 다루면 질문도 흐려진다.
그래서 detailedProjects에는 특정 프로젝트의 맥락을 더 풍부하게 담고, generalExperience에는 전체 경력의 요약을 남기도록 구조를 나눴다. 빌런이 특정 프로젝트를 물고 들어갈 때는 detailedProjects를 보고, 사용자의 전체 배경을 잡을 때는 generalExperience를 보게 만들려는 의도였다.
이 방향 자체는 맞았다. 근거를 잘게 나누면 AI가 아무 말이나 던질 여지가 줄어들고, 인터뷰에서 중요한 작은 뉘앙스도 더 잘 살아난다. 사용자가 실제로 한 일과 하지 않은 일을 섞어 버리면, follow-up은 날카로워지는 게 아니라 부정확해진다.
동시에 LLM 호출 안정성도 손봤다. 세션을 시작할 때 Gemini Flash 쪽에서 503이 나오는 경우가 있었고, 이건 코드가 잘못됐다기보다 순간적인 수요나 일시적인 외부 API 장애였다. 한 번 실패했다고 바로 세션을 포기하게 만들기보다는, 짧은 재시도와 backoff를 넣는 쪽이 맞았다.
여기까지는 입력 근거를 더 잘 나누고, 일시적인 외부 API 실패에 덜 흔들리도록 제품의 안전장치를 늘리는 작업이었다.
문제는 그다음이었다.
프롬프트를 더 가볍게 만들고 싶어서 layered system prompt 형태로 리팩터링했다. 중복 지시를 줄이고, no-assumption grounding rule을 더 명확히 적으면서 응답 속도와 토큰 사용량을 낮추려는 방향이었다. 긴 프롬프트를 계속 쌓아두면 비용도 늘고, 모델이 중요한 지시를 놓칠 가능성도 생기기 때문에 최적화 자체는 필요한 일이었다.
하지만 프롬프트 안에는 단순한 말투 지시만 들어 있지 않았다. flow control, JSON parsing, evaluation handling처럼 엔진의 제어 흐름과 결과 해석을 담당하는 규칙도 섞여 있었고, 리팩터링하면서 중복처럼 보였던 문장을 덜어내는 사이 실제로 인터뷰를 굴리던 규칙 일부가 같이 빠졌다.
프롬프트가 짧아진 대신, 엔진이 덜 움직였다.
이런 문제는 테스트가 애매하다. 문법 오류처럼 바로 터지지 않고, AI는 여전히 답변을 만들며 화면에도 무언가가 나온다. 그래서 겉으로는 성공한 것처럼 보이지만, 질문 흐름이나 평가 처리가 예전과 달라진다.
프롬프트 최적화에서 제일 위험한 지점이 여기에 있다. 문장을 줄였을 때 무엇이 장식이고 무엇이 동작인지 구분하기 어렵기 때문이다. 특히 LLM 시스템에서는 지시문이 곧 코드처럼 행동하는 부분이 있고, 같은 자연어라도 어떤 문장은 스타일이고 어떤 문장은 상태 전이를 붙잡는 경계다.
결국 한 번 되돌렸다. 먼저 동작하던 baseline으로 돌아가 인터뷰 엔진이 다시 제대로 굴러가는지 확인한 뒤, 필요한 부분만 다시 살렸다. evidence hook 구조와 타입 안정성은 유지하되, 페르소나 기반 엔진 지시와 평가 처리 규칙은 빠지지 않도록 수술하듯 복원했다.
타입 쪽도 같이 정리했다. schema가 바뀌면서 생긴 타입 mismatch를 억지로 as any로 눌러두면, 프롬프트 문제와 데이터 문제를 나중에 구분하기 어려워진다. 그래서 Zod 기반 파싱과 타입 안전한 접근자로 바꿔서, 적어도 구조가 깨졌을 때는 조용히 넘어가지 않게 만들었다.
이날 배운 건 프롬프트를 최적화하지 말자는 말이 아니다. 인터뷰 세션은 반복 호출이 많고, 비용과 응답 시간은 사용자 경험에 바로 붙어 있기 때문에 오히려 최적화가 필요했다.
다만 프롬프트 안의 문장을 모두 같은 무게로 보면 안 됐다. 어떤 문장은 말투를 만들고, 어떤 문장은 grounding을 만들며, 어떤 문장은 엔진의 상태 전이를 만든다. 셋을 한꺼번에 줄이면 겉보기에는 깔끔해져도 제품은 다른 행동을 한다.
이후로는 프롬프트를 줄일 때 먼저 묻게 됐다. 이 문장은 스타일인가, 근거 규칙인가, 아니면 엔진 동작인가.
삭제는 그 다음이다.