본문으로 건너뛰기

LLM과 Event Sourcing으로 조직 지식 그래프 유지하기

Arkency는 LLM Extraction과 이벤트 소싱으로 비정형 조직 기록을 감사 가능한 지식 그래프로 축적합니다.

이 요약은 AI가 원문을 분석해 생성했습니다. 정확한 내용은 원문 기준으로 확인하세요.

TL;DR

Arkency는 회의·Slack·이메일·GitHub 같은 비정형 기록을 단일 ingestion endpoint로 모은 뒤 LLM Extraction으로 엔티티와 typed relation을 PostgreSQL 그래프에 축적하는 Planet Arkency를 구축했습니다. 폐쇄형 ontology가 허용된 노드와 관계를 제한하고, search_nodes·alias·trigram·bge-m3 embedding을 결합한 hybrid search가 같은 사람이나 프로젝트의 표기 변형을 하나로 연결합니다. 모든 변경은 before·after diff와 원문·도구 호출 read set을 함께 기록하며, 사람 검토와 충돌 감지를 거쳐 이벤트 소싱 파이프라인에서 적용됩니다. 거의 2000개 노드와 5200개가 넘는 엣지, 약 300회의 Extraction, 3600개가 넘는 이벤트가 쌓였고 MCP를 통해 외부 AI assistant에도 출처 기반 질의를 제공합니다.

섹션별 상세

01
Arkency는 회의·Slack·이메일·GitHub·개인 메모에 흩어진 결정과 맥락을 잊는 문제를 해결하기 위해 Planet Arkency를 구축했습니다. 모든 입력은 단일 ingestion endpoint로 들어오고 Zapier나 n8n이 각 외부 소스의 내용을 이 endpoint에 전달합니다. LLM은 비정형 콘텐츠에서 엔티티·새로운 사실·관계를 추출하므로 기존 parser로 처리하기 어려운 조직 신호까지 하나의 지식 흐름에 편입됩니다.
02
시스템은 반복해서 등장하는 사람·프로젝트·도구·결정을 엔티티로, 이들 사이의 의미를 typed relation으로 저장합니다. PostgreSQL의 nodes 테이블과 edges 테이블, 양쪽의 jsonb attributes만으로 구성하고 엣지는 source·target·relation 조합을 유일하게 유지합니다. 깊은 multi-hop traversal에는 전용 graph database가 더 적합할 수 있지만, 단순한 저장 계층은 다른 adapter로 옮기기 쉬운 장점이 있습니다.
03
허용 가능한 노드와 관계는 YAML에 담긴 폐쇄형 ontology로 제한됩니다. 이 목록이 Extraction 프롬프트의 markdown 표와 결과 스키마의 enum으로 변환되므로 LLM은 목록 밖의 타입을 생성할 수 없습니다. 초기의 open ontology가 짧은 시간 안에 그래프의 분류를 혼란스럽게 만든 경험 때문에, 찾을 대상을 미리 지정하는 방식이 선택됐습니다.
yaml
# from config/ontology.yml
node_kinds:
  - kind: person
    description: "team member, candidate, client contact, external person"
  - kind: decision
    description: "formal decision requiring group verdict — for casual suggestions use idea"
edge_relations:
  - relation: works_on
    signature: "person --works_on--> project"

YAML 온톨로지에서 허용된 노드 종류와 관계의 방향을 정의하는 부분입니다.

Planet Arkency의 ontology 화면으로 8개 노드 종류와 9개 관계를 목록으로 보여줍니다.
Diagram화면에는 project, person, company, idea, known_problem, question, release, content 노드 종류가 각각의 의미와 함께 표시됩니다. 아래에는 person이 project에서 works_on 관계를 맺고, company가 project를 uses하며, person이 known_problem을 reported하고 idea를 raised하는 식의 방향성 있는 edge relation이 나열됩니다. 글에서 설명한 폐쇄형 ontology가 실제 UI에서 허용된 분류와 관계의 목록으로 관리되는 모습을 뒷받침합니다.
04
하나의 조직 그래프를 모든 용도로 공유하지 않고 multi-tenant 구조로 분리한 점도 핵심 설계입니다. 내부 Arkency 그래프는 사람·프로젝트·결정처럼 CRM에 가까운 어휘를 쓰고, Rails Event Store 유지보수자 그래프는 release·known problem·community content를 사용합니다. 각 bounded context가 별도의 그래프와 ubiquitous language를 제공하므로 도메인별 의미를 섞지 않고 동일한 처리 장치를 재사용합니다.
ruby
node = Node.find_or_initialize_by(name: data[:name])
enforce_status!(data[:name], data[:status], node) # raises when the model's new/existing claim disagrees with the DB
was_new = node.new_record?
node.assign_attributes(short_description: ..., description: ..., attrs: node.attrs.merge(attrs))
changes = node.changes.except("updated_at", "created_at", "kind", "slug")
{
  op: was_new ? "create" : "update",
  node_id: node.persisted? ? node.id : nil,
  changes: changes
}

