본문 바로가기
C.W.K.
Lesson 02 of 05 · published

부탁한 쪽이 그림에 나오는 쪽은 아니야

~14 min · identity, actor, subjects, contracts

Level 0스케치
0 XP0/43 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

얼굴이 자라난 풍경

실패부터 그려 보자. 아빠랑 바다 얘기를 하다가 내가 해 질 녘의 조용한 항구 그림을 부탁해. "누가 부탁했나"를 "누가 나오나"로 읽는 시스템은 부탁한 소울의 레퍼런스를 집어서 붙여. 그러면 모델은 레퍼런스 이미지가 시키는 대로 해. 항구 어딘가에 내 얼굴을 넣는 거야. 아무도 바란 적 없는데. 아키텍처 문서는 이걸 한 줄로 못 박아. 부탁한 소울이 저절로 그려지는 소울이 되는 일은 없다. 피파가 부탁했다고 평범한 풍경에 피파 얼굴이 생기면 안 된다.

하우스 사인은 이걸 타입으로는 못 지키는 불변식 목록에 올려 놨어. 부탁한 쪽과 그려지는 쪽은 서로 다른 개념이다. 타입이 못 지켜 줘도 코드가 둘을 어떻게 떼어 놓는지, 이 레슨에서 봐.

섞이지 않는 두 값

요청 계약엔 subjects 목록이 있고, 부탁한 쪽을 담는 필드는 아예 없어. subjects 위 주석은 부탁한 소울을 여기서 짐작하지 않는다는 문장으로 끝나. 두뇌가 부르는 내부 라우트는 부탁한 쪽을 따로 받는 필수 필드를 하나 더해. InternalGeneration은 평범한 Generation에 actor_soul_id를 붙인 거고, 라우트는 계획을 세우기 전에 그 필드를 떼어 내. 부탁한 쪽은 요청 옆에서 같이 가. 누가 나오는지 적은 목록엔 절대 안 끼어.

피파 쪽에서도 두 값은 나오는 데가 달라. 부탁한 쪽은 그 호출을 만든 대화의 소울이야. 피사체는 두뇌가 subject_soul_ids에 적은 것 그대로고. 도구 설명은 이걸 대문자로 박아 둬. The acting soul is NOT automatically a depicted subject. 부탁한 소울은 저절로 피사체가 되지 않는다는 거야. 두뇌가 아무도 안 적어도, 부탁한 소울이 그 빈자리를 채우진 않아. 그러면 계획의 피사체는 고른 레퍼런스와 세트가 원래 달고 있는 것뿐이야. 둘 다 없으면 그 그림엔 등록된 피사체가 없고.

두뇌가 붙인 파일에도 부탁한 쪽 이름은 안 찍혀. 피파 클라이언트는 첨부 경로를 올릴 때 가시성 검사용으로 부탁한 쪽을 넘기고, 피사체 목록은 비워서 보내. 내가 건넨 스케치는 누가 묶어 주기 전까진 어디에도 안 묶인 레퍼런스야. 다만 Avatars가 전에 저장한 적 있는 바이트는 원래 피사체를 그대로 달고 있어. 그 혈통이 왜 픽셀을 따라가는지는 트랙 8에서 다뤄.

그럼 부탁한 쪽은 뭐에 써?

누가 나오는지 안 정한다면 왜 보내냐고? 다른 두 가지를 정하거든.

질문답하는 것검사하는 곳
그림에 누가 나와?이름이 적힌 피사체, 고른 세트의 소울, 그리고 고른 레퍼런스가 이미 달고 있는 피사체작업마다 계획에 붙는 피사체 목록
이 호출자는 뭘 볼 수 있어?부탁한 쪽모든 레지스트리 검사. 보호된 소울은 자기 자신일 때만 통과
이 생성은 누구 거야?요청과 함께 저장된 부탁한 쪽/internal/batches/{id}가 다른 쪽엔 403으로 답해

첫 줄은 한 번 더 볼 만해. 작업의 피사체는 이름 적힌 피사체에, 레퍼런스 이미지의 혈통에 든 피사체를 전부 합친 거야. 이건 작가 표시가 아니라 혈통이야. 라이브러리에 있는 피파 아바타를 스타일 레퍼런스로 고르면, 누가 부탁했든 결과물은 피파한테서 나왔고 그렇게 적혀. 다만 혈통은 기록 속에 있어. 픽셀만 봐선 아무것도 증명 못 해. 그 아바타를 다른 앱에서 잘라 올리면 바이트가 달라져서 저장된 해시 어디에도 안 맞아. 그러면 업로드가 적어 준 피사체만 달고 들어와. 저장된 레퍼런스를 데려오는 건 이름이 적힌 피사체뿐이고, 외모 규칙은 이름이 적힌 피사체와 고른 세트의 소울이 데려와. 레퍼런스 혈통으로 따라온 피사체는 기록만 되고 묘사되진 않아. 부탁한 쪽은 이 어디에도 안 나와.

웹 스튜디오도 같은 말을 해

스튜디오의 Create image 페이지엔 People & souls in the image라는 칸이 있어. 그림에 나올 사람과 소울을 고르는 칩 줄이야. 등록된 피사체마다 칩이 하나, 저장된 룩마다 하나 더 붙어. 새 초안은 아무것도 안 고른 채로 시작하고, 라벨 밑 안내문이 칩이 하는 일을 알려줘. 고른 피사체는 저장된 기본 레퍼런스랑 외모 규칙을 데려온다. 웹 라우트는 부탁한 쪽을 아예 안 보내. 그래서 WebUI에선 부탁했다는 이유로 끼어드는 피사체가 없어. 그려지는 목록은 아빠가 누른 칩에, 작업 중인 세트의 소울과 고른 레퍼런스가 이미 달고 있는 피사체를 합친 거야.

