사람들이 실제로 쓰고 싶어 하는 회의 어시스턴트 설계

발행: (2026년 6월 7일 AM 12:50 GMT+9)
11 분 소요
원문: Dev.to

출처: Dev.to

대부분의 회의 도구는 회의 중에 도움을 주지만, 실제 어려움은 종종 회의가 시작되기 전부터 시작됩니다. 사용자는 컨텍스트를 찾고, 과거 상호작용을 검토하며, 논의 포인트를 준비하는 데 시간을 소비합니다.
MeetMind를 만들면서 우리는 회의 준비와 사후 작업을 더 간단하고 직관적으로 만드는 것을 목표로 했습니다. 프론트엔드 개발자로서 저는 사용자 친화적인 인터페이스 설계, 반응형 컴포넌트 구축, 회의 준비부터 회의 후 인사이트까지 매끄러운 워크플로우를 만드는 데 집중했습니다.
이 글에서는 MeetMind의 사용자 경험을 구축하면서 내린 디자인 결정, 프론트엔드에서 마주한 도전 과제, 그리고 배운 교훈을 공유하고자 합니다.

어떻게 Hindsight 메모리를 활용해 AI 회의 어시스턴트가 실제로 기억하도록 만들었는가

몇 주 전 클라이언트가 말한 내용을 완전히 까먹은 회의에 너무 많이 참석한 적이 있습니다. 당신은 고개를 끄덕이며 앉아 있지만, 머리 뒤편 어딘가에 예산 숫자나 마감일을 언급했다는 기억이 남아 있습니다—하지만 그것을 꺼내지 못합니다. 그 느낌은 비용이 많이 듭니다. 신뢰를 무너뜨리고, 의사결정을 늦추며, 준비되지 않은 사람처럼 보이게 합니다.

그것이 MeetMind가 해결하고자 만든 문제이며, 가장 어려운 부분은 AI 자체가 아니라 AI에게 기억시키는 것이었습니다.

MeetMind는 AI 기반 사전 회의 어시스턴트 역할을 하는 웹 애플리케이션입니다. 전체 사용자 흐름은 다음과 같습니다.

회의 전: 연락처 이름을 입력하고 “Get Briefing”(브리핑 받기)을 클릭합니다. 앱은 해당 인물에 대한 모든 저장 정보를—노트, 약속, 프로젝트 상세—불러와 LLM에 전달하고, 구조화된 브리핑을 반환합니다. 여기에는 과거 상호작용 요약, 주요 알림, 실제 이력에 기반한 대화 시작 문구가 포함됩니다.

회의 후: 노트를 입력하고 “Save”(저장)를 클릭합니다. 시스템은 해당 연락처 이름 아래에 노트를 저장해 다음에 사용할 수 있게 합니다.

내부 구조: Python + Flask 백엔드, Groq의 추론 API 위에 Llama 3.3 70B, 그리고 Hindsight 아키텍처를 모델링한 JSON 기반 메모리 레이어.

인터페이스는 의도적으로 최소화되었습니다. 두 개의 패널, 두 개의 액션—“Generate my briefing”(브리핑 생성)과 “Save to memory”(메모리 저장)만 있습니다. 복잡성은 UI가 아니라 백엔드에 존재합니다.

저는 memory_vault.py—연락처 이력을 저장하고 검색하는 모듈—를 구현하고 이를 app.py의 Flask 라우트에 연결했습니다. 또한 ai_brain.py에서 프롬프트 엔지니어링을 담당하여, 원시 노트를 LLM이 신뢰할 수 있는 구조화된 브리핑으로 합성하도록 만들었습니다.

제가 계속 고민했던 질문: “상태가 없는 LLM에게 의미 있는 과거 컨텍스트를 어떻게 제공할까?”

Groq API에 대한 모든 호출은 처음부터 시작합니다. 모델은 세션 상태도, 이전 호출에 대한 메모리도, 연락처에 대한 지식도 없습니다. “Rahul”에 대한 브리핑을 요청하면, 현재 프롬프트 안에 명시적으로 알려주지 않으면 LLM은 Rahul이 누구인지 전혀 모릅니다.

이는 상태 없는 LLM API 위에 상태ful 애플리케이션을 구축하는 근본적인 제약입니다. 메모리를 외부에 두고, 쿼리 시 적절한 조각을 가져와 모델이 실제로 활용할 수 있는 형태로 컨텍스트에 주입해야 합니다. 이 파이프라인이 잘못되면 “AI 어시스턴트”는 현실과 전혀 연결되지 않은 그럴듯한 텍스트만 생성하게 되며, 회의 도구로서는 오히려 해가 됩니다.

