업무 트랜잭션에 LLM을 연결할 때 정한 다섯 가지 경계
AI가 초안을 만들더라도 권한, 상태 전이와 최종 쓰기는 기존 업무 서비스가 책임지도록 OpsMate Local의 경계를 설계했습니다.
구매 요청에 LLM을 적용하면 자연어를 구조화된 초안으로 바꾸는 데 도움을 받을 수 있습니다. 하지만 모델의 응답을 그대로 승인이나 발주 명령으로 사용하면 기존 업무 시스템이 지켜온 권한과 상태 규칙을 우회하게 됩니다.
OpsMate Local에서는 모델의 역할을 초안 제안으로 한정하고, 실제 업무 변경은 Spring 서비스가 책임지도록 다섯 가지 경계를 정했습니다.
1. 모델 출력은 명령이 아니라 제안입니다
모델은 제목, 목적, 금액, 분류와 참고한 정책 ID를 구조화된 형태로 제안합니다. 응답이 JSON 형식에 맞더라도 서버의 입력 검증과 정책 ID 확인을 통과하기 전에는 구매 요청을 저장하지 않습니다.
승인, 반려와 발주 API는 모델에게 노출하지 않습니다. 이 작업은 각 역할로 인증된 사용자의 요청을 통해서만 실행됩니다.
2. 정책 조회는 서버가 먼저 수행합니다
현재 구현은 모델이 도구를 선택하거나 호출하는 tool-calling 구조가 아닙니다. 서버가 타입이 고정된 정책 조회 포트를 먼저 실행하고, 사용자 요청과 조회 결과만 모델에 전달합니다.
정책 조회 포트에는 URL, SQL이나 파일 경로를 실행 인자로 받는 API가 없습니다. 요청 문자열에 이런 문자가 포함되더라도 검색용 텍스트로만 취급하며 실행하거나 경로를 해석하지 않습니다. 모델이 응답에 포함한 정책 ID도 서버가 실제 조회 결과의 부분집합인지 다시 확인합니다.
3. 권한과 상태 전이는 애플리케이션이 결정합니다
요청자, 승인자, 구매 담당자와 감사 역할을 분리하고 endpoint와 service method에서 권한을 확인합니다. 요청자는 자신의 초안만 제출할 수 있고, 승인자는 자기 요청을 승인할 수 없으며, 구매 담당자는 승인된 요청만 발주할 수 있습니다.
DRAFT -> PENDING_APPROVAL -> APPROVED -> ORDERED
\-> REJECTED
허용되지 않은 상태 전이는 도메인 객체가 거부합니다. 모델이 응답 텍스트에서 “승인됐다”고 주장하더라도 실제 상태에는 영향을 주지 않습니다.
4. 재시도와 중간 실패를 정상 시나리오로 다룹니다
초안과 발주 생성에는 멱등성 키를 사용합니다. 같은 키와 입력으로 다시 요청하면 기존 결과를 반환하고, 같은 키에 다른 입력을 사용하면 충돌로 거부합니다. 발주는 데이터베이스 고유 제약으로 요청당 한 건만 허용합니다.
발주 저장, 요청 상태 변경과 감사 이벤트 기록은 하나의 트랜잭션으로 묶었습니다. 후처리에서 오류가 발생하면 세 작업을 함께 되돌립니다.
5. 모델을 사용할 수 없으면 작업을 중단합니다
모델 설정이 없거나 HTTP 호출에 실패하면 초안 API는 503 Service Unavailable로 끝납니다. 빈 응답이나 잘못된 JSON처럼 출력 형식이 유효하지 않으면 502 Bad Gateway를 반환합니다. 두 경우 모두 구매 요청은 저장하지 않으며 외부 유료 API로 자동 우회하지 않습니다.
모델 장애는 예외적인 사고가 아니라 테스트해야 할 정상적인 실패 경로로 다룹니다.
현재 구현과 다음 검증
구매 요청 생성, 제출, 승인·반려, 발주와 감사 이벤트에 공개 session UI, workspace 격리·TTL, 모델 single-flight·호출량·동시 실행 제한, PostgreSQL 역할 분리와 배포·중단 자산을 추가했습니다. 2026-08-04 전체 clean verify에서 권한 위반, 모델 오류, 동시 요청, 상태 충돌, PostgreSQL migration과 트랜잭션 롤백을 포함한 54개 자동화 테스트가 통과했습니다.
Ollama 연동 계층은 mock HTTP 서버로 정상·오류·과대 응답을 확인했습니다. 승인된 실제 오픈웨이트 모델의 생성 품질·p95·GPU 요구량, 공개 URL과 외부 network gate, 앱·모델 양쪽 호스트의 close/reopen rehearsal은 아직 검증하지 않았습니다.