DEV Community

TreeSoop
TreeSoop

Posted on

업무 자동화 시스템을 연구부터 운영까지 책임지는 전문팀이 설계하는 방법

업무 자동화 시스템이 PoC 단계에서 멈추는 가장 흔한 이유는 연구 팀과 개발 팀이 따로 존재하기 때문이다. 리서치에서 나온 모델이 프로덕션 파이프라인과 분리되는 순간, 유지보수 비용이 두 배가 되고 개선 사이클이 느려진다. 나무숲이 단일 팀으로 연구부터 운영까지 가져가는 이유가 바로 여기에 있다.


리서치와 프로덕션 개발을 단일 시스템으로 묶는 기술적 아키텍처

리서치 코드와 서비스 코드를 처음부터 같은 레포지토리 구조 안에서 설계하면 두 가지가 달라진다. 배포 파이프라인이 실험 브랜치를 직접 스테이징 환경으로 올릴 수 있고, 모델 버저닝이 API 버저닝과 동기화된다.

일반적인 분리 구조는 이렇다.

research/
  experiments/      # Jupyter, wandb 실험 로그
  models/           # 훈련된 모델 아티팩트
service/
  api/              # FastAPI 엔드포인트
  workers/          # Celery 태스크
Enter fullscreen mode Exit fullscreen mode

이 구조의 문제는 research/models에서 검증된 가중치가 service/api로 넘어오는 시점에 수동 개입이 필요하다는 점이다. 파일을 복사하거나, 경로를 하드코딩하거나, 담당자가 직접 배포 스크립트를 수정한다. 이 수동 구간이 곧 버그 발생 지점이다.

단일 시스템 아키텍처는 이 구간을 없앤다.

pipeline/
  research/
    train.py         # DVC 파이프라인 정의
    evaluate.py
  serve/
    app.py           # FastAPI + MLflow 모델 레지스트리 참조
  shared/
    schema.py        # Pydantic 스키마 — 연구·서빙 양쪽이 동일하게 사용
    config.py        # 환경 분리 없이 단일 설정 파일
Enter fullscreen mode Exit fullscreen mode

핵심은 shared/schema.py다. 연구 단계에서 정의한 입출력 스키마를 서빙 레이어가 그대로 참조한다. Pydantic으로 스키마를 강제하면, 모델 입력 형식이 바뀔 때 서빙 코드가 컴파일 시점에 실패한다. 런타임 오류가 아니라 타입 오류로 잡힌다.

모델 등록과 불러오기는 MLflow Model Registry로 표준화한다.

# serve/app.py
import mlflow.pyfunc
from shared.schema import InferenceRequest, InferenceResponse

model = mlflow.pyfunc.load_model("models:/document-classifier/Production")

@app.post("/predict", response_model=InferenceResponse)
def predict(req: InferenceRequest):
    return model.predict(req.dict())
Enter fullscreen mode Exit fullscreen mode

"models:/document-classifier/Production" 문자열 하나가 모델 버전과 서빙 코드 사이의 계약이다. 연구 팀이 새 버전을 Staging으로 올리면 CI가 평가 지표를 체크하고, 통과하면 자동으로 Production으로 승격된다.


POSTECH·KAIST·서울대 출신 기술진이 설계하는 에이전트 협업 파이프라인

단일 에이전트로 복잡한 업무를 처리하려 하면 컨텍스트 윈도우 한계와 오류 전파 문제가 생긴다. 하나의 에이전트가 긴 작업을 전담할 때, 중간 단계 오류가 하류 전체에 쌓인다. 에이전트 협업 파이프라인은 이 문제를 오케스트레이터-워커 구조로 분리해 푼다.

아래가 우리 팀이 실제 업무 자동화 시스템에서 쓰는 기본 패턴이다.

# agents/orchestrator.py
from agents.worker import DocumentExtractor, Validator, Reporter

class OrchestratorAgent:
    def __init__(self):
        self.extractor = DocumentExtractor()
        self.validator = Validator()
        self.reporter = Reporter()

    def run(self, document_path: str) -> dict:
        raw = self.extractor.extract(document_path)       # 1단계: 추출
        validated = self.validator.validate(raw)           # 2단계: 검증
        return self.reporter.generate(validated)           # 3단계: 보고
