AI 에이전트를 사용하여 정보 수집이나 팟캐스트 제작 등을 자동화하고 있습니다.
처음에는 업무에 필요한 능력을 만들고, 그것들을 순서대로 연결하면 될 것이라고 생각했습니다. 그런데 안정적으로 구동시키기 위해 조건이나 검사 과정을 추가하다 보니, 에이전트와 워크플로우가 뒤섞여 버렸습니다.
예를 들어, 특정 능력을 다른 작업에서 호출하려고 하면 원래 워크플로우의 전처리 과정이나 자료 일체까지 필요하게 됩니다. 반면, 실행을 관리해야 할 메커니즘이 무엇을 비교하고 무엇을 채택할지까지 결정하고 있습니다.
구동하기 위해 수정은 하고 있지만, 고쳐야 할 장소만 늘어나고 있습니다. 이번 개조에서는 개별적인 결함을 수정하기 전에, 이 책임(責務)의 분리 방식을 재검토했습니다.
이 글에서는 그 과정에서 겪었던 어려움과 다른 에이전트 개발에도 활용할 수 있는 지견을 정리합니다. 특정 프레임워크를 소개하는 것이 아니라, 직접 만든 시스템을 운영하면서 경계를 다시 설정한 이야기입니다.
정보 수집, 자료 선정, 대본 작성 같은 능력들은 본래 여러 가지 작업에서 사용하고 싶은 것입니다.
하지만 고정된 워크플로우를 확실하게 통과시키기 위해서는 그 작업 특유의 조건이 필요합니다. 어떤 자료를 사용할지, 어떤 제약을 지킬지, 무엇이 갖춰져야 완료되는지 말입니다. 이 부분을 모호한 상태로 두면 매번 다른 결과가 나옵니다.
문제는 바로 그 조건을 능력 자체의 필수 조건으로 너무 많이 끌어당겼다는 점이었습니다.
설명하기 위해 단순화하자면, '대본을 만드는 능력'에 '이 프로그램의 이전 자료', '이 워크플로우의 자료 선정 결과'까지 고정해 버리는 상태입니다. 그 프로그램에서는 편리하지만, 다른 주제로 단 한 번 대본을 만들고 싶을 때는 사용하기 어렵게 됩니다.
그래서 능력을 보편적인 조건과 이번 작업에서 필요한 조건으로 분리했습니다.
- 능력 측은 정식 결과물을 만들기 위해 최소한으로 필요한 입력과 반환되는 결과물만 정의합니다.
- 워크플로우 측은 이번에 사용할 자료, 그 역할, 제약, 전후 의존 관계, 완료 조건을 정의합니다.
이는 조건을 완화하는 변경이 아닙니다. 조건의 '위치'를 바꾸는 변경입니다. 고정된 워크플로우에서는 지금까지와 같이 엄격한 조건을 전달하고, 다른 용도에서는 그 용도에 필요한 조건을 전달할 수 있도록 합니다.
정리하는 과정에서 주로 다음 세 가지를 분리하여 생각하니 이해하기 쉬웠습니다.
| 부분 | 주요 역할 |
|---|
| 능력을 실행하는 에이전트 | 내용을 읽고, 비교하고, 채택 여부나 이유를 판단하며, 결과물을 만듭니다. |
| ... | |
구현에서는 능력을 Capability, 실행 기반을 Runner 등으로 부르고 있습니다. 하지만 이름이나 폴더만 분리해서는 불충분했습니다.
특히 어려웠던 점은, 실행 기반에 깊숙이 들어와 있던 업무 판단을 제거하는 것이었습니다. 정보 수집의 경우, 기반 측에서 비교 조합이나 단계적인 처리 계획, 채택 여부나 이유의 수정까지 관여하고 있었습니다. 이렇게 되면 에이전트에게 판단을 맡기고 있다고 생각해도, 실제로는 기반이 업무 진행 방식을 강하게 결정하고 있습니다.
그래서 탐색이나 비교의 계획, 채택 여부의 판단은 능력 측으로 되돌리고, 기반에는 실제로 전달된 입력, 소모한 예산, 저장된 결과 등을 확실하게 다루는 책임만 남겼습니다.
능력 내부에는 결정적인 처리나 재사용 가능한 툴이 있어도 괜찮습니다. 모든 처리를 LLM에게 판단하게 할 필요는 없습니다. 중요한 것은, 그 능력의 업무에 속하는 판단과 전체 실행을 보호하는 관리가 뒤섞이지 않는 것입니다.
경계의 어려움이 드러난 예로, 가져온 본문의 판별이 있었습니다.
실행 기반의 정규 표현식이 기사 중에 인용된 HTML 확인 화면이라고 판단하여, 읽을 수 있는 자료를 거부해 버리는 경우가 있었습니다. 이것 자체가 확인 화면인지, 그것을 해설한 기사인지 문자열만으로는 판단할 수 없습니다.
이 판별을 재검토하여, 기반에서는 HTTP 상태나 응답이 비어 있는지 등 관찰 가능한 사실만을 다루도록 했습니다. 본문의 의미를 읽고 이용 가능한지 판단하는 역할은 능력 측으로 되돌렸습니다.
여기서 배운 것은, 검사와 판단의 경계도 설계해야 한다는 것입니다.
형식이나 전달에 대한 검사를 엄격하게 하는 것에는 의미가 있습니다. 다만, 그 안에 내용 해석을 섞어 넣게 되면, 같은 자료를 기반과 에이전트가 각각 따로 판단하는 구조가 되어버립니다.
책임을 분리하자, 다음으로 '필요한 입력을 정말 사용했는지'가 신경 쓰입니다.
여기서도 하나의 성공 판정으로 묶으면 잘 되지 않았습니다. 나누어 확인하고 싶은 것은 다음 세 가지입니다.
- 필요한 내용이 실제 호출에 빠짐없이 전달되었는가
- 에이전트가 무엇을 사용했고, 무엇을 확인한 후에 사용하지 않았다고 보고했는가
- 그 내용이나 제약이 결과물에 적절하게 반영되어 있는가
실제 재감사(re-audit) 과정에서는 필요한 색인(index)이 배송되거나 보고되지 않았더라도 정상적으로 완료할 수 있는 경로를 발견했습니다. 이는 중간 호출(invocation)에서 선택한 입력과 전체 작업에 공급된 원래의 입력을 혼동하고 있었던 것 중 하나가 원인이었습니다.
따라서, 이제는 중간 응답은 해당 호출의 입력에 대해 확인하도록 하고, 작업 완료 시에는 원래의 전체 입력에 대해 확인하도록 수정했습니다.
또한, 입력이 임의적일 수 있다는 점과 전달된 입력을 확인할 필요가 없다는 점을 분리했습니다. 제 시스템에서는 참고 자료도 확인한 후 채택 여부를 판단하는 계약으로 했습니다. '획득할 수 없었다'와 '확인했으나 부적격 처리했다'는 별도의 결과로 남깁니다.
실패하면 아무튼 끝까지 통과하도록 만들고 싶어집니다. 하지만 그 수정 사항을 실행 기반(execution platform)에 모으면, 다시 책임이 뒤섞이기 시작합니다.
이번에 특히 의식한 점은, 부족한 판단이나 보고를 기반이 보완하여 논리적 일관성을 맞추지 않도록 하는 것이었습니다. 결과물을 만들 수 없었다면 능력 측에서 이유를 반환하게 합니다. 형식이 부적절하다면 기반이 거부합니다. 이러한 부정확한 응답도 확인된 범위 내에서는 진단용으로 남기지만, 공식적인 결과물로는 채택하지 않습니다.
예산 상한에도 비슷한 분리가 필요했습니다.
정보 수집에서 20건을 상한으로 설정했을 때, 19건까지 진행된 상태에서 2건을 한 번에 가져오려다 전체가 멈추는 사례가 있었습니다. 제한하고 싶었던 것은 새로 획득할 후보 건수인데, 이미 획득한 정보를 평가하는 작업까지 멈춰버리고 있었던 것입니다.
그래서 신규 획득의 남은 여유분을 확인하여, 상한에 도달하면 새로운 획득만 닫고, 획득된 후보 평가로 돌아갈 수 있도록 했습니다.
상한을 설정하는 것 자체보다, 무엇에 대한 상한인지를 명확히 하는 것이 중요했습니다. 건별 획득 실패, 작업 전체의 예산 초과, 권한 문제 등에서는 중지 범위와 처리 방식이 다릅니다.
'성공'의 의미도 재정립했습니다.
응답이 돌아온 것, 결과물이 계약을 충족한 것, 저장이 확정된 것, 워크플로우 전체가 완료된 것은 각각 별개입니다. 공개를 수반하는 작업이라면, 공개처에서 실물을 확인할 수 있었는지까지 필요합니다.
예를 들어 팟캐스트의 경우, 음성 파일이 만들어졌다는 것과 업로드 처리가 끝났다는 것뿐만 아니라, 공개 결과를 확인한 후에 후속 정리로 진행하도록 하고 있습니다.
또한, 실행 기록의 일관성을 검증하는 메커니즘이 PASS하더라도, 작업 자체가 성공했다고 할 수는 없습니다. 실패한 작업이라도 그 실패를 올바르게 기록하고 종료할 수 있었다면, 기록 검증에는 통과합니다.
테스트에도 비슷한 함정이 있었습니다. 구현이 만든 입력 목록을 그대로 테스트의 기대값(expected value)에도 사용하면, 둘 다 같은 입력을 놓치게 됩니다. 기대값은 원래의 테스트 데이터로부터 독립적으로 결정하고, 실제 배송이나 저장 내용과 대조할 필요가 있었습니다.
이번 개조 후, 정보 수집, 일일 Wiki 뉴스, 일기 분석, 팟캐스트 제작 등 대표적인 워크플로우를 통과시킬 수 있었습니다.
하지만 이것만으로 모든 비정상 케이스나 결과물의 품질까지 보장된 것은 아닙니다. 큰 구조 변경은 일단 구분하고, 실제로 구동하면서 수정하는 단계라고 생각합니다.
이번에 특히 다음 개발로 넘기고 싶은 점은 다음과 같습니다.
- 능력의 최소 조건과 용도의 조건을 분리한다. 고정 워크플로우의 엄격함을 모든 호출에 지우지 않는다.
- 판단의 주체를 결정한다. 내용의 채택 여부나 이유를 실행 기반이 대신 만들지 않도록 한다.
- 증거의 종류를 분리한다. 전달된 사실, 이용했다고 보고된 것, 결과물의 품질을 각각 확인한다.
- 실패의 범위를 보존한다. 건별 실패와 전체의 비정상을 혼동하지 않고, 확인할 수 없었던 것을 성공에 포함시키지 않는다.
- 검증을 독립시킨다. 구현 자체의 출력만을 정당성의 근거로 삼지 않는다.
에이전트와 워크플로우를 결합하면, 편리하게 만들기 위한 처리를 어디에 둘지 고민하는 상황이 늘어납니다. 그럴 때 단순히 코드를 나누는 것이 아니라, '누가 판단하고, 누가 사실을 기록하며, 누가 결과를 확정하는지'를 확인해야 합니다.
이번 개조에서는 이 질문으로 돌아가는 것이 임시방편적인 수정을 줄이는 데 도움이 되었습니다.