본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 20. 09:31

내가 두 번째 LLM이 아닌 정규 표현식(Regex)으로 AI 문장을 다듬는 이유

요약

AI 코딩 에이전트가 작성한 텍스트를 인간처럼 자연스럽게 다듬어주는 CLI 도구인 klaussy-agents를 소개합니다. LLM의 불확실한 프롬프트 준수에 의존하는 대신, 정규 표현식(Regex) 기반의 결정론적인 스크러버를 사용하여 AI 특유의 말투를 제거합니다.

핵심 포인트

  • LLM의 프롬프트는 일관성을 보장하지 못하므로 규칙 기반의 사후 처리가 필요함
  • klaussy-agents는 정규 표현식을 사용하여 AI 특유의 문장 구조와 흔적을 제거함
  • 단순 문장 교정을 넘어 어조와 문장 길이까지 제어하는 명세를 포함함
  • 코드 수정 없이 텍스트 출력물만 안전하게 다듬는 방식임

Stephanie Dover, 10년 이상의 경력을 가진 소프트웨어 엔지니어(전 GitHub, Twitch, Microsoft 근무). Klaussy의 제작자 작성.

LinkedIn · GitHub · Klaussy Desktop · Klaussy Agents

요약 (TL;DR)

klaussy-agents는 AI 코딩 에이전트가 작성하는 산문(prose), PR 코멘트, 리뷰 노트, 커밋 메시지가 마치 사람이 작성한 것처럼 읽히도록 만들어주는 무료 MIT 라이선스 CLI (pip install klaussy-agents)입니다. 이 도구는 두 가지 계층으로 작동합니다. 첫째는 에이전트의 기술(skills)에 내장되어 처음부터 깔끔한 문장을 쓰도록 하는 인간화 명세(humanization spec)이며, 둘째는 출력된 결과물을 사후에 다듬는 결정론적인 klaussy humanize 단계입니다. 이 스크러버(scrubber)는 LLM이 아닌 규칙 기반의 정규 표현식(regex)을 사용하며, 코드는 절대 건드리지 않습니다. 또한 시작할 때 예상하지 못했던 부분도 있었습니다. AI 특유의 흔적(tells)이 사라지면 남은 문장이 너무 퉁명스럽거나 길어질 수 있다는 점입니다. 따라서 이 명세는 어조(무례하게 굴지 말 것)와 길이(답장은 한 문장, 리뷰 코멘트는 한 문장에서 다섯 문장 사이)도 함께 처리합니다. 저장소: github.com/steph-dove/klaussy-agents.

문제점

이제 AI가 작성한 글은 눈에 띕니다. 누구나 알아챌 수 있습니다. 그리고 가장 거슬리는 곳은 코드 리뷰 코멘트나 커밋 메시지입니다. 이곳의 문장은 팀원들이 읽는 스레드 내에서 당신의 이름 옆에 놓이게 됩니다.

AI의 특징은 일관적입니다. 가장 큰 특징은 엠 대시(em-dash)입니다. 그 다음으로는 "It's worth noting that…(참고할 만한 점은…)" 또는 "I wanted to point out that…(짚고 넘어가고 싶은 점은…)"와 같은 불필요한 도입부, "Hope this helps!(도움이 되길 바랍니다!)"나 "Let me know if you have questions!(질문이 있으면 알려주세요!)"와 같은 챗봇식 구조(scaffolding), 그리고 could potentially와 같이 중첩된 완곡어법(hedges) 등이 있습니다. 이러한 흔적을 PR에 남기는 에이전트는 봇처럼 보이며, 사람들은 이를 알아차립니다.

가장 명백한 해결책은 모델에게 그렇게 하지 말라고 말하는 것입니다. 프롬프트(Prompt)에 "AI처럼 들리지 마세요"를 추가하고 넘어가면 됩니다. 그것이 도움이 되긴 하지만, 일관성이 없으며 모델을 변경하거나 프롬프트가 표류(drift)하는 순간 조용히 퇴보합니다. 모든 댓글을 수동으로 편집하는 것도 방법이지만, 모든 댓글을 일일이 수동 편집하는 것은 에이전트가 댓글을 작성하게 만드는 목적 자체를 무색하게 만듭니다. 저는 다시 읽어보지 않고도 신뢰할 수 있는 무언가를 원했습니다.

왜 "그저 모델에게 말하는 것"만으로는 충분하지 않았는가