Enter fullscreen mode Exit fullscreen mode

각 워커는 단일 책임을 갖는다. DocumentExtractor는 OCR이나 파서로 원시 데이터를 뽑는 일만 한다. Validator는 스키마 검증과 비즈니스 룰 체크만 한다. Reporter는 최종 출력 형식화만 맡는다.

오케스트레이터는 흐름만 제어한다. 각 단계의 실패는 독립적으로 처리되고, 재시도 로직은 워커 레벨에 있다.

에이전트 간 메시지 포맷은 엄격하게 정의한다.

# shared/schema.py
from pydantic import BaseModel
from typing import Optional

class ExtractionResult(BaseModel):
    document_id: str
    content: dict
    confidence: float
    error: Optional[str] = None

class ValidationResult(BaseModel):
    document_id: str
    is_valid: bool
    issues: list[str]
    corrected_content: Optional[dict] = None
Enter fullscreen mode Exit fullscreen mode

confidenceerror 필드가 있는 이유는 부분 실패를 표현하기 위해서다. 에이전트가 실패했을 때 파이프라인 전체를 멈추는 대신, 오케스트레이터가 is_valid: False인 결과를 모아 별도 검토 큐로 보낸다. 사람이 개입해야 하는 케이스와 자동 처리 가능한 케이스를 분리하는 것이 실제 운영 자동화의 핵심이다.

비동기 처리가 필요한 규모에서는 Celery와 Redis를 결합한다.

# workers/tasks.py
from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True, max_retries=3)
def process_document(self, document_path: str):
    try:
        orchestrator = OrchestratorAgent()
        return orchestrator.run(document_path)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)
Enter fullscreen mode Exit fullscreen mode

max_retries=3, countdown=60은 일시적 외부 API 실패를 흡수한다. 세 번 실패한 태스크는 Dead Letter Queue로 들어가고 알림이 발송된다.


학술 자산을 구현 가능한 시스템으로 전환하는 설계 원칙

논문에 있는 알고리즘을 프로덕션에 올리는 작업은, 논문을 이해하는 것과 구현하는 것이 완전히 다른 문제라는 데서 시작해야 한다.

논문에서 프로덕션으로 넘어올 때 자주 빠지는 함정은 아래 네 가지다.

  • 배치 추론 가정 문제: 논문은 오프라인 배치를 가정하는 경우가 많다. 실시간 API로 서빙하려면 레이턴시 요구사항에 맞게 모델을 재설계해야 한다.
  • 데이터 분포 차이: 논문의 벤치마크 데이터셋과 실제 운영 데이터의 분포가 다르다. 도메인 파인튜닝 없이 곧바로 배포하면 정확도가 논문 수치보다 낮게 나온다.
  • 의존성 버전 충돌: requirements.txt에 버전이 명시되지 않은 리서치 코드는 환경을 다시 만들기 어렵다. Docker 이미지에 CUDA 버전까지 고정해야 재현 가능하다.
  • 모니터링 부재: 모델 드리프트를 감지하는 파이프라인 없이 배포하면, 정확도 저하를 사용자 불만으로 처음 알게 된다.

나무숲 팀이 오픈소스로 공개한 리포지토리들(★120+)은 이 전환 과정을 문서화한 결과물이기도 하다. 논문 구현과 서빙 코드를 같은 레포에 두고, README에 재현 방법과 배포 방법을 함께 적는 것이 우리 팀의 기본 방식이다.

모델 드리프트 감지는 Evidently AI로 구현할 수 있다.

# monitoring/drift_check.py
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset

def check_drift(reference_data, current_data):
    report = Report(metrics=[DataDriftPreset()])
    report.run(reference_data=reference_data, current_data=current_data)
    results = report.as_dict()
    return results["metrics"][0]["result"]["dataset_drift"]
Enter fullscreen mode Exit fullscreen mode

드리프트가 감지되면 재훈련 파이프라인을 트리거하거나 알림을 보낸다. 운영 중인 시스템이 스스로 상태를 보고하는 구조다.


오픈소스 기여 방식이 기술 검증 수단인 이유는?