LLM이 반환한 노드의 신규·기존 상태를 데이터베이스와 대조하고 dirty tracking으로 필드별 변경 내역을 만드는 코드입니다.

05
Extraction 결과는 노드와 엣지뿐 아니라 기존 그래프를 어떻게 바꿀지 나타내는 구조화된 제안입니다. 노드는 new 또는 existing 상태를 선언하고 기존 엔티티의 canonical name을 정확히 재사용하며, 서버는 ActiveRecord dirty tracking의 before·after 쌍으로 생성·수정 diff를 계산합니다. LLM의 신규·기존 판정이 데이터베이스와 맞지 않으면 자연어 피드백을 같은 대화에 돌려보내 재시도하므로 모델의 주장을 그대로 저장하지 않습니다.
Extraction 결과가 노드와 엣지 생성·수정 제안으로 변환되는 화면입니다.
Screenshot화면은 pjurewicz 노드의 기존 description이 수정되는 과정과, 브라우저 확장 기능을 위한 idea 노드가 새로 생성되는 내용을 before·after 형태로 보여줍니다. 이어서 idea와 ruby_event_store-browser 사이의 about 관계, pjurewicz가 idea를 raised하고 project에서 works_on하는 관계가 함께 제시됩니다. 이는 LLM이 반환한 구조화된 결과를 서버가 그래프 변경 제안과 필드별 diff로 바꾸는 설계와 연결됩니다.
06
가장 어려운 문제인 identity resolution은 검색 전 쓰기 금지, alias, hybrid search의 세 층으로 처리됩니다. LLM은 노드를 만들기 전에 search_nodes를 호출하고, 모호한 후보나 연결 맥락이 필요할 때 get_node_edges를 조회하며, Piotrek·Piotr Jurewicz처럼 같은 사람을 가리키는 표기를 하나의 canonical node와 여러 alias로 묶습니다. pg_trgm의 GIN 인덱스 기반 trigram similarity가 철자 차이를 찾고, Ollama에서 실행한 bge-m3 embedding과 pgvector가 문자를 공유하지 않는 의미적 일치까지 보완합니다.
ruby
def self.hybrid_search(query, limit: 10)
  # fuzzy match on canonical names and aliases, powered by pg_trgm
  by_name = where("similarity(nodes.name, ?) > 0.3", query)
  by_alias = joins(:aliases).where("similarity(node_aliases.name, ?) > 0.3", query)
  trigram_results = union_by_best_similarity(by_name, by_alias)

  response = RubyLLM.embed(query, model: "bge-m3", provider: :ollama)
  semantic_results = nearest_neighbors(:embedding, response.vectors, distance: "cosine")
    .select { |n| n.neighbor_distance < SEMANTIC_THRESHOLD }

  merge_and_rank(trigram_results, semantic_results, limit)
end

정확한 문자열 유사도와 의미적 embedding 검색 결과를 합치고 순위를 매기는 노드 검색 함수입니다.

Extraction 중 search_nodes 도구를 호출해 프로젝트와 아이디어, 사람 노드를 조회한 기록입니다.
Screenshot화면의 첫 번째 roundtrip은 ruby_event_store-browser를 검색해 여러 project·release·idea·person 노드를 읽고, 두 번째 호출은 browser extension point stylesheets를 검색해 관련 idea를 좁힙니다. 세 번째 호출에서는 pjurewicz라는 이름으로 person 노드를 확인합니다. 이 read set은 모델이 노드를 생성하거나 관계를 연결하기 전에 어떤 검색 결과를 참고했는지 보여주며 identity resolution과 provenance 설계를 구체화합니다.
07
그래프 변경은 사람이 승인할 수 있는 제안으로 남고, 적용 과정은 이벤트 소싱 상태 기계로 보호됩니다. TranscriptIngested에서 ExtractionRequested, KnowledgeExtracted, GraphChangeProposed를 거쳐 GraphChangeApplied 또는 GraphChangeConflicted로 이어지며, 제안 시점과 적용 시점의 현재 상태가 다르면 적용을 중단하고 충돌 행과 사람이 읽을 수 있는 설명을 남깁니다. 노드·엣지의 필드별 diff, 원문 Extraction, 모델이 읽은 도구 호출, 토큰 비용까지 read model로 제공하므로 자동화와 감사를 함께 유지합니다.
Claude Opus 4.8 기반 Extraction별 토큰 사용량과 처리 시간, 비용을 집계한 화면입니다.
Chart표에는 claude-opus-4-8을 사용한 여러 Extraction의 입력·출력 토큰, cached·write 토큰, 처리 시간과 비용이 기록되어 있습니다. 예시 비용은 $0.07, $0.12, $0.13, $0.18, $0.25, $0.28 등이며 처리 시간은 17.3초에서 58.6초 범위로 표시됩니다. 반복되는 시스템 프롬프트와 콘텐츠를 Prompt Caching으로 처리해 입력 비용을 낮추고 Extraction별 운영 비용을 추적한다는 글의 설명과 직접 연결됩니다.
08
그래프에 대한 Research도 일반 입력과 같은 파이프라인으로 흘려보내는 구조입니다. 특정 노드의 기존 정보로 검색 범위를 잡은 모델이 Anthropic의 server-side web_search와 web_fetch로 자료를 모은 뒤 Research brief를 만들고, 결과는 별도의 TranscriptIngested 이벤트가 되어 Extraction·제안·검토를 거칩니다. 운영 시점의 그래프는 거의 2000개 노드와 5200개가 넘는 엣지, 약 300회의 Extraction, 3600개가 넘는 이벤트를 보유하며 MCP를 통해 외부 AI assistant도 출처와 함께 질의할 수 있습니다.
ruby
chat = RubyLLM
  .chat(model: MODEL)
  .with_params(tools: [
    { type: "web_search_20250305", name: "web_search", max_uses: 10 },
    { type: "web_fetch_20250910", name: "web_fetch", max_uses: 10 }
  ])
  .with_schema(ResearchBriefSchema.build)

