news 2026/10/2 21:30:28

从零搭建AI工程能力:三次踩坑经验与完整落地指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建AI工程能力:三次踩坑经验与完整落地指南

从零搭建AI工程能力这件事,我前前后后折腾过三回。第一回是跟着网上的教程跑通了几个Demo,觉得自己行了;第二回是接手一个真实项目,发现Demo和工程之间隔着一条鸿沟;第三回才算真正摸到了门道——不是模型调得多好,而是整条链路的每个环节都能兜住底。这篇内容就是把这三次踩坑的经验揉在一起,讲清楚一个AI工程项目从零到能跑起来,到底需要哪些东西、为什么需要、以及怎么落地。适合刚入行想建立完整认知的工程师,也适合做了几年传统开发想往AI方向转的朋友。我不会只给你一堆概念,而是把每个环节的选型逻辑、实操细节和容易翻车的地方都摊开讲。

1. 先搞清楚“从零搭建”到底从哪个零开始

很多人看到“AI工程”四个字,第一反应是去学Transformer架构、去啃注意力机制的公式。这个方向不能说错,但如果你目标是搭建一个能用的AI工程系统,从数学公式开始大概率会让你在半路放弃。我见过太多人卡在反向传播的推导上,最后连一个完整的推理服务都没跑起来。

1.1 三种“零”的起点,对应三条完全不同的路径

“从零”这个词其实很模糊。根据我的观察,不同背景的人说的“零”根本不是同一个东西。

第一种零是编程零基础。这类朋友需要先解决的是编程语言和基本工具链的问题,AI工程对他们来说还太远。我的建议是先花两三个月把Python和命令行用熟,能独立写出一个读写文件、调用接口的小脚本,再来看AI工程的内容。

第二种零是有编程基础但没接触过AI。这是最主流的群体,也是这篇内容主要服务的对象。你懂变量、循环、函数,可能还写过Web服务或者数据处理脚本,但没训练过模型、没部署过推理服务。你的“零”在于不知道AI系统的各个组件怎么串起来。

第三种零是做过AI实验但没做过AI工程。你在Notebook里跑通过模型,调过参,甚至微调过开源模型,但这些东西只存在于你的本地环境里。你的“零”在于不知道怎么把它变成一个别人能访问、能稳定运行、能持续迭代的服务。

我下面讲的内容,主要面向第二种和第三种起点。如果你属于第一种,先把编程基础打牢,这篇可以先收藏。

1.2 为什么我不建议一上来就啃模型原理

这里说一个可能有点反直觉的观点:搭建AI工程能力,优先级最高的不是模型知识,而是系统思维。

原因很简单。在实际项目里,模型往往是最容易被替换的组件。今天用这个开源模型,明天可能换成另一个;今天用API调用,明天可能换成自部署。但数据管道、服务架构、监控体系这些东西,换起来成本极高,而且它们决定了你的系统能不能稳定运行。

我举个例子。假设你要做一个文档问答系统。模型层面,你可以调用现成的接口,也可以用开源模型自己部署。但真正花时间的部分是:文档怎么切分、向量怎么存储和检索、检索结果怎么和模型输出拼接、用户提问怎么做预处理、返回结果怎么做后处理、整个链路的延迟怎么控制。这些才是AI工程的核心。

我的建议是:先用现成的模型接口把整条链路跑通,理解每个环节的作用,然后再回头深入模型层面做优化。这个顺序反过来,很容易陷入“模型调得很好但系统跑不起来”的困境。

1.3 一个最小可用的AI工程系统包含哪些模块

在动手之前,先在心里画一张地图。一个能跑的AI工程系统,不管多简单,通常包含这几个部分:

  • 输入处理层:接收用户请求,做格式校验、预处理、路由分发
  • 推理层:调用模型完成核心计算,可能是API调用也可能是本地推理
  • 后处理层:对模型输出做解析、过滤、格式化
  • 数据层:存储对话历史、向量索引、配置信息等
  • 监控层:记录请求日志、延迟、错误率、资源占用

这五层里,推理层反而是最“薄”的一层。很多新手把90%的精力花在推理层,结果系统上线后发现瓶颈全在输入处理和数据层。

