Cookbook
Agents & skills · for AI agents

필요 시 로드되는 스킬 작성

모델이 관련성 있을 때만 가져오는 기능을 패키징합니다. 스킬을 생성하고 에이전트에 연결하세요.

MCP tools:list_skillscreate_skillupdate_agenttest_skill_trigger

원칙 — 스킬 개수를 작게 유지합니다. 이 에이전트의 업무에 필요한 것만 연결합니다(약 5개 이하). 모델은 각 스킬의 1줄짜리 description을 보고 스킬을 선택합니다. 연결된 스킬이 많을수록 모델이 잘못된 스킬이나 아무 스킬도 로드할 가능성이 높아집니다. 두 스킬이 동일한 상황에서 중복된다면 병합하십시오. 자세한 내용은 디자인 원칙을 참조하십시오.

스킬은 필요할 때 로드되는 기능 팩입니다: 시스템 프롬프트에는 description("언제 사용할지")만 포함되며, 모델은 스킬이 관련 있다고 판단될 때만 전체 instructions를 가져옵니다. 따라서 description은 짧고 언제 사용해야 하는지 명시해야 하며, 그렇지 않으면 모델이 이를 호출할지 알 수 없습니다.

list_skills()                                # what already exists
 
create_skill(
  name="refund-policy",
  description="Use when the user asks about refunds, cancellations or chargebacks.",
  instructions="The full procedure: eligibility windows, how to word the outcome, when to escalate …",
)
 
update_agent(name="Support", add_skills=["refund-policy"])   # attach it (incremental — keeps existing skills)

절차는 instructions(로드 시 가져옴)에 보관하고, 에이전트의 soul(모든 턴에서 프롬프트에 포함됨)에는 넣지 마십시오. 언제 사용해야 하는지 명시하는 1줄짜리 description이 스킬을 발견 가능하게 만드는 핵심입니다. 모호한 설명은 모델이 스킬을 호출하지 못하게 만듭니다.

도구는 스킬에 함께 탑재할 수 있습니다

스킬은 자체 도구 바인딩을 포함할 수 있습니다 — create_skill(tools=[…]) 또는 update_skill(name, add_tools=[…])를 사용하여, 스킬은 절차와 필요한 도구를 하나의 단위로 묶은 자체 완결형 기능 팩으로 배송됩니다.

스킬의 자체 도구는 스킬이 로드된 후에만 제공됩니다. 턴 시작 시 도구 세트에 포함되지 않으며, 스킬을 로드해야 비로소 도구 세트에 추가됩니다. 따라서 순서는 고정되어 있습니다: 절차를 먼저 읽고, 그 다음 도구를 얻습니다 — 모델이 도구를 건너뛰고 즉흥적으로 처리할 수 없습니다.

이 순서 강제 로직은 프롬프트에 요청하는 것이 아니라 코드에서 구현됩니다. 프롬프트에 요청하는 방식은 효과가 없었습니다. 2026-08-22에 측정된 결과, *"회사 X의 경쟁사를 조사하라"*는 지시에서 절차와 도구를 함께 제공했을 때: 모델은 웹 검색을 세 번 호출했지만 스킬을 로드하지 않았습니다. 모델은 기억에서 경쟁사 이름을 임의로 만들어냈고, 그 가짜 이름으로 검색하여 아무 결과도 찾지 못했으며(실제로 존재하지 않는 이름이므로), 그 가짜 이름을 정답으로 작성했습니다 — 반면 절차의 첫 단계에는 *"먼저 이 회사가 실제로 무엇을 판매하는지 확인하십시오"*라고 명시되어 있었습니다. 선택적 절차는 읽히지 않습니다.

이를 의존하기 전에 알아야 할 두 가지 사항이 있습니다:

  • 스킬 도구는 에이전트의 자체 tools 목록에 표시되지 않습니다. get_agent는 에이전트에 직접 연결된 도구만 표시합니다. 콘솔의 Tools 탭에서는 스킬에 바인딩된 도구를 별도의 읽기 전용 "via skills" 라인으로 표시합니다. "도구 X가 활성화되었는지" 확인하려면 두 곳을 모두 검사하십시오 — 또는 테스트 채팅에서 도구를 직접 호출해 보십시오.
  • 에이전트가 이미 보유한 도구는 영향을 받지 않습니다. 에이전트 자체가 web_search를 보유하고 있다면, 해당 도구는 전체 턴 동안 계속 사용 가능합니다. 스킬의 로드 때문에만 추가되는 도구들만 해당 스킬의 로드를 기다립니다.
  • 도구 바인딩은 해당 절차 내에서만 의미가 있을 경우 스킬에, 턴 전반에 걸쳐 일반적으로 유용할 경우 에이전트에 바인딩하십시오(예: 환불 스킬 내부의 환불 조회는 스킬에, save_contact, web_search는 에이전트에).

