GitHub Copilot SDK 시작 가이드: 5분 만에 첫 번째 AI 에이전트 구축하기

핵심 요약

GitHub Copilot SDK의 주요 가치는 "LLM 호출"의 편리함(이는 이미 OpenAI SDK, LangChain 등에서 해결됨)보다 생산 환경에서 검증된 에이전트 런타임을 제공하는 것입니다.

해결하고자 하는 문제는 다음과 같습니다:

  • 복잡성 관리: 플래너, 도구 라우팅, 상태 관리가 내장되어 있음
  • 안정성: 수백만 명의 개발자가 매일 사용하는 신뢰성 보장
  • 진화 가능성: CLI를 통해 새로운 모델 및 도구 기능 자동 업데이트

다음 두 가지 질문을 스스로에게 던져보세요:

  1. 핵심 가치는 무엇인가요? 비즈니스 로직과 도구 정의에 초점을 맞춘다면 SDK를 사용하고, 하위 계층 혁신이 필요하다면 프레임워크를 직접 구축하세요.
  2. 얼마나 빨리 생산 환경으로 이동할 수 있을까요? SDK는 인프라 작업의 80%를 건너뛰고 나머지 20%의 차별화된 능력에 집중하게 해줍니다.

에이전트 개발의 장벽은 낮아졌지만 진정한 도전은 유용한 도구 정의, 자연스러운 상호작용 설계, 실제 문제 해결입니다. 기술적 장벽은 더 이상 문제가 되지 않으며 창의성이 중요합니다.

시작하기: 왜 에이전트 개발이 더 이상 소수의 전문가 영역이 아닌가?

2026년 1월, GitHub는 Copilot SDK를 출시하여 AI 에이전트 개발이 "전문가 영역"에서 "대중적인 도구"로 전환되는 중요한 시점이 되었습니다.

이전에는 독립적으로 계획하고 도구를 호출하며 파일을 수정하는 AI 에이전트를 만들려면 다음과 같은 작업이 필요했습니다:

  • LLM 서비스 선택 및 통합 (OpenAI, Anthropic, Azure 등)
  • 에이전트 오케스트레이터 자체 구축 (플래너, 도구 라우팅, 상태 관리)
  • 스트리밍 출력, 에러 재시도, 컨텍스트 관리 처리
  • 도구 정의 표준 (function calling schema) 구현

이 과정은 복잡하고 취약했으며, 오픈 소스 프레임워크(LangChain, AutoGPT)는 진입 장벽을 낮추었지만 여전히 에이전트 실행 메커니즘을 깊이 이해해야 했습니다. 실제 전환점은 GitHub가 Copilot CLI의 생산급 에이전트 런타임을 SDK로 공개한 것입니다.

이것은 무엇을 의미할까요? 단 5줄의 코드로 완전한 에이전트 런타임을 시작할 수 있습니다:

import asyncio
from github_copilot import CopilotClient

async def main():
    client = CopilotClient()
    await client.start()
    session = await client.create_session({"model": "gpt-4.1"})
    response = await session.send_and_wait({"prompt": "양자 얽힘에 대해 설명해주세요"})
    print(response.content)

asyncio.run(main())

모델 연결, 프롬프트 엔지니어링, 응답 파싱에 대한 걱정 없이, Copilot CLI가 수백만 명의 개발자를 통해 검증된 부분을 처리합니다. 여러분은 비즈니스 로직만 정의하면 됩니다.

준비 단계: 환경 설정

코딩을 시작하기 전에 개발 환경이 다음 조건을 충족하도록 합니다.

사전 요구 사항 목록

1. GitHub Copilot CLI 설치

SDK 자체에는 AI 추론 기능이 포함되어 있지 않으며 JSON-RPC를 통해 Copilot CLI와 통신합니다. CLI는 실제 "엔진"이며 SDK는 "조종 장치"입니다.

# macOS/Linux
brew install copilot-cli

# 설치 확인
copilot --version

2. GitHub 계정 인증

copilot login

GitHub Copilot 구독(개인 또는 기업 버전)이 필요합니다. BYOK(Bring Your Own Key) 모드를 사용한다면 이 단계를 생략할 수 있습니다.

환경 확인

CLI가 정상적으로 작동하는지 확인하려면 다음 명령을 실행하세요:

copilot -p "재귀를 한 문장으로 설명해줘"

AI의 답변을 본다면 환경이 준비되었습니다.

첫 번째 단계: 첫 번째 메시지 보내기

SDK 설치

프로젝트 디렉토리를 생성하고 Python SDK를 설치합니다:

mkdir copilot-demo && cd copilot-demo
# virtual environment 사용
python -m venv venv && source venv/bin/activate
pip install github-copilot-sdk

최소 코드 예제

main.py를 생성합니다:

