使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块

使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块

深入浅出:将高性能 C++ 日志库与 Python 生态无缝融合


https://gitee.com/galaxy_0/micro-log

一、缘起

在 Python 项目中,日志记录是不可或缺的基础设施。Python 标准库 logging 功能丰富,但在极端性能场景下(如高频日志、低延迟要求),C++ 原生实现的日志库往往具有更优的表现。microLog 是一个轻量级、高性能的 C++ 日志库,支持 vlog 变参日志宏、多后端(文件、Redis 等)和异步刷盘。如果能将其能力以 Python 扩展模块的形式暴露给开发者,便能兼得原生性能与 Python 的易用性。

本文将通过 pybind11 将 microLog 的 C++ 接口封装为 Python 模块,重点处理 vlog 变参日志方法的跨语言传递,并展示完整的架构设计与实现细节。


二、microLog C++ 库概览

microLog 的核心设计包括:

  • 日志门面(logItfc :提供统一的 logvlogdebug 等接口。
  • 后端抽象(logBackend:支持文件、Redis、自定义后端。
  • 工厂模式:动态创建后端实例。
  • vlog :变参日志宏,支持格式化字符串,类似 printf

C++ 示例代码:

cpp 复制代码
// 使用 vlog 宏
vlog(INFO, "用户 %s 登录成功,ID: %d", username.c_str(), userId);

我们的目标是在 Python 中实现相同效果:

python 复制代码
logger.vlog('INFO', '用户 %s 登录成功,ID: %d', 'Alice', 1001)

三、pybind11 简介

pybind11 是一个轻量级的头文件库,用于在 C++ 和 Python 之间创建无缝绑定。它的主要优势包括:

  • 现代 C++ 语法:基于 C++11 及以上特性,与现代 C++ 语法完美融合
  • 自动类型转换:自动处理 STL 容器、智能指针、类等的类型转换
  • 头文件库:无需额外编译,直接包含即可使用
  • 高性能:编译时内省推断类型信息,运行时零额外开销

本文将使用 pybind11(版本 2.10+)构建 Python 扩展。


四、整体架构设计

下图展示了封装后的模块层次结构:
#mermaid-svg-oEOlCvMQ7vRp0jCL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oEOlCvMQ7vRp0jCL .error-icon{fill:#552222;}#mermaid-svg-oEOlCvMQ7vRp0jCL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oEOlCvMQ7vRp0jCL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .marker.cross{stroke:#333333;}#mermaid-svg-oEOlCvMQ7vRp0jCL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oEOlCvMQ7vRp0jCL p{margin:0;}#mermaid-svg-oEOlCvMQ7vRp0jCL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster-label text{fill:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster-label span{color:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster-label span p{background-color:transparent;}#mermaid-svg-oEOlCvMQ7vRp0jCL .label text,#mermaid-svg-oEOlCvMQ7vRp0jCL span{fill:#333;color:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .node rect,#mermaid-svg-oEOlCvMQ7vRp0jCL .node circle,#mermaid-svg-oEOlCvMQ7vRp0jCL .node ellipse,#mermaid-svg-oEOlCvMQ7vRp0jCL .node polygon,#mermaid-svg-oEOlCvMQ7vRp0jCL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .rough-node .label text,#mermaid-svg-oEOlCvMQ7vRp0jCL .node .label text,#mermaid-svg-oEOlCvMQ7vRp0jCL .image-shape .label,#mermaid-svg-oEOlCvMQ7vRp0jCL .icon-shape .label{text-anchor:middle;}#mermaid-svg-oEOlCvMQ7vRp0jCL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .rough-node .label,#mermaid-svg-oEOlCvMQ7vRp0jCL .node .label,#mermaid-svg-oEOlCvMQ7vRp0jCL .image-shape .label,#mermaid-svg-oEOlCvMQ7vRp0jCL .icon-shape .label{text-align:center;}#mermaid-svg-oEOlCvMQ7vRp0jCL .node.clickable{cursor:pointer;}#mermaid-svg-oEOlCvMQ7vRp0jCL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .arrowheadPath{fill:#333333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oEOlCvMQ7vRp0jCL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oEOlCvMQ7vRp0jCL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oEOlCvMQ7vRp0jCL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster text{fill:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL .cluster span{color:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oEOlCvMQ7vRp0jCL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oEOlCvMQ7vRp0jCL rect.text{fill:none;stroke-width:0;}#mermaid-svg-oEOlCvMQ7vRp0jCL .icon-shape,#mermaid-svg-oEOlCvMQ7vRp0jCL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oEOlCvMQ7vRp0jCL .icon-shape p,#mermaid-svg-oEOlCvMQ7vRp0jCL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oEOlCvMQ7vRp0jCL .icon-shape .label rect,#mermaid-svg-oEOlCvMQ7vRp0jCL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oEOlCvMQ7vRp0jCL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oEOlCvMQ7vRp0jCL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oEOlCvMQ7vRp0jCL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后端存储
C++层
Python层
Python 应用
Logger 门面
pybind11 绑定的 _core 模块
microLog 门面 logItfc
后端工厂 logFactoryHelper
文件后端
Redis后端
自定义后端
本地日志文件
Redis List
外部系统

  • Python 层 :业务代码通过 Logger 对象调用 vlog 等方法。
  • pybind11 绑定层:作为胶水层,将 Python 调用转换为 C++ 调用。
  • microLog 核心:原生 C++ 日志引擎,负责格式化、过滤、写入。

五、实现步骤

5.1 环境准备

确保系统已安装:

  • Python(3.7+)
  • C++ 编译器(支持 C++17)
  • pybind11(pip install pybind11 或从源码获取)
  • microLog 库源码(或已编译的动态库)

项目结构:

复制代码
my-microlog-pybind/
├── setup.py              # 构建配置
├── CMakeLists.txt        # CMake 构建(可选)
├── src/
│   ├── microlog_bind.cpp # 绑定入口
│   └── logger_wrap.h     # 封装类定义
├── test/
│   └── test.py           # 测试脚本
└── README.md

5.2 编写 setup.py(使用 setuptools + pybind11)

python 复制代码
# setup.py
from setuptools import setup, Extension
import pybind11

ext_modules = [
    Extension(
        'microlog_core',
        sources=[
            'src/microlog_bind.cpp',
            'src/logger_wrap.cpp'
        ],
        include_dirs=[
            pybind11.get_include(),
            './deps/microlog/include'  # microLog 头文件路径
        ],
        library_dirs=['./deps/microlog/lib'],
        libraries=['microlog'],
        language='c++',
        extra_compile_args=['-std=c++17', '-O3'],
    ),
]

setup(
    name='microlog',
    version='0.1.0',
    description='Python binding for microLog C++ logging library',
    ext_modules=ext_modules,
    install_requires=['pybind11>=2.10'],
)

5.3 定义 C++ 封装类 LoggerWrap

我们创建一个 C++ 类,用于包装 microLog 的 logItfc 实例,并通过 pybind11 暴露给 Python。

logger_wrap.h

cpp 复制代码
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "microlog/logItfc.h"   // microLog 核心头文件

namespace py = pybind11;

class LoggerWrap {
public:
    // 构造函数:创建 microLog 实例
    LoggerWrap(const std::string& backend_type = "console");
    ~LoggerWrap();

    // 暴露给 Python 的方法
    void vlog(const std::string& level, const std::string& format, py::args args);
    void log(const std::string& level, const std::string& message);
    void setLevel(const std::string& level);
    void close();

private:
    // 内部 microLog 门面指针
    std::unique_ptr<logItfc> m_logger;
    
    // 辅助方法:将日志级别字符串转为枚举
    int parseLevel(const std::string& level);
    
    // 辅助方法:格式化消息(将 Python 参数转为 C++ 字符串)
    std::string formatMessage(const std::string& format, const py::tuple& args);
};

logger_wrap.cpp

cpp 复制代码
#include "logger_wrap.h"
#include <sstream>
#include <cstdarg>
#include <vector>

// 假设 microLog 有 consoleLog 后端类
LoggerWrap::LoggerWrap(const std::string& backend_type) {
    // 根据后端类型创建不同的后端实例
    // 此处简化:默认使用控制台后端
    // 实际可从参数解析配置对象
    m_logger = std::make_unique<logItfc>(new consoleLog());
}

LoggerWrap::~LoggerWrap() {
    close();
}

int LoggerWrap::parseLevel(const std::string& level) {
    if (level == "DEBUG") return 0;
    if (level == "INFO") return 1;
    if (level == "WARN") return 2;
    if (level == "ERROR") return 3;
    return 1; // 默认 INFO
}

std::string LoggerWrap::formatMessage(const std::string& format, const py::tuple& args) {
    // 将 Python 元组中的参数转换为字符串列表
    std::vector<std::string> argStrings;
    for (size_t i = 0; i < args.size(); ++i) {
        py::object obj = args[i];
        // 使用 py::str 将 Python 对象转为字符串
        argStrings.push_back(py::str(obj).cast<std::string>());
    }
    
    // 简单的占位符替换:将 %s 替换为参数
    // 更完善的实现可支持 %d, %f 等
    std::string result = format;
    size_t pos = 0;
    size_t argIdx = 0;
    while ((pos = result.find("%s", pos)) != std::string::npos && argIdx < argStrings.size()) {
        result.replace(pos, 2, argStrings[argIdx++]);
        pos += argStrings[argIdx - 1].length();
    }
    return result;
}

void LoggerWrap::vlog(const std::string& level, const std::string& format, py::args args) {
    // 将格式化字符串和参数列表合并为最终消息
    std::string message = formatMessage(format, args);
    
    // 调用 microLog 的 log 方法
    int levelEnum = parseLevel(level);
    m_logger->log(levelEnum, message);
}

void LoggerWrap::log(const std::string& level, const std::string& message) {
    int levelEnum = parseLevel(level);
    m_logger->log(levelEnum, message);
}

void LoggerWrap::setLevel(const std::string& level) {
    int levelEnum = parseLevel(level);
    // 假设 logItfc 有 setLevel 方法
    // m_logger->setLevel(levelEnum);
}

void LoggerWrap::close() {
    // 释放资源,刷新缓冲区
    // m_logger->flush();
}

5.4 编写 pybind11 绑定入口

microlog_bind.cpp

cpp 复制代码
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "logger_wrap.h"

namespace py = pybind11;

PYBIND11_MODULE(microlog_core, m) {
    m.doc() = "Python binding for microLog C++ logging library";
    
    // 绑定 LoggerWrap 类
    py::class_<LoggerWrap>(m, "Logger")
        // 构造函数:支持指定后端类型
        .def(py::init<const std::string&>(), 
             py::arg("backend_type") = "console",
             "创建 Logger 实例")
        // vlog 方法:变参日志记录
        .def("vlog", &LoggerWrap::vlog,
             py::arg("level"), py::arg("format"),
             "变参日志记录,支持格式化字符串",
             "用法: logger.vlog('INFO', '用户 %s 登录', 'Alice')")
        // log 方法:简单日志记录
        .def("log", &LoggerWrap::log,
             py::arg("level"), py::arg("message"),
             "简单日志记录")
        // 设置日志级别
        .def("set_level", &LoggerWrap::setLevel,
             py::arg("level"),
             "设置日志级别")
        // 关闭并释放资源
        .def("close", &LoggerWrap::close,
             "关闭日志系统,刷新缓冲区")
        // 支持上下文管理器(with 语句)
        .def("__enter__", [](LoggerWrap& self) -> LoggerWrap& { return self; })
        .def("__exit__", [](LoggerWrap& self, py::object, py::object, py::object) { 
            self.close(); 
        });
}

5.5 重点:处理 vlog 变参方法

vlog 方法在 C++ 中通常接受格式化字符串和可变参数(...)。在 Python 中,我们利用 py::args 捕获所有额外参数,然后在 C++ 端进行格式化处理。

Python 调用方式:

python 复制代码
logger.vlog('INFO', '用户 %s 登录成功,ID: %d', 'Alice', 1001)
logger.vlog('ERROR', '连接 %s 失败,错误码 %d', 'database', 500)

在 C++ 端:

  1. py::args 捕获所有位置参数为一个 py::tuple
  2. formatMessage 将每个参数转为字符串
  3. 依次替换格式化字符串中的 %s 占位符
  4. 调用 microLog 的 log 方法写入最终消息

更完善的格式化实现 :支持 %d%f%s 等多种占位符:

cpp 复制代码
std::string LoggerWrap::formatMessage(const std::string& format, const py::tuple& args) {
    std::string result = format;
    size_t argIdx = 0;
    size_t pos = 0;
    
    while (pos < result.length() && argIdx < args.size()) {
        // 查找 % 占位符
        size_t pct = result.find('%', pos);
        if (pct == std::string::npos) break;
        
        // 检查是否是 %%(转义)
        if (pct + 1 < result.length() && result[pct + 1] == '%') {
            result.erase(pct, 1);  // 删除一个 %
            pos = pct + 1;
            continue;
        }
        
        // 提取占位符类型
        char type = result[pct + 1];
        py::object obj = args[argIdx++];
        
        std::string replacement;
        switch (type) {
            case 's':
                replacement = py::str(obj).cast<std::string>();
                break;
            case 'd':
            case 'i':
                replacement = std::to_string(obj.cast<int>());
                break;
            case 'f':
                replacement = std::to_string(obj.cast<double>());
                break;
            default:
                replacement = py::str(obj).cast<std::string>();
                break;
        }
        
        result.replace(pct, 2, replacement);
        pos = pct + replacement.length();
    }
    return result;
}

5.6 编译安装

使用 pip 直接安装(自动调用编译器):

bash 复制代码
pip install .

或使用 CMake 构建(更灵活):

cmake 复制代码
# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(microlog_pybind)

find_package(Python REQUIRED COMPONENTS Interpreter Development)
find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(microlog_core
    src/microlog_bind.cpp
    src/logger_wrap.cpp
)

target_include_directories(microlog_core PRIVATE
    ./deps/microlog/include
)
target_link_libraries(microlog_core PRIVATE
    microlog
)

六、在 Python 中使用

编译后,即可在 Python 中导入使用:

python 复制代码
# test.py
from microlog_core import Logger

# 创建 Logger 实例(默认控制台后端)
logger = Logger()

# 使用 vlog 变参方法
logger.vlog('INFO', '用户 %s 登录成功,ID: %d', 'Alice', 1001)
logger.vlog('DEBUG', '调试信息: 变量 x = %s, y = %s', 42, True)
logger.vlog('WARN', '磁盘使用率 %d%%', 85)
logger.vlog('ERROR', '捕获异常: %s', '连接超时')

# 使用简单 log 方法
logger.log('INFO', '服务启动完成')

# 设置日志级别
logger.set_level('WARN')

# 使用上下文管理器(自动关闭)
with Logger() as logger:
    logger.vlog('INFO', '在 with 语句中记录日志')

预期控制台会输出格式化后的日志(假设 microLog 后端为 console)。


七、高级特性

7.1 传递后端配置

若需要从 Python 配置后端(如 Redis、文件),可以在构造函数中接收配置字典:

cpp 复制代码
LoggerWrap::LoggerWrap(const py::dict& config) {
    std::string backend_type = config["type"].cast<std::string>();
    if (backend_type == "redis") {
        std::string host = config["host"].cast<std::string>();
        int port = config["port"].cast<int>();
        // 创建 Redis 后端
        m_logger = std::make_unique<logItfc>(new redisLog(host, port));
    } else if (backend_type == "file") {
        std::string path = config["path"].cast<std::string>();
        m_logger = std::make_unique<logItfc>(new fileLog(path));
    } else {
        m_logger = std::make_unique<logItfc>(new consoleLog());
    }
}

Python 调用:

python 复制代码
logger = Logger({
    'type': 'redis',
    'host': '127.0.0.1',
    'port': 6379
})

7.2 与 Python logging 模块集成

可以将 microLog 作为 Python logging 的 Handler:

python 复制代码
import logging
from microlog_core import Logger

class MicrologHandler(logging.Handler):
    def __init__(self, backend_type='console'):
        super().__init__()
        self.logger = Logger(backend_type)
    
    def emit(self, record):
        msg = self.format(record)
        level = record.levelname
        self.logger.log(level, msg)

# 使用
logging.basicConfig(level=logging.INFO)
handler = MicrologHandler()
logging.getLogger().addHandler(handler)
logging.info('通过 microLog 记录的日志')

7.3 异步日志支持

microLog 可能支持异步刷盘,我们可以在 Python 端启用该特性:

cpp 复制代码
LoggerWrap::LoggerWrap(const std::string& backend_type, bool async) {
    // 创建后端并设置异步模式
    auto* backend = new consoleLog();
    backend->setAsync(async);
    m_logger = std::make_unique<logItfc>(backend);
}

八、性能优化建议

8.1 避免频繁的类型转换

每次 vlog 调用都会将 Python 对象转为 C++ 字符串,如果日志量极大,这可能成为瓶颈。可考虑:

  • 在 microLog 内部进行异步批处理
  • 使用 pybind11 的 py::object 直接传递,延迟转换

8.2 使用 py::gil_scoped_release

对于长时间运行的操作(如刷盘),可释放 GIL:

cpp 复制代码
void LoggerWrap::flush() {
    py::gil_scoped_release release;
    m_logger->flush();  // 长时间操作,不持有 GIL
}

8.3 批量写入

在 Python 端实现批量写入接口:

cpp 复制代码
void LoggerWrap::vlog_batch(const py::list& entries) {
    for (auto item : entries) {
        auto dict = item.cast<py::dict>();
        std::string level = dict["level"].cast<std::string>();
        std::string msg = dict["message"].cast<std::string>();
        m_logger->log(parseLevel(level), msg);
    }
}

九、常见问题

Q1: 如何处理阻塞操作?

A: vlog 方法应尽快返回,将实际 I/O 操作放在后台线程。microLog 本身若支持异步,可直接启用。

Q2: 如何传递复杂配置?

A: 使用 Python 字典(py::dict)作为参数,在 C++ 端解析。

Q3: 多个后端如何同时使用?

A: 可组合多个后端(如同时写入文件和控制台),microLog 本身支持多后端。

Q4: 如何实现日志采样?

A: 在 Python 层或 C++ 层增加采样逻辑,按比例丢弃部分日志。


十、总结

本文展示了如何利用 pybind11 将 C++ 日志库 microLog 封装为 Python 模块。通过 pybind11 的现代 C++ 语法和自动类型转换,我们能够:

  • 简洁地暴露 C++ 类和方法:只需几行代码即可完成绑定
  • 优雅地处理变参函数 :利用 py::args 捕获 Python 变参,在 C++ 端格式化
  • 保持高性能:pybind11 编译时内省,运行时零额外开销

将 C++ 的高效与 Python 的灵活结合,为开发者提供了更多选择。希望本文能为您在 Python 中引入原生 C++ 能力提供参考。

相关推荐
为自己_带盐1 小时前
.NET 项目集成可观测性:从架构思考到落地实践
架构
WZF-Sang1 小时前
网络基础——2
服务器·网络·c++·学习·网络编程·php
zhixingheyi_tian1 小时前
一份GC日志的解读
java·开发语言
ywl4708120871 小时前
ArrayList和LinkedList内部的实现原理和区别
java·java基础
一只专注api接口开发的技术猿1 小时前
电商评论自动化监控与情感数据分析完整落地教程(附可直接运行 Python 代码)
大数据·数据库·python·数据分析·自动化
熊猫钓鱼>_>1 小时前
2026 前端的三场底层革命:当 Vue、React Native 和 Three.js 同时去掉中间层
前端·javascript·vue.js·react native·架构·webgl·three.js
沉静的小伙1 小时前
Spring事务
java·数据库·spring
apihz2 小时前
随机驾考题目(C 照科一 / 科四 2000+ 题)免费API调用教程
android·java·c语言·开发语言·网络协议·tcp/ip
AIGS0012 小时前
企业AI落地:Agent OS 治理框架实践
java·大数据·人工智能·机器学习·人工智能ai大模型应用