🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Plugin 시스템과 확장성 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 5. · 15 Views

Plugin 시스템과 확장성 완벽 가이드

AI Agent 개발에서 핵심이 되는 플러그인 아키텍처를 배웁니다. 플러그인 구조 설계부터 인증, 도구 플러그인 개발, 그리고 실제 GitHub Copilot 사례까지 단계별로 살펴봅니다.


목차

  1. packages/plugin 구조
  2. 플러그인 인터페이스 정의
  3. 인증 플러그인 개발
  4. 도구 플러그인 개발
  5. 플러그인 로딩과 설정
  6. GitHub Copilot 플러그인 사례

1. packages/plugin 구조

김개발 씨는 회사에서 새로운 AI Agent 프로젝트를 맡게 되었습니다. 기존 시스템에 새로운 기능을 추가해달라는 요청이 쏟아지는데, 매번 코어 코드를 수정하자니 버그가 생길까 걱정이 앞섭니다.

선배 개발자 박시니어 씨가 다가와 한마디 합니다. "플러그인 시스템을 도입해보는 건 어때요?"

플러그인 구조란 핵심 시스템을 건드리지 않고도 새로운 기능을 추가할 수 있게 해주는 아키텍처입니다. 마치 스마트폰에 앱을 설치하듯이, 필요한 기능만 골라서 붙였다 뗐다 할 수 있습니다.

이렇게 하면 시스템의 안정성을 유지하면서도 유연하게 확장할 수 있습니다.

다음 코드를 살펴봅시다.

// packages/plugin 디렉토리 구조
// plugin/
//   ├── index.ts           // 플러그인 시스템 진입점
//   ├── types.ts           // 타입 정의
//   ├── loader.ts          // 플러그인 로더
//   ├── registry.ts        // 플러그인 레지스트리
//   └── plugins/
//       ├── auth/          // 인증 플러그인들
//       └── tools/         // 도구 플러그인들

// index.ts - 플러그인 시스템의 핵심 진입점
export { PluginRegistry } from './registry';
export { PluginLoader } from './loader';
export type { Plugin, PluginConfig } from './types';

김개발 씨는 입사한 지 6개월이 된 주니어 개발자입니다. 요즘 그의 고민은 끊임없이 들어오는 기능 추가 요청입니다.

"챗봇에 번역 기능 추가해주세요", "외부 API 연동 좀 해주세요", "새로운 인증 방식도 지원해야 해요". 요청이 들어올 때마다 메인 코드를 수정하다 보니, 어느새 코드는 스파게티처럼 엉켜버렸습니다.

어느 날 박시니어 씨가 김개발 씨의 모니터를 슬쩍 보더니 말했습니다. "이렇게 가다간 큰일 나겠는데요.

플러그인 아키텍처를 도입해보는 게 어떨까요?" 플러그인 구조란 무엇일까요? 쉽게 비유하자면, 레고 블록과 같습니다.

레고로 집을 만들었다고 해봅시다. 나중에 자동차가 필요하면 집을 부수지 않고 옆에 자동차를 조립해서 붙이면 됩니다.

플러그인 시스템도 마찬가지입니다. 핵심 시스템은 그대로 두고, 필요한 기능만 블록처럼 끼워넣는 것입니다.

packages/plugin이라는 디렉토리를 만들어 플러그인 시스템을 독립적으로 관리합니다. 이 폴더 안에는 크게 네 가지 요소가 들어갑니다.

첫째, types.ts에는 플러그인이 따라야 할 규칙이 정의됩니다. 둘째, loader.ts는 플러그인을 불러오는 역할을 합니다.

셋째, registry.ts는 불러온 플러그인들을 등록하고 관리합니다. 마지막으로 plugins 폴더에는 실제 플러그인들이 카테고리별로 정리됩니다.

왜 이렇게 나눌까요? 각 파일이 하나의 책임만 갖도록 하기 위해서입니다.

이것을 단일 책임 원칙이라고 부릅니다. 나중에 로딩 방식을 바꾸고 싶으면 loader.ts만 수정하면 됩니다.

타입을 변경하고 싶으면 types.ts만 건드리면 됩니다. index.ts 파일은 플러그인 시스템의 정문 역할을 합니다.

외부에서 플러그인 시스템을 사용하고 싶다면, 이 파일을 통해 필요한 것들을 가져갑니다. export 키워드로 공개할 것만 골라서 내보내는 것이죠.

