머릿말

피리부는 사나이

만화의 제목이기도 하지만, 드라마 실리콘밸리에서 유니콘 스타트업이 된 주인공네 회사 이름이기도 하다. 종종 싸우고 부딪히지만 서로가 유기적으로 연결되는 조직을 보며 멋지다고 생각했다. 드라마를 보면서, 나중에 저렇게 협업해보고 싶다는 생각을 했었다.

이번 프로덕션 기간에 팀을 결성하면서, 어쩌다 보니 피리 부는 사나이가 되어 팀원 대부분을 직접 섭외하게 되었다. 프로젝트를 진행하며 느낀 점과 배운 점, 그리고 앞으로의 계획까지 솔직하게 담아보려고 한다.


TL; DR

기록되지 않는 회의는 금세 사라지고, 문서로 남긴 회의록도 빠르게 현재 맥락과 어긋난다. 변화가 빠르고 컨텍스트 전환이 잦은 조직에서 문서 최신화와 이해의 동기화에 계속 비용을 쓰는 것은 비효율적이다. MIT는 이런 문제를 줄이기 위해, 단순한 회의록 검색을 넘어 반복되는 회의와 일정, 조직의 결정 사항을 더 쉽게 추적하고 활용할 수 있게 하려는 서비스였다.

이번 최종프로젝트에서는 그 경험을 뒷받침하는 에이전트 워크플로우를 설계하고 구현했다. 에이전트가 필요한 도구를 선택하고, 결과를 관찰한 뒤 다음 행동과 응답을 스스로 결정하는 흐름을 구현했다. 또한 HITL(Human-in-the-Loop)을 적용해 도구 호출시에 팀원초대, 회의 생성, 회의 삭제와 같은 data에 mutation을 가하는 동작에 대해서는 사람의 명시적인 승인을 거쳐 처리하도록 설계했다.

회의 기록을 탐색하고 질의해야 하는 서비스 특성상, 시간을 확인하기 위해 매번 툴을 호출하면 불필요한 지연과 API 호출 낭비가 발생했다. 이를 줄이기 위해 현재 시간을 컨텍스트에 미리 주입하는 방식을 적용했고, 응답 시간과 툴 호출 iteration을 줄일 수 있었다.

앞으로는 컨텍스트 비용을 낮추면서도 더 정확하게 동작할 수 있는 계층형 툴 콜링 구조를 적용해보고 싶다. 또한 프로젝트 과정에서 남겨진 숙제인 프롬프트의 정량 평가는 LLM-as-a-Judge를 활용한 후속 과제로 이어가려 한다.

TODO: LLM-as-a-Judge 평가를 실제로 이후에 진행했다면 현재 시점 기준으로 문장을 과거형/진행형으로 수정하기.

함께 자라기

팀으로 자라기

막연했다. 사실 팀프로젝트 경험이 많지는 않았다. 팀으로 일한다는 건 결국 서로 다른 속도와 관점을 조율하는 일이라고 생각했다. 그리고 실제로도 그랬다.

프로젝트를 진행하며 팀원과 수차례 갈등을 겪었다. 하지만 중요한 것은 모두가 제품이 잘 되기를 바라는 마음에서 이야기하고 있었다는 점이었다. 감정적으로 불편한 순간도 있었지만, 갈등이 생긴 뒤에는 각자가 전달하고자 했던 말과 상황을 다시 설명하고 서로의 입장을 들으려 했다.

갈등 중 하나는 프로젝트의 진행 속도와 관련된 것이었다. 내가 속한 에이전트 팀은 워크플로우를 정의하기 위해 도메인을 먼저 구체화해야 했고, STT/TTS 팀은 비교적 빠르게 구현 가능한 영역부터 먼저 만들어 나가고 있었다. 그러다 보니 STT/TTS 팀에서는 에이전트 팀의 코드 산출물이 늦어지는 것을 우려했고, 에이전트 팀에서는 도메인 정의가 어느 정도 정리되어야만 제대로 작업을 진행할 수 있다는 입장이었다.

결국 우리는 서로의 입장을 충분히 이해한 뒤, 프로젝트 초기에는 도메인 정의에 충분한 시간을 투자하기로 합의했다. 이 과정을 거치며 분명하게 배운 것은 팀으로 일한다는 건 환상과는 거리가 멀지만 결국 신뢰가 있어야 앞으로 나아갈 수 있다는 점이었다.