2. 环境搭建:那些教程里不会告诉你的细节

环境搭建看起来是最没技术含量的部分,但根据我的经验,新手在这一步卡住的时间占总时间的30%以上。不是因为你笨,而是因为教程往往假设你的环境是干净的,而现实中的环境从来不是干净的。

2.1 Python环境管理的正确姿势

先说一个我踩过的坑。早期我直接用系统自带的Python,pip install各种包,结果不同项目之间依赖冲突,最后把系统环境搞崩了。后来改用虚拟环境,但一开始用的是venv,每个项目手动创建,时间长了也乱。

现在我固定用conda来管理环境,理由有三个:第一,conda不仅能管理Python包,还能管理非Python的依赖,比如某些需要编译的库;第二,conda的环境隔离更彻底;第三,conda可以导出完整的环境配置文件,换机器时一键复现。

具体操作上,我习惯给每个AI工程项目建一个独立环境:

conda create -n ai-eng python=3.10 conda activate ai-eng

Python版本我选3.10,不是最新的但兼容性最好。很多AI相关的库对3.11和3.12的支持还不完善,用3.10能省去很多麻烦。

环境建好后,第一件事是装pip和setuptools的最新版:

pip install --upgrade pip setuptools wheel

这一步很多人会跳过,但老版本的pip在解析依赖时经常出问题,升级一下能避免很多莫名其妙的报错。

2.2 依赖安装的顺序有讲究

装包不是一条pip install就完事的。AI工程涉及的包大致分几类:基础科学计算库(numpy、scipy)、深度学习框架(PyTorch、TensorFlow)、推理加速库(onnxruntime、tensorrt)、服务框架(FastAPI、Flask)、数据处理库(pandas、polars)。

我的安装顺序是:先装numpy,再装框架,最后装服务框架。为什么?因为numpy是很多库的底层依赖,先装好它,后面的库在编译时能找到正确的链接。如果顺序反了,有时候会出现numpy版本被覆盖或者链接错误的问题。

另外,PyTorch的安装一定要去官网查对应的命令。不同操作系统、不同CUDA版本对应的安装命令完全不同。我见过有人直接pip install torch,装上了CPU版本,然后纳闷为什么推理这么慢。

# 以CUDA 11.8为例,具体命令以官网为准 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

2.3 硬件资源的评估与选择

AI工程对硬件的要求取决于你的场景。如果只是调用外部API,那普通开发机就够了。如果要本地推理,就要认真评估。

我一般按这个标准来估算:7B参数的模型,FP16精度下需要约14GB显存;4bit量化后约4GB。13B模型翻倍。这只是模型本身的占用,实际运行时还要加上KV Cache、中间激活值等开销,通常要再留30%到50%的余量。

如果你没有GPU,也不是不能做AI工程。可以用CPU推理小模型,或者用外部API。但要做好心理准备,CPU推理的速度可能只有GPU的十分之一甚至更低。

一个实用的建议:刚开始学习时,优先用外部API或者云端GPU按需付费,不要一上来就买硬件。等你确定了方向,再根据实际需求配置设备。

2.4 项目目录结构的约定

环境搭好后,先别急着写代码。花十分钟把目录结构定下来,后面会省很多事。我常用的结构是这样的:

project/ ├── configs/ # 配置文件 ├── data/ # 数据文件 │ ├── raw/ # 原始数据 │ └── processed/ # 处理后的数据 ├── src/ # 源代码 │ ├── data/ # 数据处理模块 │ ├── models/ # 模型相关 │ ├── services/ # 服务层 │ └── utils/ # 工具函数 ├── tests/ # 测试代码 ├── notebooks/ # 实验性代码 ├── requirements.txt # 依赖清单 └── README.md

这个结构的好处是职责清晰。notebooks放实验代码,src放正式代码,data放数据,configs放配置。新手最容易犯的错是把所有代码堆在一个目录里,等到项目变大就理不清了。

3. 数据管道:AI工程里最容易被低估的部分