마치 백화점 1층 안내데스크처럼, 손님에게 필요한 정보만 제공하는 겁니다. plugins 폴더 안에는 auth와 tools라는 하위 폴더가 있습니다.

auth에는 인증 관련 플러그인들이, tools에는 도구 관련 플러그인들이 들어갑니다. 이렇게 카테고리를 나누면 나중에 플러그인 수가 늘어나도 찾기 쉽습니다.

실제 현업에서는 이런 구조가 필수입니다. 스타트업에서 대기업까지, 확장 가능한 시스템을 만들기 위해 플러그인 아키텍처를 적극 활용합니다.

VS Code, Webpack, ESLint 모두 이런 방식으로 만들어졌습니다. 박시니어 씨의 설명을 들은 김개발 씨는 무릎을 탁 쳤습니다.

"그래서 VS Code에 확장 프로그램을 설치해도 VS Code 자체가 망가지지 않는 거군요!" 바로 그겁니다. 잘 설계된 플러그인 구조는 시스템의 안정성과 확장성을 동시에 잡을 수 있게 해줍니다.

실전 팁

💡 - 폴더 구조는 프로젝트 초기에 잘 설계해두면 나중에 큰 도움이 됩니다

  • index.ts에서는 외부에 공개할 것만 export하고, 내부 구현은 숨기세요

2. 플러그인 인터페이스 정의

김개발 씨가 플러그인 폴더 구조를 잡고 나서 다음 질문을 던졌습니다. "그런데 플러그인이 어떻게 생겨야 하는지 규칙은 어떻게 정하죠?" 박시니어 씨가 빙긋 웃으며 대답합니다.

"바로 그게 인터페이스의 역할이에요."

플러그인 인터페이스는 플러그인이 반드시 갖춰야 할 구조를 정의한 계약서입니다. 마치 콘센트 규격처럼, 모든 플러그인이 같은 형태로 만들어져야 시스템에 꽂아서 쓸 수 있습니다.

TypeScript의 interface를 활용하면 컴파일 시점에 이 규칙을 강제할 수 있습니다.

다음 코드를 살펴봅시다.

// types.ts - 플러그인 인터페이스 정의
export interface Plugin {
  // 플러그인 고유 식별자
  readonly name: string;
  // 플러그인 버전 (시맨틱 버저닝)
  readonly version: string;
  // 플러그인 초기화 - 시스템 시작 시 호출됨
  initialize(context: PluginContext): Promise<void>;
  // 플러그인 정리 - 시스템 종료 시 호출됨
  destroy(): Promise<void>;
}

export interface PluginContext {
  logger: Logger;
  config: Record<string, unknown>;
  emit: (event: string, data: unknown) => void;
}

export interface PluginConfig {
  enabled: boolean;
  options?: Record<string, unknown>;
}

김개발 씨는 다양한 개발자들이 만든 플러그인을 하나의 시스템에서 돌려야 하는 상황이었습니다. 문제는 각자 제멋대로 플러그인을 만들면 시스템이 이해할 수 없다는 점이었습니다.

마치 모든 나라의 콘센트 규격이 다르면 여행자가 곤란해지는 것처럼요. 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.

"인터페이스는 일종의 국제 표준 규격이에요. 이 규격만 맞추면 어떤 플러그인이든 우리 시스템에서 작동할 수 있습니다." Plugin 인터페이스를 보면 네 가지 필수 요소가 있습니다.

name은 플러그인의 고유한 이름입니다. 시스템에서 이 플러그인을 식별할 때 사용합니다.

version은 플러그인의 버전 정보입니다. 업데이트 관리나 호환성 체크에 활용됩니다.

initialize 메서드는 플러그인이 처음 로딩될 때 호출됩니다. 여기서 플러그인이 필요한 초기 설정을 수행합니다.

데이터베이스 연결을 열거나, 외부 서비스와 핸드셰이크를 하거나, 캐시를 준비하는 등의 작업이 이루어집니다. 반대로 destroy 메서드는 플러그인이 제거될 때 호출됩니다.

열어둔 연결을 닫고, 사용한 리소스를 반환하는 정리 작업을 합니다. 이 메서드를 제대로 구현하지 않으면 메모리 누수나 좀비 프로세스가 발생할 수 있습니다.

