About
Project2026

KT Enterprise B2B Sales AI Agent 구축

㈜휴트리온 · KT ds / Client: KT Enterprise

2026.07 — 진행중 (2026.09 베타 목표)

Overview

KT Enterprise B2B 영업 부문의 생산성 개선을 위해 협업 툴(Teams·Outlook)과 내부 CRM을 연동하는 백엔드 AI 에이전트 시스템을 구축하는 과제입니다. 기존에는 단순 질의응답형 챗봇과 수동 응대(월 2천 건)에 의존했고, 영업 정보가 개인 PC에 흩어져 전사 자산으로 축적되지 못하는 한계가 있었습니다. 저는 이 가운데 Account Plan 에이전트의 전 계층을 단독으로 설계·구현하고 있습니다. 영업 대상 기업의 전자공시(금융감독원 DART), 조달 정보(조달청 나라장터·조달데이터허브), 기관 정보(행정안전부 행정표준코드·기획재정부 ALIO), 뉴스 등 7개 기관 원천을 자동으로 수집·분석해 고객사별 Account Plan 초안을 생성하고 이를 API로 제공합니다. 2026년 9월 베타를 목표로 진행 중이며, 현재 1만 곳 이상의 고객사를 적재하고 초안 생성 파이프라인을 운영하고 있습니다.

Key Metrics

1만 곳 이상
적재 규모
사기업 · 공공기관 통합 스키마
약 107만 행
누적 데이터
600MB
53,262 → 15,138자
입력 토큰
회사당 -72%
회사당 평균 79초
생성 시간
분석 4종 병렬 (106곳 실측)
출처 4,501개 · 추정 48건
근거 표기
100곳 실측 기준
312회 → 152회
DART 호출
정기보고서 미제출 법인 사전 판별 (52곳 기준)
불필요 호출 80% 제거
뉴스 수집
재검색 상한 조정
1인 전담
담당 범위
Account Plan 에이전트 전 계층 단독 설계·구현

Highlights

Key Decisions

근거 블록별 소유자 지정으로 입력 72% 감축

문제초기 구조에서는 분석 에이전트 3종이 같은 근거를 통째로 전달받았습니다. 사업보고서 9,000자가 3번 들어가는 셈이어서 회사당 입력이 53,262자에 달했습니다.

결정근거를 블록으로 분할하고 블록마다 전달받을 에이전트를 지정하는 소유자 매핑 구조로 변경했습니다. 사업보고서는 사업 구조를 Value Chain에, 시장여건을 산업 환경에 나누어 전달하고, 입찰공고문은 공기업에서 가장 큰 블록이므로 Value Chain·고객사 현황에만 넘깁니다. 업종 밸류체인과 주요 뉴스처럼 모든 에이전트에 필요한 것만 공용으로 남겼습니다.

결과개편 이전 53,262자(근거 17,754자 × 3) → 1차 개편 29,002자(블록별 분배, -46%) → 2차 개편 15,138자(사업보고서·공고문 추가 분리, -72% 누적).

배분이 어긋나면 품질이 먼저 무너집니다

문제이 배분은 비용만의 문제가 아니었습니다. 공기업의 산업환경 에이전트에 전체 근거의 11%만 전달되던 시기가 있었는데, 그때 산출물에는 근거 없는 일반론이 4건 포함되었습니다.

결정배분 비율 자체를 품질 지표로 보고 조정했습니다.

결과74%가 전달되자 해당 문장은 사라졌습니다. 입력을 줄이는 것과 필요한 입력을 주는 것은 별개의 문제라는 점을 수치로 확인한 사례입니다.

프롬프트 2계층 분리 — 중복 제거가 드러낸 지시 충돌

문제네 에이전트가 각자 프롬프트를 보유해, 공통으로 지키는 규칙(사실 취급·문장·공공기관 처리·출력 형식)이 페이지마다 반복되고 있었습니다. 페이지마다 같은 내용을 반복하면 회사당 네 번 전송하는 셈이고, 대상 고객사가 많을수록 그 중복이 그대로 비용이 됩니다.

결정공통 규칙을 공통 시스템 프롬프트에 한 번만 두고, 각 페이지 지침에는 해당 페이지에서만 다른 내용을 적었습니다.

결과회사당 입력이 5,558자에서 4,499자로 19.1% 감소했습니다. 분리 과정에서 상충하는 지시도 발견했습니다. 조립부 말미에 "근거가 부족하면 항목 수를 줄여라"가 있는데, 각 페이지 지침은 정반대로 "비우지 말고 채워라"라고 지시하고 있었습니다. 두 지시가 충돌하면 모델은 짧은 쪽을 선택합니다. 화면에 칸이 비어 나오던 원인이었습니다.