如果说模型是AI系统的大脑,那数据管道就是血管。血管堵了,大脑再聪明也没用。我在实际项目里见过太多这样的情况:模型效果不好,大家第一反应是换模型、调参数,折腾一圈后发现根本问题是数据管道有问题——要么数据没清洗干净,要么检索逻辑有缺陷。

3.1 数据清洗不是可选项而是必选项

原始数据几乎不可能直接拿来用。以文本数据为例,常见的脏数据包括:HTML标签残留、特殊字符、重复内容、编码错误、格式不统一。

我处理文本数据一般走这几步:

第一步是编码统一。把所有文本统一转成UTF-8,遇到无法解码的字符用替换策略处理。这一步看起来简单,但很多乱码问题都出在这里。

第二步是去重。完全重复的内容直接删掉,近似重复的用相似度算法识别。我常用的是MinHash加LSH,速度快,效果也不错。

第三步是格式规范化。把全角转半角、统一标点符号、去除多余空白字符。这些操作看似琐碎,但对后续处理影响很大。

第四步是质量过滤。根据业务需求设定规则,比如过滤掉过短的文本、过滤掉包含特定关键词的内容、过滤掉语言不匹配的内容。

import re import unicodedata def clean_text(text): # 统一编码 text = unicodedata.normalize('NFKC', text) # 去除HTML标签 text = re.sub(r'<[^>]+>', '', text) # 去除多余空白 text = re.sub(r'\s+', ' ', text) # 去除控制字符 text = ''.join(ch for ch in text if unicodedata.category(ch)[0] != 'C') return text.strip()

这段代码不复杂,但能解决大部分常见的文本脏数据问题。

3.2 分块策略决定了检索质量

做检索增强生成(RAG)类应用时,文档分块是最关键的决策之一。分块太大,检索精度下降;分块太小,上下文信息丢失。

我的经验是:分块大小要根据文档类型和查询类型来定,没有万能参数。

对于技术文档,我通常按段落分块,每块300到500个token,块之间保留50到100个token的重叠。重叠的目的是防止关键信息被切断。

对于对话记录,我按对话轮次分块,一个完整的问答对作为一块。

对于长篇文章,我先按章节分,章节内再按段落分,形成层级结构。

分块时还要考虑一个因素:你的嵌入模型的最大输入长度。如果嵌入模型最多处理512个token,那你的分块就不能超过这个数,否则会被截断。

3.3 向量化与索引构建的实操细节

文本分好块后,下一步是转成向量并建索引。

嵌入模型的选择上,英文场景我常用all-MiniLM-L6-v2,体积小、速度快、效果够用。中文场景可以用BGE系列或者M3E系列。如果预算充足,用外部API的嵌入服务效果更好,但要注意数据隐私和调用成本。

索引构建我推荐用FAISS,Facebook开源的向量检索库,支持多种索引类型。小规模数据(百万级以下)用Flat索引就够了,精度最高。数据量再大可以用IVF或者HNSW索引,牺牲一点精度换速度。

import faiss import numpy as np # 假设embeddings是N x D的numpy数组 dimension = embeddings.shape[1] index = faiss.IndexFlatIP(dimension) # 内积索引 # 归一化后内积等价于余弦相似度 faiss.normalize_L2(embeddings) index.add(embeddings) # 检索 query = np.array([query_embedding], dtype='float32') faiss.normalize_L2(query) distances, indices = index.search(query, k=5)

这里有个细节:用内积索引前一定要做L2归一化,否则内积和余弦相似度不等价。这个坑我踩过,当时检索结果乱七八糟,排查了半天才发现是没归一化。

3.4 数据版本管理:别等到出问题才后悔

数据是会变的。今天用的数据集,明天可能被更新、被修正、被替换。如果没有版本管理,出了问题你都不知道是哪个版本的数据导致的。

我的做法是给每次数据处理的结果打上版本号,记录处理时间、处理脚本的commit hash、输入数据的来源。可以用DVC这类工具,也可以简单地用文件命名约定加一个记录表。

一个血泪教训:曾经有一次模型效果突然下降,排查了两天才发现是上游数据源更新了,而我们的管道没有做版本记录,无法回滚。从那以后,我养成了给数据打版本的习惯。

