OpenAI 兼容性快速入门指南

本教程面向有编程基础的开发者,系统讲解如何通过 OpenAI Python 库调用 Gemini API,实现文本生成、多模态理解、结构化输出、函数调用、思考控制、批量预测与嵌入生成。内容涵盖环境配置、核心原理、架构设计、代码解析、常见错误排查及生产优化,帮助开发者快速掌握跨平台 AI 工程实践。

技术背景

随着大语言模型(LLM)的普及,开发者常面临多模型切换的痛点。OpenAI 的 API 已成为事实标准,但不同厂商的 SDK 差异增加了学习成本。Gemini API 提供 OpenAI 兼容层,使开发者无需重写代码即可复用现有 OpenAI 工具链,降低迁移门槛。该兼容层适用于需要快速原型验证、多模型对比、或已有 OpenAI 代码库的团队。主要限制包括:不支持视频输入、部分高级功能(如流式、微调)可能受限,且需注意模型标识符与 OpenAI 的差异。

核心原理

OpenAI 兼容层本质上是将 OpenAI 的请求格式(如 /v1/chat/completions)映射到 Gemini API 的底层接口。客户端通过设置 base_url 指向 Google 的端点,发送标准 OpenAI 结构(如 messages、tools、response_format),服务端将其转换为 Gemini 的生成请求。响应也转换为 OpenAI 格式,确保客户端无缝解析。核心数据流:客户端构造请求 → HTTP 发送至兼容端点 → 服务端解析并调用 Gemini 模型 → 返回 OpenAI 格式响应。关键设计决策包括:使用 base64 编码内联图像/音频,通过 response_format 实现结构化输出,利用 tools 参数触发函数调用,以及通过 reasoning_effort 映射思考预算。

架构分析

整体架构分为三层:客户端层(OpenAI SDK)、兼容层(Google 的 OpenAI 兼容端点)、模型层(Gemini 模型)。客户端层负责请求构建与响应解析,兼容层负责协议转换与认证,模型层执行推理。设计上,兼容层复用了 OpenAI 的 API 语义,但内部调用 Gemini 的生成接口,并处理多模态数据的编码转换。对于批量任务,需借助 Google GenAI SDK 上传文件,因为 OpenAI 文件 API 尚未支持。嵌入生成则直接映射到 Gemini 的嵌入模型。该架构的优点是低迁移成本,但牺牲了部分 Gemini 特有功能(如视频、流式)。

完整实战教程

##### 版权所有 2026 Google LLC.

# @title Licensed under the Apache License, Version 2.0 (the "License");
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Gemini API OpenAI 兼容性入门

<a target="_blank" href="https://colab.research.google.com/github/google-gemini/cookbook/blob/main/quickstarts/Get_started_OpenAI_Compatibility.ipynb">

</a>

此示例演示了如何使用 OpenAI Python 库 与 Gemini API 进行交互。

本笔记本将引导您完成:

* 使用 Gemini 模型通过 OpenAI 库执行基本文本生成

* 尝试多模态交互,在提示中发送图像

* 使用结构化输出(即特定字段或 JSON 输出)从文本中提取信息

* 使用 Gemini API 工具,如函数调用

* 使用 Gemini API 模型生成嵌入

有关此 OpenAI 兼容性的更多详细信息,请参阅 文档。

设置

安装所需模块

在运行此笔记本时,您需要安装以下依赖项:

%pip install -U -q openai pillow pdf2image pdfminer.six
!apt -qq -y install poppler-utils # required by pdfminer

获取你的 Gemini API 密钥

你需要 Gemini API 密钥才能完成本笔记本中的活动。你可以在 获取 API 密钥 AI Studio 页面生成一个新的密钥。

from openai import OpenAI

try:
  # if you are running the notebook on Google Colab
  # and if you have saved your API key in the
  # Colab secrets
  from google.colab import userdata

  GEMINI_API_KEY = userdata.get('GEMINI_API_KEY')

except:
  # enter manually your API key here if you are not using Google Colab
  GEMINI_API_KEY = "--enter-your-API-key-here--"