조회 경로에서 LLM 제거

문제화면이 60초마다 자동 갱신되므로, 조회할 때마다 생성하면 브라우저를 하루 8시간 열어두는 것만으로 480회가 호출됩니다.

결정생성(POST)과 조회(GET)를 엄격히 분리하여 조회 경로에는 LLM을 두지 않고 저장된 결과만 읽도록 했습니다. 렌더 함수에서 생성을 예약하던 코드 2건도 제거했습니다. 페이지 로드·회사 전환·자동 갱신 때마다 호출이 발생해, 서버를 띄우기만 해도 비용이 나가던 경로였습니다.

결과소비자가 조회를 반복해도 과금이 발생하지 않는 구조를 확보했습니다.

추론 모델의 빈 응답 — 재시도 비용 2배

문제사내 게이트웨이의 추론 모델은 출력 토큰 한도 범위 안에서 사고까지 수행합니다. 한도가 부족하면 사고만 하다 종료되어 응답 본문이 빈 채로 반환되는데, 예외가 아니라 빈 문자열이라 호출부는 조용히 폴백으로 떨어집니다. 오버뷰 요약이 며칠간 폴백 문구로 표시된 원인이었습니다.

결정에이전트마다 한도를 2단계로 두고 1차가 비면 상향해 재호출하는 구조로 변경했습니다. 다만 재시도는 비용을 두 배로 만들기 때문에, 1차 한도를 '비지 않을 만큼만' 설정하는 것이 핵심이었습니다.

결과실측 출력량을 기준으로 sector 5,000 · sectorPolicy 5,000 · valueChain 5,600 · customerStatus 4,000으로 각각 다르게 설정했습니다.

DART 일일 한도 분할 적재

문제회사 한 곳을 채우는 데 DART 호출이 6회 필요하므로, 전체 대상 기준으로는 일일 한도(20,000회)를 크게 초과합니다.

결정하루치만 수집하고 커서를 기록해 다음 날 이어받는 방식으로 분할했습니다. 한도는 두 가지 방식으로 감지합니다. 자체 집계가 예산에 도달하면 선제적으로 중단하고, DART가 한도 초과 상태코드를 반환하면 즉시 중단합니다. 재시도나 키 공유로 양측 집계가 어긋날 수 있기 때문입니다. 예산은 18,000회로 설정해 2,000회를 사용자 요청 몫으로 확보했습니다. 배치가 한도를 모두 소진하면 사용자가 버튼을 눌러도 최신 자료를 받지 못하기 때문입니다.

결과약 4일이면 전체를 한 바퀴 순회합니다. 더해서 정기보고서를 제출하지 않는 법인은 6회 모두 '자료 없음'으로 반환되는데, 비상장 115곳을 확인한 결과 94곳이 이에 해당했습니다. 먼저 1회만 호출해 정기보고서 이력을 확인하고 없으면 나머지 5회를 건너뛰도록 하여, 52곳 기준 호출이 312회에서 152회로 감소했습니다.

뉴스 재검색 상한 조정 — 더 찾을수록 나빠지던 구조

문제관련 기사가 기준(10건)에 미달하면 예비 검색어를 최대 3개까지 추가로 사용하는 구조였습니다. 코스피 50곳 실측에서 재검색을 끝까지 수행한 그룹의 성적이 가장 낮았습니다. 검색 1회 8곳은 받은 기사 93건에 관련 11.8건, 검색 2회 15곳은 189건에 관련 13.6건이었는데, 검색 5회 17곳은 350건을 받고도 관련이 8.8건으로 가장 적었습니다.

결정원인은 검색어가 좁아서가 아니라 해당 기업의 기사 자체가 적다는 데 있었습니다. 4배를 수집하고도 관련 기사가 더 적다면 재검색 자체가 답이 아니라고 판단해 상한을 1로 조정했습니다.

결과17곳 기준 86회 중 69회의 불필요한 호출을 제거했습니다.

'추정' 문장을 근거 있는 문장으로

문제초기 산출물에는 근거 없이 추론한 문장이 3곳 기준 약 150건 있었습니다.

결정스키마에 출처(source) 필드를 추가하고, 공통 규칙을 "항목 수를 채우려고 지어내지 말 것 — 확인되는 것만으로 적게 쓰는 편이 낫다"로 변경했습니다.

