직원들은 매일 우리 내부 지식 베이스에 15,000건이 넘는 질문을 합니다. 출시 3개월 만에 회사에서 가장 널리 쓰이는 내부 도구 중 하나가 되었습니다. 사람, 자동화 시스템, 에이전트가 함께 사용합니다.
Cerebras의 팀들은 데이터센터 운영, 칩 설계, 하드웨어, 학습, 추론, 클라우드 플랫폼 등 여러 영역에 걸쳐 일합니다. 해마다 수백 명이 새로 합류하면서 커뮤니케이션 채널은 같은 질문으로 채워지고 있었습니다.
“X는 어디서 찾나요?” “Y 전문가는 누구인가요?” “Z가 뭔가요?”
사람과 시스템이 유용한 정보에 닿도록 돕기 위해 우리는 Cerebras Knowledge를 만들었습니다.

내부 지식 베이스를 수직으로 펼쳐 그린 구조도
데이터가 있는 곳으로 찾아가기
조직 안에서 정보를 찾기는 어렵습니다. 데이터가 여러 도구에 흩어져 있다 보니, 분기마다 한 번쯤 누군가 똑같은 기발한 해법을 제안합니다. 모든 정보를 플랫폼 하나에 모아 기록하자는 것입니다. 물론 단일 진실 공급원(single source of truth)의 꿈은 현실에서 좀처럼 이루어지지 않습니다.
정보는 편하고 손에 익은 곳에서 만들어집니다. 문서의 제안 편집, Slack의 스레드, GitHub의 코드 참조, Jira의 상태 메타데이터가 그렇습니다. 이 플랫폼들은 저마다의 영역에 꼭 맞게 만들어졌고, 수년에 걸친 제품 엔지니어링과 분석으로 다듬어졌습니다. 풀 리퀘스트를 Google Docs에서 논의한다면 끔찍한 경험일 것입니다.
그래서 우리는 기존 행동을 거의 바꾸지 않아도 되는 시스템을 설계하기로 했습니다. 데이터 수집 쪽에서 이는 각 플랫폼에서 데이터를 직접 추출한다는 뜻입니다.
지식 베이스의 구조
우리 지식 베이스는 세 가지를 제공합니다.
- 내부 데이터를 모으고 저장하는 플랫폼
- 그 데이터를 질의하는 플랫폼
- 감사와 분석을 갖추고 인증과 권한을 강제하는 계층
핵심에는 여러 소스에서 온 임베딩, 원본 요약, 메타데이터를 담는 Postgres 테이블 하나가 있습니다. 시스템은 회사 전역에서 데이터를 끊임없이 수집해 바로 질의할 수 있는 데이터 저장소를 유지합니다.
우리는 단순하면서도 대부분의 데이터 형태를 다룰 수 있는 데이터 인터페이스를 원했습니다. Cerebras의 다른 개발자들이 맞춤형 커넥터를 만들 수 있기를 바라기도 했습니다. 그 결과는 의도적으로 단순합니다. Slack 스레드부터 넷리스트까지 모든 소스가 같은 임베딩 테이블에 들어가고, 그 테이블에 있는 것은 무엇이든 같은 인터페이스로 곧바로 질의할 수 있습니다.

여러 데이터 소스가 하나의 질의 가능한 임베딩 테이블로 모이는 구조
각 데이터 소스는 데이터가 무엇인지, 어떻게 연결하는지, 얼마나 자주 가져와야 하는지를 정의합니다. 그렇게 만들어진 임베딩 행은 Slack에서 왔든 코드 저장소, 문서 시스템, 맞춤형 데이터베이스에서 왔든 같은 인터페이스를 따릅니다.
Slack
Slack은 우리가 설계에서 가장 공들여야 했던 데이터 소스입니다. 회사 전체에서 가장 최신의 엔지니어링 논의가 오가는 곳이기 때문입니다.