4. 模型选型与推理服务:在效果和成本之间找平衡

模型选型是AI工程里最纠结的环节之一。开源模型那么多,闭源API也在不断更新,到底选哪个?我的答案是:没有最好的模型,只有最适合你场景的模型。

4.1 选型时我实际关注的几个维度

网上很多模型对比只讲效果分数,但实际工程中,效果只是其中一个维度。我通常从这几个方面评估:

维度说明权重
任务效果在你的具体任务上的表现高
推理延迟单次请求的响应时间高
吞吐量单位时间能处理的请求数中
部署成本硬件需求或API调用费用高
上下文长度能处理的最大输入长度中
可控性能否微调、能否本地部署视场景
稳定性服务可用性、输出一致性高

注意这里的权重是“视场景”的。比如做实时对话,延迟权重就很高;做离线批处理,吞吐量更重要;做涉及敏感数据的场景,可控性就是硬性要求。

4.2 API调用与本地部署的决策逻辑

这是新手最常问的问题:到底用API还是自己部署?

我的决策逻辑是这样的:

如果数据敏感度低、请求量不大、团队没有GPU运维能力,优先用API。省心省力,按量付费,不用操心硬件和扩缩容。

如果数据不能出本地、请求量大且稳定、团队有运维能力,考虑本地部署。长期来看成本更低,而且完全可控。

如果请求量波动大、想快速验证想法,先用API跑通,等模式验证了再考虑迁移到本地。

还有一个中间方案:用API做主力,本地部署一个小模型做兜底。当API不可用时,降级到本地模型,保证服务不中断。

4.3 推理服务的性能优化手段

如果你选择了本地部署,性能优化就是绕不开的话题。我常用的手段有这几个:

量化是最直接的手段。把FP16的模型量化成INT8或INT4,显存占用能降到原来的四分之一甚至八分之一,速度也有提升。代价是精度会有所下降,但很多场景下这个损失可以接受。

批处理能显著提升吞吐量。把多个请求攒成一批一起推理,GPU利用率更高。但要注意批处理会增加单个请求的延迟,需要根据场景权衡。

KV Cache优化对大模型推理很关键。通过PagedAttention等技术,可以更高效地管理KV Cache,提升并发能力。

模型蒸馏是用小模型学习大模型的行为,在保持大部分效果的同时大幅降低推理成本。但这个需要训练,门槛较高。

# 使用量化模型加载的示例(以transformers为例) from transformers import AutoModelForCausalLM, AutoTokenizer model = AutoModelForCausalLM.from_pretrained( "model_name", load_in_4bit=True, # 4bit量化 device_map="auto", # 自动分配设备 torch_dtype="auto" )

4.4 服务框架的选择与接口设计

推理服务需要一个Web框架来暴露接口。我首选FastAPI,理由是:异步支持好、自动生成文档、类型校验强、性能足够。

接口设计上,我遵循几个原则:

  • 请求和响应都用JSON格式,字段命名清晰
  • 支持流式输出,提升用户体验
  • 请求中带上超时参数,避免长时间等待
  • 响应中带上耗时信息,方便排查性能问题
from fastapi import FastAPI from pydantic import BaseModel import time app = FastAPI() class GenerateRequest(BaseModel): prompt: str max_tokens: int = 512 temperature: float = 0.7 class GenerateResponse(BaseModel): text: str latency_ms: float @app.post("/generate", response_model=GenerateResponse) async def generate(req: GenerateRequest): start = time.time() # 调用模型推理 result = await model_inference(req.prompt, req.max_tokens, req.temperature) latency = (time.time() - start) * 1000 return GenerateResponse(text=result, latency_ms=latency)

这个接口看起来简单,但包含了几个关键设计:请求参数有默认值、响应包含延迟信息、使用异步处理。这些细节在实际运行中很重要。

5. 监控与迭代:让系统在真实环境中活下来

系统上线只是开始,真正的挑战在上线之后。没有监控的AI系统就像没有仪表盘的汽车,你不知道它什么时候会出问题。

5.1 必须监控的核心指标

我通常把监控指标分成四类:

