大模型技术开发与应用——5.大模型Agent开发(CrewAI)

5.1 docker介绍与常用命令

课程目标:

  • 重点掌握 docker 的基础概念
  • 重点掌握 docker 的常用命令
  • 会拉取镜像、制作镜像、理解docker-compose.yml 文件
  • 知道通过docker 部署 web 服务

基本的使用一定要会,部署完整的服务一般都由运维操作。


1. docker概述[¶](#1. docker概述¶)

1.1 什么是虚拟化[¶](#1.1 什么是虚拟化¶)

在计算机中,虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如:服务器、网络、内存、存储等等,予以抽象、转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以比原来的组态更好的方式来应用这些资源,这些资源的核心虚拟部分是不受现有资源的架设方式,低于或者物理组态所限制,一般所指的虚拟化资源包括计算能力和资料存储。

在实际的生产过程中,虚拟化技术主要是用来解决高性能的物理硬件产能过剩和老的硬件产能过低的重用重组,透明化底层物理硬件,从而最大化的利用物理硬件,对资源充分利用。

虚拟化技术种类很多,例如:软件虚拟化、硬件虚拟化、内存虚拟化、网络虚拟化(vip),桌面虚拟化、服务虚拟化、虚拟机等等。

虚拟化简单讲,就是把一台物理计算机虚拟成多台逻辑计算机,每个逻辑计算机里面可以运行不同的操作系统,相互不受影响,这样就可以充分利用硬件资源

1.2 初识Docker

Docker是一个开源的**应用容器引擎**,诞生于2013年初,基于Go语言实现,dotCloud公司出品(后改名为Docker Inc),Docker可以让开发者打包他们的应用以及依赖包到一个**轻量级,可移植的容器中**,然后发布到任何流行的linux服务器上,容器是完全使用沙箱机制,相互隔离。

容器性能开销极低,Docker是一种容器技术,解决软件跨环境迁移的问题。

1.3 容器与虚拟机的比较[¶](#1.3 容器与虚拟机的比较¶)

什么是虚拟机

虚拟机是一个计算机系统的仿真,简单来说,虚拟机可以实现在一台物理计算机上模拟多台计算机运行任务。操作系统和应用共享一台或多台主机(集群)的硬件资源,每台VM有自己的OS,硬件资源是虚拟化的。管理程序(hypervisor)负责创建和运行VM,它连接了硬件资源和虚拟机,完成server的虚拟化。由于虚拟化技术和云服务的出现,IT部门通过部署VM可以降低成本提高效率。

VMs也消耗大量系统资源,每个VM不仅运行一个OS的完整copy并且需要所有硬件的虚拟化copy,这消耗大量RAM和CPU。相比单独计算机,VM是比较经济的,但对于一些应用VM是过度浪费的,需要容器。

什么是容器

容器是将操作系统虚拟化,这与VM虚拟化一个完整的计算机有所不同。容器是在操作系统之上,每个容器共享OS内核,执行文件和库等。共享的组件是**只读**的,通过共享OS资源能够减少复现OS的代码,意味着一台server仅安装一个OS可以运行多个任务。容器是非常轻量的,仅仅**MB**水平并且几秒即可启动。相比容器,VM需要几分钟启动,并且大小也大很多。

与VM相比,容器仅需OS、支撑程序和库文件便可运行应用,这意味你可以在同一个server上相比VM运行2-3倍多的应用,并且,容器能帮助创建一个可移植的,一致的开发测试部署环境。

小结:

特性 虚拟机 容器
隔离级别 操作系统级 进程级
隔离策略 运行于Hypervisor上 直接运行在宿主机内核中
系统资源 5-15% 0-5%
启动速度 慢,分钟级 快,秒级
占用磁盘空间 非常大,GB-TB级 小,KB-MB甚至KB级
并发性 一台宿主机十几个,最多几十个 上百个,甚至上百上千个
高可用策略 备份、容灾、迁移 弹性、负载、动态

结论:

与传统的虚拟化相比,Docker优势体现在启动速度快,占用体积小

2. Docker与虚拟机[¶](#2. Docker与虚拟机¶)

2.1 什么是物理机[¶](#2.1 什么是物理机¶)

2.2 什么是虚拟机[¶](#2.2 什么是虚拟机¶)

2.3 什么是docker[¶](#2.3 什么是docker¶)

3. Docker组件[¶](#3. Docker组件¶)

3.1 Docker服务端和客户端[¶](#3.1 Docker服务端和客户端¶)

Docker****是一个客户端-服务端(C/S)架构程序,Docker客户端只需要向Docker服务端或者守护进程发出请求,服务端或者守护进程完成所有工作返回结果,Docker提供了一个命令行工具Docker以及一整套的**Restful API**,可以在同一台宿主机器上运行Docker守护进程或者客户端,也可以从本地的Docker客户端连接到运行在另一台宿主机上的远程Docker守护进程

docker引擎是一个c/s结构的应用,主要组件见下图:

Docker使用C/S架构,Client 通过接口与Server进程通信实现容器的构建,运行和发布。client和server可以运行在同一台集群,也可以通过跨主机实现远程通信。

3.2 Docker镜像[¶](#3.2 Docker镜像¶)

Docker 镜像(Image)就是一个只读的模板。例如:一个镜像可以包含一个**完整的操作系统环境**,里面仅安装了 Apache 或用户需要的其它**应用程序**。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用

镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,看看下面这张图:

右边我们看到了多个**只读层**,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够在docker宿主机的文件系统上访问到。统一文件系统(Union File System)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。

3.3 Docker容器[¶](#3.3 Docker容器¶)

Docker 利用容器(Container)来运行应用。容器是从镜像创建的**运行实例**。它可以被**启动、开始、停止、删除**。每个容器都是相互隔离的、保证安全的平台。

可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。

创建Container首先要有Image,也就是说Container是通过image创建的。

Container是在原先的Image之上新加的一层,称作**Container layer**,这一层是可读可写的(Image是只读的)。

在面向对象的编程语言中,有类跟对象的概念。类是抽象的,对象是类的具体实现。Image跟Container可以类比面向对象中的类跟对象,Image就相当于抽象的类,Container就相当于具体实例化的对象。

Image跟Container的职责区别:Image****负责APP的存储和分发,Container负责运行APP

结论:

容器 = 镜像 + 读写层。并且容器的定义并没有提及是否要运行容器

4. docker 常用命令教程[¶](#4. docker 常用命令教程¶)

docker 相关的几个重要概念

  • 镜像 image:是一个只读的模板,用于创建容器。它包含了应用运行所需的所有内容,比如代码、运行时、库、环境变量和配置文件等
  • 容器 container:容器是镜像的运行实例,是一个轻量级、可移植、自给自足的运行环境。每个容器都是彼此隔离的。
  • 数据卷Volume: 数据卷是 Docker 提供的一种持久化机制,用于在容器间共享数据,或将容器数据持久保存到宿主机
  • Dockerfile:Dockerfile 是一组指令的脚本,用于构建自定义镜像
  • Docker Compose:是 Docker 提供的多容器应用编排工具,通过一个 docker-compose.yml 文件定义应用的服务、网络和数据卷等
  • 仓库Registry:Docker 镜像存储和分发的地方。官方公共仓库:Docker Hub(默认),私有仓库:Harbor、GitHub Container Registry 等。

4.1 镜像(Image)相关命令[¶](#4.1 镜像(Image)相关命令¶)

命令 说明
docker pull <镜像名> 拉取镜像(如 docker pull nginx
docker images 查看本地镜像
docker rmi <镜像名或ID> 删除本地镜像
docker build -t <镜像名> . 构建镜像(需配合 Dockerfile

示例:

复制代码
docker pull redis:6.2
docker rmi redis
docker build -t myapp:latest .

4.2 容器(Container)相关命令[¶](#4.2 容器(Container)相关命令¶)

命令 说明
docker run 创建并运行容器
docker ps 查看正在运行的容器
docker ps -a 查看所有容器(包括已停止)
docker stop <容器名/ID> 停止容器
docker start <容器名/ID> 启动已停止容器
docker rm <容器名/ID> 删除容器
docker exec -it <容器名> bash 进入容器终端

示例:

复制代码
# 以交互方式运行 Ubuntu 容器
docker run -it ubuntu bash

# 以后台方式运行 nginx 容器并映射端口
docker run -d -p 8080:80 nginx

# 查看容器
docker ps -a

# 进入正在运行的容器
docker exec -it mynginx bash

4.3 网络与数据卷[¶](#4.3 网络与数据卷¶)

命令 说明
docker network ls 查看网络
docker volume ls 查看数据卷
docker run -v /宿主路径:/容器路径 映射数据卷
docker run --network=<网络名> 指定容器网络

示例:

复制代码
docker run -d -p 3306:3306   -v /mydata/mysql:/var/lib/mysql   --name mysql   -e MYSQL_ROOT_PASSWORD=123456   mysql:8.0

4.4 Dockerfile 简单示例[¶](#4.4 Dockerfile 简单示例¶)

这里演示构建Dockerfile 文件,部署 web 应用

复制代码
# Dockerfile 示例
FROM python:3.10
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]

5. Docker Compose 使用教程[¶](#5. Docker Compose 使用教程¶)

5.1 Docker Compose 是什么?[¶](#5.1 Docker Compose 是什么?¶)

Docker Compose 是用于定义和运行多容器 Docker 应用的工具,通过一个 docker-compose.yml 文件定义服务、网络、卷等内容。


5.2 安装(已随 Docker Desktop 一起安装)[¶](#5.2 安装(已随 Docker Desktop 一起安装)¶)

验证安装:

复制代码
docker compose version  # 或 docker-compose version(旧写法)

5.3 常用命令[¶](#5.3 常用命令¶)

命令 说明
docker compose up 启动服务(读取当前目录下的 docker-compose.yml
docker compose up -d 后台启动
docker compose down 停止并清理资源
docker compose ps 查看服务状态
docker compose logs 查看日志
docker compose exec <服务名> bash 进入服务容器终端

5.4 Compose 示例:运行一个 Web + 数据库[¶](#5.4 Compose 示例:运行一个 Web + 数据库¶)

复制代码
version: "3.5"

services:
  milvus:
    image: milvusdb/milvus:v2.4.10
    container_name: milvus_standalone
    ports:
      - "19530:19530"
      - "9091:9091"
    volumes:
      - ./milvus_data:/var/lib/milvus
    environment:
      - ETCD_ENDPOINTS=milvus-etcd:2379
      - MINIO_ADDRESS=milvus-minio:9000
      - MINIO_ACCESS_KEY=minioadmin
      - MINIO_SECRET_KEY=minioadmin
      - ETCD_AUTO_COMPACTION_RETENTION=0
      - MILVUS_LOG_LEVEL=info
    command: ["milvus", "run", "standalone"]
    restart: unless-stopped

  redis:
    image: redis:7.2
    container_name: redis
    ports:
      - "6379:6379"
    volumes:
      - ./redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

volumes:
  milvus_data:
  redis_data:
  etcd_data:
  minio_data:

docker compose up -d     # 启动所有服务
docker compose ps        # 查看服务状态
docker compose exec web bash  # 进入 web 服务容器
docker compose down      # 停止并清理容器、网络和卷

6. 其他命令使用[¶](#6. 其他命令使用¶)

6.1 清理无用资源[¶](#6.1 清理无用资源¶)

复制代码
docker system prune          # 清理所有未使用的容器、网络、镜像
docker volume prune          # 清理未使用的数据卷
docker image prune           # 清理未使用镜像

6.2 导入导出镜像[¶](#6.2 导入导出镜像¶)

复制代码
docker save -o myapp.tar myapp:latest     # 导出镜像
docker load -i myapp.tar                  # 导入镜像

6.3 小结[¶](#6.3 小结¶)

类型 推荐使用方式
单个容器运行 直接 docker run
多服务协同部署 使用 docker compose
自动化构建 编写 Dockerfile
本地调试 使用 docker exec, logs

7. web 应用部署案例[¶](#7. web 应用部署案例¶)

下面是一个完整的 FastAPI 接口服务示例,并通过 Docker 和 Docker Compose 部署的完整流程。包括:

  1. 创建 FastAPI 项目代码
  2. 编写 Dockerfile
  3. 编写 docker-compose.yml
  4. 构建并运行容器
  5. 浏览器验证接口访问

7.1 项目目录结构:[¶](#7.1 项目目录结构:¶)

复制代码
fastapi-docker-app/
├── app/
│   └── main.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml

app/main.py - FastAPI 服务:

复制代码
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello from FastAPI + Docker"}

requirements.txt - 依赖文件

复制代码
fastapi
uvicorn[standard]

7.2. Dockerfile - 构建镜像[¶](#7.2. Dockerfile - 构建镜像¶)

复制代码
# 使用官方 Python 镜像作为基础
FROM python:3.10-slim

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制代码
COPY app ./app

# 设置启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

7.3 docker-compose.yml - 编排服务[¶](#7.3 docker-compose.yml - 编排服务¶)

复制代码
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./app:/app/app
    restart: always

fastapi-docker-app 根目录执行:

复制代码
# 构建镜像并启动服务
docker compose up --build -d

7.4 验证服务是否部署成功[¶](#7.4 验证服务是否部署成功¶)

打开浏览器访问:

复制代码
http://localhost:8000/

返回:

复制代码
{
  "message": "Hello from FastAPI + Docker"
}

7.5 常用维护命令[¶](#7.5 常用维护命令¶)

复制代码
docker compose ps               # 查看服务状态
docker compose logs -f         # 查看实时日志
docker compose exec web bash   # 进入容器
docker compose down            # 停止并移除容器

可选:添加热更新功能(用于开发)修改 docker-compose.yml

复制代码
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

8. 总结[¶](#8. 总结¶)

本文介绍的 docker 的基础概念已经 docker 的常用命令,最后通过一个简单的案例演示了 docker 部署服务的完整流程。服务部署是演示基于自有的服务器,实际在公司可能会采用供应商的 k8s 来做服务的部署编排和维护。需要把制作好的镜像上传到云端,然后配置启动参数来更新服务,使用更简单。

5.2 大模型Function Call的应用

学习目标

  • 了解什么是Function Call
  • 理解Function Call工作的原理
  • 掌握Function Call开发应用的代码

1 什么是Function Call[¶](#1 什么是Function Call¶)

2023年6月13日 OpenAI 公布了 Function Call(函数调用) 功能,Function Call 允许开发者向 GPT-4 和 GPT-3.5-turbo 模型描述函数,模型会智能地选择输出一个包含调用这些函数参数的JSON对象。这是一种更可靠地将 GPT 的功能与外部工具和 API 相连接的新方法。

GPT4 及 GPT-3.5-turbo 模型之所以能够使用函数 Function Call 功能,是因为这些模型经过训练,不仅可以检测到何时需要调用函数(根据用户的输入),并且又可以回复符合函数参数的 JSON对象,而不是直接返回常规的文本,简而言之:函数调用使开发者能够更可靠地从模型中获得结构化数据。


那么 Function Call 可以解决大模型什么问题:

  • 信息实时性:大模型训练时使用的数据集往往有时间限制,无法包含最新的信息,如最新的新闻、实时股价等。通过Function Call,模型可以实时获取最新数据,提供更加时效的服务。
  • 数据局限性:模型的训练数据量庞大但有限,无法覆盖所有可能的查询,如医学、法律等领域的专业咨询。Function Call允许模型调用外部数据库或API,获取特定领域的详细信息。
  • 功能扩展性:大模型虽然功能强大,但不可能内置所有可能需要的功能。通过Function Call,可以轻松扩展模型能力,如调用外部工具进行复杂计算、数据分析等。

总的来说,Function Call功能的出现,极大地提升了大型语言模型的实用性和灵活性,使其能够更好地服务于用户的各种需求。

2 Function Call 工作原理[¶](#2 Function Call 工作原理¶)

接下来,我们通过举例分别对比有无Function Call功能时GPT模型工作流程的差异:

当没有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式非常简单。

  • 主要步骤:

  • 用户(client)发请求给我们的服务(chat server)

  • 我们的服务(chat server)给GPT提示词

  • 重复执行

当有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式比之前要复杂一些。

  • 主要步骤:

  • 用户(client)发请求提示词以及可以调用的函数给我们的服务(chat server)

  • GPT模型根据用户的提示词,判断是用普通文本还是函数调用的格式响应我们的服务(chat server)

  • 如果是函数调用格式,那么Chat Server就会执行这个函数,并且将结果返回给GPT

  • 然后模型使用提供的数据,用连贯的文本响应。返回

3 Function Call 实践应用[¶](#3 Function Call 实践应用¶)

假设我们要创建一个具备查询实时天气的聊天机器人。

3.1 定义外部函数[¶](#3.1 定义外部函数¶)

  • 查询某地的天气函数:get_current_weather(location: str)

    #!/usr/bin/env python3

    -- coding: utf-8 --

    """
    @Author : Vincent
    @Time : 2025/7/29 09:36
    @File : tools.py
    @Function:
    """
    import json
    import requests

    城市名称对应的编码

    data = [
    {
    "city_name": "北京",
    "city_code": "101010100"
    },
    {
    "city_name": "昌平",
    "city_code": "101010700"
    },
    {
    "city_name": "广州",
    "city_code": "101280101"
    }
    ]

    def get_current_weather(location):
    """得到给定地址的当前天气信息"""

    复制代码
      # 根据location 匹配city_name, 如果找到,则返回对应的city_code,如果没找到city_code为空字符串
      city_code = ""
      for loc in data:
          if location == loc["city_name"]:
              city_code = loc["city_code"]
    
      # 没有匹配到,直接返回
      if city_code == "":
          return json.dumps({"location": location, "code": 0, "result": {}}, ensure_ascii=False)
    
      # 否则获取天气信息
      weather_url = "http://t.weather.itboy.net/api/weather/city/" + city_code
      response = requests.get(weather_url)
      result1 = json.loads(response.text)
      forecast = result1["data"]["forecast"][0]
      weather_info = {
          "location": location,
          "high_temperature": forecast["high"],
          "low_temperature": forecast["low"],
          "week": forecast["week"],
          "type": forecast["type"],
      }
      return json.dumps({
          "location": location,
          "code": 1,
          "result": weather_info}, ensure_ascii=False)

    if name == 'main':
    print(get_current_weather("广州"))

3.2 描述函数功能[¶](#3.2 描述函数功能¶)

  • 为了向模型描述外部函数库,需要向 tools 字段传入可以调用的函数列表。参数如下表:
参数名称 类型 是否必填 参数说明
type String 设置为function
function Object
name String 函数名称
description String 用于描述函数功能,模型会根据这段描述决定函数调用方式。
parameters Object parameters字段需要传入一个Json Schema对象,以准确地定义函数所接受的参数。若调用函数时不需要传入参数,省略该参数即可。
required 指定哪些属性在数据中必须被包含。

样例:

复制代码
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定位置的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或区,例如北京、海淀",
                    },
                },
                "required": ["location"],
            },
        }
    }
]

3.3 模型应用Function Call[¶](#3.3 模型应用Function Call¶)

3.3.1创建 client[¶](#3.3.1创建 client¶)
  • 这里默认使用ZhipuAI,第一次注册免费赠送100万token,没有 key 可以自己去 智普开发平台注册一下。
3.3.2 模型应用(第一次)[¶](#3.3.2 模型应用(第一次)¶)
  • 第一次模型得到回复时符合函数参数的 JSON对象

  • 比如:我们想查询"今天北京的天气如何?"。我们向模型提供这个信息:

    #!/usr/bin/env python3

    -- coding: utf-8 --

    """
    @Author : Vincent
    @Time : 2025/7/29 10:41
    @File : get_weather_tool.py
    @Function:
    """

    from openai import OpenAI
    from dotenv import load_dotenv
    import os

    load_dotenv(".env")
    client = OpenAI(api_key=os.environ.get("ZHIPUAI_API_KEY"),
    base_url="https://open.bigmodel.cn/api/paas/v4/")

    messages = []
    messages.append({"role": "system",
    "content": "你是一个天气播报小助手,你需要根据用户提供的地址来回答当地的天气情况,如果用户提供的问题具有不确定性,不要自己编造内容,提示用户明确输入"})
    messages.append({"role": "user", "content": "今天北京的天气如何?"})

    tools = [
    {
    "type": "function",
    "function": {
    "name": "get_current_weather",
    "description": "获取给定位置的当前天气",
    "parameters": {
    "type": "object",
    "properties": {
    "location": {
    "type": "string",
    "description": "城市或区,例如北京、海淀",
    },
    },
    "required": ["location"],
    },
    }
    }
    ]
    def chat_completion_request(messages, tools, tool_choice):
    """
    Send a ChatCompletion request to the OpenAI API
    """
    return client.chat.completions.create(
    model="GLM-4-Flash-250414",
    messages=messages,
    tools=tools,
    tool_choice=tool_choice,
    )

    chat_completion = chat_completion_request(messages, tools=tools, tool_choice="auto")

    模型返回结果:

    print(chat_completion.choices[0].message)

    """
    ChatCompletionMessage(content='{"index":0,"finish_reason":"tool_calls","delta":{"role":"assistant","content":null,"reasoning_content":null,"audio":null,"tool_calls":[{"id":"call_20250729104924da7593846f6544d3_0","index":0,"type":"function","function":{"name":"get_current_weather","arguments":"{\"location\": \"北京\"}","outputs":null},"code_interpreter":null,"retrieval":null,"mcp":null,"drawing_tool":null,"web_browser":null,"search_intent":null,"search_result":null}],"tool_call_id":null,"attachments":null,"metadata":null}}', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_-8481825350119211342', function=Function(arguments='{"location": "北京"}', name='get_current_weather'), type='function', index=0)])
    """

关于 tool_choice 如果不写,则默认情况下模型将决定何时适合使用其中一个函数。

如果要控制模型如何选择函数调用,需要设置 tool_choice 参数。参数默认值为auto,此时模型根据上下文信息自行选择是否返回函数调用。

若将其设置为 {"name": "your_function_name"} 时,可以强制 API 返回特定函数的调用。

还可以通过将 tool_choice 参数设置为 "none" 来强制 API 不返回任何函数的调用。

  • 可以看到此时**模型成功触发对 get_current_weather 函数的应用** 参数为location=北京"。
3.3.3 定义处理 Function call 的函数[¶](#3.3.3 定义处理 Function call 的函数¶)
  • 本质 Function call,就是通过大模型选择函数以及获取函数的参数。然后进行函数的运行

    def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
    # 调用函数
    available_functions = {
    "get_current_weather": get_current_weather,
    } # only one function test in this example, but you can have multiple
    function_name = response_message.tool_calls[0].function.name
    fuction_to_call = available_functions[function_name]
    function_args = json.loads(response_message.tool_calls[0].function.arguments)
    function_response = fuction_to_call(
    location=function_args.get("location"),
    )
    return function_response

上述parse_response函数得到真实函数运行的结果

复制代码
{"location": "北京", "code": 1, "result": {"location": "北京", "high_temperature": "高温 34℃", "low_temperature": "低温 24℃", "week": "星期二", "type": "中雨"}}
3.3.4 模型应用(第二次)[¶](#3.3.4 模型应用(第二次)¶)
  • 基于上下文以及函数返回的结果,重新作为prompt输入模型,得到最终的结果

    extend conversation with assistant's reply

    assistant_message = chat_completion.choices[0].message
    print(f'assistant_message-->{assistant_message}')

    extend conversation with assistant's reply

    messages.append(assistant_message.model_dump())
    function_name = response.choices[0].message.tool_calls[0].function.name
    print(f'function_name--》{function_name}')
    function_id = response.choices[0].message.tool_calls[0].id
    print(f'function_id--》{function_id}')
    function_response = parse_response(response)
    messages.append(
    {
    "role": "tool",
    "tool_call_id": function_id,
    "name": function_name,
    "content": function_response,
    }

    ) # extend conversation with function response
    last_response = chat_completion_request(
    messages, tools=tools, tool_choice="auto"
    )
    print(f'last_response--》{last_response.choices[0].message}')

3.4 Function Call应用完整代码[¶](#3.4 Function Call应用完整代码¶)

  • 该 Function Call 应用目的:创建一个具备查询实时天气的聊天机器人。
  • 完整代码包含两个部分:一个main.pytools.py。另外还包含一个cityCode的json文件,该文件的目的是实现城市名称到城市编码的映射,因为天气接口API是只能基于编码的结果进行查询。

tools.py脚本内容为:

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author  : Vincent
@Time    : 2025/7/29 09:36
@File    : tools.py
@Function:
"""
import json
import requests

# 城市名称对应的编码
data = [
    {
        "city_name": "北京",
        "city_code": "101010100"
    },
    {
        "city_name": "昌平",
        "city_code": "101010700"
    },
    {
        "city_name": "广州",
        "city_code": "101280101"
    }
]

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定位置的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或区,例如北京、海淀",
                    },
                },
                "required": ["location"],
            },
        }
    }
]


def get_current_weather(location):
    """得到给定地址的当前天气信息"""

    # 根据location 匹配city_name, 如果找到,则返回对应的city_code,如果没找到city_code为空字符串
    city_code = ""
    for loc in data:
        if location == loc["city_name"]:
            city_code = loc["city_code"]

    # 没有匹配到,直接返回
    if city_code == "":
        return json.dumps({"location": location, "code": 0, "result": {}}, ensure_ascii=False)

    # 否则获取天气信息
    weather_url = "http://t.weather.itboy.net/api/weather/city/" + city_code
    response = requests.get(weather_url)
    result1 = json.loads(response.text)
    forecast = result1["data"]["forecast"][0]
    weather_info = {
        "location": location,
        "high_temperature": forecast["high"],
        "low_temperature": forecast["low"],
        "week": forecast["week"],
        "type": forecast["type"],
    }
    return json.dumps({
        "location": location,
        "code": 1,
        "result": weather_info}, ensure_ascii=False)


if __name__ == '__main__':
    print(get_current_weather("北京"))

main.py 的内容为:

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author  : Vincent
@Time    : 2025/7/29 10:41
@File    : main.py
@Function:
"""

from openai import OpenAI
from dotenv import load_dotenv
import os
from tools import get_current_weather, tools
import json

load_dotenv(".env")
client = OpenAI(api_key=os.environ.get("ZHIPUAI_API_KEY"),
                base_url="https://open.bigmodel.cn/api/paas/v4/")

messages = []
messages.append({"role": "system",
                 "content": "你是一个天气播报小助手,你需要根据用户提供的地址来回答当地的天气情况,如果用户提供的问题具有不确定性,不要自己编造内容,提示用户明确输入"})
messages.append({"role": "user", "content": "今天北京的天气如何?"})


def chat_completion_request(messages, tools, tool_choice):
    """
    Send a ChatCompletion request to the OpenAI API
    """
    return client.chat.completions.create(
        model="GLM-4-Flash-250414",
        messages=messages,
        tools=tools,
        tool_choice=tool_choice,
    )


def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
        # 调用函数
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function test in this example, but you can have multiple
        function_name = response_message.tool_calls[0].function.name
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message.tool_calls[0].function.arguments)
        function_response = fuction_to_call(
            location=function_args.get("location"),
        )
        return function_response


def main():
    messages = []
    messages.append({"role": "system",
                     "content": "你是一个天气播报小助手,你需要根据用户提供的地址来回答当地的天气情况,如果用户提供的问题具有不确定性,不要自己编造内容,提示用户明确输入"})
    messages.append({"role": "user", "content": "今天北京的天气如何"})
    print(messages)
    response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    assistant_message = response.choices[0].message
    print(f'assistant_message-->{assistant_message}')
    # extend conversation with assistant's reply
    messages.append(assistant_message.model_dump())
    function_name = response.choices[0].message.tool_calls[0].function.name
    print(f'function_name--》{function_name}')
    function_id = response.choices[0].message.tool_calls[0].id
    print(f'function_id--》{function_id}')
    function_response = parse_response(response)
    messages.append(
        {
            "role": "tool",
            "tool_call_id": function_id,
            "name": function_name,
            "content": function_response,
        }

    )  # extend conversation with function response
    last_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    print(f'last_response--》{last_response.choices[0].message}')


if __name__ == '__main__':
    main()

输出结果

复制代码
last_response--》ChatCompletionMessage(content='今天北京的天气情况如下:中雨,高温 34℃,低温 24℃。这是星期二的天气状况。', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None)

4 多个Functions的应用实践[¶](#4 多个Functions的应用实践¶)

  • 该 Function Call 应用目的:假设我们要创建一个具备查询航班功能的聊天机器人。

  • 完整代码包含三个部分:一个muti_function_zhipu.py、airplane_function_tools.py、muti_utils.py。

  • airplane_function_tools.py 定义需要的工具类函数:描述函数功能

    tools = [
    {
    "type": "function",
    "function": {
    "name": "get_plane_number",
    "description": "根据始发地、目的地和日期,查询对应日期的航班号",
    "parameters": {
    "type": "object",
    "properties": {
    "start": {
    "description": "出发地",
    "type": "string"
    },
    "end": {
    "description": "目的地",
    "type": "string"
    },
    "date": {
    "description": "日期",
    "type": "string",
    }
    },
    "required": ["start", "end", "date"]
    },
    }
    },
    {
    "type": "function",
    "function": {
    "name": "get_ticket_price",
    "description": "查询某航班在某日的价格",
    "parameters": {
    "type": "object",
    "properties": {
    "number": {
    "description": "航班号",
    "type": "string"
    },
    "date": {
    "description": "日期",
    "type": "string",
    }
    },
    "required": [ "number", "date"]
    },
    }
    },
    ]

  • muti_utils.py

  • 定义两个外部函数供模型选择调用:查询两地之间某日航班号函数:get_flight_number(departure: str, destination: str, date: str);查询某航班某日票价函数:get_ticket_price(flight_number: str, date: str)

  • 定义解析Function Call的函数

    import json

    def get_plane_number(date, start , end):
    plane_number = {
    "北京": {
    "深圳": "126",
    "广州": "356",
    },
    "郑州": {
    "北京": "1123",
    "天津": "3661",
    }
    }
    return {"date": date, "number": plane_number[start][end]}

    def get_ticket_price(date:str , number:str):
    print(date)
    print(number)
    return {"ticket_price": "668"}

    def parse_function_call(model_response):
    '''

    复制代码
      :param model_response: 模型返回的结果
      :return: 返回函数的结果
      '''
      function_result = ''
      if model_response.choices[0].message.tool_calls:
          tool_call = model_response.choices[0].message.tool_calls[0]
          args = tool_call.function.arguments
          function_result = {}
          if tool_call.function.name == "get_plane_number":
              function_result = get_plane_number(**json.loads(args))
          if tool_call.function.name == "get_ticket_price":
              function_result = get_ticket_price(**json.loads(args))
      return function_result
  • muti_function_zhipu.py 主逻辑函数

    #!/usr/bin/env python3

    -- coding: utf-8 --

    """
    @Author : Vincent
    @Time : 2025/7/29 11:43
    @File : muti_function_zhipu.py
    @Function:
    """
    import json

    from openai import OpenAI
    from dotenv import load_dotenv, find_dotenv
    from muti_utils import get_plane_number, get_ticket_price, parse_function_call
    from airplane_function_tools import tools
    import os

    _ = load_dotenv(find_dotenv())

    获取环境变量 ZhiPu_API_KEY

    zhupu_ak = os.environ['ZHIPUAI_API_KEY']
    client = OpenAI(api_key=zhupu_ak, base_url="https://open.bigmodel.cn/api/paas/v4/") # 填写您自己的APIKey
    ChatGLM = "GLM-4-Flash-250414"

    def chat_completion_request(messages, tools=None, tool_choice=None, model=ChatGLM):
    try:
    response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tools,
    tool_choice=tool_choice,
    )
    return response
    except Exception as e:
    print("Unable to generate ChatCompletion response")
    print(f"Exception: {e}")
    return e

    def main():
    messages = []
    messages.append({"role": "system",
    "content": "现在你是一个航班查询助手,将根据用户问题提供答案,但是不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息 "})
    messages.append({"role": "user", "content": "帮我查询2025年7月30日,郑州到北京的航班的票价, 如果你不知道请调用工具查询"})
    # 1.得到第一次回复:调用:get_plane_number函数
    first_response = chat_completion_request(
    messages, tools=tools, tool_choice="auto")

    复制代码
      assistant_message1 = first_response.choices[0].message
      print(f'assistant_message1-->{assistant_message1}')
      # 2. 将第一次得到的模型回复结果加入messages
      messages.append(first_response.choices[0].message.model_dump())
      # 3. 第一次得到函数的结果
      first_function = parse_function_call(model_response=first_response)
      print(f'first_function--》{first_function}')
      tool_call = first_response.choices[0].message.tool_calls[0]
      # 4. 将函数的结果添加到messages中,继续送入模型问答
    
      messages.append({"role": "tool",
                       "tool_call_id": tool_call.id,
                       "content": str(json.dumps(first_function))})
      # 5. 第二次调用模型
      print(messages)
      second_response = chat_completion_request(
          messages, tools=tools, tool_choice="auto")
      print(f'second_response--》{second_response.choices[0].message}')
      # 6. 将第二次得到函数结果加入信息中
      messages.append(second_response.choices[0].message.model_dump())
      second_function = parse_function_call(model_response=second_response)
      print(f'second_function--》{second_function}')
      tool2_call = second_response.choices[0].message.tool_calls[0]
      # 4. 将函数的结果添加到messages中,继续送入模型问答
      messages.append({"role": "tool",
                       "tool_call_id": tool2_call.id,
                       "content": str(json.dumps(second_function))})
    
      last_response = chat_completion_request(
          messages, tools=tools, tool_choice="auto")
      print(f'last_response--》{last_response.choices[0].message}')

    if name == 'main':
    main()

结果输出:

复制代码
last_response--》ChatCompletionMessage(content='2025年7月30日,从郑州到北京的航班号为1123的票价为668元。', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None)

5 使用Function Call功能应用Sql查询[¶](#5 使用Function Call功能应用Sql查询¶)

  • 该 Function Call 应用目的:假设我们要创建一个具备sql语句查询的聊天机器人。

  • 完整代码包含三个部分:一个sql_function_tools.py、sql_zhipu.py。

  • sql_function_tools.py

  • 定义需要的工具类函数:描述函数功能

  • 定义表的结构

  • 定义表查询的函数

  • 定义解析function call函数

    import json
    import requests
    import os
    import pymysql
    from dotenv import load_dotenv, find_dotenv

    todo: 1.描述数据库表结构(单一个表格)

    database_schema_string = """
    CREATE TABLE emp (
    empno int DEFAULT NULL, --员工编号, 默认为空
    ename varchar(50) DEFAULT NULL, --员工姓名, 默认为空
    job varchar(50) DEFAULT NULL,--员工工作, 默认为空
    mgr int DEFAULT NULL,--员工领导, 默认为空
    hiredate date DEFAULT NULL,--员工入职日期, 默认为空
    sal int DEFAULT NULL,--员工的月薪, 默认为空
    comm int DEFAULT NULL,--员工年终奖, 默认为空
    deptno int DEFAULT NULL,--员工部分编号, 默认为空
    )"""

    todo: 2.描述数据库表结构(多个表格)

    database_schema_string1 = """
    CREATE TABLE emp (
    empno int DEFAULT NULL, --员工编号, 默认为空
    ename varchar(50) DEFAULT NULL, --员工姓名, 默认为空
    job varchar(50) DEFAULT NULL,--员工工作, 默认为空
    mgr int DEFAULT NULL,--员工领导, 默认为空
    hiredate date DEFAULT NULL,--员工入职日期, 默认为空
    sal int DEFAULT NULL,--员工的月薪, 默认为空
    comm int DEFAULT NULL,--员工年终奖, 默认为空
    deptno int DEFAULT NULL,--员工部分编号, 默认为空
    );

    CREATE TABLE DEPT (
    DEPTNO int NOT NULL, -- 部门编码, 默认为空
    DNAME varchar(14) DEFAULT NULL,--部门名称, 默认为空
    LOC varchar(13) DEFAULT NULL,--地点, 默认为空
    PRIMARY KEY (DEPTNO)
    );

    """
    tools = [
    {
    "type": "function",
    "function": {
    "name": "ask_database",
    "description": "使用此函数回答业务问题,要求输出是一个SQL查询语句",
    "parameters": {
    "type": "object",
    "properties": {
    "query": {
    "type": "string",
    "description": f"SQL查询提取信息以回答用户的问题。"
    f"SQL应该使用以下数据库模式编写:{database_schema_string1}"
    f"查询应该以纯文本返回,而不是JSON。"
    f"查询应该只包含MySQL支持的语法。",
    }
    },
    "required": ["query"],
    },
    }
    }
    ]

    todo:1.连接数据库,进行sql语句的查询

    def ask_database(query):
    """连接数据库,进行查询"""
    # 1.连接到 MySQL 数据库
    print("进入函数内部")
    conn = pymysql.connect(
    host='localhost',
    port=3306,
    user='',
    password='密码',
    database='数据库名称',
    charset='utf8mb4', # 指定游标类,返回结果为字典
    )
    # 2. 创建游标
    cursor = conn.cursor()
    print(f'开始测试')
    # 3. 执行sql语句测试
    # 示例:执行 SQL 查询
    # sql = "SELECT * FROM emp"
    print(f'query--》{query}')
    cursor.execute(query)
    # 4. 获取查询结果
    result = cursor.fetchall()
    # 5.关闭游标
    cursor.close()
    # 6.关闭连接
    conn.close()
    return result

    # todo: 2.根据模型回复来确定使用工具函数:

    def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
    # 调用函数
    available_functions = {
    "ask_database": ask_database
    } # only one function test in this example, but you can have multiple
    function_name = response_message.tool_calls[0].function.name
    fuction_to_call = available_functions[function_name]
    function_args = json.loads(response_message.tool_calls[0].function.arguments)
    function_response = fuction_to_call(
    query=function_args.get("query"),
    )
    return function_response

    if name == 'main':
    query = "select count(*) from emp"
    a = ask_database(query)
    print(a)

  • sql_zhipu.py

  • 主逻辑函数

    from zhipuai import ZhipuAI
    from dotenv import load_dotenv, find_dotenv
    from sql_function_tools import *
    import os
    _ = load_dotenv(find_dotenv())

    获取环境变量 ZhiPu_API_KEY

    zhupu_ak = os.environ['zhupu_api']
    client = ZhipuAI(api_key=zhupu_ak) # 填写您自己的APIKey
    ChatGLM = "glm-4"

    def chat_completion_request(messages, tools=None, tool_choice=None, model=ChatGLM):
    try:
    response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=tools,
    tool_choice=tool_choice,
    )
    return response
    except Exception as e:
    print("Unable to generate ChatCompletion response")
    print(f"Exception: {e}")
    return e

    def main():
    messages = []
    messages.append({"role": "system",
    "content": "通过针对业务数据库生成 SQL 查询来回答用户的问题"})
    messages.append({"role": "user", "content": "查询工资最高的前5员工姓名及相应的工资,及其部门号和部门名称"})
    response = chat_completion_request(
    messages, tools=tools, tool_choice="auto"
    )
    assistant_message = response.choices[0].message
    print(f'assistant_message1-->{assistant_message}')
    function_name = response.choices[0].message.tool_calls[0].function.name
    function_id = response.choices[0].message.tool_calls[0].id
    function_response = parse_response(response)
    print(f'assistant_message.model_dump()-->{assistant_message.model_dump()}')
    messages.append(assistant_message.model_dump()) # extend conversation with assistant's reply
    messages.append(
    {
    "role": "tool",
    "tool_call_id": function_id,
    "name": function_name,
    "content": str(function_response),
    }

    复制代码
      )  # extend conversation with function response
      print(f'messages-->{messages}')
      last_response = chat_completion_request(
          messages, tools=tools, tool_choice="auto"
      )
      print(f'last_response--》{last_response}')
      print(f'last_response--》{last_response.choices[0].message}')
      print(f"{last_response.choices[0].message.content}")

    if name == 'main':
    main()

输出结果为:

复制代码
工资最高的前5员工信息如下:

1. 员工姓名:David Anderson,工资:14955,部门号:9,部门名称:SUPPORT
2. 员工姓名:Ian Jones,工资:14949,部门号:6,部门名称:MARKETING
3. 员工姓名:Eva Brown,工资:14915,部门号:10,部门名称:ADMIN
4. 员工姓名:Charlie Anderson,工资:14912,部门号:2,部门名称:ACCOUNTING
5. 员工姓名:Eva Jones,工资:14912,部门号:1,部门名称:SALES

6 小节总结[¶](#6 小节总结¶)

本章节介绍了大模型 Function call 功能的基本概念和使用方法,包括定义外部函数、描述函数功能、代码编写等。

5.3 MCP协议介绍

MCP协议

MCP(Model Context Protocol,模型上下文协议) ,2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大模型与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而无法充分发挥潜力的难题,MCP 使得 AI 应用能够安全地访问和操作本地及远程数据,为 AI 应用提供了连接万物的接口。

对于 LLM 开发者,MCP 是一个变革性的协议。它消除了为每个数据源或工具进行定制集成的需要,减少了开发时间和维护成本。从本质上来说,MCP是一种技术协议,一种智能体Agent开发过程中共同约定的一种规范。在统一的规范下,协作效率就能大幅提高**,最终**提升智能体Agent的开发效率**。

1.MCP协议

MCP协议本质就是Function calling技术的更高层封装和实现。传统的Function calling技术要求围绕不同的外部工具API单独创建一个外部函数,类似一把锁单独配一把钥匙,而一个智能体又往往涉及到多个外部工具设计,因此开发工作量很大。

MCP 的灵感部分来源于 USB-C 的类比:如同 USB-C 通过统一接口连接多种设备,MCP 旨在为 AI 应用提供一个"即插即用"的上下文管理框架。

MCP 的核心思想是将模型与外部系统之间的通信抽象为一个客户端-服务器架构,通过标准化的接口(如基于 JSON-RPC 的通信)实现上下文的动态传递和工具的灵活调用。Anthropic 在发布时提供了初步的规范和 SDK(如 Python 和 TypeScript),并开源了多个预构建的 MCP 服务器(如 Google Drive、GitHub 集成),以加速该协议的推广。

2. MCP服务器与客户端[¶](#2. MCP服务器与客户端¶)

MCP技术体系中对大模型和外部工具的一种划分方式,也就是说在MCP技术体系中,会将外部工具称作服务器,而接入这些外部工具的大模型运行环境称作客户端。

一个客户端可以接入多个不同类型的服务器的,但要求是都要遵循MCP通信协议。MCP服务器的输出内容是一种标准格式的内容,只能被MCP客户端所识别。在客户端和服务器都遵循MCP协议的时候,客户端就能够像Function calling中大模型调用外部工具一样,调用MCP服务器里面的工具。

在MCP技术爆发的这几个月,市面上已经诞生了成百上千的MCP服务器,甚至还出现了大量的MCP服务器集合网站:

  • 百度智能体平台
  • 阿里云百炼平台:

在实际进行智能体开发过程中,我们可以参考这些网站上的MCP工具,并有选择的对其进行调用。

3. MCP与Function calling[¶](#3. MCP与Function calling¶)

通过在 MCP 运行过程中进行数据包捕获与分析可知,MCP 的底层实现机制本质上仍是依托于大模型原生自带的 Function calling 能力,以完成对外部工具的调用操作。只不过,MCP 在此基础上对这一过程进行了更高层次的封装与优化,从而构建起更为完善的交互与功能体系。

MCP 客户端调用服务器工具的流程如下:

  1. 建立连接 :与 MCP 服务器搭建通信链路。

  2. 查询工具 :获取服务器上所有外部工具的数量信息。

  3. 生成列表 :将查询到的外部工具整理成列表,并融入当前对话场景。

  4. 调用工具 :通过 Function calling 技术调用所需的外部工具。

    Function Calling是AI大模型模型调用函数的机制,MCP是一个标准协议,使大模型与API无缝交互,而AI Agent是一个自主运行的智能系统,利用Function Calling和MCP来分析和执行任务,实现特定目标。

4. MCP的技术架构[¶](#4. MCP的技术架构¶)

随着技术迭代加速,MCP 技术在发展进程中实现了重大跨越。如今,它已远非简单的"协议"概念所能涵盖,而是构建起一个完整且自成体系的技术生态。该生态以协议为底层支撑,确保数据传输与交互的规范性和稳定性;开发工具作为重要赋能手段,极大提升了开发效率与创新空间;现成的 MCP 工具则进一步丰富了生态应用场景,降低了技术应用门槛,三者协同共进,彰显出 MCP 技术生态独特的完整性与创新性。

具体介绍如下:

  • MCP 协议 :作为一套抽象的规范集合,涵盖了如大模型与工具的调度规范、服务器与客户端之间的通信规范等内容。遵循这些协议标准的对象,即被认定为 MCP 服务器或客户端。

MCP 协议支持两种主要的通信机制:

  • 本地通信**:**通过 stdio 传输数据,适用于在同一台机器上运行的客户端和服务器之间的通信。

  • 远程通信**:**利用 SSE 与 HTTP 结合,实现跨网络的实时数据传输,适用于需要访问远程资源或分布式部署的场景。

  • MCP 开发工具 :提供了多种编程语言版本的 SDK,开发者借助这些 SDK 可高效完成 MCP 服务器和客户端的开发工作,大大缩短开发周期,提升开发效率。

  • MCP 服务器生态 :以开源的 MCP 服务器为核心构建起一个庞大的技术生态,该生态基于 MCP 协议发展而来。智能体开发人员可直接利用生态中开源的 MCP 工具,加速自身项目的开发进程。

5.4 阿里云百炼平台

​ **阿里云百炼平台**是一款一站式的大模型开发及应用构建平台,旨在帮助开发者和业务人员快速设计和构建大模型应用。用户可以通过简洁的界面操作,在短时间内开发出大模型应用或训练专属模型,从而将更多精力专注于应用创新。

1. 百炼平台接入MCP[¶](#1. 百炼平台接入MCP¶)

​ 阿里云百炼平台正式推出了全生命周期的MCP(Model-Connect-Protocol)服务,实现了从资源管理到部署运维的全流程自动化。用户仅需5分钟即可快速创建连接MCP服务的智能体(Agent),将大模型技术转化为生产力工具。首批集成了包括高德地图、无影、Fetch、Notion等50余款阿里巴巴集团及第三方MCP服务,覆盖生活服务、办公协同、内容创作等多个领域。

接入MCP的优势:

  1. 快速开发与部署 :通过MCP服务,用户无需管理资源、开发部署和工程运维等复杂工作,可在短时间内搭建并上线智能体应用。
  2. 丰富的生态系统 :百炼平台整合了200多款业界领先的大模型和阿里云函数计算资源,以及众多MCP服务,提供一站式智能体开发解决方案,满足不同场景的应用需求。
  3. 深度场景化定制 :与市场上通用的Agent应用不同,百炼MCP服务支持深度场景化定制。用户无需编写代码,通过简单的可视化配置即可打造具备自主思考、任务拆解和决策执行等能力的专属智能体。
  4. 持续扩展的应用边界 :随着MCP协议生态的不断扩展,百炼平台将持续引入更多阿里巴巴集团及第三方应用服务,进一步拓宽智能体的应用边界,推动大模型技术在各行业的落地应用。

通过接入MCP服务,阿里云百炼平台为用户提供了高效、便捷的大模型应用开发环境,降低了开发门槛,加速了大模型技术的产业化应用进程。

2. 接入MCP服务的流程[¶](#2. 接入MCP服务的流程¶)

2.1 开通mcp服务[¶](#2.1 开通mcp服务¶)

进入MCP服务中心,先选择高德MCP工具进行测试:

点击开启服务:

**高德地图 MCP 工具**是高德地图基于 MCP 协议构建的服务器,整合了高德开放平台的地图服务与智能算法,为企业及开发者提供全场景的地图服务解决方案。 其 12 项核心功能涵盖了地图服务的方方面面,满足企业开发的多样化需求。

主要功能:

  • POI 智能提取 :能够从文字中精准提取 POI(兴趣点)信息,涵盖位置、详情、打卡点、价格等多维度内容。
  • 路径规划 :提供驾车、步行、骑行等多种出行方式的路径规划服务,帮助用户选择最优路线。
  • 实时路况查询 :实时查询特定道路或区域的拥堵状况及趋势,为出行提供及时参考。
  • 天气查询 :通过经纬度信息,获取实时天气情况及未来天气预报,为用户出行计划提供支持。

通过高德地图 MCP 工具,AI 智能体可以直接调用高德地图的各项服务,实现如位置查询、路线规划、实时路况查询等功能,提升用户体验和服务效率。

接下来自进入应用管理,即可看到当前开启的MCP服务:

2.2 创建客户端应用[¶](#2.2 创建客户端应用¶)

点击创建新的应用,其实也就是新的Agent:

点击创建

然后即可进行模型和MCP工具配置了:

然后输入系统提示词:你是一名经验丰富的导游,请耐心认真的为用户规划出游行程。

然后测试进行出游路线规划:

能够看到规划结果和MCP工具调用流程:

5.5 Qwen接入MCP实现天气查询

为了让大家更好的理解MCP的底层原理,这里我们先手动搭建一个Qwen客户端,并接入本地的MCP工具。需要注意的是,以后无论使用哪种Agent开发框架,搭建Qwen+MCP的智能体,本质上都是这个手动实现流程的更高层的封装与更便捷的实现形式。

1.环境需求

1.1 安装以下工具包:[¶](#1.1 安装以下工具包:¶)

|-----------|-------------------------------------------|
| 1 | pip install "mcp>=1.6.0" "openai>=1.76.2" |

1.2 申请百炼模型的API[¶](#1.2 申请百炼模型的API¶)

我的API-KEY页面,单击**创建我的API-KEY**。

在已创建的API Key**操作**列,单击**查看**,获取**API KEY**。

1.3 申请天气查询API[¶](#1.3 申请天气查询API¶)

OpenWeather 作为老牌天气信息 API 服务商,可以说是非常良心了。不仅在调用次数上给了 100 万次/月,而且在预报的精度上免费了 60 分钟分钟级预报和 48 小时每小时预报,对于天气敏感的行业来说,非常实用。获取APIkey的流程如下:

  • 点击右上角 "Sign in" , 选择 "Create an Account"
  • 填写身份信息

填写完成后(如下图),提交即可。

  • 确认邮件链接

进入邮箱,点击"Verify your email",会跳转回官网,并且是登录状态(如果不是,使用刚注册的用户名、密码直接登录)。

  • 获取API Key,进入OpenWeatherMap官网,点击你的用户名,选择"My API keys"

注意:API Key大概需要一小时生效。

2.MCP服务器端

该部分实现基于OpenWeather API的城市天气查询服务并通过MCP框架构建服务工具,供大模型使用:

  1. fetch_weather(city: str): 获取天气数据

  2. 通过OpenWeather API请求指定城市的实时天气

  3. 内置参数配置(单位制/语言/API密钥)

  4. 处理网络异常和API错误(返回含error的字典)

  5. format_weather(data): 数据格式化

  6. 兼容原始数据或JSON字符串输入

  7. 提取关键指标(温度/湿度/风速等)

  8. 自动处理API返回的错误信息

  9. query_weather(city: str):服务入口函数

  10. 通过@mcp.tool()注册为MCP服务工具

  11. 协调数据获取(fetch_weather)与格式化(format_weather)

  12. 返回最终用户可读的天气报告

最后通过: mcp.run(transport='stdio') 启动服务,监听大模型的请求。具体代码在实现如下所示:

复制代码
import os
import json
import httpx
# 导入类型提示相关模块,Any表示任意类型
from typing import Any
# 导入环境变量加载模块,用于从.env文件加载配置
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP

# 初始化名为"WeatherServer"的FastMCP服务器实例
mcp = FastMCP("WeatherServer")

# OpenWeather API配置常量
# OpenWeather API基础URL(当前为天气接口)
OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5/weather"
# API密钥
API_KEY = "XXXXX"
# 自定义用户代理标识,用于标识客户端身份
USER_AGENT = "weather-app/1.0"

async def fetch_weather(city: str) -> dict[str, Any] | None:
    """
    从OpenWeather API异步获取指定城市的天气数据

    :param city: 城市名称(需使用英文,如Beijing)
    :return: 包含天气数据的字典,出错时返回包含error键的字典
    """
    # 构造请求参数:城市名、API密钥、单位制(公制)和语言(中文)
    params = {
        "q": city,
        "appid": API_KEY,
        "units": "metric",
        "lang": "zh_cn"
    }
    # 设置请求头:自定义用户代理
    headers = {"User-Agent": USER_AGENT}

    # 创建异步HTTP客户端上下文
    async with httpx.AsyncClient() as client:
        try:
            # 发送GET请求,设置30秒超时
            response = await client.get(
                OPENWEATHER_API_BASE,
                params=params,
                headers=headers,
                timeout=30.0
            )
            # 检查HTTP响应状态,非2xx状态码会抛出异常
            response.raise_for_status()
            # 返回解析后的JSON数据(Python字典格式)
            return response.json()
        except httpx.HTTPStatusError as e:
            # 处理HTTP状态错误(如404,500等)
            return {"error": f"HTTP错误: {e.response.status_code}"}
        except Exception as e:
            # 处理其他未知异常
            return {"error": f"请求失败: {str(e)}"}

def format_weather(data: dict[str, Any] | str) -> str:
    """
    格式化天气数据为易读字符串

    :param data: 原始天气数据(字典或JSON字符串)
    :return: 格式化后的天气信息字符串
    """
    # 如果输入是字符串,尝试转换为字典
    if isinstance(data, str):
        try:
            data = json.loads(data)
        except Exception as e:
            return f"无法解析天气数据: {e}"

    # 检查是否存在错误信息
    if "error" in data:
        return f"⚠️ {data['error']}"

    # 安全提取数据字段(使用get方法避免KeyError)
    city = data.get("name", "未知")  # 城市名称
    country = data.get("sys", {}).get("country", "未知")  # 国家代码
    temp = data.get("main", {}).get("temp", "N/A")  # 温度(摄氏度)
    humidity = data.get("main", {}).get("humidity", "N/A")  # 湿度百分比
    wind_speed = data.get("wind", {}).get("speed", "N/A")  # 风速(米/秒)
    # 处理可能为空的weather数组,默认返回空字典
    weather_list = data.get("weather", [{}])
    description = weather_list[0].get("description", "未知")  # 天气描述

    # 构造格式化的天气信息字符串
    return (
        f"🌍 {city}, {country}\n"
        f"🌡 温度: {temp}°C\n"
        f"💧 湿度: {humidity}%\n"
        f"🌬 风速: {wind_speed} m/s\n"
        f"🌤 天气: {description}\n"
    )

@mcp.tool()  # 注册为MCP服务器的工具函数
async def query_weather(city: str) -> str:
    """
    城市天气查询工具

    :param city: 城市英文名称(如Beijing)
    :return: 格式化后的天气信息字符串
    """
    # 获取原始天气数据
    data = await fetch_weather(city)
    # 格式化并返回结果
    return format_weather(data)

if __name__ == "__main__":
    # 当运行时,启动MCP服务器
    # 使用标准输入输出作为传输方式(适用于命令行交互)
    mcp.run(transport='stdio')

3.MCP服务器的接入

MCP服务器标准接入流程是通过写入一个配置文件来完成的,格式如下:

复制代码
{
  "mcpServers": {
    "[服务名称]": {  // 自定义服务标识(如filesystem/git)
      "command": "执行命令", 
      "args": ["服务模块名",          // 如@modelcontextprotocol/server-filesystem
                "--flag",             // 命令行标志
                "参数值"              // 如--repository后的路径
               ]
    }
  }
}

示例内容如下所示:

复制代码
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
    },
    "git": {
      "command": "uvx",
      "args": ["mcp-server-git", "--repository", "path/to/git/repo"]
    }
  }
}

在当前的任务中,我们的配置文件servers_config.json为:

复制代码
{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["weather_server.py"]
    }
  }
}

4.mcp客户端

接下来,我们实现MCP客户端系统,主要功能包括:

  1. 管理多个MCP服务器连接
  2. 将MCP工具无缝接入OpenAI的Function Calling体系,实现自然语言驱动的自动化操作,实现工具的调用
  3. 将 MCP 返回的工具信息封装为 Tool 对象,生成 LLM 可理解的描述
  4. 实现交互式聊天循环

该部分代码在main.py文件中,具体实现内容如下:

4.1导入工具包

复制代码
# 导入异步IO库,用于编写协程和异步任务
import asyncio
# 导入JSON处理模块,用于数据的序列化和反序列化
import json
# 导入日志记录模块,用于系统运行日志记录
import logging
# 导入操作系统接口模块,处理文件路径和环境变量
import os
# 导入高级文件操作模块,支持文件和目录的复制/删除操作
import shutil
# 导入异步上下文管理器,用于管理多个异步资源
from contextlib import AsyncExitStack
# 导入类型提示模块,增强代码可读性和静态检查
from typing import Any, Dict, List, Optional

# 导入支持HTTP/2的全功能异步HTTP客户端库
import httpx
# 从dotenv导入环境变量加载器,用于读取.env文件
from dotenv import load_dotenv
# 导入OpenAI官方Python SDK
from openai import OpenAI  # OpenAI Python SDK
# 从MCP框架导入客户端会话和标准IO服务器参数类
from mcp import ClientSession, StdioServerParameters
# 导入MCP标准输入输出客户端实现
from mcp.client.stdio import stdio_client

# 配置日志系统:INFO级别,格式为"时间 - 日志级别 - 消息"
logging.basicConfig(
    level=logging.INFO,                # 设置日志记录级别为INFO
    format="%(asctime)s - %(levelname)s - %(message)s"  # 定义日志格式
)

4.2 配置加载类[¶](#4.2 配置加载类¶)

主要实现以下内容:环境变量管理 :从.env文件加载敏感配置信息**配置校验**:强制校验必需参数的存在性(如API密钥)文件配置加载 :读取JSON格式的服务器配置文件。

复制代码
# =============================
# 配置加载类(支持环境变量及配置文件)
# =============================
class Configuration:
    """管理 MCP 客户端的环境变量和配置文件"""

    def __init__(self) -> None:
        load_dotenv()
        # 从环境变量中加载 API key, base_url 和 model
        self.api_key = os.getenv("LLM_API_KEY")
        self.base_url = os.getenv("BASE_URL")
        self.model = os.getenv("MODEL")
        if not self.api_key:
            raise ValueError("❌ 未找到 LLM_API_KEY,请在 .env 文件中配置")

    @staticmethod
    def load_config(file_path: str) -> Dict[str, Any]:
        """
        从 JSON 文件加载服务器配置

        Args:
            file_path: JSON 配置文件路径

        Returns:
            包含服务器配置的字典
        """
        with open(file_path, "r") as f:
            return json.load(f)

4.3 客户端类[¶](#4.3 客户端类¶)

Server 类用于 管理单个 MCP 服务器的全生命周期 ,包括连接初始化、工具发现、工具调用和资源清理。它是与 MCP 服务器交互的核心代理,支持异步操作和错误恢复。主要的方法如下所示:

1.__init__(self, name: str, config: Dict[str, Any])

功能 :初始化服务器实例

  • 存储服务器名称(name)和配置字典(config
  • 初始化异步资源栈(AsyncExitStack)管理连接
  • 创建清理锁(asyncio.Lock)防止并发资源释放冲突

2.async initialize(self)

功能 :建立与 MCP 服务器的连接

  1. 从配置中解析启动命令(command)和参数(args
  2. 合并系统环境变量与自定义环境变量(env
  3. 通过 stdio_client 创建标准输入输出通道
  4. 初始化 ClientSession 会话对象

3.async list_tools(self) -> List[Any]

功能 :获取服务器提供的工具列表

  • 解析服务器返回的元组结构(("tools", [tool1, tool2...])
  • 将每个工具封装为 Tool 对象(含名称、描述、输入模式)

4.async execute_tool(...)

功能 :执行工具调用并支持自动重试

  • retries=2:最大重试次数
  • delay=1.0:重试间隔(秒)

5.async cleanup(self)

功能 :安全释放所有连接资源

  • 使用 _cleanup_lock 锁防止多线程并发清理

  • 通过 AsyncExitStack.aclose() 关闭所有异步上下文

    =============================

    MCP 服务器客户端类

    =============================

    class Server:
    """管理单个 MCP 服务器连接和工具调用"""

    复制代码
      def __init__(self, name: str, config: Dict[str, Any]) -> None:
          self.name: str = name
          self.config: Dict[str, Any] = config
          self.session: Optional[ClientSession] = None
          self.exit_stack: AsyncExitStack = AsyncExitStack()
          self._cleanup_lock = asyncio.Lock()
    
      async def initialize(self) -> None:
          """初始化与 MCP 服务器的连接"""
          # command 字段直接从配置获取
          command = self.config["command"]
          if command is None:
              raise ValueError("command 不能为空")
    
          server_params = StdioServerParameters(
              command=command,
              args=self.config["args"],
              env={**os.environ, **self.config["env"]} if self.config.get("env") else None,
          )
          try:
              stdio_transport = await self.exit_stack.enter_async_context(
                  stdio_client(server_params)
              )
              read_stream, write_stream = stdio_transport
              session = await self.exit_stack.enter_async_context(
                  ClientSession(read_stream, write_stream)
              )
              await session.initialize()
              self.session = session
          except Exception as e:
              logging.error(f"Error initializing server {self.name}: {e}")
              await self.cleanup()
              raise
    
      async def list_tools(self) -> List[Any]:
          """获取服务器可用的工具列表
    
          Returns:
              工具列表
          """
          if not self.session:
              raise RuntimeError(f"Server {self.name} not initialized")
          tools_response = await self.session.list_tools()
          tools = []
          for item in tools_response:
              if isinstance(item, tuple) and item[0] == "tools":
                  for tool in item[1]:
                      tools.append(Tool(tool.name, tool.description, tool.inputSchema))
          return tools
    
      async def execute_tool(
          self, tool_name: str, arguments: Dict[str, Any], retries: int = 2, delay: float = 1.0
      ) -> Any:
          """执行指定工具,并支持重试机制
    
          Args:
              tool_name: 工具名称
              arguments: 工具参数
              retries: 重试次数
              delay: 重试间隔秒数
    
          Returns:
              工具调用结果
          """
          if not self.session:
              raise RuntimeError(f"Server {self.name} not initialized")
          attempt = 0
          while attempt < retries:
              try:
                  logging.info(f"Executing {tool_name} on server {self.name}...")
                  result = await self.session.call_tool(tool_name, arguments)
                  return result
              except Exception as e:
                  attempt += 1
                  logging.warning(
                      f"Error executing tool: {e}. Attempt {attempt} of {retries}."
                  )
                  if attempt < retries:
                      logging.info(f"Retrying in {delay} seconds...")
                      await asyncio.sleep(delay)
                  else:
                      logging.error("Max retries reached. Failing.")
                      raise
    
      async def cleanup(self) -> None:
          """清理服务器资源"""
          async with self._cleanup_lock:
              try:
                  await self.exit_stack.aclose()
                  self.session = None
              except Exception as e:
                  logging.error(f"Error during cleanup of server {self.name}: {e}")

4.4 工具封装类[¶](#4.4 工具封装类¶)

Tool 类用于 标准化封装 MCP 工具的描述信息 ,并生成适合大语言模型(LLM)理解的提示文本,实现自然语言到工具调用的桥梁。

复制代码
# =============================
# 工具封装类
# =============================
class Tool:
    """封装 MCP 返回的工具信息"""

    def __init__(self, name: str, description: str, input_schema: Dict[str, Any]) -> None:
        self.name: str = name
        self.description: str = description
        self.input_schema: Dict[str, Any] = input_schema

    def format_for_llm(self) -> str:
        """生成用于 LLM 提示的工具描述"""
        args_desc = []
        if "properties" in self.input_schema:
            for param_name, param_info in self.input_schema["properties"].items():
                arg_desc = f"- {param_name}: {param_info.get('description', 'No description')}"
                if param_name in self.input_schema.get("required", []):
                    arg_desc += " (required)"
                args_desc.append(arg_desc)
        return f"""
Tool: {self.name}
Description: {self.description}
Arguments:
{chr(10).join(args_desc)}
"""

4.5 大模型客户端[¶](#4.5 大模型客户端¶)

实现与大语言模型的交互,支持 Function Calling(工具调用) 功能,简化了复杂提示工程和工具调用的集成流程。

复制代码
# =============================
# LLM 客户端封装类(使用 OpenAI SDK)
# =============================
class LLMClient:
    """使用 OpenAI SDK 与大模型交互"""

    def __init__(self, api_key: str, base_url: Optional[str], model: str) -> None:
        self.client = OpenAI(api_key=api_key, base_url=base_url)
        self.model = model

    def get_response(self, messages: List[Dict[str, Any]], tools: Optional[List[Dict[str, Any]]] = None) -> Any:
        """
        发送消息给大模型 API,支持传入工具参数(function calling 格式)
        """
        payload = {
            "model": self.model,
            "messages": messages,
            "tools": tools
        }
        try:
            response = self.client.chat.completions.create(**payload)
            return response
        except Exception as e:
            logging.error(f"Error during LLM call: {e}")
            raise

4.6 多服务器 MCP 客户端类[¶](#4.6 多服务器 MCP 客户端类¶)

作为系统的核心控制器,管理多个MCP服务器,整合它们的工具,并通过自然语言交互驱动工具调用。它转换工具格式为OpenAI兼容格式,维护对话上下文,处理多轮工具调用

复制代码
# =============================
# 多服务器 MCP 客户端类(集成配置文件、工具格式转换与 OpenAI SDK 调用)
# =============================
class MultiServerMCPClient:
    def __init__(self) -> None:
        """
        管理多个 MCP 服务器,并使用 OpenAI Function Calling 风格的接口调用大模型
        """
        self.exit_stack = AsyncExitStack()
        config = Configuration()
        self.openai_api_key = config.api_key
        self.base_url = config.base_url
        self.model = config.model
        self.client = LLMClient(self.openai_api_key, self.base_url, self.model)
        # (server_name -> Server 对象)
        self.servers: Dict[str, Server] = {}
        # 各个 server 的工具列表
        self.tools_by_server: Dict[str, List[Any]] = {}
        self.all_tools: List[Dict[str, Any]] = []

    async def connect_to_servers(self, servers_config: Dict[str, Any]) -> None:
        """
        根据配置文件同时启动多个服务器并获取工具
        servers_config 的格式为:
        {
          "mcpServers": {
              "sqlite": { "command": "uvx", "args": [ ... ] },
              "puppeteer": { "command": "npx", "args": [ ... ] },
              ...
          }
        }
        """
        mcp_servers = servers_config.get("mcpServers", {})
        for server_name, srv_config in mcp_servers.items():
            server = Server(server_name, srv_config)
            await server.initialize()
            self.servers[server_name] = server
            tools = await server.list_tools()
            self.tools_by_server[server_name] = tools

            for tool in tools:
                # 统一重命名:serverName_toolName
                function_name = f"{server_name}_{tool.name}"
                self.all_tools.append({
                    "type": "function",
                    "function": {
                        "name": function_name,
                        "description": tool.description,
                        "input_schema": tool.input_schema
                    }
                })

        # 转换为 OpenAI Function Calling 所需格式
        self.all_tools = await self.transform_json(self.all_tools)

        logging.info("\n✅ 已连接到下列服务器:")
        for name in self.servers:
            srv_cfg = mcp_servers[name]
            logging.info(f"  - {name}: command={srv_cfg['command']}, args={srv_cfg['args']}")
        logging.info("\n汇总的工具:")
        for t in self.all_tools:
            logging.info(f"  - {t['function']['name']}")

    async def transform_json(self, json_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        将工具的 input_schema 转换为 OpenAI 所需的 parameters 格式,并删除多余字段
        """
        result = []
        for item in json_data:
            if not isinstance(item, dict) or "type" not in item or "function" not in item:
                continue
            old_func = item["function"]
            if not isinstance(old_func, dict) or "name" not in old_func or "description" not in old_func:
                continue
            new_func = {
                "name": old_func["name"],
                "description": old_func["description"],
                "parameters": {}
            }
            if "input_schema" in old_func and isinstance(old_func["input_schema"], dict):
                old_schema = old_func["input_schema"]
                new_func["parameters"]["type"] = old_schema.get("type", "object")
                new_func["parameters"]["properties"] = old_schema.get("properties", {})
                new_func["parameters"]["required"] = old_schema.get("required", [])
            new_item = {
                "type": item["type"],
                "function": new_func
            }
            result.append(new_item)
        return result

    async def chat_base(self, messages: List[Dict[str, Any]]) -> Any:
        """
        使用 OpenAI 接口进行对话,并支持多次工具调用(Function Calling)。
        如果返回 finish_reason 为 "tool_calls",则进行工具调用后再发起请求。
        """
        response = self.client.get_response(messages, tools=self.all_tools)
        # 如果模型返回工具调用
        if response.choices[0].finish_reason == "tool_calls":
            while True:
                messages = await self.create_function_response_messages(messages, response)
                response = self.client.get_response(messages, tools=self.all_tools)
                if response.choices[0].finish_reason != "tool_calls":
                    break
        return response

    async def create_function_response_messages(self, messages: List[Dict[str, Any]], response: Any) -> List[Dict[str, Any]]:
        """
        将模型返回的工具调用解析执行,并将结果追加到消息队列中
        """
        function_call_messages = response.choices[0].message.tool_calls
        messages.append(response.choices[0].message.model_dump())
        for function_call_message in function_call_messages:
            tool_name = function_call_message.function.name
            tool_args = json.loads(function_call_message.function.arguments)
            # 调用 MCP 工具
            function_response = await self._call_mcp_tool(tool_name, tool_args)
            messages.append({
                "role": "tool",
                "content": function_response,
                "tool_call_id": function_call_message.id,
            })
        return messages

    async def process_query(self, user_query: str) -> str:
        """
        OpenAI Function Calling 流程:
         1. 发送用户消息 + 工具信息
         2. 若模型返回 finish_reason 为 "tool_calls",则解析并调用 MCP 工具
         3. 将工具调用结果返回给模型,获得最终回答
        """
        messages = [{"role": "user", "content": user_query}]
        response = self.client.get_response(messages, tools=self.all_tools)
        content = response.choices[0]
        logging.info(content)
        if content.finish_reason == "tool_calls":
            tool_call = content.message.tool_calls[0]
            tool_name = tool_call.function.name
            tool_args = json.loads(tool_call.function.arguments)
            logging.info(f"\n[ 调用工具: {tool_name}, 参数: {tool_args} ]\n")
            result = await self._call_mcp_tool(tool_name, tool_args)
            messages.append(content.message.model_dump())
            messages.append({
                "role": "tool",
                "content": result,
                "tool_call_id": tool_call.id,
            })
            response = self.client.get_response(messages, tools=self.all_tools)
            return response.choices[0].message.content
        return content.message.content

    async def _call_mcp_tool(self, tool_full_name: str, tool_args: Dict[str, Any]) -> str:
        """
        根据 "serverName_toolName" 格式调用相应 MCP 工具
        """
        parts = tool_full_name.split("_", 1)
        if len(parts) != 2:
            return f"无效的工具名称: {tool_full_name}"
        server_name, tool_name = parts
        server = self.servers.get(server_name)
        if not server:
            return f"找不到服务器: {server_name}"
        resp = await server.execute_tool(tool_name, tool_args)
        return resp.content if resp.content else "工具执行无输出"

    async def chat_loop(self) -> None:
        """多服务器 MCP + OpenAI Function Calling 客户端主循环"""
        logging.info("\n🤖 多服务器 MCP + Function Calling 客户端已启动!输入 'quit' 退出。")
        messages: List[Dict[str, Any]] = []
        while True:
            query = input("\n你: ").strip()
            if query.lower() == "quit":
                break
            try:
                messages.append({"role": "user", "content": query})
                messages = messages[-20:]  # 保持最新 20 条上下文
                response = await self.chat_base(messages)
                messages.append(response.choices[0].message.model_dump())
                result = response.choices[0].message.content
                # logging.info(f"\nAI: {result}")
                print(f"\nAI: {result}")
            except Exception as e:
                print(f"\n⚠️  调用过程出错: {e}")

    async def cleanup(self) -> None:
        """关闭所有资源"""
        await self.exit_stack.aclose()

4.7 主函数类[¶](#4.7 主函数类¶)

主函数是程序的 入口点 ,负责协调整个系统的启动、运行和关闭流程:

  1. 加载配置:从.env和JSON文件读取设置。

  2. 初始化客户端:创建多服务器客户端实例。

  3. 连接服务器:根据配置连接所有MCP服务器并收集工具。

  4. 启动交互循环:进入用户对话。

  5. 清理资源:无论成功与否都关闭连接。

    =============================

    主函数

    =============================

    async def main() -> None:
    # 从配置文件加载服务器配置
    config = Configuration()
    servers_config = config.load_config("servers_config.json")
    client = MultiServerMCPClient()
    try:
    await client.connect_to_servers(servers_config)
    await client.chat_loop()
    finally:
    try:
    await asyncio.sleep(0.1)
    await client.cleanup()
    except RuntimeError as e:
    # 如果是因为退出 cancel scope 导致的异常,可以选择忽略
    if "Attempted to exit cancel scope" in str(e):
    logging.info("退出时检测到 cancel scope 异常,已忽略。")
    else:
    raise

    if name == "main":
    asyncio.run(main())

5.运行结果

回答结果:

6.多MCP服务器的实现

6.1 服务器端[¶](#6.1 服务器端¶)

基于 MCP(Model Context Protocol)框架 的简单文件写入服务,主要功能是 接收文本内容并写入本地文件

复制代码
import json
import httpx
from typing import Any
from mcp.server.fastmcp import FastMCP

# 初始化 MCP 服务器
mcp = FastMCP("WriteServer")
USER_AGENT = "write-app/1.0"

@mcp.tool()
async def write_file(content: str) -> str:
    """
    将指定内容写入本地文件。
    :param content: 必要参数,字符串类型,用于表示需要写入文档的具体内容。
    :return:是否成功写入
    """
    try:

        filename = r"tmp.txt"
        # 将文本写入txt文档中
        with open(filename, 'w', encoding='utf-8') as file:
            file.write(content)

        # 返回结果展示:文件已经写入
        return f"已成功写入本地文件。"
    except Exception:
        return "未成功写入."

if __name__ == "__main__":
    # 以标准 I/O 方式运行 MCP 服务器
    mcp.run(transport='stdio')

6.2 服务端接入的配置文件

复制代码
{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": [
        "weather_server.py"
      ]
    },
    "write": {
      "command": "python",
      "args": [
        "write_server.py"
      ]
    }
  }
}

6.3 运行结果[¶](#6.3 运行结果¶)

直接执行main文件,结果如下:

回答结果为:

文件内容为:

5.6 Agent原理

大模型Agent介绍与应用

学习目标

  • 了解什么是AI Agent
  • 了解AI Agent和传统软件的区别和联系
  • 了解AI Agent的应用场景及实现工具
  • 掌握如何基于CrewAI框架开发AI Agent的流程

1 什么是AI Agent[¶](#1 什么是AI Agent¶)

在人工智能领域,AI Agent(Artificial Intelligence Agent)是指一种能够感知环境进行自主理解,进行决策和执行动作的智能实体

不同于传统的人工智能,AI Agent 具备通过独立思考、调用工具去逐步完成给定目标的能力。Agent可以是物理实体(如机器人)或虚拟实体(如软件程序)。


1.1 AI Agent的主要类别[¶](#1.1 AI Agent的主要类别¶)

谈到 AI Agent,很多人都认为它是 LLM 的产物,毕竟大部分人接触 Agent 是从基于GPT-4 的 AutoGPT、 BabyGPT、GPT-Engineer 等开源 Agent 程序开始的。 但了解 AI Agent 的人应该知道,Agent 概念并不是当今的产物,而是伴随人工智能而出现的智能实体概念不断进化的结果。


伴随着 AI 技术的发展,至 2000 年左右, Agent 已经衍生出不少种类。 根据其感知的智能和能力程度的不同,可以将 AI Agent 分为以下几类:

  • 简单反射型 Agent:根据当前环境状态做出直接反应。eg:一个简单的温度调节器,它根据当前温度调整加热或制冷。
  • 目标导向型 Agent:它们根据预设的目标来做决策,能够规划和执行序列动作以达成这些目标。eg:自动驾驶汽车,以安全到达目的地为目标进行各种驾驶操作,如特斯拉的自动驾驶统。
  • 学习型 Agent:这类Agent能够基于过去的经验和数据学习,不断优化自身的表现。eg:基于用户反馈不断优化的聊天机器人。

当下谈论到的AI Agent:本质是一个控制 LLM(大语言模型) 来解决问题的代理系统。


1.2 AI Agent的原理(LLM Agent)[¶](#1.2 AI Agent的原理(LLM Agent)¶)

  • AI Agent是一种超越简单文本生成的人工智能系统。它使用大型语言模型(LLM)作为其核心计算引擎,使其能够进行对话、执行任务、推理并展现一定程度的自主性。简而言之,Agent是一个具有复杂推理能力、记忆和执行任务手段的系统。

主要组成部分:


AI Agent 工作流程:

  • 01-Prompt提示词:
  • 提示词是Agent接收到的初始输入,它描述了Agent需要完成的任务或解决的问题。
  • 02-LLM大模型:
  • 大模型是Agent进行任务规划和知识推理的重要工具。利用LLM大模型对提示词进行深入分析,生成可能的解决方案。
  • 03-Memory记忆:
  • 可以保留当前用户输入内容;上下文内容;外部向量存储的知识库;网页信息等。
  • 04-Planning规划:
  • 任务规划是Agent根据提示词、大模型以及知识库进行决策和规划的过程它涉及对任务的分解、目标的设定、路径的规划等。
  • 05 Action行动:
  • 行动执行是Agent根据任务规划结果执行具体操作的过程。包含工具:计算器、代码解释器、搜索、API等

举例说明AI Agent的工作原理:

  • 以"AI Agent来处理客户的退货请求"为例,进而理解Agent的原理:

1.3 AI Agent呈现的主要形式[¶](#1.3 AI Agent呈现的主要形式¶)

当前基于大模型的AI Agent的呈现形式对比:


2 AI Agent和传统软件的区别[¶](#2 AI Agent和传统软件的区别¶)

AI Agent将使软件架构的范式从面向过程迁移到面向目标。

传统软件的架构只能解决有限范围的任务,AI Agent的架构则可以解决无限域的任务


3 AI Agent的应用场景及实现工具[¶](#3 AI Agent的应用场景及实现工具¶)

3.1 AI Agent的应用场景[¶](#3.1 AI Agent的应用场景¶)

AI Agent 的应用范围非常广泛,包括但不限于:

AI Agent 的广泛应用可以极大的减轻企业或个人的时间和人力成本,提高工作效率,丰富人们的生活体验


3.2 AI Agent的开发工具[¶](#3.2 AI Agent的开发工具¶)

其实在前面章节我们讲到的GPTs以及Assistant API本质都是AI Agent,或者说AI Agent的初代产品。目前,国内外已出现多个领域的 AI Agent 架构与产品。下面我们就举个代表性的例子:


百度 AgentBuilder:


字节扣子平台:


昆仑万维天工SkyAgents平台:


AgentGPT:

  • 一个基于GPT-4的开源AI自动化机器人工具,可以让你在浏览器中配置和部署自主的 AI机器人。
  • 访问地址:https://agentgpt.reworkd.ai/zh

LangChain:

  • 一个用于构建AI应用的开源框架,其中的Agents模块支持开发基于大模型的AI Agent。它提供了Agent管理、记忆模块、工具集成等能力。
  • 访问地址:https://github.com/langchain-ai/langchain

AutoGen:


ChatDev:

  • 一个基于大型语言模型(LLM)的软件开发框架,旨在通过自然语言交互和多智能体协作的方式,实现软件开发全流程的自动化。
  • 访问地址:https://github.com/OpenBMB/ChatDev

CrewAI:

本章小结

本章节主要介绍了什么是AI Agent,详细阐述了尤其是LLM时代下AI Agent的工作原理,并对比分析了AI Agent和传统软件的区别,以及如何实现AI Agent的开发工具进行了介绍。

5.7 Agent应用

大模型Agent应用

学习目标

掌握如何基于CrewAI框架开发AI Agent的流程


1 大模型Agent的应用


1.1 项目介绍

本次项目以CrewAI为框架,开发AI Agent实现自动写情书以及发送邮件。

CrewAI是一个创新的多角色agent框架,专为角色扮演中的AI代理提供自动化设置。它通过促进AI代理之间的合作,使得这些代理能够共同解决复杂问题。


本次项目基本流程图:


1.2 准备工作[¶](#1.2 准备工作¶)

在开展此项目之前,完成环境配置:

复制代码
首先,确保你的机器安装了Python3.10-Python3.11, 终端查看python的版本: $ python --version
如果还没有下载python的小伙伴,可以复制链接下载:https://www.python.org/
  • 安装第三方库:pip install -r requirements.txt

  • requirements.txt内容如下:

    aiohttp==3.9.1
    aiosignal==1.3.1
    annotated-types==0.6.0
    anyio==4.2.0
    attrs==23.2.0
    certifi==2023.11.17
    cffi==1.16.0
    charset-normalizer==3.3.2
    click==8.1.7
    colorama==0.4.6
    crewai==0.1.32
    curl-cffi==0.5.10
    dataclasses-json==0.6.3
    distro==1.9.0
    duckduckgo_search==4.2
    frozenlist==1.4.1
    greenlet==3.0.3
    h11==0.14.0
    httpcore==1.0.2
    httpx==0.26.0
    idna==3.6
    jsonpatch==1.33
    jsonpointer==2.4
    langchain==0.1.1
    langchain-community==0.0.13
    langchain-core==0.1.13
    langchain-openai==0.0.2.post1
    langsmith==0.0.83
    lxml==5.1.0
    marshmallow==3.20.2
    multidict==6.0.4
    mypy-extensions==1.0.0
    numpy==1.26.3
    openai==1.8.0
    packaging==23.2
    pycparser==2.21
    pydantic==2.5.3
    pydantic_core==2.14.6
    PyYAML==6.0.1
    regex==2023.12.25
    requests==2.31.0
    sniffio==1.3.0
    SQLAlchemy==2.0.25
    tenacity==8.2.3
    tiktoken==0.5.2
    tqdm==4.66.1
    typing-inspect==0.9.0
    typing_extensions==4.9.0
    urllib3==2.1.0
    yarl==1.9.4

  • 使用Qwen大模型的模型

    没有api-key的需要去平台注册,或者重新创建。

  • 可以借助Ollama使用本地大模型


1.3 代码实现[¶](#1.3 代码实现¶)

1.3.1 导入必备的工具包[¶](#1.3.1 导入必备的工具包¶)
复制代码
from crewai import Agent, Task, Crew, Process  # CrewAI相关组件
from agent_tools import CustomTools  # 自定义工具
from dotenv import load_dotenv, find_dotenv  # API环境配置
from langchain_community.chat_models import ChatTongyi  # 模型

load_dotenv()

client = ChatTongyi()

1.3.2 定义Agent[¶](#1.3.2 定义Agent¶)
作家Agent
复制代码
poet = Agent(
  role='作家', #表明其主要功能。
  goal='根据用户需求,创作出情感丰富的文章(最长字数不超过300个词)。', # 目标
  backstory="""你作为一名著名的作家,拥有千万级别的粉丝,最擅长写情感类型的文章。""",
  verbose=True, # 设置为True,这通常意味着代理将提供详细的日志、输出或解释
  allow_delegation=False, # 设置为False,表示不允许此代理将其任务委派给其他代理或进程
  llm=client
)

内容编辑Agent
复制代码
letter_writer = Agent(
  role='内容编辑',
  goal='对作家撰写的文章内容进行精心编辑。',
  backstory="""作为一名经验丰富的编辑,你在编辑书信方面有多年的专业经验,
  你需要将作家写的文章内容整理编排成书信的样式,并将书信内容存储在本地磁盘上。
  你必须使用提供的工具将存储到指定文件中,并确保文件已保存到磁盘上。当文件成功保存时返回 "书信已保存.".
  """,
  verbose=True,
  allow_delegation=False,
  tools=[CustomTools.store_poesy_to_txt],
  llm=client
)

定义寄信人Agent
复制代码
sender = Agent(
  role='寄信人',
  goal='将编辑好的书信以邮件的形式发送给心仪的人',
  backstory="""你是一名勤恳的信使,专注于将书信传递给每个人,
  你必须使用提供的工具将指定文件的书信内容中传送到其他人的邮箱里,如果成功传送,记得返回"信件已发送"
  """,
  verbose=True,
  allow_delegation=True,
  tools=[CustomTools.send_message],
  llm=client
)

1.3.3 自定义工具类函数[¶](#1.3.3 自定义工具类函数¶)

发送邮件的工具类函数

复制代码
from langchain.tools import tool
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
from dotenv import load_dotenv
import os

load_dotenv()


class CustomTools(object):

    @tool("将文本写入文档中")
    def store_poesy_to_txt(content: str) -> str:
        """
        将编辑后的书信文本内容自动保存到txt文档中
        """
        try:

            filename = "poie.txt"
            # 将文本写入txt文档中
            with open(filename, 'w') as file:
                file.write(content)

            # 返回结果展示:文件已经写入
            return f"File written to {filename}."
        except Exception:
            return "Error with the input for the tool."

    @tool("发送文本到邮件")
    def send_message(self):
        """
        读取生成的本地书信文件txt文本,并以邮件的形式发送到某个人的邮箱中
        """

        # 发件人
        from_name = "小可爱"
        # 发件邮箱
        from_addr = "1980xxxx428@163.com"
        # 发件邮箱授权码,注意不是QQ邮箱密码
        from_pwd = os.environ.get("EMAIL_PWD")
        # 收件邮箱
        to_addr = "vixxxanx@icloud.com"
        # 邮件标题
        my_title = "520小情书"
        # 邮件正文
        # 书信路径
        filename = "poie.txt"

        # 定义md文档的绝对路径
        with open(filename) as f:
            my_msg = f.read()
        # MIMEText三个主要参数
        # 1. 邮件内容
        # 2. MIME子类型,plain表示text类型
        # 3. 邮件编码格式,使用"utf-8"避免乱码
        msg = MIMEText(my_msg, 'plain', 'utf-8')
        msg['From'] = formataddr([from_name, from_addr])
        # 邮件的标题
        msg['Subject'] = my_title
        # SMTP服务器地址,QQ邮箱的SMTP地址是"smtp.qq.com"
        smtp_srv = "smtp.163.com"

        try:
            # 不能直接使用smtplib.SMTP来实例化,第三方邮箱会认为它是不安全的而报错
            # 使用加密过的SMTP_SSL来实例化,它负责让服务器做出具体操作,它有两个参数
            # 第一个是服务器地址,但它是bytes格式,所以需要编码
            # 第二个参数是服务器的接受访问端口,SMTP_SSL协议默认端口是465
            srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465)
            # 使用授权码登录QQ邮箱
            srv.login(from_addr, from_pwd)
            # 使用sendmail方法来发送邮件,它有三个参数
            # 第一个是发送地址
            # 第二个是接受地址,是list格式,可以同时发送给多个邮箱
            # 第三个是发送内容,作为字符串发送
            srv.sendmail(from_addr, [to_addr], msg.as_string())
            print('发送成功')
        except Exception as e:
            print('发送失败', e)
        finally:
            # 无论发送成功还是失败都要退出你的QQ邮箱
            srv.quit()

1.3.4 为Agent定义任务[¶](#1.3.4 为Agent定义任务¶)
作家Agent的任务
复制代码
content = input("请输入你的需求:\n")
# 为你设计的Agent创建任务
task1 = Task(
  description=f"""用户需求:{content}。
  你最后给出的答案必须是一份富含爱情表示的情书.""",
  agent=poet
)

内容编辑Agent的任务
复制代码
task2 = Task(
  description="""查找任何语法错误,进行编辑和格式化(如果需要)。并要求将内容保存在本地磁盘中。将内容保存到本地非常重要,
  你最后的答案必须是信息是否已被存储在本地磁盘中.""",
  agent=letter_writer
)

寄信人Agent的任务
复制代码
task3 = Task(
  description="""根据本次磁盘保存的书信内容,你将整理并发送邮件给心仪的人,这个很重要.
你最后的答一定要成功发送该邮件.""",
  agent=sender
)

1.3.5 工作人员和流程[¶](#1.3.5 工作人员和流程¶)
复制代码
crew = Crew(
  agents=[poet, letter_writer, sender],
  tasks=[task1, task2, task3],
  verbose=2,
  process=Process.sequential 
    # 使用按顺序执行任务的流程。上一个任务的结果将作为附加内容传递给下一个任务。
)
result = crew.kickoff()

print("######################")
print(result)

输出结果:

小节总结

介绍了如何基于CrewAI框架,实现多个AI Agent协同工作,并完成自动写书信以及发送邮件的案例。

相关推荐
妍妍爱学习1 小时前
技术破界 数智共生:华为星河AI网络商业峰会5月18日深圳启幕
网络·ai·生态·数智化·峰会
aqi001 小时前
鸿蒙版本的小小机器人APP开放源码啦
人工智能·华为·harmonyos·鸿蒙·harmony
眼泪划过的星空1 小时前
快速了解LangGraph:构建智能Agent工作流的核心框架
人工智能·python·langchain
眞bilibili1 小时前
如何快速无缝的从 vscode 转向AI编辑器 cursor、kiro、trae 等
人工智能·vscode·编辑器
码农小白AI1 小时前
适配外贸出口质检标准!IACheck AI报告审核打通制造业来料成品质控壁垒
人工智能
二川bro2 小时前
2026主流大模型横向对比:Kimi/Claude/GPT选型+网关管理实战
人工智能
2501_942389552 小时前
Nifty IT指数的K线犹如断线的风筝
人工智能·postgresql·时序数据库·storm·tdengine
空杆推不起2 小时前
穿透 Flink CDC 表层用法:数据库日志捕获机制、Flink Source 运行时、端到端一致性底层原理详解
大数据·数据库·flink
u0103055272 小时前
泛型类与类型安全详解
人工智能·1024程序员节