第 6 章DeepSeek的Function Calling与MCP应用实战

项目管理中 PMO 的关键职能解析 在企业数字化转型与战略落地的浪潮中,PMO(Project Management Office,项目管理办公室)逐渐从“项目辅助角色”升级为“组织级战略执行中枢”。然而,多数人对PMO的认知仍停留在“管项目的部门”或“文档审核机器”的层面。本文将打破误解,通过生活化类比(如“中枢神经”“导航系统”)、流程图解(战略对齐循环)、实际案例(制造企业PMO升级),深入解析PMO的五大核心职能:战略对齐、流程标准化、资源管理、绩效监控、知识沉淀。最终揭示PMO的本质—— 阅读详情

DeepSeek原生应用与智能体开发实践(人工智能技术丛书)【行情 报价 价格 评测】-京东

王晓华DeepSeek开发AI应用入门书《DeepSeek原生应用与智能体开发实践》全文分享~持续更新-优快云博客

目录

6.1  DeepSeek自带的Function Calling详解

6.1.1  Python使用工具的基本原理

6.1.2  DeepSeek工具使用详解

6.1.3  DeepSeek工具箱的使用

6.2  给大模型插上翅膀的MCP协议详解

6.2.1  MCP协议目的、功能与架构详解

6.2.3  MCP实战2:本地客户端搭建与使用

6.3  在线MCP服务器的搭建与使用实战

6.3.1  在线MCP服务器搭建

6.3.2  在线MCP服务的连接和使用

6.4  本 章 小 结


大模型的工具调用(Function Calling)功能,作为人工智能领域的一项关键技术,赋予了大模型与外部世界进行交互和协作的能力。在这一领域,DeepSeek所实现的工具调用机制,以其高效、灵活的特点,成为众多开发者和研究者关注的焦点。它不仅能够让模型根据需求精准地调用各类外部工具,还极大地拓展了模型的应用场景并增加了其实用价值。

而MCP(Model Context Protocol,模型上下文协议),作为一种具有开创性的开放协议,其核心目标在于标准化人工智能模型与外部数据源、工具之间的交互方式。在当今复杂多变的人工智能应用环境中,模型需要与各种不同类型的数据源和工具进行交互,以实现更加智能、高效的任务处理。MCP通过定义一套统一的通信规范,成功打破了模型与外部系统之间的壁垒,使得大语言模型(LLM)能够轻松、无缝地连接本地文件、数据库、API以及各类专业工具。这一协议的出现,为人工智能模型的集成和应用提供了更加便捷、高效的解决方案。

在本章中,我们将深入剖析DeepSeek工具调用的使用技巧以及识别方法。通过详细的讲解和实例演示,读者将能够全面了解如何运用DeepSeek的工具调用功能,实现模型与外部工具的高效协同。同时,我们也将深入探讨MCP协议在其中的重要作用,以及如何利用MCP协议进一步优化模型与外部系统的交互过程。

随着学习的深入,读者将逐渐掌握如何在实际项目中灵活运用DeepSeek的工具调用和MCP协议,构建出更加智能、高效的人工智能应用。无论是处理复杂的自然语言任务,还是与各类外部数据源和工具进行深度集成,这些知识和技能都将成为读者在人工智能领域取得成功的有力武器。我们相信,通过本章的学习,读者将能够开启一段全新的人工智能探索之旅,为未来的技术创新和应用发展奠定坚实的基础。      

6.1  DeepSeek自带的Function Calling详解

相对于只能完成普通文本任务的大模型,DeepSeek一个激动人心的功能是可以自主调用外部工具函数,以自主意识的形式借用工具,完成使用者发布的命令。这意味着DeepSeek不再仅仅是一个被动的执行者,而是成为一个具有主动性的智能助手。

DeepSeek的Function Calling功能是一项具有划时代意义的进步。这一功能的实现,使得DeepSeek不仅仅局限于自身数据库知识的回答,而是跃进到了一个全新的层次——调用外部函数,其调用流程如图6-1所示。

图6-1  DeepSeek的Function Calling功能

这意味着DeepSeek大语言模型在与用户交互时,可以实时检索外部函数库。当用户提问时,模型不再仅仅是从自身知识库中寻找答案,而是会根据实际需求,在外部函数库中进行检索,找出合适的函数并调用它。这种调用外部函数的能力,使得DeepSeek可以获取到函数的运行结果,并基于这些结果进行回答。

6.1.1  Python使用工具的基本原理

工具的使用是一项非常简单的事情,从我们的祖先钻木取火,到现在人类飞上月球在太空建立永久基地,这些都离不开工具的使用。甚至在现实生活中,你决定今天出门要不要带上雨伞,都需要借助网络信息或者广播工具了解到今天的天气情况。

而Python同样也可以使用工具来完成对外部API的调用,其所需要的仅仅是一个函数名称而已。示例代码如下:

# 创建一个简单的查询天气的API

def get_weather(location = ""):

    "读者可以编写对应的天气查询API,这里我们仅仅作演示"

    if location == "Shanghai":

        return 23.0

    elif location == "TianJin":

        return 25.0

    else:

        return "未查询到相关内容"

location = "Shanghai"

# 注意写法格式,里面的单引号不能少

result = eval(f"get_weather(location='{location}')")#使用eval调用与字符串名称对应的函数

print("查询到的结果是:", result)

最终打印结果如下:

查询到的结果是: 23.0

可以看到,Python中提供的eval()函数可以根据传入的字符串自动运行对应的函数。在这个示例中,我们将location变量的值嵌入到字符串中,然后将该字符串作为代码传给eval()函数执行。注意,在嵌入变量值时,我们使用了单引号将变量值括起来,以确保代码的正确解析。

eval()函数是Python的一个内置函数,它的功能是将字符串作为Python代码执行。其工作原理可以简单概括为“字符串解析和执行”。

当我们调用eval()函数并传入一个字符串时,函数会尝试解析这个字符串,将它转换成Python的表达式或语句,然后在当前的命名空间中执行这些表达式或语句。例如,如果我们传入字符串"1+2",print(eval("1+2"))。eval()函数会将这个字符串解析为Python的加法表达式,然后计算这个表达式的值,返回结果3。

6.1.2  DeepSeek工具使用详解

在上一小节中,我们展示了如何在Python中调用函数,但是,我们面临一个更复杂的问题:如何在大模型DeepSeek中调用工具?这个问题看似简单,实则涉及许多深层次的技术与思考。就如同多年前人们询问计算机“今天是晴天还是雨天”一样,我们如今要探讨的是如何让大模型调用工具来解决问题。

先回到日常生活中的一个例子。在决定今天的穿着之前,我们通常会有一个明确的前置任务:了解今天的天气。那么,如何获取天气信息呢?以下是一些可能的方法:

  • A:对着衣橱问自己应该穿什么衣服。这显然不是获取天气信息的正确途径。
  • B:使用互联网登录天气网站,输入本地名称查询。这是一个有效且常用的方法。
  • C:打开一本书并阅读任意一页。这与获取天气信息无关。
  • D:打开空调。这同样不能告诉我们今天的天气情况。

对于大多数读者来说,选择B是显而易见的,这是基于我们的常识和日常经验。然而,这种基于目标寻找最合适解决方案的能力并非天生,而是需要我们后天的学习和积累。我们需要知道哪些工具或方法可以帮助我们实现目标,这通常需要一个知识库或他人的指导。有知识库辅助研判的任务流程如图6-2所示。

图6-2  有知识库辅助研判的任务流程

上图所示是一个基于常识的决策过程,同时也是我们在日常生活中做出明智决策并取得良好结果的通用步骤。在每次决策之前,我们依赖的是深厚的知识储备或知识库,它们如同明灯,照亮我们前行的道路,引导我们做出最优决策。

当我们回到DeepSeek调用工具的问题时,面临的挑战是如何让这个大模型也具备这样的决策能力,即根据给定的任务,它能知道应当调用哪些工具。作为深度学习程序设计人员,我们的责任不仅是开发模型,更要引导模型如何使用工具。我们可以提供格式化的API信息,这种方式就像是给大模型提供一本详细的程序文档。在这份文档中,我们详细描述每个工具API的功能、参数以及返回值,告诉大语言模型在何时、何地可以调用这些API,并且当API被调用后,返回相应的API的JSON对象。

这样的方式能够让大模型更加智能化地运用工具,进而提升其解决问题的效率和准确性。想象一下,当大模型遇到问题时,它可以像人类一样查阅“工具书”,找到最合适的工具,然后利用这个工具解决问题。

一个可供DeepSeek进行调用的简单函数如下所示:

# 定义工具函数

def get_weather(function_params):

    """模拟获取天气的工具函数"""

    location = function_params[0]

    # 这里可以调用真实的天气 API

    return f"{location}的天气晴朗"

