파이썬으로 MCP 프로토콜 지원하는 간단한 에이전트 구현
Python에서의 Tiny Agents: 약 70줄짜리 코드로 MCP 파워드 에이전트 구현 JS에서의 Tiny Agents를 영감으로 삼아 Python에서도 이 아이디어를 포팅하여 확장하였습니다. 이를 통해 Hugging Face Hub 클라이언트 SDK를 MCP 클라이언트로 확장해, LLM이 MCP 서버로부터 도구를 불러와 추론 중에 사용할 수 있도록 하였습니다. MCP(Model Context Protocol)는 LLM이 외부 도구 및 API와 상호작용하는 방식을 표준화하는 오픈 프로토콜입니다. 이 프로토콜 덕분에 각 도구에 대한 맞춤형 통합을 작성할 필요가 없어졌으며, 새로운 기능을 LLM에 쉽게 추가할 수 있게 되었습니다. 이 블로그 포스트에서는 Python에서 MCP 서버에 연결된 Tiny Agent를 시작하는 방법과 강력한 도구 기능을 활용하는 방법을 보여드립니다. 자신의 Agent를 실행하고 개발하는 것이 얼마나 쉬운지 확인해보세요! 스포일러: Agent는 Essentially MCP 클라이언트 위에 구축된 while 루프입니다! 데모 실행 방법 이 섹션에서는 기존 Tiny Agents를 사용하는 방법을 안내합니다. 설정 방법과 Agent를 실행하기 위한 명령어들을 소개합니다. 먼저, 최신 버전의 huggingface_hub를 설치해야 합니다. mcp 옵션을 사용하여 필요한 모든 컴포넌트를 포함할 수 있습니다. pip install "huggingface_hub[mcp]>=0.32.0" CLI를 통해 Agent를 실행할 수 있습니다. 특정 Agent 구성 파일의 경로를 제공하지 않으면, 기본적으로 다음과 같은 두 가지 MCP 서버에 연결됩니다: - Hugging Face Hub의 tiny-agents 데이터셋에서 Agent를 직접 로드하거나 - 로컬 Agent 구성 파일의 경로를 지정합니다. 예시 명령어: tiny-agents run --help 사용 예: - 웹 검색 Agent: Qwen/Qwen2.5-72B-Instruct 모델을 사용하며, Playwright MCP 서버를 통해 웹 브라우저를 사용할 수 있습니다. - 이미지 생성 Agent: Qwen/Qwen2.5-72B-Instruct 모델을 사용하며, FLUX.1 [schnell] 이미지 생성 HF Space를 MCP 서버로 사용합니다. Agent 구성 각 Agent의 동작(기본 모델, 추론 제공자, 연결할 MCP 서버, 초기 시스템 프롬프트 등)은 agent.json 파일에서 정의됩니다. 필요에 따라 같은 디렉토리에 자세한 시스템 프롬프트를 제공하기 위한 custom PROMPT.md 파일도 포함할 수 있습니다. agent.json 예시 json { "model": "Qwen/Qwen2.5-72B-Instruct", "provider": "nebius", "servers": [ { "type": "stdio", "config": { "command": "npx", "args": ["@playwright/mcp@latest"] } } ] } PROMPT.md 예시 당신은 에이전트입니다 - 사용자의 쿼리를 완전히 해결할 때까지 계속 진행해주세요 [...] LLM이 도구를 사용하는 방법 현대 LLM은 함수 호출(또는 도구 사용)을 지원하여 사용자가 특정 용례와 실제 작업에 맞는 응용 프로그램을 쉽게 구축할 수 있습니다. 함수는 스키마로 정의되며, 이 스키마는 LLM에게 함수의 역할과 기대되는 입력 인수를 알려줍니다. 예시: json [ { "type": "function", "function": { "name": "get_weather", "description": "주어진 위치의 현재 온도를 가져옵니다.", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "도시와 국가, 예: 파리, 프랑스" } }, "required": ["location"], } } } ] InferenceClient는 OpenAI Chat Completions API와 동일한 도구 호출 인터페이스를 구현하며, 이는 추론 제공자와 커뮤니티의 표준입니다. Python MCP 클라이언트 구축 MCPClient는 도구 사용 기능의 핵심입니다. 이제 huggingface_hub의 일부로 통합되었으며, AsyncInferenceClient를 사용하여 LLM과 통신합니다. MCPClient는 로컬 도구(파일 시스템 접근 등)를 위한 stdio 서버와 원격 도구를 위한 http 서버를 지원합니다! 이전 표준인 sse도 호환됩니다. MCP 서버 연결 예시 ```python class MCPClient: ... async def add_mcp_server(self, type: ServerType, **params: Any): # 'type'는 "stdio", "sse", 또는 "http"일 수 있습니다. # 'params'는 서버 유형별로 다릅니다. 예: # "stdio"의 경우: {"command": "my_tool_server_cmd", "args": ["--port", "1234"]} # "http"의 경우: {"url": "http://my.tool.server/mcp"} # 1. 연결 설정 (stdio, sse, http) read, write = await self.exit_stack.enter_async_context(...) # 2. MCP ClientSession 생성 session = await self.exit_stack.enter_async_context( ClientSession(read_stream=read, write_stream=write, ...) ) await session.initialize() # 3. 서버에서 도구 목록 받아오기 response = await session.list_tools() for tool in response.tools: # 도구 세션 저장 self.sessions[tool.name] = session # 사용 가능한 도구 목록에 추가 및 LLM 형식으로 포맷팅 self.available_tools.append({ "type": "function", "function": { "name": tool.name, "description": tool.description, "parameters": tool.input_schema, }, }) ``` 도구 사용: 스트리밍 및 처리 MCPClient의 process_single_turn_with_tools 메서드에서는 LLM과의 상호작용이 이루어집니다. 이 메서드는 대화 기록과 사용 가능한 도구를 AsyncInferenceClient.chat.completions.create(..., stream=True)를 통해 LLM에 전송합니다. 1. 도구 준비 및 LLM 호출 ```python 도구 목록 준비 tools = self.available_tools if exit_loop_tools is not None: tools = [exit_loop_tools, self.available_tools] 스트리밍 요청 생성 response = await self.client.chat.completions.create( messages=messages, tools=tools, tool_choice="auto", # LLM가 도구 사용 여부 결정 stream=True, ) ``` LLM에서 반환된 청크들을 처리하며, 각 청크는 즉시 반환되고, 전체 텍스트 응답과 도구 호출 부분이 재구성됩니다. 2. 도구 실행 ```python for tool_call in final_tool_calls.values(): function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments or "{}") # 도구 결과 메시지 준비 tool_message = {"role": "tool", "tool_call_id": tool_call.id, "content": "", "name": function_name} # a. 특수 "루프 종료" 도구인가? if exit_loop_tools and function_name in [t.function.name for t in exit_loop_tools]: # 그렇다면 메시지를 반환하고 이 차례의 처리를 종료 messages.append(ChatCompletionInputMessage.parse_obj_as_instance(tool_message)) yield ChatCompletionInputMessage.parse_obj_as_instance(tool_message) return # Agent의 메인 루프가 이 신호를 처리합니다. # b. 일반 도구: 해당 MCP 세션 찾고 실행 session = self.sessions.get(function_name) # self.sessions는 도구 이름을 MCP 연결에 매핑합니다. if session is not None: result = await session.call_tool(function_name, function_args) tool_message["content"] = format_result(result) # format_result는 도구 출력을 처리합니다. else: tool_message["content"] = f"Error: No session found for tool: {function_name}" tool_message["content"] = error_msg # 대화 기록에 도구 결과 추가하고 반환 ... ``` Tiny Python Agent: 단순한 루프! MCPClient가 모든 도구 상호작용을 처리해주므로, Agent 클래스는 매우 간단해졌습니다. 이 클래스는 MCPClient를 상속받아 대화 관리 로직을 추가합니다. Agent 클래스는 소스 코드에서 확인할 수 있으며, 주요 초점을 대화 루프에 맞춰 구현되었습니다. 1. Agent 초기화 ```python class Agent(MCPClient): def init( self, *, model: str, servers: Iterable[Dict], # MCP 서버 구성 provider: Optional[PROVIDER_OR_POLICY_T] = None, api_key: Optional[str] = None, prompt: Optional[str] = None, # 시스템 프롬프트 ): # 기반 MCPClient 초기화 super().init(model=model, provider=provider, api_key=api_key) # 로드할 서버 구성 저장 self._servers_cfg = list(servers) # 시스템 메시지로 대화 시작 self.messages: List[Union[Dict, ChatCompletionInputMessage]] = [ {"role": "system", "content": prompt or DEFAULT_SYSTEM_PROMPT} ] async def load_tools(self) -> None: # 구성된 모든 MCP 서버 연결 및 도구 등록 for cfg in self._servers_cfg: await self.add_mcp_server(cfg["type"], **cfg["config"]) ``` 2. Agent의 핵심: 루프 ```python async def run(self, user_input: str, *, abort_event: Optional[asyncio.Event] = None, ...) -> AsyncGenerator[...]: ... while True: # 사용자 입력 처리를 위한 메인 루프 ... # MCPClient를 위임하여 한 단계 동안 LLM과 도구 상호작용 # 이는 LLM 텍스트, 도구 호출 정보, 도구 결과를 스트리밍으로 반환합니다. async for item in self.process_single_turn_with_tools( self.messages, ... ): yield item ... # 종료 조건 # 1. "종료" 도구 호출? if last.get("role") == "tool" and last.get("name") in {t.function.name for t in EXIT_LOOP_TOOLS}: return # 2. 최대 회차 도달 또는 LLM이 최종 텍스트 답변 제공? if last.get("role") != "tool" and num_turns > MAX_NUM_TURNS: return if last.get("role") != "tool" and next_turn_should_call_tools: return next_turn_should_call_tools = (last_message.get("role") != "tool") ``` 다음 단계 MCP 클라이언트와 Tiny Agent를 탐색하고 확장할 수 있는 다양한 방법들이 있습니다. 여기 몇 가지 제안을 드립니다: - GitHub 리포지토리에 기여하고 개선하세요! 모든 것은 오픈 소스입니다! ❤️ 업계 관계자들은 이번 개발이 LLM의 사용성을 크게 향상시키는 중요한 단계라고 평가하고 있습니다. Hugging Face는 이러한 기술을 통해 더 많은 개발자들이 LLM을 활용할 수 있는 플랫폼을 제공하고 있으며, 앞으로도 계속 이 부분에 투자를 아끼지 않을 것입니다.