CodeGraph 구축기: 코드를 수정하기 전 무엇이 망가질지 알려주는 살아있는 지식 그래프 (Knowledge Graph)
요약
코드베이스의 함수 호출 관계를 지식 그래프로 시각화하여 코드 수정 시 발생할 수 있는 사이드 이펙트를 예측하는 CodeGraph 구축기를 소개합니다. Neo4j, tree-sitter, Groq LLaMA를 활용해 복잡한 코드 간의 연결성을 분석합니다.
핵심 포인트
- tree-sitter를 이용한 코드 파싱 및 함수 간 호출 관계 매핑
- Neo4j AuraDB를 활용한 코드베이스의 지식 그래프 구축
- LLM을 통해 자연어로 코드 구조 및 영향도 질문 가능
- 코드 수정 전 잠재적 오류와 망가질 기능을 즉시 확인
How I Built CodeGraph: A Living Knowledge Graph That Tells You What Breaks Before You Break It
Built for HACKHAZARDS '26 — powered by Neo4j AuraDB, tree-sitter, Groq LLaMA, and Next.js
내가 겪었던 문제점
모든 개발자가 이 기분을 알고 있습니다.
새로운 코드베이스(codebase)에 합류했습니다. 코드는 50,000줄에 달합니다. 매니저는 "인증 모듈(authentication module)의 이 작은 버그만 수정해 주세요"라고 말합니다. 당신은 수정을 완료하고 푸시(push)합니다. 그런데 갑자기 전혀 관련 없는 세 가지 기능이 망가집니다 — 결제 흐름(payment flow), 알림 시스템(notification system), 그리고 한 번도 본 적 없는 대시보드 위젯(dashboard widget)까지 말이죠.
당신은 이후 4시간 동안 함수 호출(function calls)을 수동으로 추적하고, 본 적 없는 코드를 읽으며, 왜 auth.py의 함수 하나를 바꾼 것이 코드베이스 반대편에 있는 notifications.py를 망가뜨렸는지 이해하려고 애씁니다.
이것은 드문 경험이 아닙니다. JetBrains의 개발자 설문조사에 따르면, 엔지니어들은 코드를 작성하는 것이 아니라 코드를 읽고 이해하는 데 시간의 58%를 소비합니다. 대규모 코드베이스에서의 단 한 번의 잘못된 변경은 몇 시간의 디버깅(debugging), 배포 실패, 그리고 사용자들의 불만을 초래할 수 있습니다.
저는 이 문제를 해결하기 위해 CodeGraph를 만들었습니다. 당신의 코드를 추측하는 또 다른 AI 챗봇(chatbot)이 아니라, 코드베이스가 어떻게 연결되어 있는지 실제로 이해하는, 쿼리 가능한(queryable) 실제 지식 그래프(knowledge graph)를 통해서 말이죠.
CodeGraph가 하는 일
CodeGraph는 어떤 공개 GitHub 저장소(repository) URL이든 입력받아 몇 초 내에 다음을 수행합니다:
- tree-sitter를 사용하여 코드베이스의 모든 함수(function)를 파싱(parse)합니다.
- 함수 간의 모든 호출 관계(call relationship)를 유향 그래프(directed graph)로 매핑(map)합니다.
- 모든 것을 Neo4j AuraDB에 살아있는 지식 그래프(knowledge graph)로 저장합니다.
- 일상적인 영어로 질문하면, 실제 그래프 데이터에 기반한 AI가 답변합니다.
결과적으로: GitHub URL을 붙여넣으면 전체 코드베이스를 인터랙티브한 그래프(interactive graph)로 볼 수 있으며, 어떤 함수든 클릭하면 그것을 수정했을 때 무엇이 망가질지 즉시 알 수 있습니다.
기술 스택 (Tech Stack)
제가 사용한 기술과 각 선택이 중요했던 이유는 다음과 같습니다:
백엔드 (Backend):
Python + FastAPI (REST API 서버)
Neo4j AuraDB (그래프 데이터베이스 (graph database) — 모든 것의 핵심)
tree-sitter (Python, JS, TS, TSX를 위한 AST 파서)
LLaMA 3.3 70B를 사용하는 Groq API (무료 티어 LLM)
GitPython (런타임 시 저장소 클론)
프론트엔드 (Frontend):
Next.js 15 및 TypeScript
Tailwind CSS (다크 테마, 보라색/청록색 팔레트)
Cytoscape.js (대화형 그래프 시각화)
Framer Motion (애니메이션)
D3.js (랜딩 페이지의 회전하는 지구본)
왜 PostgreSQL이나 MongoDB가 아닌 Neo4j AuraDB인가
이것은 제가 내린 가장 중요한 기술적 결정이며, 자세히 설명할 가치가 있습니다.
코드베이스는 본질적으로 그래프입니다. 함수는 다른 함수를 호출하고, 그 함수는 또 다른 함수를 호출하며, 이는 계속 이어집니다. 변경 사항의 결과를 이해하려면 이 그래프를 탐색해야 하며, 때로는 5단계 깊이까지 들어가야 합니다.
관계형 데이터베이스 (Relational Database)에서는 "이 함수에 직접적 또는 간접적으로, 최대 5단계 깊이까지 의존하는 것은 무엇인가?"라는 질문에 답하기 위해 비용이 많이 들고, 작성하기 복잡하며, 대규모 데이터셋에서 느린 재귀적 JOIN (recursive JOINs)이 필요합니다.
Neo4j에서는 단 하나의 Cypher 쿼리로 해결됩니다:
cypher
MATCH path = (caller:Function)-[:CALLS*1..5]->(target:Function {name: $name})
RETURN caller.name, caller.file, length(path) AS depth
ORDER BY depth
이 쿼리는 495,000개의 호출 관계가 있는 코드베이스에서도 밀리초 단위로 실행됩니다. 이는 SQL로는 불가능한 일입니다.
저의 Neo4j 스키마 (schema):
cypher
// 모든 함수는 노드 (node)입니다
(f:Function {name, file, line, version})
// 함수 간의 모든 호출은 관계 (relationship)입니다
(caller:Function)-[:CALLS]->(callee:Function)
단순한 스키마. 무한한 분석 능력.
구축 과정 — 단계별 안내
1단계: 파서 (The Parser)
첫 번째 과제는 코드를 실행하지 않고 읽는 것이었습니다. 저는 모든 프로그래밍 언어에서 작동하는 빠르고 오류 허용 범위가 넓은 AST (추상 구문 트리 (Abstract Syntax Tree)) 파서인 tree-sitter를 사용했습니다.
저장소의 모든 파일에 대해 tree-sitter는 구문 트리 (syntax tree)를 구축합니다. 저는 그 트리를 순회하며 다음을 추출합니다:
모든 함수 정의(이름 + 파일 + 라인 번호)
모든 함수 호출(호출자 → 피호출자)
모든 import 구문
pythondef walk(node, current_function=None):
if node.type == "function_definition":
name_node = node.child_by_field_name("name")
func_name = source_code[name_node.start_byte:name_node.end_byte]
result["functions"].append({
"name": func_name,
"file": file_path,
"line": node.start_point[0] + 1
})
if node.type == "call":
func_node = node.child_by_field_name("function")
called_name = source_code[func_node.start_byte:func_node.end_byte]
...
한 가지 중요한 세부 사항이 있습니다. Neo4j에 저장하기 전에 Python 내장 함수(print, strip, len 등)는 필터링합니다. 그렇지 않으면 print가 모든 코드베이스에서 가장 많이 호출된 함수로 나타나 의미 없는 노이즈가 됩니다.
2단계: 그래프를 Neo4j AuraDB에 저장하기
파서가 함수의 딕셔너리와 호출의 딕셔너리를 출력하면, 저는 MERGE 구문(동일한 레포지토리가 두 번 수집될 경우 중복을 방지함)을 사용하여 모든 것을 Neo4j에 작성합니다.
pythondef store_function(name, file_path, line_number, version="default"):
with driver.session() as session:
session.run("""
MERGE (f:Function {name: $name, file: $file, version: $version})
SET f.line = $line
"", name=name, file=file_path, line=line_number, version=version)
def store_call(caller_name, callee_name):
with driver.session() as session:
session.run("""
MERGE (a:Function {name: $caller})
MERGE (b:Function {name: $callee})
MERGE (a)-[:CALLS]->(b)
""", caller=caller_name, callee=callee_name)
ReplyIQ 저장소(본인의 프로젝트)의 경우, 이를 통해 628개의 함수 노드(function nodes)와 495,000개 이상의 호출 관계(call relationships)가 생성되었으며, 30초 이내에 데이터가 주입(ingested)되었습니다.
3단계: 영향 범위 점수 (The Blast Radius Score)
개발자에게 특정 함수를 수정하는 것이 얼마나 위험한지 알려주는 단일 수치를 만들고 싶었습니다. 저는 이를 영향 범위 점수 (Blast Radius Score, 0-100)라고 불렀습니다.
공식:
raw = (직접 호출자(direct_callers) × 3) + (2-3 홉 호출자(2-3 hop callers) × 1) + (4-5 홉 호출자(4-5 hop callers) × 0.3)
score = min(100, round(raw × 5))
직접 호출자는 즉시 오류를 발생시키기 때문에 간접 호출자보다 3배 더 높은 가중치를 부여했습니다. 4-5 홉 떨어진 함수들은 영향이 더 추측에 가깝기 때문에 가중치를 낮게 설정했습니다.
점수가 100점이면 "이것을 수정하면 코드베이스의 절반이 망가진다"는 의미입니다. 점수가 0점이면 "아무도 이것을 호출하지 않는다 — 아마도 삭제해도 안전하다"는 의미입니다.
4단계: GraphRAG — 그래프에 기반한 AI (AI Grounded in the Graph)
여기서부터 흥미로워졌습니다.
일반적인 RAG (Retrieval Augmented Generation, 검색 증강 생성)는 벡터 데이터베이스에서 텍스트 청크(text chunks)를 검색하여 LLM에 전달합니다. 저는 다른 방식을 시도했습니다: 바로 GraphRAG입니다. AI 에이전트가 Neo4j에서 구조화된 그래프 데이터(structured graph data)를 검색하고 이를 바탕으로 추론(reasoning)하는 방식입니다.
저는 Groq LLaMA 모델에 호출할 수 있는 도구(tools) 세트를 제공했습니다:
pythonTOOLS_SCHEMA = [
{
"type": "function",
"function": {
"name": "get_impact",
"description": "함수가 변경될 경우 무엇이 망가지는지 찾습니다",
"parameters": {
"type": "object",
"properties": {
"function_name": {"type": "string"}
},
"required": ["function_name"]
}
}
},
# ... get_stats, find_dead_code, get_blast_radius, run_cypher
]
사용자가 "수정하기 가장 위험한 함수는 무엇인가요?"라고 물으면, 모델은 자동으로 get_blast_radius를 호출하고, Neo4j에서 최상위 결과를 읽어온 뒤, 실제 함수 이름, 점수, 그리고 파일 경로와 함께 답변합니다.
환각 (Hallucination) 없음 — 답변이 모델의 학습 데이터가 아닌 데이터베이스에서 오기 때문입니다.
5단계: 프론트엔드 (Frontend)
대시보드는 세 가지 주요 섹션으로 구성됩니다:
왼쪽 패널: Groq 에이전트 기반의 AI 채팅. 어떤 질문이든 입력하면 특정 함수 이름과 파일 경로를 포함한 근거 있는 (grounded) 답변을 얻을 수 있습니다.
오른쪽 패널: Cytoscape.js 대화형 그래프. 노드(Node)는 영향 범위 (blast radius) 점수에 따라 색상이 지정됩니다 — 빨간색 (심각), 호박색 (중간), 노란색 (낮음), 회색 (정상). 노드를 클릭하면 영향력 드로어 (impact drawer)가 슬라이드되어 나타나며, 전체 CRITICAL/MODERATE/LOW 의존성 체인을 보여줍니다.
상단 바: GitHub URL 입력창. 공개 리포지토리(public repo) URL을 붙여넣으면 전체 파이프라인이 자동으로 실행됩니다.
랜딩 페이지는 D3.js로 구축된 애니메이션 회전 와이어프레임 지구본, Framer Motion을 사용한 부유하는 기하학적 도형, 그리고 스크롤 기반의 리빌 (reveal) 섹션이 특징이며, 모두 CodeGraph의 다크 퍼플/틸 (dark purple/teal) 팔레트 테마로 구성되어 있습니다.
내가 직면한 과제들
과제 1: tree-sitter 버전 불일치
처음 설치한 버전 (tree-sitter==0.21.3)은 정확한 패치 버전에 따라 Language()가 서로 다른 인자를 기대하는 파괴적인 API 변경 사항이 있었습니다. 두 시간 동안의 디버깅 끝에, tree-sitter==0.25.2와 이에 매칭되는 tree-sitter-python==0.25.0로 업그레이드하는 것이 해결책임을 발견했습니다. 교훈: 핵심 라이브러리와 언어 바인딩 (language bindings) 모두 항상 동일한 메이저 버전으로 고정(pin)하세요.
과제 2: 작동하지 않는 Gemini API 키
원래 AI 에이전트를 위해 Google Gemini를 사용할 계획이었습니다. 몇 시간 동안의 디버깅 끝에, 새로운 Google 계정(2026년 중반 이후)에는 표준 Gemini Developer API와 호환되지 않는 AQ.-접두사 API 키가 발급된다는 사실을 발견했습니다. 이 키들은 단순한 API 키 인증 대신 OAuth 플로우를 요구합니다. Groq(완전 무료이며, 실제로 결제가 전혀 필요 없음)로 전환함으로써 이 문제는 즉시 해결되었으며, 실제로 더 나은 도구 호출 (tool-calling) 신뢰성을 제공했습니다.
도전 과제 3: 그래프를 오염시키는 Python 내장 함수 (built-ins)
Flask에 파서 (parser)를 처음 실행했을 때, `print`와 `strip`이 25 이상의 영향 범위 (blast radius) 점수를 가진 가장 위험도가 높은 함수로 나타났습니다. 이는 해당 함수들이 코드베이스 전반에 걸쳐 "호출된 (called)" 함수로 나타나기 때문입니다. 저는 약 80개의 Python 내장 함수 (built-in) 이름으로 필터 목록을 유지하고, `store_call()` 과정에서 이들을 건너뛰는 방식으로 이 문제를 해결했습니다. 그 결과 그래프가 즉시 의미 있는 정보를 담게 되었습니다.
도전 과제 4: Cytoscape 엣지 (edge) 검증
그래프가 처음 렌더링될 때, "Cannot create edge with nonexistent target 'jsonify'"라는 오류와 함께 충돌이 발생했습니다. Cytoscape는 엣지를 생성하기 전에 모든 엣지의 소스 (source)와 타겟 (target)이 노드 (node)로서 반드시 존재해야 합니다. 해결 방법은 간단했습니다. 유효한 노드 ID들의 집합 (Set)을 구축하고, Cytoscape에 전달하기 전에 이 집합을 기준으로 엣지들을 필터링하는 것이었습니다.
도전 과제 5: Neo4j Aura 자동 일시 중지
Neo4j Aura의 무료 티어 (free tier)는 활동이 없으면 인스턴스 (instance)를 자동으로 일시 중지합니다. 개발 과정에서 이로 인해 네트워크 문제처럼 보이는 혼란스러운 `getaddrinfo failed` DNS 오류가 발생했습니다. 해결책은 데모 세션 전에 항상 `console.neo4j.io`를 확인하고, 인스턴스가 일시 중지되어 있다면 'Resume'을 클릭하는 것이었습니다. 또한, FastAPI 라우트 (routes)에 더 나은 에러 메시지를 추가하여 일반적인 500 에러 대신 도움이 되는 503 응답을 반환하도록 했습니다.
결과
세 개의 실제 코드베이스 (codebases)에서 테스트를 진행했습니다:
| 리포지토리 (Repository) | 함수 (Functions) | 관계 (Relationships) | 수집 시간 (Ingest Time) |
| :--- | :--- | :--- | :--- |
| ReplyIQ (본인의 프로젝트) | 628 | 4,95,000+ | ~25초 |
| Flask (pallets/flask) | 944 | 832 | ~18초 |
| emotion_aitutor | 124 | 31,744 | ~8초 |
AI는 ReplyIQ에서 `useRoom`이 가장 위험한 함수임을 정확히 식별했습니다 (영향 범위 100/100, 9개의 직접적인 호출자 (callers)). 영향력 드로어 (impact drawer)는 어떤 컴포넌트들이 깨질지 정확히 보여주었습니다 — `HTMLOverlay`, `NavigationUI`, `CameraController` 및 기타 6개 — 이 모든 것은 수동 코드 리뷰를 통해 검증된 정확한 파일 경로를 포함하고 있었습니다.
다음에 구축할 것들
VS Code 확장 프로그램 — 타이핑하는 동안 인라인으로 영향 범위 (blast radius) 경고를 표시합니다. 에디터를 떠나지 않고 함수 위에 마우스를 올려 위험 점수 (risk score)를 확인할 수 있습니다.
GitHub Actions 연동 — 모든 PR (Pull Request)에 대해 영향 분석 (impact analysis)을 자동으로 실행합니다. 만약 PR이 영향 범위가 80보다 큰 함수를 수정한다면, CI 파이프라인이 무엇이 망가질 수 있는지 목록을 담은 경고 댓글을 추가합니다.
JavaScript/TypeScript 파서 — tree-sitter는 모든 주요 언어를 지원합니다. JS/TS 파싱을 추가하는 것이 가장 레버리지가 높은 다음 단계입니다.
팀 협업 — 공유된 그래프 데이터베이스 (graph databases)를 통해 전체 엔지니어링 팀이 동시에 동일한 코드베이스 그래프를 쿼리할 수 있습니다.
직접 시도해보기
라이브 데모: [https://codegraph-nine.vercel.app/]
GitHub: [https://github.com/likitha2003-ctrl/codegraph]
API 문서: [https://codegraph-backend-u0qq.onrender.com]
로컬에서 실행하려면:
bashgit clone https://github.com/likitha2003-ctrl/codegraph.git
cd codegraph/backend
cp .env.example .env
# Neo4j 및 Groq 키를 입력하세요
pip install -r requirements.txt
uvicorn main:app --reload --port 8000
bashcd codegraph/frontend
echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local
npm install && npm run dev
그 다음 http://localhost:3000 을 열고 아무 공개 GitHub URL이나 붙여넣으세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기