上述函数对象描述了一个名为get_weather的工具API。通过这个API,大模型可以根据输入的城市名称获取当前的天气情况。这样的描述方式清晰明了,使得大模型能够准确理解并调用这个API。因此,通过对工具API中的描述进行甄别,从而判定使用哪一个最合适的工具,加上合理的引导和训练,可以使大模型更加智能化,从而完成对工具的使用。

作者完成了一个在DeepSeek中使用工具的完整示例,代码如下所示:

from openai import OpenAI

import json

client = OpenAI(

    api_key="sk-c646e1c201d74777b54f45c60973f4f3",

    base_url="https://api.deepseek.com",

)

def get_weather(function_params):

    return "天气晴朗"

def send_messages(messages):

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=messages

    )

    return response.choices[0].message

system_prompt = """

   你在运行一个“思考”“工具调用”“响应”循环。每次只运行一个阶段

   1.“思考”阶段:你要仔细思考用户的问题。

   2.“工具调用”阶段:选择可以调用的工具,并且输出对应工具需要的参数。

   3.“响应”阶段:根据工具调用返回的影响,回复用户问题。

   已有的工具如下:

   get_weather:

   e.g. get_weather:天津

   返回天津的天气情况

   Example:

   question:天津的天气怎么样?

   thought:我应该调用工具查询天津的天气情况

   Action:

   {

        "function_name":"get_weather",

        "function_params":["天津"]

   }

   调用Action的结果:“天气晴朗”

   Answer:天津的天气晴朗

"""

question = "Shanghai的天气怎么样"

messages = [{"role": "system", "content": system_prompt},

            {"role": "user", "content": question}]

message = send_messages(messages)

response = message.content

action = response.split("Action:")[1]

action = json.loads(action)

print(f"ModelResponse:\n {action}")

# 生成调用代码

function_name = action["function_name"]

function_params = action["function_params"]

code = f"{function_name}({function_params})"

print(code)

# 使用eval对生成的代码进行计算,这里假设get_weather函数已经被定义过

result = eval(code)

print(result)

这段代码实现了一个简单的对话系统,能够根据用户的问题调用相应的工具并生成回答。首先,代码通过OpenAI库初始化了一个客户端,并设置了API密钥和基础URL。接着,定义了一个get_weather函数,用于模拟获取天气的功能,返回固定的“天气晴朗”结果。send_messages函数则负责向模型发送消息并获取模型的响应。系统提示(system_prompt)中详细描述了对话系统的三个阶段:思考、工具调用和响应,并提供了一个示例说明如何调用get_weather工具,以回答与天气相关的问题。

生成结果如下所示:

ModelResponse:

 {'function_name': 'get_weather', 'function_params': ['上海']}

get_weather(['上海'])

天气晴朗

具体来看,在代码的执行部分,用户提出了一个关于上海天气的问题。系统通过send_messages函数将问题发送给模型,模型根据系统提示生成一个包含工具调用信息的响应。代码通过解析响应中的Action部分,提取出需要调用的工具名称和参数,并生成相应的调用代码。最后,使用eval函数执行生成的代码,模拟工具调用的过程,并输出结果。整个过程展示了如何通过模型生成工具调用指令,并动态执行这些指令来完成用户请求。

6.1.3  DeepSeek工具箱的使用

上面我们演示了在DeepSeek中使用单一工具的方法。但是,在具体工作中,我们可能会面临一个选择的问题,即在一个“工具箱”中完成工具的选择,之后再使用工具去完成我们的目标。下面给出一个在DeepSeek中有选择地使用工具的完整示例:

from openai import OpenAI

import json

# 初始化 OpenAI 客户端

client = OpenAI(

    api_key="sk-c646e1c201d74777b54f45c60973f4f3",  # 替换为你的API 密钥

    base_url="https://api.deepseek.com",  # DeepSeek API的基础 URL

)

# 定义工具函数

def get_weather(function_params):

    """模拟获取天气的工具函数"""

    location = function_params[0]

    # 这里可以调用真实的天气 API

    return f"{location}的天气晴朗"

def get_stock(function_params):

    """模拟获取股票的工具函数"""

    location = function_params[0]

    # 这里可以调用真实股票价格 API

    return f"{location}的股票价格为18.88"

# 定义工具列表

tools = [

    {

        "type": "function",

        "function": {

            "name": "get_weather",

            "description": "获取指定城市的天气情况",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "城市名称,例如:上海",

                    }

                },

                "required": ["location"],

            },

        },

    },

    {

        "type": "function",

        "function": {

            "name": "get_stock",

            "description": "模拟获取股票的工具函数",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "股票名称,例如:上海证券",

                    }

                },

                "required": ["location"],

            },

        },

    }

]

# 发送消息并调用工具

def send_messages_tools(messages):

    """发送消息并调用工具"""

    response = client.chat.completions.create(

        model="deepseek-chat",  # 使用的模型

        messages=messages,  # 消息列表

        tools=tools,  # 工具列表

        tool_choice="auto",  # 让模型自动选择是否调用工具

    )

    return response.choices[0].message

# 系统提示

system_prompt = """

你是一个智能助手,能够通过“思考”“工具调用”和“响应”三个阶段来处理用户的问题。每个阶段的任务如下:

1. **思考阶段**:你需要仔细分析用户的问题,判断是否需要调用工具来获取信息。如果需要调用工具,明确选择适合的工具并准备调用参数。

2. **工具调用阶段**:根据思考阶段的结果,选择合适的工具并生成工具调用请求。工具调用的参数需要符合工具的定义。

3. **响应阶段**:根据工具调用的返回结果,生成对用户问题的最终回答。

示例:

用户问题:天津的天气怎么样?

思考:我需要调用工具查询天津的天气情况。

工具调用:

{

    "function_name": "get_weather",

    "function_params": {"location": "天津"}

}

工具调用结果:“天津的天气晴朗”

最终回答:天津的天气晴朗。

"""

# 用户问题

question = "Shanghai的天气是什么?"

# 消息列表

messages = [

    {"role": "system", "content": system_prompt},

    {"role": "user", "content": question},

]

# 发送消息并获取模型响应

message = send_messages_tools(messages)

# 打印模型返回的原始消息

print(f"Initial Model Response: {message}")

# 检查模型是否返回了工具调用请求

if message.tool_calls:

    # 解析工具调用请求

    tool_call = message.tool_calls[0]

    function_name = tool_call.function.name

    function_params = json.loads(tool_call.function.arguments)

    # 打印工具调用信息

    print(f"Tool Call: {tool_call}")

    print(f"Function Name: {function_name}")

    print(f"Function Params: {function_params}")

    # 根据工具名称调用相应的工具

    if function_name == "get_weather":

        result = get_weather([function_params["location"]])

    elif function_name == "get_stock":

        result = get_stock([function_params["location"]])

    else:

        result = "未知工具"

    # 打印工具执行结果

    print(f"Tool Result: {result}")

结果如下所示:

Initial Model Response: ChatCompletionMessage(content='', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_0_18c66898-e9e9-45de-98bf-4e4961cb9400', function=Function(arguments='{"location":"Shanghai"}', name='get_weather'), type='function', index=0)])

Tool Call: ChatCompletionMessageToolCall(id='call_0_18c66898-e9e9-45de-98bf-4e4961cb9400', function=Function(arguments='{"location":"Shanghai"}', name='get_weather'), type='function', index=0)

Function Name: get_weather

Function Params: {'location': 'Shanghai'}

Tool Result: Shanghai的天气晴朗

我们分别对其进行讲解。首先,定义要使用的工具,并通过列表的形式对工具进行汇总,这里定义了两个工具函数get_weather和get_stock,分别用于模拟获取指定城市的天气情况和股票价格。每个函数接受一个参数function_params,其中包含所需的参数信息(如城市名称或股票名称),并返回相应的模拟结果。接着,代码定义了一个tools列表,其中包含这两个工具函数的元数据描述,包括函数名称、功能描述以及参数的定义(如参数类型、描述等)。这些元数据可以用于动态调用这些工具函数,或者集成到其他系统中进行自动化处理。

以下是对代码的详细分步讲解和模型介绍,我们将结合代码的每一部分进行说明。

1. 初始化OpenAI客户端

from openai import OpenAI

import json

# 初始化 OpenAI 客户端

client = OpenAI(

    api_key="sk-c646e1c201d74777b54f45c60973f4f3",  # 替换为你的API密钥

    base_url="https://api.deepseek.com",  # DeepSeek API的基础URL

)

(1)功能:初始化OpenAI客户端,用于与DeepSeek API进行交互。

(2)关键点:

  1. api_key:用于身份验证的API密钥。
  2. base_url:DeepSeek API的基础URL,指定API的访问地址。

2. 定义工具函数

def get_weather(function_params):

    """模拟获取天气的工具函数"""

    location = function_params[0]

    return f"{location}的天气晴朗"

def get_stock(function_params):

    """模拟获取股票的工具函数"""

    location = function_params[0]

    return f"{location}的股票价格为18.88"

(1)功能:定义了两个工具函数,分别用于模拟获取天气和股票信息。

(2)参数:

  1. function_params:一个列表,包含工具调用时传递的参数(如城市名称或股票名称)。

(3)返回值:返回一个字符串,表示模拟的结果。

(4)说明:这些函数是模拟实现,实际应用中可以通过调用真实的API获取数据。

3. 定义工具列表

tools = [

    {

        "type": "function",

        "function": {

            "name": "get_weather",

            "description": "获取指定城市的天气情况",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "城市名称,例如:上海",

                    }

                },

                "required": ["location"],

            },

        },

    },

    {

        "type": "function",

        "function": {

            "name": "get_stock",

            "description": "模拟获取股票的工具函数",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "股票名称,例如:上海证券",

                    }

                },

                "required": ["location"],

            },

        },

    }

]

