资讯动态

基于OpenAI Responses API的AI应用开发:从工具调用到联网搜索

发布时间:2026/8/15 4:16:57 来源:尧图企业网站定制
1. 项目概述与核心价值如果你和我一样经常在Cursor里折腾各种AI驱动的Python小项目那你肯定遇到过这个场景想快速验证一个结合了联网搜索和工具调用的AI应用想法结果光是搭建基础框架、处理API响应、管理对话状态就耗去大半天。每次都是从零开始写openai库的调用手动拼接messages列表再写一堆if-else来处理函数调用结果既繁琐又容易出错。最近在GitHub上发现了一个名为cursor-python-responses-template的仓库作者是pchordia。这个项目本质上是一个为Cursor编辑器量身定制的Python启动模板它深度集成了OpenAI最新的Responses API并原生支持了联网搜索Web Search和工具调用Tool Calling这两个最实用的功能。我花了一些时间深入研究并实践了这个模板发现它绝不仅仅是一个“Hello World”示例。它提供了一套清晰、可扩展的工程化结构能让你在几分钟内就搭建起一个功能完整、具备复杂交互能力的AI应用原型把精力从“造轮子”完全转移到“实现想法”上。简单来说这个模板解决的核心痛点就是**“快速启动”和“结构清晰”**。它预设了与OpenAI Responses API交互的最佳实践包括会话管理、流式响应处理、工具调用的脚手架代码。对于独立开发者、产品经理或是任何想快速验证AI应用概念的人来说这无疑是一个效率利器。接下来我将带你彻底拆解这个模板从环境搭建到每一行代码的深意并分享我根据实际项目经验补充的配置技巧和避坑指南。2. 核心架构与设计思路拆解在深入代码之前我们有必要先理解OpenAI Responses API以及这个模板的整体设计哲学。这能帮助我们在自定义和扩展时做出更合理的决策。2.1 OpenAI Responses API 是什么为什么是它传统的OpenAI Chat Completions API需要我们以“请求-响应”的轮询方式管理对话。我们需要维护一个messages数组每次调用都将其发送给API然后处理返回的文本或工具调用请求。这种方式下会话状态thread的管理责任完全在客户端。而Responses API引入了一个更高级的抽象响应对象Response Object。你可以将它理解为一个托管式的会话。你创建一个响应Response时OpenAI会在其服务器端为你维护一个会话线程Thread。之后你可以向这个响应追加新的消息用户输入或工具执行结果API会自动更新这个线程并返回最新的AI回复。这带来了几个关键优势状态托管无需在客户端手动维护messages历史简化了代码逻辑。增量更新支持流式streaming输出可以实时看到AI生成的内容。内置工具处理API响应中直接包含了工具调用的请求结构更清晰。这个模板正是基于Responses API构建的这意味着它天生就具备了管理多轮、复杂对话的能力。2.2 模板项目结构解析让我们看看这个模板的典型目录结构我根据最佳实践进行了一些补充和解释cursor-python-responses-template/ ├── .env # 环境变量文件存放API密钥等敏感信息 ├── .env.example # 环境变量示例文件说明需要配置哪些变量 ├── .gitignore # Git忽略文件配置 ├── README.md # 项目总览和快速开始指南 ├── requirements.txt # Python依赖包列表 ├── docs/ │ └── AI_SETUP.md # 详细的AI功能设置和使用的文档 ├── src/ # 主要源代码目录 │ ├── __init__.py │ ├── main.py # 应用的主入口文件 │ ├── agents/ # 智能体模块可根据需要扩展 │ │ ├── __init__.py │ │ └── base_agent.py # 基础智能体类定义通用行为 │ ├── tools/ # 工具函数模块 │ │ ├── __init__.py │ │ └── stub_tools.py # 工具调用的“桩”函数实现示例 │ └── utils/ # 工具类函数 │ ├── __init__.py │ └── config.py # 配置加载器 └── tests/ # 单元测试目录可选但建议 └── __init__.py设计思路解读关注点分离模板将核心运行逻辑main.py、工具定义tools/、配置管理utils/config.py清晰地分离开。这使得项目易于维护和扩展。例如当你需要增加一个新工具时只需在tools/目录下创建新文件或修改现有文件而无需触动主流程。环境配置优先使用.env文件管理OPENAI_API_KEY等密钥并通过python-dotenv加载。这是生产级应用的基本要求避免了将密钥硬编码在代码中带来的安全风险。“桩”工具示范stub_tools.py提供了工具调用的标准写法。这些工具目前只返回固定的模拟数据因此叫“桩”但这完美地展示了如何定义工具、如何被AI调用以及如何将结果返回给AI的完整闭环。你需要做的就是把get_current_weather函数里的模拟数据替换成真实的调用第三方API如OpenWeatherMap的逻辑。为Cursor优化项目结构干净依赖明确非常适合在Cursor中直接打开。Cursor能很好地识别requirements.txt并提示安装依赖其内置的AI也能更好地理解项目上下文为你提供编码协助。注意原模板可能只包含最核心的文件。我强烈建议你按照上述结构来组织代码尤其是引入src目录和模块化的设计这对于任何超过100行的Python项目都至关重要能有效防止代码变成难以维护的“面条代码”。3. 环境配置与依赖安装详解“工欲善其事必先利其器。” 再好的模板跑不起来也是白搭。这一节我会手把手带你完成从零到一的环境搭建并解释每一个步骤背后的原因。3.1 前置条件与工具准备首先确保你的系统满足以下条件Python 3.8Responses API和一些现代异步特性需要较新的Python版本。在终端输入python --version或python3 --version检查。Git用于克隆模板仓库。在终端输入git --version检查。一个代码编辑器当然是Cursor。它的核心优势在于深度集成了AI辅助编程对于使用OpenAI API的项目来说它能提供无与伦比的代码补全、解释和生成体验。OpenAI API 密钥你需要一个有效的OpenAI账户并在 API密钥管理页面 创建一个新的密钥。请妥善保管它就像你家的钥匙。3.2 克隆项目与虚拟环境搭建打开你的终端或Cursor的内置终端执行以下步骤# 1. 克隆模板仓库到本地 git clone https://github.com/pchordia/cursor-python-responses-template.git # 进入项目目录 cd cursor-python-responses-template # 2. 创建Python虚拟环境强烈推荐 # 这能隔离项目依赖避免污染系统Python环境。 python -m venv venv # 3. 激活虚拟环境 # 在 macOS/Linux 上 source venv/bin/activate # 在 Windows 上CMD # venv\Scripts\activate # 在 Windows 上PowerShell # .\venv\Scripts\Activate.ps1 # 激活后终端提示符前通常会显示 (venv)。为什么用虚拟环境想象一下你同时开发项目A和项目B它们依赖同一个库requests的不同版本。没有虚拟环境你只能安装其中一个版本必然导致一个项目运行失败。虚拟环境为每个项目提供了独立的“沙箱”其中的Python解释器和安装的包都是独立的完美解决了依赖冲突问题。3.3 安装依赖与配置密钥# 4. 安装项目所需的所有Python包 # 模板的 requirements.txt 通常包含 openai, python-dotenv 等。 pip install -r requirements.txt安装完成后我们来配置最关键的API密钥# 5. 复制环境变量示例文件创建你自己的 .env 文件 cp .env.example .env现在用Cursor打开项目目录并编辑根目录下的.env文件。你会看到类似如下的内容# .env.example 示例内容 OPENAI_API_KEYyour_openai_api_key_here # MODELgpt-4o # 可选指定使用的模型将your_openai_api_key_here替换为你从OpenAI平台获取的真实密钥。# 你的 .env 文件应该像这样 OPENAI_API_KEYsk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx MODELgpt-4o-mini # 例如我推荐使用 gpt-4o-mini 兼顾性能与成本重要安全警告绝对不要将.env文件提交到Git仓库模板中的.gitignore通常已经包含了.env请务必确认。绝对不要在代码、日志或任何公开场合打印或泄露你的API密钥。一旦泄露立即在OpenAI平台将其撤销Revoke。.env文件中的键值对在代码中通过os.getenv(OPENAI_API_KEY)来读取。3.4 核心依赖包解析让我们看看requirements.txt里可能有什么以及它们各自扮演的角色# requirements.txt (典型配置) openai1.0.0 # OpenAI官方Python SDK用于调用Responses API等所有服务。 python-dotenv1.0.0 # 从 .env 文件加载环境变量到系统环境简化配置。 pydantic2.0.0 # 数据验证和设置管理。用于严谨地定义工具的参数Schema。 httpx0.25.0 # 现代化的HTTP客户端支持异步。OpenAI SDK底层可能使用它。openai: 这是主角。版本1.0.0以上提供了对Responses API的稳定支持。务必使用较新版本。python-dotenv: 配置管理的“瑞士军刀”让12-Factor App一种云原生应用开发方法论的配置实践变得简单。pydantic: 你可能在tools/stub_tools.py中看到它的身影。它用来定义工具函数的参数类型确保AI传递过来的参数格式正确并在出错时给出清晰的错误信息而不是让程序默默崩溃。httpx: 如果你未来需要为你自己实现的工具函数调用外部API比如获取真实天气httpx是一个比老旧的requests库更佳的选择尤其是需要异步操作时。至此你的开发环境已经就绪。接下来我们深入核心代码看看这个模板是如何运转起来的。4. 核心代码逐行解析与实操让我们打开src/main.py这是整个应用的心脏。我会逐段分析并补充必要的背景知识和实操细节。4.1 导入与配置加载import asyncio import os from typing import Optional from dotenv import load_dotenv from openai import OpenAI, AsyncOpenAI # 加载 .env 文件中的环境变量 load_dotenv() # 初始化OpenAI客户端 # 注意这里同时初始化了同步和异步客户端。模板可能主要用异步。 client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) async_client AsyncOpenAI(api_keyos.getenv(OPENAI_API_KEY)) MODEL os.getenv(MODEL, gpt-4o-mini) # 从环境变量读取模型默认为 gpt-4o-mini代码解读与实操要点load_dotenv(): 这行代码至关重要。它会在项目根目录寻找.env文件并将其中的所有变量加载到当前进程的环境变量中。这样os.getenv(“OPENAI_API_KEY”)才能读到值。同步 vs 异步客户端OpenAI是同步客户端AsyncOpenAI是异步客户端。简单理解同步调用会“阻塞”程序直到收到响应而异步调用在等待响应时可以让程序去处理其他任务效率更高。对于需要流式输出或高并发的AI应用异步是更优选择。模板可能主要展示异步用法。模型选择MODEL变量给了我们灵活性。gpt-4o-mini是性价比很高的最新模型响应快成本低。你也可以在.env文件中设置为gpt-4o以获得更强的推理能力但成本更高。根据你的应用场景是需要复杂逻辑还是简单交互来权衡。4.2 工具定义与注册工具调用Function Calling/Tool Calling是让AI从“聊天机器人”升级为“智能助手”的关键。它允许AI在需要时请求你的程序去执行一个具体的函数比如查天气、搜数据库、发邮件。模板中的src/tools/stub_tools.py提供了范例# src/tools/stub_tools.py from pydantic import BaseModel, Field from typing import Optional # 1. 定义工具参数模型Schema class GetCurrentWeatherParams(BaseModel): location: str Field(descriptionThe city and state, e.g. San Francisco, CA) unit: Optional[str] Field(defaultcelsius, descriptionTemperature unit: celsius or fahrenheit) # 2. 实现工具函数本身 def get_current_weather(location: str, unit: str celsius) - str: Get the current weather in a given location. Args: location: The city and state, e.g. San Francisco, CA. unit: The temperature unit, celsius or fahrenheit. Returns: A string describing the weather. # 这里是“桩”实现返回模拟数据。 # 在实际应用中你会在这里调用真实的天气API如 OpenWeatherMap。 return fThe weather in {location} is 22 degrees {unit}, sunny. # 3. 定义工具列表供主程序注册 # 这是一个字典列表每个字典描述一个工具。 weather_tool { type: function, function: { name: get_current_weather, description: Get the current weather in a given location, parameters: GetCurrentWeatherParams.model_json_schema(), # 使用Pydantic自动生成JSON Schema } } # 你可以定义更多工具并放在一个列表里 available_tools [weather_tool]关键解析Pydantic模型GetCurrentWeatherParams类定义了工具需要的参数。Field提供了描述这非常重要因为AI就是靠这些description来理解何时以及如何使用这个工具。清晰的描述能极大提升工具调用的准确性。函数实现get_current_weather是具体的执行函数。当前它返回的是假数据。你需要将其替换为真实的逻辑例如使用httpx发起一个GET请求到api.openweathermap.org/data/2.5/weather。工具Schemaweather_tool字典是这个工具的描述它会被发送给OpenAI API。GetCurrentWeatherParams.model_json_schema()是Pydantic V2的便捷方法能自动将数据模型转化为OpenAI要求的JSON Schema格式避免了手动编写的繁琐和错误。工具列表available_tools包含了所有可用的工具。主程序会把这个列表传给AI告诉它“你现在可以使用这些功能了”。4.3 主逻辑创建、运行与管理响应这是main.py中最核心的部分通常包含一个main函数或异步的run函数。我们来看一个典型的实现流程async def run_conversation(user_input: str): 运行一次与AI的对话交互支持工具调用。 # 1. 创建初始响应Response这同时创建了一个线程Thread response await async_client.responses.create( modelMODEL, toolsavailable_tools, # 传入我们定义的工具列表 tool_choiceauto, # “auto”表示由AI决定是否以及何时调用工具 inputuser_input, # 用户的初始问题 streamFalse, # 先以非流式开始简化处理逻辑 ) # 2. 检查并处理工具调用 # Responses API的返回中工具调用请求在 output 里 final_output_text for item in response.output: if item.type message: # 如果是普通文本消息直接累加 final_output_text item.content[0].text elif item.type tool_calls: # 如果AI决定调用工具 for tool_call in item.tool_calls: if tool_call.function.name get_current_weather: # 解析AI传递过来的参数已经是JSON格式 import json arguments json.loads(tool_call.function.arguments) location arguments.get(location) unit arguments.get(unit, celsius) # 执行对应的工具函数 tool_result get_current_weather(location, unit) # 3. 提交工具执行结果回给AI继续对话 # 我们需要向这个响应Response追加新的消息工具执行结果 response await async_client.responses.update( response_idresponse.id, output[ { type: tool_result, tool_call_id: tool_call.id, # 必须匹配之前的调用ID content: tool_result, } ], ) # 处理更新后的响应获取AI根据工具结果生成的新回复 for new_item in response.output: if new_item.type message: final_output_text new_item.content[0].text print(fAI: {final_output_text}) # 在 __main__ 中运行 if __name__ __main__: user_query input(You: ) asyncio.run(run_conversation(user_query))流程拆解与注意事项responses.create这是起点。它创建了一个托管在OpenAI的会话线程并发送了第一条用户消息。tool_choice“auto”是精髓它授权AI在认为必要时自动选择调用我们提供的工具。解析response.output响应输出是一个列表可能包含多种类型。type“message”是文本内容type“tool_calls”则包含了工具调用请求。我们必须遍历这个列表来处理所有情况。执行工具当识别到工具调用后我们根据tool_call.function.name找到对应的本地函数解析参数arguments然后执行它。这里有一个关键点arguments已经是解析好的JSON字符串我们需要用json.loads将其转为Python字典。responses.update这是将工具执行结果反馈给AI的关键步骤。注意tool_call_id必须与AI请求中的ID一致这样API才知道这个结果是针对哪个工具调用的。提交后AI会基于这个结果生成新的回复。流式处理上面的例子为了清晰设置了streamFalse。在实际体验更好的应用中你应该使用streamTrue。流式响应会逐步返回token你需要处理AsyncStream对象并在收到tool_calls时暂停流执行工具再继续。逻辑更复杂但用户体验是实时的。模板的docs/AI_SETUP.md里可能会有流式处理的更高级示例。实操心得工具调用的调试工具调用失败最常见的原因是Schema不匹配或函数执行异常。建议在开发时在工具函数内部添加详细的print日志打印接收到的参数。使用try...except包裹工具执行逻辑并将异常信息作为content返回给AIAI有时能理解错误并调整后续问题。在responses.create时可以暂时设置tool_choiceNone让AI只进行纯文本对话以隔离是否是工具引入的问题。5. 启用联网搜索Web Search功能这是本模板的另一大亮点。OpenAI的Responses API内置了联网搜索能力无需你自己去集成SerpAPI或爬虫。启用它非常简单只需在创建响应时添加一个参数response await async_client.responses.create( modelMODEL, toolsavailable_tools, tool_choiceauto, inputuser_input, streamFalse, # 启用联网搜索 search_tool{type: web_search_preview, include_images: False} # 或 True )关键说明“web_search_preview”: 这是当前可用的搜索工具类型。它允许模型在生成回复前先进行一轮网络搜索以获取最新信息。include_images: 设置为True时搜索结果可能包含图片信息但通常文本信息已足够。效果当你问“今天北京有什么科技新闻”时AI会先调用搜索工具获取最新结果然后基于这些结果生成回答。这极大地扩展了AI的知识时效性和范围。成本与限制联网搜索会产生额外的费用在OpenAI账单中单独列示并且有使用频率限制。在开发测试时请注意。此外搜索结果是模型生成答案的参考模型可能会对其进行总结、提炼但不会直接引用大段原文。6. 项目扩展与高级实践基础功能跑通后我们可以考虑如何将这个模板进化成一个真正的应用。6.1 添加更多实用工具除了天气你可以添加无数种工具。关键在于定义清晰的Schema和可靠的函数。例如添加一个计算器工具# 在 src/tools/calculator.py 中 from pydantic import BaseModel, Field import math class CalculatorParams(BaseModel): expression: str Field(descriptionA mathematical expression to evaluate, e.g., 2 3 * (sqrt(16) / 2)) def evaluate_expression(expression: str) - str: Safely evaluate a mathematical expression. WARNING: Using eval() is dangerous. In production, use a safe evaluator like asteval. # 安全警告这里仅作演示。实际生产环境中直接使用eval()处理用户输入是极端危险的 # 应使用受限制的求值库如 asteval。 try: # 非常简陋且不安全的示例 result eval(expression, {__builtins__: None}, {sqrt: math.sqrt, sin: math.sin, cos: math.cos, pi: math.pi}) return fThe result of {expression} is {result}. except Exception as e: return fError evaluating expression: {e} calculator_tool { type: function, function: { name: evaluate_expression, description: Evaluate a mathematical expression. Supports basic operators (, -, *, /, **) and some functions like sqrt, sin, cos. Use pi for π., parameters: CalculatorParams.model_json_schema(), } } # 然后在主程序中将 calculator_tool 也加入到 available_tools 列表中。严重安全警告上例中的eval()用法是极其危险的因为它允许执行任意Python代码。这只是一个概念演示。在真实项目中你必须使用像asteval这样没有访问系统权限的安全表达式求值库。6.2 构建简单的智能体Agent循环模板可能提供了一个基础Agent类。我们可以扩展它实现一个能自动处理多轮工具调用的循环直到AI认为不再需要调用工具为止。# src/agents/weather_agent.py import json from typing import List, Dict, Any from src.tools import stub_tools # 导入工具函数 class WeatherAgent: def __init__(self, async_client, model: str): self.client async_client self.model model self.response_id None # 用于跟踪当前的响应会话 async def ask(self, question: str) - str: 向智能体提问并处理可能的多轮工具调用。 full_conversation [] # 第一轮创建初始响应 response await self.client.responses.create( modelself.model, tools[stub_tools.weather_tool], # 只给天气工具 tool_choiceauto, inputquestion, streamFalse, ) self.response_id response.id full_conversation.append(self._format_output(response)) # 处理可能的工具调用循环理论上Responses API一次调用可能返回多个工具请求这里简化处理一轮 # 更健壮的实现需要while循环检查response.output是否还有tool_calls for item in response.output: if item.type tool_calls: for tool_call in item.tool_calls: if tool_call.function.name get_current_weather: args json.loads(tool_call.function.arguments) result stub_tools.get_current_weather(args[location], args.get(unit, celsius)) # 提交结果 response await self.client.responses.update( response_idself.response_id, output[{ type: tool_result, tool_call_id: tool_call.id, content: result, }] ) self.response_id response.id full_conversation.append(self._format_output(response)) return \n.join(full_conversation) def _format_output(self, response) - str: 格式化响应输出为字符串。 text_parts [] for item in response.output: if item.type message: for content in item.content: if content.type text: text_parts.append(content.text) return .join(text_parts)这个WeatherAgent类封装了与天气查询相关的所有逻辑主程序只需要调用agent.ask(“今天旧金山天气如何”)即可。这种封装让代码更清晰也便于测试。6.3 错误处理与健壮性增强生产级应用必须考虑错误处理。API调用错误网络超时、额度不足、无效请求等。from openai import APIError, APIConnectionError try: response await async_client.responses.create(...) except APIConnectionError as e: print(f网络连接失败: {e}) # 可以在这里实现重试逻辑 except APIError as e: print(fOpenAI API 错误 (状态码: {e.status_code}): {e}) # 根据e.code或e.status_code进行特定处理如额度不足、模型不存在等 except Exception as e: print(f未知错误: {e})工具执行错误工具函数内部可能抛出异常。def get_current_weather_safe(location: str, unit: str) - str: try: # 模拟可能失败的真实API调用 # response httpx.get(...) # response.raise_for_status() # data response.json() # return fWeather in {location}: {data[main][temp]}°{unit} return fThe weather in {location} is 22 degrees {unit}, sunny. except httpx.HTTPStatusError as e: return fFailed to fetch weather data from service. HTTP Error: {e.response.status_code} except Exception as e: return fAn unexpected error occurred while getting weather: {str(e)}将错误信息返回给AIAI有时能理解并给出用户友好的提示。7. 常见问题、排查技巧与优化建议在实际使用和教学过程中我总结了一些高频问题和优化点。7.1 问题排查清单问题现象可能原因解决方案ModuleNotFoundError: No module named ‘openai’依赖未安装或虚拟环境未激活。1. 确认终端提示符前有(venv)。2. 运行pip install -r requirements.txt。openai.AuthenticationErrorAPI密钥无效或未设置。1. 检查.env文件中的OPENAI_API_KEY是否正确。2. 确认已运行load_dotenv()。3. 在OpenAI平台检查密钥是否被撤销。AI从不调用工具1. 工具描述不清晰。2. 用户问题不需要工具。3.tool_choice参数设置错误。1. 优化工具函数的description和参数的description确保清晰无歧义。2. 用明确需要工具的问题测试如“使用工具查询一下东京的天气”。3. 确认创建响应时传入了tools列表且tool_choice“auto”或“required”。工具调用参数解析错误1. AI生成的参数格式与Schema不匹配。2. 本地解析代码错误。1. 打印tool_call.function.arguments查看原始JSON字符串。2. 确保使用json.loads()正确解析。3. 检查Pydantic模型定义的数据类型如str,int是否合理。流式输出不工作或混乱流式处理逻辑有误未正确处理不同事件类型。参考OpenAI官方SDK中关于流式Responses的示例代码。关键是要区分event.type例如“response.created”,“response.output_added”,“response.output_done”并正确处理tool_calls事件。搜索功能没返回最新信息1. 未启用搜索。2. 问题本身不需要搜索AI已有知识。3. 搜索配额用尽。1. 确认search_tool参数已正确添加。2. 询问非常近期的事件如“今天上午特斯拉股价涨了吗”。3. 检查OpenAI用量面板。7.2 性能与成本优化建议模型选择对于大多数工具调用和搜索场景gpt-4o-mini在速度、成本和能力上取得了很好的平衡。仅在需要复杂推理、长上下文或代码生成时考虑gpt-4o。上下文管理Responses API自动管理上下文但长时间、多轮对话会导致上下文越来越长增加token消耗和成本。对于超长对话可以考虑定期总结之前的历史让AI生成一个摘要然后基于摘要开启一个新响应Thread。工具设计工具应保持单一职责和幂等性。一个工具只做一件事并且多次调用同一参数应产生相同结果。这能让AI更可靠地使用它们。异步与并发如果你的应用需要同时处理多个用户请求务必使用异步客户端AsyncOpenAI并配合像asyncio.gather这样的并发原语可以大幅提升吞吐量。缓存对于频繁查询且结果变化不快的工具如天气可以缓存几分钟可以在工具函数内部实现简单的缓存逻辑减少对第三方API的调用和等待时间。这个cursor-python-responses-template项目是一个绝佳的起点它抽象了与OpenAI Responses API交互的复杂性让你能聚焦于构建有价值的工具和应用逻辑。从理解其架构开始逐步替换掉桩函数添加错误处理封装业务逻辑你就能快速构建出功能强大、交互自然的AI应用原型。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价