본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 02. 23:14

자연어(및 스크린샷)를 AWS 아키텍처 다이어그램으로 변환하는 Diagram AI 구축기

요약

자연어와 스크린샷을 AWS 아키텍처 다이어그램으로 변환하는 Diagram AI 구축 과정을 다룹니다. Amazon Bedrock과 AWS의 diagram-as-code를 활용하며, Go 라이브러리 임포트 제한 문제를 Lambda와 Python 핸들러로 해결한 기술적 시행착오를 공유합니다.

핵심 포인트

  • Amazon Bedrock(Claude Haiku)을 통한 언어 이해 구현
  • Go internal 패키지 제약으로 인한 라이브러리 호출 방식의 한계
  • AWS Lambda와 Python 셸 핸들러를 이용한 바이너리 실행 전략
  • Vercel과 Lambda를 분리하여 프론트엔드와 렌더링 역할 분담

아키텍처 다이어그램을 손으로 그리는 것은 지루한 작업입니다. 상자를 끌어다 놓고, 아이콘을 정렬하고, 화살표를 수정해야 하며, 디자인이 변경될 때마다 이 모든 과정을 다시 해야 합니다. 저는 단순히 평범한 영어로 아키텍처를 _설명_하거나 — 또는 기존 다이어그램을 붙여넣기만 하면 — 깔끔한 AWS 스타일의 다이어그램을 얻을 수 있는 무언가를 원했습니다.

이 글은 Diagram AI를 구축한 이야기입니다. Diagram AI는 자연어와 이미지를 아키텍처 다이어그램으로 변환하는 브라우저 앱입니다. 이 앱은 Vercel에서 실행되며, 언어 이해에는 Amazon Bedrock (Claude Haiku 4.5)을 사용하고, 실제 다이어그램 렌더링은 AWS의 오픈 소스 diagram-as-code 도구를 사용합니다. 순조롭지 못했던 부분에 대해서도 솔직하게 이야기할 것입니다. 왜냐하면 그곳에서 진정한 교훈을 얻었기 때문입니다.

핵심 아이디어

파이프라인은 설명하기에 간단합니다:

[사용자 입력: 텍스트 또는 다이어그램 스크린샷]
        │
        ▼
...

여기서 영리한 부분은 중간 계층입니다. LLM에게

제 초기 계획은 서류상으로는 우아했습니다. diagram-as-code가 Go 언어로 작성되어 있으니, 이를 Vercel Go Serverless Function으로 컴파일하여 라이브러리처럼 호출하면 된다고 생각했습니다. 깔끔하고, 모든 것이 하나로 통합되며, 추가적인 인프라가 필요 없는 방식이었죠.

하지만 소스 코드를 읽어보니 상황이 달랐습니다. 거의 모든 핵심 로직이 Go의 internal/ 디렉토리 아래에 위치해 있었습니다 — internal/ctl, internal/types, internal/definition 등등 말이죠. Go의 언어 규칙상 모듈 외부에서 internal/ 패키지를 임포트(import)하는 것은 금지되어 있습니다. 즉, 라이브러리 임포트 방식은 애초에 불가능했습니다.

그래서 전략을 수정했습니다. awsdacAWS Lambda 내부에서 CLI로 실행하고, Function URL을 통해 노출시킨 뒤, Vercel 앱이 HTTPS를 통해 이를 호출하도록 만든 것입니다. Vercel은 프론트엔드와 LLM을 담당하고, Lambda는 렌더링(rendering)을 담당합니다.

두 번째 벽: 머신에 Docker도, Go도 없음

Lambda를 컨테이너 이미지(container image)로 패키징할 계획이었습니다. 하지만 빌드 머신에는 Docker도, Go도 설치되어 있지 않았습니다. 그 문제와 싸우는 대신, 저는 Python 3.12 런타임 기반의 zip 기반 Lambda로 전환했습니다.

  • GitHub releases에서 공식적으로 미리 빌드된 awsdac Linux 바이너리를 다운로드합니다.
  • 바이너리를 셸(shell)로 실행하는 아주 작은 Python 핸들러(handler)를 작성합니다.
  • 이 두 가지를 함께 zip으로 묶어 업로드합니다. Docker도, Go 빌드 단계도 필요 없습니다.