"왜 그냥 프롬프트로 해결하지 않나요?"라는 질문에 대한 솔직한 답변은 이렇습니다. 프롬프트는 요청할 뿐, 강제하지 못하기 때문입니다. 모델은 따르려고 노력합니다. 때로는 완벽하게 따르지만, 때로는 긴 댓글에 다시 em-dash(—)를 끼워 넣기도 하며, 그 흔적이 스레드에 남을 때까지 당신은 이를 알아차리지 못합니다. 프롬프트 준수(Prompt compliance)는 본질적으로 유연합니다. 개선할 수는 있지만, 결코 보장되지는 않습니다.

그래서 저는 프롬프트를 정답 전체로 취급하는 대신, 두 개의 레이어 중 첫 번째 레이어로 취급하기 시작했습니다.

첫 번째 레이어는 프롬프트 측면입니다. 내부적으로 HUMANIZE_BLOCK이라 불리는 단일 공유 인격화(humanization) 블록이 있으며, 이는 klaussy-agents가 제공하는 5개 에이전트 전반에 걸쳐 리뷰(review), PR, 커밋(commit), 설명(explain)과 같은 모든 산문 출력 기술에 치환되어 들어갑니다. 그 안의 규칙은 명확합니다: em-dash나 en-dash 사용 금지, 불필요한 서두(filler openers) 금지, 챗봇 구조(chatbot scaffolding) 금지, 완곡어법(hedges) 축소, 이모지 금지, "Certainly" 사용 금지, 문장 형태의 다양화, 그리고 코드를 절대 재작성하지 말 것. 하나의 사양(spec)을 모든 곳에 적용함으로써, 에이전트가 서로 다른 위치에서 상충되는 지침을 받지 않도록 합니다.

두 번째 레이어는 실제로 강제하는 부분입니다. 에이전트가 글을 쓴 후, 텍스트는 klaussy humanize를 거칩니다. 이는 모델이 지침을 얼마나 잘 따랐는지와 관계없이 높은 신뢰도의 편집을 수행하는 결정론적(deterministic) 통과 과정입니다. 프롬프트는 요청하고, 스크러버(scrubber)는 강제합니다. 어느 레이어 하나만으로는 충분하지 않으며, 이것이 바로 두 개의 레이어가 존재하는 이유입니다.

왜 두 번째 모델이 아닌 결정론적 방식인가

유혹적인 설계 방식은 에이전트의 산문을 "이것을 사람처럼 들리도록 다시 작성하세요"라는 지침과 함께 다른 LLM에 통과시키는 것입니다. 시중에 나와 있는 대부분의 "AI 인격화(AI humanizer)" 도구들이 작동하는 방식입니다. 저는 의도적으로 그렇게 하지 않았습니다.

재작성 모델 (Rewrite model)은 정규 표현식 (Regex)이 결코 해결할 수 없는 어색한 문구들을 고칠 수 있습니다. 하지만 재작성 모델은 무언가를 미묘하게 틀린 방식으로 바꾸어 놓을 수도 있으며, 기술적인 산문 (Technical prose)에서는 이것이 실질적인 위험이 됩니다. 즉, 식별자 (Identifier)를 재작성하거나, 명령어를 망가뜨리거나, 예제 코드를 더 이상 실행되지 않을 때까지 "개선"해 버릴 수 있습니다. 동료가 실제로 조치를 취할 PR (Pull Request) 코멘트에 들어갈 텍스트의 경우, 저는 단순히 엠 대시 (Em-dash) 하나를 제거하려다 새로운 실수를 유발할 수 있는 프로세스를 원하지 않았습니다.

그래서 이 스크러버 (Scrubber)는 규칙 기반의 정규 표현식 (Regex)을 사용합니다. 이러한 선택의 결과는 이 작업에 있어 모두 장점으로 작용합니다. 속도가 빠르고, 비용이 들지 않으며, 네트워크 호출 없이 오프라인에서 실행됩니다. 또한, 신뢰도가 높은 고정된 집합의 치환 작업만 수행하기 때문에 새로운 오류를 유발할 수 없습니다. 문장 구조를 재구성하거나 단락을 의역하지 않습니다. 작고 신뢰할 수 있는 편집 작업 세트만 수행하고 멈춥니다.

작동 방식

수행하는 편집 작업

