Go로 만든 AI 에이전트 파이프라인, 상품 이미지로 상세페이지 생성
Go로 AI 에이전트 파이프라인을 만들어 상품 이미지 1장에서 타오바오 상세페이지 세트를 생성하는 과정을 공유했다.
중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.
시리즈 안내: 이것은 「AI 상세페이지 생성 도구」 시리즈의 1번째 글(아키텍처 분석)입니다. 2번째 글은 Wails v2로 이 AI 데스크톱 앱을 처음부터 만드는 방법(실전 튜토리얼), 3번째 글은 혼자서 AI 제품을 만들며 밟았던 함정을 복기합니다. 발행 후 여기에 링크를 추가하겠습니다.
一、왜 이 일을 하는가
타오바오/티몰의 중소 판매자가 신상품을 올릴 때, 상세페이지는 보통 외주 디자이너를 찾습니다. 한 세트에 300–1500 위안, 3–7일을 기다려야 합니다. 시중의 AI 도구들은 "원클릭 미화"만 할 수 있거나, 생성된 이미지 속 상품이 "재디자인"되어 원형을 알아볼 수 없게 만들거나, 아예 대형 모델에게 이미지에 "글자를 쓰게" 해서 변형된 가짜 한자가 잔뜩 나오게 합니다.
제가 만든 이 프로젝트(ecom-detail-ai)의 목표는: 상품 이미지 1장을 업로드하면, 전 과정에서 카테고리도 고르지 않고 스타일도 고르지 않고, 약 10분 만에 사실감 있는 광과 그림자를 갖춘 상세페이지 롱 이미지 한 세트 + 메인 이미지 패키지 + 카피 를 얻는 것입니다 . 이것은 Wails v2 데스크톱 클라이언트(Go 백엔드 + Vue3 프런트엔드)이며, 핵심은 완전 자동화된 하나의 Agent 파이프라인입니다.
이 글에서는 이 파이프라인을 완전히 해부해서 각 단계의 설계 상의 선택을 이야기합니다. 본문의 코드는 프로젝트 소스에서 발췌했으며 일부 삭제된 부분이 있습니다.
먼저 최종 산출물의 효과를 한번 봅시다(예시 상품, 모두 프로젝트가 실제로 생성한 결과):
예시 상품 이미지
二、파이프라인 전경
하나의 작업이 생성에서 완료까지 실제로 거치는 단계는:
누끼(matte) → 이미지 인식(perceive) → 자율 계획(plan) → [계획 확인 게이트] → 렌더 × N (render, 자체 점검 후 재렌더 포함) → 화면 묘사(caption) → 카피(copy) → 로컬 조판 합성(layout) → [SOP 카드] → 완료(비용 기록)
오케스트레이션 핵심은 internal/service/pipeline.go 에 있습니다. 먼저 짚고 갈 두 가지 설계 결정이 있습니다:
첫째, LLM 단계와 결정적 단계를 엄격히 분리한다. 인식, 계획, 카피, 자체 점검이라는 네 가지 "지능이 필요한"环节은 internal/agent/ 패키지에 모여 있는데, 이 패키지는 "모델 호출 + 구조화된 결과 파싱"만 하고 데이터베이스는 건드리지 않습니다. 누끼, 렌더, 조판, 내보내기 같은 "지능은 필요 없지만 중요하고 꼼꼼해야 하는"环节은 전부 결정적 코드입니다. LLM은 의사 결정을, 코드는 실행을 맡고, 양쪽의 인터페이스는 JSON입니다.
둘째, 모든 단계가 영속화되고 관측 가능하다. 모든 하위 단계는 TaskStep 기록을 남기고 Wails 이벤트 task:progress 로 프런트엔드에 푸시해서, 사용자가 UI에서 "이미지 읽는 중 → 『휴대용 착즙기』로 인식 → 5장 이미지 계획 → 2번째 렌더에서 속도 제한 발생, 20초 후 재시도……"와 같은 실시간 타임라인을 볼 수 있습니다. 오래 걸리는 Agent 제품을 만들 때 진행 상황의 가시성은 금상첨화가 아니라 신뢰의 토대입니다.
三、1단계: 인식 — 먼저 상품을 "이해"한다
파이프라인의 품질 상한은 첫 번째环节이 결정합니다. 인식 단계는 비전 모델(GLM-4.6V)로 상품 이미지를 읽고 구조화된 Analysis 를 출력합니다:
type Analysis struct { Name string `json:"name"` // 상품명 제안 Category string `json:"category"` // 1차 카테고리 Material string `json:"material"` // 재질 SellingPoints [] string `json:"selling_points"` // 셀링 포인트 단서 Details [] string `json:"details"` // 구조적 특징: 상단 손잡이/측면 통풍구/logo 위치 VisibleText [] string `json:"visible_text"` // 이미지에 실제로 인쇄된 문자, 글자 그대로 전사 Params []Param `json:"params"` // 이미지에서 보이는 파라미터(키-값 쌍) Palette string `json:"palette"` // 상품 주 색상 hex, 조판 색상 구성 구동 }
시스템 프롬프트에는 철칙이 하나 있는데, 이것이 프로젝트 전체에서 환각에 대항하는 첫 번째 댐입니다:
"visible_text 와 params 는 이미지에 실제로 보이는 내용을 글자 그대로 전사하는 것만 허용하며, 잘 안 보이거나 없으면 빈 배열을 반환하고, 상식으로 추론해 채워 넣는 것은 금지한다 — 파라미터를 지어내는 것은 규칙 위반이다."
이 규칙은 뒤에서도 반복해서 등장합니다: 하위의 어떤 단계(계획, 카피, 파라미터 표)든 인용할 수 있는 사실은 최대한 인식 출력 ∪ 사용자 입력 까지다 . 모델이 볼 수 없는 것은 차라리 비워 두고, 지어내지 못하게 합니다.
또 다른 두 가지 세부 사항:
- "JSON 추출" 같은 작업에서는 비전 모델의 사고 모드를 명시적으로 꺼야 합니다( WithThinkingDisabled() ). 긴 추론 사슬은 요청을 느리게 만들 뿐이고 심지어 멈춰 세울 수도 있으며, 추출 품질에는 도움이 되지 않습니다.
- 모델 출력을 파싱할 때 순진하게 json.Unmarshal 해서는 안 됩니다. 모델은 markdown 코드 펜스를 감싸는 걸 좋아하고 앞뒤에 설명 문구를 붙이는 걸 좋아하므로, unmarshalJSON 이 있습니다: 펜스를 벗기고, 첫 번째 { 부터 마지막 } 까지를 취해 다시 파싱합니다. 이것은 순수 JSON 출력을 요구하는 모든 LLM 앱의 표준 방어입니다.
四、2단계: 계획 — 모델을 감독으로 삼되, 시나리오 프레임을 준다
계획 단계(DeepSeek)는 인식 결과에 따라 결정합니다: 이미지를 몇 장 낼지, 각 장의 kind(whitebg / hero / scene / detail / selling)는 무엇인지, 어떤 장면과 광과 그림자인지, 상세페이지 모듈 시퀀스, 카피 톤. 역시 순수 JSON 출력입니다.
여기서 아주 전형적인 함정을 밟았습니다: "모델이 스스로 장수를 결정하게" 하고 싶을 때, 프롬프트에 숫자 0 이 절대 나타나면 안 됩니다 . 초기 버전에서는 사용자가 장수를 지정하지 않으면 image_count: 0 을 전달했더니, 모델이 아주 순순하게 0장을 계획해서 작업이 바로 중단되었습니다. 지금의写法:
countHint := fmt.Sprintf( "계획 %d 장 이미지" , imageCount) if imageCount <= 0 { // 0은 프롬프트에 나타나면 안 된다. 그렇지 않으면 모델이 "순순하게" 0개 항목을 반환한다 countHint = "렌더 장수는 네가 결정한다(3-8장)" }
계획은 또한 전체 파이프라인의 첫 번째环节이라, 빈 items 를 출력하면 작업이 바로 중단되므로, 계획 호출에는 "자동 교정 재시도"가 한 번 내장되어 있습니다: 파싱 실패거나 items 가 비어 있을 때, 사용자 메시지를 하나 덧붙여 "반드시 최소 3개 항목을 출력해야 한다"고 꾸짖고, 다시 한 번 기회를 줍니다.
계획 완료 후에는 계획 확인 게이트 가 있습니다: 기본 모드에서는 30초의 "기회 창"(사용자가 이때 계획을 손쉽게 조정 가능) 동안 머문 뒤 자동으로 계속 진행하고, 확인 모드에서는 작업이 planned 상태가 되어 최대 10분 동안 사용자가 계속을 누르기를 기다립니다. 완전 자동과 휴먼 인 더 루프 사이에서, 저는 둘 다를 선택하고 설정으로 전환합니다.
五、3단계: 렌더 — 5단 구성 프롬프트와 상품 충실도
렌더는 전체 파이프라인에서 가장 비싼环节(장당 과금)이자, 가장 쉽게 사고가 나는环节입니다: 이미지-투-이미지 모델은 "손김에 최적화"해서 당신의 상품을 바꿔놓습니다 — logo를 바꾸고, 구멍 위치를 고치고, 한 세트를 다 만들면 이미 다른 제품이 되어 있습니다.
대책은 5단 구성 렌더 프롬프트 ( internal/agent/prompt.go )입니다. 하나의 prompt를 모델 자유도가 확연히 다른 다섯 단으로 쪼갭니다:
보존 단(자유도 0) + 장면 단(모델 계획) + 광과 그림자 단(단어库 매핑) + 구도 단(kind별 고정) + 품질 단(모든 kind 공유)
보존 단은 "일관성을 유지하라"는 한마디가 아니라, 인식 결과를 인용해 조목조목 지목 합니다:
b.WriteString( "참조 이미지의 상품을 엄격히 원래대로 유지한다" ) if len (a.Details) > 0 { b.WriteString( ", 다음 특징은 반드시 원래대로 보존하고 변경해서는 안 된다: " ) b.WriteString(strings.Join(a.Details, "、" )) // 상단 손잡이, 측면 통풍구…… } if len (a.VisibleText) > 0 { b.WriteString( "상품 위의 문자 「…」는 반드시 글자 그대로 유지하고 선명하며 변형이 없어야 한다." ) }
(보존 항목을 두루뭉술하게 "일관성을 유지하라"고 하지 않고 명시적으로 나열하는 것은, Google 편집 모델 공식 가이드의 요구 사항이며, 실측에서도 확실히 효과가 있었습니다.)
광과 그림자 단은 중국어 프리셋을 사진술 용어 단어库에 매핑합니다 — "소프트 라이트"는 "소프트박스 조명, 광선이 균일하고 부드러우며 그림자 전환이 자연스러움"으로 매핑되는데, 모델에 형용사를 던지는 것보다 훨씬 안정적입니다. 구도 단은 kind별로 고정하며(hero는 "3분의 2 측면 시점, 낮은 카메라로 수평 시선, 상품이 화면의 60%~70% 차지"로 고정), 품질 단은 모든 kind가 공유해서 전체 상세페이지의 시각적 통일성을 보장합니다. 품질 단에는 "네온 외곽선 없음, 광과 그림자는 실제 상업 사진의 물리 법칙에 부합"이라는 문장이 있는데, 이는 실측 교훈에서 나온 것입니다: 계획이 "윤곽광으로 테두리 강조"를 제시했을 때, Seedream은 이를 문자 그대로 사이버펑크 네온 외곽선으로 실행했습니다.
렌더의 엔지니어링 골격은 renderOne 입니다: 다중 참조 이미지 data URL → 이미지-투-이미지 → 다운로드 후 디스크 저장 → 자체 점검. 두 가지 세부 사항:
- 속도 제한 지수 백오프 : 이미지 생성 신규 계정은 RPM이 매우 낮아 429가 일상입니다. "속도 제한/429/Throttling" 등의 오류 특징을 식별하고, 10s/20s/40s/60s로 백오프 재시도하며, 대기 중에는 select 로 ctx.Done() 을 동시에 감시해서 사용자가 취소를 누르면 즉시 대기를 종료합니다.
- 재시도에는 반드시 진단이 따라야 한다 : 자체 점검 불통과로 재렌더가触发될 때, 같은 파라미터로 다시 돌리는 것은 아무 의미가 없습니다. 두 번째의 prompt에는 "지난 회차 생성에 문제가 있었다: 상품이 원본 이미지와 일치하지 않고, 광과 그림자가 부자연스럽다. 재생성 시 반드시 수정하고 나머지 요구는 변경하지 않는다."를 덧붙입니다 — 심사 의견을 생성 모델에 되먹이는 것입니다.
첫 화면(hero)은 전환의 핵심 위치라, 여기에 이중 후보 선별 을 추가했습니다: 계획 구도와 "정면 수평 시선" 구도를 각각 한 번씩 렌더하고, 자체 점검 점수로 더 좋은 것을 골라 남깁니다. best-of-N은 이미지 생성에서 가성비가 가장 높은 품질 지렛대이며, 1회당 +0.1 위안의 비용입니다. 다만 코드 주석에 있는 실측 교훈에 주의하세요: 이중 후보 자체가 이미 유효한 재시도이므로, 단일 후보에는 자체 점검 재시도를 더 이상 겹치지 않습니다, 그렇지 않으면 최악의 경우 한 슬롯에 이미지 4장 값이 타버립니다.
六、"문자 제로 환각": 이 프로젝트에서 제가 가장 만족하는 설계
전자상거래 상세페이지에는 방대한 양의 문자가 있습니다: 제목, 셀링 포인트, 파라미터 표. 이미지 생성 모델에게 글자를 그리게 하면 반드시 변형된 가짜 한자가 나옵니다. 그렇다고 모델이 "최대한 글자를 그리지 않게" 할 수 있을까요? 그것도 안 됩니다, 모델은 자기 손을 통제하지 못합니다.
저의 방안은 문자這件事을 애초에 AI의 손에서 거둬들이는 것입니다 :
- 계획 프롬프트에 철칙( planNoTextRule )을 써넣습니다: 장면 묘사에는 "글자를 그린다"는 의도를 담아서는 안 되고, prompt는 상품, 장면, 분위기와 구도만 묘사합니다;
- 렌더 품질 단에서 다시 명시적으로 금지합니다: "화면에 어떠한 문자, 숫자, 주석, 라벨 스티커도 나타나서는 안 된다(상품 자체에 인쇄된 문자는 제외)" — 이 예외 조항은 보존 단의 "상품 위에 인쇄된 문자를 글자 그대로 보존"과 자기 일관성을 이루어야 한다는 점에 주의하세요. 그렇지 않으면 모델이 혼란스러워합니다;
- 실제 문자는 로컬 조판 엔진이 후반에 오버레이합니다 . internal/render/ 는 손으로 작성한 750px 폭 비트맵 합성 엔진으로, golang.org/x/image 에 시스템 CJK 폰트를 더해 카피를 실제 폰트로 해당 모듈 슬롯에 조판해 넣습니다.
이렇게 해서 AI는 "화면"만, 조판 엔진은 "문자"를 맡아, 각자 잘하는 일만 합니다. 폰트, 글자 크기, 행간, 색상 구성이 전부 통제 가능하고, 글자 하나를 고치는 데 이미지를 재생성할 필요가 없습니다. 덤으로 얻는 이점은: 카피를 편집한 후 실시간 미리보기 재조판은 수백 밀리초면 되지만, 이미지를 재생성하는 데는 수십 초에 몇 마오(角)의 비용이 듭니다.
자체 점검环节에서는 "화면 문자 오류"를 별도로 검사합니다( text_error 는 치명적 tag로, 바로 불통과 판정) — 금지 + 품질 검사의 이중 안전장치입니다.
七、4단계: 자체 점검 — 모델은 심사위원, 코드는 게이트를 세운다
자체 점검은 비전 모델로 이중 이미지 대조 를 합니다: 참조 이미지 + 생성 이미지 + 계획 묘사를 함께 "품질 검사원"에게 주고, 출력합니다:
type CheckResult struct { Pass bool `json:"pass"` Fidelity float64 `json:"fidelity"` // 상품 유지도 0-1 Tags [] string `json:"tags"` // 진단 태그(5선1 열거) Issue string `json:"issue"` // 불통과의 사람 말 이유 ... }
핵심 인식: 모델의 pass 판단만 믿어서는 안 되고, 서버 측에서 결정적 문턱을 둬야 한다 . 모델 심사위원은 물을 빼는 경향이 있으므로, 점수를 받은 후 코드가 다시 단단히 검증합니다 — fidelity < 0.95면 바로 불통과 판정; product_drift / structure_deform / text_error 중 어느 하나라도 치명적 tag가 나오면 바로 불통과 판정; light_off, scene_mismatch 같은 분위기 문제는 단독으로 나올 때 통과시키되 알림은 남깁니다. 이것이 enforceCheckGate 입니다.
진단 tags는 동시에 두 소비자를 섬깁니다: 재렌더의 prompt에 되먹이는 것(기계가 읽음)과, 프런트엔드 타임라인의 "수동 확인 대기" warn 상태(사람이 읽음)입니다. "자체 점검 의심" 이미지 한 장은 버려지지 않고, 노란색으로 표시되어 사용자가 결정하도록 넘깁니다 — 자동화 시스템의 마지막 칸은 언제나 사람을 위해 남겨둡니다.
장면도/셀링 포인트 이미지 자체 점검이 여전히 통과되지 않을 때를 위한 합성 충실도 최후 방어책도 있다. 텍스트-투-이미지로 순수 배경만 생성하고(프롬프트에 "어떤 상품도 등장하지 말 것"을 명확히 지정), 실제 누끼 딴 상품 픽셀을 다시 붙여 넣는다. 상품 픽셀은 100% 실제이므로 드리프트가 존재하지 않아 자체 점검이 아예 필요 없다. 이것이 바로 "절약 모드"의 기반 경로이기도 하며, 더 저렴하고 비용 예측이 가능하다.
八、다중 모델 분업: 하나의 게이트웨이, 다섯 개의 모델 슬롯
어떤 플랫폼도 모든 모달리티에서 최강은 아니기에, 나는 "각 단계마다 그 단계에서 가성비가 가장 좋은 모델을 쓴다"는 원칙으로 설계했고, 동시에 아키텍처로 벤더 종속(lock-in) 위험을 0으로 눌렀다.
설정은 다섯 개의 모델 슬롯으로 추상화된다(internal/config/config.go):
모델 슬롯 기본 설정 역할 perception 즈푸 GLM-4.6V 이미지 인식 + 완성품 자체 점검 brain DeepSeek flash(fallback: pro) 기획(Agent 두뇌) copy DeepSeek flash 상세페이지 문안 render GLM-Image(방저 Seedream으로 전환 가능) 이미지 생성 matting 알리윈 상품 분할 누끼
그리고 게이트웨이 계층(internal/llm/gateway.go)은 OpenAI 호환 프로토콜만 구현하므로, 공급사 교체 = base_url + api_key + model 세 파라미터 교체다:
var presetBaseURL = map [ string ] string { "zhipu" : "https://open.bigmodel.cn/api/paas/v4" , "deepseek" : "https://api.deepseek.com" , "ark" : "https://ark.cn-beijing.volces.com/api/v3" , }
즈푸, DeepSeek, 화산 방저 모두 chat/completions와 호환되므로 Chat 메서드 하나로 다 커버된다. 유일한 프로토콜 예외는 이미지-투-이미지인데, 각 사의 참조 이미지 파라미터 형태가 일관되지 않아 이를 images.go 한 파일에 격리하고 주석에 "연동 시 이 파일만 수정이 허용되며, 호출부는 영향을 받지 않는다"고 명시했다. 더러운 작업을 최소 면적으로 수렴시킨 것이다.
BYOK(사용자 자체 키)는 부수적으로 컴플라이언스와 비용 투명성 문제도 해결한다. 비용은 사용자 자신의 모델 계정에서 직접 정산되고, 각 작업이 끝날 때마다 정가표 기준으로 환산한 소모량(taskCost가 토큰과 생성 이미지 장수를 기록)을 인터페이스에 명시한다. 제품 제공 측은 자금을 취급하지 않고 상품 이미지를 저장하지 않으며, 레이아웃 합성은 전부 로컬에서 이루어진다.
九、문안과 레이아웃의 포카요케(fool-proof) 계약
문안(DeepSeek)은 모듈별로 작성되며, 두 가지 철칙이 system prompt에 적혀 있다. 파라미터는 "이미지에서 보이는 파라미터 ∪ 판매자 입력"만 사용할 수 있고, 둘 다 없으면 "상세 파라미터는 실물 기준"이라고 쓴다. 서로 다른 모듈 간에 같은 셀링 포인트를 재진술해서는 안 되며, "중복은 곧 낭비"다.
모델 출력과 레이아웃 엔진 사이에는 또 하나의 용량 계약(CleanCopy)이 있다. 제목 ≤12자, 본문 ≤80자로, 레이아웃 글자 크기 단계와 맞물려 있으며, 초과 시 잘라내어 오버플로를 방지하고, 빈 블록은 버리며, 정제 후 블록이 하나도 남지 않으면 인식 필드로 조립한 기본 문안으로 폴백한다. 파이프라인은 강등될 수 있어도 결코 빈 결과를 산출하지 않는다. 문안 단계가 전체적으로 실패해도 작업을 막지 않으며, 폴백 문안이 평소처럼 나오고 프런트엔드에서 수동 수정이 가능하다고 안내한다.
덧붙여 "문안 대 이미지" 관련한 소소한 설계 하나. 렌더링 완료 후 각 완성 이미지는 시각 모델을 한 번 거쳐 ≤40자의 화면 설명(caption)을 생성하고, 문안의 user 메시지에 "2번째 장면/침실 침대 협탁: 따뜻한 조명 아래 상품이 침대 협탁 위에 서 있다……"가 포함되어, 텍스트 모듈이 써낸 말과 화면이 진짜로 호응하게 만들어 그림과 글이 따로 노는 것을 없앤다.
十、소결
돌이켜 보면 이 파이프라인의 핵심 방법론은 딱 세 가지다:
- LLM 출력 전부를 순수 JSON + 구조화 파싱 + 결정론적 임계값으로 수렴시킨다. 모델은 창의와 판단을 맡고, 코드는 검증과 실행을 맡으며, 심사위원이 물을 흘리면 강하게 막는다.
- 환각에 빠지기 쉬운 모든 단계에 "사실 출처 화이트리스트"가 있다. 파라미터는 오직 이미지에서 보이는 것 ∪ 판매자 입력에서만 올 수 있고, 텍스트는 오직 레이아웃 엔진에 의해서만 화면에 기입될 수 있다.
- 실패 경로는 층층이 폴백되며 비용은 사실대로 반영한다. 레이트리밋 백오프 → 진단 피드백 후 재렌더링 → 합성 충실도 폴백 → 로컬 크롭 폴백 → 기본 문안 폴백. 동시에 유료 생성된 모든 이미지는 비용 명세에 계수되고, 재시도도 과금되며, 결코 자기를 속이지 않는다.
이 도구가 데스크톱에서 이 체계를 어떻게 돌리는지(Wails 바인딩, SQLite 스케줄러, 키 암호화, 3개 플랫폼 패키징), 그리고 그 과정에서 밟은 구체적인 함정들은 각각 시리즈 2편, 3편의 주제이니 관심 있게 봐 주시길 바란다.
(프로젝트 코드는 개인 오픈소스/비공개 프로젝트 ecom-detail-ai에서 발췌했으며, 모두 삭제·축약했다. 본문 중 모델 가격과 설정은 원고 작성 시점 기준이다.)
샤하오(沙蒿) 동학
서버 엔지니어 @선전시 야생균 유한공사
123
글
92k
조회
51
팔로워