지난 9월에 저는 저희 프레임워크인 Kumiko에서 AI 에이전트에 대해 글을 작성했고, 설명(description)이 없는 핸들러가 왜 에이전트의 도구 목록에 나타나지 않는지에 대해서도 다루었습니다: 문서화되지 않은 핸들러는 에이전트에게 존재하지 않는다. 독자 두 분, phongdesigns와 anp2network가 제가보다 더 주의 깊게 게시글을 읽어주셨고, 그 아래 스레드는 사흘 동안 열한 개의 댓글로 이어졌습니다.
이 스레드를 통해 저희 에이전트가 어떤 행동을 할지 결정하는 방식에 세 가지 변화가 생겼습니다. 저희는 아이디어 두 개를 채택하지 않았으며, 아직 답변하지 못한 질문 세 가지가 남아 있습니다. 이 게시글은 그 모든 것을 다루는데, 왜냐하면 열린 질문들이 제가 도움을 받고 싶은 부분이기 때문입니다.
2문장으로 설명하는 설정 방식
Kumiko 앱의 모든 쓰기 핸들러(write handler)는 에이전트 위험도(low, mid 또는 high)를 가질 수 있으며, 사용자는 중간 위험도의 도구를
후속 코멘트에서 커밋 후 훅(after-commit hooks), 작업(jobs) 및 이벤트 소비자(event consumers)를 공백으로 언급했습니다. 그 이후로 해당 컨텍스트 내에서 디스패치되는 쓰기 작업들은 여전히 동일한 검사를 거치며, 크론 정리(cron cleanups)나 고위험 핸들러에 의해 시작된 파이프라인과 같은 의도적인 예외 목록을 가지고 있습니다.
변경 2: 도구 목록과 디스패치 테이블은 서로 교차 테스트됩니다
저는 모델이 목록에 없는 도구를 호출할 수 없다고 주장했는데, 이는 목록과 디스패치 테이블이 동일한 카탈로그에서 오기 때문입니다. anp2network의 답변은 이것이 단지 코드 배치가 우연히 그렇게 되어 있었을 뿐이며, 이를 테스트한 적이 없었다는 것이었습니다. 이제 테스트가 모든 도구 이름이 정확히 하나의 디스패치 항목을 가지고 있고, 그 역도 그러함을 주장합니다.
변경 3: 읽는 주체 때문에 일부 필드는 쓰기에 위험합니다
이것은 예상하지 못했던 부분입니다. 저희 프롬프트 스토어는 기능의 시스템 프롬프트(system prompt) 모든 개정판을 보관합니다. 프롬프트를 편집하는 것은 고위험 작업이었습니다. 이전 개정판으로 되돌리는 것 역시 위험했고, 되돌리기 작업은 이전 개정판 사본을 추가하며, 이것이 라이브 프롬프트가 됩니다. '항상 허용'으로 되돌리기를 설정한 사용자는 에이전트에게 질문 없이 다른 기능의 지침을 교체할 수 있는 방법을 제공했습니다.
핸들러는 이를 알 수 없습니다. 왜냐하면 스토리지 측면에서 되돌리기는 평범하고 되돌릴 수 있는 쓰기 작업이기 때문입니다. anp2network은 다음과 같이 설명합니다: 위험은 목적지의 속성입니다. 나중에 실행되는 것은 해당 행을 지침으로 읽습니다.
0.324.0 버전부터 필드는 다음과 같은 설정을 가질 수 있습니다:
systemPrompt: createLongTextField({
required: true,
personal: false,
...
이러한 필드를 쓰기 위해 생성하거나 업데이트하는 모든 작업은 일반적인 업데이트 핸들러를 통해서도 직접 디스패치되는 핸들러에서 high 권한을 필요로 합니다. 프롬프트 스토어의 콘텐츠 필드가 플래그를 가지고 있으므로, 되돌리기는 이제 고위험(high)입니다.
또한 모든 에이전트의 작성(write)이 어떻게 승인되었는지도 기록합니다: 승인 불필요, '항상' 규칙 적용, 또는 사용자가 확인하는 방식입니다. 고위험 도구에는 '항상' 옵션이 제공되지 않으므로, '항상' 규칙에 의해 승인된 플래그가 지정된 작성 건수는 0이어야 하며, 이는 쿼리를 통해 확인할 수 있습니다.
거부한 사항
anp2network는 사용자가 '절대 안 함(never)'으로 설정한 도구가 모델의 시야에서 사라진다는 점을 발견했습니다. 따라서 모델에게 '금지됨(forbidden)'은 '존재하지 않음(doesn't exist)'과 정확히 똑같이 보입니다. 제안된 해결책은 묘비명(tombstone)이었습니다: 항목을 보이게 유지하고 디스패치(dispatch)가 자체적인 이유와 함께 이를 거부하도록 하는 것입니다. 저희는 이것을 구현하지 않았습니다. '절대 안 함'은 도구를 숨기는 여러 필터 중 하나입니다(역할, 모드, 명시적 거부 목록). 따라서 묘비명은 다른 필터들과 일관성이 없거나 그 필터들이 숨기기 위해 존재하는 이름을 유출할 수 있습니다. anp2network는 이 추론에 동의했습니다. 대신, 에이전트의 시스템 프롬프트가 이제 해당 동작을 '유사한 도구로 근사화하는 것보다는 사용 불가능하다고 말하도록' 지시합니다. — 유사한 도구가 같은 동작은 아닙니다.
다른 제안은 고위험 핸들러(high-risk handlers)를 계산하는 테스트였습니다. 따라서 그 세트에 대한 모든 변경 사항이 diff에 나타납니다. 일단 위험이 효과에서 비롯된다면, 카운트를 추가해도 큰 의미가 없습니다: 새로운 하드 삭제는 누군가 숫자를 업데이트하든 안 하든 고위험입니다.
여전히 해결되지 않은 문제
1. 우리 데이터베이스 외부의 되돌릴 수 없는 효과
플로어(floor)는 삭제(forget)와 하드 삭제를 처리합니다. 하지만 메일, 웹훅 또는 결제 건은 알지 못합니다. 저희 스토어에서 메일을 예약하는 작성 건은 다른 모든 가역적(reversible) 작성 건처럼 보이지만, 이 메일은 전송 취소할 수 없습니다. 이제 저희 웹훅은 Idempotency key를 보내므로 수신자가 재전달을 중단시킬 수는 있지만, 이는 중복에 대비한 것뿐입니다. 에이전트가 질문 없이 첫 번째 동작을 트리거해야 하는지 여부를 결정하는 것은 아무것도 없습니다.
우리의 절반 아이디어는 아웃바운드 채널을 플래그가 지정된 필드처럼 취급하는 것입니다. 즉, 해당 채널이 스스로 되돌릴 수 없다고 선언하고, 그 작업물을 받는 모든 핸들러는 'high'로 해결됩니다. 우리는 아직 그것을 구축하지 않았습니다. 게다가 이 경계선 자체가 맞는지 확신할 수 없습니다. 왜냐하면 사용자에게 보내는 상태 메일과 제3자에게 하는 결제 모두 같은 클래스에 속하기 때문입니다.
2. 필드를 지침으로 읽는 주체
readAsInstruction 플래그는 코드에서 나중에 해당 필드를 프롬프트에 넣는 것 외에, 필드 옆에 수동으로 설정됩니다. 만약 새로운 기능이 플래그가 지정되지 않은 필드를 시스템 프롬프트에 계속 공급하기 시작한다면, 설정된 플래그는 오래되고 모든 검사는 'green' 상태로 유지됩니다. anp2network는 이 경우 아무것도 실패하는지 물었습니다. 아무것도 실패하지 않으며, 에이전트 측에서 시스템 프롬프트는 여전히 일반 문자열입니다.
이 플래그는 쓰기(write) 측면에서도 허점이 있습니다. 프로젝션이 플래그가 지정된 필드를 쓰는 수동으로 작성된 도메인 이벤트는 게이트를 건너뜁니다. 이 게이트는 값이 변경되었는지 여부가 아니라 페이로드에 해당 필드가 포함되어 있는지를 확인하는데, 이는 우리가 의도적으로 'fail closed'하게 만든 것입니다. 프롬프트를 되돌리는 것(Reverting a prompt) 역시 이미 나쁜 버전을 읽은 실행을 취소하지 못합니다.
우리의 절반 아이디어는 타입입니다. 시스템 프롬프트 매개변수는 플래그가 지정된 필드나 코드에서 온 텍스트만 허용하도록 하여, 다른 것을 공급하면 컴파일되지 않도록 하는 것입니다. 이것이 여러 조각으로 구성된 프롬프트를 거쳐도 유지되는지는 확인하지 못했습니다.
3. 이웃을 찾기보다 거부하기
모델이 사용자가 원하는 것에 대한 도구를 찾을 수 없을 때, 그렇게 말해야 합니다. 위험한 점은 대신 가장 근접하게 들리는 도구, 어쩌면 사용자가 '항상 허용'으로 설정한 도구를 선택할 수 있다는 것입니다. 현재 유일한 방어책은 위에서 인용한 시스템 프롬프트의 문장입니다.
anp2network는 숫자를 제안했습니다: 거부된 호출 이후에 모델이 다음에 무엇을 호출하는지 기록해야 한다는 것입니다. 우리의 루프에서는 목록에 없는 도구를 호출하면 실행이 종료되므로, 셀 다음(next) 것을 계산할 수 없습니다. 우리가 전혀 볼 수 없는 경우는 모델이 누락된 도구를 시도조차 하지 않고 바로 이웃으로 가는 경우입니다.
phongdesigns는 거의 중복되는 문제(near-duplicate problem)를 추가했습니다: 거의 같은 단어로 설명된 두 개의 쓰기 작업이 있는데, 모델이 이를 혼동합니다. 우리의 매니페스트 스냅샷은 여전히 핸들러 이름만 기록하므로, 재작성된 설명은 검토 과정에 나타나지도 않습니다. 저는 스레드에서 설명을 추가하고 위험도를 포함하는 것이 다음 단계라고 말했고, 그것은 아직도 그렇습니다.
제가 생각할 수 있는 대체(substitution) 테스트는 실제 모델에게 유사한 도구 쌍을 제공하고 어디서 잘못되는지 보는 것입니다. 이것은 실행마다 비용이 들고, 결과는 모델에 따라 달라집니다.
모든 풀 리퀘스트(pull request)마다 모델 호출 비용을 지불하지 않고도 에이전트가 즉흥적으로 행동하는 대신 거부하도록 테스트하려면 어떻게 해야 할까요?