从工具调用到 MCP、Skill完整学习记录

文章目录

工具调用、MCP 与 Skill:从"模型能干什么"到"模型怎么干"

最近在啃 MCP、Skill 以及工具调用这几个概念,发现它们很容易混淆,而且在不同语境下含义还不一样。这篇文章先梳理清楚它们分别是什么,以及之间的区别。为了方便理解,所有例子都用客服场景来写(订单查询、退换货流程),比天气和旅行更贴近真实业务。

一、工具调用:模型"开口要函数"

工具调用(Tool Calling) 是 LLM 的一种能力,不是框架也不是协议。模型本身只会生成文本,但我们可以让它输出一种特殊格式的指令,告诉宿主程序:"我想调用这个函数,参数是这些"。程序收到后真正去执行,然后把结果塞回给模型,让它继续生成回答。

我第一次跑通的工具调用代码大概长这样:

python 复制代码
import openai
import json

client = openai.OpenAI(api_key="your-api-key")

# 定义一个工具:查询订单详情
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_order_details",
            "description": "根据订单号查询订单状态、金额和物流信息",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "订单编号,例如 ORD-2024001"
                    }
                },
                "required": ["order_id"]
            }
        }
    }
]

# 用户问题(模拟客服场景)
messages = [
    {"role": "user", "content": "帮我查一下订单 ORD-2024001 现在到哪里了,还有总金额是多少"}
]

# 第一次请求,让模型决定是否调用工具
response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    tool_choice="auto"
)

# 检查模型是否请求工具调用
message = response.choices[0].message
if message.tool_calls:
    tool_call = message.tool_calls[0]
    function_name = tool_call.function.name
    arguments = json.loads(tool_call.function.arguments)

    print(f"模型请求调用 {function_name},参数:{arguments}")

    # 模拟一个订单查询函数
    def get_order_details(order_id: str) -> str:
        # 实际项目中这里会连接订单数据库
        return (
            f"订单号:{order_id}\n"
            f"状态:已发货\n"
            f"总金额:299.00元\n"
            f"物流:快递单号 SF123456,预计明天送达"
        )

    # 执行函数,拿到结果
    result = get_order_details(**arguments)

    # 把工具调用结果附加到对话中
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": result
    })

    # 第二次请求,让模型基于工具结果生成最终回复
    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages
    )
    print(final_response.choices[0].message.content)

流程非常清晰:模型输出调用意图 → 宿主程序执行 → 返回结果 → 模型总结回复。模型自己一行代码都不跑,只负责"指挥"。

二、MCP:让工具连接标准化

工具调用很好,但每接入一个新服务都得写一套对接逻辑,而且不同 LLM 的工具定义格式都不一样。MCP(Model Context Protocol) 正是为了解决这个问题而诞生的。

MCP 由 Anthropic 开源,可以理解为"工具调用的 USB 接口"。它定义了一套标准协议,让任何兼容 MCP 的客户端都能以统一的方式发现、调用任何 MCP 服务器上的工具。

架构里有两个角色:

MCP Server:真正提供工具或数据的一方,暴露标准化接口。