모든 두뇌가 읽는 사용법 문단

두뇌는 이걸 플레이북에서 배우지 않아. 피파의 ImageGen이 action=options를 부르면, Avatars는 피사체 디렉터리랑 짧은 사용법 문단을 같이 돌려줘. 그 문단의 마지막 문장이 이거야. 부탁한 소울은 이름이 적히지 않는 한 그려지는 피사체가 아니다. 같은 문단이 피사체 목록에도, 영상 생성기 목록에도 실려. 두뇌가 두드릴 수 있는 문마다, 누군가의 이름을 적으려는 바로 그 순간에 규칙을 다시 들려주는 거지. 그 문들은 트랙 5에서 하나씩 열어 봐.

원칙: "누가 부탁하나"와 "뭘 부탁하나"는 다른 필드에 두고, 한쪽이 다른 쪽의 기본값이 되게 두지 마. 호출자를 내용에 슬쩍 복사해 넣는 편한 기본값은 리뷰에서 안 보여. 결과가 누군가 일부러 한 요청이랑 똑같이 생겼으니까.

가족이라서 더 중요해

혼자 쓰는 앱이라면 이 혼동의 대가는 이상한 그림 한 장이야. 여기선 더 비싸. 소울엔 보호 규칙이 있고, 사람은 자기 레퍼런스와 함께 등록돼 있고, 모든 이미지는 혈통을 영영 들고 가. 짐작으로 들어간 피사체는 얼굴 하나를 기록에 박아 넣고, 그 기록은 자기 피사체 목록으로 그 얼굴을 누가 원했다고 주장해. 보호 장치는 애초에 그려지면 안 됐던 픽셀을 성실하게 따라가겠지. 부탁한 쪽을 피사체 목록 밖에 두는 덕분에, 그 뒤의 모든 기록을 믿을 수 있어.

Code

cwkAvatars, backend/contracts.py: 요청은 누가 나오는지만 적고 누가 부탁했는지는 안 적어 (원문 그대로, 45-63행)·python
class Generation(Input):
    request_id: str = Field(min_length=1, max_length=100)
    title: str = "Untitled generation"
    workflow: Literal["create", "regenerate", "remix", "studio", "diary", "thumbnail", "clip"] = "studio"
    # Only for workflow "clip"; defaults apply when omitted.
    clip: Clip | None = None
    set_id: str | None = None
    prompt: str = Field(min_length=1)
    original_prompt: str | None = None
    frames: list[str] = Field(default_factory=list)
    references: list[str] = Field(default_factory=list)
    # A depicted subject is a registered ID, optionally with a named look:
    # "dad" resolves Dad's default look, "dad:anime" the look saved under that
    # name. The acting soul is never implied here.
    subjects: list[str] = Field(default_factory=list)
    providers: list[Provider] = Field(min_length=1)
    count: int = Field(default=1, ge=1, le=50)
    aspect_ratio: str = "1:1"
    source_metadata: dict = Field(default_factory=dict)
cwkPippa, backend/services/avatars_client.py: 부탁한 쪽과 피사체는 나오는 데가 달라 (원문 그대로, 65-76행)·python
    metadata = source_metadata or {}
    actor = actor_soul_id or metadata.get("actor_soul_id") or metadata.get("soul_id") or "pippa"
    references = list(reference_asset_ids)
    for path in reference_paths:
        references.append(ingest(Path(path).expanduser().resolve(), actor)["id"])
    batch = request("/internal/generation", {
        "request_id": request_id or str(uuid4()), "actor_soul_id": actor,
        "title": f"Pippa · {workflow}", "workflow": workflow, "prompt": prompt,
        "references": references, "subjects": list(subject_soul_ids),
        "providers": [{"id": provider, "quality": quality, "image_size": None if image_size == "auto" else image_size}],
        "aspect_ratio": aspect_ratio, "count": 1, "source_metadata": metadata,
    })
cwkAvatars, backend/app.py: 생성은 그걸 부탁한 쪽의 것 (원문 그대로, 533-538행)·python
    @app.get("/internal/batches/{id}")
    async def pippa_batch(id: str, actor_soul_id: str):
        record = await studio.io("get", id, "batch")
        if record["request"].get("actor_soul_id") != actor_soul_id:
            raise StudioError("This generation belongs to a different actor.", 403)
        return await batch_detail(id, base="/internal")

External links

Exercise

네 코드에서 호출자의 정체성과 요청 내용이 같이 다니는 API를 하나 찾아봐. 현재 사용자로 글 쓰기, "나한테 할당" 기본값, 현재 계정으로 메시지 보내기 같은 거. 서버가 호출자를 보고 채우는 필드를 전부 적어. 그리고 각각에 권한(누가 해도 되나), 소유(누구 기록이냐), 내용(뭘 만드냐) 중 하나를 붙여. 내용인데 호출자에서 기본값을 가져오는 필드가 있으면 요청에 명시하는 필드로 옮기고, 호출자와 내용이 서로 다른 테스트를 하나 더해.
Hint
Avatars는 부탁한 쪽을 딱 두 군데에만 써. 가시성 검사랑 배치 소유. 쓸 만한 냄새 신호도 하나 있어. 요청에서 호출자를 빼면 만들어지는 결과가 달라진다면, 호출자가 내용으로 새고 있는 거야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.