빌드잇은 엔터프라이즈 솔루션 10년 위에서 'AI Agent 기반 프로세스 자동화 솔루션 제품화' 로 전환 중인 회사입니다. 자유 텍스트 업무 요청을 사내 시스템 액션으로 잇는 이 라우터는, 빌드잇이 만들려는 바로 그 제품의 최소 뼈대이고 Kotlin/Spring·FastAPI·메시지 큐·EKS 라는 JD 스택을 결정·실행 분리 MSA 로 그대로 씁니다. 다만 이 포트폴리오가 보여주는 것은 자유 텍스트에서 구조화된 액션 결정을 뽑고 인용으로 검증해 큐로 디스패치하는 기술이지, 특정 고객사의 결재·ERP 도메인 전문성이 아닙니다. 실 커넥터 권한과 고객사별 업무 규칙의 깊이는 모르면 면접에서 모른다고 말하는 편이 낫습니다.
엔터프라이즈 솔루션 10년 위에서 'AI Agent 기반 프로세스 자동화 솔루션' 을 제품화하는 단계입니다. 자유 텍스트 업무 요청을 사내 시스템 액션으로 잇는 워크플로우가 바로 그 제품의 최소 뼈대입니다.
작은 팀에서 요구사항 분석부터 배포까지 자기주도적으로 완결하는 역할입니다. 2주면 요청 인테이크 → 액션 결정 → 큐 디스패치 → 콘솔 추적 한 루프가 끝나는 최소 단위입니다.
Kotlin/Spring 과 Python/FastAPI, 메시지 큐(Kafka·RabbitMQ), Docker/Kubernetes, AWS 가 주요 스택입니다. 결정 서비스와 실행 커넥터를 큐로 분리한 MSA 로 이 스택을 그대로 골조에 씁니다.
네이버웍스·슬랙으로 들어오는 사내 업무 요청(자유 텍스트)을 AI Agent 가 읽어, 의도와 실행할 액션 타입(승인·조회·등록), 액션 ID, 필요한 파라미터를 뽑고 파라미터마다 원문 인용 근거를 붙입니다. 값이 빠졌거나 매핑되는 액션이 없으면 지어내지 않고 결측·미매핑으로 알린 뒤 사람에게 라우팅하고, 결정된 액션은 Kotlin/Spring 게이트웨이가 메시지 큐로 커넥터에 디스패치합니다. 1주차만으로 요청→액션→감사 추적이 도는 완성품이고, 분류가 실제로 맞았는지 확인(2주차)·실 커넥터 연동과 레지스트리 확장(3주차)으로 단계적으로 키웁니다. (근거는 채용공고 정렬이고, 데이터는 작성자가 만든 더미입니다.)
빌드잇 PE. 사내로 쏟아지는 '다음 주 연차 올려주세요', '법카 3월 사용내역 조회', '회의실 프로젝터 고장 수리 티켓 등록' 같은 자유 텍스트 요청을 사람이 일일이 읽고 어느 시스템에 어떤 값으로 넣을지 손으로 판단하던 상황을 끝내고, 에이전트가 근거를 붙여 액션을 정하되 실제 실행은 큐로 안전하게 넘기게 하고 싶은 사람입니다.
업무 요청 원문과 실행 가능한 액션 카탈로그(required_params 포함)를 한 스키마로 묶으면, LLM 이 (a) 의도와 실행할 액션 타입·액션 ID 를, (b) 원문에서 인용한 근거(evidence_span)를 붙인 파라미터를, 몇 초 안에 뽑고, (c) 필요한 값이 빠졌거나 매핑되는 액션이 없으면 지어내지 않고 결측·미매핑으로 표시한 뒤 사람에게 라우팅합니다.
자유 텍스트 업무 요청을 받아 action plan(의도·액션 타입·액션 ID·파라미터·원문 인용 근거)을 생성하고, 인용이 원문에 실제로 있는지 검증한 뒤 목(mock) 커넥터로 큐 디스패치하고, 콘솔에서 요청→액션→커넥터 결과를 추적하는 동작하는 도구입니다. 실 사내 시스템(네이버웍스 결재·ERP·Jira)은 이번 주에는 목 API 로 가정하고, 실 커넥터 연동은 3주차로 분리합니다.
정답셋 40건으로 액션 타입·액션 ID 분류가 맞았는지 확인하고, Claude Haiku 저비용 2차 분류로 불일치·저확신 건을 사람 큐로 라우팅하며, RabbitMQ 재시도·DLQ 와 Kafka 감사 스트림을 붙인 뒤 2주차부터 AWS EKS 로 배포·운영하는 단계입니다.
목 커넥터를 실 시스템 어댑터 인터페이스로 교체 가능하게 추상화하고, 액션 카탈로그가 수백 개로 늘어도 후보를 검색(retrieval)으로 압축하며, Redis 멱등 키와 권한 가드로 중복 실행을 막는 단계입니다.
업무 요청 12건을 넣으면 각 요청의 action plan(의도·액션 타입·액션 ID·파라미터·원문 인용 근거)이 몇 초 안에 나오고 큐로 디스패치되는 걸 시연합니다
일부러 만든 결측·표기흔들림·미매핑·잡담 입력 30종을 넣어도 출력 형식(JSON Schema)이 한 번도 안 깨지고, 인용 span 이 원문에 실제로 존재하는지 자동 검증을 100% 통과합니다
내가 만든 정답 40건 중 액션 타입 분류가 일치한 게 34건(85%), 액션 ID 해소(미매핑→null 포함)가 일치한 게 31건입니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이므로 '성능 검증' 이 아니라 '데모 조건에서의 동작' 임을 명시합니다
매 결정과 커넥터 실행 결과가 감사 이벤트로 Kafka 에 남고, 콘솔에서 요청→액션→커넥터 결과가 추적되는 걸 시연합니다
위 수치는 전부 작성자가 만든 더미데이터 자가채점이며, 실제 분류 정확도·인용 정합은 빌드잇 실제 업무 요청과 실 커넥터 연동이 있어야 측정 가능합니다. 이 도구는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다
이건 고객·실데이터 없이 혼자 만든 포트폴리오입니다. 모든 수치는 합성 데이터 자가채점이라 '성능 증명' 이 아닙니다. 면접에서 솔직히 '데모 조건 동작 + 실검증 준비 상태' 로 설명합니다
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 일부러 헷갈리는 결측(금액 미기재)·표기흔들림(법카/월차)·미매핑(레지스트리에 없는 요청)·잡담·맥락부족 케이스를 더미에 섞어, 쉬운 문제만 푸는 게 아님을 보여줘야 합니다
자유 텍스트 의도 분류는 표면 단어에 휘둘리기 쉽고, 한 요청에 조회+결재가 섞인 멀티인텐트를 단정하면 위험합니다. 저확신·불일치 건은 지어내지 않고 사람에게 라우팅하며, 멀티인텐트는 1차 액션만 실행하고 후속을 별도 요청으로 분해합니다
실제 도입 시 달라지는 것을 명시합니다. 여기서는 사내 시스템 커넥터(네이버웍스 결재·ERP·Jira)를 목 API 로 두고 action plan 생성부터 큐 디스패치까지만 만들며, 실 커넥터 연동·실행 권한·감사 연계와 고객사별 업무 규칙의 깊이는 도입 단계 과제로 분리합니다
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
네이버웍스·슬랙·이메일·폼으로 들어온 사내 업무 요청 원문과, 규칙으로 정해지는 정답 라벨(액션 타입·액션 ID·파라미터·결측). 에이전트가 가장 먼저 받는 입력이자 자가채점의 정답입니다. 운영 현실대로 금액 결측·별칭 표기·미매핑·맥락부족·잡담·멀티인텐트를 일부러 섞어, 쉬운 데이터만 푸는 게 아님을 보여줍니다.
| request_id | channel | raw_text | submitted_by | gold_action_type | gold_action_id | gold_params | gold_missing_params | note |
|---|---|---|---|---|---|---|---|---|
| req_001 | slack | 다음 주 월화 이틀 연차 상신 부탁드려요 | 인사팀 | approval | act_leave_approval | {"start_date":"다음 주 월","days":"이틀"} | [] | — |
| req_002 | naverworks | 3월 팀 회식비 법카 결재 좀 올려주세요 | 영업1팀 | approval | act_expense_approval | {"category":"회식비","period":"3월"} | ["amount"] | 결측: 금액(amount) 미기재. 지어내지 말고 결측 플래그 |
| req_003 | slack | 판교 창고에 프로젝터 재고 몇 대 남았는지 확인해줘 | 총무팀 | lookup | act_inventory_lookup | {"item":"프로젝터","location":"판교 창고"} | [] | — |
| req_004 | A커머스 계약서 만료일 언제인지 조회 부탁합니다 | 제휴팀 | lookup | act_contract_lookup | {"counterparty":"A커머스"} | [] | — | |
| req_005 | slack | 회의실 미팅룸 빔프로젝터 고장났어요. 수리 티켓 등록해주세요 | 개발팀 | register | act_issue_register | {"asset":"빔프로젝터","symptom":"고장","location":"회의실"} | [] | 표기흔들림: '미팅룸'='회의실' 별칭 정규화 필요 |
아래 JSON Schema 에 맞는 한국 엔터프라이즈 사내 업무 요청 로그 40건을 JSON 배열로 생성하세요. 채널 4종(slack/naverworks/email/form), 부서 분포 자연스럽게, 액션 타입 approval/lookup/register 를 고루 포함하세요.
{"type":"object","properties":{"request_id":{"type":"string"},"channel":{"enum":["slack","naverworks","email","form"]},"raw_text":{"type":"string"},"submitted_by":{"type":"string"},"gold_action_type":{"enum":["approval","lookup","register","route","reject"]},"gold_action_id":{"type":["string","null"]},"gold_params":{"type":"object"},"gold_missing_params":{"type":"array","items":{"type":"string"}},"note":{"type":["string","null"]}},"required":["request_id","channel","raw_text","gold_action_type","gold_action_id"]}
쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞으세요(쉬운 문제만 푸는 함정 회피): (1) 결측: required_params 중 일부(금액·기간 등)가 원문에 없는 요청 4~6건, gold_missing_params 에 그 필드명 표기, (2) 표기흔들림: 같은 액션의 별칭 원문('법카'/'법인카드', '월차'/'연차', '미팅룸'/'회의실') 3~4건, (3) 미매핑/orphan: action_registry 에 대응 액션이 없는 요청 2~3건(gold_action_id=null, gold_action_type='route'), (4) 잡담·인사 등 업무 요청이 아닌 것 2건(gold_action_type='reject', gold_action_id=null), (5) 경계값: 맥락이 극단적으로 부족한 한두 단어 요청('그거 처리해줘') 1~2건(route), (6) 멀티인텐트: 한 요청에 조회+결재가 섞인 것 2건(1차 액션만 gold, note 에 분해 필요 표기). JSON 배열만 출력하세요.{"type":"object","properties":{"request_id":{"type":"string"},"channel":{"enum":["slack","naverworks","email","form"]},"raw_text":{"type":"string"},"submitted_by":{"type":"string"},"gold_action_type":{"enum":["approval","lookup","register","route","reject"]},"gold_action_id":{"type":["string","null"]},"gold_params":{"type":"object"},"gold_missing_params":{"type":"array","items":{"type":"string"}},"note":{"type":["string","null"]}},"required":["request_id","channel","raw_text","gold_action_type","gold_action_id"]}에이전트가 트리거할 수 있는 사내 시스템 액션 카탈로그. 각 액션의 필수 파라미터(required_params)와 대상 시스템, 커넥터, 트리거 별칭을 담습니다. 요청의 gold_action_id 가 이 표에 조인되어야 하며, 조인 실패(orphan)면 지어내지 말고 미매핑으로 알려야 합니다. 결측 판정의 기준(required_params)이자 별칭 정규화의 사전입니다.
| action_id | name | action_type | target_system | required_params | aliases | connector |
|---|---|---|---|---|---|---|
| act_leave_approval | 연차/휴가 결재 상신 | approval | 네이버웍스결재 | ["start_date","days"] | ["연차","휴가","반차","월차"] | connector_naverworks |
| act_expense_approval | 지출 결재 상신 | approval | ERP | ["amount","category","period"] | ["법인카드","법카","지출","경비","회식비"] | connector_erp |
| act_purchase_approval | 구매 품의 | approval | ERP | ["item","qty","amount"] | ["구매","품의","발주"] | connector_erp |
| act_inventory_lookup | 재고 조회 | lookup | WMS | ["item","location"] | ["재고","창고","남은 수량"] | connector_wms |
| act_contract_lookup | 계약 조회 | lookup | 계약관리 | ["counterparty"] | ["계약","계약서","만료일"] | connector_contract |
아래 JSON Schema 에 맞는 한국 엔터프라이즈 사내 시스템 액션 카탈로그 20행을 JSON 배열로 생성하세요. action_type 은 approval/lookup/register 세 종을 고루, 대상 시스템(target_system)은 네이버웍스결재·ERP·WMS·Jira·CRM·계약관리 등으로 자연스럽게 분포시키세요.
{"type":"object","properties":{"action_id":{"type":"string"},"name":{"type":"string"},"action_type":{"enum":["approval","lookup","register"]},"target_system":{"type":"string"},"required_params":{"type":"array","items":{"type":"string"}},"aliases":{"type":"array","items":{"type":"string"}},"connector":{"type":"string"}},"required":["action_id","name","action_type","target_system","required_params"]}
조건: (1) 각 액션의 required_params 는 실제 결재·조회·등록에 필요한 2~3개로 현실적으로, (2) aliases 에 구어체 별칭('법카','월차','미팅룸' 같은)을 1~4개씩 넣어 표기흔들림 정규화가 가능하게, (3) connector 는 target_system 과 정합되는 이름으로. JSON 배열만 출력하세요.{"type":"object","properties":{"action_id":{"type":"string"},"name":{"type":"string"},"action_type":{"enum":["approval","lookup","register"]},"target_system":{"type":"string"},"required_params":{"type":"array","items":{"type":"string"}},"aliases":{"type":"array","items":{"type":"string"}},"connector":{"type":"string"}},"required":["action_id","name","action_type","target_system","required_params"]}| Kotlin | Spring Boot 액션 게이트웨이의 대표 언어. 결정된 action plan 을 사내 시스템 커넥터로 디스패치하는 오케스트레이터입니다 |
| Spring | 요청 인테이크 API + 액션 게이트웨이 (`POST /requests` 로 요청을 받아 큐 발행, 커넥터 라우팅, 감사 로그 적재) |
| Python | FastAPI 에이전트의 전처리, 인용 검증(evidence_span 이 원문 substring 인지 결정론 매칭), 분류 정확도 평가 스크립트 |
| FastAPI | AI Agent 추론 엔드포인트 (`POST /plan`. 자유 텍스트 요청 → action plan 반환) |
| PostgreSQL | work_requests·action_registry·audit_log 단일 진실원 + 결정·실행 감사 추적 |
| Redis | 요청 멱등(idempotency) 키 + 커넥터 호출 rate-limit |
| RabbitMQ | 액션 디스패치 작업 큐. 결정과 커넥터 실행을 분리하고 재시도·DLQ 로 실패를 격리합니다 |
| Apache Kafka | 감사 이벤트 스트림. 모든 결정과 커넥터 실행 결과를 append-only 로 남겨 재생 가능하게 합니다 |
| Docker | 에이전트·게이트웨이·커넥터를 각각 컨테이너로 분리 패키징 |
| Kubernetes | 세 서비스를 MSA 배포 단위로 오케스트레이션 |
| AWS(EKS) | 2주차부터 EKS 로 배포·운영합니다 (JD 명시 인프라) |
| TypeScript | 요청→액션 콘솔의 타입 안전 (프론트 전반) |
| Next.js | 콘솔 UI (인입 요청 큐·결정된 액션·원문 인용 하이라이트·감사 로그) |
| Zustand | 콘솔 요청 큐·필터·선택 상태 관리 |
| Zod | 프론트 런타임 action plan 스키마 검증 |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우고 싶으면 2주차(분류가 실제로 맞았는지 확인 + 재시도·감사 + EKS 배포)로 진행합니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우면 3주차(실 커넥터 어댑터 교체 + 레지스트리 확장 + 멱등·권한 가드)로 진행합니다.
자유 텍스트 업무 요청 하나에 대해 에이전트가 내놓는 실행 계획입니다. 1) 의도 한 줄, 2) 실행할 액션 타입·액션 ID(미매핑이면 null), 3) 원문에서 인용한 근거(evidence_span)와 함께 뽑은 파라미터, 4) 빠진 필수값(missing_params), 5) 라우팅 대상·확신도·판단 근거. 이 한 스키마가 다음 커넥터 호출의 입력이 됩니다.
{"type":"object","properties":{"request_id":{"type":"string"},"intent":{"type":"string"},"action_type":{"enum":["approval","lookup","register","route","reject"]},"action_id":{"type":["string","null"]},"params":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"},"evidence_span":{"type":"string"}},"required":["name","value","evidence_span"]}},"missing_params":{"type":"array","items":{"type":"string"}},"route_to":{"type":["string","null"]},"confidence":{"enum":["low","mid","high"]},"rationale":{"type":"string"}},"required":["request_id","action_type","action_id","params","missing_params","confidence"]}[
{
"input_ref": "req_002 · naverworks · '3월 팀 회식비 법카 결재 좀 올려주세요'",
"output": {
"request_id": "req_002",
"intent": "3월 팀 회식비를 법인카드 지출로 결재 상신",
"action_type": "approval",
"action_id": "act_expense_approval",
"params": [
{
"name": "category",
"value": "회식비",
"evidence_span": "회식비"
},
{
"name": "period",
"value": "3월",
"evidence_span": "3월"
}
],
"missing_params": [
"amount"
],
"route_to": null,
"confidence": "mid",
"rationale": "'법카'·'회식비'가 지출 결재(act_expense_approval) 별칭과 일치합니다. required_params 중 amount 가 원문에 없어 지어내지 않고 결측으로 표시했습니다. 금액 확인 후 상신이 필요합니다."
}
},
{
"input_ref": "req_003 · slack · '판교 창고에 프로젝터 재고 몇 대 남았는지 확인해줘'",
"output": {
"request_id": "req_003",
"intent": "판교 창고 프로젝터 재고 수량 조회",
"action_type": "lookup",
"action_id": "act_inventory_lookup",
"params": [
{
"name": "item",
"value": "프로젝터",
"evidence_span": "프로젝터"
},
{
"name": "location",
"value": "판교 창고",
"evidence_span": "판교 창고"
}
],
"missing_params": [],
"route_to": null,
"confidence": "high",
"rationale": "'재고'·'창고'가 act_inventory_lookup 별칭과 일치하고 item·location 이 원문에서 모두 추출됩니다. 필수값 결측이 없어 바로 조회 디스패치가 가능합니다."
}
},
{
"input_ref": "req_008 · slack · '고객사 상주 교통비 정산은 어느 팀에 문의해야 하나요?'",
"output": {
"request_id": "req_008",
"intent": "고객사 상주 교통비 정산 담당 문의",
"action_type": "route",
"action_id": null,
"params": [],
"missing_params": [],
"route_to": "총무팀",
"confidence": "mid",
"rationale": "교통비 정산에 대응하는 레지스트리 액션이 없어(미매핑) 액션을 지어내지 않고 총무팀으로 라우팅합니다."
}
},
{
"input_ref": "req_009 · slack · '다들 점심 뭐 드셨어요 ㅎㅎ'",
"output": {
"request_id": "req_009",
"intent": "잡담(업무 요청 아님)",
"action_type": "reject",
"action_id": null,
"params": [],
"missing_params": [],
"route_to": null,
"confidence": "high",
"rationale": "실행 가능한 업무 요청이 아니어서 어떤 커넥터로도 디스패치하지 않습니다."
}
}
][자동 검증: 규칙·결정론으로 채점] 1. JSON Schema parse: action plan 이 위 스키마 형식을 통과하는지(필드 누락·타입 깨짐 차단). 2. 인용 청크 일치: 각 params[].evidence_span 이 요청 raw_text 의 실제 substring 인지(공백 정규화 후). 지어낸 근거를 차단합니다. 3. 액션 타입 정확도: action_type 이 gold_action_type 과 일치한 비율. 4. 액션 ID 해소 정확도: action_id 가 gold_action_id 와 일치한 비율(미매핑은 null 로 표기 포함). action_registry 조인 실패 시 null 이어야 정답. 5. 결측 재현율/정밀도: missing_params 가 action_registry.required_params 대비 실제 빠진 값과 일치하는지. [수동 검증: 사람 표본] 1. Claude Haiku 저비용 2차 분류와 1차 결정이 불일치한 건, 그리고 confidence=low 건을 사람 큐로 보내 검토합니다. 2. route/reject 경계 케이스(맥락 부족·멀티인텐트)는 전수 대비 5% 를 사람이 spot check 합니다. (κ 등 통계 지표는 이 포트폴리오 범위에서 쓰지 않고, 분류 일치율과 인용 존재율 같은 직접 지표로만 채점합니다.)