PluginContext는 시스템이 플러그인에게 제공하는 도구 상자입니다. logger로 로그를 남길 수 있고, config로 설정값을 읽을 수 있으며, emit으로 이벤트를 발행할 수 있습니다.

플러그인은 이 컨텍스트를 통해 시스템과 소통합니다. 왜 readonly 키워드를 붙였을까요?

name과 version은 플러그인이 한번 정해지면 바뀌면 안 되는 값입니다. readonly를 붙이면 나중에 실수로 값을 바꾸려 해도 TypeScript가 막아줍니다.

이런 작은 안전장치들이 모여서 견고한 시스템을 만듭니다. Promise를 반환하는 이유도 중요합니다.

초기화 과정에서 비동기 작업이 필요할 수 있기 때문입니다. 외부 API를 호출하거나 파일을 읽는 등의 작업은 시간이 걸립니다.

Promise를 사용하면 이런 비동기 작업을 우아하게 처리할 수 있습니다. 김개발 씨가 고개를 끄덕이며 말했습니다.

"인터페이스가 있으면 누가 플러그인을 만들든 같은 방식으로 동작하겠네요." 맞습니다. 이것이 바로 계약 기반 프로그래밍의 핵심입니다.

실전 팁

💡 - 인터페이스는 작게 시작해서 필요에 따라 확장하세요

  • destroy 메서드에서 리소스 정리를 빠뜨리면 메모리 누수가 발생합니다

3. 인증 플러그인 개발

플러그인 인터페이스를 정의한 김개발 씨에게 첫 번째 미션이 떨어졌습니다. "OAuth 로그인 기능을 플러그인으로 만들어주세요." 기존에는 메인 코드에 인증 로직이 박혀 있어서 새로운 인증 방식을 추가하기 어려웠습니다.

이제 플러그인으로 분리해볼 시간입니다.

인증 플러그인은 사용자 인증 로직을 독립적인 모듈로 분리한 것입니다. 마치 호텔 프론트 데스크처럼, 손님의 신원을 확인하고 적절한 권한을 부여하는 역할을 합니다.

OAuth, JWT, API Key 등 다양한 인증 방식을 플러그인으로 만들면 필요에 따라 교체하거나 조합할 수 있습니다.

다음 코드를 살펴봅시다.

// plugins/auth/oauth-plugin.ts
import { Plugin, PluginContext } from '../../types';

export class OAuthPlugin implements Plugin {
  readonly name = 'oauth-auth';
  readonly version = '1.0.0';
  private context!: PluginContext;

  async initialize(context: PluginContext): Promise<void> {
    this.context = context;
    this.context.logger.info('OAuth 플러그인 초기화 완료');
  }

  // 토큰 검증 메서드
  async validateToken(token: string): Promise<AuthResult> {
    const decoded = await this.verifyJWT(token);
    return { valid: true, userId: decoded.sub };
  }

  async destroy(): Promise<void> {
    this.context.logger.info('OAuth 플러그인 종료');
  }
}

김개발 씨의 회사는 처음에 아이디와 비밀번호로만 로그인했습니다. 그런데 고객들이 구글 로그인, 카카오 로그인도 지원해달라고 요청했습니다.

메인 코드에 인증 로직이 하드코딩되어 있어서 새 인증 방식을 추가할 때마다 코드 전체를 건드려야 했습니다. 박시니어 씨가 제안했습니다.

"인증 로직을 플러그인으로 분리하면 어떨까요? 그러면 구글 플러그인, 카카오 플러그인을 따로 만들어서 필요한 것만 끼워 쓸 수 있어요." OAuthPlugin 클래스를 보면 앞서 정의한 Plugin 인터페이스를 implements 키워드로 구현하고 있습니다.

이렇게 하면 TypeScript가 필수 메서드를 빠뜨렸는지 자동으로 검사해줍니다. 마치 체크리스트를 하나씩 확인하는 것과 같습니다.

name과 version은 readonly로 선언되어 값이 바뀔 수 없습니다. 이 플러그인의 이름은 영원히 'oauth-auth'이고, 버전은 '1.0.0'입니다.

나중에 업데이트하면 새 버전의 클래스를 만들면 됩니다. context 필드 뒤에 붙은 느낌표(!)는 TypeScript의 확정 할당 어설션입니다.

