Các tác nhân nhỏ trong Python- một tác nhân được hỗ trợ bởi MCP trong ~70 dòng mã
Bài đăng trên blog giới thiệu cách tạo các tác nhân nhỏ trong Python bằng cách sử dụng MCP.
- 12 min read
Các Agent Nhỏ trong Python: Một Agent được cung cấp bởi MCP chỉ trong ~70 dòng code
Lấy cảm hứng từ Tiny Agents in JS, chúng tôi đã chuyển ý tưởng này sang Python 🐍 và mở rộng SDK client huggingface_hub để hoạt động như một MCP Client để nó có thể kéo các công cụ từ các máy chủ MCP và chuyển chúng cho LLM trong quá trình suy luận.
MCP (Model Context Protocol) là một giao thức mở, tiêu chuẩn hóa cách các Mô hình Ngôn ngữ Lớn (LLM) tương tác với các công cụ và API bên ngoài. Về cơ bản, nó loại bỏ sự cần thiết phải viết các tích hợp tùy chỉnh cho từng công cụ, giúp việc cắm các khả năng mới vào LLM của bạn trở nên đơn giản hơn.
Trong bài đăng trên blog này, chúng tôi sẽ chỉ cho bạn cách bắt đầu với một Agent nhỏ trong Python được kết nối với các máy chủ MCP để mở khóa các khả năng công cụ mạnh mẽ. Bạn sẽ thấy việc khởi động Agent của riêng bạn và bắt đầu xây dựng dễ dàng như thế nào!
Spoiler: Một Agent về cơ bản là một vòng lặp while được xây dựng ngay trên đầu một MCP Client!
Cách chạy Demo
Phần này hướng dẫn bạn cách sử dụng Tiny Agents hiện có. Chúng ta sẽ đề cập đến thiết lập và các lệnh để chạy một agent.
Đầu tiên, bạn cần cài đặt phiên bản mới nhất của huggingface_hub với mcp extra để có được tất cả các thành phần cần thiết.
pip install "huggingface_hub[mcp]>=0.32.0"
Bây giờ, hãy chạy một agent bằng CLI!
Phần thú vị nhất là bạn có thể tải các agent trực tiếp từ Hugging Face Hub tiny-agents Dataset, hoặc chỉ định một đường dẫn đến cấu hình agent cục bộ của riêng bạn!
> tiny-agents run --help
Usage: tiny-agents run [OPTIONS] [PATH] COMMAND [ARGS]...
Run the Agent in the CLI
╭─ Arguments ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ path [PATH] Path to a local folder containing an agent.json file or a built-in agent stored in the 'tiny-agents/tiny-agents' Hugging Face dataset │
│ (https://huggingface.co/datasets/tiny-agents/tiny-agents) │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Nếu bạn không cung cấp một đường dẫn đến một cấu hình agent cụ thể, Tiny Agent của chúng tôi sẽ kết nối theo mặc định đến hai máy chủ MCP sau:
- “canonical” file system server, có quyền truy cập vào Desktop của bạn,
- và máy chủ Playwright MCP, biết cách sử dụng một trình duyệt Chromium sandboxed cho bạn.
Ví dụ sau đây cho thấy một agent duyệt web được cấu hình để sử dụng mô hình Qwen/Qwen2.5-72B-Instruct thông qua Nebius inference provider, và nó được trang bị một máy chủ playwright MCP, cho phép nó sử dụng một trình duyệt web! Cấu hình agent được tải bằng cách chỉ định đường dẫn của nó trong tiny-agents/tiny-agents Hugging Face dataset.
Khi bạn chạy agent, bạn sẽ thấy nó tải, liệt kê các công cụ mà nó đã khám phá từ các máy chủ MCP được kết nối của nó. Sau đó, nó đã sẵn sàng cho các prompt của bạn!
Prompt được sử dụng trong bản demo này:
thực hiện một Web Search cho các HF inference provider trên Brave Search và mở kết quả đầu tiên và sau đó cung cấp cho tôi danh sách các inference provider được hỗ trợ trên Hugging Face
Bạn cũng có thể sử dụng Gradio Spaces làm máy chủ MCP! Ví dụ sau sử dụng mô hình Qwen/Qwen2.5-72B-Instruct thông qua Nebius inference provider, và kết nối đến một HF Space tạo ảnh FLUX.1 [schnell] như một máy chủ MCP. Agent được tải từ cấu hình của nó trong dataset tiny-agents/tiny-agents trên Hugging Face Hub.
Prompt được sử dụng trong bản demo này:
Tạo một hình ảnh 1024x1024 của một phi hành gia nhỏ bé nở ra từ một quả trứng trên bề mặt của mặt trăng.
Bây giờ bạn đã thấy cách chạy Tiny Agents hiện có, các phần sau đây sẽ đi sâu hơn vào cách chúng hoạt động và cách xây dựng của riêng bạn.
Cấu hình Agent
Hành vi của mỗi agent (mô hình mặc định, inference provider, các máy chủ MCP để kết nối và system prompt ban đầu của nó) được xác định bởi một file agent.json. Bạn cũng có thể cung cấp một PROMPT.md tùy chỉnh trong cùng thư mục cho một system prompt chi tiết hơn. Đây là một ví dụ:
agent.json
Các trường model và provider chỉ định LLM và inference provider được sử dụng bởi agent. Mảng servers xác định các máy chủ MCP mà agent sẽ kết nối. Trong ví dụ này, một máy chủ MCP “stdio” được cấu hình. Loại máy chủ này chạy như một tiến trình cục bộ. Agent khởi động nó bằng cách sử dụng command và args được chỉ định, và sau đó giao tiếp với nó thông qua stdin/stdout để khám phá và thực thi các công cụ có sẵn.
{
"model": "Qwen/Qwen2.5-72B-Instruct",
"provider": "nebius",
"servers": [
{
"type": "stdio",
"config": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
]
}
PROMPT.md
You are an agent - please keep going until the user’s query is completely resolved [...]
Bạn có thể tìm thấy thêm chi tiết về Hugging Face Inference Providers tại đây.
LLM Có Thể Sử Dụng Các Công Cụ
LLM hiện đại được xây dựng cho việc gọi hàm (hoặc sử dụng công cụ), cho phép người dùng dễ dàng xây dựng các ứng dụng phù hợp với các trường hợp sử dụng cụ thể và các nhiệm vụ thực tế.
Một hàm được xác định bởi schema của nó, thông báo cho LLM những gì nó làm và những đối số đầu vào mà nó mong đợi. LLM quyết định khi nào sử dụng một công cụ, Agent sau đó điều phối việc chạy công cụ và cung cấp kết quả trở lại.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current temperature for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country e.g. Paris, France"
}
},
"required": ["location"],
},
}
}
]
InferenceClient triển khai cùng một giao diện gọi công cụ như OpenAI Chat Completions API, là tiêu chuẩn đã được thiết lập cho các inference provider và cộng đồng.
Xây Dựng MCP Client Python của Chúng Ta
MCPClient là trái tim của chức năng sử dụng công cụ của chúng ta. Nó bây giờ là một phần của huggingface_hub và sử dụng AsyncInferenceClient để giao tiếp với LLM.
Mã
MCPClientđầy đủ nằm ở đây nếu bạn muốn theo dõi bằng cách sử dụng mã thực tế 🤓
Các trách nhiệm chính của MCPClient:
- Quản lý các kết nối async đến một hoặc nhiều máy chủ MCP.
- Khám phá các công cụ từ các máy chủ này.
- Định dạng các công cụ này cho LLM.
- Thực thi các lệnh gọi công cụ thông qua máy chủ MCP chính xác.
Đây là một cái nhìn thoáng qua về cách nó kết nối với một máy chủ MCP (phương thức add_mcp_server):
# Lines 111-219 of `MCPClient.add_mcp_server`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/mcp_client.py#L111:L219
class MCPClient:
...
async def add_mcp_server(self, type: ServerType, **params: Any):
# 'type' can be "stdio", "sse", or "http"
# 'params' are specific to the server type, e.g.:
# for "stdio": {"command": "my_tool_server_cmd", "args": ["--port", "1234"]}
# for "http": {"url": "http://my.tool.server/mcp"}
# 1. Establish connection based on type (stdio, sse, http)
# (Uses mcp.client.stdio_client, sse_client, or streamablehttp_client)
read, write = await self.exit_stack.enter_async_context(...)
# 2. Create an MCP ClientSession
session = await self.exit_stack.enter_async_context(
ClientSession(read_stream=read, write_stream=write, ...)
)
await session.initialize()
# 3. List tools from the server
response = await session.list_tools()
for tool in response.tools:
# Store session for this tool
self.sessions[tool.name] = session
# Add tool to the list of available tools and Format for LLM
self.available_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.input_schema,
},
})
Nó hỗ trợ các máy chủ stdio cho các công cụ cục bộ (như truy cập vào hệ thống file của bạn), và các máy chủ http cho các công cụ từ xa! Nó cũng tương thích với sse, là tiêu chuẩn trước đó cho các công cụ từ xa.
Sử Dụng Các Công Cụ: Streaming và Xử Lý
Phương thức process_single_turn_with_tools của MCPClient là nơi xảy ra tương tác LLM. Nó gửi lịch sử hội thoại và các công cụ có sẵn đến LLM thông qua AsyncInferenceClient.chat.completions.create(..., stream=True).
1. Chuẩn Bị Các Công Cụ Và Gọi LLM
Đầu tiên, phương thức xác định tất cả các công cụ mà LLM nên biết cho lượt hiện tại - điều này bao gồm các công cụ từ các máy chủ MCP và bất kỳ công cụ “exit loop” đặc biệt nào để kiểm soát agent; sau đó, nó thực hiện một lệnh gọi streaming đến LLM:
# Lines 241-251 of `MCPClient.process_single_turn_with_tools`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/mcp_client.py#L241:L251
# Prepare tools list based on options
tools = self.available_tools
if exit_loop_tools is not None:
tools = [*exit_loop_tools, *self.available_tools]
# Create the streaming request to the LLM
response = await self.client.chat.completions.create(
messages=messages,
tools=tools,
tool_choice="auto", # LLM decides if it needs a tool
stream=True,
)
Khi các chunk đến từ LLM, phương thức lặp qua chúng. Mỗi chunk được yield ngay lập tức, sau đó chúng ta xây dựng lại phản hồi văn bản hoàn chỉnh và bất kỳ lệnh gọi công cụ nào.
# Lines 258-290 of `MCPClient.process_single_turn_with_tools`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/mcp_client.py#L258:L290
# Read from stream
async for chunk in response:
# Yield each chunk to caller
yield chunk
# Aggregate LLM's text response and parts of tool calls
…
2. Thực Thi Các Công Cụ
Khi stream hoàn tất, nếu LLM yêu cầu bất kỳ lệnh gọi công cụ nào (bây giờ được xây dựng lại hoàn toàn trong final_tool_calls), phương thức sẽ xử lý từng lệnh gọi:
# Lines 293-313 of `MCPClient.process_single_turn_with_tools`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/mcp_client.py#L293:L313
for tool_call in final_tool_calls.values():
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments or "{}")
# Prepare a message to store the tool's result
tool_message = {"role": "tool", "tool_call_id": tool_call.id, "content": "", "name": function_name}
# a. Is this a special "exit loop" tool?
if exit_loop_tools and function_name in [t.function.name for t in exit_loop_tools]:
# If so, yield a message and terminate this turn's processing
messages.append(ChatCompletionInputMessage.parse_obj_as_instance(tool_message))
yield ChatCompletionInputMessage.parse_obj_as_instance(tool_message)
return # The Agent's main loop will handle this signal
# b. It's a regular tool: find the MCP session and execute it
session = self.sessions.get(function_name) # self.sessions maps tool names to MCP connections
if session is not None:
result = await session.call_tool(function_name, function_args)
tool_message["content"] = format_result(result) # format_result processes tool output
else:
tool_message["content"] = f"Error: No session found for tool: {function_name}"
tool_message["content"] = error_msg
# Add tool result to history and yield it
...
Đầu tiên, nó kiểm tra xem công cụ được gọi có thoát khỏi vòng lặp hay không (exit_loop_tool). Nếu không, nó tìm thấy phiên MCP chính xác chịu trách nhiệm cho công cụ đó và gọi session.call_tool(). Kết quả (hoặc phản hồi lỗi) sau đó được định dạng, thêm vào lịch sử hội thoại và yield để Agent nhận biết được đầu ra của công cụ.
Agent Python Nhỏ Của Chúng Ta: Nó (Gần Như) Chỉ Là Một Vòng Lặp!
Với MCPClient thực hiện tất cả công việc cho các tương tác công cụ, class Agent của chúng ta trở nên đơn giản một cách tuyệt vời. Nó kế thừa từ MCPClient và thêm logic quản lý hội thoại.
Class Agent nhỏ và tập trung vào vòng lặp hội thoại, mã có thể được tìm thấy tại đây.
1. Khởi Tạo Agent
Khi một Agent được tạo, nó lấy một cấu hình agent (mô hình, provider, các máy chủ MCP để sử dụng, system prompt) và khởi tạo lịch sử hội thoại với system prompt. Phương thức load_tools() sau đó lặp qua các cấu hình máy chủ (được xác định trong agent.json) và gọi add_mcp_server (từ MCPClient cha) cho mỗi cấu hình, điền vào hộp công cụ của agent.
# Lines 12-54 of `Agent`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/agent.py#L12:L54
class Agent(MCPClient):
def __init__(
self,
*,
model: str,
servers: Iterable[Dict], # Configuration for MCP servers
provider: Optional[PROVIDER_OR_POLICY_T] = None,
api_key: Optional[str] = None,
prompt: Optional[str] = None, # The system prompt
):
# Initialize the underlying MCPClient with model, provider, etc.
super().__init__(model=model, provider=provider, api_key=api_key)
# Store server configurations to be loaded
self._servers_cfg = list(servers)
# Start the conversation with a system message
self.messages: List[Union[Dict, ChatCompletionInputMessage]] = [
{"role": "system", "content": prompt or DEFAULT_SYSTEM_PROMPT}
]
async def load_tools(self) -> None:
# Connect to all configured MCP servers and register their tools
for cfg in self._servers_cfg:
await self.add_mcp_server(cfg["type"], **cfg["config"])
2. Lõi Của Agent: Vòng Lặp
Phương thức Agent.run() là một trình tạo bất đồng bộ xử lý một đầu vào của người dùng. Nó quản lý các lượt hội thoại, quyết định khi nào nhiệm vụ hiện tại của agent hoàn thành.
# Lines 56-99 of `Agent.run()`
# https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/inference/_mcp/agent.py#L56:L99
async def run(self, user_input: str, *, abort_event: Optional[asyncio.Event] = None, ...) -> AsyncGenerator[...]:
...
while True: # Main loop for processing the user_input
...
# Delegate to MCPClient to interact with LLM and tools for one step.
# This streams back LLM text, tool call info, and tool results.
async for item in self.process_single_turn_with_tools(
self.messages,
...
):
yield item
...
# Exit Conditions
# 1. Was an "exit" tool called?
if last.get("role") == "tool" and last.get("name") in {t.function.name for t in EXIT_LOOP_TOOLS}:
return
# 2. Max turns reached or LLM gave a final text answer?
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")
Bên trong vòng lặp run():
- Đầu tiên, nó thêm prompt của người dùng vào hội thoại.
- Sau đó, nó gọi
MCPClient.process_single_turn_with_tools(...)để nhận phản hồi của LLM và xử lý bất kỳ thực thi công cụ nào cho một bước lý luận. - Mỗi item được yield ngay lập tức, cho phép streaming thời gian thực đến caller.
- Sau mỗi bước, nó kiểm tra các điều kiện thoát: nếu một công cụ “exit loop” đặc biệt đã được sử dụng, nếu đạt đến giới hạn lượt tối đa, hoặc nếu LLM cung cấp một phản hồi văn bản có vẻ cuối cùng cho yêu cầu hiện tại.
Các Bước Tiếp Theo
Có rất nhiều cách thú vị để khám phá và mở rộng trên MCP Client và Tiny Agent 🔥
Đây là một vài ý tưởng để giúp bạn bắt đầu:
- Benchmark cách các mô hình LLM khác nhau và các inference provider tác động đến hiệu suất agentic: Hiệu suất gọi công cụ có thể khác nhau vì mỗi provider có thể tối ưu hóa nó khác nhau. Bạn có thể tìm thấy danh sách các provider được hỗ trợ tại đây.
- Chạy các agent nhỏ với các máy chủ suy luận LLM cục bộ, chẳng hạn như llama.cpp, hoặc LM Studio.
- .. và tất nhiên là đóng góp! Chia sẻ các agent nhỏ độc đáo của bạn và mở PR trong dataset tiny-agents/tiny-agents trên Hugging Face Hub.
Các pull request và đóng góp đều được hoan nghênh! Một lần nữa, mọi thứ ở đây đều là mã nguồn mở! 💎❤️
Link bài viết gốc
- Tags:
- Ai
- May 23, 2025
- Huggingface.co