# OpenAI client
client = OpenAI(
    api_key=GEMINI_API_KEY,
    base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
)

定义要使用的 Gemini 模型

你可以先使用 OpenAI 库列出可用的模型。

models = client.models.list()
for model in models:
  if 'gemini-2' in model.id:
    print(model.id)

定义要使用的 Gemini 模型

在本示例中,你将使用 gemini-3.7-flash 模型。有关可用模型的更多详细信息,请参阅 Gemini API 文档中的 Gemini 模型 页面。

MODEL_ID = "gemini-3.7-flash" # @param ["gemini-3.1-pro-preview", "gemini-3.7-flash", "gemini-3.5-flash-lite", "gemini-2.5-pro"] {"allow-input":true, isTemplate: true}

初始交互 - 生成文本

对于您的第一个请求,请使用 OpenAI SDK 通过文本提示执行文本生成。

from IPython.display import Markdown

prompt = "What is generative AI?" # @param

response = client.chat.completions.create(
  model=MODEL_ID,
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {
      "role": "user",
      "content": prompt
    }
  ]
)

Markdown(response.choices[0].message.content)

生成代码

你可以使用 Gemini API 为你生成代码。

prompt = """
    Write a C program that takes two IP addresses, representing the start and end of a range
    (e.g., 192.168.1.1 and 192.168.1.254), as input arguments. The program should convert this
    IP address range into the minimal set of CIDR notations that completely cover the given
    range. The output should be a comma-separated list of CIDR blocks.
"""

response = client.chat.completions.create(
    model=MODEL_ID,
    messages=[
        {
            "role": "user",
            "content": prompt
        }
    ]
)

Markdown(response.choices[0].message.content)

多模态交互

Gemini 模型能够处理不同的数据模态,例如非结构化文件、图像、音频和视频,使您能够试验多模态场景,在这些场景中,您可以要求模型描述、解释、获取见解或从提示中包含的多媒体信息中提取信息。在本节中,您将处理涉及多媒体信息的不同场景。

重要提示: OpenAI SDK 兼容性仅支持内联图像和音频文件。对于视频支持,请使用 Gemini API 的 Python SDK。

处理图像(单张图像)

首先,你需要下载想要处理的图像。

from PIL import Image as PImage


# define the image you want to download
image_url = "https://storage.googleapis.com/generativeai-downloads/images/Japanese_Bento.png" # @param
image_filename = image_url.split("/")[-1]

# download the image
!wget -q $image_url

# visualize the downloaded image
im = PImage.open(image_filename)
im.thumbnail([620,620], PImage.Resampling.LANCZOS)
im

现在你可以对图像进行编码,并使用 OpenAI 库与 Gemini 模型进行交互。

import base64
import requests


# define a helper function to encode the images in base64 format
def encode_image(image_path):
  image = requests.get(image_path)
  return base64.b64encode(image.content).decode('utf-8')

# Getting the base64 encoding
encoded_image = encode_image(image_url)

response = client.chat.completions.create(
  model=MODEL_ID,
  messages=[
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Describe the items on this image. If there is any non-English text, translate it as well"
        },
        {
          "type": "image_url",
          "image_url": {
            "url": f"data:image/png;base64,{encoded_image}",
          },
        },
      ],
    }
  ]
)

Markdown(response.choices[0].message.content)

处理图像(多张图像)

你可以在同一个提示中发送多张图像,并执行相同的处理流程。

import matplotlib.pyplot as plt
from IPython.display import display, Image
from matplotlib.pyplot import imread


# define the images you want to download
image_urls = [
    "https://storage.googleapis.com/github-repo/img/gemini/retail-recommendations/furnitures/cesar-couto-OB2F6CsMva8-unsplash.jpg",
    "https://storage.googleapis.com/github-repo/img/gemini/retail-recommendations/furnitures/daniil-silantev-1P6AnKDw6S8-unsplash.jpg",
    "https://storage.googleapis.com/github-repo/img/gemini/retail-recommendations/furnitures/ruslan-bardash-4kTbAMRAHtQ-unsplash.jpg",
    "https://storage.googleapis.com/github-repo/img/gemini/retail-recommendations/furnitures/scopic-ltd-NLlWwR4d3qU-unsplash.jpg",
]

