왜 더 나은 결과를 요구하는 것이 실제 문제를 놓치는가

발행: (2026년 1월 12일 오후 01:30 GMT+9)
11 분 소요
원문: Dev.to

Source: Dev.to

번역을 진행하려면 번역하고자 하는 전체 텍스트를 제공해 주세요. 코드 블록이나 URL은 그대로 유지하고, 본문 내용만 한국어로 번역해 드리겠습니다. 텍스트를 복사해서 여기에 붙여 주시면 바로 작업을 시작하겠습니다.

Source:

Debugging Ideogram V3 – 일관되지 않은 건축 렌더링

문제
어제 나는 Ideogram V3가 일관되지 않은 건축 렌더링을 계속 생성하는 이유를 찾는 데 네 시간을 보냈다. 백서에서는 *“향상된 공간 일관성”*을 약속했지만, 내 출력물은 마치 위원회가 설계한 것처럼 보였다.

배경
나는 전자상거래 플랫폼을 위한 인테리어 디자인 변형을 생성하는 파이프라인을 구축하고 있었다. 백서에는 완벽한 조명을 갖춘 아름다운 건축 공간 예시가 나와 있었다.

프롬프트 (백서에서 발췌)

"Modern minimalist living room, floor-to-ceiling windows, 
natural light, Scandinavian furniture, architectural photography"

관찰 내용

GenerationResult
1‑3완벽
4가구가 바닥에서 떠 있음
5창 위치가 변경됨
10서로 다른 방 레이아웃 7가지

같은 시드, 같은 파라미터, 같은 모델 버전.
문제는 무작위성이 아니라 각 생성을 독립적으로 처리한 것이었다. 백서 예시는 단일, 신중하게 구성된 프롬프트였기 때문에 잘 작동했다. 나는 상태를 유지하지 않은 채 반복 실험을 진행하고 있었다.

해결책 – 프롬프트 컨텍스트와 메모리

class PromptContext:
    def __init__(self, base_intent):
        self.base_intent = base_intent
        self.style_locks = {}

    def generate_with_memory(self, variation):
        locked = " ".join([f"{k}: {v}" for k, v in self.style_locks.items()])
        return f"{self.base_intent}. {locked}. {variation}"
context = PromptContext("Modern minimalist living room")
context.style_locks["windows"] = "floor-to-ceiling on north wall"
context.style_locks["floor"]   = "light oak hardwood"

비용: ≈ 요청당 토큰 40 % 추가.
이점: 사용 가능한 출력이 약 60 %에서 95 %로 상승.

백서에서는 기능을 보여줄 뿐, 워크플로우는 보여주지 않는다. 동일한 프롬프트를 여러 AI 모델에서 테스트할 수 있을 때, 문서와 실제 사이의 불일치가 좌절이 아니라 측정 가능한 형태가 된다.

패키징 개념 – “프리미엄하지만 친근함”

Brief – 일본식 미니멀리즘과 1970년대 미국식 낙관주의의 만남.

첫 번째 시도

{
    "prompt": "Premium beverage packaging, minimalist, warm nostalgic colors, sophisticated",
    "cfg_scale": 7.5,
    "sampler": "DPM++ 2M Karras"
}

Result: 일반적인 웰니스 브랜드 미학 – 기술적으로는 완벽하지만 전략적으로는 무용지물.

파라미터 스윕

cfg_scaleObservation
5.0브랜드 정체성 상실
7.5안전하고 평균적인 미학
10.0흥미로운 긴장이 드러남
12.0과잉 조리됐지만 일관성은 있음

Solution – Describe the Extremes

prompt_a = """1970s American optimism, warm oranges,
             rounded typography, sunburst graphics"""

prompt_b = """Japanese minimalism, white space,
             geometric precision"""

Generate separately at cfg_scale=11.0, then synthesize specific elements.