그래프의 특정 노드를 대상으로 Anthropic의 server-side web_search와 web_fetch를 사용하는 Research 작업을 구성합니다.

용어 해설

지식 그래프(Knowledge Graph)
지식 그래프는 사람·프로젝트·결정 같은 엔티티를 노드로 저장하고, 엔티티 사이의 의미 있는 관계를 타입이 지정된 엣지로 연결하는 구조입니다. 문장 속 사실을 데이터로 바꾸므로 관계를 검색·순회·집계할 수 있으며, 이 글에서는 조직의 기록을 지속적으로 축적하고 출처까지 추적하는 기반으로 사용됩니다.
온톨로지(Ontology)
온톨로지는 그래프에 허용할 노드 종류와 관계 종류를 정의한 어휘 체계입니다. Planet Arkency는 YAML 파일에 정의된 폐쇄형 온톨로지를 Extraction 프롬프트와 스키마의 enum으로 주입해 LLM이 임의의 타입을 만들지 못하게 합니다. 덕분에 그래프의 분류 체계가 빠르게 무너지는 문제를 줄입니다.
식별자 해소(Identity Resolution)
식별자 해소는 서로 다른 표기나 오탈자가 같은 엔티티를 가리키는지 판별하고 하나의 노드로 통합하는 과정입니다. 시스템은 검색 도구, canonical name과 alias, trigram·embedding을 결합한 hybrid search로 이 작업을 수행합니다. 이 과정이 실패하면 한 사람이 여러 노드로 쪼개져 그래프의 신뢰도가 떨어집니다.
이벤트 소싱(Event Sourcing)
이벤트 소싱은 상태 자체보다 상태 변화를 나타내는 불변 이벤트를 기록하고, 그 이벤트에서 현재 상태와 화면용 read model을 재구성하는 방식입니다. 이 시스템은 수집·Extraction·변경 제안·적용·충돌을 이벤트 흐름으로 연결합니다. 각 사실의 생성·수정 경로와 적용 당시 상태를 되짚을 수 있다는 점이 핵심입니다.
데이터 계보(Provenance)
데이터 계보는 그래프의 각 사실이 어떤 입력과 처리 과정에서 만들어졌는지 추적하는 정보입니다. 노드·엣지와 Extraction 사이의 조인 테이블에 작업 유형, 상태, 필드별 변경 내역을 저장하고, 도구 호출 결과도 read set으로 남깁니다. 따라서 특정 병합이나 수정의 근거를 원문과 모델의 조회 결과까지 거슬러 올라갈 수 있습니다.
프롬프트 캐싱(Prompt Caching)
Prompt Caching은 여러 차례의 LLM 호출에서 반복되는 시스템 프롬프트와 입력 콘텐츠를 캐시해 후속 라운드의 토큰 처리 비용을 낮추는 기능입니다. 이 글의 Extraction은 도구 호출이 끼어든 여러 라운드에서도 같은 프롬프트와 콘텐츠를 재사용합니다. 그 결과 대부분의 입력 토큰이 cache-read 요율로 청구되고, 작성자의 Extraction 대부분이 1달러보다 낮은 비용으로 처리됩니다.

기술

  • Planet Arkency
  • Rails Event Store
  • PostgreSQL
  • RubyLLM
  • Ruby
  • Rails
  • Zapier
  • n8n
  • pg_trgm
  • GIN indexes
  • bge-m3
  • Ollama
  • pgvector
  • Anthropic
  • MCP

활용 사례

  • 회의·Slack·이메일·GitHub 기록을 조직 지식 그래프로 축적
  • 사람·프로젝트·결정·도구 사이의 관계 검색과 탐색
  • LLM이 제안한 그래프 변경을 사람 검토 후 적용
  • 엔티티별 웹 Research와 출처 기반 지식 갱신
  • MCP를 통한 외부 AI assistant의 조직 지식 질의
AI 분석 전체 내용 보기

AI 요약 · 북마크 · 개인 피드 설정 — 무료

출처 · 인용 안내

원문 발행 2026. 08. 28.수집 2026. 08. 28.출처 타입 RSS

인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.