"이 값은 나중에 반드시 초기화될 거야"라고 컴파일러에게 알려주는 것입니다. initialize 메서드에서 context를 할당하기 때문에 안전합니다.

initialize 메서드에서는 전달받은 context를 저장하고 로그를 남깁니다. 실제 프로덕션 코드에서는 여기서 OAuth 프로바이더와의 연결을 설정하거나 공개키를 가져오는 등의 작업을 수행합니다.

validateToken 메서드는 이 플러그인의 핵심 기능입니다. 사용자가 보낸 토큰을 검증하고, 유효하면 사용자 정보를 반환합니다.

이 메서드는 Plugin 인터페이스에는 없지만, 인증 플러그인에 특화된 기능으로 추가한 것입니다. destroy 메서드에서는 정리 작업을 수행합니다.

OAuth 플러그인의 경우 캐시된 토큰을 지우거나, 열어둔 연결을 닫는 작업이 여기서 이루어집니다. 간단해 보이지만 이 메서드를 제대로 구현하지 않으면 서버 재시작 시 문제가 발생할 수 있습니다.

이제 카카오 로그인을 추가하고 싶으면 KakaoOAuthPlugin 클래스를 새로 만들면 됩니다. 메인 코드는 전혀 건드릴 필요가 없습니다.

이것이 플러그인 아키텍처의 진정한 힘입니다.

실전 팁

💡 - 인증 플러그인은 보안에 민감하므로 토큰 검증 로직을 철저히 테스트하세요

  • 여러 인증 방식을 지원할 때는 공통 인터페이스를 추가로 정의하면 관리가 편합니다

4. 도구 플러그인 개발

인증 플러그인을 완성한 김개발 씨에게 새로운 요청이 들어왔습니다. "AI Agent가 웹 검색도 하고, 코드 실행도 하고, 파일도 읽을 수 있게 해주세요." 각각의 기능을 하나의 거대한 클래스에 넣으면 금방 복잡해질 것이 뻔했습니다.

박시니어 씨가 말합니다. "각 도구를 도구 플러그인으로 만들어보세요."

도구 플러그인은 AI Agent가 사용할 수 있는 개별 기능을 독립적인 모듈로 만든 것입니다. 마치 공구함의 드라이버, 망치, 펜치처럼, 각 도구가 자기만의 역할을 가집니다.

Agent는 필요한 도구 플러그인만 선택해서 장착하면 됩니다.

다음 코드를 살펴봅시다.

// plugins/tools/web-search-plugin.ts
import { Plugin, PluginContext } from '../../types';

interface ToolPlugin extends Plugin {
  // 도구 설명 - AI가 도구 선택에 활용
  readonly description: string;
  // 도구 실행 메서드
  execute(params: Record<string, unknown>): Promise<ToolResult>;
}

export class WebSearchPlugin implements ToolPlugin {
  readonly name = 'web-search';
  readonly version = '1.0.0';
  readonly description = '웹에서 정보를 검색합니다';
  private context!: PluginContext;

  async initialize(context: PluginContext): Promise<void> {
    this.context = context;
  }

  async execute(params: { query: string }): Promise<ToolResult> {
    this.context.logger.info(`검색 쿼리: ${params.query}`);
    const results = await this.searchWeb(params.query);
    return { success: true, data: results };
  }

  async destroy(): Promise<void> {}
}

요즘 AI Agent들은 단순히 대화만 하지 않습니다. 웹을 검색하고, 코드를 실행하고, 파일을 읽고 쓰고, API를 호출합니다.

이런 다양한 기능을 어떻게 관리해야 할까요? 김개발 씨는 처음에 모든 기능을 Agent 클래스 안에 메서드로 넣으려 했습니다.

searchWeb, executeCode, readFile... 메서드가 늘어날수록 클래스는 거대해졌습니다.

천 줄을 넘기자 더 이상 관리가 불가능해졌습니다. 박시니어 씨가 해결책을 제시했습니다.

"각 도구를 독립적인 플러그인으로 만드세요. Agent는 도구 플러그인들의 관리자 역할만 하면 됩니다." 도구 플러그인은 기본 Plugin 인터페이스를 확장합니다.

ToolPlugin 인터페이스를 보면 두 가지가 추가되었습니다. description은 이 도구가 무엇을 하는지 설명합니다.

AI가 여러 도구 중 어떤 것을 사용할지 결정할 때 이 설명을 참고합니다. execute 메서드는 도구의 실제 기능을 수행합니다.