for url in image_urls:
    display(Image(url=url, width=200, height=250))

现在你可以对图像进行编码,并将其与提示一起发送。

import base64
import requests


# define a helper function to encode the images in base64 format
def encode_image(image_path):
  image = requests.get(image_path)
  return base64.b64encode(image.content).decode('utf-8')


# Getting the base64 encoding
encoded_images =[]
for image in image_urls:
  encoded_images.append(encode_image(image))

response = client.chat.completions.create(
  model=MODEL_ID,
  messages=[
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Describe for what type of living room each of those items are the best match"
        },
        *[{
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_data}",
                    },
        }
                for image_data in encoded_images]
      ],
    }
  ]
)

Markdown(response.choices[0].message.content)

处理音频文件

你也可以在提示中发送音频文件。音频数据比纯文本提供更丰富的输入,可用于转录等任务,或像语音助手一样直接作为提示。

首先,你需要下载要使用的音频。

from IPython.display import display, Audio


audio_url = "https://storage.googleapis.com/generativeai-downloads/data/Apollo-11_Day-01-Highlights-10s.mp3" # @param
audio_filename = audio_url.split("/")[-1]

# download the audio
!wget -q $audio_url

# listen to the downloaded audio
display(Audio(audio_filename, autoplay=False))

现在,你将音频编码为 base64,并将其作为请求提示的一部分发送。

# define a helper function to encode the images in base64 format
def encode_audio(audio_path):
  with open(audio_path, 'rb') as audio_file:
    audio_content = audio_file.read()
    return base64.b64encode(audio_content).decode('utf-8')

base64_audio = encode_audio(audio_filename)

prompt = "Transcribe this audio file. After transcribing, tell me from what this can be related to." # @param
response = client.chat.completions.create(
    model=MODEL_ID,
    messages=[
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": prompt,
        },
        {
              "type": "input_audio",
              "input_audio": {
                "data": base64_audio,
                "format": "mp3"
          }
        }
      ],
    }
  ],
)

Markdown(response.choices[0].message.content)

结构化输出

Gemini API 允许你通过结构化输出来格式化响应的生成方式。你可以定义要使用的结构作为定义的架构,并使用 OpenAI 库将此结构作为 response_format 参数发送。

在此示例中,你将:

首先,你需要下载参考论文。你将使用引入了 Transformers 架构) 的 Google 论文 Attention is all you need。

from IPython.display import Image
from pdf2image import convert_from_path


# download the PDF file
pdf_url = "https://arxiv.org/pdf/1706.03762.pdf" # @param
pdf_filename = pdf_url.split("/")[-1]
!wget -q $pdf_url

## visualize the pdf as an image
# convert the PDF file to images
images = convert_from_path(pdf_filename, 200)
for image in images:
    image.save('cover.png', "PNG")
    break

# show the pdf first page
Image('cover.png', width=500, height=600)

现在你将创建你的参考结构。它将是一个Python Class,用于引用论文的标题、作者、摘要和关键词。

from pydantic import BaseModel


class ResearchPaperExtraction(BaseModel):
    title: str
    authors: list[str]
    abstract: str
    keywords: list[str]

现在,你将向Gemini API发送请求,提交PDF文件和参考结构。

import json
from pdfminer.high_level import extract_text


# extract text from the PDF
pdf_text = extract_text(pdf_filename)

prompt = """
    As a specialist in knowledge organization and data refinement, your task is to transform
    raw research paper content into a clearly defined structured format. I will provide you
    with the original, free-form text. Your goal is to parse this text, extract the pertinent
    information, and reconstruct it according to the structure outlined below.
"""

# send your request to the Gemini API
completion = client.beta.chat.completions.parse(
  model=MODEL_ID,
  messages=[
    {"role": "system", "content": prompt},
    {"role": "user", "content": pdf_text}
  ],
  response_format=ResearchPaperExtraction,
)