스크러버는 보수적인 변환 작업 목록을 짧게 수행합니다. 각 작업은 알려진 안전한 수정 방법이 있는 특징적인 사례들입니다:

  • 대시 (Dashes). 산문에서의 엠 대시 (Em-dashes, )와 엔 대시 (En-dashes, )를 쉼표 (Comma)나 하이픈 (Hyphen)으로 변경합니다.
  • 채움 문구 (Filler openers). 문장 시작 부분의 채움 문구 ("It's worth noting that", "I noticed that", "Please note that" 및 유사 문구)를 제거하며, 문장이 여전히 올바르게 읽히도록 다음 단어의 첫 글자를 대문자로 다시 바꿉니다.
  • 챗봇 구조 (Chatbot scaffolding). "Let me know if…", "Hope this helps", "Feel free to…"와 같이 끝에 붙는 문장들을 삭제합니다.
  • 장황한 표현 (Verbose phrasings). 몇 가지 표현을 간결하게 다듬습니다: in order toto로, could potentiallycould로 변경합니다.

다음은 테스트 스위트 (Test suite)가 다루는 정확한 사례들에 대한 전후 비교입니다:

입력 (Input)출력 (Output)
Leaks a connection — wrap it.Leaks a connection, wrap it.
...
Let me know if you have questions!This races on startup.
Refactor in order to avoid the N+1.Refactor to avoid the N+1.
This could potentially deadlock.This could deadlock.

그 모든 것들은 인간 편집자가 생각 없이 적용할 법한 규칙들입니다. 그 중 어느 것도 주석의 의미를 바꾸지 않습니다. 이것이 포함 기준입니다. 만약 수정 사항이 의미를 바꿀 가능성이 있다면, 그 규칙은 목록에 포함되지 않습니다.

코드는 절대 건드리지 않음

개발자 산문 (developer prose)에 어떠한 텍스트 변환 (text transform)을 실행할 때 발생하는 단 하나의 가장 큰 위험은, 그것이 코드 예제에 침투하여 코드를 망가뜨리는 것입니다. 셸 명령 (shell command) 내부의 대시 (dash)를 쉼표 (comma)로 바꾸는 "인간화 도구 (humanizer)"는 당신의 예제를 틀리게 만듭니다.

이 스크러버 (scrubber)는 구조적으로 이를 방지합니다. 입력값을 펜스 코드 블록 (fenced blocks)과 인라인 코드 (inline code)를 기준으로 분할하여, 산문 (prose) 세그먼트만 스크러빙하고 모든 코드 세그먼트는 바이트 단위로(byte-for-byte) 전혀 건드리지 않은 채 남겨둡니다. 명령 내의 대시는 대시로 유지됩니다. 식별자 (identifiers)는 식별자로 유지됩니다.

구체적으로, Use `a — b` then:과 같이 산문과 코드가 섞인 문자열이 있고, 그 뒤에 x — y를 포함한 펜스 블록이 온다고 가정할 때, 인라인 스팬 (inline span) 내부와 펜스 내부의 대시는 작성된 그대로 유지됩니다. 산문에 있는 대시만이 정규화 (normalized)됩니다. 코드는 입력된 그대로, 변경 없이 출력됩니다.

인터페이스

사용 방법에는 두 가지가 있습니다. 라이브러리 (library)로 사용하는 경우:

from klaussy.humanize import humanize

clean = humanize("It's worth noting that this races, fix it.")
...

humanize(text: str) -> str. 문자열이 아닌 입력은 변경 없이 그대로 통과됩니다.

CLI로 사용하는 경우, 파이프 (pipe)나 CI 게이트 (CI gate)에 바로 적용할 수 있도록 설계되었습니다:

# stdin을 stdout으로
printf '%s' "$comment" | klaussy humanize

...

제가 가장 즐겨 사용하는 것은 --check 모드입니다. 이는 "이 문서에 AI의 흔적이 스며들었는가"를 누군가 눈으로 직접 잡아내야 하는 일이 아니라, 풀 리퀘스트 (pull request)를 실패하게 만드는 체크 항목으로 바꿔줍니다.

제품 전반에 걸쳐 공유되는 하나의 사양 (spec)