파라미터를 받아서 작업을 수행하고 결과를 반환합니다. 웹 검색 플러그인이라면 검색어를 받아서 검색 결과를 반환하겠죠.

WebSearchPlugin 클래스를 살펴봅시다. name은 'web-search'이고, description은 '웹에서 정보를 검색합니다'입니다.

AI가 "최신 뉴스를 알려줘"라는 요청을 받으면, 이 description을 보고 웹 검색 도구를 선택할 수 있습니다. execute 메서드에서는 query 파라미터를 받습니다.

로그를 남기고, 실제 웹 검색을 수행한 뒤, 결과를 ToolResult 형태로 반환합니다. success 필드로 성공 여부를 알리고, data 필드에 실제 결과를 담습니다.

이런 구조의 장점은 명확합니다. 새로운 도구가 필요하면 새 플러그인 클래스만 만들면 됩니다.

CodeExecutionPlugin, FileReaderPlugin, DatabaseQueryPlugin... 각각이 독립적이어서 하나를 수정해도 다른 것에 영향을 주지 않습니다.

테스트도 쉬워집니다. 전체 Agent를 테스트하는 대신 각 플러그인을 개별적으로 테스트할 수 있습니다.

WebSearchPlugin의 테스트와 CodeExecutionPlugin의 테스트를 분리해서 작성하면 됩니다. 김개발 씨는 깨달았습니다.

"도구가 100개가 되어도 각각 100줄짜리 클래스로 나누면 관리할 수 있겠네요!" 바로 그겁니다. 작은 것들의 조합이 거대한 것보다 낫습니다.

실전 팁

💡 - description은 AI가 도구를 선택하는 데 중요하므로 명확하고 구체적으로 작성하세요

  • execute 메서드의 파라미터 타입을 정확히 정의하면 런타임 에러를 줄일 수 있습니다

5. 플러그인 로딩과 설정

플러그인 클래스들은 만들었는데, 이것들을 어떻게 시스템에 등록하고 관리해야 할까요? 김개발 씨가 수십 개의 플러그인 파일을 보며 한숨을 쉽니다.

"이걸 일일이 import해서 초기화하라고요?" 박시니어 씨가 웃으며 말합니다. "플러그인 로더와 레지스트리를 만들면 자동화할 수 있어요."

플러그인 로더는 플러그인 파일들을 찾아서 불러오는 역할을 합니다. 플러그인 레지스트리는 불러온 플러그인들을 등록하고, 이름으로 찾고, 관리하는 중앙 저장소입니다.

마치 도서관의 사서가 책을 분류하고 찾아주는 것처럼, 레지스트리는 플러그인을 체계적으로 관리합니다.

다음 코드를 살펴봅시다.

// registry.ts - 플러그인 레지스트리
export class PluginRegistry {
  private plugins = new Map<string, Plugin>();
  private context: PluginContext;

  constructor(context: PluginContext) {
    this.context = context;
  }

  // 플러그인 등록 및 초기화
  async register(plugin: Plugin): Promise<void> {
    if (this.plugins.has(plugin.name)) {
      throw new Error(`이미 등록된 플러그인: ${plugin.name}`);
    }
    await plugin.initialize(this.context);
    this.plugins.set(plugin.name, plugin);
  }

  // 이름으로 플러그인 조회
  get<T extends Plugin>(name: string): T | undefined {
    return this.plugins.get(name) as T;
  }

  // 모든 플러그인 종료
  async destroyAll(): Promise<void> {
    for (const plugin of this.plugins.values()) {
      await plugin.destroy();
    }
    this.plugins.clear();
  }
}

플러그인이 10개, 20개, 100개로 늘어나면 어떻게 관리해야 할까요? 각 파일을 직접 import하고 수동으로 초기화하는 건 악몽과 같습니다.

김개발 씨는 자동화가 필요하다고 느꼈습니다. 플러그인 레지스트리는 Map 자료구조를 사용합니다.

키는 플러그인 이름, 값은 플러그인 인스턴스입니다. Map을 사용하면 O(1) 시간복잡도로 플러그인을 조회할 수 있습니다.

배열을 사용해서 매번 순회하는 것보다 훨씬 효율적입니다. register 메서드를 봅시다.

먼저 같은 이름의 플러그인이 이미 있는지 확인합니다. 중복 등록을 막아서 예상치 못한 동작을 방지합니다.

