InsuQ
약관을 근거 조항과 함께 답하는 RAG 에이전트
무엇을 푸나
보험 약관은 조항이 서로 얽혀 있고(참조 조항), 보장금액표·감액표가 많으며, 같은 상품도 가입 시점에 따라 보장이 다르다. InsuQ는 약관을 검색 가능한 지식베이스로 만들고 출처 조항을 명시해 답한다. 못 찾으면 '약관에서 확인 불가'라고 밝힌다 — 보험은 오답의 비용이 커서, 틀리게 답하느니 모른다고 답하는 쪽을 택했다.
QMesh는 금융 FinAllQ · 보험 InsuQ · 제조 MaintQ — 스택도 소유권도 다른 세 AI 에이전트를 A2A 프로토콜로 잇는 중립 오케스트레이션 허브입니다. 세 도메인은 서로의 DB도, 프레임워크도, 내부 프롬프트도 모릅니다. 오직 Agent Card와 표준 Task 스키마로만 대화합니다.
MCP = 에이전트가 자기 도구를 쓴다 (도메인 내부)
A2A = 독립된 타사 에이전트에게 Task를 위임한다 (QMesh가 조율)
하나의 request_chain_id가 홉 전체를 관통한다
MCP는 한 에이전트가 자기 도구를 쓰는 문제를 푼다. A2A는 소유권이 다른 조직의 에이전트에게 작업을 위임하는 문제를 푼다. 뒤쪽에는 앞쪽에 없는 것들이 붙는다 — 신원 증명, 사람의 승인 대기, 그리고 끝까지 따라가는 추적.
input-required — 상대 조직의 사람이 결재를 마칠 때까지 기다린다.세 프로젝트는 기술 스택도 소유권도 다른 '독립 회사'다. 연결 코드를 어느 한쪽 안에 넣으면 그 순간 경계가 사라진다. 그래서 QMesh를 바깥에 뒀다 — 누구의 소유도 아닌 중립 프로토콜 계층.
QMesh는 각 프로젝트의 DB도, 프레임워크도, 내부 프롬프트도 알지 못한다. Java Spring과 Python FastAPI와 LangGraph가 섞여 있어도, 바깥에서 보이는 것은 표준 HTTP 창구 하나뿐이다.
각 도메인은 자기 일정으로 배포한다. QMesh는 그 위에서 크로스도메인 워크플로우만 따로 갱신한다. 실제로 세 레포는 서로 다른 스프린트를 돌면서 동시에 개발됐다.
세 에이전트가 서로 직접 대화한다. MaintQ가 FinAllQ에 대출을 물으면, FinAllQ는 다시 InsuQ에 담보 보험을 확인한다 — 최초 요청자는 두 번째 홉이 일어났다는 사실조차 알 필요가 없다.
| 도메인 | 에이전트 | 기술 스택 | A2A 창구 |
|---|---|---|---|
| 조율 | QMesh Orchestrator | Python · FastAPI | :9000 |
| 금융 | FinAllQ | Java 21 · Spring Boot 3 · PostgreSQL · React 19 · LangGraph | :9001 |
| 보험 | InsuQ | Java Spring · Python FastAPI · Qdrant · LangGraph | :9002 |
| 제조 | MaintQ | Python FastAPI · Next.js · PostgreSQL(pgvector) · MCP | :9003 |
FastAPI + httpx + pydantic(Python) 또는 Spring MVC 직접 구현으로 JSON Schema 계약을 손으로 구현했다. 프로토콜을 라이브러리에 맡기지 않고 직접 짜본 것이 이 프로젝트의 목적이었다.세 프로젝트는 A2A를 위해 급조된 목업이 아니다. 각각 독립적으로 설계·측정·평가된 서비스이고, 그 위에 통신 계층을 얹었다.
약관을 근거 조항과 함께 답하는 RAG 에이전트
보험 약관은 조항이 서로 얽혀 있고(참조 조항), 보장금액표·감액표가 많으며, 같은 상품도 가입 시점에 따라 보장이 다르다. InsuQ는 약관을 검색 가능한 지식베이스로 만들고 출처 조항을 명시해 답한다. 못 찾으면 '약관에서 확인 불가'라고 밝힌다 — 보험은 오답의 비용이 커서, 틀리게 답하느니 모른다고 답하는 쪽을 택했다.
설비 진단부터 부품 발주까지 잇는 보전 에이전트
중소 제조공장에서 인버터·PLC 에러가 나면 PDF 매뉴얼을 10~30분 뒤지고, 고참 정비사 경험에 의존해 진단하고, 자재담당에게 전화로 재고를 확인하고, 엑셀로 발주서를 쓴다. 매체가 계속 바뀌고 단계마다 대기가 생긴다. CMMS는 기록 시스템이고 매뉴얼은 정적 문서다 — MaintQ는 그 사이의 판단을 자동화하고, 판단 결과를 조달 액션까지 연결한다.
create_po_draft·generate_disposal_document·create_repair_record)은 전부 draft INSERT만 가능하고 UPDATE 권한이 없다. 승인·반려·서명은 사람 전용 API만 한다 — 프롬프트가 아니라 도구 권한 수준에서 강제된다.크로스도메인 시나리오 대부분의 발신자(client)다. 설비에서 시작된 사건이 금융·보험으로 번져나가는 구조라, S5~S16의 출발점이 거의 항상 MaintQ다.
은행과 증권을 한 곳에 모은 개인화 금융 비서
파편화된 은행(자산·보안) 데이터와 증권(투자) 데이터를 단일 대시보드로 통합하고, 대출 신청부터 이체까지 여신 업무를 자동화한다. 코어 트랜잭션은 Spring+RDBMS로, 모델 서빙은 Python 마이크로서비스로 격리한 하이브리드 MSA다 — 탐지는 빠르고 값싼 ML이, 설명은 그래프가 맡는다.
plan·synthesize 노드는 규칙기반·템플릿이고 FDS·스미싱 탐지도 scikit-learn 고전 기법뿐이었다. 금융 판정에 생성 모델을 쓰지 않겠다는 선택이 코드에 그대로 남아 있다 — 세 에이전트가 같은 방식으로 만들어졌다고 말하지 않기 위해 적어둔다.아래 캡처는 전부 2026-08-24에 실제 서비스를 상대로 찍은 것이다. 목업 응답이나 재현 영상이 아니라, 실 DB·실 인증·실 A2A 호출이 통과한 화면이다.
진단(INV-L3-02 · OCT) → 발주 초안 → 팀장 승인 → 재무 승인 → 진단보고서·발주요청서·자금집행요청서 3종이 실 데이터로 렌더 → A2A 출금 요청 전송 → /manager/a2a 이력에서 CHAIN-PO-0122-b66672d7 · ok 확인까지 전 구간 라이브. 돈이 움직이는 요청이라 요청 부서 승인 → 집행 부서 재무 승인 2단 계단을 거치고, 그 뒤로도 FinAllQ 내부 결재함에서 사람이 한 번 더 결재한다.
정비사가 채팅에 "인버터 과전압 손해가 약관상 보장되나?"라고 물으면 MaintQ가 InsuQ에 lookup-clause를 던지고, InsuQ는 근거 조항 8건을 인용하며 판정은 "판단 유보"로 돌려준다. 실측 9.7초 — 처음엔 에이전트 루프의 전역 타임아웃 10초가 병목이라 계속 끊겼고, 이 도구 하나에만 예외를 둬서 풀었다.
5억원 대출 신청(담보 BLD-A, 인정액 3억) → FinAllQ가 자체 LTV 판정을 하면서 InsuQ에 담보 보험 충분성까지 확인 → 1.3초 만에 decision: conditional(보장 부족) 반환 → /manager/a2a에서 CHAIN-LOAN-a0639920 · ok. 5천만원으로 찍으면 무조건 approved만 나와서, 판정 로직이 실제로 동작한다는 걸 보여주려고 일부러 부결 나는 금액을 골랐다.
| ID | 시나리오 | 경로 | 성격 | 스킬 |
|---|---|---|---|---|
| S5 | 발주 승인 → 출금 요청 | MaintQ → FinAllQ | 자금이동 (2단 승인) | request-withdrawal |
| S6 | 환헤지 · 여유자금 운용 상담 | MaintQ → FinAllQ | 제안 (위험도 0) | advise-hedge |
| S7 | 화재보험 갱신 (고장이력 연계) | MaintQ → InsuQ | 상담 + 데이터 연계 | advise-policy-renewal |
| S8 | 담보 대출 심사 시 보험 검증 | MaintQ → FinAllQ → InsuQ | 멀티홉 릴레이 | assess-loan |
| S11 | 설비 개조 · 폐기 자산변동 통지 | MaintQ → InsuQ | 원장 갱신 통지 | notify-asset-change |
| S13 | 중고설비 담보대출 (비례보상) | MaintQ → FinAllQ → InsuQ | 멀티홉 심사 | assess-used-equipment-loan |
| S14 | 설비 사고 보험금 청구 | MaintQ → InsuQ | 청구 접수 · 조사 | claim-insurance |
| S15 | 전손 교체 금융 상담 | MaintQ → FinAllQ | 연쇄 금융 제안 | advise-replacement-financing |
| S16 | 신규 설비 도입 자금조달 비교 | MaintQ → FinAllQ | 비교 상담 | advise-financing |
에이전트끼리 자율적으로 대화하게 두면, 사람이 책임지던 판단이 조용히 자동화된다. 이 세 가지는 그 일이 일어나지 않게 하려고 프로토콜 층에 못 박아 둔 규칙이다.
AI 에이전트는 요청서 · 제안서 · 심사 초안까지만 작성한다. 실제 계좌 출금, 대출 실행, 보험 계약 체결은 반드시 승인권자의 결정 뒤에 실행된다. 자금이 이동하는 작업(S5)은 요청 부서 승인 → 집행 부서 재무 승인의 2단 승인 계단을 거친다.
A2A 응답의 input-required 상태가 이 대기를 표현한다 — 요청은 성공했지만 아직 끝나지 않았고, 상대 조직의 사람이 결재를 마칠 때까지 기다린다는 뜻이다.
Actor는 인증의 문제다 — 비대칭 서명된 파트너 액세스 토큰으로 위조 불가능한 신원을 증명한다. Subject는 인가의 문제다 — 페이로드의 finallq_company_id, building_id, policy_id가 처리 대상을 명시한다. 둘을 섞으면 '인증된 상대가 아무 데이터나 만질 수 있는' 구멍이 생긴다.
자격증명은 위험도에 따라 3단계로 쪼갰다 — 조회/상담(위험도 0, 정보 조회만) · 심사(위험도 저, 판정은 하되 실행 권한 없음) · 자금이동(위험도 고, input-required 기반 2단 승인 필수).
모든 A2A 요청·응답 헤더에 request_chain_id를 전파한다. MaintQ → FinAllQ → InsuQ로 이어지는 여정이 세 조직의 로그에 흩어지지 않고 단일 타임라인으로 묶여 감사 로그에 남는다. 위 데모의 CHAIN-LOAN-a0639920·CHAIN-PO-0122-b66672d7이 그 값이다.
여기에 Idempotency-Key 3중 복합키를 얹어 재시도가 중복 처리로 번지지 않게 했다.
Authorization: Bearer + X-A2A-Partner-Id 자기신고 헤더만 검사하고 있었고 — FinAllQ→InsuQ 2차 홉은 이미 그 스킴으로 실 성공 중이었다. MaintQ 쪽 credentials.py·auth_header.py를 실제 스킴에 맞춰 다시 썼다.세 프로젝트 모두 평가 하네스로 baseline을 숫자로 고정한 뒤 개선했다. 미달 항목을 지우면 나머지 숫자도 못 믿게 되므로 그대로 둔다.
| 에이전트 | 지표 | 결과 | 목표 | 상태 |
|---|---|---|---|---|
| InsuQ | 검색 성능 Hit@5 | 0.8333 | 0.85 | 미달 (1문항) |
| InsuQ | 거부 정확도 | 100% | ≥ 90% | 초과 달성 |
| InsuQ | 오탐율 (환각) | 0% | 0% | 달성 |
| InsuQ | 응답 레이턴시 p95 | 10.4s | ≤ 30s | 달성 |
| MaintQ | 근거 페이지 인용률 | 90.7% | 100% | 미달 |
| MaintQ | 부품 특정 정확률 | 40.0% | ≥ 90% | 미달 (원천 데이터 문제) |
| MaintQ | 미지 에러코드 환각 | 0% | 0% | 달성 |
| MaintQ | 권한 위반 차단 | 100% | 100% | 달성 |
| FinAllQ | 테스트 (백엔드 957 · 프론트 480 · 어댑터 130) | 1,567 | 전부 GREEN | 달성 |
| FinAllQ | 회귀 | 0 | 0 | 달성 |
| QMesh | 어댑터 테스트 (finallq_a2a · insuq_a2a) | 113 | 전부 GREEN | 달성 |
데모에서 구현된 것과 아닌 것이 섞이면 안 된다. 아래는 2026-08-24 기준 실제 상태다.
세 에이전트의 스킬 선언서와 요청/응답 스키마가 QMesh 레포에 SSOT로 고정돼 있다. 어느 도메인도 이 계약을 자기 마음대로 바꿀 수 없다.
verify-collateral-insurance · advise-policy-renewal · notify-asset-change · notify-risk-change · claim-insurance. 서비스 간 인증, request_chain_id 감사로그, Idempotency-Key 인프라, Task 생명주기 상태머신까지 포함. claim-insurance의 requires_human_approval:true는 우회 불가능한 하드 리터럴이다.
assess-loan은 InsuQ 2차 홉 호출까지 연결됐고, request-withdrawal은 실 서비스 계정(CORPORATE)으로 종단 실동작을 확인했다. 폴링이 결재대기와 TOTP필요를 pending_action으로 구분한다. 그래도 Loan.status는 여전히 UNDER_REVIEW — 자동승인은 없다.
request-withdrawal(S5) · lookup-clause · assess-loan(S8 멀티홉). 위 데모 3종이 그 캡처다.
finallq_a2a(:9101, request-withdrawal·assess-loan) · insuq_a2a(:9102, lookup-clause). 각 도메인 레포에 얹기 전의 참조 구현이다.
지금은 세 에이전트가 서로 직접 호출한다. 중앙 오케스트레이터와 통합 기동 환경은 아직 설계 단계다 — README의 레포 구조에 적힌 orchestrator/·contracts/는 목표 구조이지 현재 상태가 아니다.
기술적으로는 만들 수 있다. 만들지 않았다 — CORPORATE 서비스 계정이 결재권자를 대신해 코드를 내면 2단계 인증이 1단계로 붕괴한다. 이건 못 만든 기능이 아니라 지킨 불변식이다.
Agent Card 3종, Task JSON Schema 13종, 신원 식별 및 보안 아키텍처 결정서(SSOT), S5~S16 시나리오 상세 명세.