import asyncio
from github_copilot import CopilotClient

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session({"model": "gpt-4.1"})
    response = await session.send_and_wait({"prompt": "양자 얽힘은 무엇인가요?"})

    print(response.content)

    await client.stop()

asyncio.run(main())

실행:

python main.py

AI의 완전한 답변을 볼 수 있습니다. 9줄의 코드로 완전한 AI 대화가 이루어집니다.

실행 흐름 분석

이 코드 뒤에서 일어나는 것은 다음과 같습니다:

  1. client.start() → SDK가 백그라운드에서 Copilot CLI 프로세스를 시작합니다
  2. create_session() → JSON-RPC 요청을 통해 CLI 세션을 생성합니다
  3. send_and_wait() → 프롬프트를 보내고 CLI가 이를 LLM에 전달합니다
  4. LLM 추론 → 응답이 CLI를 통해 SDK로 반환됩니다
  5. response.content → SDK가 JSON 응답을 파싱하여 내용을 추출합니다

아키텍처의 핵심: SDK는 CLI의 "리모컨"

GitHub의 설계 철학은 관심사의 분리입니다:

컴포넌트 역할
Copilot CLI 에이전트 런타임 (플래닝, 도구 호출, LLM 통신)
SDK 프로세스 관리, JSON-RPC 포장기, 이벤트 리스너
코드 프로세스 관리, JSON-RPC 포장기, 이벤트 리스너

이 아키텍처의 장점:

  • CLI 독립 업그레이드 가능: 새로운 모델 및 도구 기능은 SDK 수정 없이 가능
  • 다중 언어 지원 비용 절감: 각 언어의 SDK는 JSON-RPC 클라이언트만 구현하면 됨
  • 디버깅 용이: CLI는 독립적으로 실행되므로 로그 확인 및 문제 해결이 쉬움

두 번째 단계: 실시간 응답 활성화 - 스트리밍 출력

왜 스트리밍 응답이 필요한가?

send_and_wait()를 사용할 때 LLM이 전체 답변을 생성할 때까지 아무런 출력도 볼 수 없습니다. 긴 텍스트 생성(예: 코드 설명, 문서 작성)에서는 사용자가 10~30초 동안 빈 화면을 볼 수 있습니다.

스트리밍 응답은 AI가 타자기처럼 글자를 하나씩 출력하여 사용자 경험을 향상시키며, 모델이 잘못 진행되고 있는지 미리 알 수 있게 해줍니다.

이벤트 리스너 메커니즘

main.py를 수정하여 스트리밍 출력을 활성화합니다:

import asyncio
import sys
from github_copilot import CopilotClient, SessionEventType

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session({
        "model": "gpt-4.1",
        "streaming": True,  # 스트리밍 모드 활성화
    })

    # 응답 델타 수신 리스너
    def handle_event(event):
        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
            sys.stdout.write(event.delta_content)
            sys.stdout.flush()
        if event.type == SessionEventType.SESSION_IDLE:
            print()  # 완료 시 줄 바꿈

    session.on(handle_event)

    await session.send_and_wait({"prompt": "퀵소트 코드 예제를 작성해주세요"})

    await client.stop()

asyncio.run(main())

실행 후 결과가 점차 "출력"되는 것을 볼 수 있습니다.

이벤트 기반 모델의 설계 철학

SDK는 옵저버 패턴을 사용하여 CLI의 비동기 이벤트 스트림을 처리합니다:

CLI 생성 이벤트 → SDK 파싱 → 리스너에게 전달 → handle_event() 실행

주요 이벤트 유형:

이벤트 트리거 시점 일반적인 용도
ASSISTANT_MESSAGE_DELTA AI가 일부 내용을 생성했을 때 실시간 표시
ASSISTANT_MESSAGE AI가 완전한 메시지를 완성했을 때 최종 내용 가져오기
SESSION_IDLE 세션이 유휴 상태에 들어갔을 때 작업 완료 표시
TOOL_CALL AI가 도구 호출을 결정했을 때 로그 기록, 권한 확인

코드 비교: 동기 vs 스트리밍

동기 모드, 짧은 답변에 적합:

response = await session.send_and_wait({"prompt": "1+1=?"})
print(response.content)  # 한번에 기다렸다가 출력

스트리밍 모드, 긴 텍스트에 적합:

session.on(lambda event: 
    print(event.delta_content, end="") 
    if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA 
    else None
)
await session.send_and_wait({"prompt": "글을 써주세요"})

기술적 세부사항

스트리밍 응답은 Server-Sent Events (SSE) 또는 WebSocket을 기반으로 합니다:

  1. CLI는 LLM로부터 토큰 스트림을 받습니다
  2. 각 토큰을 받으면 CLI는 SDK에 message_delta 이벤트를 보냅니다
  3. SDK는 이벤트 리스너를 트리거합니다
  4. 사용자는 즉시 새로운 내용을 볼 수 있습니다