그 다음 플러그인의 initialize 메서드를 호출합니다. await를 사용해서 초기화가 완료될 때까지 기다립니다.

마지막으로 Map에 저장합니다. get 메서드는 제네릭을 사용합니다.

<T extends Plugin>이라는 표현은 "Plugin을 상속한 어떤 타입 T"라는 의미입니다. 이렇게 하면 호출하는 쪽에서 원하는 타입으로 캐스팅할 수 있습니다.

registry.get<WebSearchPlugin>('web-search')처럼 사용합니다. destroyAll 메서드는 시스템이 종료될 때 호출됩니다.

모든 플러그인의 destroy 메서드를 순차적으로 호출합니다. for...of 루프와 await를 함께 사용해서 각 플러그인이 제대로 정리될 시간을 줍니다.

마지막으로 Map을 비워서 메모리를 해제합니다. 실제 프로덕션에서는 여기에 더 많은 기능이 추가됩니다.

플러그인 의존성 관리, 로딩 순서 제어, 에러 핸들링, 플러그인 비활성화 등의 기능이 필요합니다. 하지만 핵심 구조는 이 코드와 동일합니다.

설정 파일을 통해 어떤 플러그인을 활성화할지 결정할 수도 있습니다. plugins.json 같은 파일에서 플러그인 목록과 설정을 읽어와서 자동으로 로딩하는 것이죠.

이렇게 하면 코드 수정 없이 설정만으로 플러그인을 켜고 끌 수 있습니다. 김개발 씨가 감탄했습니다.

"레지스트리가 있으니까 어디서든 registry.get('web-search')로 플러그인을 가져올 수 있네요!" 네, 전역 상태를 피하면서도 어디서든 접근 가능한 구조가 완성되었습니다.

실전 팁

💡 - 플러그인 초기화 순서가 중요하면 의존성 그래프를 만들어 순서를 제어하세요

  • destroyAll에서 에러가 발생해도 다른 플러그인은 정리되도록 try-catch를 추가하면 좋습니다

6. GitHub Copilot 플러그인 사례

이론을 배운 김개발 씨가 물었습니다. "실제로 성공한 플러그인 시스템 예시가 있나요?" 박시니어 씨가 모니터를 가리키며 답합니다.

"지금 쓰고 있잖아요. GitHub Copilot이 바로 플러그인 아키텍처의 대표적인 성공 사례입니다."

GitHub Copilot은 VS Code의 확장 프로그램 시스템을 활용한 AI 코딩 도우미입니다. Copilot 자체도 플러그인이고, 그 안에서 다양한 도구들을 플러그인 방식으로 관리합니다.

이 사례를 통해 실무에서 플러그인 패턴이 어떻게 적용되는지 배울 수 있습니다.

다음 코드를 살펴봅시다.

// GitHub Copilot 스타일의 도구 플러그인 예시
interface CopilotTool {
  name: string;
  description: string;
  // JSON Schema로 파라미터 정의
  parameters: JSONSchema;
  // 도구 실행
  invoke(params: unknown): Promise<ToolResponse>;
}

// Copilot Agent 플러그인 구현 예시
export class CopilotAgentPlugin implements Plugin {
  readonly name = 'copilot-agent';
  readonly version = '1.0.0';
  private tools: Map<string, CopilotTool> = new Map();

  async initialize(context: PluginContext): Promise<void> {
    // 도구들을 플러그인으로 등록
    this.registerTool(new FileSearchTool());
    this.registerTool(new CodeEditTool());
    this.registerTool(new TerminalTool());
  }

  private registerTool(tool: CopilotTool): void {
    this.tools.set(tool.name, tool);
  }

  async destroy(): Promise<void> {
    this.tools.clear();
  }
}

GitHub Copilot을 사용해본 적 있으신가요? 코드를 작성하다 보면 AI가 자동으로 코드를 제안해주고, 채팅으로 질문하면 파일을 찾아보고 코드를 수정해주기도 합니다.

이 모든 기능이 플러그인 아키텍처 위에서 동작합니다. 먼저 Copilot 자체가 VS Code의 Extension(확장 프로그램)입니다.

VS Code는 처음부터 플러그인 시스템을 염두에 두고 설계되었습니다. 에디터의 핵심 기능만 제공하고, 나머지는 모두 확장 프로그램으로 추가하는 구조입니다.