두 계층(layer) 모두에 있는 규칙, 즉 프롬프트 측 블록(prompt-side block)과 CLI는 하나의 소스인 klaussy 데스크톱 코드베이스의 humanize-comment.js를 충실하게 포팅(port)한 것입니다. (해당 데스크톱 앱은 별개의 제품이며, 이것은 오픈 소스인 klaussy-agents 패키지입니다. 이 언급은 이번이 마지막입니다.) 하나의 표준 구현(canonical implementation)으로부터 포팅한다는 것은 프롬프트 규칙과 스크러버(scrubber) 규칙이 시간이 지나면서 서로 어긋나지 않음을 의미하며, 어떤 파이프라인, CI, 데스크톱 앱 또는 사용자 본인의 스크립트라도 동일한 동작을 수행할 수 있음을 의미합니다.

산문(prose)뿐만 아니라 댓글의 위생(hygiene)

동일한 본능이 한 단계 아래인 코드 리뷰(code review)에서도 나타납니다. 산문의 특징(tells)을 식별하는 것 외에도, 생성된 review 스킬은 코드 내에서 과도하거나 서술적인 댓글(해당 라인이 무엇을 하는지 다시 설명하거나 변경 로그(changelog)처럼 읽히는 종류)을 플래그(flag)로 표시하며, 커밋 가드(commit guard)는 ruff --select ERA를 통해 커밋된 주석 처리된 코드를 차단합니다. 판단이 많이 필요한 부분은 모델이 문맥(context)을 가늠할 수 있는 스킬(skill)에 존재하며, 결정론적인(deterministic) 부분은 훅(hook)에 존재합니다. 이는 두 산문 계층과 동일한 분업 방식입니다. 판단이 필요한 곳에는 질문을 던지고, 확실할 수 있는 곳에는 강제(enforce)하십시오.

깨끗함이 친절함이나 짧음과 동일한 것은 아니다

특징(tells)을 제거하면서 제가 계획하지 않았던 두 번째 문제에 직면했습니다. 채우기(filler)를 제거하면 완곡한 표현(softening)도 함께 제거된다는 점입니다. "이것이 잠재적으로 오류를 삼킬 수 있다는 점을 주목할 가치가 있으며, 이를 감싸는 것이 좋을 수 있습니다"라는 문장에서 핵심 내용만 남기고 스크러빙(scrub)하면 "이것은 오류를 삼킵니다. 감싸세요."가 됩니다. 이는 깔끔하지만, 풀 리퀘스트(PR) 스레드에서는 다소 차갑게 느껴지며, 차가운 말투는 퉁명스럽게 읽힙니다. 리뷰 댓글은 사람의 작업물에 직접 전달되기 때문에 최악의 사례가 될 수 있습니다. 실제 사례를 아주 살짝 순화해 보자면 다음과 같습니다: "개인적으로 이 유닛 테스트(unit tests)들이 유용하다고 생각하지 않습니다. 모든 것을 모킹(mocking)하고 있기 때문입니다." 여기서 문제는 특징(tells)이 아니라 프레이밍(framing)입니다.

따라서 인간화(humanizing)는 순수하게 뺄셈(subtractive) 방식일 수 없습니다. 산문을 기계처럼 들리게 만드는 요소를 제거한 후, 사양(spec)은 다른 모든 것과 마찬가지로 동일한 두 가지 방식으로 배분하여 배려심 있는 인간처럼 들리게 만드는 요소를 다시 추가합니다.

프롬프트 측면(Prompt-side)에서의 예의 수준(civility floor): 작업물을 비판하되 사람은 비판하지 말 것; 단정적인 판결보다는 질문을 선호할 것; 과한 찬사가 아닌 가벼운 터치를 유지할 것. 이것은 최소한의 기준(floor)이지 강요된 온정(forced warmth)이 아닙니다. 따라서 직설적인 리뷰를 요청했다면 그 직설적인 성격은 유지되되, 단지 모욕적인 수준으로 넘어가지만 않으면 됩니다. 두 번째 규칙은 답변(replies)에 적용됩니다: 답변할 댓글의 내용은 읽되 감정(temperature)은 읽지 말 것, 그리고 초안을 작성하기 전에 그 무례함을 중화(neutralize)시켜서 적대적인 스레드가 적대적인 답변을 유도하지 않도록 할 것. 그리고 "간결하게 작성하세요" 대신 실제 숫자를 사용하는 간결함 규칙(brevity rule): 스레드 답변은 한 문장을 목표로 하고, 단일 리뷰 댓글은 한 문장에서 다섯 문장 사이로 하며, 이보다 길어지면 요약하는 것이 아니라 잘라냅니다.