순진한 접근법

모든 상호작용을 하나의 텍스트 블롭으로 저장하고 매번 프롬프트에 전체를 전달하는 방법입니다.
잠시 작동했지만, 기록이 쌓일수록 토큰 수가 급증하고 출력 품질이 떨어졌습니다. 모델은 오래된 무관한 세부 사항—예를 들어 6개월 전 커피 취향—에까지 집중했고, 현재 프로젝트 마감일은 묻히게 되었습니다. 브리핑은 잡음이 많고 신뢰할 수 없게 되었습니다. 우리는 무엇을 저장하고, 어떻게 검색하며, 모델에 전달하기 전에 어떻게 포맷할지에 대한 구조가 필요했습니다.

Hindsight를 참고한 메모리 설계

우리는 에이전트 메모리가 어떻게 설계되어야 하는지에 대한 레퍼런스로 Hindsight를 연구했습니다. 핵심 개념은 vectorize.io/what-is-agent-memory에서 잘 설명되어 있듯이, 메모리는 수동적인 저장소가 아니라 두 가지 깔끔한 원시 연산을 제공하는 능동적인 시스템이어야 합니다: retain(메모리 쓰기)와 recall(쿼리와 관련된 메모리 검색).

이 재구성은 중요한 의미를 가집니다. “이걸 어디에 저장할까?” 대신 “메모리를 쓰고 읽을 인터페이스는 무엇인가?” 라고 묻게 됩니다. 인터페이스가 정의되면 백엔드, 검색 전략, 프롬프트 포맷은 모두 교체 가능한 구현 세부 사항이 됩니다.

우리는 바로 이 인터페이스를 구현한 MockHindsightClient를 만들었고, 준비가 되면 JSON 백엔드를 전체 Hindsight SDK와 의미론적 벡터 검색으로 교체할 계획입니다.

Snippet 1 — 메모리 쓰기: retain()

# memory_vault.py
def retain(self, text):
    if "Meeting with " in text:
        content_split = text.split("Meeting with ")[1]
        parts = content_split.split(": ")
        if len(parts) >= 2:
            contact_name = parts[0].strip().lower()
            notes = ": ".join(parts[1:]).strip()
            if contact_name in self.storage:
                self.storage[contact_name].append(notes)
            else:
                self.storage[contact_name] = [notes]
            self._save()
            return True

이 메서드는 "Meeting with Rahul: He confirmed Tailwind CSS and wants the project in 3 weeks."와 같은 자연어 문자열을 받아 연락처 이름(소문자 정규화)과 노트 본문을 추출한 뒤, 해당 연락처의 JSON 리스트에 추가합니다.

": ".join(parts[1:]) 부분이 핵심입니다. ": " 로만 split하고 parts[1]만 사용하면, "follow up at 3:00 PM" 같은 콜론이 포함된 노트가 잘려버립니다. 인덱스 1부터 모두 합치면 콜론 개수와 관계없이 전체 문자열을 복원합니다. 우리는 테스트 중 이 버그를 발견했고, 놓친 노트를 복구했습니다.

_save()는 매 쓰기마다 호출됩니다. 느릴까요? 네. 하지만 쓰기와 플러시 사이에 앱이 충돌해 메모리를 잃는 것보다 약간 느린 저장이 더 나은 트레이드오프입니다. 내구성이 여기서는 핵심입니다.

Snippet 2 — 메모리 읽기: recall()

# memory_vault.py
def recall(self, query_name):
    contact_name = query_name.strip().lower()
    return self.storage.get(contact_name, [])

네 줄이지만 모든 결정이 의도적입니다. retain()에서 적용한 .strip().lower() 정규화가 recall()에도 그대로 적용돼 “Rahul”, “rahul”, “ rahul ” 모두 같은 키로 매핑됩니다. 정규화가 없으면 저장은 한 키에, 조회는 다른 키에 이루어져 결과를 얻지 못합니다.

연락처가 없을 때 None 대신 []를 반환하는 선택도 작지만 중요한 API 설계입니다. 호출자는 `len(history)

0 조회
Back to Blog

관련 글

더 보기 »

모바일 한여름 열풍

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...