비공개 포트폴리오는 외부에서 검증할 수 없다. NDA로 묶인 프로젝트 결과물을 "우리가 잘 만들었다"고 말하는 것과, 코드를 공개하고 커뮤니티에서 별을 받는 것은 다른 종류의 신뢰다.

★120+ 스타는 단순한 숫자가 아니다. 다른 개발자가 코드를 보고, 실제로 써보고, 유용하다고 판단했다는 의미다. 코드 품질과 문서화 수준이 외부에서 검증된 상태다.

기술 담당자가 파트너를 고를 때 오픈소스 기여 이력이 유효한 근거가 되는 구조는 이렇다.

검증 수단 확인 가능한 것 확인 불가능한 것
비공개 포트폴리오 결과물 스크린샷, 고객사 이름 코드 품질, 설계 결정, 실제 구현 수준
오픈소스 리포지토리 코드 구조, 문서화, 커밋 히스토리, 테스트 커버리지 비즈니스 컨텍스트
오픈소스 + 스타 수 위에 더해 커뮤니티 검증 여부

CTO나 기술 리드가 파트너를 평가할 때, GitHub 링크 하나가 미팅 한 번보다 더 많은 정보를 준다. 코드를 직접 보면 추상화 수준, 테스트 작성 방식, 의존성 관리 방식을 한 번에 확인할 수 있다.


자주 묻는 질문

업무 자동화 시스템 구축에 필요한 최소 요구사항은 무엇인가요?

자동화할 업무 프로세스의 입출력이 명확하게 정의되어 있어야 한다. "복잡한 판단이 필요한 작업"은 자동화 가능하지만, 판단 기준이 문서화되지 않은 경우 먼저 규칙 정의 작업이 선행되어야 한다. 데이터 접근 권한과 내부 시스템 API 여부도 초기에 확인한다.

에이전트 파이프라인과 단순 API 연동의 차이는 무엇인가요?

단순 API 연동은 고정된 흐름을 실행한다. 에이전트 파이프라인은 중간 상태를 판단하고 분기할 수 있다. 예외 케이스를 사람 검토 큐로 보내거나, 신뢰도가 낮은 결과를 재처리하는 로직이 파이프라인 수준에서 설계된다. 이 차이가 운영 안정성을 결정한다.

리서치 단계 결과물이 프로덕션에 그대로 올라가지 않는 이유는 무엇인가요?

리서치 코드는 재현 가능성을 목표로 작성되고, 프로덕션 코드는 안정성과 레이턴시를 목표로 작성된다. 배치 처리 가정, 의존성 버전 고정, 에러 핸들링 방식이 다르다. 단일 팀이 두 단계를 모두 담당할 때 이 전환 비용이 가장 낮다.

오픈소스로 공개한 코드가 기업 프로젝트에서도 그대로 적용되나요?

오픈소스 코드는 일반화된 패턴을 담는다. 기업 프로젝트는 도메인 데이터, 내부 시스템 연동, 보안 요구사항에 맞게 커스터마이징된다. 공개 리포지토리는 설계 철학과 구현 방식의 레퍼런스이고, 실제 프로젝트는 그 위에 구체적인 요구사항을 얹는 작업이다.

착수부터 운영까지 어떤 구조로 진행되나요?

초기 2주 동안 요구사항 정의와 아키텍처 설계를 완료한다. 이후 스프린트 단위로 워커 에이전트를 순서대로 구현하고, 스테이징 환경에서 실제 데이터로 검증한다. 모니터링 파이프라인을 포함한 상태로 프로덕션에 배포하고, 이후 드리프트 감지와 성능 유지를 지속 운영한다.


연구부터 운영까지 한 팀이 책임지는 구조는 선택의 문제가 아니라 설계 결정이다. 아키텍처 수준에서 리서치와 서빙을 분리하지 않고, 에이전트 협업 파이프라인을 명확한 스키마로 강제하며, 오픈소스로 코드를 공개해 외부 검증을 쌓는 것이 나무숲이 일하는 방식이다. 기술 구조를 직접 확인하고 싶다면 GitHub 리포지토리를 먼저 보고, 구체적인 시스템 요구사항은 기술 상담으로 이야기하자.


더 보기: treesoop.com

Top comments (0)