코드를 찾는 것은 쉽습니다. 하지만 올바른 코드를 찾고, 그 변경 사항이 무엇에 영향을 미칠지 이해하는 것은 더 어렵습니다.
AI 코딩 에이전트에게 결제 재시도 정책을 수정해 달라고 요청한다고 상상해 봅시다.
에이전트는 재시도를 담당하는 함수를 찾아 그것을 변경하고 성공했다고 보고합니다. 하지만 다른 모듈이 그 함수를 호출하고, 설정(configuration)이 그 동작을 제어하며, 기존 테스트는 현재의 계약(contract)에 의존하고 있습니다.
문제는 반드시 에이전트가 코드를 작성할 수 없다는 것은 아닙니다. 단지 변경 사항을 이해하는 데 충분한 컨텍스트를 검색하지 못했을 수도 있습니다.
바로 이 지점에서 **시맨틱 코드 인텔리전스(semantic code intelligence)**가 유용해집니다. 이는 개발자와 AI 에이전트가 구조, 심볼(symbol), 관계, 때로는 시맨틱 유사성을 사용하여 코드를 탐색하도록 돕는 일련의 기술을 의미합니다.
이는 단일 표준화된 기술이라기보다는 광범위한 산업 용어입니다. 다양한 구현 방식은 서로 다른 기능을 제공합니다.
1. 텍스트 검색 대 코드 인텔리전스
getUser가 어디에 정의되어 있는지 찾고 싶다고 가정해 봅시다.
이것은 텍스트 일치 항목을 반환하며, 여기에는 정의, 호출, 주석, 테스트 등이 포함될 수 있습니다.
언어 인식 도구(language-aware tool)는 언어 서버가 해당 작업을 지원하고 충분한 프로젝트 정보를 가지고 있다면 심볼을 해결하여 그 정의 위치를 찾을 수 있습니다.
차이점은 간단합니다. 텍스트 검색은 일치하는 텍스트를 찾고; 심볼 인식 탐색(symbol-aware navigation)은 코드 엔티티와 그 관계를 식별할 수 있습니다.
둘 다 유용합니다. 어려운 점은 질문에 맞는 올바른 방법을 선택하는 것입니다.
2. 주요 구성 요소
시맨틱 코드 인텔리전스는 여러 상호 보완적인 기술을 결합합니다.
구문 파싱 (Syntax parsing)
Tree-sitter는 소스 코드를 구문 트리(syntax trees)로 파싱합니다. 이는 함수, 클래스, 표현식 및 기타 구조를 식별할 수 있어 구조적 쿼리 및 심볼 인식 코드 청킹에 유용합니다.
파싱만으로는 모든 심볼을 해결하거나 프로그램의 완전한 의미를 확립할 수는 없습니다.
심볼 탐색 및 인덱싱 (Symbol navigation and indexing)
두 가지 중요한 기술은 Language Server Protocol (LSP)과 SCIP입니다.
- LSP는 개발 도구와 언어 서버 간의 통신을 정의합니다. 구현 방식에 따라, 정의로 이동(go-to-definition), 참조 찾기(find-references), 이름 변경(rename) 기능을 지원합니다.
- SCIP는 코드 탐색을 지원하기 위한 언어 비종속적(language-agnostic) 소스 코드 인덱싱 프로토콜입니다.
LSP 사양과 SCIP 프로젝트를 참고하십시오.
이러한 기술들은 서로 다른 목적을 수행합니다: LSP는 통신 프로토콜을 정의하는 반면, SCIP는 인덱싱 형식을 정의합니다.
어느 쪽도 모든 언어 기능이나 저장소에서 완전한 결과를 보장하지는 않습니다. 정확도는 언어 지원 여부, 인덱스 커버리지, 프로젝트 구성 및 최신 상태에 따라 달라집니다.
임베딩과 벡터 검색 (Embeddings and vector search)
때로는 찾고자 하는 동작은 알지만, 어떤 함수가 이를 구현하는지는 모르는 경우가 있습니다.
예를 들어:
실패한 결제를 어디서 재시도해야 할까요?
임베딩 기반 검색(Embedding-based retrieval)은 코드 스니펫과 쿼리를 벡터로 표현하고, 유사성(similarity)을 기반으로 후보군을 검색합니다.
이는 쿼리와 소스 코드가 다른 용어를 사용하더라도 구현체를 발견하는 데 도움을 줍니다. 하지만 유사성이 기능적 동등성을 증명하지는 않습니다.
임베딩은 포괄적인 참조를 보장하거나 동작을 증명하기 위해서가 아니라, 후보군을 발견하는 데 사용하십시오.
코드 그래프 (Code graphs)
코드 그래프는 함수 호출(function calls), 가져오기(imports), 의존성(dependencies), 상속(inheritance)과 같은 관계를 나타냅니다.
이는 다음과 같은 질문을 조사하는 데 도움을 줍니다:
- 어떤 함수가 이 메서드를 호출하나요?
- 어떤 모듈이 이 인터페이스에 의존하나요?
- 이 컴포넌트를 변경할 경우 무엇에 영향을 미칠 수 있나요?
정적 그래프(Static graphs)는 동적 디스패치(dynamic dispatch), 리플렉션(reflection), 생성된 코드(generated code) 또는 런타임 구성과 관련된 관계를 놓칠 수 있습니다. 의존성 정보 역시 변경이 안전하다는 것을 보장하지 않습니다.
하이브리드 검색 (Hybrid retrieval)
검색 파이프라인은 어휘적 검색(lexical search), 의미론적 유사성(semantic similarity), 구조적 정보를 결합할 수 있습니다.
일반적인 워크플로우는 다음과 같습니다:
- 소스 코드를 심볼 인식 청크(symbol-aware chunks)로 분할합니다.
- BM25 또는 다른 어휘 검색(lexical retrieval) 방법을 사용하여 청크를 인덱싱합니다.
- 의미론적 검색(semantic retrieval)을 위해 임베딩을 생성합니다.
- 검색된 후보들을 병합하고 재순위화(rerank)합니다.
- 사용 가능한 경우 심볼 및 의존성 정보(symbol and dependency information)를 사용하여 결과를 정제합니다.
어휘 검색은 관련 용어를 찾고, 임베딩은 개념적으로 관련된 코드를 표면화하며, 재순위화는 후보들의 우선순위를 지정하는 데 도움을 줍니다.
하이브리드 검색(Hybrid retrieval)이 자동으로 더 정확한 것은 아닙니다. 그 효과는 인덱싱, 청크 분할, 순위 지정 및 근본적인 데이터의 품질에 달려 있습니다.
3. 실용적인 예시: 결제 재시도 정책 변경하기
최대 결제 재시도 횟수를 늘리라는 요청을 받은 에이전트를 가정해 봅시다.
텍스트 검색은 retry의 여러 발생 지점을 찾을 수 있지만, 관련 동작은 서비스, 설정 파일 및 테스트 스위트 전반에 걸쳐 분산되어 있을 수 있습니다.
다양한 검색 방법들이 어떻게 도움을 줄 수 있는지 살펴보겠습니다:
| 단계 | 방법 | 목적 |
|---|
| 1 | 어휘 검색(Lexical search) | 재시도 관련 식별자 및 설정 키 찾기 |
| ... | | |
예를 들어, retry에 대한 검색은 retry_payment(), max_attempts, 그리고 recover_transaction()을 보여줄 수 있습니다. 이후 심볼 해석(Symbol resolution)은 retry_payment()의 정의와 호출자들을 구별할 수 있으며, 의존성 분석(Dependency analysis)은 해당 함수가 어떻게 사용되는지 추적하는 데 도움을 줍니다.
이들은 측정된 검색 결과가 아닌 예시적인 이름입니다.
핵심은 각 단계가 서로 다른 질문에 답한다는 것입니다. 의미론적 검색은 후보들을 발견하는 데 도움을 주고, 심볼 인덱싱은 코드 관계를 확립하는 데 도움을 주며, 테스트는 동작에 대한 증거를 제공합니다.
이러한 단계들 중 어느 하나만으로는 모든 관련 구성 요소가 발견되었거나 결과로 나온 변경 사항이 올바르다는 것을 증명하지 못합니다.
4. AI 코딩 에이전트에게 중요한 이유
AI 코딩 에이전트는 코드베이스를 수정하기 전에 관련 컨텍스트(context)가 필요합니다. 효과적인 검색 기능 없이는, 이들은 파일을 반복적으로 검색하고, 관련 없는 섹션을 읽으며, 의존성을 수동으로 재구성하게 될 수 있습니다.
이는 불필요한 토큰 소비, 누락된 의존성, 불완전한 변경 사항, 그리고 반복적인 탐색 작업으로 이어질 수 있습니다.
영구 인덱스(Persistent indexes)는 태스크 전반에 걸쳐 심볼 및 관계 정보를 사용할 수 있게 하여 반복적인 탐색을 줄일 수 있습니다. 그 가치는 저장소(repository), 언어 지원, 인덱싱 오버헤드, 그리고 코드 변경 빈도에 따라 달라집니다.
더 나은 검색(retrieval)은 에이전트가 사용할 수 있는 컨텍스트를 개선할 수 있지만, 정확한 추론이나 안전한 수정까지 보장하지는 않습니다. 에이전트는 여전히 소스 코드를 검사하고, 가정을 검증하며, 변경 사항을 테스트해야 합니다.
5. 적절한 도구 선택하기
| 엔지니어링 질문 | 적합한 접근 방식 |
|---|
| 이 정확한 문자열은 어디에 나타나는가? | grep 또는 어휘 검색 (lexical search) |
| ... | |
| 이러한 기능들은 API, 도구 또는 Model Context Protocol (MCP)와 같은 통합을 통해 AI 에이전트에게 노출될 수 있습니다. 에이전트는 해당 통합에서 실제로 지원하는 기능만을 사용해야 합니다. | |
6. 고려할 트레이드오프(Trade-offs)
코드 인텔리전스를 도입하기 전에 운영 비용을 고려하십시오:
- 인덱스 신선도 (Index freshness): 오래된 인덱스는 오해를 불러일으키는 결과를 초래할 수 있습니다. 증분 업데이트 또는 재색인화(reindexing)가 필수적입니다.
- 언어 지원 (Language support): 인덱싱 기능과 정밀도는 언어와 프레임워크에 따라 다릅니다.
- 인프라 비용 (Infrastructure cost): 영구 인덱스는 저장소, 컴퓨팅 자원 및 유지 관리를 필요로 합니다.
- 검색 품질 (Retrieval quality): 임베딩(Embeddings)과 리랭커(rerankers)는 평가가 필요합니다. 유사도 점수만으로는 정확성을 확립할 수 없습니다.
- 분석 한계 (Analysis limitations): 정적 분석(Static analysis)은 모든 런타임 동작을 포착하지 못할 수 있습니다.
- 보안 (Security): 코드 인덱스와 임베딩 저장소는 독점적인 소스 정보를 포함할 수 있으므로 적절한 접근 통제가 필요합니다.
작은 저장소의 경우, 일반 검색과 기존 언어 서버 기능만으로 충분할 수 있습니다. 하지만 대규모 모노레포(monorepos)나 파일 간 빈번한 변경을 수반하는 에이전트 워크플로우의 경우, 추가적인 인덱싱 및 의존성 분석이 복잡성을 정당화할 수 있습니다.
기존 검색으로 시작하고, 필요할 때 심볼 인식 탐색(symbol-aware navigation)을 추가하며, 특정 문제가 정당화될 경우 시맨틱 검색(semantic retrieval)이나 그래프 분석(graph analysis)을 도입해야 한다. 대표 과제, 검색 정밀도 및 재현율(retrieval precision and recall), 인덱스 최신성(index freshness), 지연 시간(latency), 토큰 소비량(token consumption), 그리고 엔드투엔드 과제 성공률(end-to-end task success)을 사용하여 결과를 평가해야 한다.
결론
시맨틱 코드 인텔리전스는 텍스트 검색을 대체하는 것이 아니다. 이는 구조적 및 의미적 정보 수준이 다른 코드를 검색하기 위해 상호 보완적인 기술들을 결합한다.
정확한 일치(exact matches)를 위해서는 어휘 검색(lexical search)을 사용하고, 정의와 참조를 위해서는 심볼 인덱싱(symbol indexing)을 사용하며, 의도에 따른 구현체를 발견하기 위해서는 임베딩(embeddings)을 사용하고, 종속성 및 잠재적 영향을 탐색하기 위해서는 코드 그래프(code graphs)를 사용한다.
AI 코딩 에이전트에게 목표는 무차별적으로 더 많은 코드를 검색하는 것이 아니다. 관련 증거를 검색하고, 그 한계를 이해하며, 결과로 나오는 변경 사항을 검증하는 것이다.
목표는 모든 검색을 시맨틱하게 만드는 것이 아니다. 질문받은 내용에 맞는 올바른 증거를 검색하는 것이다.