Python 中的微智能体:一个由 MCP 驱动的约 70 行代码的智能体
受 JS 中的微智能体 的启发,我们将其理念移植到了 Python 🐍,并扩展了
huggingface_hub
客户端 SDK,使其可以作为 MCP 客户端,从 MCP 服务器拉取工具,并在推理过程中将它们传递给大语言模型。
MCP (模型上下文协议) 是一个开放协议,它标准化了大型语言模型(LLM)与外部工具和 API 的交互方式。从本质上讲,它消除了为每个工具编写自定义集成的需要,使向您的 LLM 中插入新功能变得更加简单。
在这篇博文中,我们将向您展示如何开始使用连接到 MCP 服务器的微型 Python 智能体,以解锁强大的工具能力。您将看到启动自己的智能体并开始构建是多么容易!
剧透:智能体本质上是一个直接构建在 MCP 客户端之上的 `while` 循环!
如何运行演示
本节将引导您了解如何使用现有的微智能体。我们将介绍设置和运行智能体的命令。
首先,您需要安装最新版本的 huggingface_hub
,并带有 mcp
额外依赖,以获取所有必要的组件。
pip install "huggingface_hub[mcp]>=0.32.0"
现在,让我们使用命令行界面(CLI)来运行一个智能体!
最酷的部分是,您可以直接从 Hugging Face Hub 的 tiny-agents 数据集加载智能体,或者指定一个指向您自己本地智能体配置的路径!
> 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. │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
如果您没有提供特定智能体配置的路径,我们的微智能体将默认连接到以下两个 MCP 服务器:
- “规范的”文件系统服务器,用于访问您的桌面文件,
- 以及 Playwright MCP 服务器,它知道如何为您使用一个沙盒化的 Chromium 浏览器。
以下示例展示了一个网页浏览智能体,它被配置为通过 Nebius 推理提供商使用 Qwen/Qwen2.5-72B-Instruct 模型,并配备了一个 Playwright MCP 服务器,使其能够使用网页浏览器!该智能体配置通过指定其在 Hugging Face 数据集 tiny-agents/tiny-agents
中的路径进行加载。
当您运行智能体时,您会看到它加载,并列出从其连接的 MCP 服务器发现的工具。然后,它就准备好接收您的提示了!
此演示中使用的提示
在 Brave 搜索上进行关于 HF 推理提供商的网页搜索,打开第一个结果,然后给我 Hugging Face 支持的推理提供商列表
您还可以使用 Gradio Spaces 作为 MCP 服务器!以下示例通过 Nebius 推理提供商使用 Qwen/Qwen2.5-72B-Instruct 模型,并连接到一个 FLUX.1 [schnell]
图像生成 HF Space 作为 MCP 服务器。该智能体从其在 Hugging Face Hub 上的 tiny-agents/tiny-agents 数据集中的配置加载。
此演示中使用的提示
生成一张 1024x1024 的图片,内容是一个微型宇航员在月球表面从蛋里孵化出来。
现在您已经了解了如何运行现有的微智能体,接下来的部分将深入探讨它们的工作原理以及如何构建您自己的智能体。
智能体配置
每个智能体的行为(其默认模型、推理提供商、要连接的 MCP 服务器以及其初始系统提示)都由一个 agent.json
文件定义。您还可以在同一目录中提供一个自定义的 PROMPT.md
文件,以获得更详细的系统提示。下面是一个示例:
agent.json
文件中的 model
和 provider
字段指定了智能体使用的 LLM 和推理提供商。servers
数组定义了智能体将连接的 MCP 服务器。在此示例中,配置了一个 "stdio" MCP 服务器。这种类型的服务器作为本地进程运行。智能体使用指定的 command
和 args
启动它,然后通过标准输入/输出(stdin/stdout)与其通信,以发现和执行可用的工具。
{
"model": "Qwen/Qwen2.5-72B-Instruct",
"provider": "nebius",
"servers": [
{
"type": "stdio",
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
]
}
PROMPT.md
You are an agent - please keep going until the user’s query is completely resolved [...]
您可以在此处找到有关 Hugging Face 推理提供商的更多详细信息。
大语言模型可以使用工具
现代大语言模型(LLM)专为函数调用(或工具使用)而构建,这使得用户可以轻松构建针对特定用例和现实世界任务的应用程序。
函数由其模式(schema)定义,该模式告知 LLM 函数的功能以及它期望的输入参数。LLM 决定何时使用工具,然后由智能体协调运行该工具并将结果反馈给 LLM。
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
实现了与 OpenAI 聊天补全 API 相同的工具调用接口,这是推理提供商和社区公认的标准。
构建我们的 Python MCP 客户端
MCPClient
是我们工具使用功能的核心。它现在是 huggingface_hub
的一部分,并使用 AsyncInferenceClient
与 LLM 进行通信。
完整的
MCPClient
代码在这里,如果您想跟着实际代码一起学习 🤓
MCPClient
的主要职责
- 管理与一个或多个 MCP 服务器的异步连接。
- 从这些服务器发现工具。
- 为 LLM 格式化这些工具。
- 通过正确的 MCP 服务器执行工具调用。
这里简要展示了它如何连接到 MCP 服务器(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,
},
})
它支持用于本地工具的 stdio
服务器(例如访问您的文件系统),以及用于远程工具的 http
服务器!它还与 sse
兼容,后者是远程工具的旧标准。
使用工具:流式传输和处理
MCPClient
的 process_single_turn_with_tools
方法是 LLM 交互发生的地方。它通过 AsyncInferenceClient.chat.completions.create(..., stream=True)
将对话历史和可用工具发送给 LLM。
1. 准备工具并调用大语言模型
首先,该方法确定 LLM 在当前回合中应该知晓的所有工具——这包括来自 MCP 服务器的工具和任何用于智能体控制的特殊“退出循环”工具;然后,它对 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,
)
随着数据块从 LLM 陆续到达,该方法会遍历它们。每个数据块都会立即被 yield,然后我们重构完整的文本响应和任何工具调用。
# 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. 执行工具
一旦流结束,如果 LLM 请求了任何工具调用(现在已在 final_tool_calls
中完全重构),该方法会处理每一个调用。
# 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
...
它首先检查被调用的工具是否会退出循环(exit_loop_tool
)。如果不是,它会找到负责该工具的正确 MCP 会话并调用 session.call_tool()
。然后,结果(或错误响应)被格式化,添加到对话历史中,并被 yield,以便智能体能够知晓该工具的输出。
我们的微型 Python 智能体:它(几乎)只是一个循环!
由于 MCPClient
为工具交互做了所有工作,我们的 Agent
类变得非常简单。它继承自 MCPClient
并添加了对话管理逻辑。
Agent 类非常小巧,专注于对话循环,代码可以在这里找到。
1. 初始化智能体
创建 Agent 时,它会接收一个智能体配置(模型、提供商、要使用的 MCP 服务器、系统提示),并用系统提示初始化对话历史。然后,load_tools()
方法会遍历服务器配置(在 agent.json 中定义),并为每个配置调用 add_mcp_server
(来自父类 MCPClient
),从而填充智能体的工具箱。
# 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)
2. 智能体的核心:循环
Agent.run()
方法是一个异步生成器,用于处理单个用户输入。它管理对话轮次,决定智能体当前任务何时完成。
# 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")
在 run()
循环内部
- 它首先将用户提示添加到对话中。
- 然后它调用
MCPClient.process_single_turn_with_tools(...)
来获取 LLM 的响应,并处理任何工具执行,完成一个推理步骤。 - 每个项目都会立即被 yield,从而实现对调用者的实时流式传输。
- 在每一步之后,它会检查退出条件:是否使用了特殊的“退出循环”工具,是否达到了最大轮次限制,或者 LLM 提供的文本响应对于当前请求似乎是最终的。
下一步
有很多很酷的方式来探索和扩展 MCP 客户端和微智能体 🔥 这里有一些想法可以帮助您开始:
- 基准测试不同的 LLM 模型和推理提供商如何影响智能体性能:工具调用性能可能会有所不同,因为每个提供商可能会以不同的方式对其进行优化。您可以在此处找到支持的提供商列表。
- 使用本地 LLM 推理服务器运行微智能体,例如 llama.cpp 或 LM Studio。
- ……当然还有贡献!在 Hugging Face Hub 的 tiny-agents/tiny-agents 数据集中分享您独特的微智能体并提交 PR。
欢迎提交拉取请求和贡献!再次强调,这里的一切都是开源的!💎❤️