단순 프롬프트를 넘어: AI 에이전트 설계
Source: Dev.to
The Problem Space
Contract review follows a predictable pattern. A legal team receives a counter‑party’s redlined contract, reviews each change against the organization’s risk tolerance, and either accepts, rejects, or modifies each suggestion. This process can take hours for a single contract.
계약 검토는 예측 가능한 패턴을 따릅니다. 법무팀은 상대방이 레드라인 처리한 계약서를 받아 조직의 위험 허용 범위에 따라 각 변경 사항을 검토하고, 각 제안을 수락, 거부, 또는 수정합니다. 이 과정은 하나의 계약서에 몇 시간이 걸릴 수 있습니다.
When I set out to automate it, I realized the problem involves multiple aspects, including but not limited to:
자동화를 시도하면서, 문제는 다음과 같이 여러 측면을 포함하고 있음을 깨달았습니다(이것에 국한되지 않음):
- 특정 가이드라인에 따라 계약서 분석
- 합리적인 근거와 함께 구체적인 텍스트 제안 생성
- Word 트랙 변경으로 변경 적용 – 일반 텍스트 교체가 아니라
- 문서 변형에도 견디기 – 분석이 진행되는 동안 사용자가 계약서를 편집함
Requirements #3 and #4 are where most tools stumble. They output suggestions in a chat interface, leaving users to copy‑paste and re‑format manually. That’s not automation; it’s a fancier Ctrl + F.
요구 사항 #3과 #4가 대부분의 도구가 어려움을 겪는 부분입니다. 이 도구들은 제안을 채팅 인터페이스에 출력하여 사용자가 직접 복사‑붙여넣기하고 수동으로 재포맷해야 합니다. 이는 자동화가 아니라 더 정교한 Ctrl + F에 불과합니다.
시스템 아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Web App │ │ Word Add‑in │ │ Backend │ │
│ │ (Next.js) │ │ (Office.js) │ │ (FastAPI) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ REST + SSE │ REST + SSE │ │
│ └────────────────────┴────────────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ Analysis Engine │ │
│ │ ┌───────────────┐ │ │
│ │ │ DSPy + LLM │ │ │
│ │ │ (OpenAI / │ │ │
│ │ │ Mistral) │ │ │
│ │ └───────────────┘ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
세 가지 핵심 구성 요소
- Web Dashboard – 규칙 관리, 분석, 관리 기능을 제공하는 Next.js 애플리케이션.
- Word Add‑in – 사용자가 실제로 계약서를 검토하는 Microsoft Office 플러그인 (React + Office.js).
- Backend API – 분석, LLM 오케스트레이션 및 문서 처리를 담당하는 FastAPI 서비스.
흥미로운 엔지니어링은 Word Add‑in (문서 조작)과 Backend API (분석 파이프라인) 부분에 집중됩니다.
챌린지 #1: 가변 문서 문제
시나리오가 나이브 구현을 깨뜨리는 경우
| Step | What Happens |
|---|---|
| 1️⃣ | 사용자가 50페이지짜리 계약서를 업로드합니다. |
| 2️⃣ | 시스템이 1‑50단락을 분석하고, 단락 인덱스를 키로 해서 제안을 저장합니다. |
| 3️⃣ | 대기 중에 사용자가 12단락을 삭제합니다. |
| 4️⃣ | 시스템이 “47단락을 수정해야 합니다.” 라고 반환합니다. |
| 5️⃣ | 47단락은 이제 46단락이 되었으므로, 제안이 잘못된 위치에 적용됩니다. |
해결책 – 단락 앵커링
전처리 단계에서 각 단락에 영구 ID를 할당하는 로직을 구축했습니다. 이 ID들은 OOXML에 저장되어 다음 상황에서도 유지됩니다:
- 인접 단락 삭제
- 섹션 잘라내기 및 붙여넣기
- 다른 추적 변경을 수락/거부
프론트엔드에서는 Zustand 스토어가 양방향 매핑을 유지합니다:
interface ParagraphStore {
/** index → UUID */
indexToPersistentIdMap: Map;
/** UUID → index */
persistentIdToIndexMap: Map;
/** Fallback matching by text */
findAnchorByText(text: string): string | null;
}
분석 결과가 반환될 때는 UUID를 참조합니다. 스토어는 현재 단락 인덱스를 적용 시점에 해결하므로, 분석 시점이 아니라 최신 문서 상태에 맞게 제안을 적용할 수 있습니다.
챌린지 #2: Word 추적 변경 생성
이것이 가장 어려운 부분입니다. Office.js는 추적 변경을 생성하는 API를 제공하지 않으며, paragraph.insertText()는 단순히 텍스트를 교체합니다. 실제 빨간선(취소선 삭제, 색상 삽입)을 만들려면 다음을 수행해야 합니다:
- 원본 텍스트와 제안된 텍스트 사이의 diff 생성.
- 그 diff를 OOXML 요소(예:
<w:del>,<w:ins>) 로 변환. - Office.js를 통해 해당 OOXML 요소들을 문서에 적용.
토큰 기반 Diffing
문자 수준 diff는 Word에서 잡동사니를 생성합니다.
T̶h̶e̶A quick b̶r̶o̶w̶n̶red fox
토큰 수준 diff는 훨씬 깔끔합니다:
The → A quick brown → red fox
단락 속성 보존
계약서는 번호 매기기, 들여쓰기, 스타일에 크게 의존합니다. 순진한 교체는 이러한 서식을 파괴합니다. 따라서 diff‑to‑OOXML 변환은 원본 단락 속성을 보존하고 추적 변경 마크업만 삽입합니다.
Source:
챌린지 #3: 장기 실행 분석
30개의 플레이북 규칙이 포함된 50페이지 분량의 계약서는 2–3 분 정도 걸릴 수 있습니다. HTTP 요청을 그 정도 시간 동안 차단하는 것은 허용될 수 없습니다.
세션 기반 비동기 처리
Client Server
│ │
│ POST /analysis/start → {sessionId}
│ │
│←─────────────────────────────│
│ │
│ GET /analysis/status?sessionId → {status, progress}
│ │
│←─────────────────────────────│
│ │
│ GET /analysis/result?sessionId → {suggestions}
│ │
│←─────────────────────────────│
- 클라이언트는 분석 세션을 시작하고
sessionId를 받습니다. - 서버는 무거운 LLM 기반 파이프라인을 백그라운드 워커(예: Celery, RQ)에서 실행합니다.
- 클라이언트는 상태를 폴링하거나 Server‑Sent Events(SSE) 업데이트를 수신합니다.
- 분석이 완료되면 클라이언트는 제안을 가져오며, 여기에는 영구적인 단락 ID가 포함됩니다.
요약
| ✅ 성공한 점 | ❌ 어려웠던 점 |
|---|---|
| 지속적인 단락 ID가 사용자 편집을 견딥니다. | Office.js에는 기본 추적 변경 API가 없습니다. |
| 토큰 수준 차이점이 Word 출력의 가독성을 유지합니다. | 비동기 결과를 실시간 문서에 매핑합니다. |
| 세션 기반 비동기 처리가 UI 반응성을 유지합니다. | 경계 사례 처리(표, 각주, 머리글). |
강력한 앵커링, 토큰 수준 차이 → OOXML 변환, 그리고 비동기 세션 처리를 결합함으로써 문서의 원본 형식을 유지하고 사용자 주도 변형을 수용하는 진정한 자동 계약 검토 경험을 제공할 수 있습니다.
TL;DR
- UUID를 할당하여 각 단락에 부여하고 OOXML에 저장합니다.
- 토큰 수준에서 차이를 비교, 차이를 추적 변경 OOXML로 변환한 뒤 Office.js를 통해 삽입합니다.
- 분석을 비동기적으로 실행하고 지속적인 ID를 키로 결과를 반환합니다.
결과: 법무팀이 AI가 생성한 수정 표시와 함께 계약을 검토할 수 있는 원활한 엔드‑투‑엔드 시스템으로, Word를 떠날 필요가 없습니다.
세션 기반 폴링 예시
POST │
─────►│ 세션 생성
{ session_id: "abc123" } │ 백그라운드 작업 시작
◄─────│
│
GET /sessions/abc123 │
─────►│
{ status: "processing", │
progress: 45% } │
◄─────│
│
... 3초마다 폴링 ... │
│
GET /sessions/abc123 │
─────►│
{ status: "complete", │
results: [...] } │
◄─────│
콘텐츠 해싱을 이용한 캐시 검증
사용자는 동일한 계약을 여러 번 분석하는 경우가 많습니다—다른 가이드라인을 적용하거나 사소한 수정 후 확인하는 경우 등. 변경되지 않은 콘텐츠를 다시 분석하면 시간과 API 비용이 낭비됩니다.
해시 비교를 통해 다음을 감지합니다:
- 동일한 파일의 재업로드
- 실제 변경 없이 “다시 분석” 클릭
- 여러 사용자가 동일한 템플릿을 분석
프로덕션에서의 캐시 적중률: 일반적인 계약 검토 워크플로우에서 약 40 %입니다.
챌린지 #4 – 근거 확보 및 환각 방지
법률 문서는 정확성이 필요합니다. 계약서에 “$500K”라고 명시되어 있는데 AI가 “Vendor liability is capped at $1M”(공급업체 책임이 $1M으로 제한됨)이라고 제안하는 것은 전혀 제안하지 않는 것보다 더 나쁩니다.
Solution: Use Structured Output with Explicit Citations.
Every suggestion must reference the exact source text. This catches cases where the model paraphrases instead of quoting verbatim.
분석 파이프라인
┌────────────────────────────────────────────────────────────────┐
│ Redline Analysis Pipeline │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. DOCUMENT INGESTION │
│ ┌─────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ DOCX │────>│ Extract │────>│ Paragraph │ │
│ │ File │ │ OOXML │ │ Anchoring │ │
│ └─────────┘ └─────────────┘ └──────────────┘ │
│ │
│ 2. CONTENT NORMALIZATION │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ OOXML with │────>│ Unified │ │
│ │ Tracked │ │ Markdown │ │
│ │ Changes │ │ (Original + │ │
│ │ │ │ Revised views) │ │
│ └─────────────┘ └─────────────────┘ │
│ │
│ 3. LLM ANALYSIS │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ │────>│ DSPy │────>│ Structured │ │
│ │ Rules │ │ Signatures │ │ Suggestions │ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
│ │
│ 4. OUTPUT GENERATION │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ Suggestions │────>│ Token Diff │────>│ OOXML │ │
│ │ + Rationale │ │ Algorithm │ │ │ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
OOXML‑to‑Markdown 변환
들어오는 계약서는 종종 상대방 협상 과정에서 발생한 추적 변경(Tracked Changes)을 이미 포함하고 있습니다. 변환기는:
- OOXML 요소를 파싱합니다.
- 두 개의 동기화된 뷰를 생성합니다: Original(삭제만 표시, 삽입 없음)과 Revised(삽입만 표시, 삭제 없음)
- 콘텐츠 컨트롤에서 가져온 단락 ID를 보존합니다.
이 추상화 덕분에 LLM은 원시 XML 대신 깔끔한 Markdown에서 작업할 수 있으며, 변환 복잡성은 변환 레이어에 격리됩니다.
결과
| 지표 | 값 |
|---|---|
| 처리 시간 (20‑페이지 계약) | 30‑45 초 (규칙 복잡도에 따라 다름) |
| 캐시 적중률 | ~40 % (변경되지 않은 내용에 대한 재분석을 절약) |
| 환각률 | < 5 % (검증으로 포착, 사용자에게 표시되지 않음) |
| 형식 보존 | 95 % (단락 속성 유지) |
| 추적 변경 정확도 | 토큰 수준 정밀도 |
Lessons Learned
- Office.js는 강력하지만 문서가 부족합니다. OOXML 조작 패턴은 공식 가이드에 없으며, 문서를 내보내고 XML을 읽어 역공학했습니다.
- 문서에 문자 수준 차이는 부적절합니다. 항상 먼저 토큰화하세요; 일반 diff 라이브러리는 단어 경계를 이해하지 못합니다.
- 비동기 패턴은 생각보다 중요합니다. 세션 기반 폴링은 간단해 보이지만, 브라우저 새로고침, 네트워크 끊김, 서버 재시작 같은 예외 상황을 처리하려면 신중한 상태 관리가 필요했습니다.
- 모든 것을 근거로 삼으세요. LLM은 존재하지 않는 텍스트를 자신 있게 인용합니다. 검증 레이어가 이를 잡아내지만, 출력 스키마가 명시적인 출처 참조를 요구할 때만 가능합니다.
- 콘텐츠 해싱은 저비용 보험입니다. SHA‑256 계산은 LLM 비용에 비해 무시할 수준이며, 캐시 검증은 첫 주에 이미 비용을 회수했습니다.
기술 스택 요약
| 계층 | 기술 | 이유 |
|---|---|---|
| 백엔드 API | FastAPI (Python) | 비동기 네이티브, 장기 실행 작업에 적합 |
| LLM 오케스트레이션 | DSPy | 구조화된 출력, 제공자에 구애받지 않음 |
| LLM 제공자 | OpenAI, Mistral | 중복성, 비용 최적화 |
| 데이터베이스 | Supabase (PostgreSQL) | 실시간 구독, 호스팅 |
| 웹 프론트엔드 | Next.js | 대시보드용 SSR, API 라우트 |
# Word Add‑in
**Technology:** React + Office.js
*Only option for Word integration*
워드 애드인
Technology: React + Office.js
Only option for Word integration
문서 처리
- 도구:
python-docx, custom OOXML - 제한 사항: 어떤 라이브러리도 추적된 변경 사항을 처리하지 못합니다.
마무리 생각
“AI for X” 제품에서 흥미로운 엔지니어링은 거의 AI 자체가 아니다.
LLM API를 호출하는 것은 간단하다. 도전 과제는 그 주변의 모든 것이다:
- 문서 충실도 유지
- 장기 실행 작업 전반에 걸친 상태 관리
- 사용자가 보기 전에 모델 실패를 포착하는 검증 레이어 구축
법률 레드라인 작업은 내가 예상하지 못한 문제들을 해결하도록 만들었다—단락 앵커링, OOXML 조작, 토큰 기반 차이점 분석. 각 솔루션은 더 나은 프롬프트를 찾는 것이 아니라 도메인을 깊이 이해함으로써 나왔다.
이 분야에서 작업하고 있다면, 당신의 접근 방식에 대해 듣고 싶다.
Arun Venkataramanan은 Ottimate의 시니어 소프트웨어 엔지니어로, 계정지불 자동화를 위한 솔루션 아키텍처를 담당한다. 핵심 은행 시스템(TCS), 핀테크 플랫폼, 엔터프라이즈 자동화에 걸친 배경을 가지고 있으며, 일상 업무에서 사용자가 반복 작업을 자동화하도록 돕는 솔루션 및 도구 구축에 집중한다.
LinkedIn에서 연결하세요(LinkedIn).