print(completion.choices[0].message.parsed.model_dump_json(indent=2))

鉴于 Gemini API 能够处理结构化输出,你也可以应对更复杂的场景——比如利用结构化输出功能来帮助你生成用户界面。

首先,你定义代表输出所需结构的 Python 类。

from enum import Enum


class UIType(str, Enum):
    div = "div"
    button = "button"
    header = "header"
    section = "section"
    field = "field"
    form = "form"

class Attribute(BaseModel):
    name: str
    value: str

class UI(BaseModel):
    type: UIType
    label: str
    children: list[str]
    attributes: list[Attribute]

UI.model_rebuild() # This is required to enable recursive types

class Response(BaseModel):
    ui: UI

现在,你使用 Response 类作为 response_format 来发送请求。

completion = client.beta.chat.completions.parse(
    model=MODEL_ID,
    messages=[
        {"role": "system", "content": "You are a UI generation assistant. Convert the user input into a UI."},
        {"role": "user", "content": "Make a User Profile Form including all required attributes"}
    ],
    response_format=Response,
)

print(completion.choices[0].message.content)

使用 Gemini API 函数调用进行开发

Gemini API 的函数调用功能允许你通过提供外部函数或 API 的描述来扩展模型的能力。

要进一步了解函数调用如何与 Gemini 模型配合使用,请查看 Gemini API 文档。

tools = [
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Gets the weather at the user's location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {"type": "string"},
        },
      },
    },
  }
]

现在,你在请求中添加 tools 结构。

prompt = """
    What's the weather like in Boston?
"""

completion = client.chat.completions.create(
  model=MODEL_ID,
  messages=[{"role": "user", "content": prompt}],
  tools=tools,
)

print(completion.choices[0].message.tool_calls[0])

思考

Gemini 2.5 和 3 模型经过训练,能够对复杂问题进行思考,从而显著提升推理能力。Gemini API 提供了 "思考预算"参数,可以精细控制模型的思考量。

与 Gemini API 不同,OpenAI API 提供三个级别的思考控制:"低"、"中"和"高"。对应关系如下:

| OpenAI <br> 推理力度 | Gemini 2.5 <br> 思考预算 | Gemini 3 Pro <br> 思考级别 |

| --------| ----------| ----------- |

| none | 0* | 不适用 |

| minimal | 1024 | 低 |

| low | 1024 | 低 |

| medium | 8192 | 高 |

| high | 24576 | 高 |

如果你想禁用思考,可以将推理力度设置为"none",但请注意,Pro 模型无法关闭思考功能。

prompt = """
    What is 45-78+5x13?
    Double check and explain why your answer is correct.
"""

response = client.chat.completions.create(
  model=MODEL_ID,
  reasoning_effort="low",
  messages=[
      {"role": "system", "content": "You are a helpful assistant."},
      {
        "role": "user",
        "content": prompt
      }
  ]
)

Markdown(response.choices[0].message.content)

批量预测

除了实时生成之外,Gemini API 还提供了一个兼容层,用于执行批量生成。

准备一个采用 OpenAI 批量输入格式的 JSONL 文件。请注意,URL 和整体结构使用 OpenAI 语法,但模型是 Gemini API 模型标识符。

%%writefile batch_requests.jsonl
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gemini-3.7-flash", "messages": [{"role": "user", "content": "Tell me a one-sentence joke."}]}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gemini-3.7-flash", "messages": [{"role": "user", "content": "Why is the sky blue?"}]}}

现在使用 GenAI SDK 上传 JSONL 文件。在支持 OpenAI 文件上传 API 之前,您必须使用 Google GenAI SDK 上传文件,以便将数据提供给 Gemini API。

%pip install -qU google-genai
# Upload JSONL file in OpenAI batch input format...
from google import genai
from google.genai import types

genai_client = genai.Client(api_key=GEMINI_API_KEY)

uploaded_file = genai_client.files.upload(
    file="batch_requests.jsonl",
    config=types.UploadFileConfig(display_name="my-batch-requests", mime_type="jsonl"),
)
print(f'{uploaded_file.name=}')