도메인이 뭐길래?

프로젝트를 본격적으로 시작하고, 에이전트 팀을 맡게 되었다. 에이전트 파트를 함께 맡은 팀원들과 에이전틱 워크플로우 설계를 시작했는데, 에이전트 팀 모두에게 이 영역은 처음이었다. 그러다 보니 우리에게 맞는 기술 스택을 정하고 선정하는 것부터가 큰 고비였다.

그런데 그보다 더 큰 고비는 도메인 정리였다. 모두가 ‘회의록’, ‘결정사항’, ‘PR’, ‘리뷰’ 같은 단어를 쓰고 있었지만, 각자가 떠올리는 스코프는 조금씩 달랐다. 예를 들어 PR의 단위를 안건으로 볼지, 회의 단위로 볼지부터 의견이 갈렸다. 팀의 결정사항(Ground Truth)을 언제 업데이트된 상태로 간주할지, 안건을 결정사항까지 포함한 단위로 볼지도 명확하지 않았다.

이처럼 같은 도메인 용어를 쓰고 있으면서도 실제로는 서로 다른 대상을 떠올리는 경우가 많았다. 그래서 서로의 이해를 맞추기 위해 도메인 용어집을 만들자고 제안했고, 주요 개념과 용어를 정리해나갔다.

도메인 용어를 명료하게 정리하고 팀원들과 합의하는 과정은 꽤 힘들었다. 하지만 이 과정은 꼭 필요했다. 용어를 정리하지 않았다면, 회의가 끝난 뒤 어떤 워크플로우가 동작해야 하는지, 회의록에 대한 피드백이 들어왔을 때 어떤 방식으로 업데이트해야 하는지조차 결정하기 어려웠을 것이다.

좀 더 여유롭게 개념 간 관계까지 면밀하게 검토할 수 있었으면 하는 아쉬움은 남는다. 그렇다고 마냥 붙잡고 있을 수도 없었기에, 당시 우리가 정의한 정도가 그 시점에서는 최선이었다고 생각한다.

용어를 정한 뒤에는 반드시 그 정의를 기준으로 사용하기로 했다. 그렇게 고정하고 나니 소통 과정에서 용어 때문에 생기던 오해가 확실히 줄어들었다.

결국 도메인을 정리한다는 것은 말을 예쁘게 맞추는 일이 아니라, 이후의 설계와 구현이 흔들리지 않도록 기준점을 세우는 일이었다.

기술적으로 남긴 것

에이전트가 스스로 판단하게 만든다는 것

이번 프로젝트에서 가장 직접적으로 부딪힌 주제 중 하나는, 에이전트가 어떤 도구를 언제 호출하고 그 결과를 어떻게 해석해 다음 행동을 결정할 것인가였다.

처음에는 Planning/Executing 구조를 사용하려 했다. 하지만 실제로는 툴 호출 결과를 모델이 다시 확인해야 했고, 그 결과를 바탕으로 추가적인 툴 호출이 필요한 경우도 자주 생겼다. evaluator 노드를 LangGraph 안에 두고 품질 게이트를 두는 방식도 시도했지만, 응답 시간이 길어지고 컨텍스트 관리도 복잡해졌다.

결국 구조를 ReAct 패턴으로 바꾸기로 했다. 모델이 현재 상태를 바탕으로 필요한 도구를 직접 선택하고, 결과를 확인한 뒤 다시 다음 행동을 결정하도록 만든 것이다. 이 구조로 바꾸고 나니, 에이전트가 단순히 응답을 생성하는 역할을 넘어 도구를 활용해 스스로 판단을 이어가는 흐름을 더 자연스럽게 구현할 수 있었다.

이 과정을 겪으며 느낀 것은, 에이전트의 자율성은 단순히 모델 성능만으로 생기지 않는다는 점이었다. 어떤 도구를 어떤 단위로 노출할지, 어떤 상태를 다음 턴으로 넘길지, 어떤 시점에 다시 판단하게 할지가 모두 품질에 직접적인 영향을 줬다. 코딩에 AI를 자주 사용하다보니 익숙한 개념처럼 보였지만, 실제로 구현해보니 에이전트를 잘 만든다는 건 결국 도구와 상태, 흐름을 어떻게 설계하느냐의 문제에 가까웠다.

