무색
기술블로그
에세이
연구
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

contact@museck.com

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관·연락처
INCHEON, KR
서브에이전트가 같은 파일을 동시에 수정하면 생기는 일 — generative 스타일 키 비주얼
논문 번역기
2026. 1. 13.

서브에이전트가 같은 파일을 동시에 수정하면 생기는 일

multi-agentglossaryscatter-gatherconcurrencyrefactoring

논문 번역기에 glossary 시스템을 붙인 직후였다. 대용량 논문을 빨리 처리하겠다고 페이지 범위를 나눠 서브에이전트 여러 개를 동시에 돌렸는데, 결과가 엉망이었다. 두 에이전트가 동시에 같은 glossary.json을 수정하면서 한쪽 데이터가 통째로 날아간 거다.

문제의 원인은 명확했다. 하나의 에이전트가 용어 추출부터 파일 저장, 품질 검수, 번역 실행까지 다 하고 있었다. 프롬프트가 복잡해지니 역할 혼동이 생기고, 병렬 실행하면 파일 I/O가 충돌했다. 전통적인 소프트웨어 설계에서 이미 답을 알고 있는 문제였는데 에이전트라고 다를 게 없었다.

리팩토링 방향: 누가 파일을 쓸 수 있는가

핵심 원칙은 단순하다. 서브에이전트는 데이터만 반환하고, 파일 I/O는 메인 에이전트가 독점한다.

서브에이전트(Claude Haiku)는 PDF 텍스트에서 용어를 추출해 JSON dict로 반환만 한다. 파일을 만들거나 기존 파일을 수정하는 건 절대 안 된다. 메인 에이전트(Claude Opus)가 그 결과를 받아서 검수하고, 불필요한 항목을 걸러낸 뒤 스크립트를 통해 파일로 저장한다.

이전 글에서 만든 마스터 용어 사전(glossary.json)과 논문별 CSV 시스템은 그대로 유지하되 "누가 쓰는가"만 명확히 분리한 셈이다.

Short-Circuit: glossary가 이미 있으면 건너뛰기

번역 요청이 들어오면 가장 먼저 glossary_output/<논문>.glossary.csv 파일이 있는지 확인한다. 이미 있으면 용어 추출 과정 전체를 스킵하고 바로 번역으로 넘어간다.

처음에는 번역 직전에 체크했었는데 비효율적이었다. 페이지 수 파악하고 서브에이전트 몇 개 띄울지 결정하는 것만 해도 시간이 꽤 걸리거든. glossary가 이미 있으면 그 모든 과정이 필요 없으니 "가장 먼저" 확인하는 게 맞다.

Scatter-Gather: 병렬 추출과 병합

glossary가 없으면 전체 경로를 탄다. 대용량 논문은 페이지 범위를 나눠 서브에이전트 여러 개가 병렬로 용어를 추출한다. 각 에이전트는 자기 범위의 텍스트를 읽고 JSON dict만 돌려준다. 파일은 안 건드린다.

메인 에이전트가 결과를 모아 검수한 뒤 agent_total_glossary.py --merge를 호출한다. 이 스크립트가 분할된 CSV를 하나로 합치고 원본 분할 파일은 삭제해준다.

def merge_csvs(input_patterns, output_path):
    merged = OrderedDict()
    for csv_path in input_files:
        with csv_path.open("r", encoding="utf-8") as f:
            reader = csv.DictReader(f)
            for row in reader:
                src = row.get("source", "").strip()
                if src and src not in merged:
                    merged[src] = row.get("target", "").strip()
    # 원본 분할 파일 삭제
    for csv_path in input_files:
        if str(csv_path.resolve()) != out_real:
            csv_path.unlink()

OrderedDict를 쓰는 이유가 있다. Python 3.7+ dict도 삽입 순서를 보장하지만 JSON 직렬화할 때 sort_keys=True를 쓰면 순서가 날아간다. 신규 용어를 맨 위에 두고 싶으면 sort_keys=False + OrderedDict 조합이 필요하다.

역시간순 정렬이 편한 이유

glossary.json에서 신규 용어를 맨 위에 넣는 건 소소하지만 실용적인 변경이다. 논문을 번역하고 나서 용어 사전을 열어볼 때 방금 작업한 논문의 용어가 맨 위에 있으면 확인이 빠르다.

# 신규 용어를 맨 위에, 기존 용어를 아래에
updated_master = OrderedDict()
for src, tgt in added.items():
    updated_master[src] = tgt
for src, tgt in master.items():
    if src not in updated_master:
        updated_master[src] = tgt

처음에는 알파벳순 정렬이었는데 수백 개 용어가 쌓이니 최근 추가한 항목을 찾기가 어려웠다. 역시간순으로 바꾸고 나서 작업 흐름이 훨씬 매끄러워졌다.

디렉토리 구조 정리

리팩토링하면서 I/O 디렉토리도 정리했다. pdf_input/과 pdf_output/과 glossary_output/ 세 디렉토리로 입출력을 분리하고 출력 파일명에서 모델 태그도 제거했다. paper.translategemma-27b.mono.pdf 같은 긴 이름 대신 paper.mono.pdf로 단순화했다. 어차피 어떤 모델로 번역했는지는 디렉토리 구조나 로그에서 추적 가능하니까.

삽질에서 배운 것

이번 리팩토링에서 가장 크게 배운 건 "AI 에이전트도 결국 소프트웨어"라는 점이다.

서브에이전트가 파일을 직접 저장하게 하니까 동시성 문제가 터졌다. 멀티스레드 프로그래밍에서 공유 자원 접근을 통제하는 것과 똑같은 문제다. 해법도 같았다. 파일 쓰기 권한을 한 곳(메인 에이전트)으로 집중시키면 된다.

Haiku로 돌리는 서브에이전트가 "있으나 마나한" 용어(source와 target이 같은 약어)를 걸러내지 못하는 것도 재밌는 포인트다. 추출은 저비용 모델로 빠르게, 검수는 고비용 모델로 꼼꼼하게. 사람 조직에서 주니어가 초안을 쓰고 시니어가 리뷰하는 것과 크게 다르지 않다.

관심사 분리, 동시성 제어, Short-Circuit 최적화. 멀티 에이전트 시스템을 설계할 때도 전통적인 설계 원칙이 그대로 먹힌다. LLM이라고 마법이 아니다.

자주 묻는 질문

서브에이전트가 같은 파일을 동시에 수정하면 어떻게 되나요?
파일 충돌이 발생하여 한쪽 결과가 덮어씌워지거나 데이터가 유실됩니다. 이 글에서는 서브에이전트 출력을 분리한 뒤 메인에이전트가 병합하는 패턴으로 해결합니다.
glossary short-circuit란 무엇인가요?
이미 용어 추출이 완료된 논문은 glossary_output 디렉토리에 파일이 존재합니다. 이 경우 추출 단계를 건너뛰고 바로 번역을 실행하여 불필요한 API 호출과 비용을 절약합니다.
논문 번역기(4/8)
Prev

논문 번역에서 용어가 뒤죽박죽이라면: TranslateGemma + 마스터 용어 사전

Next

Zotero에서 원클릭 논문 번역: Claude Headless로 서버 만들기