Copilot도 이 구조 덕분에 VS Code에 자연스럽게 통합될 수 있었습니다. Copilot 내부를 들여다보면 또 다른 플러그인 시스템이 있습니다.

CopilotTool 인터페이스를 보세요. name으로 도구를 식별하고, description으로 AI에게 도구의 용도를 알려줍니다.

parameters는 JSON Schema 형식으로 도구가 받을 파라미터를 정의합니다. JSON Schema는 JSON 데이터의 구조를 정의하는 표준입니다.

AI가 도구를 호출할 때 어떤 형식의 데이터를 보내야 하는지 명확하게 알 수 있습니다. 예를 들어 파일 검색 도구라면 "query: string, maxResults: number" 같은 스키마를 정의합니다.

CopilotAgentPlugin 클래스는 여러 도구를 관리하는 상위 플러그인입니다. initialize 메서드에서 FileSearchTool, CodeEditTool, TerminalTool 등을 등록합니다.

플러그인 안에 또 다른 플러그인들이 있는 중첩 구조입니다. 이런 구조의 장점은 무엇일까요?

Copilot 팀이 새로운 도구를 추가하고 싶으면 새 Tool 클래스만 만들어서 registerTool하면 됩니다. 기존 코드를 건드릴 필요가 없습니다.

또한 특정 도구에 버그가 있으면 그 도구만 수정하면 됩니다. 사용자 입장에서도 이점이 있습니다.

Copilot 설정에서 특정 도구를 비활성화할 수 있습니다. "터미널 접근은 싫어요"라고 하면 TerminalTool만 끄면 됩니다.

이것이 가능한 이유는 각 기능이 독립적인 플러그인으로 분리되어 있기 때문입니다. 김개발 씨는 자신의 프로젝트에 이 패턴을 적용하기로 마음먹었습니다.

"우리 Agent도 Copilot처럼 도구를 플러그인으로 만들면, 나중에 새 도구 추가가 훨씬 쉬워지겠네요!" 플러그인 아키텍처는 단순히 코드를 나누는 것이 아닙니다. 확장성, 유지보수성, 테스트 용이성을 모두 잡는 설계 철학입니다.

오늘 배운 내용을 바탕으로 여러분의 프로젝트에도 적용해 보시기 바랍니다.

실전 팁

💡 - JSON Schema를 사용하면 AI가 도구 파라미터를 정확히 이해할 수 있습니다

  • 실제 Copilot의 확장 API 문서를 참고하면 더 많은 패턴을 배울 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#TypeScript#Plugin#Architecture#Extensibility#Agent#DesignPattern

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

마이크로서비스 배포 완벽 가이드

Kubernetes를 활용한 마이크로서비스 배포의 핵심 개념부터 실전 운영까지, 초급 개발자도 쉽게 따라할 수 있는 완벽 가이드입니다. 실무에서 바로 적용 가능한 배포 전략과 노하우를 담았습니다.

마이크로서비스 아키텍처 완벽 가이드

모놀리식에서 마이크로서비스로의 전환은 현대 소프트웨어 개발의 핵심 화두입니다. 초급 개발자도 쉽게 이해할 수 있도록 실무 관점에서 마이크로서비스의 개념, 장단점, 설계 원칙을 스토리텔링으로 풀어냅니다.

Application Load Balancer 완벽 가이드

AWS의 Application Load Balancer를 처음 배우는 개발자를 위한 실전 가이드입니다. ALB 생성부터 ECS 연동, 헬스 체크, HTTPS 설정까지 실무에 필요한 모든 내용을 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.

고객 상담 AI 시스템 완벽 구축 가이드

AWS Bedrock Agent와 Knowledge Base를 활용하여 실시간 고객 상담 AI 시스템을 구축하는 방법을 단계별로 학습합니다. RAG 기반 지식 검색부터 Guardrails 안전 장치, 프론트엔드 연동까지 실무에 바로 적용 가능한 완전한 시스템을 만들어봅니다.

에러 처리와 폴백 완벽 가이드

AWS API 호출 시 발생하는 에러를 처리하고 폴백 전략을 구현하는 방법을 다룹니다. ThrottlingException부터 서킷 브레이커 패턴까지, 실전에서 바로 활용할 수 있는 안정적인 에러 처리 기법을 배웁니다.