이 설계는 애플리케이션이 AI의 "생각 과정"을 감지하게 하여 최종 결과뿐만 아니라 중간 과정도 확인할 수 있게 합니다.

세 번째 단계: AI에 능력을 부여 - 사용자 정의 도구

도구의 본질: LLM이 당신의 코드를 호출하도록 함

현재까지 AI는 "말할" 수 있지만 외부 세계와 상호작용하지 못합니다. 도구(Tools) 는 에이전트의 핵심 능력입니다: 함수를 정의하고 AI가 언제 호출할지 결정합니다.

예를 들어:

  1. 사용자: "오늘 베이징 날씨는 어때?"
  2. AI 생각: 날씨 데이터가 필요하다 → get_weather("베이징") 호출
  3. 코드: {"temperature": "15°C", "condition": "맑음"} 반환
  4. AI 응답: "베이징은 오늘 맑고 15°C입니다."

중요한 점은 AI가 도구를 호출할지 여부와 어떤 매개변수를 전달할지 스스로 결정한다는 것입니다.

도구 정의의 세 가지 요소

도구는 다음을 포함합니다:

  1. 설명(description): AI에게 이 도구의 용도를 알려줍니다
  2. 매개변수 스키마(parameters): 입력 매개변수의 구조를 정의합니다 (Pydantic 사용)
  3. 처리기(handler): 실제로 실행되는 Python 함수

완전한 날씨 도우미 예제

weather_assistant.py를 생성합니다:

import asyncio
import random
import sys
from github_copilot import CopilotClient, define_tool, SessionEventType
from pydantic import BaseModel, Field

# 1. 매개변수 스키마 정의
class GetWeatherParams(BaseModel):
    city: str = Field(description="도시 이름, 예: 베이징, 상하이")

# 2. 도구 정의 (설명 + 처리기)
@define_tool(description="지정된 도시의 현재 날씨 가져오기")
async def get_weather(params: GetWeatherParams) -> dict:
    city = params.city

    # 실제 날씨 API 호출 예정 (데모용으로 모의 데이터 사용)
    conditions = ["맑음", "흐림", "비", "구름 많음"]
    temp = random.randint(10, 30)
    condition = random.choice(conditions)

    return {
        "city": city,
        "temperature": f"{temp}°C",
        "condition": condition
    }

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session({
        "model": "gpt-4.1",
        "streaming": True,
        "tools": [get_weather],  # 도구 등록
    })

    # 스트리밍 응답 리스너
    def handle_event(event):
        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
            sys.stdout.write(event.delta_content)
            sys.stdout.flush()
        if event.type == SessionEventType.SESSION_IDLE:
            print("\n")

    session.on(handle_event)

    await session.send_and_wait({
        "prompt": "베이징과 상하이의 날씨는 어떠한가요? 비교해주세요."
    })

    await client.stop()

asyncio.run(main())

실행:

python weather_assistant.py

실행 흐름 설명

"What's the weather in Beijing and Shanghai"라는 질문을 할 때:

  1. AI 분석 → 날씨 데이터가 필요함
  2. AI 도구 검색 → get_weather 함수 발견
  3. AI 결정 → get_weather(city="베이징") 호출
  4. SDK 처리기 트리거 → 함수가 {"temperature": "22°C", ...} 반환
  5. AI 응답 → get_weather(city="상하이") 다시 호출
  6. AI 합성 → "베이징은 맑고 22°C, 상하이는 구름 많고 18°C입니다..."

AI는 여러 번 도구를 호출하며(베이징 한 번, 상하이 한 번), 여러분은 반복 논리를 작성할 필요가 없습니다.

매개변수 스키마의 중요성

왜 Pydantic으로 매개변수를 정의할까요?

class GetWeatherParams(BaseModel):
    city: str = Field(description="도시 이름")
    unit: str = Field(default="celsius", description="온도 단위: 섭씨 또는 화씨")

SDK는 이 스키마를 JSON Schema로 변환하여 LLM에 전달합니다:

{
  "type": "object",
  "properties": {
    "city": {"type": "string", "description": "도시 이름"},
    "unit": {"type": "string", "description": "온도 단위"}
  },
  "required": ["city"]
}

LLM은 이 스키마를 기반으로 매개변수를 추출합니다. 따라서 설명이 명확할수록 AI의 호출이 정확해집니다.

네 번째 단계: 대화형 도우미 구축

이제 모든 기능을 결합합니다: 스트리밍 출력 + 도구 호출 + 명령줄 상호작용.

완전한 실행 가능한 코드

interactive_assistant.py를 생성합니다:

import asyncio
import random
import sys
from github_copilot import CopilotClient, define_tool, SessionEventType
from pydantic import BaseModel, Field