(1)功能:定义工具函数的元数据,包括名称、描述和参数。

(2)结构:

  • type:工具类型,这里是function。
  • name:工具函数的名称。
  • description:工具函数的功能描述。
  • parameters:定义工具函数的参数类型和结构。
  • properties:参数的属性(如location的类型和描述)。
  • required:指定哪些参数是必需的。

(3)说明:这些元数据用于指导模型如何调用工具函数。

4. 发送消息并调用工具

def send_messages_tools(messages):

    """发送消息并调用工具"""

    response = client.chat.completions.create(

        model="deepseek-chat",  # 使用的模型

        messages=messages,  # 消息列表

        tools=tools,  # 工具列表

        tool_choice="auto",  # 让模型自动选择是否调用工具

    )

    return response.choices[0].message

(1)功能:发送用户问题到模型,并返回模型的响应。

(2)参数:

  • model:使用的模型名称(这里是deepseek-chat)。
  • messages:包含系统提示和用户问题的消息列表。
  • tools:工具列表。
  • tool_choice:设置为auto,表示由模型自动决定是否调用工具。

(3)返回值:返回模型生成的消息。

(4)说明:该函数是核心逻辑,负责与模型交互并获取工具调用请求。

5. 系统提示

system_prompt = """

你是一个智能助手,能够通过“思考”“工具调用”和“响应”三个阶段来处理用户的问题。每个阶段的任务如下:

1. **思考阶段**:你需要仔细分析用户的问题,判断是否需要调用工具来获取信息。如果需要调用工具,明确选择适合的工具并准备调用参数。

2. **工具调用阶段**:根据思考阶段的结果,选择合适的工具并生成工具调用请求。工具调用的参数需要符合工具的定义。

3. **响应阶段**:根据工具调用的返回结果,生成对用户问题的最终回答。

示例:

用户问题:天津的天气怎么样?

思考:我需要调用工具查询天津的天气情况。

工具调用:

{

    "function_name": "get_weather",

    "function_params": {"location": "天津"}

}

工具调用结果:“天津的天气晴朗”

最终回答:天津的天气晴朗。

"""

(1)功能:指导模型如何处理用户问题。

(2)内容:

  1. 定义了模型的工作流程(思考、工具调用、响应等)。
  2. 提供了一个示例,帮助模型理解如何调用工具并生成响应。

(3)说明:系统提示是模型行为的关键指导,确保模型能够正确调用工具。

6. 用户问题与消息列表

question = "Shanghai的天气是什么?"

messages = [

    {"role": "system", "content": system_prompt},

    {"role": "user", "content": question},

]

(1)功能:定义用户问题,并将其与系统提示一起组成消息列表。

(2)结构:

  1. "role": "system":系统提示,用于指导模型。
  2. "role": "user":用户问题。

(3)说明:消息列表是模型输入的核心部分,决定了模型的行为和输出。

7. 发送消息并获取模型响应

message = send_messages_tools(messages)

print(f"Initial Model Response: {message}")

(1)功能:发送消息列表到模型,并打印模型的初始响应。

(2)说明:模型的初始响应可能包含工具调用请求或直接回答。

8. 检查工具调用请求

if message.tool_calls:

    # 解析工具调用请求

    tool_call = message.tool_calls[0]

    function_name = tool_call.function.name

    function_params = json.loads(tool_call.function.arguments)

    # 打印工具调用信息

    print(f"Tool Call: {tool_call}")

    print(f"Function Name: {function_name}")

    print(f"Function Params: {function_params}")

    # 根据工具名称调用相应的工具

    if function_name == "get_weather":

        result = get_weather([function_params["location"]])

    elif function_name == "get_stock":

        result = get_stock([function_params["location"]])

    else:

        result = "未知工具"

    # 打印工具执行结果

    print(f"Tool Result: {result}")

(1)功能:解析模型的工具调用请求,执行相应的工具函数,并返回结果。

(2)步骤:

  • 检查模型是否返回了工具调用请求(tool_calls)。
  • 解析工具调用的名称和参数。
  • 根据工具名称调用相应的工具函数。
  • 打印工具执行结果。

(3)说明:该部分是工具调用的核心逻辑,负责执行工具函数并处理结果。

9. 代码运行流程

(1)初始化OpenAI客户端。

(2)定义工具函数和工具列表。

(3)发送用户问题到模型,并获取模型的初始响应。

(4)检查模型是否返回了工具调用请求。

(5)解析工具调用请求,执行相应的工具函数。

(6)打印工具执行结果。

上面示例代码实现了一个基于工具调用的智能助手系统,能够根据用户问题动态调用工具函数并返回结果。通过定义工具函数、工具列表和系统提示,代码实现了灵活的问题处理机制,适用于需要外部数据支持的场景(如天气查询、股票查询等)。

6.1.4  DeepSeek工具调用判定依据

在我们进行工具调用时,DeepSeek也需要有一个判断依据,为了找到这个判断依据,我们修改部分代码,特别是工具箱的描述,如下所示:

from openai import OpenAI

import json

# 初始化 OpenAI 客户端

client = OpenAI(

    api_key="sk-c646e1c201d74777b54f45c60973f4f3",  # 替换为你的API密钥

    base_url="https://api.deepseek.com",  # DeepSeek API的基础URL

)

# 定义匿名工具函数

def get_tool2(function_params):

    location = function_params[0]

    return f"{location}的天气晴朗"

def get_tool1(function_params):

    location = function_params[0]

    return f"{location}的股票价格为18.88"

# 定义工具列表

tools = [

    {

        "type": "function",

        "function": {

            "name": "get_tool1",

            "description": "模拟获取股票的工具函数",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "",

                    }

                },

                "required": ["location"],

            },

        },

    },

    {

        "type": "function",

        "function": {

            "name": "get_tool2",

            "description": "模拟获取天气的工具函数",

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "",

                    }

                },

                "required": ["location"],

            },

        },

    }

]

# 发送消息并调用工具

def send_messages_tools(messages):

    """发送消息并调用工具"""

    response = client.chat.completions.create(

        model="deepseek-chat",  # 使用的模型

        messages=messages,  # 消息列表

        tools=tools,  # 工具列表

        tool_choice="auto",  # 让模型自动选择是否调用工具

    )

    return response.choices[0].message

# 系统提示

system_prompt = """

你是一个智能助手,能够通过“思考”“工具调用”和“响应”三个阶段来处理用户的问题。每个阶段的任务如下:

1. **思考阶段**:你需要仔细分析用户的问题,判断是否需要调用工具来获取信息。如果需要调用工具,明确选择适合的工具并准备调用参数。

2. **工具调用阶段**:根据思考阶段的结果,选择合适的工具并生成工具调用请求。工具调用的参数需要符合工具的定义。

3. **响应阶段**:根据工具调用的返回结果,生成对用户问题的最终回答。

示例:

用户问题:天津的天气怎么样?

思考:我需要调用工具查询天津的天气情况。

工具调用:

{

    "function_name": "get_tool",

    "function_params": {"location": "天津"}

}

工具调用结果:“天津的天气晴朗”

最终回答:天津的天气晴朗。

"""

# 用户问题

question = "Shanghai的天气是什么?"

# 消息列表

messages = [

    {"role": "system", "content": system_prompt},

    {"role": "user", "content": question},

]

# 发送消息并获取模型响应

message = send_messages_tools(messages)

# 打印模型返回的原始消息

print(f"Initial Model Response: {message}")

# 检查模型是否返回了工具调用请求

if message.tool_calls:

    # 解析工具调用请求

    tool_call = message.tool_calls[0]

    function_name = tool_call.function.name

    function_params = json.loads(tool_call.function.arguments)

    # 打印工具调用信息

    print(f"Tool Call: {tool_call}")

    print(f"Function Name: {function_name}")

    print(f"Function Params: {function_params}")

    # 根据工具名称调用相应的工具

    if function_name == "get_tool1":

        result = get_tool1([function_params["location"]])

    elif function_name == "get_tool2":

        result = get_tool2([function_params["location"]])

    else:

        result = "未知工具"

    # 打印工具执行结果

    print(f"Tool Result: {result}")