性能指标:请求延迟(P50、P95、P99)、吞吐量、错误率。这些是最基础的,能反映系统是否健康。

资源指标:GPU利用率、显存占用、CPU使用率、内存占用。这些能帮你判断是否需要扩容。

质量指标:模型输出的质量评分、用户反馈、异常输出比例。这些指标比较难量化,但对AI系统特别重要。

业务指标:请求量、活跃用户数、任务完成率。这些和业务直接挂钩。

监控工具上,Prometheus加Grafana是经典组合,开源免费,功能强大。日志收集可以用ELK或者Loki。如果不想自己搭,也可以用云服务商提供的监控方案。

5.2 日志记录的正确方式

日志不是越多越好,也不是越少越好。关键是记录对排查问题有用的信息。

我的日志里通常包含:请求ID(用于追踪)、时间戳、请求内容摘要、响应内容摘要、耗时、模型版本、错误信息(如果有)。

注意不要记录敏感信息,比如用户的隐私数据。如果必须记录,要做脱敏处理。

import logging import uuid logger = logging.getLogger(__name__) def log_request(prompt, response, latency, model_version): request_id = str(uuid.uuid4()) logger.info({ "request_id": request_id, "prompt_length": len(prompt), "response_length": len(response), "latency_ms": latency, "model_version": model_version }) return request_id

5.3 效果下降时怎么排查

模型效果下降是迟早会遇到的问题。排查时我按这个顺序来:

先看数据。输入数据的分布是不是变了?有没有出现之前没见过的类型?数据管道有没有出问题?

再看模型。模型版本有没有变?推理参数有没有被改动?有没有出现异常输出?

然后看服务。延迟是不是变高了?有没有超时?有没有降级?

最后看外部依赖。如果用了外部API,对方服务是不是有变化?网络是不是稳定?

这个排查顺序的逻辑是:从最可能变化的部分开始查。数据是最容易变的,模型和服务相对稳定,外部依赖不可控但影响明显。

5.4 迭代节奏的把控

AI系统的迭代不能太慢也不能太快。太慢跟不上需求变化,太快容易引入不稳定因素。

我的经验是:小步快跑,但每次变更都要可回滚。

具体做法是:每次只改一个变量,改完观察一段时间,确认没问题再改下一个。同时保留上一个版本的完整配置,出问题能快速切回去。

变更类型上,配置变更可以频繁一些,模型变更要谨慎,架构变更要非常谨慎。因为影响范围不同,风险也不同。

6. 从能跑到好用:几个提升工程质量的实操习惯

前面讲的都是“怎么让系统跑起来”,这一节讲“怎么让系统跑得好”。这些习惯不会让你的系统功能变多,但会让它更可靠、更好维护。

6.1 配置与代码分离

新手常犯的错是把配置写死在代码里。模型名称、API地址、超时时间、批处理大小,这些都应该放在配置文件里。

我用YAML格式的配置文件,结构清晰,支持注释。不同环境用不同的配置文件,通过环境变量指定加载哪个。

# configs/production.yaml model: name: "model_name" max_tokens: 512 temperature: 0.7 service: host: "0.0.0.0" port: 8000 timeout: 30 database: host: "localhost" port: 5432 name: "ai_service"

这样做的好处是:改配置不用改代码,不同环境用不同配置,配置可以纳入版本管理。

6.2 错误处理要区分类型

AI系统的错误类型比传统系统多。除了网络错误、超时错误,还有模型输出格式错误、内容过滤触发、资源不足等。

我的做法是定义一套错误码,不同类型的错误走不同的处理逻辑。比如:

  • 输入格式错误:返回400,提示用户修正
  • 模型超时:返回504,可以重试
  • 内容过滤:返回200但标记过滤原因
  • 资源不足:返回503,触发告警
class AIError(Exception): def __init__(self, code, message, retryable=False): self.code = code self.message = message self.retryable = retryable class ModelTimeoutError(AIError): def __init__(self): super().__init__(504, "Model inference timeout", retryable=True) class ContentFilteredError(AIError): def __init__(self, reason): super().__init__(200, f"Content filtered: {reason}", retryable=False)

