발견: 에이전트가 당신의 API를 찾는 방법
요약
에이전트가 API를 스스로 탐색하고 이해하는 '발견(discovery)' 과정의 중요성을 다룹니다. 기존의 도구 카탈로그 방식이나 인간용 문서를 활용하는 방식의 한계를 지적하며, 에이전트 친화적인 새로운 접근법을 제안합니다.
핵심 포인트
- 에이전트 시대에는 API를 읽고 이해하는 '발견' 단계가 필수적임
- 모든 함수를 미리 전달하는 방식은 컨텍스트 오염과 비용 상승을 초래함
- 인간을 위해 설계된 기존 문서는 에이전트가 소비하기에 부적합함
- 에이전트가 실행 시간에 스스로 API를 쿼리할 수 있는 구조가 필요함
지난번 저는 왜 Neander와 Grotto가 존재하는지에 대해, 즉 샌드박스화된 범용 언어 대신 목적에 맞게 구축되고 구조적으로 안전한 (safe-by-construction) 언어가 필요한 이유를 주장했습니다. 그것은 언어 전체에 대한 논거였습니다. 여기서부터 Field Notes from the Grotto는 이를 한 번에 하나의 기능씩 분해하여 살펴볼 것이며, 그 첫 번째 기능은 나머지 기능들이 존재하는 이유를 설명해 주는 기능인 '발견 (discovery)'입니다.
전통적인 통합 (integration)은 우리 모두가 인식하는 형태를 가지고 있었습니다: 개발자가 한 시스템의 API 문서를 읽고, 그다음 적절한 데이터와 함께 올바른 순서로 호출하는 통합 코드를 작성하는 것입니다. 두 단계, 한 명의 인간. 호출하는 시스템을 에이전트화하여 — 실행 시간 (runtime)에 무엇이 필요한지 스스로 결정하게 하면 — 중간에 있던 인간은 사라집니다. 하지만 두 단계가 사라지는 것은 아닙니다. 그 단계들은 어딘가에 도달해야 합니다.
작성 (writing) 단계는 모두가 이야기하는 절반입니다: 개발자 대신 에이전트가 프로그램을 작성하는 것이죠. 정작 생략되는 것은 바로 읽기 (reading) 단계입니다. API를 대상으로 코드 한 줄을 작성하기 전에, 당신은 그 API가 무엇인지 — 무엇이 존재하는지, 무엇을 입력받고 무엇을 반환하는지 — 먼저 알아내야 합니다. 그것이 바로 브라우저 탭에 문서를 띄워 놓았던 개발자의 역할이었습니다. 개발자가 떠나면, 그 알아내는 과정도 함께 떠나버립니다. 무언가가 그것을 물려받아야 합니다. 그 무언가가 바로 발견 (discovery)입니다.
성급한 답변은 도구 카탈로그 (tool catalog)입니다: 에이전트에게 모든 함수 정의를 미리 전달하고 그것이 선택하게 하는 것이죠. 그것 역시 발견입니다 — 다만 전체를 다 주는 방식이며, 미리 비용을 지불하는 셈입니다. 이는 확장성 (scale)이 없습니다. 에이전트가 아무것도 하기 전에 수백 개의 정의가 컨텍스트 (context)를 어지럽히고, 모든 중간 결과가 그 위에 쌓이게 되며, 그에 따라 지연 시간 (latency)과 비용이 상승합니다.
문서가 머물렀던 곳
제 포스트 Source Code as the Seam Between Systems에서 제안한 실용적인 경로를 택한다면 — 즉, 모델이 이미 유창하게 작성할 수 있는 범용 언어를 사용하고, 그 API들을 샌드박스 (sandbox) 뒤에 두는 방식 — 또 다른 형태의 어색함을 마주하게 됩니다. 해당 언어와 API 패키지들은 인간 개발자를 위해 구축되었으며, 인간 개발자는 레퍼런스 사이트, PDF, README, 또는 생성된 타입독 (typedocs) 페이지와 같은 문서를 읽습니다. 하지만 실행 중인 에이전트 (agent)는 인간 개발자가 아닙니다. 에이전트는 해당 레퍼런스 페이지를 검색하고 도달하기 위한 웹 접근 권한이 제한적일 수 있으며, 설령 접근할 수 있다 하더라도 그 페이지는 사람을 위해 구성되어 있습니다. 즉, 산문(prose), 예제 코드, 훑어보기 좋은 레이아웃 등으로 이루어져 있어 에이전트가 소비하도록 설계된 것이 아닙니다. 따라서 이 경로를 택하는 모든 시스템은 무언가를 덧붙여야만 합니다. 사용 가능한 함수 목록을 나열하는 메타 도구 (meta-tool)를 만들거나, 에이전트가 무언가를 작성하기 전에 쿼리할 수 있는 검색 엔드포인트 (search endpoint)를 구축해야 합니다. 작동은 합니다. 하지만 이는 부속물에 불과합니다. 대역 외 (out of band) 방식이며, 매 스택마다 새로 만들어야 합니다.
Neander는 발견 (discovery)을 언어 자체의 일부로 만듭니다. discover는 프로그램에 작성하는 동사이며, 실제 작업을 수행하는 프로그램을 제출하는 것과 정확히 동일한 방식으로 제출합니다. 이것이 바로 강조할 가치가 있는 통합 (unification)입니다. Neander에서 모든 것은 프로그램입니다. 무엇이 존재하는지 찾아내는 것과 그것을 호출하는 것은 동일한 언어를 통해, 동일한 진입점 (entry point)을 거쳐 수행되는 동일한 종류의 행위입니다. 런타임 (runtime)은 에이전트에게 단 하나의 연산인 submitProgram을 노출하며, 에이전트가 사용 가능한 것을 묻든 작업을 수행하든 모든 상호작용은 이를 통해 흐릅니다. 이를 통해 '찾아내는 행위'가 인밴드 (in-band) 방식으로 이동했습니다. 개발자가 브라우저 탭을 통해 수행하던 일을 이제 에이전트는 프로그램으로 수행합니다.
두 개의 동사
Neander의 거의 모든 것은 두 개의 동사를 결합하기 위해 존재합니다. call은 등록된 API 함수 중 하나를 호출합니다. discover는 런타임에 호출할 수 있는 것이 무엇인지 묻습니다.
discover에는 여섯 가지 형태가 있습니다. 즉, 찾고자 하는 세 가지 대상(네임스페이스 (namespaces), 함수 (functions), 문서 (documents))과 찾는 두 가지 방식(목록 검색, 또는 정확한 이름으로 가져오기)이 교차하여 구성됩니다.
discover namespaces ["payment"] // [Namespace] — 검색
discover namespace "shipping" // Namespace? — 네임스페이스의 정확한 조회
discover functions ns ["estimate", "intl"] // [Function] — 네임스페이스 내 함수 검색
...
검색어는 대소문자를 구분하지 않는 부분 문자열(substrings)이며, AND 조건으로 결합되어 각 후보의 이름 및 설명과 매칭됩니다. 빈 목록은 모든 항목과 매칭됩니다. 이것이 발견(discovery) 가능한 전체 표면(surface)입니다.
루프 (The loop)
아무런 정보가 없는 에이전트 (cold agent)는 당신의 API를 알지 못하므로, 안쪽으로 파고들며 작동합니다. 그 힘은 코드의 순차적인 작성과 실행에 있습니다. 네임스페이스를 나열하는 첫 번째 프로그램, 네임스페이스 내부의 함수를 찾는 두 번째 프로그램, 그리고 함수를 호출하는 세 번째 프로그램 — 에이전트는 모든 프로그램 제출의 결과로서 런타임이 반환하는 응답 봉투 (response envelopes)를 읽고 학습합니다.
neander 1 {
types {}
main -> [Namespace] {
...
런타임은 표준 봉투 (standard envelope)로 응답합니다. 여기에는 success 플래그, result, 그리고 텔레메트리 (telemetry)가 담긴 meta 블록이 포함됩니다. [Namespace] 타입의 반환 값은 사용 가능한 네임스페이스의 완전히 직렬화된 (serialized) 목록입니다.
{
"success": true,
"result": [
...
이 목록을 컨텍스트 (context)에 담음으로써, 에이전트는 이제 관심 있는 네임스페이스를 추가로 조사할 수 있습니다.
neander 1 {
types {}
main -> [Function] {
...
이번에는 결과에 해당 네임스페이스에서 사용 가능한 함수 목록이 포함되며, 각 함수는 완전히 직렬화되어 제공됩니다 (여기서는 하나의 함수만 표시됨):
{
"success": true,
"result": [
...
나열된 모든 함수 이름은 다음에 작성해야 할 정확한 문자열입니다. 즉, qualifiedName은 call에 전달하는 값이며, types 사이드 테이블(side-table)은 이행적으로 완전(transitively complete)하므로, bookings.Booking의 형태는 해당 객체를 반환하는 함수와 함께 전달됩니다. 에이전트는 이를 JSON 응답 엔벨로프(envelope)에서 직접 읽어낸 다음, 작업을 수행하는 프로그램을 작성합니다:
neander 1 {
types {}
main -> decimal(2, half_away) {
...
발견(Discover), 호출(Call), 반환(Return). 언어의 나머지 모든 것은 세부 사항일 뿐입니다.
의도적인 불투명성 (Opaque on purpose)
짚고 넘어갈 만한 설계상의 선택은 discover가 반환하는 값들 — Namespace, Function, Document 타입의 핸들(handle) — 이 불투명(opaque)하다는 점입니다. 프로그램은 이를 보유하거나 반환할 수는 있지만, 그 필드(field)를 읽을 수는 없습니다. 엔벨로프(envelope)는 발견 핸들(discovery handle)이 main으로부터 반환될 때 런타임(runtime)이 직렬화(serialize)하는 내용을 포함합니다. 프로그램 내부에서 핸들은 봉인된 토큰(sealed token)입니다. 오직 밖으로 나가는 과정에서만 읽을 수 있게 됩니다.
제약 사항처럼 들릴 수 있는 이 점은 사실 보장(guarantee)입니다. 발견 핸들은 오직 discover 로부터만 올 수 있습니다. 레코드 리터럴(record literal)로 이를 위조하거나 API 함수로부터 반환할 수 없습니다. 따라서 에이전트가 보는 발견 핸들은 항상 런타임이 발행(mint)한 핸들이며, 런타임이 실제로 등록한 무언가를 가리킵니다. 발견(Discovery)은 관습이 아니라, 유일한 진입로입니다.
Grotto의 다음 단계
발견은 어떤 API가 존재하는가에 답하지만, 한 가지 질문이 남아 있습니다. 에이전트는 발견(discovery)을 어떻게 발견할까요? Neander와 같이 목적에 맞게 새로 구축된 언어의 단점은, 잘 확립된 범용 언어(general-purpose language)와 극명하게 대조적으로, 콜드 스타트(cold-start) 에이전트가 이 언어에 대해 들어본 적이 없다는 것입니다. 따라서 에이전트가 단 세 줄짜리 네임스페이스(namespace) 발견 프로그램을 작성하기도 전에, submitProgram 연산 외에는 사용할 수 있는 것이 없는 상태에서 언어 자체를 배워야 합니다. 만약 당신이 언어를 모른다면, 추측이 아닌 유일하게 작성할 수 있는 프로그램은 무엇일까요?
바로 빈 프로그램(empty program)입니다!
그동안 Neander 명세(spec)를 읽어보시고, 당신의 앱에 Grotto를 임베딩(embed)한 뒤 무엇을 발견했는지 저에게 알려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기