Cerebras는 사내 지식 베이스를 어떻게 만들었나
TMThttps://www.cerebras.ai/blog/how-we-built-our-knowledge-base
직원들은 매일 사내 지식 베이스에 1만 5천 건이 넘는 질문을 던집니다. 출시 3개월 만에 회사에서 가장 널리 쓰이는 사내 도구 중 하나가 되었고, 사람뿐 아니라 자동화 시스템과 에이전트도 사용합니다.
Cerebras에서는 데이터 센터 운영, 칩 설계, 하드웨어, 학습, 추론, 클라우드 플랫폼 등 다양한 분야에서 팀들이 일하고 있습니다. 해마다 수백 명의 신규 직원이 합류하면서 커뮤니케이션 채널은 똑같은 질문으로 채워져 갔습니다.
"X는 어디서 찾을 수 있나요?" "Y 전문가는 누구인가요?" "Z가 뭔가요?"
그래서 사람과 시스템을 유용한 정보와 이어 주기 위해 Cerebras Knowledge를 만들었습니다.
정보가 있는 곳으로 찾아가기
조직 안에서 정보를 찾기란 어렵습니다. 데이터가 여러 도구에 흩어져 있다 보니, 분기마다 한 번쯤은 누군가 똑같은 '기막힌 해결책'을 제안합니다. 모든 것을 하나의 플랫폼에 기록해서 모든 정보를 한곳에 모으자는 것이죠. 물론 단일 진실 공급원(single source of truth)이라는 꿈이 실제로 이루어지는 경우는 드뭅니다.
정보는 만들기 편하고 손에 익은 곳에서 생겨납니다. 문서의 수정 제안, Slack의 스레드, GitHub의 코드 참조, Jira의 상태 메타데이터가 그렇습니다. 이들 플랫폼은 각자의 영역에 맞춰 만들어졌고, 수년간의 제품 엔지니어링과 분석을 거쳐 최적화되어 있습니다. 풀 리퀘스트 논의를 Google Docs에서 한다면 끔찍한 경험이 될 것입니다.
그래서 우리는 기존 업무 방식을 거의 바꾸지 않아도 되는 시스템을 설계하기로 했습니다. 데이터 수집 측면에서 이는 각 플랫폼에서 데이터를 직접 추출한다는 뜻입니다.
지식 베이스의 구조
우리 지식 베이스는 세 가지를 제공합니다.
- 사내 데이터를 수집하고 저장하는 플랫폼.
- 그 데이터를 질의하는 플랫폼.
- 감사와 분석 기능을 갖추고 인증·인가를 강제하는 계층.
핵심에는 여러 소스에서 온 임베딩, 원본 요약, 메타데이터를 담는 단일 Postgres 테이블이 있습니다. 시스템은 회사 곳곳에서 데이터를 끊임없이 수집해 언제든 질의할 수 있는 데이터 저장소를 유지합니다.
데이터 인터페이스는 단순하면서도 대부분의 데이터 형태를 다룰 수 있기를 원했습니다. 또 Cerebras의 다른 개발자들도 커스텀 커넥터를 만들 수 있기를 바랐습니다. 그 결과물은 의도적으로 단순합니다. Slack 스레드부터 넷리스트(netlist)까지 모든 소스가 같은 임베딩 테이블에 쌓이고, 그 테이블에 있는 것은 무엇이든 같은 인터페이스로 즉시 질의할 수 있습니다.
각 데이터 소스는 데이터가 무엇인지, 어떻게 연결하는지, 얼마나 자주 가져와야 하는지를 정의합니다. 그렇게 만들어진 임베딩 행은 Slack에서 왔든 코드 저장소, 문서 시스템, 커스텀 데이터베이스에서 왔든 모두 같은 인터페이스를 따릅니다.
Slack
Slack은 설계할 때 가장 중요하게 고려해야 했던 데이터 소스였습니다. 회사 전반에서 가장 최신의 엔지니어링 논의가 오가는 곳이기 때문입니다.
비정형 Slack 대화를 처리하는 방법
처음에는 원문 텍스트에 단순 임베딩만 적용해도 충분한지 시험해 봤습니다. 그러나 벡터 검색만으로는 관련 데이터를 모두 찾아내기에 부족하다는 사실을 금방 깨달았습니다.
Slack 메시지에는 몇 가지 어려움이 있습니다.
- 정보 밀도가 크게 들쭉날쭉합니다. "어 그래 마이크 좋아" 같은 답장도, 커널을 상세히 설명한 글도 똑같이 하나의 메시지입니다.
- 메시지 길이가 제각각인데, 코사인 유사도에서는 짧은 메시지가 길고 상세한 메시지를 이기는 경우가 많습니다.
- 메시지의 의미가 앞뒤 대화에 좌우되는 경우가 흔합니다.
하이브리드 접근이 필요했습니다. 우리는 모든 스레드를 여러 검색 기법으로 동시에 찾을 수 있도록 Slack 수집을 구축했고, 각 기법이 서로의 약점을 보완하게 했습니다.
- 전문(full-text) 검색은 임베딩이 뭉개 버리는 정확한 토큰을 잡아냅니다. 오류 문자열, 플래그 이름, 호스트 이름 같은 것들입니다. 엔지니어가 오류 메시지를 그대로 붙여 넣었다면 정확한 어휘 일치가 거의 항상 최고의 근거이며, 의미 유사도가 아무리 높아도 이를 앞질러서는 안 됩니다.
- 임베딩 검색은 같은 뜻을 다른 말로 표현한 경우를 잡아냅니다. "매니페스트 로드 후 복원이 멈춘다"라고 질문한 사람과 "NFS 마운트에서 체크포인트가 멎는다"라고 답한 사람은 겹치는 어휘가 하나도 없을 수 있습니다. 서로 다른 단어로 쓰인 질문과 답을 이어 주는 것이 벡터 유사도입니다.(1)
- **역문서빈도(IDF)**는 의미 있는 신호와 잡담을 가려냅니다. 잘 알려지지 않은 설정 플래그처럼 희귀한 토큰을 중심으로 쓰인 짧은 메시지는 상위에 오를 자격이 있습니다. "좋네요, 감사합니다!"는 임베딩 공간에서 많은 질의와 가깝게 붙어 있지만, 용어 희귀도를 반영하면 점수가 0에 가까워집니다.
- **시간 감쇠(age decay)**는 Slack의 답변에 유효 기간이 있다는 사실을 반영합니다. 같은 질문에 답하는 스레드가 둘 있어도, 6개월 전 스레드는 이미 사라진 인프라를 설명하고 있을 수 있습니다. 다른 조건이 같다면 최신 스레드가 이깁니다.
어떤 스코어러도 단독으로는 신뢰하지 않습니다. 각 기법이 같은 말뭉치에 대해 자기만의 순위를 만들고, 이 순위들을 질의 시점에 융합합니다(리랭킹 참고).
소켓 모드
데이터를 실시간으로 수집하기 위해 워크스페이스에 Slack 봇을 설치하고 소켓 모드(Socket Mode)로 실행했습니다. Slack이 모든 메시지 이벤트를 지속 연결된 WebSocket으로 밀어 주기 때문에, Web API를 폴링하며 요청 한도를 소진하지 않고도 실시간 업데이트를 받을 수 있습니다.
이벤트가 도착하면 즉시 수신 확인을 보내고, 고정된 이벤트 ID로 중복을 제거한 뒤, 수집 컨슈머가 처리하도록 해당 메시지를 표시해 둡니다.
수집 컨슈머는 새 메시지를 낱개로 저장하지 않습니다. 메시지가 속한 스레드를 찾아내고, 부모 메시지와 모든 답글을 포함한 대화 전체를 Slack API에서 다시 가져옵니다. 그런 다음 스레드 전체를 하나의 행으로 다시 씁니다. 기존 스레드에 답글이 달리면 부모와 형제 메시지를 전부 다시 끌어오므로, 저장된 내용과 참여자 목록, 마지막 활동 시각이 항상 대화 전체를 반영합니다.
우리 시스템에서는 Slack 채널마다 별도의 데이터 소스를 둡니다. 덕분에 데이터 신선도를 세밀하게 조율할 수 있습니다. 예를 들어 어떤 팀은 바쁘게 돌아가는 장애 채널을 더 자주 수집하도록 설정할 수 있습니다.
스레드와 메시지
원본 콘텐츠에 Postgres 전문 검색(GIN) 인덱스를 유지하고 있어서, Slack 원문 텍스트는 저장되는 즉시 키워드로 검색할 수 있습니다. 하지만 쓸모 있는 벡터 검색을 위해서는 추가 처리를 거칩니다.(8)
증류(distillation) 단계에서는 LLM이 스레드 전체에서 구조화된 데이터를 뽑아냅니다.
- 엔지니어가 실제로 검색할 법한 한 줄짜리 질문.
- 짧은 요약.
- 해결 방법.
- 언급된 시스템과 코드 참조.
이렇게 뽑은 데이터 포인트를 임베딩해 공유 임베딩 테이블에 씁니다. 원본 대화록을 그대로 임베딩하지는 않습니다. 실험해 보니 스레드를 일관된 형식으로 정규화했을 때 정확도가 크게 올랐습니다.(7,9) 덧붙인 메타데이터도 의미 기반 매칭에 더 유용한 신호를 제공합니다.
버스팅
이 시점에서 Slack 검색은 꽤 좋아졌지만, 같은 문제가 계속 나타났습니다. 긴 스레드 안의 중요한 메시지가 스레드 수준 요약에 항상 담기지는 않는다는 것이었습니다.
개별 메시지의 신호를 키우기 위해 버스팅(bursting)을 사용합니다. 버스트는 같은 작성자가 연달아 남긴 메시지 묶음입니다. 각 버스트는 스레드 주제를 앞에 붙여 맥락을 더한 상태로 임베딩하는데(2), 답이 곁가지 메시지 하나에 들어 있고 그 어휘가 스레드 요약에는 전혀 반영되지 않는 경우가 있기 때문입니다. 버스트 임베딩 덕분에 그런 메시지도 단독으로 찾을 수 있습니다.
신호가 약한 데이터가 데이터베이스에 들어오지 못하도록, 각 버스트는 여러 신호의 가중 조합으로 점수를 매기고 문턱값을 넘어야만 임베딩합니다.
- 말뭉치 전체에서 비교적 희귀한 토큰(IDF 4.0 이상)을 포함한다.
- 버스트를 합친 길이가 200자 이상이다.
- 버스트 안의 메시지 하나 이상에 리액션이 달려 있어 소셜 가점을 받는다.
증류를 거친 뒤, 기준을 통과한 버스트는 임베딩되어 스레드 수준 레코드와 나란히 임베딩 테이블에 저장됩니다.
코드 저장소
처음에는 코드 저장소 임베딩이 과연 필요한지 논쟁이 있었습니다. Claude Code를 비롯한 커맨드라인 도구가 부상하면서 "grep만 있으면 된다"라는 분위기였고, 코드 임베딩을 만드는 일이 직관에 어긋나 보였습니다. 업계 사람들과 이야기를 나누고 대규모 코드베이스의 시맨틱 검색에 관한 Cursor의 연구 결과를 읽은 뒤, 시도해 보기로 했습니다.
사내에는 저장소가 많고, 40GB가 넘는 것도 있습니다. 가장 큰 고민은 이것들을 어떻게 효율적으로 최신 상태로 유지하느냐였습니다.
CocoIndex로 코드 임베딩 유지하기
여러 실험 끝에 코드베이스 벡터화에 특화된 오픈소스 문서 임베딩 프레임워크인 CocoIndex에 정착했습니다.
저장소마다 언어별 정규식 경계를 굵은 단위에서 세밀한 단위 순서로 적용해 코드를 분할합니다. 분할기는 클래스처럼 상위 수준 경계를 먼저 시도하고, 그래도 청크가 너무 크면 메서드 경계로, 그다음에는 더 작은 블록으로 내려갑니다. 이렇게 만든 청크를 임베딩해 벡터를 Postgres에 씁니다. 파일 하나가 파일 수준 레코드와 함수 수준 레코드처럼 구체성이 다른 여러 임베딩을 만들어 낼 수도 있습니다.
CocoIndex는 동기화 메타데이터를 Postgres에 기록합니다. 커밋이 있을 때마다 저장소 전체를 다시 계산하는 대신 변경된 코드 청크만 다시 임베딩해 내보냅니다. 동기화 상태와 임베딩 저장소가 같은 데이터베이스에 있는 우리 구성에서 특히 잘 맞았습니다.
코드베이스가 늘어나면서 저장소 온보딩은 팀이 직접 제출할 수 있는 설정 파일 방식으로 바꿨고, 파일 경로 수준의 허용 목록과 차단 목록도 지원합니다.
커스텀 데이터 소스
이미 자체 데이터베이스를 가진 팀들은 지식 베이스에 참여하겠다고 데이터를 Slack이나 문서 시스템으로 옮기고 싶어 하지는 않았습니다. 기존 테이블 위에서 같은 질의 창구를 쓰고 싶어 했습니다.
이를 지원하기 위해 커스텀 소스를 플러그인 스크립트로 취급합니다. 팀은 자기 시스템에서 데이터를 읽어 우리 임베딩 테이블 모양의 행을 내보낼 줄 아는 작은 Python 모듈과, 그에 대응하는 데이터 소스 항목을 담아 풀 리퀘스트를 올립니다.
스크립트가 다른 임베딩 행과 같은 스키마로 공유 데이터베이스에 쓰기만 하면, 나머지 스택은 아무것도 바꾸지 않아도 그대로 동작합니다. 시스템의 다른 어느 곳에서도 특별한 처리 없이, 그 데이터는 Slack·코드·문서와 나란히 질의 대상이 됩니다.
플래닝과 도구 팬아웃
모든 질의는 먼저 짧은 플래닝 패스를 거칩니다. 여기서 LLM이 어떤 도구와 데이터 소스가 중요할지 판단합니다. 주요 도구는 다음과 같습니다.
subsystem_index: 파일별 LLM 요약.search: Slack, 위키, 코드 등 색인된 소스 전반을 아우르는 통합 벡터 파이프라인. 내부에서 병합과 리랭킹을 수행.search_slack: Slack 직접 검색.search_code: 소스 저장소에 대한 ripgrep 검색.recent_prs: 질문과 관련된 최근 풀 리퀘스트.who_knows: 해당 주제에 전문성을 입증해 온 사람들.
플래너는 우리가 무엇을 색인해 두었는지에 대한 압축된 설명을 바탕으로 동작합니다. 어떤 프로젝트가 있고, 각 프로젝트에서 어떤 소스를 쓸 수 있으며, 각 소스가 어떤 질문에 잘 답하는지가 담겨 있습니다. 사용자의 질의와 활성 범위가 주어지면 플래너가 도구 선택을 내보내고, 실행기가 이를 병렬로 펼쳐 실행한 뒤 공통 증거 형식으로 정규화해 최종 합성 LLM에 넘깁니다.(4)
리랭킹
질의와 어휘만 겹칠 뿐 전혀 다른 질문에 답하는 문서가 상위에 떠오를 수 있습니다. 리랭킹 전에, 서로 호환되지 않는 검색기들의 결과 목록을 상호 순위 융합(reciprocal rank fusion, RRF)으로 합칩니다. 각 문서에 대해, 그 문서가 등장하는 목록마다 weight / (60 + rank)를 더합니다. 기본 가중치는 1.0, 평활 상수는 60입니다.
평활 상수 덕분에 한 번의 강한 표보다 여러 검색기의 합의가 더 중요해집니다. 여러 검색기에서 고르게 상위권에 오른 문서가, 단 한 곳에서만 1위인 문서를 이길 수 있습니다. 그다음 중복 청크를 원본 소스 하나로 합치고, 파일 하나가 기여할 수 있는 결과 수에 상한을 두어, 더 다양한 상위 20건을 얻습니다.
원래 질의와 이 후보들을 소형 리랭커 모델에 보냅니다. 모델이 각 문서에 0점부터 10점까지 점수를 매기고, 상위 10건만 남깁니다.(6)
순위가 확정되면 살아남은 문서에 맥락을 다시 붙입니다. 예를 들어 위키의 한 섹션이 매칭되면 이웃한 두 섹션을 함께 가져와서, 청킹 과정에서 떨어져 나간 제목·전제 조건·주의 사항이 사라지지 않게 합니다. 덕분에 독자는 중요한 맥락이 빠진 외톨이 문단이 아니라 온전한 스니펫을 받게 됩니다.
결국 검색의 출력은 풍부한 증거 꾸러미입니다. 서로 다른 검색기의 결과를 융합하고, 소스 수준에서 중복을 제거하고, 실제 질문에 맞춰 리랭킹한 다음에야 주변 맥락을 덧붙인 결과물입니다.
MCP
MCP 통합에서는 검색 빌딩 블록을 "이 질문에 답해 줘"라는 단일 엔드포인트 뒤에 숨기지 않고, 직접 호출할 수 있는 도구로 노출합니다. 이 도구들은 의도적으로 단순하게, 가능한 한 LLM 없이 동작하도록 만들어서 클라이언트가 빠르고 저렴하게 질의할 수 있습니다.(5)
각 MCP 도구는 search_slack, search_code, search, who_knows처럼 하부의 검색 프리미티브 하나에 대응합니다. 도구의 입력과 출력은 좁고 구조화되어 있으며 안정적이어서, 도구 안에 별도의 오케스트레이션 로직을 심지 않고도 어떤 클라이언트나 에이전트에서든 쉽게 호출할 수 있습니다.
대부분의 도구는 벡터 검색, 어휘 검색, ripgrep 같은 질의 파이프라인 하나를 실행하고, 가벼운 점수 휴리스틱을 적용한 뒤, 가공하지 않은 증거 행을 반환합니다.
Claude Code든 다른 MCP 호환 에이전트든, 에이전트가 곧 오케스트레이션 엔진이 됩니다. 어떤 도구를 어떤 순서로 호출할지, 결과를 어떻게 조립해 최종 답변이나 코드 수정으로 만들지 에이전트가 결정합니다. 검색 계층 자체는 요청을 처리하는 데 그런 LLM의 결정에 의존하지 않습니다.
웹 UI
웹 UI에도 같은 도구들이 있지만, 여기서는 모든 사용자 질문에 대해 처음부터 끝까지 도는 완결된 질의 파이프라인에 연결되어 있습니다. 플래너와 실행기 단계는 UI 에이전트가 담당합니다.
플래너: 가벼운 LLM 패스가 질의와 활성 프로젝트를 살펴본 뒤 search, search_slack, subsystem_index 같은 검색 도구 중 무엇을 호출할지 고릅니다.
실행기: 시스템이 그 도구 호출들을 병렬로 펼쳐 실행하고, 결과를 모아 점수·최신성·소스 힌트가 담긴 공통 증거 스키마로 정규화합니다.
합성: 마지막 LLM 패스가 타입이 정의된 증거 묶음과 원래 질문을 받아, 인용과 주의 사항, 소스 간 종합까지 갖춰 UI에 표시될 답변을 만들어 냅니다.
사용자 입장에서 웹 UI는 그저 "질문하면 답이 나오는" 화면입니다. 속을 들여다보면 MCP 클라이언트가 명시적으로 재현할 수 있는 것과 같은 플래너 → 실행기 → 합성기 패턴이 돌아가고 있습니다.
조직화
말뭉치가 커지자 "모든 곳에서 모든 것을 검색"하는 방식은 금세 쓸모가 없어졌습니다. 컴파일러 팀 엔지니어는 검색 결과에 인프라 런북이 섞이는 것을 원치 않았고, 그 반대도 마찬가지였습니다. 검색이 기본적으로 자기 일과 관련 있게 만드는 장치가 바로 프로젝트입니다.
프로젝트와 범위 지정 검색
질의가 도는 작업 공간을 조직하는 기본 수단으로 프로젝트를 도입했습니다. 프로젝트는 이름이 붙은 데이터 소스 묶음으로, 특정 팀이나 이니셔티브와 관련 있는 Slack 채널, 코드 저장소, 사내 데이터베이스, 문서 공간으로 이루어집니다.
프로젝트는 의도적으로 가볍게 설계했습니다. 공용 장애 채널이나 중앙 플랫폼 저장소 같은 데이터 소스 하나를 복제하지 않고 여러 프로젝트에서 참조할 수 있습니다.
온보딩과 기본값
온보딩 과정에서 사용자는 ML 학습 인프라, 컴파일러, 데이터 센터 운영처럼 자기 업무 방식에 맞는 기본 프로젝트를 고르거나 새로 만들라는 안내를 받습니다.
이 기본 프로젝트는 사용자 프로필에 저장되어 질의 범위를 자동으로 좁혀 줍니다. 새로 온 엔지니어도 어떤 Slack 채널과 저장소, 문서 공간이 중요한지 먼저 익히지 않고도 알짜 답변을 얻을 수 있습니다.
맺으며
결국 이 지식 베이스가 통하는 이유는 모든 것을 하나의 경직된 시스템에 몰아넣는 대신, 정보가 이미 살고 있는 곳으로 찾아가기 때문입니다. 다양한 검색 기법을 조합한 덕분에 증거를 빠르게 찾아낼 수 있습니다. 그 결과, 실제 회사 데이터를 다루기에 충분히 유연하면서도 Cerebras가 계속 성장해도 쓸모를 잃지 않을 만큼 구조화된 검색 경험이 만들어졌습니다.
참고 문헌
- Malkov and Yashunin, Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs, arXiv:1603.09320 / IEEE TPAMI 2018.
- Anthropic, Introducing Contextual Retrieval, 2024.
- Cormack, Clarke, and Büttcher, Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods, SIGIR 2009.
- Li et al., Search-o1: Agentic Search-Enhanced Large Reasoning Models, arXiv:2501.05366, 2025.
- Anthropic, Code Execution with MCP, 2025.
- Liu et al., Lost in the Middle: How Language Models Use Long Contexts, arXiv:2307.03172, 2023.
- Anthropic, Use XML Tags.
- Salesforce/Slack Engineering, How Slack AI Processes Billions of Messages.
- Improving Agents, Best Nested Data Format.
- Cursor, Improving Agent with Semantic Search, 2025.