SD3.5 Medium은 “아무것도 깨지지 않음”을 목표로 최적화돼 있습니다. 모호한 목표 대신 모순되는 구체적 지시와 높은 CFG를 주면 흥미로운 실패를 얻을 수 있습니다. 쓸모없는 이미지 3개와 뛰어난 이미지 1개가 평범한 이미지 10개보다 낫습니다.

Trade‑off: ≈ 3× 생성 시간, 하지만 수정 시간 절감으로 가치가 있었습니다.

Model Regression Test – Newsletter Summaries

시나리오 – 3개월 된 파이프라인이 주간 뉴스레터 요약을 생성했습니다.

  • v1.2 (이전): 480 토큰, 대화형.
  • v1.3 (이후): 310 토큰, 기업형.

릴리스 노트: “Improved efficiency and coherence.” 온도 재조정에 대한 언급은 없습니다.

Diff Script

def model_regression_test(old_model, new_model, test_prompts):
    results = []
    for prompt in test_prompts:
        old_response = generate(old_model, prompt, temp=0.7)
        new_response = generate(new_model, prompt, temp=0.7)

        diff = {
            "length_delta": len(new_response) - len(old_response),
            "formality_delta": analyze_formality(new_response) -
                               analyze_formality(old_response)
        }

        if abs(diff["length_delta"]) > 100:
            print(f"WARNING: Length shift")
        results.append(diff)
    return results

근본 원인 – 온도 스케일링이 변경되었습니다: v1.3의 temp=0.7이 v1.2의 temp=0.4처럼 동작했습니다.

해결책 – 프로덕션에서 모델 버전을 고정하고 업그레이드 전에 회귀 테스트를 실행합니다.

# requirements.txt
nano-banana-pro==1.2.8  # Regression test before upgrade

“Improved”는 종종 “다름”을 의미합니다. 모델 업데이트를 데이터베이스 마이그레이션처럼 다루세요. Nano Banana PRO New와 레거시 버전을 동시에 테스트하면 릴리스 노트가 숨기는 내용을 확인할 수 있습니다.

Source:

실험 로깅 – 법적 면책 조항 생성

워크플로우 (지난 달)

  1. ChatGPT에서 프롬프트 초안 작성
  2. Jupyter 노트북에서 테스트
  3. Notion에서 결과 확인
  4. Slack에서 논의
  5. Google 문서 업데이트
  6. 노트북 재실행
  7. 1단계 결정은 잊어버리기

법적 면책 조항 변형을 생성할 때, 각 카테고리마다 특정 규제 언어가 필요했습니다. 동일한 프롬프트라도 모델 버전 차이 때문에 ChatGPT와 노트북에서 다른 결과가 나왔으며, 버전 불일치를 깨닫기까지 디버깅에 30분을 소비했습니다.

로깅 시스템

import sqlite3, json
from datetime import datetime