创建一个批量生成任务,引用刚刚上传的文件。

batch = client.batches.create(
    input_file_id=uploaded_file.name,
    endpoint="/v1/chat/completions",
    completion_window="24h"
)
print(f'{batch.id=}')

批次处理最多可能需要24小时。您可以使用以下代码在此处轮询状态,或者稍后返回,并将上面创建时打印的ID替换为batch.id。

import time

while (batch := client.batches.retrieve(batch.id)).status == 'in_progress':
    print(f"Job not finished. Current state: {batch.status}. Waiting 30 seconds...")
    time.sleep(30)

print(f'{batch.status=}')

批次处理完成后,下载结果并打印出来。

if batch.status == 'completed':
    # Download the output file.
    file_content_bytes = genai_client.files.download(file=batch.output_file_id)
    file_content = file_content_bytes.decode('utf-8')

    # Print each output record.
    for i, line in enumerate(file_content.splitlines(), start=1):
            print(i, line)

else:
    print(f'An error occurred. Batch status is "{batch.status}".')

生成和使用嵌入

文本嵌入提供了一种基于向量的文本压缩表示,通常位于较低维度的空间中。其核心原理是,语义相似的文本在嵌入向量空间中的位置会彼此接近。这种表示方式使得解决多种常见的自然语言处理问题成为可能,包括:

有关使用 Gemini API 和嵌入的更多详细信息,请参阅 API 文档。

在本示例中,您将使用 Gemini API 中的 gemini-embedding-001 模型来生成嵌入。

EMBEDDINGS_MODEL="gemini-embedding-001"
prompt = """
    The quick brown fox jumps over the lazy dog.
"""

response = client.embeddings.create(
  model=EMBEDDINGS_MODEL,
  input=prompt,
)

print(len(response.data[0].embedding))
print(response.data[0].embedding[:4], '...')

文本嵌入的一个简单应用是计算句子之间的相似度(例如产品评论、文档内容等)。首先,你需要创建一组句子。

import pandas as pd


text = [
    "i really enjoyed the movie last night",
    "so many amazing cinematic scenes yesterday",
    "had a great time writing my Python scripts a few days ago",
    "huge sense of relief when my .py script finally ran without error",
    "O Romeo, Romeo, wherefore art thou Romeo?",
]

df = pd.DataFrame(text, columns=["text"])
df

现在你可以创建一个函数来生成嵌入,将该函数应用于数据框的 text 列,并将其保存到一个名为 embeddings 的新列中。

def generate_embeddings(text):
  response = client.embeddings.create(
    model=EMBEDDINGS_MODEL,
    input=text,
  )
  return response.data[0].embedding

df["embeddings"] = df.apply(
    lambda x: generate_embeddings([x.text]), axis=1
)
df

现在你已经获得了所有句子的嵌入表示,就可以计算它们之间的相似度了。

from sklearn.metrics.pairwise import cosine_similarity

cos_sim_array = cosine_similarity(list(df.embeddings.values))

# display as DataFrame
analysis = pd.DataFrame(cos_sim_array, index=text, columns=text)
analysis

你也可以将其绘制出来,以便更好地可视化。

import seaborn as sns

ax = sns.heatmap(analysis, annot=True, cmap="Blues")
ax.xaxis.tick_top()
ax.set_xticklabels(text, rotation=90)

批量生成嵌入

您可以使用 OpenAI SDK 离线批量生成嵌入,并以较低费率处理大量请求。API 遵循与生成内容相同的步骤。

首先创建一个 JSONL 文件,其中包含要处理的每个嵌入请求。

