Gemini函数调用教程:Python Function Calling实战
技术背景
大型语言模型(LLM)虽能生成自然语言,但无法直接访问外部数据或执行真实操作。函数调用(Function Calling)允许开发者将自定义函数描述传递给模型,模型在需要时返回函数名和参数,由应用执行,从而将 LLM 与外部系统(如数据库、API、设备控制)连接。此技术广泛应用于智能助手、自动化工作流、信息检索等场景。Gemini API 提供两种交互方式:Interactions API(最新,需手动处理函数调用)和 ChatSession(支持自动函数执行)。主要限制包括:模型可能产生幻觉参数、函数描述需精确、复杂调用链可能增加延迟。
核心原理
函数调用的核心是让模型在生成文本时,根据用户输入和工具描述,决定是否调用函数。模型输出包含函数名和参数(JSON 格式),应用执行函数后,将结果以 FunctionResponse 形式返回给模型,模型再生成最终文本。数据流:用户输入 → 模型(结合工具描述)→ FunctionCall → 应用执行函数 → FunctionResponse → 模型 → 文本输出。组件包括:工具声明(FunctionDeclaration)、模型响应(FunctionCall)、执行器(应用代码)、结果回传(FunctionResponse)。关键设计决策:工具描述需包含函数名、描述、参数 schema,模型据此理解何时调用。Interactions API 通过 previous_interaction_id 维护多轮状态,而 ChatSession 自动管理上下文。
架构分析
系统架构分为三层:客户端层(Python SDK)、API 层(Interactions 或 generateContent)、模型层。客户端层提供两种接口:client.interactions.create 和 client.chats.create。Interactions API 采用无状态设计,通过 previous_interaction_id 串联请求,响应包含 steps 列表,每个 step 可能是 function_call、function_result 或 model_output。ChatSession 封装了自动函数调用,内部使用 generateContent,自动处理 FunctionCall 并执行对应 Python 函数。工具声明支持两种形式:字典(Interactions)和 Python 函数(ChatSession 自动生成模式)。设计思想:Interactions 提供细粒度控制,适合复杂工作流;ChatSession 简化常见用例,但灵活性较低。生产环境建议使用 Interactions 以更好地控制错误处理和验证。
完整实战教程
##### 版权所有 2026 Google LLC。
# @title 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:使用 Python 进行函数调用
函数调用让开发者可以在代码中创建函数的描述,然后在请求中将该描述传递给语言模型。模型的响应包括与描述匹配的函数名称以及调用该函数所需的参数。函数调用允许你在生成式 AI 应用中将函数用作工具,并且可以在单个请求中定义多个函数。
本笔记本提供了代码示例,帮助你快速上手。文档中的快速入门也是理解函数调用的好起点。
注意: 本笔记本使用 Interactions API,这是与 Gemini 模型交互的最新方式。正在寻找 generateContent 版本?请查看 归档分支。
设置
安装依赖
%pip install -U -q "google-genai>=2.9.0" # 2.0 for Interactions API
设置你的 API 密钥
要运行以下单元格,你的 API 密钥必须存储在名为 GEMINI_API_KEY 的 Colab 密钥中。如果你还没有 API 密钥,或者不确定如何创建 Colab 密钥,请参阅 身份验证 获取详细指南。
from google import genai
from google.colab import userdata
GEMINI_API_KEY = userdata.get("GEMINI_API_KEY")
client = genai.Client(api_key=GEMINI_API_KEY)
选择模型
函数调用适用于所有 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}
将函数设置为工具
要使用函数调用,在创建交互时将函数列表传递给 tools 参数。使用 Interactions API 时,工具以字典形式声明。SDK 也可以从 Python 函数自动生成工具模式。
def enable_lights():
"""Turn on the lighting system."""
print("LIGHTBOT: Lights enabled.")
def set_light_color(rgb_hex: str):
"""Set the light color. Lights must be enabled for this to work."""
print(f"LIGHTBOT: Lights set to {rgb_hex}.")
def stop_lights():
"""Stop flashing lights."""
print("LIGHTBOT: Lights turned off.")
# Map function names to callables for execution
light_functions = {
"enable_lights": enable_lights,
"set_light_color": set_light_color,
"stop_lights": stop_lights,
}
# For interactions.create, tools must be declared as dicts
light_controls = [
{
"type": "function",
"name": "enable_lights",
"description": "Turn on the lighting system.",
"parameters": {"type": "object", "properties": {}},
},
{
"type": "function",
"name": "set_light_color",
"description": "Set the light color. Lights must be enabled for this to work.",
"parameters": {
"type": "object",
"properties": {
"rgb_hex": {"type": "string", "description": "The color as an RGB hex string"}
},
"required": ["rgb_hex"],
},
},
{
"type": "function",
"name": "stop_lights",
"description": "Stop flashing lights.",
"parameters": {"type": "object", "properties": {}},
},
]
instruction = """
You are a helpful lighting system bot. You can turn
lights on and off, and you can set the color. Do not perform any
other tasks.
"""
多轮对话中的基础函数调用
函数调用自然适合多轮对话。使用 Interactions API,模型会返回 function_call 输出,你可以通过 function_result 输入来处理并响应这些输出。
注意: 自动函数调用(即 SDK 自动调用你的函数)是 client.chats.create 的功能。使用 interactions.create 时,你需要手动处理函数调用,这样可以获得更多控制权。
interactions.create的响应包含一个steps列表。对于文本响应,可以通过interaction.steps[-1].content[0].text访问输出。
# First turn: ask the model, providing tool declarations
interaction = client.interactions.create(
model=MODEL_ID,
input="It's awful dark in here...",
system_instruction=instruction,
tools=light_controls,
)
# Check if the model wants to call a function
for step in interaction.steps:
if step.type == "function_call":
print(f"Model wants to call: {step.name}({step.arguments})")
fn = light_functions[step.name]
result = fn(**step.arguments) if step.arguments else fn() if step.arguments else fn()
检查函数调用与执行历史
通过 Interactions API,对话状态由服务端通过 previous_interaction_id 进行管理。每个交互对象包含该轮次的输出,其中可能包括函数调用、函数结果或文本响应。
from IPython.display import Markdown, display
# Show the outputs of the last interaction
for step in interaction.steps:
print(f"Type: {step.type}")
if hasattr(step, 'content') and step.content and step.content[0].text:
print(f"Text: {step.content[0].text}")
这段历史展示了流程:
- 用户:发送消息。
- 模型:不返回文本,而是返回一个
FunctionCall,请求调用enable_lights。
- 用户(SDK):由于启用了
automatic_function_calling,ChatSession自动执行enable_lights()。它将结果作为FunctionResponse发送回去。
- 模型:使用函数的结果(“Lights enabled.”)来生成最终的文本响应。
自动函数执行(仅限 client.chats)
Python SDK 中的 ChatSession(即 client.chats.create)有一个强大的功能,称为自动函数执行。当启用时(默认启用),如果模型响应一个 FunctionCall,SDK 将:
- 在提供的
tools中找到对应的 Python 函数。
- 使用模型提供的参数执行该函数。
- 将函数的返回值以
FunctionResponse的形式发送回模型。
- 仅将模型的最终响应(通常是文本)返回给你的代码。
这大大简化了常见用例的工作流程。
注意: 自动函数执行仅适用于client.chats.create(其底层使用generateContent)。对于 Interactions API(client.interactions.create),你需要手动处理函数调用——请参阅下面的手动函数调用。
示例:数学运算
from google.genai import types
def add(a: float, b: float):
"""returns a + b."""
print(f" >> add({a}, {b})")
return a + b
def subtract(a: float, b: float):
"""returns a - b."""
print(f" >> subtract({a}, {b})")
return a - b
def multiply(a: float, b: float):
"""returns a * b."""
print(f" >> multiply({a}, {b})")
return a * b
def divide(a: float, b: float):
"""returns a / b."""
print(f" >> divide({a}, {b})")
if b == 0:
return "Cannot divide by zero."
return a / b
operation_tools = [add, subtract, multiply, divide]
chat = client.chats.create(
model=MODEL_ID,
config={
"tools": operation_tools,
"automatic_function_calling": {"disable": False}, # Enabled by default
},
)
response = chat.send_message(
"I have 57 cats, each owns 44 mittens, how many mittens is that in total?"
)
print(response.text)
# Print interaction outputs summary
for step in interaction.steps:
print(f"Type: {step.type}", end="")
if step.type == "model_output":
print(f" | {step.content[0].text[:100]}")
elif step.type == "function_call":
print(f" | {step.name}({step.arguments})")
else:
print()
自动执行无缝地处理了 multiply 调用。
自动函数模式声明
Python SDK 的一个关键便利之处在于,它能够根据你的 Python 函数自动生成所需的 FunctionDeclaration 模式。它会检查:
- 函数名称:(
func.__name__)
- 文档字符串:用于函数的描述。
- 参数:名称和类型注解(
int、str、float、bool、list、dict)。参数的文档字符串(如果使用特定格式,如 Google 风格)也可以增强描述。
- 返回类型注解:虽然模型在决定调用哪个函数时并不严格使用它,但这是一个好习惯。
当直接使用 Python 函数作为工具时,你通常不需要手动创建 FunctionDeclaration 对象。
然而,如果你需要检查、修改模式,或者在无法直接获得 Python 函数对象的情况下使用它,你可以使用 genai.types.FunctionDeclaration.from_callable 显式生成模式。
使用 .to_json_dict() 获取 JSON 表示形式——这是在使用 client.interactions.create 时作为工具传递的格式。
import json
set_color_declaration = types.FunctionDeclaration.from_callable(
callable = set_light_color,
client = client
)
print(json.dumps(set_color_declaration.to_json_dict(), indent=4))
<a name="manual_fc"></a>
手动函数调用
使用 Interactions API(client.interactions.create)时,函数调用始终是手动的——你会收到模型的函数调用请求,并自行处理。这让你对执行、错误处理和验证拥有完全的控制权。
Interactions API 不使用聊天会话,而是通过 previous_interaction_id 将请求串联起来,从而在多次交互中保持对话上下文。
手动工作流如下:
- 向
client.interactions.create发送带有工具声明的提示。 - 检查响应中的
steps是否有function_call条目。 - 在本地执行相应的函数。
- 使用
previous_interaction_id将结果发送回去以继续对话。
如果你使用 Chat 并设置 "automatic_function_calling": {"disable": True},情况也是如此。
示例:电影
以下示例大致相当于 Python 中的函数调用单轮 curl 示例。它使用返回(模拟)电影播放时间信息的函数,这些信息可能来自一个假设的 API:
def find_movies(description: str, location: str):
"""find movie titles currently playing in theaters."""
return ["Barbie", "Oppenheimer"]
def find_theaters(location: str, movie: str):
"""Find theaters showing a movie in a location."""
return ["Googleplex 16", "Android Theatre"]
def get_showtimes(location: str, movie: str, theater: str, date: str):
"""Find the start times for movies playing in a specific theater."""
return ["10:00", "11:00"]
# Map function names to callables
available_functions = {
"find_movies": find_movies,
"find_theaters": find_theaters,
"get_showtimes": get_showtimes,
}
# Dict-based tool declarations for interactions.create
theater_functions = [
{"type": "function", "name": "find_movies",
"description": "find movie titles currently playing in theaters based on any description, genre, title words, etc.",
"parameters": {"type": "object", "properties": {
"description": {"type": "string", "description": "Any kind of description including category or genre"},
"location": {"type": "string", "description": "The city and state, e.g. San Francisco, CA"}
}, "required": ["description", "location"]}},
{"type": "function", "name": "find_theaters",
"description": "Find theaters based on location and movie title.",
"parameters": {"type": "object", "properties": {
"location": {"type": "string"}, "movie": {"type": "string"}
}, "required": ["location", "movie"]}},
{"type": "function", "name": "get_showtimes",
"description": "Find the start times for movies playing in a specific theater.",
"parameters": {"type": "object", "properties": {
"location": {"type": "string"}, "movie": {"type": "string"},
"theater": {"type": "string"}, "date": {"type": "string"}
}, "required": ["location", "movie", "theater", "date"]}},
]
使用 interactions.create() 提问后,模型会请求一个 function_call:
interaction = client.interactions.create(
model=MODEL_ID,
input="Which theaters in Mountain View, CA show the Barbie movie?",
tools=theater_functions,
)
# Find the function call in the outputs
for step in interaction.steps:
if step.type == "function_call":
print(f"Function call: {step.name}")
print(f"Arguments: {step.arguments}")
function_call_step = step
break
由于这里没有使用带有自动函数调用的 ChatSession,你需要自己调用函数。
一个非常简单的方法是使用 if 语句:
if function_call.name == 'find_theaters':
find_theaters(**function_call.args)
elif ...
不过,既然你已经创建了 theater_functions 列表,这可以简化为:
def call_function(function_call_output, functions):
function_name = function_call_output.name
function_args = function_call_output.arguments
return functions[function_name](**function_args)
result = call_function(function_call_step, available_functions)
print(result)
最后,使用 previous_interaction_id 将函数结果传回,以继续对话并从模型获取最终的文本响应。
# Send the function result back to the model
interaction_2 = client.interactions.create(
model=MODEL_ID,
previous_interaction_id=interaction.id,
input={
"type": "function_result",
"name": function_call_step.name,
"call_id": function_call_step.id,
"result": str(result),
},
tools=theater_functions,
)
# Get the final text response
text_outputs = [o for o in interaction_2.steps if o.type == "text"]
if text_outputs:
print(text_outputs[-1].text)
这展示了手动工作流:调用、检查、执行、响应、再次调用。
并行函数调用
Gemini API 可以在单轮中调用多个函数。这适用于存在多个可以独立执行的函数调用以完成任务的场景。
首先设置工具。与上面的电影示例不同,这些函数不需要彼此提供输入即可被调用,因此它们是并行调用的理想候选。
def power_disco_ball(power: bool) -> bool:
"""Powers the spinning disco ball."""
print(f"Disco ball is {'spinning!' if power else 'stopped.'}")
return True
def start_music(energetic: bool, loud: bool, bpm: int) -> str:
"""Play some music matching the specified parameters."""
print(f"Starting music! {energetic=} {loud=}, {bpm=}")
return "Never gonna give you up."
def dim_lights(brightness: float) -> bool:
"""Dim the lights."""
print(f"Lights are now set to {brightness:.0%}")
return True
house_functions = {
"power_disco_ball": power_disco_ball,
"start_music": start_music,
"dim_lights": dim_lights,
}
house_fns = [
{"type": "function", "name": "power_disco_ball", "description": "Powers the spinning disco ball.",
"parameters": {"type": "object", "properties": {"power": {"type": "boolean"}}, "required": ["power"]}},
{"type": "function", "name": "start_music", "description": "Play some music matching the specified parameters.",
"parameters": {"type": "object", "properties": {
"energetic": {"type": "boolean"}, "loud": {"type": "boolean"}, "bpm": {"type": "integer"}
}, "required": ["energetic", "loud", "bpm"]}},
{"type": "function", "name": "dim_lights", "description": "Dim the lights.",
"parameters": {"type": "object", "properties": {"brightness": {"type": "number"}}, "required": ["brightness"]}},
]
现在调用模型,并给出一个可以使用所有指定工具的指令。
# Use "any" mode via tool_config is not yet supported in Interactions API.
# Instead, we make a direct call and handle functions manually.
interaction = client.interactions.create(
model=MODEL_ID,
input="Turn this place into a party!",
tools=house_fns,
)
prev_id = interaction.id
while any(s.type == "function_call" for s in interaction.steps):
results = []
for step in interaction.steps:
if step.type == "function_call":
print(f"Calling: {step.name}({step.arguments})")
fn = house_functions[step.name]
result = fn(**step.arguments) if step.arguments else fn() if step.arguments else fn()
results.append({
"type": "function_result",
"name": step.name,
"call_id": step.id,
"result": str(result),
})
interaction = client.interactions.create(
model=MODEL_ID,
input=results,
previous_interaction_id=prev_id,
tools=house_fns,
)
prev_id = interaction.id
for step in interaction.steps:
if step.type == "model_output":
print(f"\nModel: {step.content[0].text}")
注意,单次模型回合包含三个 FunctionCall 部分,SDK 在执行这些调用后才获得最终的文本响应。
组合式函数调用
模型可以在多轮对话中链式调用函数,利用一次调用的结果来指导下一次调用。这支持复杂的多步骤推理和任务完成。
示例:查找特定电影场次
让我们复用 theater_functions,并提出一个更复杂的查询,该查询需要先查找电影,然后可能查找影院,最后查找场次。
interaction = client.interactions.create(
model=MODEL_ID,
input="""
Find comedy movies playing in Mountain View, CA on 01/01/2025.
First, find the movie titles.
Then, find the theaters showing those movies.
Finally, find the showtimes for each movie at each theater.
""",
tools=theater_functions,
)
prev_id = interaction.id
while any(s.type == "function_call" for s in interaction.steps):
results = []
for step in interaction.steps:
if step.type == "function_call":
print(f"Calling: {step.name}({step.arguments})")
fn = available_functions[step.name]
result = fn(**step.arguments) if step.arguments else fn()
results.append({
"type": "function_result",
"name": step.name,
"call_id": step.id,
"result": str(result),
})
interaction = client.interactions.create(
model=MODEL_ID,
input=results,
previous_interaction_id=prev_id,
tools=theater_functions,
)
prev_id = interaction.id
for step in interaction.steps:
if step.type == "model_output":
print(step.content[0].text)
在这里你可以看到,模型为了回答你的问题进行了七次调用,并在后续调用和最终答案中使用了这些调用的输出。
使用模式配置函数调用
注意:tool_config模式(AUTO、ANY、NONE)是generateContentAPI 的功能。它们尚不可用于 Interactions API。下面的示例使用client.interactions.create来展示等效的变通方法。
使用 generateContent,您可以通过 tool_config 参数精确控制模型何时以及可以调用哪些函数。
tool_config 接受一个 ToolConfig 对象,其中包含一个 FunctionCallingConfig,具有两个主要字段:
mode:控制整体函数调用行为(AUTO、ANY、NONE)。
allowed_function_names:一个可选的函数名称列表,模型在此轮中只能调用这些函数。
以下是每种模式的工作原理以及如何使用 Interactions API 实现类似行为:
AUTO(默认模式)
generateContent行为:模型自行决定是返回文本还是调用所提供的tools中的一个或多个函数。这是最灵活的模式。
- Interactions API 等价物:这是默认行为——当你将
tools传递给interactions.create时,模型会自然地决定是否调用它们。
# In Interactions API, there is no "auto" mode config.
# The model will naturally decide whether to call tools.
interaction = client.interactions.create(
model=MODEL_ID,
input="Turn on the lights!",
system_instruction=instruction,
tools=light_controls,
)
for step in interaction.steps:
if step.type == "function_call":
print(f"Function call: {step.name}({step.arguments})")
fn = light_functions[step.name]
result = fn(**step.arguments) if step.arguments else fn() if step.arguments else fn()
elif step.type == "model_output":
print(f"Model: {step.content[0].text}")
NONE 模式
generateContent行为:模型被明确禁止调用任何函数,即使提供了工具也是如此。它只会以文本形式响应。
- Interactions API 等效:只需在调用时不传递任何
tools即可。
# "none" mode equivalent: don't pass any tools
interaction = client.interactions.create(
model=MODEL_ID,
input="Hello light-bot, what can you do?",
system_instruction=instruction,
# No tools provided = model can't call functions
)
for step in interaction.steps:
if step.type == "model_output":
print(f"Model: {step.content[0].text}")
ANY 模式
generateContent行为:强制模型至少调用一个函数。
- 如果设置了
allowed_function_names,模型必须从该列表中选择。
- 如果未设置
allowed_function_names,模型必须从完整工具列表中选择。
- Interactions API 等效:目前没有直接等效项。传入工具并依赖模型的判断。你可以通过系统指令或提示措辞鼓励函数调用。
以下是一个使用 tool_config 的 generateContent API 示例:
from google.genai import types
response = client.models.generate_content(
model=MODEL_ID,
contents="Make this place PURPLE!",
config=types.GenerateContentConfig(
system_instruction=instruction,
tools=light_controls,
tool_config=types.ToolConfig(
function_calling_config=types.FunctionCallingConfig(
mode="ANY",
),
),
),
)
使用 Interactions API 时,你只需传入工具并手动处理调用:
# Force function calling by providing tools
interaction = client.interactions.create(
model=MODEL_ID,
input="Make this place PURPLE!",
system_instruction=instruction,
tools=light_controls,
)
# Manual function execution loop
prev_id = interaction.id
while any(s.type == "function_call" for s in interaction.steps):
results = []
for step in interaction.steps:
if step.type == "function_call":
print(f"Calling: {step.name}({step.arguments})")
fn = light_functions[step.name]
result = fn(**step.arguments) if step.arguments else fn() if step.arguments else fn()
results.append({
"type": "function_result",
"name": step.name,
"call_id": step.id,
"result": str(result),
})
interaction = client.interactions.create(
model=MODEL_ID,
input=results,
previous_interaction_id=prev_id,
tools=light_controls,
)
prev_id = interaction.id
for step in interaction.steps:
if step.type == "model_output":
print(f"Model: {step.content[0].text}")
后续步骤
有用的 API 参考:
- genai.Client 类
- 其
client.interactions.create方法接受tools作为函数声明或 Python 可调用对象的列表。 - 其
client.chats.create方法通过automatic_function_calling配置支持自动函数调用。 - 使用 Interactions API,响应包含
steps。类型为function_call的步骤具有.name、.arguments和.id属性。使用previous_interaction_id和function_result输入返回结果。 - 使用
generateContent,响应的 candidate 的 content 的 parts 可能包含 genai.types.FunctionCall。 - 对于 FunctionCall,模型总是期望一个 FunctionResponse。
相关示例
查看这些使用函数调用的示例,为您提供更多关于如何使用这一非常有用功能的思路:
* Barista Bot,一个用于订购咖啡的代理
* Browser-as-a-tool,使用函数调用调用 Web 浏览器。
* 使用函数调用重新排序搜索结果。
* 将工具与 Live API 结合使用,将函数调用和其他工具与 Live API 结合使用。
继续探索 Gemini API
在 函数调用配置 快速入门中了解如何控制 Gemini API 与您的函数的交互方式,在 JSON 或使用 Enum 中了解如何控制模型输出,或了解 Gemini API 如何使用 代码执行 自行生成和运行代码。
常见问题
常见错误:1) API 密钥未设置或无效,导致认证失败;2) 工具声明格式错误,如缺少 required 字段;3) 函数参数类型不匹配,模型返回的参数无法直接解包;4) 未处理 function_call 步骤,导致对话中断;5) 在 Interactions API 中误用自动函数调用。排查方法:检查响应 steps 类型,打印 function_call 的 name 和 arguments;验证函数映射字典键与声明名称一致;使用 try-except 捕获参数解析错误;确保 previous_interaction_id 正确传递。生产优化:使用缓存减少重复调用;设置超时和重试机制;对函数结果进行验证和清洗;监控函数调用频率和延迟;考虑使用异步执行提升吞吐量。
企业应用扩展
可扩展方向:1) 将函数调用与外部 API 集成,如数据库查询、REST 服务;2) 实现多代理系统,每个代理负责特定工具集;3) 结合流式输出,实时展示函数调用过程;4) 使用函数调用实现复杂任务分解,如研究助手、代码生成器;5) 探索 Live API 与函数调用的结合,实现实时交互。参考官方示例:Barista Bot(对话式代理)、Browser-as-a-tool(浏览器控制)、Search reranking(搜索重排)。
总结
本文深入讲解 Gemini API 的函数调用功能,涵盖 Interactions API 与 ChatSession 两种方式。通过灯光控制、数学运算、电影查询等示例,展示如何声明工具、处理函数调用、实现自动与手动执行、并行与组合调用,并探讨模式配置。面向有编程基础的开发者,提供环境搭建、代码分析、常见错误排查及生产优化建议,帮助构建可靠的 AI 应用。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:S_CANDIDATE
- 难度:中级
- 验证等级:V1
- 原文作者:
- 原文日期:
- 来源许可:Apache-2.0
- 来源版本:
ae43652870b4fe7694e09147febfee706f7915c7 - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员