# 도구 정의
class GetWeatherParams(BaseModel):
    city: str = Field(description="도시 이름, 예: 베이징, 상하이, 광저우")

@define_tool(description="지정된 도시의 현재 날씨 가져오기")
async def get_weather(params: GetWeatherParams) -> dict:
    city = params.city
    conditions = ["맑음", "흐림", "비", "구름 많음", "연무"]
    temp = random.randint(5, 35)
    condition = random.choice(conditions)
    humidity = random.randint(30, 90)

    return {
        "city": city,
        "temperature": f"{temp}°C",
        "condition": condition,
        "humidity": f"{humidity}%"
    }

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session({
        "model": "gpt-4.1",
        "streaming": True,
        "tools": [get_weather],
    })

    # 이벤트 리스너
    def handle_event(event):
        if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
            sys.stdout.write(event.delta_content)
            sys.stdout.flush()
        if event.type == SessionEventType.SESSION_IDLE:
            print()  # 완료 시 줄 바꿈

    session.on(handle_event)

    print("🌤️ 날씨 도우미 (종료하려면 'exit' 입력)")
    print("시도해보세요: '베이징 날씨는?' 또는 '광저우와 선전의 날씨를 비교해주세요'\n")

    while True:
        try:
            user_input = input("당신: ")
        except EOFError:
            break

        if user_input.lower() in ["exit", "quit"]:
            break

        if not user_input.strip():
            continue

        sys.stdout.write("도우미: ")
        await session.send_and_wait({"prompt": user_input})
        print()  # 추가 줄 바꿈

    await client.stop()
    print("안녕히 가세요!")

asyncio.run(main())

실행 결과

python interactive_assistant.py

샘플 대화:

️ 날씨 도우미 (종료하려면 'exit' 입력)
시도해보세요: '베이징 날씨는?' 또는 '광저우와 선전의 날씨를 비교해주세요'

당신: 광저우와 선전의 날씨를 비교해주세요
도우미: 광저우: 21°C, 맑음, 습도 84%.
선전: 33°C, 연무, 습도 77%.
선전은 훨씬 더 따뜻하고 연무가 있으며, 광저우는 시원하고 맑으며 습도가 조금 더 높습니다.

당신: 베이징 날씨는?
도우미: 베이징의 날씨는 8°C, 구름 많음, 습도 47%입니다.

당신: quit
안녕히 가세요!

주요 설계 포인트

1. 세션 지속성

세션을 한 번만 생성하고 전체 대화 루프에서 계속 사용합니다. 이는 다음과 같은 의미입니다:

  • AI는 이전 대화 내용을 기억합니다
  • "내일은 어떻게 될까?"와 같은 질문에 대해 AI는 어떤 도시를 말하는지 알 수 있습니다
  • 도구 호출 기록도 유지됩니다

2. 비동기 I/O의 올바른 사용법

# while True 루프에서 input() 사용
user_input = input("당신: ")  # 동기식 블로킹, 하지만 여기서는 허용됨

# send_and_wait()는 비동기
await session.send_and_wait({"prompt": user_input})

input()의 블로킹이 왜 허용되는지 이유는 무엇일까요? 사용자 입력을 기다리는 것이므로 I/O 작업을 기다리는 것이 아닙니다. 실제 비동기는 CLI와 통신할 때 발생합니다.

3. 우아한 종료

try:
    user_input = input("당신: ")
except EOFError:  # Ctrl+D를 잡음
    break

EOFError와 일반적인 종료 명령('exit', 'quit')을 처리하여 사용자 경험을 원활하게 합니다.

확장 가능성

이 프레임워크를 기반으로 다음과 같이 기능을 빠르게 확장할 수 있습니다:

더 많은 도구 추가:

@define_tool(description="실시간 주가 조회")
async def get_stock_price(params): ...

@define_tool(description="웹에서 정보 검색")
async def web_search(params): ...

session = await client.create_session({
    "tools": [get_weather, get_stock_price, web_search],
})

AI는 사용자의 질문에 따라 자동으로 적절한 도구를 선택합니다.

시스템 프롬프트 추가:

session = await client.create_session({
    "model": "gpt-4.1",
    "tools": [get_weather],
    "system_message": {
        "content": "당신은 전문적인 날씨 도우미입니다. 답변은 간결하면서도 유익하게 유지하세요."
    }
})

도구 호출 로그 기록:

def handle_event(event):
    if event.type == SessionEventType.TOOL_CALL:
        print(f"\n[디버그] AI가 도구를 호출했습니다: {event.data.tool_name}")
        print(f"[디버그] 매개변수: {event.data.arguments}\n")

태그: GitHub Copilot SDK python AI Agent Development json-rpc Pydantic

7월 23일 04:02에 게시됨