Socket Mode에서 증류와 임베딩까지 이어지는 Slack 이벤트 흐름
비정형 Slack 대화를 처리하는 방법
처음에는 원문 텍스트에 단순 임베딩만 적용해도 충분한지 시험했습니다. 벡터 검색만으로는 관련 데이터를 모두 찾기에 부족하다는 사실을 금방 깨달았습니다.
Slack 메시지에는 몇 가지 어려움이 있습니다.
- 정보 밀도가 크게 다릅니다. “어 그래 마이크” 같은 말과 상세한 커널 설명이 똑같이 메시지 하나입니다.
- 메시지 길이가 제각각이고, 코사인 유사도에서는 짧은 메시지가 길고 자세한 메시지를 이기는 일이 잦습니다.
- 메시지의 의미가 주변 대화에 달려 있는 경우가 많습니다.
하이브리드 접근이 필요했습니다. 모든 스레드를 여러 검색 기법으로 동시에 찾을 수 있도록 Slack 수집을 만들었고, 각 기법은 서로의 약점을 메워 줍니다.
- 전문 검색(full-text search)은 임베딩이 뭉개 버리는 정확한 토큰을 잡아냅니다. 오류 문자열, 플래그 이름, 호스트 이름이 그렇습니다. 엔지니어가 오류 메시지를 그대로 붙여 넣었다면 정확한 어휘 일치가 거의 언제나 가장 좋은 증거이며, 의미가 아무리 비슷해도 그보다 앞설 수는 없습니다.
- 임베딩 검색은 다른 말로 표현한 같은 뜻을 잡아냅니다. “매니페스트 로드 후 복원이 멈춘다”라고 묻는 사람과 “체크포인트가 NFS 마운트에서 멈춘다”라고 답한 사람은 같은 단어를 하나도 쓰지 않을 수 있습니다. 벡터 유사도가 서로 다른 말로 쓴 질문과 답을 이어 줍니다.(1)
- 역문서 빈도(IDF)는 신호와 잡담을 가릅니다. 잘 알려지지 않은 설정 플래그처럼 희귀한 토큰을 중심으로 쓴 짧은 메시지는 순위에 올라야 마땅합니다. “좋네요, 감사합니다!”는 임베딩 공간에서 많은 질의와 가깝지만, 용어의 희귀성을 반영하는 순간 점수가 0에 가까워집니다.
- 시간 감쇠(age decay)는 Slack 답변에 유효기간이 있다는 사실을 반영합니다. 같은 질문에 답하는 스레드가 두 개일 때, 6개월 전 스레드는 이미 사라진 인프라를 설명하고 있을 수 있습니다. 다른 조건이 같다면 더 새로운 스레드가 이깁니다.

어떤 채점기도 단독으로는 신뢰하지 않습니다. 각 기법이 같은 말뭉치에 대해 저마다의 순위를 만들고, 질의 시점에 그 순위들을 융합합니다(리랭킹 참고).
Socket Mode
데이터를 실시간으로 모으기 위해 워크스페이스에 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자 이상입니다.
- 버스트 안 메시지 하나 이상에 리액션이 달려 있어 사회적 신호를 더해 줍니다.

Slack 스레드를 같은 작성자의 버스트로 나누고 신호 기준으로 거르는 과정
증류를 마친 뒤, 기준을 통과한 버스트는 임베딩되어 스레드 수준 레코드와 나란히 임베딩 테이블에 저장됩니다.
코드 저장소
처음에는 코드 저장소 임베딩이 꼭 필요한지를 두고 논쟁했습니다. 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 에이전트가 맡습니다.
플래너(Planner): 가벼운 LLM 패스가 질의와 현재 프로젝트를 살펴보고 search, search_slack, subsystem_index 같은 검색 도구 중 무엇을 부를지 고릅니다.
실행기(Executor): 시스템이 그 도구 호출들을 병렬로 펼쳐 실행하고, 결과를 모아, 점수와 최신성, 소스 힌트를 담은 공유 증거 스키마로 정규화합니다.
합성(Synthesis): 마지막 LLM 패스가 타입이 정의된 증거 묶음과 원래 질문을 받아 UI에 보여줄 답을 만듭니다. 인용과 주의사항, 소스를 넘나드는 종합까지 여기에 담깁니다.
사용자가 보기에 웹 UI는 그저 “질문하면 답이 온다”입니다. 그 아래에서는 MCP 클라이언트가 직접 재현할 수 있는 것과 같은 플래너 → 실행기 → 합성기 패턴이 돌아갑니다.

MCP는 검색 기본 도구를 그대로 노출하고, 웹 UI는 완전한 에이전트 파이프라인을 실행합니다
조직화
말뭉치가 커지자 “모든 곳에서 모든 것을 검색”은 금세 쓸모가 없어졌습니다. 컴파일러 팀 엔지니어는 검색 결과에 인프라 런북이 나오길 원하지 않았고, 그 반대도 마찬가지였습니다. 프로젝트는 검색을 기본값부터 적절하게 만드는 우리의 방법입니다.
프로젝트와 범위 지정 검색
질의가 도는 작업 공간을 조직하는 기본 수단으로 프로젝트를 도입했습니다. 프로젝트는 이름 붙인 데이터 소스 묶음입니다. 팀이나 이니셔티브와 관련 있는 특정 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.