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

cwkPippa를 거쳐, 읽기 슬롯 위에서

~14 min · integration, vision, http, brains

Level 0밑준비
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Sizzle은 모델 호출을 하나도 안 가져

backend/pippa.py 첫머리 몇 줄이 경계를 그어. 대화도, 턴도, 모델 호출도 전부 cwkPippa 거야. Sizzle의 스레드는 cwkPippa 대화에 묶여(트랙 6). 읽기는 다른 문으로 가. 이미지를 싣는 apply-free 유틸리티 레인이고, Forge가 해석할 때 쓰는 바로 그 레인이야. HTTP 요청 하나가 나가고, 답 하나가 돌아오고, 대화는 열리지 않아. 이 호출을 하는 함수가 read_plate고, 길이는 29줄이야.

요청 본문, 필드 하나씩

필드값쓰임
instruction루브릭레슨 1의 계약서
input_text데이터로 넘기는 접시접시, 아빠의 수정, 아빠의 트리
brain, effort, model읽기 슬롯이 정해준 것몸이 될 두뇌. 기능별로 정하고, Sizzle 코드가 고르는 일은 없어
persona_soul_id"pippa"누가 말하는지. 내 영혼이 이 일 앞에 서
task_mode"respond"입력을 고쳐 쓰지 말고 입력에 답하라는 뜻
imagesbase64 JPEG접시 사진 전부, 엔진의 화면용 사본으로

이미지는 엔진이 직접 만든 display 사본이야. 똑바로 세우고, 긴 변을 1600픽셀 이하로 줄이고, EXIF도 XMP도 없이 저장한 거. 그래서 카메라 GPS는 Sizzle 저장소의 원본 곁에 남아. 그래도 따라 나가는 묶음이 하나 있어. Pillow가 JPEG 주석은 사본에 그대로 옮기거든. 편집 앱이 거기 장소를 적어 뒀다면 그것도 같이 가(트랙 5). 접시 사진은 한 장도 빠짐없이 같이 가. 엔진 테스트 하나가 24장을 보내고, 24장이 순서대로 다 도착하는지 확인해.

함수 끝은 검사 두 개로 닫혀. cwkPippa는 실패를 HTTP 200에 error 필드를 붙여 답할 때가 많아. 그래서 read_plate는 그 필드가 있으면 예외를 던져. 결과가 비어 있어도 실패야. Pippa returned an empty reading, 피파가 빈 읽기를 돌려줬다는 오류가 나. 그 밑에선 벤더링한 킷이 /api/admin/utility-macros/apply-free로 요청을 보내. 타임아웃은 300초야. 네트워크가 끊기면 cwkPippa에 닿지 못했다는 오류로, 200이 아닌 상태면 상태 코드랑 cwkPippa의 설명을 담은 오류로 바꿔. 이게 전부 예외 타입 하나로 올라오고, 레슨 6의 워커가 그걸 기록해.

cwkPippa가 받아서 하는 일

핸들러는 지시가 비어 있으면 거절하고, 이미지를 데이터랑 미디어 타입으로 나누고, 프롬프트를 렌더링해. task_mode가 respond면 direct_response_free_prompt 템플릿을 골라. 이 템플릿의 기본값은 루브릭을 지시 자리에 두고, 접시 데이터는 <<<INPUT에서 INPUT까지 이어지는 울타리 안에 넣어. 루브릭 마지막 규칙이랑 이 울타리는 문 하나에 채운 자물쇠 두 개야. 데이터는 울타리에 갇혀 들어오고, 지시는 울타리 안쪽이 절대 지시가 아니라고 말해.

이어서 호출은 선택된 몸으로 가. 두뇌 이름이 있거나, 이미지가 있거나, respond 모드면 그리로 가는데, Sizzle 요청엔 셋 다 있어. 이미지 쪽 이유는 코드 주석이 직접 말해. cwkPippa의 로컬 유틸리티 모델은 텍스트만 읽거든. 시스템 프롬프트는 두 층으로 쌓여. respond 틀이 두 번째 층이야. 그 앞에 내 영혼의 vault 기반 프롬프트가 서는데, persona_soul_id가 pippa고 Sizzle이 vault 맥락을 기본값 그대로 두기 때문이야. 채팅에서 내 정체성을 세우는 것과 같은 로더 경로야.

두 축: 누가 말하고, 뭐가 돌아가나

