김영준 AI Engineer's Portfolio

00. About

모델이 아니라, 모델이 딛고 설 바닥을 만듭니다.

AI가 일하려면 먼저 바탕이 있어야 합니다. 학습할 데이터, 안전하게 행동할 수 있는 도구, 읽을 수 없던 것을 읽는 경로. 저는 그 바탕을 만듭니다. 만드는 동안 늘 같은 질문을 합니다 — 편한 길 대신 이 길을 택한 이유를 댈 수 있는가. 아래 세 프로젝트는 그 질문에 답한 기록입니다.

01. AI 학습용 부동산 데이터 파이프라인 구축 및 매출 전환

㈜코그콤 · 고객사 ㈜젠스타메이트

AI가 학습할 데이터를 공급하는 바탕. 전면 Selenium이라는 편한 길 대신 페이지 유형을 나눠 비용을 통제했습니다.

  • Python
  • FastAPI
  • Selenium
  • httpx
  • BeautifulSoup
  • REST API
고객사 요청 FastAPI 수신 DOM · 네트워크 · 렌더링 분석 정적 · 동적 혼재 페이지 판별 STATIC httpx + BeautifulSoup 빠르고 가볍다 DYNAMIC Selenium 느리고 비싸다 정제 · 표준화 → 응답 반환
요청을 받으면 그 자리에서 페이지 유형을 갈라 수집하고 정제해 응답합니다. 비용이 큰 브라우저 경로는 꼭 필요한 페이지에만 씁니다.

Situation

  • 젠스타메이트 의뢰로 성북구청 건축 인허가 데이터를 AI 학습용으로 수집·공급하는 파이프라인을 맡았습니다.
  • 대상 사이트는 정적 페이지와 동적 페이지가 뒤섞여 있었고 수집한 데이터를 정제해 서비스에 공급하는 체계는 없었습니다.

Task

  • 혼재 구조에서 누락 없이 전부 수집할 것.
  • 수작업 없이 호출 시점의 최신 데이터를 AI가 바로 쓸 형태로 반환할 것.

Action

  • 페이지 유형을 먼저 갈랐습니다. DOM 구조·네트워크 요청·렌더링 방식을 직접 분석해 서버에서 완성돼 오는 페이지와 브라우저가 그리는 페이지를 구분했습니다.
  • 전면 브라우저 자동화를 쓰지 않았습니다. 전부 Selenium으로 여는 편이 구현은 쉽지만 브라우저를 띄우는 비용이 큽니다. 정적 페이지는 httpx로 받아 BeautifulSoup으로 파싱하고 동적 페이지만 Selenium으로 보냈습니다.
  • 배치가 아니라 요청 기반 엔진으로 만들었습니다. FastAPI가 요청을 받으면 그 자리에서 크롤러를 돌리고 표준 포맷으로 정규화한 결과를 바로 응답합니다. 미리 쌓아두고 꺼내 쓰지 않고 호출 시점의 최신 데이터를 돌려줍니다.

Result

  • 매출 전환

    구축한 파이프라인이 젠스타메이트 서비스의 핵심 인프라로 투입되어 실제 매출로 이어졌습니다.

  • 내부 솔루션 연동

    업무 자동화 솔루션 리컬럼과 연결되는 API 서버를 구축해 수집 데이터를 서비스에 직접 공급했습니다.

02. AI 에이전트 기반 이커머스 물품 구매 자동화 시스템 개발

개인 프로젝트 · FronyBrowser (공개) · FronyShopping (비공개)

에이전트가 안전하게 행동할 수 있는 바탕. 임의 JS 실행 같은 편한 기능을 넣지 않은 대가로 개인정보가 새는 길을 닫았습니다.

  • TypeScript
  • MCP
  • Playwright/CDP
  • HMAC
  • SQLite
  • DPAPI
AGENT Claude · GPT Desktop 두 서버를 잇는 유일한 지점 판정 요청 → 승인 토큰 키 이름 + 승인 토큰 JUDGEMENT FronyShopping 가격 · 수량 · 한도 판정 구매 이력 · 플랫폼 playbook EXECUTION FronyBrowser 암호화 vault · 브라우저 제어 개인정보는 이 안에서만 존재 두 서버는 서로 호출하지 않습니다 — 에이전트를 거쳐 식별자만 오갑니다
판정은 FronyShopping이, 실행은 FronyBrowser가 맡고 에이전트는 둘을 잇기만 합니다. 인젝션당한 에이전트도 판정을 뒤집거나 개인정보를 읽을 수 없습니다.
OPENED 거래 시작 IDENTIFIED 품목 확인 CHECKED 서버가 가격 판정 PAYING 되돌릴 수 없는 지점 PURCHASED 구매 기록 FronyShopping 콘솔의 거래 상세 화면 — 상태 전이 타임라인, 품목별 허용 가격과 실제 가격, 호출 로그
왼쪽이 트랜잭션이 밟는 다섯 단계, 오른쪽은 실제로 완주한 거래 기록입니다. 결제 승인 토큰은 PAYING 직전에 한 번만 발급되고, 이 상태는 만료되지 않습니다 — 돈이 이미 나갔을 수 있기 때문입니다.