在上面代码中,我们匿名定义了工具函数,并删除了其描述部分,而仅仅在工具箱列表中对函数的作用进行解释。我们重新运行工具函数调用,结果如下所示:

Initial Model Response: ChatCompletionMessage(content='', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_0_c5a5e11b-9045-4fe5-8c89-0b6ca0d768b9', function=Function(arguments='{"location":"Shanghai"}', name='get_tool2'), type='function', index=0)])

Tool Call: ChatCompletionMessageToolCall(id='call_0_c5a5e11b-9045-4fe5-8c89-0b6ca0d768b9', function=Function(arguments='{"location":"Shanghai"}', name='get_tool2'), type='function', index=0)

Function Name: get_tool2

Function Params: {'location': 'Shanghai'}

Tool Result: Shanghai的天气晴朗

可以看到,我们替换了名称,并在函数体内部删除了注释,仅仅在工具描述列表中对每个工具函数进行描述,结果如下:

Initial Model Response: ChatCompletionMessage(content='', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_0_94b9f60d-bc93-406a-bc6a-d72f985e33d1', function=Function(arguments='{"location":"Shanghai"}', name='get_tool2'), type='function', index=0)])

Tool Call: ChatCompletionMessageToolCall(id='call_0_94b9f60d-bc93-406a-bc6a-d72f985e33d1', function=Function(arguments='{"location":"Shanghai"}', name='get_tool2'), type='function', index=0)

Function Name: get_tool2

Function Params: {'location': 'Shanghai'}

Tool Result: Shanghai的天气晴朗

可以看到,此时生成的结果依旧输出了对应的天气情况。下一步,我们继续修改内容,在工具list中删除了函数描述与参数描述,但是在工具函数体内部加上对应的描述,如下所示:

# 定义工具函数

def get_tool2(function_params):

    "模拟获取天气的工具函数"

    location = function_params[0]

    return f"{location}的天气晴朗"

def get_tool1(function_params):

    "模拟获取股票的工具函数"

    location = function_params[0]

    return f"{location}的股票价格为18.88"

# 定义工具列表

tools = [

    {

        "type": "function",

        "function": {

            "name": "get_tool1",

            "description": None,

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "",

                    }

                },

                "required": ["location"],

            },

        },

    },

    {

        "type": "function",

        "function": {

            "name": "get_tool2",

            "description": None,

            "parameters": {

                "type": "object",

                "properties": {

                    "location": {

                        "type": "string",

                        "description": "",

                    }

                },

                "required": ["location"],

            },

        },

    }

]

打印结果如下所示:

Initial Model Response: ChatCompletionMessage(content='', refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_0_f5dd2f6e-e1f3-4c0a-a5ee-627fd0e9341e', function=Function(arguments='{"location":"Shanghai"}', name='get_tool1'), type='function', index=0)])

Tool Call: ChatCompletionMessageToolCall(id='call_0_f5dd2f6e-e1f3-4c0a-a5ee-627fd0e9341e', function=Function(arguments='{"location":"Shanghai"}', name='get_tool1'), type='function', index=0)

Function Name: get_tool1

Function Params: {'location': 'Shanghai'}

Tool Result: Shanghai的股票价格为18.88

可以看到,虽然有DeepSeek输出了结果,但是很明显,此时的模型存在函数调用错误,即我们在定义的工具列表中删除了函数描述,就会造成函数调用错误的结果,这一点需要读者注意。

6.2  给大模型插上翅膀的MCP协议详解

大模型的工具调用(Function Calling)功能,作为人工智能领域的一项关键技术,赋予了大模型与外部世界进行交互和协作的能力。在这一领域,DeepSeek所实现的工具调用机制,以其高效、灵活的特点,成为众多开发者和研究者关注的焦点。它不仅能够让模型根据需求精准地调用各类外部工具,还极大地拓展了模型的应用场景和实用价值,使得大模型在诸如自然语言处理、智能客服、内容生成等众多领域展现出了强大的实力。然而,随着人工智能技术在特定领域和垂直行业的深入应用,我们逐渐发现,一些专业场景下的大模型面临着新的挑战。

这些特定领域和垂直行业的大模型,由于其训练数据和应用场景的特殊性,往往本身不具备或者无法直接执行某些特定的Function Calling。例如,在医疗领域,大模型可能需要调用专业的医学诊断工具来获取准确的诊断结果;在金融领域,可能需要调用实时的金融数据接口以进行精准的风险评估。在这些情况下,仅仅依靠大模型自身的能力是远远不够的,我们额外需要一种能够使得大模型直接、高效调用这些专业工具的方法,而MCP正是为解决这一问题而诞生的。

MCP作为一种具有开创性的开放协议,如图6-3所示。其核心目标在于标准化人工智能模型与外部数据源、工具之间的交互方式。在当今复杂多变的人工智能应用环境中,模型需要与各种不同类型的数据源和工具进行交互,以实现更加智能、高效的任务处理。通过MCP,大模型可以轻松地与各种专业工具进行连接和通信,无须进行复杂的接口开发和数据转换,从而大大提高了开发效率和应用效果。同时,MCP的标准化特性也保证了不同模型、不同工具之间的兼容性和互操作性,为人工智能技术的广泛应用和深入发展奠定了坚实的基础。

图6-3  MCP通信协议

MCP通过定义一套统一的通信规范,成功打破了模型与外部系统之间的壁垒,使得大语言模型(LLM)能够轻松、无缝地连接本地文件、数据库、API以及各类专业工具。这一协议的出现,为人工智能模型的集成和应用提供了更加便捷、高效的解决方案。

读者将逐渐掌握如何在实际项目中灵活运用DeepSeek的工具调用和MCP协议,构建出更加智能、高效的人工智能应用。同时,我们也将深入探讨MCP协议在其中的重要作用,以及如何利用MCP协议进一步优化模型与外部系统的交互过程。

无论是处理复杂的自然语言任务,还是与各类外部数据源和工具进行深度集成,这些知识和技能都将成为读者在人工智能领域取得成功的有力武器。我们相信,通过本章的学习,读者将能够开启一段全新的人工智能探索之旅,为未来的技术创新和应用发展奠定坚实的基础。

6.2.1  MCP协议目的、功能与架构详解

MCP协议的问世,旨在攻克AI模型与外部数据源和工具之间错综复杂的连接难题。在过往的开发进程中,开发者常常需要为不同的数据源和工具量身定制连接方案,这不仅耗费大量的时间与精力,还极易导致代码的冗余和混乱。而MCP协议凭借其标准化的特性,犹如一把万能钥匙,巧妙地减少了开发者的重复劳动,为开发过程带来了前所未有的便捷与高效。

MCP堪称AI世界的万能连接器,其作为标准化协议的重要性不言而喻。它搭建起了一座沟通的桥梁,让形形色色的应用和工具能够毫无阻碍地实现无缝连接。无论这些应用和工具来自何处、具有何种特性,MCP都能凭借其强大的兼容性,将它们紧密地联系在一起,共同构建起一个协同共生的AI生态系统。

从架构层面来看,MCP主要由Host、Client和Server三大部分构成。其中,客户端扮演着通信使者的角色,负责在AI模型与外部系统之间传递信息;而服务器则如同一位睿智的中间人,巧妙地连接着外部系统,这些外部系统可能是庞大的数据库,也可能是功能各异的API。通过这种明确的分工协作,MCP实现了AI模型与外部世界的高效交互。

相较于传统的Function Calling,MCP展现出了显著的优势。传统的Function Calling往往呈现出碎片化的特点,不同的AI模型和应用可能采用不同的调用方式,这使得开发者在面对不同的项目时,需要不断地重新学习和适应,极大地增加了开发的难度和成本。而MCP通过统一协议,打破了这种碎片化的局面,为开发者提供了一个统一、规范的操作标准。它不仅仅是一种技术上的革新,更是一种生态整合的利器。

通过MCP,不同的AI模型、应用和工具能够在一个统一的框架下协同工作,形成了一个更加紧密、高效的AI生态。这种标准化和生态整合的优势,使得MCP成为推动AI技术发展的重要力量,为AI的广泛应用和深入发展奠定了坚实的基础。

1. 标准化交互协议

MCP以一种极具创新性的方式,将AI模型与外部资源的交互过程抽象为三个关键部分,构建起了一套高效、统一的交互体系。

  • MCP Host:作为发起请求的AI应用,它就像是整个交互流程的指挥官。常见的如Claude、Cursor IDE等,这些应用在实际运行中会产生与外部资源交互需求,从而触发整个MCP交互流程。
  • MCP Client:它运行在Host之中,扮演着格式转换大师的角色。当Host发起请求时,MCP Client会将请求精心格式化为结构化消息。这种结构化消息具有统一的格式和规范,便于后续的传输和处理,就像是将杂乱的信息整理成有序的包裹,以便准确送达目的地。
  • MCP Server:作为轻量级中间件,它是连接本地或远程资源的桥梁。无论是庞大的数据库,还是功能丰富的API,MCP Server都能与之建立连接。它负责执行具体的操作,并将操作结果返回,就如同一位高效的快递员,准确地将货物送达并带回反馈信息。

2. 核心组件

MCP的核心组件犹如一套精密的齿轮组,相互配合,共同推动AI模型与外部资源进行有效交互。

  • 资源(Resources):这是向模型暴露的数据对象,是模型获取信息的源泉。例如文件,它可能包含着重要的文本、图像等数据;数据库表,则存储着结构化的数据信息。这些资源为模型提供了丰富的素材,使其能够进行更深入地分析和处理。
  • 工具(Tools):可调用的函数是模型实现各种功能的利器。比如执行SQL查询,模型可以通过调用相应的工具从数据库中提取所需的数据;操作浏览器,则可以让模型获取网页上的信息。这些工具赋予了模型强大的操作能力,使其能够与现实世界进行更紧密的互动。
  • 提示词(Prompts):预定义的指令模板就像是模型的行动指南。它们指导模型如何利用资源和工具,让模型知道在什么情况下使用什么资源和工具,以及如何正确地使用它们。通过提示词,模型能够更加智能、高效地完成任务。

3. 动态发现机制

MCP具备动态发现机制,这一特性使其具有类似即插即用的硬件生态的优势。它能够自动识别新接入的服务器功能,无须修改模型代码即可扩展能力。当有新的资源或工具接入时,MCP可以迅速发现并适配,为模型提供更多的功能和可能性。这大大简化了系统的扩展和维护过程,提高了系统的灵活性和可扩展性。其主要作用如下:

  • 消除碎片化集成:在传统方案中,每个AI应用都需要单独开发连接器,例如连接天气API、数据库驱动等。这不仅增加了开发的工作量,还导致了代码的冗余和复杂。而MCP通过统一协议,打破了这种碎片化的局面,减少了重复开发,提高了开发效率。
  • 提升开发效率:开发者无须再关注底层的实现细节,只需专注于如何利用MCP提供的资源和工具来实现业务逻辑。这使得开发者能够将更多的精力投入到创新和业务优化上,大大缩短了开发周期。
  • 增强数据安全:本地Server直接访问敏感数据,避免了将数据传输到云端可能带来的安全风险。同时,权限控制精细到工具级别,例如可以限制模型仅能读取特定目录的数据,进一步保障了数据的安全性。
  • 提升模型实用性:MCP使模型能够突破训练数据的限制,实时操作现实系统。例如,模型可以自动抓取新闻、分析销售数据等,为实际应用提供了更多的可能性。

4. MCP协议的通信流程

MCP的通信流程在整体中,各个环节紧密配合,确保信息的准确传递和处理。其流程如下:

(1)MCP Host向MCP Client发送请求,请求中包含了资源、工具和提示词等关键信息。这些信息就像是舞蹈的指令,指导着后续的动作。

(2)MCP Client接收到请求后,将其格式化为结构化消息。这一过程就像是将指令转化为标准的舞蹈动作,便于后续的传输和执行。

(3)格式化后的结构化消息被发送给MCP Server,MCP Server根据消息中的信息执行相应的操作,并将结果返回给MCP Client。

5. MCP服务端

MCP Server作为轻量级中间件,承担着连接本地/远程资源、执行具体操作并返回结果的重要职责。其主要功能如下:

  • 资源管理:对本地资源进行全面管理,如文件、数据库表等。它负责资源的存储、访问控制等操作,确保资源的安全和有效利用。
  • 工具管理:管理可调用的函数,如执行SQL查询、操作浏览器等。它负责工具的注册、调用和监控,保证工具的正常运行。
  • 请求处理:接收MCP Client发送的请求,解析请求中的资源、工具和提示词等信息。根据解析结果,调用相应的资源或工具执行操作,确保请求得到准确处理。
  • 结果返回:将操作结果返回给MCP Client,以便后续的处理和使用。

可以看到,在与相关技术的对比中,MCP协议是针对不同模型定制的接口,生态封闭、通用性和灵活性不足,而MCP作为标准化协议适用性和兼容性更广。对于使用的大模型,MCP提供底层协议支持工具的标准化调用,避免重复开发适配层,提升效率、降低成本。

同时,MCP凭借其标准化的交互协议、核心组件、动态发现机制等优势,解决了传统AI应用中的诸多问题,为AI技术的发展和应用提供了强大的支持。

6.2.2  MCP实战1:本地工具服务端搭建

MCP的作用是搭建工具函数与大模型应用的桥梁,我们首先将完成工具函数类的设置。在具体使用上,读者需要安装MCP专用的库包,命令如下所示:

pip install mcp

下面示例实现了多个可用MCP调用的函数,并使用mcp.tool()进行修饰,代码如下所示:

'''

MCP服务端程序

提供 stdio 协议与MCP客户端进行通信

'''

import datetime

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DemoServer")  # 创建服务实例

@mcp.tool()

def add_numbers(a: int, b: int) -> int:

    """

    计算两个整数的和。

    Args:

        a (int): 第一个整数,例如:5

        b (int): 第二个整数,例如:3

    Returns:

        int: 两数之和,例如:8

    """

    return a + b

@mcp.tool()

def multiply_numbers(a: int, b: int) -> int:

    """

    计算两个整数的乘积。

    Args:

        a (int): 被乘数,例如:4

        b (int): 乘数,例如:7

    Returns:

        int: 两数乘积,例如:28

    """

    return a * b

@mcp.tool()

def date_time() -> str:

    """

    获取当前系统时间(基于服务器时区)。

    Returns:

        str: 格式为YYYY-MM-DD HH:MM:SS的时间字符串,例如:2024-05-01 15:30:00

    """

    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

@mcp.tool()

def get_weather(city_name: str) -> dict:

    """

    获取指定城市的模拟天气信息(当前为静态数据)。

    Args:

        city_name (str): 城市名称(中文或拼音),例如:shanghai 或 上海

    Returns:

        dict: 包含城市和温度的字典,例如:{'城市': '上海', '温度': '23℃'}

    """

    return {

        '城市': city_name,

        '温度': "23℃"

    }

if __name__ == "__main__":

    mcp.run(transport="stdio")  # 使用标准输入输出协议运行服务

在上面代码中,我们定义了多个函数,并通过说明文本对函数作用进行讲解,而mcp.tool()的作用是对其进行注册,将函数转换为可供MCP框架使用的内容。

6.2.3  MCP实战2:本地客户端搭建与使用

下面我们将完成MCP客户端搭建,在这里我们的目标是使用DeepSeek完成工具的调用。首先是DeepSeek大模型的提示设置与输出,代码如下所示:

from openai import OpenAI

from mcp import ClientSession, StdioServerParameters  # 从mcp模块导入ClientSession和StdioServerParameters类

from mcp.client.stdio import stdio_client  # 从mcp.client.stdio模块导入stdio_client函数

import sys   # 导入sys模块,用于处理命令行参数

import json  # 导入json模块,用于处理JSON数据

# 定义一个函数,用于转换tools工具中JSON数据

def transform_json(tools):

    s = "MCP服务器提供的工具如下:"

    for tool in tools:  # 遍历工具列表

        s = s + f"""

            tool_name: {tool.name},

            tool_description: {tool.description},

            - input title: {tool.inputSchema['title']},

            - input properties:"{tool.inputSchema['properties']},

            """

    return s

def ask_llm_deepseek(question, tools_list):

    # TODO: 实现与LLM的交互逻辑

    # 这里只是一个示例,实际应用中需要根据具体情况进行实现

    system_prompt = tools_list + '\n 根据以上描述,用户要求:%s ,请生成一个工具调用命令,要求以json格式输出{"tool":工具名,"tool_input":参数字典},只输出json,不要输出其他内容' % (

        question)

    client = OpenAI(

        api_key="sk-282074c41d594514aee6fd6f179ed292",

        base_url="https://api.deepseek.com/beta",

    )

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "system", "content": system_prompt},

            {"role": "user", "content": "Hello"},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text, client  # 返回文本而非response对象

def llm_deepseek(question,client):

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "user", "content": question},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text

从上面代码中可以看到,我们定义一个函数用于转换tools工具中JSON数据,而DeepSeek函数的作用是对输入的内容和函数进行匹配,并完成函数的调用。这里需要注意,我们使用System_prompt对DeepSeek的作用以及输出格式做出规范,并且在system_prompt中添加了函数的用法和说明。