MCP Client:AI 宿主程序,通过协议与 Server 通信。`

我用 Python 实现了一个客服场景的 MCP Server,包含订单查询和创建工单两个工具:

python 复制代码
# customer_service_server.py
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
import mcp.server.stdio
import mcp.types as types

server = Server("customer-service-server")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="get_order_details",
            description="查询订单详细信息,包括状态、金额和物流",
            inputSchema={
                "type": "object",
                "properties": {
                    "order_id": {"type": "string", "description": "订单编号"}
                },
                "required": ["order_id"]
            }
        ),
        types.Tool(
            name="create_support_ticket",
            description="为客户创建一条新的售后工单",
            inputSchema={
                "type": "object",
                "properties": {
                    "customer_name": {"type": "string", "description": "客户姓名"},
                    "issue_type": {
                        "type": "string",
                        "description": "问题类型:退货/换货/投诉/咨询"
                    },
                    "description": {"type": "string", "description": "问题描述"}
                },
                "required": ["customer_name", "issue_type", "description"]
            }
        )
    ]

@server.call_tool()
async def handle_call_tool(
    name: str, arguments: dict
) -> list[types.TextContent]:
    if name == "get_order_details":
        order_id = arguments["order_id"]
        result = (
            f"订单号:{order_id}\n"
            f"状态:已签收\n"
            f"金额:158.00元\n"
            f"物流:已送达"
        )
        return [types.TextContent(type="text", text=result)]
    elif name == "create_support_ticket":
        ticket_id = f"TK-{arguments['customer_name'][:2]}-2024001"
        result = f"工单已创建,编号:{ticket_id},类型:{arguments['issue_type']}"
        return [types.TextContent(type="text", text=result)]
    raise ValueError(f"未知工具: {name}")

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationCapabilities(
                sampling={},
                experimental={},
            ),
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

客户端调用也很直接:

python 复制代码
# mcp_client_demo.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server_params = StdioServerParameters(
        command="python",
        args=["customer_service_server.py"]
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            tools = await session.list_tools()
            print("可用工具:", [tool.name for tool in tools.tools])

            result = await session.call_tool("get_order_details", {"order_id": "ORD-2024001"})
            print("查询结果:", result.content[0].text)

            result = await session.call_tool("create_support_ticket", {
                "customer_name": "张明",
                "issue_type": "退货",
                "description": "收到的商品有破损,需要退货退款"
            })
            print("工单结果:", result.content[0].text)

asyncio.run(main())

到这里,我对 MCP 的理解就一句话:MCP 是一套标准协议,让工具可以被统一发现、描述和调用,把工具调用能力做了一层工程化封装,实现即插即用。

三、Skill:把多个工具和流程打包成一个"技能"

Skill 这个词在不同语境下含义差别很大,但核心思想一致:对一个完整能力的封装,内部可能组合了多次工具调用、流程控制甚至特定的 prompt 逻辑。

在 Claude 生态中,Skill 常常以 Markdown 文件 的形式存在。文件包含 YAML frontmatter 声明名字、描述和所需工具,正文则是写给模型的系统指令,告诉它按什么步骤完成任务。名称和描述一定要定义清楚,不然调用会出错。

markdown 复制代码
---
name: customer-refund-handler
description: 处理客户的退货退款请求,自动查询订单、创建工单、发起退款并回复客户
tools:
  - get_order_details
  - create_support_ticket
  - initiate_refund
---

你是电商客服的自动处理助理。当用户提出退货退款请求时,请按以下步骤操作:

1. 向用户确认订单号,然后使用 get_order_details 工具查询订单状态。
2. 如果订单符合退货条件(已签收且在退货期内),使用 create_support_ticket 创建一条退货工单。
3. 使用 initiate_refund 工具发起退款,参数使用订单号及订单金额。
4. 把工单编号和退款预计到账时间整合成一段安抚性话术,回复给客户。
5. 如果订单不符合退货条件,请礼貌地解释原因,并提供进一步咨询的渠道。
这种 Skill 本质上是一段"指导模型如何干活"的 prompt,依赖于底层工具(可能通过 MCP 提供),模型无关且易于分发。

四、三者核心区别

一句话区别:

工具调用:模型说"我要调用 xxx 函数"。

MCP:为这种调用铺了一条标准化的路,让任何模型和工具都能用同一套语言沟通。

Skill:把多个工具调用和流程打包成一个完整的"技能",更贴近真实业务需求。

这三者不是互斥的,而是层层递进的关系。在实际搭建客服系统时,我会先用 MCP 封装订单查询、工单创建、退款等外部能力,再在上层构建 Skill,最后让 LLM 通过工具调用或直接触发 Skill 来完成用户的退换货、咨询等指令。整个架构会清晰很多。

相关推荐
ThsPool4 小时前
【遥感学习整理 02】ENVI遥感图像处理基础:从数据读取到遥感信息产品
图像处理·人工智能·学习
卷无止境4 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境4 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1234 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102165 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb005 小时前
一个项目带你入门AI应用开发01
python
0566465 小时前
Python康复训练——常用标准库
开发语言·python·学习
极光代码工作室5 小时前
基于SpringBoot的课程预约系统
java·springboot·web开发·后端开发
昆曲之源_娄江河畔5 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql
Leighteen6 小时前
`try-finally` 里的 `return`:为什么 `finally` 会悄悄改掉返回值、吞掉异常
java·开发语言