Situation

  • "늘 사던 그 물 주문해줘"를 맡기려면 로그인 비밀번호, 카드번호, 결제 PIN이 필요합니다. 값을 건네는 순간 LLM 컨텍스트·응답·로그에 전부 남습니다.
  • 더 근본적으로, 에이전트는 웹페이지에 인젝션당할 수 있습니다. 화면의 가격과 수량을 믿고 결제하면 조작된 페이지 하나로 잘못된 주문이 나갑니다.

Task

  • 에이전트가 개인정보 값을 단 한 번도 보지 않으면서 로그인부터 결제 완료까지 완주하게 만들 것.
  • 에이전트가 오염되더라도 피해가 한도 안에 묶이도록 판단의 권한을 에이전트 밖에 둘 것.

Action

  • 값은 한 지점에서만 실체가 됩니다. 에이전트는 fill(ref, "{{vault:card.number}}")처럼 키 이름만 보냅니다. 치환하는 함수는 코드 전체에서 하나뿐입니다. 그 반환값은 호출자에게 돌아가지 않고 CDP 키스트로크로 페이지에만 들어갑니다. 원본은 Windows DPAPI로 암호화한 단일 블롭에 있고 메모리 해제는 TTL로 자동입니다. 응답에 남는 건 { filledFrom, len } — 키 이름과 길이뿐입니다.
  • 나가는 길은 한 출구뿐입니다. HTTP와 MCP 두 입구 중 한쪽에만 검사를 두면 다른 쪽이 우회합니다. 그래서 스크러버를 통과해야만 만들 수 있는 브랜드 타입을 정의해 검사를 빠뜨린 핸들러는 컴파일되지 않게 했습니다. 카드번호는 하이픈·마스킹·뒤 4자리 같은 표기 변형을 함께 만들어 지웁니다. 짧은 조각이 먼저 걸려 전체를 놓치지 않도록 긴 패턴부터 치환합니다.
  • 막는 대신 처음부터 주지 않았습니다. 페이지 트리를 만들 때 input의 value는 어떤 경로로도 읽지 않고 checked 같은 불리언 상태만 내보냅니다. 스크린샷은 스크러버가 청소할 수 없으므로 결제 iframe 안쪽까지 내려가 입력 필드를 이미지 단계에서 마스킹합니다. 임의 JS 실행과 HTML 덤프도 넣지 않았습니다 — 스크러버가 평문 일치로 동작하니 btoa(value) 한 줄이면 통째로 무력화되기 때문입니다. 편의 기능 대신 핸들러 폴더 자체를 기능 화이트리스트로 삼았습니다.
  • 판단은 에이전트 밖에 뒀습니다. 가격·수량·한도 판정은 FronyShopping이 하고 결과를 HMAC-SHA256 서명 토큰으로 넘깁니다. 서명·300초 만료·세션 바인딩·1회용 네 겹이라 인젝션당한 에이전트도 위조하거나 재사용할 수 없습니다. 토큰은 검증 시점이 아니라 행위가 성공한 시점에 소진됩니다 — 검증에서 태우면 실패한 입력 하나가 토큰을 날려버립니다.

Result

  • 실 결제 검증 및 직접 활용

    컬리와 교보문고에서 로그인부터 결제·주문내역 확인까지 실제 결제로 완주했고 보안 키패드를 통한 PIN 입력 경로까지 실측했습니다. 지금도 직접 쓰고 있습니다.

  • 잦은 재구매 상품 구매 자동화

    반복 구매를 기억·지도·판정 세 축으로 묶었습니다. 구매 이력과 플랫폼별 조작 노하우를 쌓아두고 품목마다 허용 가격 범위·재구매 주기·수량 상한을 걸어 품목만 말하면 결제까지 이어집니다.

03. 한국투자증권 이벤트 수집 모듈 개발 및 MCP 서버 구축

현업 실무자와 함께 진행한 협업 프로젝트 · project-kisevent

읽을 수 없던 것을 에이전트가 읽는 바탕. 정교한 전사 설계 대신, 재어 보고 크롭 한 장으로 줄였습니다.

  • Python
  • MCP
  • VLM
  • FastAPI
  • SQLite
  • Pillow
