코드 벡터 임베딩과 유사도 분석을 통해, 지적 재산권 보호 대상 소스코드의 침해 가능성을 탐지하는 지능형 코드 분석 시스템입니다.
참여인원: 6명
수행기간: 2025. 10. 10 - 2025. 11. 20 (7주)
수행배경: 삼성전자 생산기술연구소 연계 PJT
- 요구명세서
비공개 - 프로젝트계획서
비공개 - 기능명세서
비공개 - 포팅매뉴얼
비공개 - GitHub 연동매뉴얼
비공개 - AI 활용처
비공개
- GitHub PR 이벤트를 감지하고, 변경 사항에 대해 유사도 검사 수행
- PR 현황, 유사도 검사 결과 등 대시보드를 통한 실시간 시스템 모니터링
- 대규모 코드베이스를 안정적으로 분석하기 위한 분산·병렬 기반 처리 구조
비공개
여러 리포지토리를 하나의 세션으로 묶어 일관성을 보장합니다. Transactional 데코레이터는 DI 컨테이너(Container.infra.session)에서 세션을 받아 모든 리포지토리가 동일 세션을 공유하도록 주입하며, 성공 시 커밋·예외 시 롤백을 자동 처리합니다. DI가 실패하면 SessionLocal로 폴백합니다.
# api-server/app/core/transactional.py
class Transactional:
commit: bool
def __init__(self, commit: bool = True, readonly: bool = False):
self.commit = commit and (not readonly)
def __call__(self, func: Callable):
@wraps(func)
def wrapper(*args, **kwargs):
self_obj = args[0] if args else None
session = self._get_session()
original_sessions: Dict[str, Session] = {}
try:
if self_obj is not None:
original_sessions = self._inject_session(self_obj, session)
result = func(*args, **kwargs)
if self.commit:
session.commit()
return result
except Exception:
session.rollback()
raise
finally:
if self_obj is not None:
self._restore_sessions(self_obj, original_sessions)
session.close()
return wrapper코드 유사도 리랭킹 알고리즘을 동적으로 교체하기 위해 Reranker 인터페이스와 register_reranker 데코레이터를 사용합니다. get_reranker는 등록된 이름(levenshtein, graphcodebert 등)에 맞는 전략을 반환하며, 새 알고리즘은 데코레이터만 추가하면 자동 등록됩니다.
# api-server/app/utils/rerankers/reranker.py
_RERANKER: Dict[str, Reranker] = {}
def register_reranker(name: str):
def decorator(func):
_RERANKER[name] = func
return func
return decorator
def get_reranker(name: str, **kwargs) -> Reranker:
reranker = _RERANKER.get(name.lower())
if reranker is None:
raise InvalidValueException("유효하지 않은 알고리즘입니다.")
return reranker(**kwargs)dependency-injector 기반 컨테이너에서 설정·로거·DB 세션·리포지토리·서비스를 스코프별로 관리합니다. RdbContainer가 세션을 제공하고 상위 Container가 Factory/Singleton 제공자를 노출합니다. FastAPI 컨트롤러는 Provide[Container.xxx]로 의존성을 주입받습니다.
# api-server/app/core/containers.py
class RdbContainer(containers.DeclarativeContainer):
db = providers.Singleton(SyncDatabase)
session = providers.Callable(lambda db: db.session, db=db)
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(
modules=["app.controllers.pr_controller", ...]
)
settings = providers.Singleton(load_settings)
logger = providers.Singleton(LoggerConfig.load_logger)
infra = providers.Container(RdbContainer)
pr_log_repository = providers.Factory(PostgresqlPrLogRepository, db=infra.session)
...
pr_service = providers.Factory(
PrServiceImpl,
pr_log_repository=pr_log_repository,
pr_detail_repository=pr_detail_repository,
change_file_repository=change_file_repository,
dist_group_repository=dist_group_repository,
search_request_repository=search_request_repository,
change_file_service=change_file_service,
gh=gh,
logger=logger,
settings=settings,
)컨트롤러(FastAPI 라우터) → 서비스 → 리포지토리 → 데이터 계층으로 책임을 분리합니다. 컨트롤러는 DI로 서비스 의존성을 받고, 서비스는 트랜잭션 단위로 리포지토리를 조합합니다.
# api-server/app/controllers/pr_controller.py
@inject
def pr_controller(
pr_service: PrService = Depends(Provide[Container.pr_service]),
change_file_service: ChangeFileService = Depends(Provide[Container.change_file_service]),
...
) -> APIRouter:
router = APIRouter(prefix="/repos", tags=["pr 조회"])
@router.get("/pulls/statistics")
def pr_statistics(request: Request):
stats = pr_service.get_today_statistics()
return BaseResponse.success_response(data=DistGroupStatusSummaryOut(stats=stats))# api-server/app/services/implementation/pr_service_impl.py
class PrServiceImpl(PrService):
@Transactional()
async def create(self, pr_webhook: PrWebhook) -> None:
pr_log = self.pr_log_repository.get_log(pr_webhook.pr_id)
if pr_log:
pr_log.title = pr_webhook.title
self.pr_log_repository.update_log(pr_log)
else:
pr_log = PrLog(...)
self.pr_log_repository.create_log(pr_log)
pr_detail = PrDetail(commit_sha=pr_webhook.head_sha, ...)
self.pr_detail_repository.create(pr_detail)
change_files = await self.change_file_service.save_change_file(
pr_log,
change_file_repository=self.change_file_repository,
...
)| FastAPI | `main.py`와 각 `controllers/`에서 라우팅·미들웨어·응답 스키마를 정의하는 중심 API 프레임워크 |
| dependency-injector | `app/core/containers.py`에서 서비스/리포지토리를 Factory·Singleton으로 관리하고, `Transactional` 데코레이터로 트랜잭션을 처리해 테스트와 리팩토링을 용이하게 함 |
| Pydantic / pydantic-settings | `schemas/` DTO 검증, `load_settings.py` 기반 환경변수 로딩·검증 담당 |
| SQLAlchemy | `models/`, `repositories/`에서 ORM으로 PostgreSQL과 상호작용하며 `Transactional` 데코레이터로 트랜잭션을 관리 |
| PostgreSQL | 인증, PR 로그, 라이선스 정보 등 도메인 데이터를 저장하는 RDB |
| Milvus | 코드 임베딩 벡터를 저장·검색하여 유사 코드 탐지를 지원 |
| Kafka | API 서버 → chunking/embedding 워커로 작업 큐를 전달하고, 처리 상태를 비동기로 연계 |
| kafka-python | Kafka 토픽 publish/consume을 담당하는 Python 클라이언트 (청킹/임베딩 작업, PR 처리 이벤트 전달) |
| httpx | GitHub API 등 외부 연동을 위한 비동기 HTTP 클라이언트 |
| PyGithub | GitHub 앱 토큰 발급, PR 조회/웹훅 검증 등 GitHub 통합 로직 |
| tree-sitter / tree-sitter-languages | 코드 파싱·청킹 로직에서 언어별 구문 구조를 인식 |
| PyJWT + Passlib + Cryptography | JWT 발급·검증, 비밀번호 해싱 및 암호화 유틸리티 |
| Prometheus FastAPI Instrumentator | `/metrics` 엔드포인트에서 API 호출 지표를 노출 |
| Vite | React/TypeScript 개발 환경으로 빠른 HMR과 최적화된 번들링 제공 |
| React | 대시보드·관리 화면을 SPA 형태로 구현하는 핵심 UI 라이브러리 |
| TypeScript | 컴포넌트/스토어/API 응답에 정적 타입을 부여해 런타임 오류를 예방 |
| Zustand | 전역 상태(필터, 사용자 정보 등)를 간결하게 관리 |
| React Query | 서버 상태 동기화, 캐싱, 로딩/에러 상태 관리를 자동화 |
| Recharts | PR 통계, 코드 탐지 결과 등을 시각화하는 차트 컴포넌트 |
| CSS Modules + Tailwind CSS | 컴포넌트 단위 스타일 격리를 유지하면서 공통 유틸리티 클래스를 재사용 |
| Docker | 모든 서비스(Milvus, Kafka, API, Batch 등)를 컨테이너화해 일관된 실행 환경 확보 |
| Docker Compose | 로컬·배포 환경에서 멀티 서비스 오케스트레이션 (API, 배치, 모니터링 스택) 담당 |
| Jenkins | 환경별 배포, 정기 배치 실행 등 고급 파이프라인 운영 |
| Nginx | Reverse proxy, TLS 종료, 정적 자산 서빙 및 백엔드 라우팅 |
| Prometheus | 시스템/애플리케이션 메트릭 수집 |
| Grafana | 메트릭 기반 대시보드 시각화 |
| Loki | 중앙 로그 수집·질의 (Grafana와 연계) |
| promtail | 컨테이너 로그를 수집·정규화·민감정보 마스킹 후 Loki에 전달하는 로그 수집기 |
| PyTorch (GraphCodeBERT) | 임베딩 모델을 로딩·추론하는 딥러닝 런타임. 배치 워커와 API 서버의 embedding_service가 PyTorch로 모델 가중치를 불러와 코드 임베딩을 생성합니다 |
SSAFY PJT CODE: S13P31S405
ⓒ 2025. SAMSUNG SW·AI ACADEMY FOR YOUTH