完整的MCP客户端搭建与使用如下所示:

'''

MCP客户端程序

通过 stdio 协议与MCP服务器进行通信

获取MCP服务器的工具清单和调用参数

并调用工具实现MCP服务中函数功能

'''

from openai import OpenAI

from mcp import ClientSession, StdioServerParameters  # 从mcp模块导入ClientSession和StdioServerParameters类

from mcp.client.stdio import stdio_client  # 从mcp.client.stdio模块导入stdio_client函数

import sys   # 导入sys模块,用于处理命令行参数

import json  # 导入json模块,用于处理JSON数据

# 定义一个函数,用于转换tools工具中JSON数据

def transform_json(tools):

    s = "MCP服务器提供的工具如下:"

    for tool in tools:  # 遍历工具列表

        s = s + f"""

            tool_name: {tool.name},

            tool_description: {tool.description},

            - input title: {tool.inputSchema['title']},

            - input properties:"{tool.inputSchema['properties']},

            """

    return s

def ask_llm_deepseek(question, tools_list):

    # TODO: 实现与LLM的交互逻辑

    # 这里只是一个示例,实际应用中需要根据具体情况进行实现

    system_prompt = tools_list + '\n 根据以上描述,用户要求:%s ,请生成一个工具调用命令,要求以json格式输出{"tool":工具名,"tool_input":参数字典},只输出json,不要输出其他内容' % (

        question)

    client = OpenAI(

        api_key="sk-282074c41d594514aee6fd6f179ed292",

        base_url="https://api.deepseek.com/beta",

    )

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "system", "content": system_prompt},

            {"role": "user", "content": "Hello"},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text, client  # 返回文本而非response对象

def llm_deepseek(question,client):

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "user", "content": question},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text

import os

server_script_path = os.path.join(os.path.dirname(__file__), "mcp_tool_and_server.py")

# 定义服务器参数

server_params = StdioServerParameters(

    command="python",                    # 运行命令

    args=[server_script_path],           # 服务器脚本路径

    env=None                              # 可选的环境变量

)

async def run(question = "你是谁?"):

    # 建立连接

    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("可用工具:", tools.tools)

            s = transform_json(tools.tools) +"\n"

            #print(s)

            response, client = ask_llm_deepseek(question,s)

            # 清理Markdown标记

            response = response.strip().replace(''''json', '').replace(''''', '').strip()

            #print(response)

            mtools = json.loads(response)  # 现在response是纯JSON字符串

            print("mtools: --> ",mtools)

            if 'tool' in mtools:

                tool_name = mtools['tool']

                tool_input = mtools['tool_input']

                # 调用工具

                print("调用工具:", tool_name, tool_input)

                ret = await session.call_tool(tool_name, tool_input)

                if ret:

                    try:

                        r = json.loads(ret.content[0].text)

                    except:

                        r = ret.content[0].text

                    print("工具返回结果:", r)

                questions = f"用户的问题是{question},根据{tool_name}的返回结果为:{r},根据以上信息,回答问题。"

                r = llm_deepseek(questions,client)

                print(r)

            else:

                r = llm_deepseek(question,client)

                print(r)

if __name__ == "__main__":

    import asyncio

    questions = ['计算一下356*125', '25+38是多少?', '现在的时间是什么时候?', 'Shanghai天气怎么样?','请给我讲一个笑话']

    for question in questions:

        asyncio.run(run(question))

上面代码的核心功能是通过一个本地工具服务器(MCP服务器)和LLM(如DeepSeek)结合,根据用户问题动态调用工具并生成答案。以下是代码开始部分的详细解析:

1. 模块引入

  • from openai import OpenAI:虽然命名为OpenAI,但后续代码实际上是通过自定义类与DeepSeek的API交互,可能是为了兼容性或扩展性而命名。
  • from mcp import ClientSession, StdioServerParameters 和 from mcp.client.stdio import stdio_client:用于与MCP服务器交互,ClientSession管理会话,StdioServerParameters配置服务器参数,stdio_client用于创建客户端连接。
  • import sys, json:sys用于处理命令行参数(尽管代码中未直接使用),json用于解析和生成JSON数据。
  • import os:用于处理文件路径,确定MCP服务器脚本的位置。

2. 工具描述转换函数

  • transform_json(tools):将MCP服务器提供的工具列表转换为人类可读的字符串格式,包括工具名称、描述、输入标题和属性。这有助于将工具信息传递给LLM以生成调用命令。

3. LLM交互函数

  • ask_llm_deepseek(question, tools_list):构建系统提示(System Prompt),将工具描述和用户问题结合,生成一个JSON格式的工具调用命令。通过DeepSeek API发送请求,并返回生成的文本和客户端对象。
  • llm_deepseek(question, client):直接通过客户端对象发送用户问题到LLM,并返回生成的文本。

工具调用与结果处理:

(1)在run异步函数中,首先通过stdio_client和ClientSession与MCP服务器建立连接。

(2)列出可用工具,并将其转换为可读字符串。

(3)调用ask_llm_deepseek生成工具调用命令,解析返回的JSON字符串以获取工具名称和输入  参数。

(4)如果成功解析出工具信息,则调用相应工具并处理返回结果。如果工具返回JSON格式的结果,则尝试解析,否则直接打印文本结果。

(5)根据工具返回结果或原始问题,再次调用LLM以生成最终答案。

(6)最后使用asyncio.run(run(question))逐个处理每个问题。

注意,在这里我们每次处理一个问题都会重新建立与MCP服务器的连接和LLM的交互;这在实际应用中可能不是最高效的方式,但便于演示和测试。

6.3  在线MCP服务器的搭建与使用实战

上面我们介绍了MCP的基本使用和本地化部署的方法,并通过实际编程展示如何在单一机器上完成MCP工具的部署与使用。然而,MCP更多地应用在搭建适配于网络的MCP服务器上,通过网络服务的形式为更多的使用者提供服务。本节将介绍基于网络的MCP服务器的搭建与使用。

6.3.1  在线MCP服务器搭建

我们通过MCP官方提供的配置可以完成MCP服务器的搭建,但是对于新手来说,需要了解和掌握服务器的配置,并对文件的命名与代码的编写要求有一定的了解。为了方便使用MCP在线服务器的搭建,我们可以使用现成的Python库来完成MCP服务器的搭建。首先我们需要安装以下两个Python库,打开终端运行如下命令:

pip install fastapi

pip install fastapi_mcp

fastapi与fastapi_mcp是用于通过网络对MCP服务提供支持的Python库,在安装成功后,我们可以直接移植前面准备的函数,完整的服务器代码如下所示:

from fastapi import FastAPI, HTTPException

from fastapi_mcp import FastApiMCP

from pydantic import BaseModel

from datetime import datetime

# 初始化FastAPI应用

app = FastAPI(title="AI计算服务", description="提供基础数学计算、时间查询和天气模拟功能")

# 数学运算端点

@app.get("/add", operation_id="add_numbers")

def add_numbers(a: int, b: int):

    """计算两个整数的和"""

    return {"result": a + b}

@app.get("/multiply", operation_id="multiply_numbers")

def multiply_numbers(a: int, b: int):

    """计算两个整数的乘积"""

    return {"result": a * b}

# 时间获取端点

@app.get("/datetime", operation_id="date_time")

def date_time():

    """获取当前系统时间"""

    return {"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}

@app.post("/weather", operation_id="get_weather")

def get_weather(city_name: str):

    """获取指定城市天气信息"""

    return {

        "city": city_name,

        "temperature": "23℃"

    }

# 初始化MCP服务器

mcp = FastApiMCP(

    app,

    name="Math & Weather API",

    description="提供数学运算和天气查询服务的MCP接口",

    base_url="http://localhost:8000",

    include_operations=[

        "add_numbers",

        "multiply_numbers",

        "date_time",

        "get_weather"

    ]

)

mcp.mount()

if __name__ == "__main__":

    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

程序代码较为简单,整体逻辑就是首先定义服务器的名称与服务器描述,之后将不同的工具函数进行注册,并提供访问地址和端口,最后将MCP服务器挂载和启动。直接运行上述代码,结果如图6-4所示。

图6-4  MCP服务器的启动

可以看到此时我们已经正常启动了MCP服务,下面我们将完成客户端的编写。

6.3.2  在线MCP服务的连接和使用

接下来,我们将完成在线MCP服务的连接和使用。同样,我们可以根据前面一节展示的本地MCP客户端,修改代码如下:

import asyncio

import json

import logging

from mcp.client.session import ClientSession

from mcp.client.sse import sse_client

logging.basicConfig(level=logging.INFO)

logger = logging.getLogger(__name__)

# 工具描述转换器

def transform_tools(tools):

    tool_list = []

    for tool in tools:

        parameters = {}

        required = tool.inputSchema.get('required', [])

        for name, prop in tool.inputSchema['properties'].items():

            parameters[name] = {

                'type': prop['type'],

                'description': prop.get('description', ''),

                'required': name in required

            }

        tool_list.append({

            'name': tool.name,

            'description': tool.description,

            'parameters': parameters

        })

    return json.dumps(tool_list, ensure_ascii=False, indent=2)

from openai import OpenAI

def ask_llm_deepseek(question, tools_list):

    # TODO: 实现与LLM的交互逻辑

    # 这里只是一个示例,实际应用中需要根据具体情况进行实现

    system_prompt = tools_list + '\n 根据以上描述,用户要求:%s ,请生成一个工具调用命令,要求以json格式输出{"tool":工具名,"tool_input":参数字典},只输出json,不要输出其他内容' % (

        question)

    client = OpenAI(

        api_key="sk-282074c41d594514aee6fd6f179ed292",

        base_url="https://api.deepseek.com/beta",

    )

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "system", "content": system_prompt},

            {"role": "user", "content": "Hello"},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text, client  # 返回文本而非response对象

