루프는 그대로, 도구만 늘어났다
"루프는 한 줄도 안 바꿨어요. 그냥 TOOLS 배열에 추가했을 뿐이에요." — s02_tool_use.py 원문.
도구 하나를 추가하려면 어디를 건드려야 하나?
S01의 agent는 bash만 사용할 수 있습니다. read_file / write_file / edit_file도 추가하려면 어떻게 할까요?
대부분의 첫 반응은 "루프를 수정한다"는 것입니다. 틀렸습니다. 루프는 한 줄도 바꿀 필요 없습니다. 세 가지만 하면 됩니다:
- Python handler 함수를 작성합니다 (
run_read(path, limit)). TOOL_HANDLERS매핑 테이블에 등록합니다 ("read_file": lambda **kw: run_read(...)).TOOLS배열에 JSON schema 선언을 추가합니다 (모델에게 도구 이름과 허용 파라미터를 알립니다).
루프가 tool_use 블록을 만나면 block.name으로 dispatch map을 조회해 함수를 찾고, 실행하고, 출력을 tool_result에 담아 반환합니다. bash와 완전히 동일한 경로입니다.
# Dispatch map: name → handler lambda TOOL_HANDLERS = { "bash": lambda **kw: run_bash(kw["command"]), "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")), "write_file": lambda **kw: run_write(kw["path"], kw["content"]), "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]), }
dispatch가 어떻게 라우팅하는지 보기
아래 widget에서 모델이 보낼 수 있는 tool_use 요청을 클릭해, 어떻게 구체적인 Python 함수로 라우팅되는지 확인하세요. 핵심은 block.name이 어느 분기로 갈지를 결정한다는 것입니다.
safe_path: 생략할 수 없는 방어선
agent에 파일 접근 권한을 줄 때 보안상 가장 위험한 것은 경로 이탈입니다: 모델이 /home/user/project/ 안에서 작업해야 하는데 read_file("../../etc/passwd")를 보내는 경우입니다.
s02에는 이것을 전담하는 작은 함수가 있습니다:
def safe_path(p: str) -> Path: path = (WORKDIR / p).resolve() # 정규화, .. 와 심볼릭 링크 해석 if not path.is_relative_to(WORKDIR): raise ValueError(f"Path escapes workspace: {p}") return path
핵심은 .resolve() + .is_relative_to() 두 단계입니다—절대 경로로 변환한 다음 샌드박스 안에 있는지 확인합니다. 전자 없이는 foo/../../etc 같은 경로가 통과되고, 후자 없이는 검사 자체를 하지 않는 셈입니다.
경로 안전성 판별
아래 5개 경로는 모두 모델이 보낼 수 있는 read_file 파라미터입니다. safe_path가 어떤 것을 통과시키고 어떤 것을 차단할까요? WORKDIR = /home/user/project로 가정하세요.
agent에 위험한 도구를 넣지 마세요
도구 추가는 간단하지만, 기억하세요: 추가하는 도구 하나하나가 모델의 새로운 능력 경계입니다. s02에서 bash는 블랙리스트(rm -rf /, sudo, shutdown)가 있고, write_file은 safe_path로 제한됩니다. production에 도구를 추가하기 전에 세 가지를 물어보세요:
- 이 도구로 모델이 되돌릴 수 없는 일을 할 수 있나요? (rm, 이메일 발송, git push)
- 무엇을 유출할 수 있나요? (env vars, .ssh, cookies)
- 오류 출력이 명령으로 다시 주입될 수 있나요? (tool output을 통한 prompt injection)
s07 강의에서 이런 결정을 코드 밖으로 꺼내 선언적으로 만드는 "권한 레이어"를 다룹니다.