%%writefile embedding_requests.jsonl
{"custom_id": "request-1", "method": "POST", "url": "/v1/embeddings", "body": {"model": "gemini-embedding-001", "input": "I really enjoyed the movie last night"}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/embeddings", "body": {"model": "gemini-embedding-001", "input": "So many amazing cinematic scenes yesterday"}}

上传包含嵌入请求的 JSONL 文件。在 OpenAI 文件上传 API 得到支持之前,您必须使用 Google GenAI SDK 上传文件,并使数据可供 Gemini API 使用。

from google import genai
from google.genai import types

genai_client = genai.Client(api_key=GEMINI_API_KEY)

uploaded_file = genai_client.files.upload(
    file="embedding_requests.jsonl",
    config=types.UploadFileConfig(display_name="My embedding requests", mime_type="jsonl"),
)
print(f'{uploaded_file.name=}')

现在,文件已上传,创建一个新的批处理作业来处理 JSONL 文件中的请求。

batch = client.batches.create(
    input_file_id=uploaded_file.name,
    endpoint="/v1/embeddings",
    completion_window="24h"
)
print(f'{batch.id=}')
if batch.status == 'completed':
    # Download the output file.
    file_content_bytes = genai_client.files.download(file=batch.output_file_id)
    file_content = file_content_bytes.decode('utf-8')

    # Print each output record.
    for i, line in enumerate(file_content.splitlines(), start=1):
            print(i, line[:100] + '...')

else:
    print(f'An error occurred. Batch status is "{batch.status}".')

后续步骤

更深入地使用 Gemini

如果你想使用更多 Gemini 的功能,尤其是那些通过 OpenAI 兼容接口无法获得的独特能力,你应该查看 Google GenAI SDK。

Cookbook 中有大量关于如何使用它的示例,但建议从 快速入门 开始。

image](./Get_started.ipynb) 笔记本,以了解所有模型和 SDK 的功能。

相关示例

查看 Cookbook 的其余部分。您将学习如何使用 [Live API

image(此处应继续翻译,但原输入似乎不完整,仅包含一个链接和短语。根据要求,我应翻译该部分。原文为:

](./Get_started_LiveAPI.ipynb), juggle with [multiple tools

翻译为:

](./Get_started_LiveAPI.ipynb),并灵活运用[多种工具

但为了符合JSON格式,我输出完整内容。由于没有更多上下文,我仅翻译给出的部分。

image](../examples/LiveAPI_plotting_and_mapping.ipynb) 或使用 Gemini 的 [空间理解能力

image](./Spatial_understanding.ipynb) 能力。

另请参阅 [思考指南

image](./Get_started_thinking.ipynb) 的笔记本,它明确展示了其思考过程,并能处理更复杂的推理。

常见问题

常见错误:1) API 密钥无效或未设置,导致 401 错误,需检查环境变量或 Colab Secrets。2) 模型 ID 拼写错误,应使用 client.models.list() 验证可用模型。3) 多模态请求格式错误,确保图像使用 data:image/png;base64 前缀,音频使用 input_audio 类型。4) 结构化输出时 Pydantic 模型定义错误,需确保字段类型正确,递归模型需调用 model_rebuild()。5) 批量任务状态卡在 in_progress,需耐心等待或检查文件格式。排查方法:打印响应对象、检查 HTTP 状态码、使用官方文档对照。生产优化:启用请求重试、使用连接池、批量处理嵌入以降低费率、缓存嵌入结果、监控 token 消耗。

企业应用扩展

本教程展示了 OpenAI 兼容层的基础用法,开发者可进一步探索:1) 结合 LangChain 或 LlamaIndex 构建复杂 Agent 工作流。2) 利用 Gemini 的独特能力(如视频理解、长上下文)时,切换至原生 GenAI SDK。3) 实现流式输出以提升交互体验。4) 将函数调用与外部 API 集成,实现自动化任务。5) 使用嵌入进行语义搜索、推荐系统等下游应用。6) 关注 Google 官方 Cookbook 获取更多实战案例。

总结

本教程面向有编程基础的开发者,系统讲解如何通过 OpenAI Python 库调用 Gemini API,实现文本生成、多模态理解、结构化输出、函数调用、思考控制、批量预测与嵌入生成。内容涵盖环境配置、核心原理、架构设计、代码解析、常见错误排查及生产优化,帮助开发者快速掌握跨平台 AI 工程实践。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部API 与工程实践教程