def llm_deepseek(question,client):

    response = client.chat.completions.create(

        model="deepseek-chat",

        messages=[

            {"role": "user", "content": question},

        ],

        max_tokens=1024,

        temperature=0.99,

        stream=False

    )

    generated_text = response.choices[0].message.content  # 提取文本内容

    return generated_text

async def main(question = "25+38是多少?"):

    async with sse_client("http://localhost:8000/mcp") as streams:

        async with ClientSession(streams[0], streams[1]) as session:

            await session.initialize()

            # 获取工具列表

            tools = (await session.list_tools()).tools

            s = transform_tools(tools) +"\n"

            response, client = ask_llm_deepseek(question, s)

            # 清理Markdown标记

            response = response.strip().replace(''''json', '').replace(''''', '').strip()

            # print(response)

            mtools = json.loads(response)  # 现在response是纯JSON字符串

            print("mtools: --> ",mtools)

            if 'tool' in mtools:

                tool_name = mtools['tool']

                tool_input = mtools['tool_input']

                # 调用工具

                print("调用工具:", tool_name, tool_input)

                ret = await session.call_tool(tool_name, tool_input)

                if ret:

                    try:

                        r = json.loads(ret.content[0].text)

                    except:

                        r = ret.content[0].text

                    print("工具返回结果:", r)

                questions = f"用户的问题是{question},根据{tool_name}的返回结果为:{r},根据以上信息,回答问题。"

                r = llm_deepseek(questions,client)

                return r

if __name__ == "__main__":

    questions = ['计算一下356*125', '25+38是多少?', '现在的时间是什么时候?', 'Shanghai天气怎么样?', '请给我讲一个笑话']

    for question in questions:

        response = asyncio.run(main(question))

        print(response)

        print("--------------------------------------------------------")

从上面代码中可以看到,这里我们主要修改了其中的MCP连接地址,从而完成了MCP工具的调用与结果获取。读者可以自行尝试。

6.4  本 章 小 结

本章全面讲解了DeepSeek工具调用及MCP基本原理,并展示了两者相互调用的高效方法。通过实战演练MCP在构建复杂系统中的作用,以及DeepSeek在特定领域的应用实践,读者能够深刻理解了这两项技术在大数据处理和模型训练中的强大功能。进一步地,本章深入解析了MCP的系统架构,探讨了DeepSeek与MCP的协同优化机制,并展望了它们在大数据和人工智能领域的未来发展趋势。通过本章的学习,读者将全面掌握DeepSeek与MCP的核心技术,为未来的数据处理和模型训练项目奠定坚实基础。

计算机视觉教程1-1:透视空间与透视变换 在生活中,透视现象很常见,其物理本质是光的直线传播。由于人眼与相机在捕获图像时均存在透视现象,因此计算机几何基于透视空间进行研究。本节介绍透视变换原理 阅读详情

相关推荐

别再手搓SVG了!用Vue3+SVG.js快速搭建电力系统拓扑图(附完整代码)

本文介绍了如何利用Vue3和SVG.js实现电力系统拓扑图的低代码开发,通过组件化和响应式数据绑定,大幅提升开发效率。文章详细解析了SVG.js的核心功能,包括图形创建链式API、动态属性绑定和高级图形操作,并提供了断路器组件的完整实现代码。此外,还分享了性能优化策略和调试技巧,帮助开发者应对大规模拓扑图的挑战。

weixin_27717759的博客 353

一文讲透 MCP 与 Function calling,你想看的都在这里

最终结果。GPT 返回了一个类型为。

Python_cocola的博客 3451

主板USB接口全部失效解决方案(通用串行总线USB控制器有黄色叹号)保姆级教程亲测有效

通用串行总线控制器总是有叹号解决办法 办公室的崩腾时代老电脑,疫情期间没人用,来了开机之后发现鼠标键盘都用不了,本以为是主机放时间长出了问题,后来想起来就百度了“鼠标键盘都失效”的标题,结果发现了“串行总线控制器总是感叹号”的关联问题。正好办公室电脑都装有teamviwer常用来远程办公(在家偷懒)。所以通过远程发现电脑室可以运行的。并在控制面板发现:通用串行总线控制器总是感叹号,插入外设时USB无反应。如图:​ 并且右键选中其中一个,点击最后一个选项:“属性” 可以发现“设备状态”中会提示“由...

xiao_bai_0_0的博客 2万+

Function Calling已经过时 ,MCP才是真正的大模型接口标准

过去一个月,全球大模型圈最热的关键词,非MCP莫属。模型侧,从Claude到Open AI,从Llama到DeepSeek、通义;应用侧,从Figma到Unreal,从Milvus到高德地图,全球超过 8,000 个主流工具和软件支持MCP,适配 MCP Server已经成为行业标准动作;可以说,模型与工具对接标准的大一统时代已经呼之欲出;而借助MCP,

m0_59235245的博客 1949

ros2笔记-4.6使用launch启动脚本

ros2 4.6 使用launch启动多节点

bohu83的博客 1309

软件工程期末知识点总结【超详细】

生命周期模型(过程模型):瀑布模型、快速原型模型、增量模型、螺旋模型、喷泉模型。2、实体联系图:1、数据对象2、属性3、联系(1:1、1:N、M:N)3、语句覆盖 就是设计足够的调试用例,使得程序中的每个语句至少执行一次。 5、判定覆盖就是设计足够的测试用例,使得程序中每个判定的取“真”分支和取“假”分支至少都执行一次,判定覆盖又称分支覆盖 6、条件覆盖就是设计足够的测试用例,使得程序判定中的每个条件能获得各种可能的结果。7、软件开发时期通常分成4个阶段:总体设计、详细设计、编码和单元测试、综合测试。

这个人好有趣....hh 1万+

MCP 和 Function Calling 架构设计对比剖析

Function Calling 是一种强大的工具,它为大语言模型提供了与外部工具和服务交互的能力,从而解决了大模型知识更新停滞的问题。然而,它的局限性在于缺乏跨模型的一致性和平台依赖性。尽管如此,Function Calling 仍然是一个重要的技术,尤其是在需要快速实现特定功能时。未来,随着技术的不断发展,我们期待看到更多能够克服这些局限性的解决方案。

m0_70486148的博客 920

倍福PLC--PERSISTENT断电保持数据和BOOT中各文件对应的解析

将D:\TC3程序\TwinCAT Project146\TwinCAT RT(x86)\Plc 文件夹的中三个替换文件复制到:G:\TwinCAT\3.1\Boot\Plc完成替换,如下图。(2) 找到程序所存的路径,编程电脑的D:\TC3程序\TwinCAT Project146路径下,并选择对应系统平台TwinCAT RT(x86)(3) 控制器断电,拔出内存卡,使用读卡器,在编程电脑中打开:(本次测试读卡器为G盘)(5) 开机完成后,TwinCAT自动运行,当前闪灯频率为50ms。

weixin_41883890的博客 2339

大模型:Function Calling 到 MCP 的技术演进全知道

对大模型而言,不能满足人们需求关键问题有二:一是大模型的知识更新问题:大模型存在知识更新问题,大模型在训练完成后,其知识也已固化无法更新。因此通过提示工程及RAG技术(本质上也是提示工程的一种)把新知识输入到大模型中。但这还远远不够,比如获取实时信息(例如天气、数据检索等)、执行相关任务(例如发送邮件、自动化任务等),因此又提出了Function Calling技术,通过程序调用的方式让大模型自动获取外部的知识,并关联进一步的动作,在此过程中又遇到了第二个问题:

2501_92003677的博客 1271

01-系统技术架构师必备——软件架构设计基础与核心概念

