Python 코드를 위한 기계 검증 가능 계약 시스템 구축 — 작동 방식 안내
요약
자율 AI 시스템의 코드 일관성을 유지하기 위해 AST를 활용하여 Python 코드와 문서(INTERFACE.md) 간의 불일치를 자동으로 검증하는 ContractVerifier 시스템 구축 방법을 설명합니다.
핵심 포인트
- AST를 사용하여 함수 시그니처, 타입, 독스트링 등 핵심 정보를 추출
- 기계가 파싱 가능한(machine-parseable) 인터페이스 명세서 생성
- 코드와 문서 간의 불일치를 CI 파이프라인에서 자동으로 감지
- 대규모 코드베이스에서 런타임 에러를 방지하는 거버넌스 계층 구축
지난주 저는 자율 AI 시스템을 위한 거버넌스 계층(governance layer) 구축에 대해 글을 썼습니다. 그중 한 모듈이 그 어떤 것보다 많은 질문을 받았습니다: 바로 ContractVerifier입니다.
사람들은 다음과 같이 물었습니다: "코드가 문서와 일치하는지 어떻게 자동으로 검증하나요?" 그리고 "이것이 실제로 버그를 잡아낼 수 있나요?"
이에 대해 심층적으로 분석해 보겠습니다.
문제점
저에게는 1,240개의 Python 파일이 있습니다. 한 모듈에서 함수 시그니처(function signature)를 변경하면, 최소한 세 가지 사항이 소리 없이 깨질 수 있습니다:
- 해당 모듈의 INTERFACE.md가 이제 틀린 정보가 됩니다.
- 이전 시그니처를 사용하는 모든 호출자(caller)가 깨집니다 (하지만 Python은 런타임(runtime) 전까지는 이를 알려주지 않습니다).
- 이전 함수 이름을 모킹(mock)하는 모든 테스트는 이제 아무것도 테스트하지 않게 됩니다.
팀 단위에서는 코드 리뷰(code review)가 이를 일부 잡아냅니다. 하지만 38만 줄의 코드를 가진 단독 자율 시스템에서는 코드 리뷰가 없습니다. 오직 CI 파이프라인(CI pipeline)만 존재할 뿐입니다.
저는 CI 파이프라인이 Python 코드를 이해할 수 있어야 했습니다.
핵심 아이디어: AST → 계약(Contract) → 검증(Verification)
ContractVerifier는 세 단계로 구성됩니다:
1단계: 진실 추출. ast를 사용하여 실제 Python 소스 코드를 파싱(parse)하고, 모든 노드를 순회(walk)하며 모든 공개 함수(public function), 클래스(class), 메서드(method), 그리고 비동기 함수(async function)를 추출합니다. 이때 전체 시그니처, 반환 타입(return types), 독스트링(docstrings), 그리고 발생시키는 예외(raised exceptions)를 함께 추출합니다.
2단계: 계약 생성. 모든 공개 API 엔트리를 종류별로 그룹화하여 기계가 파싱 가능한(machine-parseable) 구조로 공식적으로 나열한 INTERFACE.md를 작성합니다.
3단계: 계약 검증. 생성된 INTERFACE.md를 실제 코드와 비교합니다. 코드에는 있지만 문서에는 없는 함수, 혹은 문서에는 있지만 코드에는 없는 함수와 같은 모든 불일치는 위반(violation)으로 간주됩니다.
핵심 단어는 '기계가 파싱 가능한(machine-parseable)'입니다. 이것은 인간을 위한 문서가 아닙니다. 스크립트가 확인할 수 있는 검증 가능한 동작 명세(verifiable behavior specification)입니다.
1단계: AST 순회(Walking the AST)
AST 방문자(visitor)가 단일 Python 파일에서 추출하는 내용은 다음과 같습니다:
From hermes/strategies/grid_strategy.py
class GridStrategy:
def place_orders(self, mid_price: float, levels: int = 5) -> list[Order]:
"""중간 가격(mid price) 주변에 그리드 주문을 배치합니다."""
...
async def run_grid_loop(symbol: str, interval: int = 60) -> None:
"""메인 그리드 트레이딩 루프."""
...
Visitor(방문자)가 생성하는 결과물은 다음과 같습니다:
GridStrategy kind=class signature=class GridStrategy
GridStrategy.place_orders kind=method signature=place_orders(mid_price: float, levels: int = 5) -> list[Order]
run_grid_loop kind=async_function signature=run_grid_loop(symbol: str, interval: int = 60) -> None
이 Visitor는 상대적 임포트(relative import, . import X를 전체 점 표기 경로로 해결)를 처리하고, 중첩 클래스(nested classes), 비동기 함수(async functions), 그리고 예외 추적(exception tracking)을 처리합니다. 또한 __init__.py 내부에 있는지 여부를 인지하여 그에 따라 모듈 이름을 조정합니다.
이 Visitor는 약 140줄의 AST(Abstract Syntax Tree, 추상 구문 트리) 순회 코드로 구성되어 있습니다. 정규 표현식(regex)이나 문자열 매칭을 사용하지 않으며, Python 자체가 사용하는 것과 동일한 파서(parser)를 사용합니다.
2단계: 계약(Contract) 생성
계약 생성기(contract generator)는 추출된 API 항목들을 가져와 INTERFACE.md를 생성합니다:
INTERFACE.md — hermes.strategies.grid_strategy
ARES ContractGenerator에 의해 2026-06-20 10:15:00에 자동 생성됨
모듈 정보 (Module Info)
- 경로 (Path):
hermes/strategies/grid_strategy.py - 라인 수 (Lines): 312
- 공개 API (Public APIs): 8
공개 API (Public API)
클래스 (Classes)
class GridStrategy
설정 가능한 레벨 수와 간격을 가진 그리드 트레이딩 전략.
- 라인 (Line): 24
메서드 (Methods)
GridStrategy.place_orders(mid_price: float, levels: int = 5) -> list[Order]
중간 가격(mid price) 주변에 그리드 주문을 배치합니다.
- 반환값 (Returns):
list[Order]- 라인 (Line): 45
비동기 함수 (Async Functions)
run_grid_loop(symbol: str, interval: int = 60) -> None
메인 그리드 트레이딩 루프.
- 라인 (Line): 287
의존성 (Dependencies)
hermes.exchanges.adaptershared.models
임포트하는 곳 (Imported By)
hermes.live_runnerscripts.run_grid두 가지 출력 형식: 모듈별(Python 파일당 하나의 INTERFACE.md) 및 통합형(패키지 전체를 요약하는 하나의 INTERFACE.md). 통합형 형식은 파서(parser)가 형식을 자동으로 감지할 수 있도록 헤더에 N modules | M public APIs | K lines를 사용합니다.
3단계: 검증 (Verification)
이 단계에서 시스템이 유용해집니다. ContractVerifier.verify()는 INTERFACE.md를 실제 코드와 비교합니다:
verifier = ContractVerifier()
report = verifier.verify(
"hermes/strategies/grid_strategy.py",
"hermes/strategies/INTERFACE.md"
)
결과값은 다음과 같습니다:
{
"consistent": false,
"module": "hermes/strategies/grid_strategy.py",
"code_api_count": 8,
"doc_api_count": 7,
"missing_in_doc": ["GridStrategy.cancel_all_orders"],
"missing_in_code": ["GridStrategy.old_legacy_method"]
}
두 가지 위반 사항이 발견되었습니다:
missing_in_doc: cancel_all_orders가 코드에는 존재하지만 아무도 문서화하지 않았습니다. 이는 빌드(build)를 차단합니다. 즉, 이를 문서화하거나 왜 공개(public)되어서는 안 되는지 설명해야 합니다.
missing_in_code: old_legacy_method가 INTERFACE.md에는 있지만 코드가 이를 삭제했습니다. 계약(contract)이 오래된 상태(stale)입니다. 이 또한 빌드를 차단하며, 계약을 다시 생성(regenerate)해야 합니다.
batch_verify()는 디렉토리 트리 내의 모든 INTERFACE.md에 대해 이 작업을 수행하며, 단일 모듈 형식과 통합형 형식을 모두 자동으로 처리합니다.
실제로 작동하게 만드는 CI 게이트 (The CI Gate That Makes It Real)
계약을 생성하는 것은 쉽습니다. 중요한 것은 계약을 강제(enforcing)하는 것입니다.
CiEnforcer에는 세 가지 게이트가 있습니다. 게이트 2가 바로 계약 게이트(contract gate)입니다:
def _gate_contract(self, modified, deleted):
for mod_path in modified:
py_mtime = source_file.stat().st_mtime
md_mtime = interface_md.stat().st_mtime
if py_mtime > md_mtime:
# 계약이 생성된 이후에 소스 코드가 수정되었습니다.
# 계약이 오래되었습니다(stale). 차단(BLOCK).
violations.append(...)
이 검사는 내용(content) 기반이 아닌 타임스탬프(timestamp) 기반입니다. 이는 의도된 설계입니다. 타임스탬프를 비교하는 것은 $O(1)$의 시간 복잡도를 가지며, 어떤 커밋(commit)이 반영되기 전에 수행됩니다. 전체 AST 검증(Stage 3)은 야간 감사(nightly audit)에서 실행됩니다. 타임스탬프 게이트는 오버헤드 없이 커밋 시점에 계약 드리프트(contract drift)의 95%를 잡아냅니다.
전체 강제 적용 흐름(enforcement flow):
git commit
→ pre-commit hook
→ CiEnforcer.enforce(modified=["hermes/strategies/grid_strategy.py"])
→ 게이트: 데드 코드(dead code) → 통과(PASS) (새 모듈이 아님)
→ 게이트: 계약(contract) → 차단(BLOCK) (소스 수정됨, INTERFACE.md가 오래됨)
→ 게이트: 통합(integration) → 통과(PASS)
커밋이 거부되었습니다. 다음을 실행하세요:
python scripts/_ares_gen_contracts.py
git add hermes/strategies/INTERFACE.md
git commit
보너스: 자동 테스트 생성
공식적인 계약(formal contracts)이 있으면, 이를 기반으로 테스트를 생성할 수 있습니다. ContractTestGenerator는 두 가지 유형을 생성합니다:
공급자 스텁 테스트(Provider stub tests) — 모듈의 공개 API(public API)를 임포트(import)할 수 있는지, 그리고 예상된 속성(attributes)을 가지고 있는지 확인합니다:
def test_GridStrategy_place_orders_stub():
from hermes.strategies.grid_strategy import GridStrategy
assert hasattr(GridStrategy, 'place_orders')
소비자 모크 테스트(Consumer mock tests) — 호출자가 API를 올바르게 사용하는지 확인하기 위한 스캐폴딩(scaffold)입니다:
def test_GridStrategy_place_orders_consumer_mock():
from unittest.mock import patch
with patch("hermes.strategies.grid_strategy.GridStrategy.place_orders") as mock_fn:
# 실제 소비자 임포트로 교체하세요
pass
이것들은 스캐폴딩(scaffolds)이며 완전한 테스트는 아닙니다. 이는 모든 계약 항목에 대응하는 테스트 파일이 있음을 보장합니다. 각 API를 사용할 때 실제 어서션(assertions)을 채워 넣으세요. 함수를 삭제하면 hasattr이 False를 반환하므로 스텁 테스트가 실패합니다. 메서드 이름을 변경하면 모크(mock) 대상이 깨지게 됩니다.
이것이 해결하지 못하는 것
ContractVerifier는 코드가 문서화된 인터페이스(interface)와 일치하는지 여부를 알려줍니다. 하지만 다음 사항은 알려주지 않습니다:
인터페이스가 잘 설계되었는지 여부. 15개의 매개변수(parameter)를 받는 함수는 완벽하게 문서화될 수 있지만, 여전히 끔찍한 설계일 수 있습니다.
동작이 올바른지 여부. def withdraw(amount: float) -> bool 함수는 모든 호출에 대해 False를 반환하더라도 계약(contract)을 준수할 수 있습니다. 계약은 시그니처(signature)를 검증하는 것이지, 의미론(semantics)을 검증하는 것이 아니기 때문입니다.
계약이 완전한지 여부. 모듈이 100% 계약 준수율을 보이더라도 문서화되지 않은 부작용(side effects, 예: 파일 I/O, 네트워크 호출, 데이터베이스 쓰기)이 있을 수 있습니다.
이것들은 모든 정적 분석(static analysis) 접근 방식의 근본적인 한계입니다. 계약은 기능(function)이 아닌 형식(form)을 검증합니다.
다음 계층인 ActivityMonitor를 통한 런타임 모니터링(runtime monitoring)은 정적 분석이 놓치는 일부 사항을 포착합니다. 만약 함수의 계약에는 bool을 반환한다고 되어 있지만 실제로는 5%의 확률로 처리되지 않은 예외(unhandled exception)를 발생시킨다면, 런타임 모니터는 이를 포착합니다. 계약 검증기(contract verifier)는 이를 포착하지 못합니다.
AI 안전(AI Safety)과의 연결
AI 안전 커뮤니티에는 행동 명세(behavioral specification)라는 개념이 있습니다. 즉, AI 에이전트가 무엇을 해야 하는지 어떻게 공식적으로 명세(specify)하고, 그 에이전트가 실제로 그렇게 하는지 어떻게 검증(verify)할 것인가에 대한 문제입니다.
ContractVerifier는 이 아이디어를 Python 모듈에 적용한 마이크로 규모의 구현체입니다. 여기서 "에이전트(agent)"는 Python 파일이며, "행동 명세(behavioral specification)"는 INTERFACE.md이고, "검증(verification)"은 AST 비교입니다.
이것이 완전한 AI 정렬(AI alignment) 문제에 대한 해결책은 아님이 분명합니다. 하지만 이는 명세(specify) → 검증(verify) → 강제(enforce)라는 패턴의 구체적이고 작동 가능한 예시입니다. 만약 각 에이전트가 Python 모듈(또는 도구 호출을 수행하는 LLM)인 멀티 에이전트 시스템(multi-agent systems)을 위한 거버넌스 인프라를 설계하고 있다면, 이 패턴은 확장 가능합니다.
에이전트 거버넌스 (agent governance) 시스템을 구축하려는 모든 분께 제가 드릴 수 있는 핵심적인 통찰은 다음과 같습니다: 명세 (specification)를 만드는 사람과 검증 (verification)을 하는 사람을 동일하게 만들지 마십시오. 제가 지난주에 작성했던 폐쇄형 평가 루프 (closed evaluation loop)가 여기에도 적용됩니다. 만약 동일한 코드가 계약 (contract)을 생성하고 이를 검증한다면, 당신은 아무것도 증명한 것이 아닙니다. 단지 생성기 (generator)와 검증기 (verifier)가 서로 일치한다는 사실만을 증명했을 뿐입니다.
유일한 진정한 검증은 외부 사용입니다. ContractVerifier의 경우, 이는 다른 누군가가 자신의 코드베이스 (codebase)에서 이를 실행하여 제가 전혀 예상하지 못했던 위반 사항을 찾아내는 것을 의미합니다.
다음 단계
저는 ContractVerifier를 ARES의 다음 오픈 소스 (open-source) 모듈로 추출하고 있습니다. dead-scanner와 동일한 접근 방식을 취합니다: 의존성 제로 (zero dependencies), 순수 Python (pure Python), 모든 프로젝트에서 작동합니다.
정적 분석 (static analysis), CI 강제 (CI enforcement), 그리고 AI 거버넌스 (AI governance)의 교차점에 관심이 있으시다면 여러분의 의견을 듣고 싶습니다. 특히 모듈 수준이 아닌 에이전트 수준에서 유사한 문제를 다루고 계신다면 더욱 그렇습니다.
GitHub: github.com/Nick-lll/dead-scanner X: x.com/senlin
이 글은 자율 AI 시스템 (autonomous AI systems)을 위한 거버넌스 인프라 구축에 관한 시리즈의 파트 2입니다. 파트 1: AI 안전 (AI Safety)의 존재를 알지 못했을 때 거버넌스 레이어 구축하기
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기