위험한 리소스에 HITL을 적용한 이유

프로젝트를 진행하면서 모든 도구 호출을 자동으로 열어두는 것이 항상 좋은 선택은 아니라는 점도 분명하게 느꼈다.

우리 쪽에서는 백엔드나 DB 입장에서 mutation에 해당하는 작업들을 위험한 동작으로 봤다. 예를 들어 팀원 초대, 회의 생성, 회의 삭제처럼 시스템 상태를 직접 바꾸는 작업은 잘못 실행되었을 때 되돌리기 어렵다. 그래서 이런 작업은 모델이 곧바로 실행하지 않고, 반드시 사용자의 승인을 거치는 HITL(Human-in-the-Loop) 게이트를 통과하도록 설계했다.

이 과정을 통해 느낀 것은, 에이전트 시스템에서 중요한 것은 단순히 더 많이 자동화하는 것이 아니라 어디까지 자동화하고 어디서 제어권을 다시 사람에게 넘길 것인가를 구분하는 일이라는 점이었다. 결국 좋은 에이전트는 단순히 똑똑한 에이전트가 아니라, 실패했을 때의 비용까지 고려한 에이전트여야 한다고 생각하게 되었다.

다만 회고의 관점에서 보면, 당시에는 위험을 줄이겠다는 생각이 앞서 다소 많은 통제를 걸었던 것 같기도 하다. 어떤 작업은 승인 게이트가 꼭 필요했지만, 어떤 작업은 그렇지 않았을 수도 있다. 결국 중요한 것은 모든 것을 막는 것이 아니라, 실패 비용이 큰 지점에만 정교하게 제약을 두는 일이라는 생각이 들었다.

시간을 매번 묻지 않도록 한 이유

우리 서비스는 회의 기록을 탐색하고 질의하는 경우가 많다 보니, 사용자의 질문 안에는 시간 맥락이 자연스럽게 섞여 들어오는 일이 많았다. 문제는 이 시간을 확인하기 위해 매번 별도의 툴을 호출하면, 응답이 느려질 뿐 아니라 불필요한 호출이 반복되면서 전체 이터레이션도 길어진다는 점이었다.

특히 “오늘 회의 일정 알려줘” 같은 질의에서는 ‘오늘’이 언제인지를 정확히 알아야 하므로 원래라면 get_time 같은 도구 호출이 필요했다. 하지만 모델이 항상 그 도구를 올바르게 선택하는 것은 아니었고, 때로는 시간을 추정하거나 유추해서 답하려는 경우도 있었다.

그래서 현재 시간을 매번 도구로 조회하게 하기보다, 애초에 시스템 프롬프트에 현재 시각을 주입하는 방식을 적용했다. 이렇게 하니 단순한 시간 확인을 위한 툴 호출이 줄었고, 시간 관련 질문에서도 현재 시각을 기준으로 더 안정적으로 추론하는 모습을 볼 수 있었다.

작은 변경처럼 보이지만, 실제로는 어떤 정보를 모델이 기본 전제로 갖고 시작해야 하는가를 다시 생각하게 만든 사례였다. 에이전트의 성능은 더 많은 툴을 붙이는 것만으로 좋아지는 것이 아니라, 아예 툴 호출이 필요 없는 구조를 만드는 것에서도 크게 달라질 수 있다는 점을 배웠다.

아직 끝내지 못한 숙제들

프로젝트를 진행하면서, 컨텍스트 비용을 줄이면서도 더 정확하게 동작하는 구조에 대한 고민도 계속 남았다. 그중 하나가 계층형 툴 콜링 구조였다. 모든 도구를 한 번에 모델에게 노출하기보다, 먼저 상위 수준에서 필요한 도구군을 좁히고 그다음 세부 도구를 선택하게 만들면 더 효율적인 흐름을 만들 수 있지 않을까 생각했다. 도구 수가 늘어날수록 선택 비용도 함께 커졌기 때문에, 이 문제는 이후에도 계속 탐색해보고 싶은 주제로 남았다.

