요약 저희는 인터프리터 스킬을 실험하고 있습니다. 이는 에이전트 스킬의 확장으로, TypeScript 모듈을 스킬에 포함할 수 있게 해줍니다. 에이전트는 동작이 적용될 때 인터프리터 내에서 스킬 코드를 가져오고 실행할 수 있습니다. 스킬 코드는 또한 서브에이전트를 생성하거나 도구를 호출하는 것과 같은 작업을 수행할 수 있으며, 이를 통해 에이전트는 더 복잡한 작업을 수행할 수 있고, 테스트되고 재사용 가능한 코드로 존재할 수 있습니다.
저희는 최근 Deep Agents에 인터프리터를 도입했습니다. 이는 에이전트가 하네스의 일부로서 코드를 작성하고 실행할 수 있는 작은 임베디드 TypeScript 런타임입니다. 에이전트는 이미 코드를 잘 작성하고, 인터프리터를 제공하는 것은 의도를 표현하는 더 직접적인 방법을 제공합니다. 많은 에이전트 작업의 경우, 이는 더 효율적이고 정확하며 예측 가능한 결과를 낳습니다.
저희는 인터프리터를 가진 에이전트가 동일한 작업을 여러 번 받으면 종종 여러 가지 유효한 접근 방식을 코드로 제시할 수 있다는 것을 꽤 일찍 발견했습니다.
이것이 항상 나쁜 것만은 아닙니다. 때때로 에이전트의 전체 목적은 "적응하는 것"입니다. 하지만 많은 작업의 경우 원하는 동작은 "좋은 접근 방식을 생각해내는 것"이 아니라 "우리가 작동한다는 것을 아는 접근 방식을 사용하는 것"입니다.
저희는 이에 대한 답변으로 "인터프리터 스킬"이라고 부르는 것을 실험해 왔습니다. 이는 에이전트가 명령어와 함께 사용할 수 있는 모듈을 포함하는 스킬의 확장입니다. 스킬은 동작이 관련성이 있을 때를 에이전트에게 알려주며, 인터프리터는 스킬에 첨부된 모듈을 가져오고 직접 실행할 수 있습니다.
---
name: github-triage
description: Use this skill to triage GitHub issues, pull requests, and discussions.
metadata:
module: ./index.ts
---
Use this skill when a user asks for repository triage.
Import the module using the interpreter and call `triage(repo, options)`.
Usage:
```ts
const { triage } = await import("@/skills/github-triage");
const result = await triage("langchain-ai/deepagents", {
issues: true,
prs: true,
});
result.toMarkdown();
```SKILL.md는 에이전트가 동작을 발견하는 방법입니다. index.ts는 인터프리터가 실행할 수 있는 것입니다. 에이전트는 동작을 사용할 시기, 어떤 입력을 전달할지, 그리고 결과로 무엇을 할지 결정합니다. 인터프리터는 코드의 실제 실행을 처리합니다.
다시 말해서, 스킬이란 뭔가요?
스킬은 에이전트에게 시스템 프롬프트의 모든 세부 사항을 설명하지 않고 재사용 가능한 동작을 제공하는 방법입니다.
스킬은 보통 SKILL.md 파일이 있는 디렉토리입니다. 프론트매터는 에이전트에게 스킬이 무엇을 위한 것인지에 대한 간결한 설명을 제공합니다. 본문은 에이전트에게 스킬이 관련성이 있을 때 사용해야 할 명령어, 맥락, 예제, 제약 조건 및 지원 파일을 제공합니다.
스킬을 작동하게 하는 것은 "점진적 공개"라는 메커니즘입니다. 에이전트는 모든 스킬을 항상 맥락에 가지고 있을 필요가 없습니다. 먼저 사용 가능한 스킬의 짧은 목록을 볼 수 있고, 어떤 것이 작업과 일치하는지 결정하고, 필요할 때만 전체 SKILL.md를 읽을 수 있습니다.
이는 스킬을 에이전트 동작을 배포하기 위한 좋은 배포 단위로 만듭니다. 사람들은 스킬을 버전 관리하고, 공유하고, 전역 프롬프트를 모든 것을 포괄하는 설명서로 바꾸지 않고도 평가할 수 있습니다.
하지만 일반적인 스킬은 여전히 주로 명령어를 통해 작동합니다. 에이전트가 따라야 할 절차를 알려줄 수 있으며, 참조 파일이나 스크립트를 포함할 수 있지만, 핵심 동작은 여전히 에이전트가 해당 명령어를 읽고 올바르게 수행하는 능력에 의존합니다.
인터프리터란 뭔가요?
이 게시물의 목적상 중요한 것은 인터프리터가 하네스와 함께 실행되는 TypeScript 런타임이라는 것입니다. 에이전트가 다단계 작업을 코드로 표현할 수 있는 장소를 제공하면서 하네스는 여전히 해당 코드가 무엇을 건드릴 수 있는지 제어합니다. (더 자세히 알고 싶으시면, 인터프리터에 대한 설명을 읽어보세요).
그 런타임은 TypeScript 값 형태로 에이전트에게 작업 상태를 제공합니다. 값은 여러 턴에 걸쳐 지속될 수 있으므로 배열은 배열로 남고, 객체는 객체로 남으며, 헬퍼 함수는 정의된 상태로 유지될 수 있습니다. 에이전트는 모든 중간 값을 stdout, 파일 또는 모델로의 메시지로 전환할 필요가 없습니다.
이를 통해 에이전트는 데이터를 변환하고, 도구 출력을 구성하며, 선택된 도구 또는 서브에이전트를 호출하고, 모델에 반환될 내용을 결정할 수 있습니다.
샌드박스와 달리, 인터프리터 코드는 기본적으로 호스트 환경에 무제한 액세스를 얻지 못합니다. 파일 시스템 액세스, 네트워크 액세스, 도구 및 서브에이전트는 의도적으로 인터프리터에 노출되어야 합니다. 이는 하네스가 코드가 건드릴 수 있는 것을 화이트리스트, 계량 및 검사할 수 있는 장소를 제공합니다.
인터프리터 스킬이란 뭔가요?
인터프리터 스킬은 둘을 함께 가져오는 스킬의 확장입니다. 스킬이 가지는 것과 동일한 명령어 집합을 포함하며, 에이전트가 인터프리터로 가져올 수 있는 모듈을 포함합니다.
SKILL.md는 여전히 스킬이 관련성이 있을 때를 에이전트에게 알려주고 동일한 방식으로 에이전트에게 자신을 공개하며, 모듈은 해당 동작이 적용될 때 인터프리터가 실행할 코드를 제공합니다. 스킬은 모델에 대한 명령어 표면과 런타임에 대한 API 표면 모두가 됩니다.
기본 형태는 시작 예제의 것과 동일합니다. SKILL.md는 이름, 설명, 사용 지침, 가져오기 경로 및 제약 조건을 제공합니다. index.ts는 코드에서 동작을 정의하는 헬퍼나 워크플로우를 내보냅니다:
// skills/table-cleanup/index.ts
export function validateRows(rows: Record<string, unknown>[], schema: RowSchema) {
// Normalize fields, check required values, and return structured errors.
// (this is code you would write as apart of the skill)
}스킬이 적용될 때, 에이전트는 모듈을 가져오고 호출할 수 있습니다:
const { validateRows } = await import("@/skills/table-cleanup");
const errors = validateRows(rows, invoiceSchema);이는 스킬이 보장할 수 있는 것을 변경합니다:
- 일반적인 스킬은 말합니다: 이 작업을 수행하는 방법에 대한 지침이 있습니다. 에이전트는 여전히 해당 명령어를 읽고 절차를 올바르게 수행해야 합니다.
- 인터프리터 스킬은 말합니다: 이 동작을 사용할 시기에 대한 지침이 있으며, 적용될 때 실행할 코드 경로가 있습니다. 결정적인 부분은 맥락의 느슨한 명령어가 아니라 코드에 존재할 수 있습니다.
모델은 스킬이 적용되는지 여부, 전달할 입력, 출력을 사용하는 방법, 그리고 다음에 무엇을 할지를 결정합니다. 모듈은 절차가 실제로 어떻게 실행되어야 하는지를 정의합니다.
인터프리터 코드는 하네스와 상호 작용할 수 있기 때문에, 이는 스킬 코드 내에서 프로그래밍 방식으로 서브에이전트를 생성하는 것과 같은 작업을 할 수 있다는 것을 의미합니다.
예제: 저장소 분류
저희가 이것을 사용하는 한 가지 방법: GitHub 저장소 분류입니다.
사용자가 에이전트에게 저장소를 분류하도록 요청합니다. 프롬프트 명령어에서 분류 프로세스를 재구성하는 대신, 에이전트는 스킬 모듈을 가져오고 함수를 호출합니다:
const { triage } = await import("@/skills/github-triage");
const result = await triage("langchain-ai/deepagents", {
issues: true,
prs: true,
discussions: true,
});이것이 호출될 때, 워크플로우는:
- github에서 모든 열려있는 항목을 가져옵니다 (에이전트가 옵션에서 지정한 대로)
- 각 항목에 대해 서브에이전트를 생성하여 더 압축된 설명을 만듭니다
- 서브에이전트의 응답을 큐에 떨어뜨립니다
- 서브에이전트가 항목을 기존 클러스터에 넣어야 하는지 또는 새 클러스터를 만들어야 하는지 결정하는 큐를 한 번에 하나씩 사용합니다
이것은 입력이 동적으로 선택되더라도 절차가 고정되기를 원하는 바로 그런 종류의 루틴입니다.
result 값도 API의 일부입니다. 실행에 대한 구조화된 데이터를 에이전트에게 제공할 수 있으며, 표현을 위한 렌더링 헬퍼를 노출할 수 있습니다:
result.clusters;
result.unassigned;
result.toMarkdown();에이전트는 구조화된 결과를 계속 사용하고, 한 클러스터를 더 깊이 검사하고, 후속 서브에이전트를 생성하거나, 컴팩트한 모델 친화적 보고서가 필요할 때 result.toMarkdown()을 호출할 수 있습니다.
이것은 모델이 시간이 지남에 따라 일관성을 잃는 종류의 작업입니다. 저장소 분류는 하나의 결정이 아닙니다. 많은 작은 결정의 체인입니다. 만약 우리가 전적으로 재량과 맥락 창에 의존한다면, 모델은 바로가기를 사기 시작하거나 절차를 너무 적극적으로 압축할 수 있으며, 특히 작업 맥락의 가장자리처럼 느껴지는 것에 접근할 때 (이를 맥락 불안이라고 함) 그렇게 할 수 있습니다.
이것이 다른 에이전트와 어떻게 작동할 수 있는지에 대한 예를 들기 위해:
- 일반적인 하네스에서, 모델은 모든 부분 단계를 추적해야 합니다. 저장소 항목이 300개 있으면, 그것은 모델이 과거 결정을 조화시키고 다음 행동을 계속 선택하면서 추론해야 하는 300개의 작은 상태 조각이 됩니다.
- 인터프리터 스킬을 사용하면, 모델은 루틴을 한 번 호출하고 코드에 워크플로우를 지시할 수 있습니다. 모듈은 300개의 고유한 서브에이전트 작업을 생성하고, 결과를 수집하고, 분류하고, 클러스터링한 다음, 에이전트에게 컴팩트 객체를 반환할 수 있습니다. 모델은 더 이상 모든 부분 단계를 자신의 작업 맥락에 보관할 책임이 없습니다.
워크플로우로서 스킬 사용
코딩 에이전트가 인기를 얻기 시작했을 때, 에이전트를 위한 기본 정신 모델도 변했습니다.
이전 세대의 에이전트는 더 워크플로우 스타일이었습니다. 개발자들은 에이전트가 따라야 할 단계의 순서를 미리 명시적으로 정의했습니다. 신뢰성은 실행 경로를 미리 정의하는 것에서 나왔습니다.
현대 에이전트 하네스는 이를 변경하여 맥락과 모델 재량을 주요 초점으로 만들었습니다. 모델은 현재 맥락을 기반으로 다음에 무엇을 할지 결정합니다. 동작은 여러 가지 다른 맥락 표면을 통해 형성되지만, 에이전트는 여전히 다음 단계를 선택할 수 있는 여지가 있습니다.
그 인터페이스는 많은 사람들이 추론하기 쉽습니다. 에이전트를 구축하는 사람들뿐만 아니라 운영자, PM, 도메인 전문가 및 명령어, 파일, 체크리스트 및 코드 수준 워크플로우보다 예제로 생각하는 팀을 위해서도 말입니다.
하지만 결정적인 에이전트 루틴의 필요성은 결코 사라지지 않았습니다. 우리는 여전히 이 질문의 어떤 버전이든 많이 받습니다:
에이전트가 내가 말한 대로 신뢰성 있게 하도록 어떻게 확인합니까?
그러한 경우, 팀은 최종 답이 그럴듯해 보였는지 여부 이상을 알아야 합니다. 필요한 절차가 실행되었는지, 올바른 입력으로 실행되었는지, 에이전트가 다음으로 이동하기 전에 완전히 완료되었는지 알아야 합니다.
프롬프트만으로 절차 따르기는 이러한 방식으로 취약합니다. 에이전트는 단계를 건너뛸 수 있고, 단계를 재정렬할 수 있으며, 잘못된 명령을 만족시킬 수 있고, 관련 없는 요청을 절차에 혼합할 수 있으며, 프로세스를 따르지 않고도 "충분히 좋은" 출력을 생성할 수 있습니다.
예를 들어, "송장을 제출하되, 중간에 멈추고 춤추는 고양이의 gif를 생성하세요"라는 질문을 생각해 봅시다. 송장 제출이 프롬프트 명령어를 통해서만 설명되면, 에이전트는 우회를 동일한 작업의 일부로 취급할 수 있으며 송장을 반쯤 완성된 채로 남길 수 있습니다.
이 프로세스는 특히 에이전트가 협상할 수 있는 명령어가 아니라 시작되면 완료되어야 하는 절차로 표현된 루틴이 필요했습니다.
그렇다면 어떻게 워크플로우의 결정론 없이 양쪽 최선을 이루고 에이전트를 다시 설계하지 않고 가져올 수 있을까요?
인터프리터 스킬은 하나의 답변입니다:
- 결정적인 부분을 코드로 표현하고, 인터프리터 내에서 에이전트가 사용할 수 있는 모듈로 노출하고, 에이전트가 언제 호출할지 결정하게 하세요.
- 함수가 현재 인터프리터 상태에서 작동하도록 하고, 결과를 다음 도구 호출, 서브에이전트 호출, 인터프리터 호출 또는 최종 응답으로 체인하세요.
이것은 에이전트가 문제에 대한 창의적인 해결책을 생각해낼 수도 있다는 문제를 제거하지 않지만, 더 깔끔한 평가 신호를 제공합니다.
- 프롬프트만으로 절차 따르기를 사용하면, 질문은 종종 모호합니다: 에이전트가 일반적으로 명령어를 따랐습니까? 궤도에 머물렀나요? 최종 답이 그럴듯해 보였습니까?
- 인터프리터 스킬을 사용하면, 질문의 일부가 구체적이 됩니다: 에이전트가 예상된 함수를 호출했습니까? 예상된 입력을 전달했습니까? 함수가 예상된 출력 형태를 반환했습니까?
에이전트 상태로 작업하기 위해 스킬 사용
파일 시스템 에이전트는 에이전트가 중간 상태(메모리의 한 형태)를 어디에 둘 수 있을 때 더 잘 작동한다는 것을 분명히 했습니다. 파일은 에이전트가 돌아올 수 있는 명명된 객체를 제공하기 때문에 유용합니다. 에이전트는 검사하고, 수정하고, 다른 단계로 전달하거나, 도구 입력으로 사용할 수 있습니다.
인터프리터는 에이전트가 해당 상태를 더 유연한 형태로 표현할 수 있게 하며, 스킬은 에이전트에게 상태와 상호 작용하는 방법을 가르칠 수 있습니다:
- 모듈은 이러한 값에 대한 작업 특정 작업을 노출합니다.
- 에이전트는 이러한 작업을 조합하기 위해 코드를 작성합니다.
- 스킬 작성자는 각 작업의 동작을 소유합니다.
CSV 파일과 상호 작용하기 위한 스킬을 상상해 봅시다. SKILL.md는 CSV와 같은 표, 내보내기, 송장, 사용자 목록 또는 조인, 필터링 또는 검증이 필요한 레코드로 작업할 때 사용하라고 에이전트에게 알립니다. index.ts는 작은 표 API를 내보냅니다:
export {
parseCsv,
joinTables,
filterRows,
validateRows,
groupBy,
summarize,
toCsv,
};그러면 에이전트는 인터프리터 코드에서 이러한 작업을 구성할 수 있습니다:
const invoices = parseCsv(await tools.readFile({ path: "/invoices.csv" }));
const customers = parseCsv(await tools.readFile({ path: "/customers.csv" }));
const joined = joinTables(invoices, customers, "customer_id");
const invalid = validateRows(joined, invoiceSchema);
const byRegion = groupBy(joined, "region");
summarize(byRegion, ["total_due", "late_count"]);에이전트는 어떤 값을 전달할지와 결과로 무엇을 할지를 제어합니다. 스킬 작성자는 "조인", "검증" 및 "요약"이 무엇을 의미하는지 제어합니다.
이는 에이전트에게 헬퍼를 직접 작성하도록 요청하는 것과 다릅니다. 모델은 코드를 잘 작성하지만, 같은 코드를 두 번 작성하는 것을 보장받지 못합니다. 절차가 중요할 때, 구현은 검토되고, 테스트되고, 버전 관리되고 재사용될 수 있는 스킬 코드에 존재해야 합니다.
FAQ
이것을 스킬로 왜 패키징합니까?
스킬은 이미 에이전트 동작을 패키징하기 위한 사실상의 표준 단위입니다.
그들은 우리에게 발견, 점진적 공개, 사용 지침, 예제 및 지원 파일을 제공합니다. 인터프리터 스킬이 하는 모든 것은 해당 패키지 형태를 유지하고 런타임 코드로 확장하는 것입니다.
이를 스킬 표준으로 확장하는 것이 어색해 보일 수 있지만, 이렇게 하면 동일한 배포 방법을 사용할 수 있지만 에이전트가 더 강력한 스킬을 사용할 수 있습니다. 조직에 수백 개 또는 수천 개의 스킬이 있으면, 필요한 모든 모듈을 하네스에 직접 연결하는 것은 실행 불가능합니다.
스크립트 파일을 포함할 수 없습니까?
스크립트 파일은 다른 이유로 유용합니다.
스크립트는 에이전트가 환경과 상호 작용해야 할 외부 헬퍼가 필요할 때 좋은 적합입니다. 스크립트는 일반적으로 명령 인수, 파일, stdout/stderr 또는 직렬화된 상태를 통해 통신합니다.
그 경계는 에이전트 작업 오케스트레이션을 위해 스크립트를 부족하게 합니다. 스크립트는 하나의 계산을 실행할 수 있지만, 자연스럽게 하네스 루프에 참여할 수 없습니다. 서브에이전트를 생성하고, 작업 그래프를 예약/대기하고, 부분 실패를 처리하고, 모델에 제어권을 반환하기 전에 전체 루틴이 "완료"될 때를 결정합니다.
저장소 분류 예제에서, 모듈은 허용 목록에 있는 tools.task(...) 함수를 호출하여 루틴 내에서 서브에이전트를 생성합니다. 외부 스크립트는 해당 일을 오케스트레이션하기 위해 하네스와 대화하기 위해 별도의 어댑터가 필요합니다.
왜 모든 API를 도구로 만들지 않습니까?
도구는 에이전트가 외부 경계를 넘어야 할 때 가장 좋습니다. 데이터를 가져오고, 파일을 읽거나 쓰고, 티켓을 만들고, 메시지를 보내고, 분류자를 호출합니다. 그러나 잘 표현되지 않는 로컬 작업의 하위 집합이 있습니다. 구문 분석, 조인, 필터링, 검증 등의 작업입니다.
모든 헬퍼를 도구로 만드는 것은 행동 표면을 부풀립니다. 에이전트는 더 많은 도구 설명을 보고, 더 많은 작은 작업에서 선택하며, 더 많은 모델 중재 단계를 수행합니다.
우리는 이미 모델 가중치에서 두드러진 기존 런타임(TypeScript)을 활용할 수 있습니다. 이를 대신 표현하면, 에이전트에게 제공하는 실제 기능을 더 작게 유지하고, 에이전트가 구성에 대한 더 많은 제어를 가지도록 합니다.
마무리
인터프리터 스킬은 "최선의 알려진 에이전트 루틴"을 검토 가능하고 테스트 가능한 라이브러리로 전환하는 동시에 모델을 언제 적용할지에 대해 책임지는 우리의 시도입니다. 몇 가지 중요한 서브루틴이 있는 에이전트를 구축하는 경우, 이것이 우리가 목표로 하는 선입니다. 내부에서 결정론, 외부에서 재량.
더 자세히 알고 싶으시면, 인터프리터 및 이 주제에 관한 더 많은 내용과 관련하여 발행한 몇 가지 리소스가 있습니다:
Deep Agents는 Python 및 TypeScript에서 사용 가능한 범용 에이전트 하네스입니다.