본문 바로가기
C.W.K.
Stream
Lesson 03 of 07 · published

정리 계약

~12 min · cleanup-contract, insertion, safety, provider

Level 0불씨 없음
0 XP0/48 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"삽입 계층은 건네받은 게 뭐든 타이핑해. 그러니 챗봇이 쓴 문장은 절대 건네지 마."

변형이지 대화가 아냐

정리는 엄격한 변형이야. 원시 전사가 들어가고, 다듬어서 바로 삽입할 수 있는 텍스트가 나와. 전사에 답을 하거나, 내용을 보태거나, 한마디 얹는 건 절대 안 돼. "은행에 전화하라고 알려줘" 라고 받아쓰면 정리는 "은행에 전화하라고 알려줘." 를 돌려줘야 해. 문장부호만 찍어서. "물론이죠, 알려드릴게요!" 가 아니라. 프로바이더가 받는 요청은 작아. 원시 전사, 컨트롤러가 짜 준 지시문(정리 강도, 톤을 고를 대상 앱 분류, 사전에서 온 선호 철자, 그리고 맥락 인식이 켜져 있고 보안 필드가 아닐 때만 근처 텍스트), 그리고 언어. 그 언어는 음성 엔진이 알려 준 거야. WhisperKit 이면 감지한 언어고, SpeechAnalyzer 면 로케일이고. 돌려주는 건 정리된 텍스트, 그거 하나야.

돌아온 걸 믿지 말고 재

삽입 계층은 설계상 멍청해. 건네받은 게 뭐든 라이브 앱에 타이핑해 버려. 정리가 자유 산문을 건네면 안 되는 이유가 정확히 이거야. 정리 브레인이 "정리된 텍스트예요: …" 나 "무슨 뜻인지 모르겠어요" 를 돌려주면 그게 유저 문서에 그대로 찍혀. Firekeeper 의 답은 일부러 좁아. 프로바이더 반환 타입은 텍스트 하나야. 지시문 끝엔 단단한 줄들이 붙어. 전사는 정리할 받아쓰기지 대답할 질문이 아니다, 정리된 텍스트만 돌려줘라, 따옴표도 해설도 펜스도 없이. 그리고 모델은 어떤 지시든 무시할 수 있으니까, 출력은 어디로 가기 전에 재. DictationCleanupGuard 가 전사 길이에 그 절반(최소 40자)을 더한 것보다 긴 받아쓰기 정리를 거부해. 정리는 단어를 빼는 거니까, 새어 나온 추론 한 문단은 그 한도에 걸려. 다만 짧은 서두 한 줄은 통과할 수 있어. 거부된 결과는 결정론적 정리로 내려가. 프롬프트도 가드도 보장은 아니야. 둘이 같이 있어야 흔한 실패가 눈에 띄게 드러나. 아예 모델한테 안 맡기는 것도 둘 있어. 사전 치환은 모든 레인 뒤에 코드가 강제하고, 말로 한 '엔터 쳐' 는 브레인이 정하는 게 아니라 컨트롤러가 찾아서 떼어 내.

CleanupRequest  { rawTranscript, instruction, language?, targetAppName? }
        |
        v   (레인: Pippa Full / Pippa 유틸리티 워커 / 로컬 빠름 / Local Mini)
        v
String          정리된 텍스트 — 그 밖의 건 유효한 반환값이 아님
        |
        v   DictationCleanupGuard: 전사보다 허용치 넘게 길어? -> 거부
        v   결정론적 후처리: 사전 치환 강제
        v
Insertion       받아들인 텍스트만 타이핑 (엔터 쳐: 컨트롤러가 감지)

약한 모델 길들이기

어려운 케이스는 로컬 모델이야. 작은 Ollama 모델은 답을 마크다운 펜스로 감싸거나, 서두를 붙이거나, 자기 추론을 늘어놓기도 해. 레인마다 저마다의 방식으로 그걸 길들여. Local Mini 프로토콜은 {"text": "..."} 를 달라고 하고, 모델이 그걸 무시하면 평문도 받아 줘. 펜스는 벗겨 내고. 빠른 레인은 아예 봉투 없이 평문만 달라고 해. 작은 모델이 틀릴 형식이 하나 줄어드니까. 길이 한도에 잘린 답은 통째인 척 삽입하지 않고 거부해. 목표는 모든 레인에서 같아. 난장판을 삽입까지 못 가게 하는 것. 솔직한 한계도 같아. 짧은 엉뚱한 문장 하나는 여전히 빠져나갈 수 있고, 커맨드 레인엔 길이 가드가 아예 없어. 만들어 낸 답은 말로 한 지시보다 길어도 맞는 거니까.

되돌릴 수 없는 곳으로 흘러가는 건 양쪽 끝을 다 지켜. 어떤 컴포넌트의 출력이 되돌릴 수 없는 데로 간다면, 그러니까 앱에 타이핑되거나, 기기로 전송되거나, 디스크에 쓰인다면, 입력은 좁게 두고, 출력은 나와도 되는 모양에 대 보고 재. 반환 타입을 좁히고 측정을 하나 더 얹어야 해. 그래야 수다스럽거나 헷갈린 모델이 사고를 쳐도, 받은 걸 그냥 믿는 아래 계층까지 번지지 않아.
정리 브레인이 유저한테 답하게 절대 두지 마. 제일 중요한 정리 규칙은 변형만 하고 응답은 안 한다는 거야. 받아쓴 질문을 정리하는 대신 '친절하게' 답해 버리는 모델은 자기 답을 네 이메일에 타이핑해. 뜻은 그대로 두고, 표기만 고치고, 돌려줘. 내용 보태기 없고, 답장 없고, 되묻기 없어.

Code

좁은 요청, 텍스트뿐인 반환, 그리고 삽입 전의 측정·swift
struct CleanupRequest: Codable {
    let rawTranscript: String
    let instruction: String       // 컨트롤러가 짬: 강도, 톤, 용어, 근처 텍스트
    let language: String?         // WhisperKit 이 감지한 언어, 아니면 SpeechAnalyzer 로케일
    let targetAppName: String?
}

protocol CleanupProvider {
    var connectionMode: PippaConnectionMode { get }
    func cleanDictation(_ request: CleanupRequest) async throws -> String
    func transformSelection(_ request: TransformRequest) async throws -> String
}

// 모델이 뭘 돌려주든 삽입이 보기 전에 재.
func acceptedDictationText(_ text: String?, raw: String) -> String? {
    guard let text, !text.isEmpty else { return nil }
    guard DictationCleanupGuard.accepts(text, raw: raw) else { return nil }  // 너무 불어남
    return text
}
// nil -> 결정론적 바닥; 통과 -> DeterministicCleaner.postPass (사전이 이김)

External links

Exercise

모델 출력이 네 앱의 되돌릴 수 없는 동작으로 이어지는 기능을 하나 잡고 결과 타입을 설계해봐. 아래쪽 계층이 필요로 하는 필드를 나열하고, 모델이 포맷을 무시하고 수다스러운 산문을 돌려줄 때 네 어댑터가 뭘 하는지 한 문장으로 써.
Hint
아래쪽 계층은 자기가 굴러가는 데 필요한 필드만 딱 필요해. 그 이상은 아니고. 모델이 잘못 굴면 어댑터가 정리해 줘야지. 수다를 벗기고, 쓸 수 있는 부분만 뽑고, 유효한 결과 모양으로 감싸는 거야. 되돌릴 수 없는 동작이 검증 안 된 원시 모델 텍스트를 절대 못 받게.

Progress

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

댓글 0

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

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