도구 바인딩은 정책이 아닌 가용성일 뿐 — 호출 시점은 instructions에서 명시해야 합니다

스킬(또는 에이전트)에 도구를 체크하는 것은 해당 도구를 호출 가능하게 만드는 것일 뿐입니다. 모델은 매 턴마다 도구의 스키마와 1줄짜리 설명을 보므로, 상황에 따라 opportunistic하게 사용할 수 있습니다 — 예를 들어 방문자가 이메일을 제공하면 save_contact가 실행됩니다. 하지만 신뢰할 수 있게 발생해야 하는 모든 동작은 문서화되어야 하며, 그 각 부분에는 하나의 올바른 위치가 있습니다:

지정하려는 사항위치
이 스킬을 언제 로드할지스킬의 description(매 턴 프롬프트에 포함됨)
절차: 어떤 단계에서 어떤 도구를 어떤 인수로 호출할지스킬의 instructions도구 이름을 명시하십시오("발신자가 관심을 확인한 후, 이메일과 전화번호를 사용하여 save_contact를 호출하십시오; 그 후 1 영업일 후 schedule_followup을 호출하십시오")
전체 대화를 주도해야 하는 동작(리드 항상 캡처, 요금 절대 인용 금지 등)에이전트의 soul / task

지시사항에 도구 이름을 명시하지 않을 때의 실패 모드: 모든 것이 구성된 것처럼 보입니다 — 도구가 체크되고, 스킬이 로드됨 — 하지만 에이전트는 여전히 해당 도구를 호출하지 않거나, 추측된 인수로 호출합니다. 오류가 발생하지 않습니다. 새로운 직원을 브리핑하듯이 절차를 작성하십시오: 단계, 도구 이름, 인수, 그리고 "완료"의 기준을 명시하십시오.

필수 도구 호출: 트리거는 instructions뿐만 아니라 description에 있어야 합니다

instructions는 모델이 load_skill을 호출한 후에만 모델에게 표시됩니다 — 그리고 모델은 종종 스킬을 로드하지 않고 직접 답변합니다. 따라서 "사용자가 전화번호를 제공하면 save_contact를 호출해야 한다"는 규칙이 instructions에만 작성되어 있다면, 정작 중요한 순간에 해당 규칙이 보이지 않게 됩니다: 도구가 조용히 호출되지 않으며, 모델은 심지어 연락처가 저장되었다고 주장할 수도 있습니다. 이는 프로덕션 환경에서 실제로 발생했습니다 — 에이전트가 연락처가 포함된 여러 메시지를 연속으로 답변하고, 세부 정보가 기록되었음을 "확인"했지만, 실제 기록은 존재하지 않았습니다.

해결책은 description(매 턴 시스템 프롬프트에 포함됨)에 한 문장을 추가하는 것입니다:

update_skill(
  name="consultative-sales",
  description="Consultative sales: understand the case, recommend products, arrange expert callbacks. "
              "When the user provides a phone number or email, call save_contact BEFORE answering anything else.",
)

자세한 절차는 instructions에 보관하고, 필수 호출의 트리거description에 명시하십시오.

관련된 두 가지 규칙이 더 있습니다:

  • 도구가 반환하지 않는 반환값을 스크립트하지 마십시오 — 실제 반환값을 그대로 전달하십시오. 내장형 save_contactschedule_followup은 성공 시 실제 참조 코드(형식 AB2C-D3EF)를 반환합니다(서버 측에 저장되며, 최종 사용자의 상세 페이지에서 테넌트가 확인할 수 있음). 모델에게 도구 결과의 코드를 그대로 전달하도록 지시하십시오 — "#12345"와 같이 임의로 생성하거나 포맷을 변경하지 마십시오. 다른 도구의 경우, 스크립트하기 전에 해당 도구가 실제로 ID를 반환하는지 확인하십시오: 약속되었지만 존재하지 않는 ID는 모델이 fabrication할 것입니다.
  • create_skill / update_skill / get_skill은 이제 warnings 목록을 반환합니다. 이 목록은 정확히 두 가지 패턴(trigger_hidden_in_instructions, promised_tool_return_id)을 플래그합니다. 경고는 advisory(권고) 성격이며 저장은 성공하지만, 린터(linter)처럼 취급하십시오 — 수정하십시오, 무시하지 마십시오.