결정론적 측면(Deterministic-side)에서, 스크러버(scrubber)의 시작 문구 제외 목록(opener list)은 무시하는 듯한 읽기 방식을 유도하는 편집자적 도입부들을 잡아내기 위해 확장되었습니다: "개인적으로(Personally)", "솔직히(Honestly)", "솔직히 말해서(Frankly)", "제 생각에는(IMO/In my opinion)", "제 의견을 물으신다면(If you ask me)" 등입니다. 따라서 Personally I don't find these useful.은 나머지 부분과 동일한 보장 하에 I don't find these useful.이 되며, 코드 내부에서는 여전히 절대 수정되지 않습니다.

솔직한 주의사항: 어조(tone)와 간결함은 대부분 판단의 영역이므로, 대부분은 소프트 프롬프트 계층(soft prompt layer)에 존재하며 오직 시작 문구(openers)만이 보장됩니다.

빠른 데모

이를 확인하는 가장 명확한 방법은 여러 가지 특징이 겹쳐 있는 댓글을 파이프(pipe)로 전달하는 것입니다:

printf '%s' "It's worth noting that this handler swallows the error, wrap it. Hope this helps!" \
 | klaussy humanize

도입부가 제거되고, 다음 단어의 첫 글자가 대문자로 재설정되며, 엠 대시(em-dash)는 쉼표가 되고, 마지막의 부수적인 문구(scaffolding line)는 삭제됩니다. 결과물은 엔지니어가 남긴 메모처럼 읽히는데, 이는 봇처럼 보이게 만들었던 요소들은 사라졌고 그 외의 다른 것은 전혀 건드리지 않았기 때문입니다.

전체 과정은 순수하게 표준 라이브러리(standard library)로만 이루어져 있으며, 네트워크나 모델을 사용하지 않습니다. 테스트 스위트(test suite)는 데스크톱 테스트 스위트에서 포팅된 위 모든 변환 작업과 코드 보존 사례를 포함하며, 총 137개의 테스트를 통과합니다.

다음 단계, 그리고 경계선

의도적인 제한은 핵심적인 트레이드오프 (tradeoff)입니다. 결정론적 (deterministic)이라는 것은 곧 제한적이라는 의미입니다. 정규 표현식 (regex) 스크러버 (scrubber)는 신뢰할 수 있는 특징들을 잡아내지만, LLM이 할 수 있는 방식으로 정말 어색한 문구들을 다시 작성하지는 못합니다. 그것이 제가 의도적으로 선택한 트레이드오프이며, 이 도구를 안전하고 빠르며 오류를 도입할 수 없게 만드는 바로 그 특성이, 더 깊은 재작성을 수행하지 못하게 만드는 특성이기도 합니다. 만약 패러프레이징 (paraphrasing, 의역)을 원한다면, 이 도구는 그것을 위한 도구가 아니며, 그렇게 되려고 노력하지도 않습니다.

몇 가지 다른 솔직한 한계점들은 다음과 같습니다:

  • 프롬프트 레이어 (prompt layer)는 유연합니다. {{HUMANIZE}} 블록은 모델이 지침을 따르는지에 달려 있습니다. 두 개의 레이어를 사용하는 이유는 어느 하나만으로는 충분하지 않기 때문입니다.
  • 주관적입니다. 어떤 사람들은 엠 대시 (em-dash)를 좋아합니다. 스크러버는 이를 정규화 (normalize)하며, 이는 하나의 입장입니다. 또한 오픈 코드 (open code)이므로, 동의하지 않는다면 규칙을 바로 수정할 수 있습니다.
  • 범위는 산문 (prose)이며, 코드가 아닙니다. 설계상 코드 블록 (code block) 내부의 어떤 것도 건드리지 않습니다. 반대로 말하면, 별도로 연결하지 않는 한 코드 주석 (code comment) 안에 있는 특징을 수정하지도 않습니다.

이 중 그 어느 것도 제가 숨기려는 것이 아닙니다. 이것들은 팀원들 앞에 나갈 텍스트를 위해 "영리하고 위험한" 것 대신 "안전하고 예측 가능한" 것을 선택했을 때 따르는 결과입니다.

사용해 보기

pip install klaussy-agents
printf '%s' "It's worth noting that this races, fix it. Hope this helps!" | klaussy humanize

리포지토리 (Repo) 및 문서: github.com/steph-dove/klaussy-agents

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0