Python 中的微智能体:一个由 MCP 驱动的约 70 行代码的智能体

发布于 2025 年 5 月 23 日
在 GitHub 上更新

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 文件中的 modelprovider 字段指定了智能体使用的 LLM 和推理提供商。servers 数组定义了智能体将连接的 MCP 服务器。在此示例中,配置了一个 "stdio" MCP 服务器。这种类型的服务器作为本地进程运行。智能体使用指定的 commandargs 启动它,然后通过标准输入/输出(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 兼容,后者是远程工具的旧标准。

使用工具:流式传输和处理

MCPClientprocess_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.cppLM Studio
  • ……当然还有贡献!在 Hugging Face Hub 的 tiny-agents/tiny-agents 数据集中分享您独特的微智能体并提交 PR。

欢迎提交拉取请求和贡献!再次强调,这里的一切都是开源的!💎❤️

社区

它不工作。我遇到了这个错误

63 │ │ os._exit(130) │
│ 64 │ │
│ 65 │ try: │
│ ❱ 66 │ │ loop.add_signal_handler(signal.SIGINT, _sigint_handler) │
│ 67 │ │ │
│ 68 │ │ async with Agent( │
│ 69 │ │ │ provider=config["provider"], │
│ │
│ ╭──────────────────────────────────────────────────────────────────────────────── locals ─────────────────────────────────────────────────────────────────────────────────╮ │
│ │ abort_event = <asyncio.locks.Event object at 0x000002B7D6EE1160 [unset]> │ │
│ │ agent_path = '.\agent_playwright\' │ │
│ │ config = { │ │
│ │ │ 'model': 'Qwen/Qwen2.5-72B-Instruct', │ │
│ │ │ 'provider': 'nebius', │ │
│ │ │ 'servers': [{'type': 'stdio', 'config': {'command': 'npx', 'args': ['@playwright/mcp']}}] │ │
│ │ } │ │
│ │ first_sigint = True │ │
│ │ loop = │ │
│ │ original_sigint_handler = functools.partial(<bound method Runner._on_sigint of <asyncio.runners.Runner object at 0x000002B7D6EE0AD0>>, main_task=<Task finished │ │
│ │ name='Task-1' coro=<run_agent() done, defined at B:\learning_llm\mcp.venv\Lib\site-packages\huggingface_hub\inference_mcp\cli.py:32> │ │
│ │ exception=NotImplementedError()>) │ │
│ │ prompt = None │ │
│ │ servers = [{'type': 'stdio', 'config': {'command': 'npx', 'args': ['@playwright/mcp']}}] │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │

·
文章作者

你好 @hevangel
非常抱歉给您带来不便,我们已经修复了这个问题并发布了一个补丁:https://github.com/huggingface/huggingface_hub/releases/tag/v0.32.1
请您升级一下 huggingface_hub 的版本好吗?

pip install -U huggingface_hub>=0.32.1

如果您发现任何其他意外行为,请告诉我们!

这是一个非常酷的想法,
我已将您的 JavaScript 版本转换为 Python,
它是一个简单的循环 + LiteLLM + MCP + 用于可扩展性的 Hook 系统。

https://github.com/askbudi/tinyagent

目前它支持以下 Hook

  • Gradio UI
  • Rich UI
  • 日志记录

以及存储层

  • PG / Supabase
  • SQLite
  • JSON

核心的 Tiny Agent 只依赖于 Litellm :D,没有其他依赖。

我认为如果任何人都可以根据自己的需求个性化自己的 Tinyagent,例如添加内存层或在 PG 中存储,那将非常酷。
在这里可以与此 repo 聊天,并为您的特定项目添加所需的功能

https://askdev.ai/github/askbudi/tinyagent

·
文章作者

你好 @insightfactory
非常棒!也欢迎您随时查看并为我们的 tiny-agents Python 实现做出贡献(我们非常欢迎社区的贡献!):https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub/inference/_mcp :)

你好,huggingface_hub[mcp] 0.32.3 似乎有问题,总是产生

TypeError: Passing coroutines is forbidden, use tasks explicitly.
<sys>:0: RuntimeWarning: coroutine 'Event.wait' was never awaited

降级到 0.32.1 似乎可以解决问题。

关于使用本地 llm,如果能提供一个使用 ollama 的 agent.json 配置示例,将不胜感激。我一直在尝试像这样的配置
编辑:这个配置确实是设置 ollama 的正确方法

{
  "model": "llama3.2:3b",
  "endpointUrl": "https://:11434",
  "servers": [... ]
}

使用 tiny-agent.js 会产生一个错误

SyntaxError: Unexpected non-whitespace character after JSON at position 35
    at JSON.parse (<anonymous>)

编辑:问题部分解决,请参阅 https://github.com/huggingface/huggingface.js/issues/1502

而使用 tiny-agent python 会产生一个错误,显然是由于缺少 provider 键。

·
文章作者

你好 @sylvain471
感谢报告这个问题!
对于使用 huggingface_hub==0.32.3 时的第一个错误,您有可复现的示例吗?如果可能的话,您能分享一下发生错误时使用的智能体配置吗?

至于本地 LLM,支持是在 huggingface_hub>=0.32.2 中引入的,所以旧版本出现错误是正常的。

你好 @celinah
我在 https://github.com/sdelahaies/tiny-agent-mcp.git 上创建了一个带有可复现示例的最小化示例,但经过一些测试后,问题似乎出在 python 版本上:在我的设置中,uv venv 安装了 python 3.13.1,示例失败了,而使用 uv venv --python 3.11 则可以正常工作。
至于 0.32.1 版本,我不得不调整 agent 类才能让本地 LLM 工作,但在运行所有测试时我忘记了这些更改... 总之我的结论是降级 python 版本
谢谢!

·
文章作者

确实如此,这个 bug 只在 python >= 3.12 上出现。我们已经修复了它并发布了一个补丁,请看我在这里的评论:https://huggingface.co/blog/python-tiny-agents#683eca4750b8f035fa0407b9

我扩展了我的 TinyAgent 实现,并创建了 CodeTinyAgent,它类似于 Smolagents(用 Python 思考),但代码只有 50 行左右。
我还将模态函数扩展了一点,使其具有状态。CodeTinyAgent 在云端执行 Python 代码。

在线版本
https://huggingface.co/spaces/Agents-MCP-Hackathon/TinyCodeAgent

源码
https://github.com/askbudi/TinyCodeAgent

·
文章作者

这太酷了 @insightfactory

这是我的 agent.json
{
"model": "qwen3:4b",
"endpointUrl": "https://:11434/",
"provider": "auto",
"servers": [
{
"type": "sse",
"config": {
"url": "http://127.0.0.1:7860/gradio_api/mcp/sse"
}
}
]
}

我收到了这个错误

代理运行期间出错:仓库 id 必须使用字母数字字符,或者'-', '_', '.',禁止使用'--'和'..','-'和'.'不能作为名称的开头或结尾,最大长度为 96:'qwen3:4b'。

我正在使用 python 3.13 和 huggingface-hub 0.33.0 的库

·
文章作者

你能移除 provider: "auto" 这一行吗?

注册登录 以发表评论