LangChain의 "Skills" 이해하기: 필요할 때마다 전문 지식 로드하기
요약
LangChain의 'skills' 패턴을 통해 에이전트의 프롬프트 비대화 문제를 해결하는 방법을 설명합니다. 모든 지침을 하나의 프롬프트에 넣는 대신, 필요할 때만 특정 전문 지식을 로드하는 점진적 공개 방식을 제안합니다.
핵심 포인트
- 거대 프롬프트 사용 시 토큰 비용 증가 및 성능 저하 발생
- 지침 혼선 및 확장성 부족 문제 해결 필요
- skills 패턴은 필요할 때만 관련 지침을 주입하는 방식
- 점진적 공개(progressive disclosure) 원칙 적용
모든 에이전트 빌더(agent builder)가 결국 맞닥뜨리게 되는 문제가 있습니다. 바로 당신의 어시스턴트가 매우 많은 일들을 잘해야 한다는 점입니다. SQL 작성하기, 이메일 작성하기, 코드 디버깅하기, 그리고 나중에는 아마 더 많은 것들이 필요할 것입니다. 그렇다면 이 모든 지침(instructions)은 어디로 가야 할까요?
뻔한 답은 이 모든 것을 시스템 프롬프트(system prompt)에 쑤셔 넣는 것입니다. 하지만 그 "뻔한" 답은 에이전트가 성장함에 따라 실제적인 문제들을 일으킵니다. 이 포스트에서는 먼저 이러한 문제들을 살펴본 다음, 작은 작동 예제를 통해 LangChain의 skills 패턴이 이를 어떻게 해결하는지 보여드리겠습니다.
문제점: 하나의 거대한 프롬프트
SQL을 작성하고, 이메일 초안을 잡으며, 코드 디버깅을 도울 수 있는 단일 어시스턴트를 원한다고 가정해 봅시다. 가장 단순한 접근 방식은 하나의 커다란 시스템 프롬프트를 사용하는 것입니다:
당신은 유능한 어시스턴트입니다.
SQL을 작성할 때:
...
세 가지 영역(domain)에 대해서는 이 방식이 잘 작동합니다. 하지만 법률 검토, 데이터 분석, 고객 지원 스크립트, 코드 리뷰 표준 등 더 많은 영역을 계속 추가하다 보면 몇 가지 구체적인 문제에 직면하게 됩니다:
1. 프롬프트 비대화 (Prompt bloat). 사용자가 SQL에 대해서만 물어보더라도, 모든 요청마다 모든 영역의 지침에 대한 토큰 비용을 지불해야 합니다. 이는 이득 없이 속도는 더 느려지고 비용은 더 많이 들게 만듭니다.
2. 지침의 혼선. SQL 규칙, 이메일 규칙, 디버깅 규칙이 모두 동일한 텍스트 블록에 모여 있으면, 모델은 한꺼번에 모든 것을 걸러내야 합니다. 한 영역의 지침이 다른 영역으로 스며들거나, 모델이 어떤 규칙이 어디에 적용되는지 놓치기 쉽습니다.
3. 확장성 부족 (It doesn't scale). 새로운 전문 분야를 추가하고 싶을 때마다, 점점 더 취약해지는 하나의 긴 프롬프트를 편집해야 합니다. 테스트도 더 어려워집니다. "디버거" 지침을 위한 변경 사항이 실수로 모델이 이메일을 작성하는 방식에 영향을 줄 수 있기 때문입니다.
4. 대부분의 경우 대부분의 내용이 무관함. 사용자가 이메일 작성을 도와달라고 요청할 때, 모델은 그 작업을 수행하는 동안 SQL 인젝션(SQL injection) 규칙을 알 필요가 없습니다. 그럼에도 불구하고 이를 계속 들고 다니는 것은 매 호출마다 불필요한 짐이 될 뿐입니다.
기본적으로는 모든 것을 하나의 프롬프트에 넣으면 모든 지식을 항상 관련 있는 것으로 취급하게 되는데, 실제로는 주어진 요청과 관련된 일부 조각만 유효합니다.
해결책: 필요할 때 전문 지식 로드하기
skills 패턴은 좋은 소프트웨어 디자인에서 차용한 아이디어인 **점진적 공개(progressive disclosure)**로 이를 해결합니다. 모든 것을 처음부터 보여주지 말고, 실제로 필요할 때만 세부 정보를 드러내는 것입니다.
모든 도메인의 규칙을 담고 있는 거대한 하나의 프롬프트 대신, 각 도메인의 규칙은 'skill'로 별도로 존재합니다. 에이전트는 가벼운 기본 프롬프트와 단일 도구인 load_skill로 시작합니다. 요청에 전문 지식이 필요하다고 인식하면, 이 도구를 호출하여 관련 지침을 가져온 다음 이를 프롬프트에 주입합니다. 다만 이는 해당 턴(turn)에서만, 그리고 해당 도메인에 대해서만 유효합니다.
예제 코드가 이것을 어떻게 구성하는지 살펴보겠습니다.
1. Skill은 저장된 프롬프트일 뿐입니다
SKILLS = {
"sql_expert": """
You are a SQL expert. Follow these rules:
...
별다른 복잡한 것이 없습니다. skill 이름을 지침 블록에 매핑하는 딕셔너리일 뿐입니다. 각각은 독립적입니다. 기본 에이전트는 이 콘텐츠 중 어떤 것도 미리 알 필요가 없다는 점에 주목하세요. 단지 이러한 skill들이 _존재_하며 대략적으로 무엇을 위한 것인지만 알면 됩니다.
2. State는 현재 활성화된 skill을 추적합니다
class SkillState(AgentState):
loaded_skill: str = ""
handoffs 패턴과 같은 아이디어입니다. 비어 있는 상태로 시작하여 에이전트가 전문 지식이 필요하다고 결정하면 채워지는 상태 조각입니다.
3. skill을 가져와 state에 저장하는 도구
@tool
def load_skill(
skill_name: str,
...
주목할 만한 몇 가지 사항이 있습니다:
- 도구의 docstring(문서화 문자열)은 모델이 언제 그리고 어떤 기술을 로드할지 결정하기 위해 실제로 보는 정보입니다. 여기에는 사용 가능한 기술 목록과 각 기술에 대한 짧은 설명이 포함됩니다. 이는 기본 프롬프트(base prompt)가 계속 들고 다녀야 할 전문 지식의 유일한 "인덱스(index)"이며, 기술의 전체 지침(instructions) 자체를 포함하는 것이 아닙니다.
- 만약 모델이 존재하지 않는 기술을 요청하더라도 도구는 충돌(crash)하지 않습니다. 대신 현재 사용 가능한 기술 목록을 담은 유용한 메시지를 응답하여 모델이 다시 시도할 수 있도록 합니다.
- 성공 시, 실제 지침(instructions) 텍스트가 상태(state) 내의
loaded_skill에 저장됩니다. 바로 이 순간 전문 지식이 등장하게 됩니다.
4. 미들웨어(Middleware)가 로드된 기술을 프롬프트에 주입합니다
@wrap_model_call
def inject_skill(
request: ModelRequest,
...
이 과정은 handoffs 예제와 마찬가지로 모든 모델 호출(model call) 전에 실행됩니다. 만약 기술이 로드되었다면, 시스템 프롬프트(system prompt)를 재작성하여 해당 특정 지침들을 포함시키며, 오직 그 지침들만 포함합니다. 아직 로드된 기술이 없다면 프롬프트는 일반적인 기본 프롬프트 상태를 유지합니다.
이 부분이 실제로 "점진적 공개 (progressive disclosure)"를 구현하는 핵심입니다. SQL 규칙은 sql_expert가 로드되었을 때만 프롬프트에 들어갑니다. 이메일 규칙은 email_writer가 요청되지 않는 한 절대 나타나지 않습니다. 모델이 현재 작업 중이지 않은 도메인의 지침을 들고 있는 상황은 발생하지 않습니다.
5. 전체 연결하기
agent = create_agent(
model=free_llm,
tools=[load_skill],
...
기본 system_prompt가 얼마나 짧은지 주목하세요. 여기에는 단 하나의 SQL 규칙, 이메일 형식, 또는 디버깅 단계도 포함되어 있지 않습니다. 그저 모델에게 기술이 존재하며 이를 어떻게 가져오는지(fetch)만 알려줍니다. 이것이 핵심입니다. 나중에 얼마나 많은 기술을 추가하더라도 기본 프롬프트는 작고 안정적인 상태를 유지합니다.
실행 과정 살펴보기
response = agent.invoke({
"messages": [{
"role": "user",
...
단계별로 일어나는 일은 다음과 같습니다:
단계별로 일어나는 일은 다음과 같습니다:
- 모델은 기본 프롬프트(base prompt)를 받습니다 — 아직 도메인 특화 지식은 없습니다.
- 요청을 읽고 이것이 SQL 작업임을 인식한 후,
load_skill(skill_name="sql_expert")를 호출합니다. - 이 도구는
SKILLS에서 "sql_expert"를 조회하여 찾고, 전체 명령어 텍스트를loaded_skill에 저장하는Command를 반환합니다. - 에이전트는 모델을 다시 호출하기 위해 루프백(loops back) 합니다. 이번에는 미들웨어(middleware)가
loaded_skill이 더 이상 비어있지 않다는 것을 보고, 시스템 프롬프트(system prompt)를 수정하여 SQL 전문가 규칙을 포함시킵니다. - 이제, 그리고 오직 이때에만, 모델은 대문자 SQL 키워드를 사용하고, 주입 공격(injection-prone) 취약 패턴을 피하며, 복잡한 쿼리에 대한 주석을 추가해야 한다는 것을 알게 됩니다.
- 이 규칙들을 따라 쿼리를 작성하고 사용자에게 응답합니다.
만약 바로 다음 메시지가 대신 이메일 작성을 요청했다면, 모델은 load_skill("email_writer")를 호출할 것이고, SQL 지침은 완전히 이메일용 지침으로 교체될 것입니다. 아무것도 남아있지 않습니다.
이것이 실제로 중요한 이유
처음의 문제들로 돌아가 보겠습니다:
- 더 이상 프롬프트 비대화(prompt bloat) 문제가 없습니다. 기본 프롬프트는 몇 줄에 불과합니다. 특화된 지침은 관련성이 있을 때만 추가되므로, 대부분의 요청이 사용하지 않는 지식에 대해 비용을 지불하지 않습니다.
- 흐릿한 지침(blurred instructions)도 사라집니다. 각 스킬은 격리되어 있습니다. SQL 형식 규칙이 이메일 작성 방식에 조용히 영향을 줄 위험이 없습니다. 왜냐하면 그 규칙들이 같은 시간에 프롬프트 안에 존재하지 않기 때문입니다.
- 확장성이 뛰어납니다(It scales). 새로운 전문 분야 — 예를 들어,
code_reviewer스킬 — 를 추가한다는 것은SKILLS딕셔너리에 새 항목 하나와 도구의 docstring에 한 줄을 추가하는 것을 의미합니다. 기본 에이전트, 미들웨어, 그리고 기존의 모든 스킬은 건드리지 않고 그대로 유지됩니다. - 관련된 지식만 필요할 때 나타납니다. 이것이 정말 핵심적인 이점입니다. 모델의 주의(attention)가 수십 개의 관련 없는 규칙 세트에 분산되지 않고, 눈앞의 작업에 정확히 필요한 하나에 집중됩니다.
기억해야 할 정신적 모델
이것을 모든 과목이 스테이플러로 찍혀 있는 교과서 대신, 참고 도서관(reference library)이라고 생각해보세요. 누군가 질문을 할 때마다 도서관 전체를 통째로 건네주지는 않습니다. 대신 인덱스(기술 이름과 짧은 설명)를 가리켜주고, 그들은 이 특정 질문에 실제로 필요한 책 한 권을 대출합니다. 나머지 모든 것은 나중에 필요할 때를 대비해 서가에 그대로 남아 있지만, 그동안 책상을 어지럽히지는 않습니다.
이것이 LangChain에서 기술(skills)을 사용하는 핵심 아이디어입니다. 기본 프롬프트(base prompt)를 가볍게 유지하고, 어떤 전문 지식을 사용할 수 있는지 설명한 뒤, 요청이 실제로 발생했을 때만 에이전트(agent)가 세부 사항을 가져오도록 하는 것입니다.
직접 시도해보기
자연스러운 다음 단계는 다른 기술과 결합할 수 있는 기술을 추가하는 것입니다. 예를 들어, 누군가 잘못된 쿼리를 수정하는 데 도움이 필요한 경우를 위해 sql_expert 기술과 debugger 기술을 동시에 사용할 수 있도록 하는 것입니다. 이를 위해 다음 작업이 필요합니다:
loaded_skill이 하나의 기술을 보유할지, 아니면 활성화된 기술의 리스트를 보유할지 결정- 하나 이상의 기술이 활성화된 경우, 여러 개의 로드된 기술을 프롬프트에 결합하도록
inject_skill을 업데이트
이 한 가지 변화만으로도 동일한 핵심 패턴을 유지하면서, 단순히 기술을 전환하는 것을 넘어 전문 분야들을 결합할 수 있는 문이 열립니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기