DATA ANALYSIS · 설계를 정하기 전 배너 34건 수집 세로 7,175 ~ 29,482px · 합계 67MB 상단 4,000px 전수 판독 진행 중 20건 · 타일 57장 정보 4항목이 한 구간에 몰려 있음 y 1,400 ~ 3,450px 전문 전사 폐기 → 고정 크롭 1장 40~50만 토큰 → 호출 1회 USER SCENARIO · 질문 한 번 "지금 영업점 대상 이벤트 뭐 있어?" 공고 목록 · 배너 이미지 수집 sync_now · 변경분만 다시 받음 상단 크롭을 MCP 이미지로 전달 별도 VLM API 없이 에이전트 비전 재사용 에이전트가 판독 → 구조화 저장 스키마 검증 실패 시 사유를 돌려주고 재시도 대상별로 걸러 답변 목록 탭이 아니라 배너 판독 결과 기준
왼쪽은 설계를 확정하기 전에 배너를 직접 세어 크롭 범위를 정한 과정, 오른쪽은 그렇게 정해진 구조에서 질문 한 번이 답으로 이어지는 흐름입니다.

Situation

  • 현업 실무자가 직접 겪던 불편에서 출발했습니다. "지금 영업점 고객 대상 이벤트가 뭐가 있지?"에 답하려면 공고를 하나씩 열어 눈으로 읽어야 했습니다. 어떤 정보가 실제로 필요한지, 대상 구분을 어떻게 나눠야 맞는지를 실무자와 함께 정의하며 진행했습니다.
  • 원인은 단순했습니다. 공고 내용이 전부 배너 이미지 안에 있고 HTML에는 텍스트가 없습니다. 검색도 필터도 걸리지 않습니다.
  • 한 번 정리해두고 끝나는 일도 아니었습니다. 창구에 고객이 올 때마다 그 고객의 거래 채널·보유 계좌에 맞는 이벤트를 골라내야 하니 대상 구분의 정확도가 곧 이 시스템의 쓸모였습니다. 그런데 목록 탭(영업점 / 뱅키스)으로 가르는 방법은 틀렸습니다 — 연금 이벤트가 양쪽에 동시에 실려 오분류됐습니다.

Task

  • 이미지 안의 기간·대상·상품·실적 조건을 구조화해, 에이전트가 자연어 질문에 바로 답할 수 있게 만들 것.
  • 조회가 상시로 일어나는 업무이므로 운영 비용이 쌓이지 않을 것. 사내 개인 PC에서 도는 환경이라 외부 API 키를 두지 않는 편이 안전하기도 했습니다.

Action

  • 처음 설계를 버렸습니다. 배너를 여백 기준으로 11~20장 타일로 쪼개 전부 읽는 방식이었고 34건에 40~50만 토큰이 들 것으로 추산됐습니다. 버리기 전에 근거를 만들었습니다 — 공고 20건의 상단 4,000px를 전수 판독해 필요한 네 항목이 모두 한 구간에 모여 있음을 확인했습니다. Pillow로 고정 구간만 잘라 JPEG 한 장으로 넘기는 방식으로 바꾼 뒤 전사 경로의 코드와 테이블을 전부 삭제했습니다.
  • 별도 VLM API를 붙이지 않았습니다. MCP의 ImageContent로 크롭을 그대로 실어 이미 구독 중인 Claude · GPT Desktop의 비전을 재사용합니다. 공고는 매달 갱신되고 창구에서 상시 조회되므로 호출당 과금되는 API였다면 비용이 계속 쌓입니다. 구독 안에서 해결하면 추가 비용이 0이고 사내 PC에 API 키를 두지 않아도 됩니다.
  • 수집은 멱등하게, 실행은 하나만. 목록과 상세는 httpx + BeautifulSoup으로 파싱합니다. 배너는 URL이나 sha256이 바뀔 때만 다시 받아 이전 행을 superseded로 넘깁니다. 크롤은 스레드 락 하나와 60초 디바운스로 묶어 중복 실행을 막고 요약 작업은 30분 claim을 걸어 동시 호출이 같은 배너를 집지 않게 했습니다.
  • 워크플로우를 문서가 아니라 프로토콜에 넣었습니다. "수집 1회 → 조회 → 미요약이면 먼저 요약 → 서브에이전트 병렬" 순서를 도구 설명문과 응답의 다음 지시에 박아 넣어 에이전트가 읽는 것만으로 순서를 따르게 했습니다. 저장은 pydantic으로 검증합니다. 실패하면 어느 필드가 왜 틀렸는지 돌려줘 에이전트가 고쳐 다시 보냅니다. 대상 판정도 탭을 버리고 배너 판독 결과를 정본으로 삼았습니다.

Result

  • 현업 문제 해결

    공고를 하나씩 열어 눈으로 읽던 일이 질문 한 번으로 끝납니다. 진행 중 33건과 종료 20건, 총 53건 전부에서 크롭 범위를 벗어나 정보를 놓친 사례가 없었습니다.

  • AI Agent 행동 최적화

    도구 설명문과 응답의 다음 지시로 호출 순서를 잡아주고 검증 실패는 사유를 돌려줘 스스로 고치게 했습니다. 판정 기준도 탭에서 배너로 옮겨 오분류 11건을 바로잡았습니다.