또 하나의 숙제는 프롬프트를 감각이 아니라 기준으로 평가하는 일이었다. 프로젝트를 하며 여러 설계를 시도했지만, 무엇이 실제로 더 나은 결과를 내는지 정량적으로 확인하는 과정은 충분히 마무리하지 못했다. 그래서 LLM-as-a-Judge를 이용한 평가를 후속 과제로 남겨두었고, 3월 초 팀원분과 함께 LLM-as-a-judge를 진행했다. 에이전트 시스템을 만들수록 프롬프트 역시 “잘 만든 것 같다”는 인상만으로는 부족하고, 반복 가능한 평가 기준 위에서 다뤄야 한다는 생각이 점점 강해졌다.


얻은 것과 놓친 것

최종 프로젝트를 통해 얻은 건, 특정 기술에 대해 더 깊은 경험을 쌓는 것보다 “자세”였다.

필요하면 도전하고, 배우고, 직접 부딪혀 익혀야 한다는 것이었다. 실무에서 프로덕션에 새로운 기술을 무작정 도입하는 건 분명 위험할 수 있겠지만, 다른 선택지가 없고 그것이 지금의 정답이라면? 그게 가야만 하는 길이라면?

내가 처음 해보는 일이라는 사실보다 프로젝트가 가야 할 방향으로 가게 만드는 일이 더 중요하다고 느꼈다.

잃은 건 워라밸이었다. 평소에도 일과 삶을 분리해서 생각하는 편은 아니지만, 이번에는 몸이 망가지는 느낌이 들 정도로 프로젝트에 매달렸던 것 같다. 하루 18~20시간 이상을 프로젝트에 쏟았고, 자다가도 눈이 떠지면 다시 프로젝트 생각부터 났다. 다만 그만큼 깊게 몰입해본 경험 자체는 분명 재미있었다. 동시에, 적절히 쉬는 것도 결국 작업의 일부라는 사실을 느꼈다. 붙잡고 있을 때는 풀리지 않던 문제도, 잠깐 쉬고 나면 의외로 금방 해결되는 경우가 있었다.

또 하나 배운 것은, 어려워 보여서 망설이는 시간은 생각보다 더 비효율적이라는 점이었다. 어려워 보이는 것과 실제로 어려운 것은 다르고, 막상 손을 대보면 예상보다 단순한 일도 많다. 경험과 지식의 부족에서 오는 막연한 공포감은 결국 경험과 지식으로만 해결된다는 걸 다시 확인했다. 그래서 앞으로도 꾸준히 학습하고, 새로운 문제 앞에서 지나치게 겁먹지 않는 엔지니어가 되고 싶다.

이번 프로젝트에서는 에이전트를 최대한 활용해 코드를 작성해보기도 했다. 그 과정에서 에이전트가 제대로 동작하려면 결국 좋은 컨텍스트가 필요하다는 점을 절감했다. 이를 위해 도메인 지식이나 문서의 최신화가 필요한 상황이 있었는데, 그 부분을 놓친 경우가 종종 있었다. 프롬프트가 이전 도메인 지식이나 낡은 맥락을 기반으로 동작하면서 에이전트의 판단 품질이 떨어지는 순간을 경험했고, 문서 최신화의 중요성을 몸소 느끼게 되었다. 결국 에이전트를 잘 활용한다는 것은 모델만 잘 붙이는 일이 아니라, 그것이 참고할 수 있는 맥락과 하네스를 얼마나 잘 설계하느냐의 문제이기도 했다.


마치며

프로젝트를 진행하며 어쩌다 보니 사실상 에이전트 파트에서 팀 리드 역할을 맡게 되었다. 나의 설계 미스와 구조적 변경 요청을 적극적으로 수용해주고, 원만한 합의를 통해 더 나은 방향으로 함께 에이전트를 설계하고 만들어간 에이전트 팀에게 감사하다. 이를 사용자들과 음성으로 잘 마주할 수 있게 해준 STT/인프라 팀, 그리고 초기 프로젝트의 방향성과 베이스를 마련하는 데 큰 역할을 해준 윤성님께도 감사드린다.

프로젝트를 진행하며 우리는 함께 문제를 정의하고, 합의하고, 구현했고, 때로는 부딪히기도 했다. 하지만 그 과정을 거치며 결국 앞으로 나아갈 수 있었고, 나는 그게 팀으로 성장한다는 뜻에 가깝다고 느꼈다. 기술적으로도, 사람과 협업하는 방식에서도 오래 남을 프로젝트였다.