핸들러는 매우 간단합니다. YAML을 받아서 /tmp에 쓰고, awsdac를 실행한 뒤, PNG를 base64 형식으로 반환합니다:

proc = subprocess.run(
    [AWSDAC_PATH, "--allow-untrusted-definitions", in_path, "-o", out_path],
    capture_output=True, text=True, env={**os.environ, "HOME": "/tmp"}, timeout=25,
...

세 번째 벽: 어떤 정책에도 없던 403 에러

함수를 배포한 후, 직접 aws lambda invoke를 실행했을 때는 완벽하게 작동했습니다 — 깨끗한 PNG가 반환되었습니다. 하지만 HTTPS를 통해 Function URL을 호출하면 403 Forbidden / AccessDeniedException이 반환되었습니다.

인증 유형(auth type)은 NONE이었습니다. 리소스 정책(resource policy)은 *에게 lambda:InvokeFunctionUrl 권한을 부여하고 있었습니다. 모든 것이 올바르게 보였습니다. 조직 수준의 SCP(Service Control Policies)나 RCP(Resource Control Policies)도 확인해 보았지만, 아무것도 없었습니다.

답은 문서 노트에 있었습니다: 2025년 10월부터는 공개 함수 URL(public function URLs)이 두 가지 권한 — lambda:InvokeFunctionUrllambda:InvokeFunction 둘 다를 요구합니다. 저는 첫 번째 것만 가지고 있었습니다. 두 번째 구문을 추가하자마자 즉시 해결되었습니다:

aws lambda add-permission --function-name diagram-ai-render \
  --statement-id FunctionURLAllowInvoke \
  --action lambda:InvokeFunction --principal "*" \
...

교훈: 403 에러가 말이 안 될 때는, 단순히 자신의 설정만 확인할 것이 아니라 플랫폼의 규칙이 최근에 변경되었는지 확인해 보세요.

LLM 출력을 좋은 다이어그램으로 만들기

유효한 YAML을 얻는 것은 쉬운 반이었습니다. 보기 좋은 다이어그램을 만드는 것은 여러 차례 프롬프트 엔지니어링(prompt engineering)을 거쳐야 했고, 매번 보기 흉한 결과물에 의해 동기 부여되었습니다:

  1. 컨테이너를 관통하는 화살표. LLM은 User에서 VPC 내부 깊숙이 있는 ALB로 곧장 향하는 화살표를 그려

diagram-as-code는 AWS 우선(AWS-first) 방식이지만, 정의 파일(definition-file) 메커니즘을 통해 임의의 아이콘을 추가할 수 있습니다. 저는 "GitHub → Vercel → AWS Lambda"와 같이 하이브리드 다이어그램(hybrid diagrams)이 올바르게 보이기를 원했습니다.

저는 작은 파이프라인을 구축했습니다: simple-icons에서 SVG를 가져와 각 브랜드의 색상으로 색조를 입히고, sharp를 사용하여 128×128 PNG로 변환합니다. 이를 통해 19개의 외부 서비스 아이콘(Vercel, Netlify, Cloudflare, GitHub, Supabase, Auth0, OpenAI, Anthropic, Stripe 등)을 생성했으며, 이를 icons.zip 파일과 External::Vercel과 같은 타입을 아이콘에 매핑하는 external-icons.yaml 정의 파일로 묶었습니다.

한 가지 미묘한 점은, awsdac--allow-untrusted-definitions 플래그를 전달하지 않으면 공식 리포지토리(official repo) 외부의 URL에서 정의 파일을 불러오는 것을 거부한다는 것입니다. 원래는 아이콘을 GitHub Releases에 호스팅했으나, 프라이빗 리포지토리(private-repo) 설정 오류로 인해 GitHub의 CDN이 캐시된 404 오류를 반환했습니다. 확실한 해결책은 정의 파일과 아이콘 zip 파일을 Vercel의 자체 /public 폴더에서 제공하는 것이었습니다. 이렇게 하면 앱과 동일한 오리진(same origin)이 되어 배포할 때마다 즉시 업데이트할 수 있습니다.

또한 Type: Group 정의를 추가하여 브랜드화된 컨테이너(container)를 그릴 수 있도록 했습니다. 예를 들어, 모서리에 Vercel 로고가 있고 내부에 하위 리소스(child resources)를 포함하는 "Vercel Platform" 박스를 만들 수 있습니다.

기존 다이어그램 읽기

제가 가장 만족하는 기능은 기존 다이어그램의 스크린샷을 업로드하여 다시 생성하는 것입니다. Bedrock의 Claude Haiku 4.5는 멀티모달(multimodal) 모델이므로, 텍스트와 함께 또는 텍스트 대신 이미지를 (base64로) 수락할 수 있도록 API를 확장했습니다.

이로부터 자연스럽게 세 가지 모드가 도출됩니다:

  • 이미지만 사용 (Image only) → 다이어그램을 읽고 그에 상응하는 YAML을 재현합니다.
  • 이미지 + 텍스트 (Image + text) → 변환을 수행합니다: "이 다이어그램을 가져오되 Cloudflare를 Vercel로, EC2를 ECS로 바꿔줘."
  • 텍스트만 사용 (Text only) → 기존의 동작 방식입니다.

주의해야 할 빌드 타임(build-time)의 문제점 하나는 Bedrock 클라이언트가 모듈 로드 시점에 생성되고 있었다는 점입니다. AWS 환경 변수 없이 실행되는 next build의 페이지 데이터 수집(page-data collection) 과정에서 Region is missing 오류가 발생했습니다. 클라이언트를 지연 실행되는 요청 시점의 팩토리(lazy, request-time factory) 방식으로 옮겨 이를 해결했으며, 이는 상황과 관계없이 권장되는 패턴입니다.

프론트엔드는 붙여넣기(⌘V), 드래그 앤 드롭(drag-and-drop), 파일 선택을 지원하며, 용량 제한은 5 MB입니다.

배포: CI/CD

마지막으로, 자동 배포를 연결했습니다. CLI를 통해 Vercel의 GitHub App을 연결하는 방식은 작동하지 않았습니다(저장소 접근을 위해 브라우저 단계가 필요함). 그래서 대신 GitHub Actions + Vercel CLI를 사용했습니다. main 브랜치에 푸시(push)할 때마다, GitHub Secrets에 저장된 토큰을 사용하여 워크플로가 vercel pull / build / deploy --prebuilt --prod를 실행합니다.

한 가지 문제(hiccup)가 있었는데, Vercel 토큰이 중간에 만료되어 The token provided via --token argument is not valid라는 오류가 발생했습니다. 토큰을 (만료 기간 없이) 다시 생성하고 시크릿(secret)을 재등록하자 파이프라인이 정상(green)으로 돌아왔습니다. 이제 git push만 하면 배포가 완료됩니다.

과거의 나에게 해주고 싶은 말

  • 통합 전략을 결정하기 전에 소스 코드를 먼저 읽으세요. internal/ 패키지 문제를 첫날에 발견했다면 하루를 아낄 수 있었을 것입니다.
  • 인증(auth)이 설명할 수 없는 이유로 실패한다면, 최근의 플랫폼 변경 사항을 의심하세요. Lambda URL의 이중 권한(dual-permission) 요구 사항은 제 설정에서는 보이지 않았습니다.
  • LLM이 생성하는 구조화된 출력(structured output)의 경우, 취향을 예시와 함께 규칙으로 인코딩하세요. "라벨을 뚫지 마세요"라는 지시는 좋은 예시와 나쁜 예시를 포함한 구체적인 레이아웃 규칙이 된 후에야 제대로 작동했습니다.
  • 배포를 직접 제어할 수 있는 상황이라면, 영리한 CDN 트릭보다는 동일 출처 호스팅(Same-origin hosting)이 더 낫습니다.

기술 스택, 한 줄 요약

Next.js on Vercel · Amazon Bedrock (Claude Haiku 4.5, 텍스트 및 비전) · AWS Lambda (zip, Python 3.12)를 실행하는 awsdac CLI · 비-AWS 아이콘을 위한 simple-icons + sharp · CI/CD를 위한 GitHub Actions.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0