区分错误类型的好处是:调用方知道什么情况该重试,什么情况该放弃;监控系统能按错误类型统计,快速定位问题。

6.3 测试不能只测正常路径

AI系统的测试比传统系统难,因为输出不是确定的。但有几类测试是必须做的:

单元测试覆盖数据处理、格式转换、错误处理这些确定性逻辑。

集成测试验证整条链路能跑通,从请求到响应。

回归测试用一组固定的输入,对比新旧版本的输出差异。这个不能保证输出完全一致,但能发现大的变化。

边界测试用极端输入,比如超长文本、空输入、特殊字符,看系统会不会崩。

def test_empty_input(): """测试空输入的处理""" result = process_input("") assert result.status == "error" assert "empty" in result.message.lower() def test_max_length_input(): """测试超长输入的处理""" long_text = "a" * 100000 result = process_input(long_text) assert result.status in ["success", "truncated"]

6.4 文档和注释的取舍

AI工程项目的文档,我重点写三部分:架构说明、接口文档、运维手册。

架构说明讲清楚系统由哪些模块组成、模块之间怎么交互、数据怎么流动。接口文档讲清楚每个接口的输入输出、错误码、调用示例。运维手册讲清楚怎么部署、怎么扩容、怎么排查常见问题。

代码注释我遵循一个原则:注释解释为什么,代码说明是什么。不要写“这行代码给变量加一”这种废话注释,要写“这里加一是因为索引从零开始,而业务逻辑从一开始”。

6.5 持续学习的方向

AI工程这个领域变化很快,新的模型、新的工具、新的方法层出不穷。但底层的东西变化没那么快:数据管道、服务架构、监控体系,这些核心能力是通用的。

我的学习策略是:花70%的时间巩固核心工程能力,20%的时间跟进新模型和新工具,10%的时间做实验性探索。

核心工程能力包括:系统设计、性能优化、可靠性工程、数据处理。这些能力不会因为模型换代而贬值。

新模型和新工具要保持关注,但不要盲目追新。等一个工具成熟了、社区验证过了再上手,能省很多时间。

最后分享一个我自己的习惯:每做一个项目,我都会写一份复盘文档,记录做了什么、遇到了什么问题、怎么解决的、下次怎么改进。这份文档不对外,但对我自己的成长帮助很大。AI工程是个实践性很强的领域,光看资料不动手,永远学不会。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 21:23:23

人脉脉动:基于SQLite与FastAPI的职场人脉管理工具设计与实现

做社交关系维护这件事&#xff0c;我以前一直靠通讯录和日历提醒硬撑。通讯录里存了上千个联系人&#xff0c;真正一年下来有过深度沟通的不到十分之一。日历提醒也是想起来就设一个&#xff0c;想不起来就算了&#xff0c;最后微信聊天记录里的“最近怎么样”都变成了群发模板…

作者头像 李华
网站建设 2026/10/2 21:22:42

从激光雷达到LOFIC纯视觉:智能驾驶传感器架构演进与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 21:21:24

超级智能(SI)简介:远超人类智慧水平的高阶智能系统

前沿技术探索&#xff1a;TVA智能体&#xff08;简称TVA&#xff09;TVA智能体&#xff08;亦称“AI智能体视觉”&#xff09;是依托Transformer架构与“因式智能体”理论构建的新型工业视觉系统&#xff0c;也是当前最具代表性的具身视觉技术之一。它有机融合深度强化学习&…

作者头像 李华
网站建设 2026/10/2 21:20:01

STM32嵌入式C++工程实战:CMake构建与Renode仿真从零到点灯

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 21:19:42

Hindsight:现代开发中被忽视的系统性认知陷阱

1. “Hindsight”不是工具名&#xff0c;而是开发者对技术债的集体自嘲最近在几个技术社区刷到“hindsight”这个词&#xff0c;高频出现在Python、npm、Docker和OpenAI相关讨论里——但它既不是PyPI上的包&#xff0c;也不是npm registry里的模块&#xff0c;更不是Docker Hub…

作者头像 李华
网站建设 2026/10/2 21:15:33

ATA SPEC 2000:航空维修结构化数据交换协议解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华