class ExperimentLog:
    def __init__(self):
        self.conn = sqlite3.connect("experiments.db")
        self.setup_db()

    def setup_db(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS experiments (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT,
                model TEXT,
                prompt TEXT,
                parameters TEXT,
                output TEXT,
                success INTEGER,
                notes TEXT
            )
        """)
        self.conn.commit()

    def log(self, model, prompt, params, output, success, notes=""):
        self.conn.execute("""
            INSERT INTO experiments 
            (timestamp, model, prompt, parameters, output, success, notes)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        """, (datetime.now().isoformat(),
              model,
              prompt,
              json.dumps(params),
              output[:500],
              int(success),
              notes))
        self.conn.commit()

    def get_successful_prompts(self, model):
        return self.conn.execute("""
            SELECT prompt, parameters FROM experiments 
            WHERE model = ? AND success = 1
            ORDER BY timestamp DESC
        """, (model,)).fetchall()

이제 “지난 주 법적 면책 조항”과 같이 검색하면 정확한 파라미터, 모델 버전, 출력 결과를 바로 가져올 수 있어 다시 찾는 시간을 절약합니다.

요점

  • 상태 기반 프롬프트 (예: PromptContext)는 일관성을 크게 향상시킵니다.
  • 극단적이고 모순되는 사양과 높은 CFG를 결합하면 유용한 “실패”를 드러낼 수 있습니다.
  • 버전 고정 및 회귀 테스트는 모델의 조용한 변경(temperature, 토큰 제한 등)으로부터 보호합니다.
  • 중앙 집중식 실험 로깅은 도구와 팀원 간의 지식 손실을 방지합니다.

컨텍스트 전환은 단순한 생산성 비용이 아니라—의도를 도구 전반에 흩어진 마이크로‑결정들로 분할합니다. 체계적인 워크플로우(상태 기반 프롬프트, 버전 관리, 로깅)는 AI 실험을 추측 게임이 아닌 반복 가능한 엔지니어링 프로세스로 바꿔줍니다.

Summary

Leena는 언어 모델을 사용해 PDF에서 기술 요구사항을 추출하는 워크플로우를 공유합니다. 이 접근 방식은 수작업으로 진행될 경우 시간이 많이 소요되는 과정을 자동화합니다.

The Problem

  • ChatGPT에 특정 섹션(예: “Section 7의 데이터 보존 요구사항은 무엇인가요?”)을 물어도 요약의 요약이 반환되는 경우가 많아 정확한 사양을 얻기 어렵습니다.
  • 수동으로 읽고 질문하는 데 몇 시간이 걸릴 수 있습니다.

The Workflow

def chunk_document(pdf_path, chunk_size=4000):
    """Split a PDF into overlapping text chunks."""
    reader = pypdf.PdfReader(pdf_path)
    chunks = []

    for i, page in enumerate(reader.pages):
        text = page.extract_text()
        words = text.split()

        # Overlap of 200 tokens to preserve context across chunks
        for start in range(0, len(words), chunk_size - 200):
            chunks.append({
                "page": i + 1,
                "text": " ".join(words[start:start + chunk_size])
            })
    return chunks
def extract_requirements(pdf_path):
    """Call the LLM on each chunk and collect requirement objects."""
    chunks = chunk_document(pdf_path)
    requirements = []

    for chunk in chunks:
        prompt = f"""Extract technical requirements from:
        Page {chunk['page']}: {chunk['text']}

        Return JSON: {{"requirements": [{{"type": "retention", 
        "spec": "7 years", "section": "7.3.2"}}]}}"""
        
        result = call_llm_api(prompt)          # ← your LLM wrapper
        requirements.extend(result.get("requirements", []))

    return requirements

Sample output

[
  {
    "type": "retention",
    "spec": "7 years for financial records",
    "section": "7.3.2",
    "page": 45
  },
  {
    "type": "retention",
    "spec": "3 years for operational logs",
    "section": "7.3.2",
    "page": 45
  }
]

Trade‑offs

AspectBenefitCost/Consideration
Processing timeReduces manual effort from ~3 h → ~20 minMore CPU/LLM API calls (higher latency)
API expenseFaster insight extractionIncreased token usage → higher cost
AccuracyDirectly pulls spec textDepends on LLM’s parsing reliability

Lessons Learned

  1. Version everything – keep prompts under Git alongside code.
  2. Log early – avoid weeks of lost work by tracking experiments from day 1.
  3. Test edge cases – not just the happy path; PDFs vary wildly in layout.
  4. Treat model updates like schema migrations – automate diff checks between LLM versions.

Call to Action

If you’ve faced similar workflow bottlenecks, feel free to comment or share your own approach.

— Leena

Back to Blog

관련 글

더 보기 »

안녕, 뉴비 여기요.

안녕! 나는 다시 S.T.E.M. 분야로 돌아가고 있어. 에너지 시스템, 과학, 기술, 공학, 그리고 수학을 배우는 것을 즐겨. 내가 진행하고 있는 프로젝트 중 하나는...