이 갈라짐은 Pippa Quest의 Soul 트랙이 영혼과 두뇌, 서로 기대지 않는 두 축으로 가르치는 바로 그거야. 영혼은 누가 말하느냐고, 읽기에선 언제나 나야. 두뇌는 어느 몸이 호출을 돌리느냐고, Sizzle 코드는 그걸 고르지 않아. 가족 Brain Control Plane에 읽기 슬롯을 물어볼 뿐이야. 슬롯 이름표는 Plate reading, 접시 읽기고, 설명엔 이미지를 볼 수 있는 두뇌가 필요하다고 적혀 있어. 이 슬롯을 처음 등록할 땐 초깃값으로 Claude, 강도 high를 넣어. 그 뒤로는 가족 설정 값만이 진실이야. 아빠는 Sizzle 설정 페이지에서도, Firelink의 Brains 탭에서도 그 값을 바꿀 수 있어. 두 화면이 같은 줄을 고치거든. 기록에 남는 건 배정이지, 영수증이 아니야. reading.recorded 이벤트는 호출 전에 슬롯이 워커한테 건넨 두뇌, 강도, 풀린 모델을 읽기 옆에 저장해. cwkPippa의 답엔 글만 있는 게 아니야. 선택된 몸 경로는 실제로 답한 모델도 execution 블록에 담아 돌려줘. 그런데 read_plate는 result만 챙기고 나머지는 버려. 그래서 저널은 가족이 어느 몸을 불렀는지는 말해도, 어느 모델이 답했는지는 증명 못 해.

원칙: 영혼이랑 몸은 따로 된 매개변수로 둬. 그러면 어떤 모델이 도느냐가 바뀌어도 누가 말하느냐는 안 바뀌고, 기록은 늘 어느 몸을 불렀는지 알려줄 수 있어. 어느 몸이 답했는지까지 말하려면, 게이트웨이가 돌려주는 영수증도 같이 챙겨.

왜 루프백일까

엔진은 서비스 호스트에서 cwkPippa 옆에 살고, 가족 레지스트리가 준 루프백 주소로 cwkPippa를 불러. 이유는 신뢰야. 루프백은 cwkPippa가 PIN 없이 믿어주는 경계라서, Sizzle의 읽기 코드엔 자격 증명이 하나도 없어. 같은 요청이 테일넷 쪽에서 오면 401 pin_required가 돌아와. PIN이 필요하다는 뜻이야. 루프백은 macOS 26의 로컬 네트워크 관문 밖에 있기도 해. 그래서 백그라운드 서비스는 대답할 수도 없는 동의 창을 거치지 않고 거기 닿아.

Code

cwkSizzle, backend/pippa.py: read_plate, 슬롯이 정한 두뇌로 하는 읽기 한 번 (원문 그대로, 107-135행)·python
async def read_plate(
    instruction: str,
    input_text: str,
    images: list[bytes],
    *,
    brain: str,
    effort: str = "",
    model: str | None = None,
) -> str:
    """One reading on the brain the reading slot resolved to."""
    payload: dict[str, Any] = {
        "instruction": instruction,
        "input_text": input_text,
        "brain": brain,
        "persona_soul_id": "pippa",
        "effort": effort or None,
        "model": model,
        "task_mode": "respond",
        "images": [
            {"data": base64.b64encode(data).decode("ascii"), "media_type": "image/jpeg"} for data in images
        ],
    }
    reply = await kit_pippa.utility_apply_free(settings.PIPPA_BASE, payload)
    if reply.get("error"):
        raise PippaError(str(reply["error"]))
    result = str(reply.get("result") or "").strip()
    if not result:
        raise PippaError("Pippa returned an empty reading")
    return result
cwkPippa, backend/routes/utility_macros.py: respond 모드가 프롬프트 템플릿을 고르는 곳 (원문 그대로, 417-431행)·python
def _render_free_prompt(
    instruction: str,
    input_text: str,
    task_mode: Literal["transform", "respond"] = "transform",
) -> str:
    prompt_key = (
        "direct_response_free_prompt"
        if task_mode == "respond"
        else "chat_input_free_prompt"
    )
    return utility_prompts.render(
        prompt_key,
        instruction=instruction.strip(),
        input_text=input_text,
    )
cwkPippa, prompts/utility/originals.json: 그 템플릿의 기본값, 루브릭과 접시 데이터가 들어가는 자리 (원문 그대로, 10행)·text
  "direct_response_free_prompt": "Follow the instruction and answer the latest input directly.\n\nInstruction:\n{instruction}\n\nLatest input:\n<<<INPUT\n{input_text}\nINPUT",

External links

Exercise

앱에 있는 모델 호출 하나를 read_plate처럼 함수 하나로 감싸봐. 지시, 데이터, 이미지, 페르소나, 몸(두뇌, 강도, 모델)을 각각 따로 된 매개변수로 받게 해. 실패는 종류와 상관없이 예외 타입 하나로 던지게 해. 전송 실패, 200이 아닌 상태, 200 답 안의 오류 필드, 빈 결과까지 전부. 마지막으로 결과마다 두 가지를 따로 저장해. 부른 몸, 그리고 제공업체 답이 실제로 답했다고 밝힌 몸.
Hint
쓰는 제공업체나 게이트웨이가 페르소나나 시스템 층을 모델 선택과 따로 받는다면, 그 둘을 끝까지 다른 인자로 둬. 그래야 설정을 바꿀 때 한쪽만 바꿀 수 있어. 이미지는 원본 메타데이터를 뺀 사본으로, 모델이 봐야 할 게 남는 선에서 가장 작게 보내. 인코더가 알아서 옮기는 것도 확인해. Pillow는 빈 주석을 넘기지 않으면 JPEG 주석을 그대로 남겨.

Progress

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

댓글 0

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

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