instructions의 작업 예시는 도구 호출 준수율을 측정 가능하게 높입니다

이 내용을 프로덕션 백엔드에서 벤치마킹했습니다(증가하는 난이도의 6개 리드 캡처 메시지 — 긴 질문 속에 숨겨진 번호, 공백이 있는 숫자 그룹, 수정 사항 등 — 조건별로 샘플링). 스킬이 로드된 상태에서 단순한 "X를 호출해야 한다"는 지시는 3~4/6의 준수율을 보였습니다. 짧은 작업 예시 블록을 추가하자 테스트된 두 모델 모두 6/6으로 향상되었습니다. 지시 섹션의 위치를 변경하는 것은 아무 효과도 없었습니다 — 위치에는 의미가 없으며, 예시가 중요합니다.

효과적인 예시 블록에는 네 가지 유형의 항목이 있으며, 각각 한 줄로 구성됩니다:

### Worked examples (follow exactly)
 
1. User: "I'd like to know about X, my phone is 13800138000"
   → call save_contact(phone="13800138000") first, then answer about X.
2. User: "email me the offer: li@example.com"
   → call save_contact(email="li@example.com").
3. COUNTER-EXAMPLE (forbidden): user gives a phone number and you reply
   "I've noted it down" WITHOUT calling the tool — claiming success without
   the call is the worst failure.
4. User: "sorry, wrong number — it's 13633334444"
   → call save_contact again with the corrected value.
5. Numbers may contain spaces ("138 0013 9000") — still a phone number;
   strip the spaces and call save_contact(phone="13800139000").

반례(3)와 형식 경계 사례(5)는 나머지 실패의 대부분을 막아줍니다 — 모델은 인식("이것이 전화번호인가?")과 압력 하의 정직성(풍부한 도메인 질문에 먼저 답변하고 저장이 발생했다고 주장)에서 실패할 가능성이 높으며, 의지 부족보다는 이러한 부분에서 더 취약합니다. 항목 수는 약 5개로 유지하십시오 — 실제적인 인수를 사용하여 정확한 호출 구문을 사용하십시오.

프로덕션에서 시체를 세지 마십시오 — 출하 전에 트리거를 테스트하십시오

스킬이 실제로 발동하는지는 측정 가능하므로, 측정하십시오. test_skill_trigger프로덕션 프롬프트 조립, 도구 스키마 및 모델 라우팅을 대상으로 메시지 dry-run을 수행하고, 모델이 내린 결정을 보고합니다 — 도구는 실행되지 않으며, 데이터가 저장되지 않으며, 토큰은 할당량에 포함됩니다(캡: 5 메시지 × 5 샘플).

test_skill_trigger(
  agent="advisor",
  messages=[
    "My phone is 555 0123, call me back",          # easy
    "long question about the product … oh and my number is 555 0123",  # buried
    "555 0123 — that's me",                        # implicit
    "sorry, wrong number, it's 555 9999",          # correction
  ],
  expect_tool="save_contact",
  samples=3,
  loaded=true,        # simulate post-load_skill → tests instructions quality
)                     # loaded=false (default) → first turn, tests the description trigger

결과를 다음과 같이 읽으십시오:

  • **hit_rate**가 현실적인 메시지 기준 ~90% 미만일 경우 → 트리거(description)를 강화하거나 작업 예시(instructions)를 추가한 후 재테스트하십시오.
  • claimed_without_call > 0은 가장 심각한 실패 유형입니다 — 모델이 도구를 호출하지 않은 채 사용자에게 "확인했습니다!"라고 말한 경우. 위의 예시 블록에서 반례를 추가하십시오.
  • 두 모드 모두 테스트하십시오: loaded=false는 첫 번째 턴에서 description만으로 트리거가 작동함을 증명합니다. loaded=true는 로드된 instructions가 이를 희석시키지 않음을 증명합니다(긴 instructions는 측정 가능하게 희석 효과를 일으킵니다 — 이것이 작업 예시가 상쇄하는 대상입니다).

결과에는 advice 목록도 포함됩니다: 샘플이 누락되거나 거짓일 경우, 각 권장 사항 뒤에 벤치마크 수치를 제시하며 어떤 수정을 적용해야 하는지 정확히 알려줍니다(트리거를 description에 추가, 작업 예시 블록 추가, 반례 또는 형식 경계 사례 추가 등) — 적용하고 재테스트하십시오.

전체 루프: create_skillwarnings 수정(정적 린트) → test_skill_trigger(동적 현실 검증) → advice 적용 → hit rate가 유지될 때까지 재테스트.