결과100곳 실측에서 추정 48건 · 출처 4,501개로 안정화되었습니다. 이 과정에서 제가 설정한 우회로도 하나 발견했습니다. 근거가 없을 때 '업종 표준'을 출처로 인정하도록 했더니, 모델이 단계명을 되풀이하는 문장으로 21개 항목을 모두 채웠습니다("조달 대상은 웨이퍼·PCB로 구분되어 있다"). 추정 표시만 사라지고 내용은 오히려 나빠진 셈입니다. '업종 표준'을 출처에서 제외하고 "회사 이름을 지워도 성립하는 문장이면 다시 작성할 것"이라는 자가 검사를 추가하여 751항목 중 5건까지 줄였습니다.

Challenges & Solutions

문제조달 계약 조회가 일부 기관에서 조용히 0건을 반환했습니다. 원인은 회사 마스터의 기관코드 컬럼에 담긴 값이 행정안전부 행정표준코드인데, 계약 원장의 수요기관코드는 조달청 자체 체계여서 같은 기관에 다른 값이 붙는다는 데 있었습니다. 한국전력공사의 경우 행정표준코드는 B410002, 계약 원장 수요기관코드는 Z015182입니다. 예외도 경고도 없이 근거 블록만 빠지므로 초안은 정상으로 보입니다. 실측 150곳 기준 공기업 50곳 중 12곳이 여기 해당했습니다.

해결코드 → 기관명 → 본청 접두어 3단계 조회로 변경해 해소했으며, 본청 코드로는 0건이고 하부기관에만 계약이 있는 기관(근로복지공단·국가철도공단 등)도 접두어 단계에서 매칭됩니다. 기관코드 컬럼을 계약 원장 값으로 덮어쓰지 않은 이유도 함께 기록했습니다. 같은 컬럼을 나라장터 입찰공고 매칭이 사용하고 있어, 변경하면 계약은 살아나고 공고가 죽기 때문입니다.

문제보험사 3곳의 뉴스 근거가 0건이었습니다. DB 등록명은 DART 등기명(「삼성생명보험」)인데 기사 제목에는 「삼성생명」으로만 표기되어, 관련성 판정이 전부 실패하고 있었습니다. 「삼성생명, 보유자산만 316조…명실상부 국가대표 보험사」는 '삼성생명보험' 기준으로는 무관 처리되고 '삼성생명' 기준으로만 관련으로 잡힙니다.

해결접미사를 규칙으로 제거하려 했으나 삼성화재해상보험이 삼성이 되어 계열사 기사가 전부 유입되는 부작용을 확인했습니다. 규칙은 실제 사용되는 표기가 남는 경우로 한정하고, 금융·보험은 별칭을 명시하는 방식으로 분리했습니다. 사명 변경(LIG넥스원 → LIG D&A)처럼 규칙으로 처리할 수 없는 사례도 함께 반영했습니다. 금융 8곳 기준 관련 기사가 22건에서 87건으로 증가했습니다.

문제지주회사는 법인 등기가 금융업(KSIC 64)이어서, 업종코드로 Value Chain 단계표를 조회하면 철강·조선·전자 회사가 「수신 자금 → 여신 심사 → 뱅킹 시스템」을 전달받았습니다.

해결사업보고서로 실사업을 확인해 코스피 7곳·공공기관 7곳의 코드를 교정했고, 업종표와 사업보고서가 명백히 다를 때는 사업보고서를 기준으로 단계를 재구성하도록 프롬프트에 예외를 두었습니다. 같은 맥락에서 사업보고서 조회가 회사명 기준이어서 자회사 보고서가 유입되던 문제도 확인해 법인 고유번호 우선 조회로 변경했습니다(㈜LG에 LG헬로비전, ㈜두산에 두산테스나 보고서가 포함되어 있었습니다).

문제기획 명세대로 구현했으나 실제 데이터에서 동작하지 않는 항목들이 있었습니다. 뉴스 중복 제거 임계값을 명세(유사도 95%)대로 적용한 결과 실제 중복이 전혀 검출되지 않았고, 최신순 정렬에서는 대상 기업이 인용만 된 기사가 상위를 점유했습니다(30건 중 유효 0건).

해결동일 사건 기사군 0.20~0.30 / 무관 기사군 0.04 실측을 근거로 임계값을 재설정하고, 수집을 정확도순으로 전환했습니다. 사업자번호 확보를 위해 상용 API 도입을 검토했으나, 기존 공공 데이터 파일에 해당 정보가 이미 포함되어 있어 외부 유료 의존성 없이 대체했습니다.

Architecture

Stack

Python · FastAPI · uvicornLangGraphThreadPoolExecutorPostgreSQL (Azure) · SQLite(WAL)APSchedulerOpenDART · 나라장터(G2B) OpenAPI조달데이터허브 · 행정표준코드 · ALIO네이버 검색 APIHWPX · PDF · DOCX · HWP(OLE) 파싱Pydantic