agenttap: API 키를 마스킹하여 LLM SDK가 실제로 전송한 내용을 정확히 확인하기
요약
LLM SDK가 실제로 전송하는 페이로드를 정확히 확인하기 위한 디버깅 도구인 agenttap을 소개합니다. API 키 유출 없이 네트워크를 통해 전달되는 실제 요청 본문을 마스킹하여 안전하게 검사할 수 있습니다.
핵심 포인트
- SDK의 내부 재구성(reshaping)으로 인한 디버깅 문제를 해결
- API 키를 자동으로 마스킹하여 보안 유지
- httpx 트랜스포트 방식으로 간편하게 설치 및 사용
- 두 요청 간의 페이로드 차이점을 비교하는 diff 기능 제공
지난달 저는 단 한 단어로 설명 가능한 버그 때문에 오후 전체를 허비했습니다. SDK의 두 마이너 버전 사이에서 system이 system_prompt로 바뀌었는데, 저의 재시도(retry) 경로가 메시지 딕셔너리(dict)를 이전 방식으로 생성하고 있었던 것입니다.
에이전트(agent)는 괜찮아 보였습니다. 트레이스(traces)도 괜찮아 보였습니다. Anthropic으로부터 받은 에러 메시지는 일반적인 400 에러였습니다. 로그 라인을 추가했습니다. 로그 라인을 더 추가했습니다. 메시지 딕셔너리를 출력했습니다. 딕셔너리는 올바르게 보였습니다. 딕셔너리는 올바랐습니다. SDK가 전송 과정에서 이를 재구성(reshaping)하고 있었던 것입니다.
이 문제를 5분 만에 잡아낼 수 있었던 유일한 방법은 제공자(provider)가 실제로 네트워크(wire)를 통해 무엇을 받았는지 확인하는 것이었습니다. 그것이 바로 agenttap이 하는 일입니다.
문제점
SDK 시대가 된 지 5년이 지났지만, "모델에 실제로 무엇이 전송되었는가?"는 여전히 어려운 질문입니다.
SDK 디버그 로깅(debug logging)은 장황하고, API 키를 터미널 스크롤백에 유출하며, 전송 중에 페이로드(payloads)를 재구성합니다. 콜백(Callbacks)은 벤더(vendor)별 추상화 계층에 흩어져 있으며, 무엇이 "요청(request)"인지에 대해 일치된 정의를 내리지 못합니다. 모든 사람이 찾는 두 줄짜리 해결책인 httpx.Client(event_hooks=...)는 Request 객체를 제공하지만, Authorization 헤더를 가려주지는(redact) 않습니다.
결국 요청 본문(request body)이 어디서 직렬화(serialized)되는지 알아내기 위해 SDK 소스 코드를 읽게 됩니다. 그러고 나서 메서드(method)를 패치합니다. 그러고 나서 메서드를 패치했다는 사실을 잊어버립니다. 그러다 새로운 SDK 버전이 메서드 이름을 변경하면, 당신의 디버그 접착제(debug glue)는 소리 없이 망가집니다.
해결 방식
agenttap은 httpx 트랜스포트(transport)로 설치됩니다. 이를 클라이언트(client)에 전달하면 됩니다. 모든 호출이 이를 통과합니다. 자격 증명(Credentials)은 들어오는 과정에서 마스킹(scrubbed)됩니다. 정확한 요청 본문은 메모리에 남아 있어 호출 후에 검사할 수 있습니다.
import httpx
import anthropic
from agenttap import Tap
...
OpenAI도 동일한 방식으로 작동합니다:
import httpx
import openai
from agenttap import Tap
...
두 호출이 왜 다른 결과를 생성했는지 알고 싶을 때는, 차이점(diff)을 비교하세요:
from agenttap import diff
print(diff(tap.all[0], tap.all[1]))
...
그 diff 호출이 저의 허비된 오후를 구해줬을 것입니다.
이것이 하지 않는 것
- 프록시(Proxy)가 아닙니다. 서버도 아닙니다. 배포할 것이 아무것도 없습니다.
- 제공자(Provider) 간에 데이터를 정규화(Normalize)하지 않습니다. 핵심 목적은 각 제공자가 실제로 무엇을 받았는지 보여주는 것입니다.
- 데이터를 영구적으로 저장(Persist)하지 않습니다.
tap.all은 메모리 상에 존재합니다. 기록을 남기고 싶다면 직접 JSON으로 저장하세요. - 완전한 관측성(Observability)을 제공하는 것은 아닙니다. 트레이스(Trace)나 대시보드가 필요하다면, 기록된 호출 내용을 Phoenix, Langfuse 또는 OpenTelemetry로 전송하세요.
라이브러리 내부 (보여줄 만한 하나의 설계 선택)
agenttap은 두 곳에서 정보를 마스킹(Redact)하며, 두 곳 모두 중요합니다.
헤더(Headers)는 이름별로 삭제됩니다. 목록은 작고 명시적입니다: Authorization, x-api-key, api-key, cookie, anthropic-api-key, openai-organization, x-amz-security-token, x-google-api-key. 이것은 지루한 계층입니다. 대부분의 자격 증명(Credential) 유출은 여기서 발생하며, 고정된 목록을 통해 깔끔하게 문을 닫아버립니다.
본문(Body)의 문자열 값은 알려진 자격 증명 형태에 대해 정규 표현식(Regex)을 사용하여 삭제됩니다: OpenAI 및 Anthropic의 sk-…, AWS의 AKIA…, Google의 AIza…, Slack의 xox[baprs]-… 등입니다. 이 계층은 요청 본문에 있어서는 안 되지만, 어떤 헬퍼(Helper)가 그곳에 넣어버려 어떠한 방식으로든 포함되어 있는 자격 증명을 잡아냅니다.
from agenttap import Tap, Redactor
# 기본값: 헤더 + 본문 내 알려진 자격 증명 패턴 삭제
...
이 설계가 중요한 이유: 처음으로 탭(Tapped)된 요청을 Slack 스레드에 복사하여 팀원에게 도움을 요청하고 싶을 때, 스크린샷에 내 키가 포함되어 있는지 머릿속으로 계산할 필요가 없습니다. 기본 설정이 이미 이를 제거했기 때문입니다.
이것이 유용한 경우
- LLM 제공업체로부터 "맞아 보이는데 400 에러가 발생함"과 같은 오류를 디버깅 중이며, 네트워크(wire)로 전송된 정확한 바이트(bytes)를 확인해야 할 때
- SDK 버전 간 마이그레이션 중이며, 요청 형태(request shape)가 조용히 변경되지 않았는지 확인하고 싶을 때
- Slack 게시물이나 버그 리포트를 작성하면서, API 키를 유출하지 않고 실제 요청 본문(request body)을 붙여넣고 싶을 때
- 두 가지 프롬프트 변형을 비교 중이며, 애플리케이션 레벨이 아닌 네트워크(wire) 레벨에서의 차이(diff)가 필요할 때
- 요청 생성(request building)을 수정하는 팀원의 PR(Pull Request)을 검토하면서, "이게 실제로 무엇을 전송하는가?"에 대한 빠른 무결성 검사(sanity check)를 하고 싶을 때
이것이 유용하지 않은 경우
- 대시보드를 통한 장기적인 관측성(observability)이 필요한 경우. Phoenix, Langfuse, Helicone 또는 OTel(OpenTelemetry) collector를 사용하세요. agenttap은 디버그 루프(debug loop)를 위한 도구이며, 프로덕션 대시보드용이 아닙니다.
- 모든 프로세스의 모든 호출 앞에 위치하는 프록시(proxy)가 필요한 경우. agenttap은 클라이언트별 인프로세스(in-process) 방식으로 작동합니다.
- 내부적으로 httpx를 사용하지 않는 SDK를 사용하는 경우. 전송 훅(transport hook)이 적용되지 않습니다.
설치
pip install agenttap
Repo: https://github.com/MukundaKatta/agenttap
형제 라이브러리
| 라이브러리 | 역할 |
|---|---|
| agentsnap | 에이전트 실행에 대한 스냅샷 테스트 (Snapshot tests) |
| ... |
agenttap은 디버그 루프(debug-loop) 도구입니다. 다른 것들은 프로덕션 도구입니다. 이들은 서로 결합됩니다: 개발 중에는 호출을 탭(tap)하고, CI에서는 스냅(snap)하며, 프로덕션에서는 추적(trace)합니다.
다음 단계
캡처된 호출을 OpenTelemetry 스팬(spans)으로 내보내는 작은 TapServer 어댑터를 추가하고 싶습니다. 그러면 개발 중에 탭한 동일한 호출이 프로덕션에서 사용하는 동일한 대시보드로 흐를 수 있습니다. 또한 테스트에서 네트워크 형태(wire shape)를 고정할 수 있도록 "저장된 JSON과 비교"하는 어설션(assertion) 기능도 추가하고 싶습니다.
만약 SDK의 유령 같은 변경 사항 때문에 오후 시간을 통째로 날려본 적이 있다면, 왜 이것이 존재하는지 이미 알고 계실 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기