摘要 本文系统阐述了软件架构设计的核心概念与方法论,重点涵盖以下内容:1)软件架构的四大要素(构件、连接件、约束、配置)及其边界划分;2)Garlan & Shaw五大架构风格(数据流、调用/返回、独立构件、虚拟机、仓库)的典型应用场景;3)基于电商库存系统的架构选型实战案例,展示如何根据业务需求(高并发、最终一致性)选择事件驱动架构配合Saga模式;4)介绍ABSD、DSSA等主流架构方法论及ISO/IEEE标准体系。文章融合金融与互联网领域的架构实践经验,为技术架构师提供系统性的知识框架和决策参考。

模界 | AI工程化实践者 181

联想开天N70z/N79Z G1d笔记本Win驱动下载

本文介绍了联想开天N70Z/N79Z G1d笔记本在Win11系统下的驱动安装方法。提供了WiFi网卡驱动(RealtekWlan)和显卡驱动(ZX C960 GPU)的详细安装步骤,包括手动更新驱动和卸载流程。驱动安装后成功支持5G WiFi连接并正确识别显卡型号。文中附有系统信息和安装效果截图,同时提供了官方驱动下载地址及备用下载链接。该指南适用于需要为联想开天N70Z/N79Z G1d笔记本安装Win11驱动的用户参考。

信创运维工程师 · 银河麒麟 & 容器实战 | 金融生产环境迁移·等保加固 | 142篇原创·17万+阅读 3862

一文搞懂MCP、Function_Calling和A2A

单纯的大模型,只能对话和生成文本,是“思想的巨人,行动的矮子”。配上Agent的大模型,能感知环境、使用工具、执行任务,成为“万能助手”。MCP、Function Calling 和 A2A。这三项技术,并不是有你无我的排斥关系,而是可以通力协作的互补关系。大模型通过 Prompt 学习工具使用,实现非结构化任务处理,克服传统规则的“刚性”,使得AGI(Artificial General Intelligence,通用人工智能)成为可能。

HJS123456780的博客 1263

YOLOv8 置信度阈值 (conf) 和 IoU 阈值 (iou) 详解:如何平衡漏检与误检?

摘要 本文深入探讨了YOLOv8目标检测模型中两个关键参数——置信度阈值(conf)和IoU阈值(iou)的核心作用。文章从目标检测的基础概念出发,解释了置信度分数反映模型预测可靠性,IoU衡量预测框重叠程度。重点剖析了非极大值抑制(NMS)机制如何通过这两个阈值进行预测框筛选:置信度阈值作为初筛门槛,IoU阈值决定重叠框的取舍。通过生动比喻和可视化图表,阐述了合理调节这两个参数能有效平衡误检与漏检问题,是优化YOLOv8模型实际表现的重要技巧。全文以实践为导向,帮助开发者掌握成为"调参大师&qu

深耕 Vue 生态与 JS/TS 技术,聚焦组件封装、响应式原理、TS 类型设计,解析工程化配置与性能优化方案。同时分享 YOLOv8 目标检测实战、AI 大模型面试核心考点,以代码实例拆解技术实现与业务解法。 1236

大模型应用技术演进与实战:从RAG到GraphRAG,从Function Calling到MCP,一篇收藏

接下来详细介绍 RAG 技术的原理,其主要包括数据准备与知识库构建、知识召回与生成增强两个阶段:数据准备与知识库构建**:**原始数据处理:将各类异构的原始数据(如 web 文档、Word、PDF 等)进行清洗与格式转换,完成信息抽取,使其便于后续处理;文档切片(Chunking):将处理后的文档分割成较小的片段,避免大模型处理时超出上下文长度限制,同时提升模型生成回答的速度;

ytt0523_com的博客 1211

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

MCP与Function Calling架构设计深度对比:剖析两者差异与优势

FunctionCalling是OpenAI于2023年6月推出的工具调用技术,允许大语言模型通过自然语言指令调用外部API获取实时信息,如天气、股票数据等。其工作流程包括需求识别、函数选择、参数准备、API调用和结果整合四个步骤。虽然易于开发,但存在跨模型不一致和平台依赖的局限。Anthropic于2024年11月推出的MCP(ModelContextProtocol)并非FunctionCalling的替代,而是基于它的工具箱标准化方案。

2401_84494441的博客 1194

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

AI新智力 | 大模型入门20:从Function Calling到MCP

本文来源公众号,仅用于学术分享,侵权删,干货满满。“为了让大模型输出超出其范围的或者特定的知识,人们发明了多种方法。从提示工程到RAG,从Function Calling到MCP,莫不如是。

csdn_xmj的博客 1252

MCP从理论到实战,别再和Function Calling搞混了!建议收藏(附实战案例)

历时1个月精心整理!MCP是什么?怎么用?万字长文带你从0到1掌握MCP,厘清MCP和Function Calling的区别

sinat_29950703的博客 1417

一文搞懂MCP、Function Calling和A2A

发现:客户端从 /.well-known/agent.json 获取 Agent Card,了解智能体的能力。启动处理:服务器处理任务,可能涉及流式更新或直接返回结果。交互(可选):若任务状态为 input-required,客户端可发送更多消息,使用相同 Task ID 提供输入。完成:任务达到终端状态(如 completed、failed 或 canceled)。单纯的大模型,只能对话和生成文本,是“思想的巨人,行动的矮子”。配上Agent的大模型,能感知环境、使用工具、执行任务,成为“万能助手”。

HUANGXIN9898的博客 916

AI 应用开发指南:一文带你快速入门 Prompt、RAG、Function Calling、MCP

prompt(提示词)不是一种具体的技术,是我们和LLM沟通的控制语言。他的构成就是指令式的语言,这是整个Agent中的一个核心,我们所有功能的实现都是基于一个我们精心设计好的提示词。RAG(Retrieval-Augmented Generation,检索增强生成):是一种将信息检索和LLM结合的技术框架,让LLM在回答问题前,先从外部知识库(内部数据库、PDF)获取相关信息然后将查询到的信息注入到Prompt中,利用LLM的总结能力来回答问题。如图所示,RAG的调用流程共包含5个步骤用户提问。

TmEnbaby的博客 1184

【agent】Function Calling、MCP、skill

工具输入风格沿用function calling。不同llm的风格不一样。可能是忘了同步pyproject.toml,同步一下。同步pyproject.toml的文件。pyproject.toml改为。里看它们的工具输入格式信息。构建项目和uv构建环境。

zt0612xd的博客 1864

一文全面总结 MCP、A2A、Function Calling 架构设计

Function Calling 是一种强大的工具,它为大语言模型提供了与外部工具和服务交互的能力,从而解决了大模型知识更新停滞的问题。然而,它的局限性在于缺乏跨模型的一致性和平台依赖性。尽管如此,Function Calling 仍然是一个重要的技术,尤其是在需要快速实现特定功能时。未来,随着技术的不断发展,我们期待看到更多能够克服这些局限性的解决方案。

star_nwe的博客 1077

一篇文章搞清楚MCP、Function Calling和A2A

摘要:文章探讨了从单纯大语言模型向AIAgent的进化,重点解析了三大关键技术:MCP协议实现工具发现与调用、FunctionCalling机制使模型学会使用工具、A2A协议支持多Agent协作。通过天气查询案例,展示了MCP协议的工作流程,并分析了600行系统提示词如何指导模型完成复杂任务。文章指出Agent本质是精心设计的上下文工程,通过控制上下文长度、结构化信息等方式克服传统自动化的局限,使AI系统具备感知环境、自主决策和执行任务的能力,从而将大模型从"思想巨人"转变为真正的&qu

Study996的博客 1301

AI大模型应用开发 | 一文解析Prompt、RAG、Function Calling、MCP

prompt(提示词)不是一种具体的技术,是我们和LLM沟通的控制语言。他的构成就是指令式的语言,这是整个Agent中的一个核心,我们所有功能的实现都是基于一个我们精心设计好的提示词。

Androiddddd的博客 1176

讨论|谁能统一Agent 接口?MCP 对比 A2A 、Function Calling

此外,对于函数的链式调用,Function Calling本身并不直接支持多步调用组合,模型只能一次调用一个函数,获取结果后如果需调用下一个函数,需要由应用逻辑将结果馈入模型下一轮对话,再触发下一个函数调用。MCP 的扩展性,则通过统一的接口标准,将复杂的M(个模型)×N(个外部工具对接)问题转化为M+N的问题:工具创建者只需为每个工具/系统实现一次MCP Server,应用开发者只需为每个应用实现一次MCP Client,各自遵循通用协议即可协同工作,接着,服务器处理请求并返回响应,告知任务的状态。

ZILLIZ 1266
上一篇: 第 5 章 基于DeepSeek的Agent开发详解
下一篇: 第 7 章 大模型驱动的即时金融信息采集与分析平台
夏天又到了
博客等级 码龄19年 6582粉丝 1589原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值