提示词与上下文工程:让大模型输出稳定JSON
你会不会也有这种感觉大模型的能力明明很强可一旦接进自己的项目效果立刻像换了一个模型。这是过去一年里我在大量开发者社区和实际项目里反复听到的问题。很多人把大模型当成一台“自动生成机”觉得把需求往对话框里一扔答案就会自己跳出来。但真实落地时你会发现同样的模型有人调出来的是干净的结构化数据有人调出来的是大段没什么用的散文还有人直接收到一条报错。差别不在模型而在“召唤”它的方式。“The Lamp and the Genie”这个标题就是在说这件事大模型是灯里的精灵能力很强而你的提示词、上下文、工具调用方式就是那盏灯。灯磨得对不对直接决定精灵是帮你实现愿望还是给你制造一堆麻烦。本文就以这个隐喻为线索梳理大模型应用开发中最关键、也最容易被低估的一段能力提示词工程与上下文工程。我会结合一个真实任务——把用户自然语言反馈转成结构化投诉工单完整走一遍从裸 Prompt 到可信输出的过程并给出的工程化建议。1. 从“神灯与精灵”说起大模型为什么经常用不好先讲一个很典型的场景。某个团队接到一个需求把用户在 App 里的投诉留言自动分类比如“服务态度差”“等待时间过长”“退款问题”“其他”。业务方希望拿到结构化结果最好能直接进工单系统。开发同学的第一反应通常是“这还不简单直接让大模型输出 JSON 就行。”于是他写了一句提示词请把下面的用户留言分类并输出 JSON。 用户留言等了半小时没人接待前台态度还很差最后也没解决问题。结果模型返回的可能是这样的内容好的根据您的留言我将它分类为“服务态度差”。同时我注意到用户还提到了等待时间过长所以也可以考虑该问题涉及多个类别。另外从语气来看用户情绪比较不满建议优先处理……这段文字本身没有错但它不是结构化数据。接下来的工序就卡住了无法直接写入数据库、无法统计、无法进入后续工单流程。于是开发同学开始加规则、写正则、做各种字符串清洗越写越痛苦。这个案例非常典型它说明了一个关键事实模型不是做不到而是开发者在“召唤”它的时候没有给它足够的上下文约束。你把任务想得越简单模型返回的结果就越“随意”。相反真正成熟的开发者会像打磨一盏灯一样仔细设计系统提示词、示例、输出格式和后置校验。这才是大模型应用开发的核心工作不是从零写逻辑而是设计一段能稳定驱动模型的外部上下文。所以本文的第一个判断是模型能力本身是相对固定的变量在于你怎么构造请求。同样一个模型在一个人手里是工具在另一个人手里是“玩具”差异几乎都出在请求侧。下面我们从概念层面把这个差异讲清楚。2. 提示词工程与上下文工程核心概念辨析先澄清两个经常被混在一起的概念Prompt Engineering提示词工程和 Context Engineering上下文工程。提示词工程指的是设计输入给模型的文本指令让它更准确地完成目标。它关注的是“怎么说”。比如用“你是一个资深客服运营专家”开场、把问题拆成若干步骤、明确输出格式这些都属于提示词工程。它的核心工具包括系统提示词、few-shot 示例、思维链引导、输出格式声明。上下文工程的范围更大。它关心的是在模型生成之前你往请求里放入了哪些信息以及这些信息以什么结构存在。除了提示词本身上下文工程还包括外部检索结果、用户历史会话、知识库片段、数据库结构、当前页面状态、工具调用结果等等。它的核心问题是为了让模型在特定任务上稳定发挥我们应该给它看什么先看什么哪些信息必须放在前面哪些工具它必须能调用为什么这两个概念要分开因为现代大模型应用的复杂度已经远远超过“写一段提示词”。举例来说一个客服问答 Agent 的上下文可能包含系统提示词角色、任务、约束、语气规范。当前用户的问题。通过向量检索召回的 5 条知识库片段。用户最近 3 轮对话历史。当前登录用户的基础信息。可供调用的工具 API 描述。这一段“上下文组合”才是模型真正用来推理的素材。提示词工程解决的是“文字指令好不好”上下文工程解决的是“信息拼盘对不对”。你可以写出一句完美的系统提示词但如果知识库字段错误、工具描述含糊、上下文顺序混乱模型依然会做出错误判断。下面用一个简单对比来收束维度提示词工程上下文工程关注对象指令文本模型所见的一切信息核心问题怎么把任务说清楚怎么把任务所需的资料组织好常见手段角色设定、few-shot、思维链RAG 检索、会话管理、工具调用、状态注入失败表现输出格式不对、理解偏差信息缺失、知识错误、工具选择错误工程价值提高单次输出质量决定复杂任务能否稳定完成所以我的第二个判断是单纯追求“提示词技巧”是不够的。你要解决的是信息组织问题而不是文字排版问题。理解了这一点你才明白为什么同样一个 Prompt在不同项目里效果天差地别。3. 大模型应用开发的整体链路与适用场景为了不让讨论停留在概念层面这里把大模型应用开发的完整链路拆出来。不管你是做客服助手、内容总结、信息抽取还是做 Agent 编排链路大体都包含六个环节。第一任务定义。明确你的输入是什么、输出是什么、验收标准是什么。很多项目一开始就失败不是因为模型不强而是因为任务定义模糊。比如“帮我识别用户情绪”听起来简单但“愤怒”和“失望”的边界在哪里如果有多个情绪取第一个还是全部输出这些都必须写清楚。第二上下文构造。这是整个链路的核心。你要把原始输入、系统提示词、外部知识、历史记录组装成一个模型可消费的 messages 结构。顺序、长度、优先级都需要设计。第三模型调用。选择模型、设置温度、控制最大 token、决定超时和重试策略。这里的工程问题比大多数人想象的多高并发、限流、网络抖动、模型升级引发的行为变化都需要考虑。第四输出校验。模型输出只是候选结果不是最终结论。必须做 JSON 解析、字段校验、枚举值检查。这一步做不到后面所有环节都是空中楼阁。第五后处理与工具调用。如果结果不满足要求可能是让模型重新生成也可能是调用外部工具补充数据再基于工具结果生成最终回答。第六存储与展示。把结果落库、同步到工单系统、推送给用户。在实际项目里这六个环节的复杂度不同。比如一个“总结用户反馈并分类”的功能第四和第五步相对简单而一个“自动处理工单”的 Agent第五步会变得非常复杂因为它涉及工具选择、参数提取、结果验证、异常处理。那么什么场景真正适合用大模型我的判断是自然语言到结构化信息的映射是大模型当前最成熟、性价比最高的落地场景之一。比如用户留言分类、合同关键信息提取、客服会话摘要、代码注释生成。这类任务的特点是输入是语言输出也是语言但需要稳定转化为程序能用的数据。相反如果任务是精确计算、强实时响应、严格事务控制那不应该把核心逻辑交给大模型而是应该用传统代码实现让大模型只负责理解和生成的部分。理清这些边界后我们直接进入实操环节。接下来的章节会围绕“自然语言反馈转结构化工单”这个示例展开一步步演示如何构造上下文、调用模型、校验输出。4. 环境准备与前置条件在开始写代码之前先把运行环境准备好。本文的示例基于 Python、OpenAI 兼容接口和一个可调用的对话补全 API。不同厂商的 SDK 包名和参数可能略有差异请以你实际使用的服务商官方文档为准。开发环境建议如下操作系统Windows / macOS / Linux 均可。Python3.10 及以上版本。包管理pip 或 poetry。模型接口具备对话补全能力并支持系统消息。可选支持 JSON 输出模式的服务商可以简化解析。先创建项目目录和虚拟环境mkdir lamp-genie-demo cd lamp-genie-demo python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate然后安装依赖。核心只用到一个 OpenAI 兼容 SDK再加一个用于读取环境变量的 python-dotenvpip install openai python-dotenv这里有一个非常值得强调的工程习惯不要在代码里硬编码 API Key。正确的做法是把 Key 放到环境变量或本地配置文件中并确保它不会进入 Git 仓库。在项目根目录创建一个.env文件OPENAI_API_KEYyour-api-key-here OPENAI_BASE_URLhttps://api.example.com/v1 MODEL_NAMEyour-model-name然后在项目目录添加一个.gitignore内容至少包括.env venv/ __pycache__/安全配置完成后我们先跑一个最小连通性测试确认 SDK、网络和鉴权都正常import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client OpenAI( api_keyos.environ[OPENAI_API_KEY], base_urlos.environ.get(OPENAI_BASE_URL), ) resp client.chat.completions.create( modelos.environ[MODEL_NAME], messages[ {role: user, content: 请只回复两个字成功} ], temperature0, ) print(resp.choices[0].message.content)如果控制台输出“成功”说明环境已经可用。这里有几个肉眼容易忽略但实际很关键的细节temperature0是这类结构化抽取任务的推荐设置目的是降低随机性。base_url不是必填项如果你直接使用 OpenAI 官方服务可以省略如果你使用国内合规服务商或私有化部署网关就需要填写对应的接口地址。API Key 千万不要提交到公共仓库。一旦泄露第一时间去控制台吊销并重新生成。5. 一个真实任务从自然语言反馈到结构化结果接下来我们进入主线任务。假设你正在为一家连锁线下门店做用户反馈分析系统输入是用户留下的自然语言投诉输出是一个可以直接写入数据库的结构化工单。先定义输入输出。输入示例等了半小时没人接待前台态度还很差最后也没解决问题。期望输出{ category: service_attitude, severity: high, emotion: angry, action_required: 需要客服致歉并跟进, summary: 用户等待超过半小时且前台接待态度差问题未解决, confidence: 0.87 }字段含义如下字段类型说明categorystring投诉分类枚举service_attitude, waiting_time, refund, othersseveritystring严重程度枚举low, medium, highemotionstring用户情绪枚举calm, neutral, angryaction_requiredstring建议的处理动作描述summarystring一句话问题摘要confidencefloat模型对自身判断的置信度0 到 1 之间这里要特别说明一下confidence的定位它并不是一个真实的统计概率值而是模型的主观评估。它不能替代业务规则的优先级判断但可以用作排序和人工抽检的参考。理解这一点能避免你在后续使用这个字段时做出错误的业务决策。现在看一个错误示范。如果提示词只写了“请把用户留言分类”就像第 1 章展示的那样输出大概率不是合法 JSON。更糟糕的是如果你没有在系统提示词里定义枚举值模型就会自己发明一堆分类比如“客户服务质量不好”“接待效率低”这种无法直接入库的字符串。分类体系一旦混乱后续所有统计报表都会变得不可信。所以构造上下文时要遵循三个原则第一把任务背景写清楚。你要告诉模型它是谁、在做什么、为谁服务、输出给谁用。第二把所有约束写彻底。枚举值有哪些、字段可选还是必填、JSON 里能不能有多余文字、置信度保留几位小数。每一条看似“不重要”的约束都可能直接影响输出质量。第三给出少量示例。示例的作用不是让模型“学会”业务而是示例一两个充满歧义的边界情况引导模型在类似情况下选择正确的处理方式。基于这三个原则下一章给出完整可运行的代码实现。6. 完整代码实现与运行验证下面这段代码是完整的实现。它包含系统提示词、few-shot 示例、模型调用、JSON 解析、字段校验和兜底处理。你复制之后只需要替换服务和模型配置即可运行。import json import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client OpenAI( api_keyos.environ[OPENAI_API_KEY], base_urlos.environ.get(OPENAI_BASE_URL), ) system_prompt 你是一位资深客服工单分析师。你的任务是从用户投诉留言中提取结构化信息生成一份可用于工单系统的 JSON 结果。 请严格遵守以下约束 1. 只输出一个 JSON 对象不要包含任何解释、前缀或 Markdown 代码块标记。 2. category 只能是以下枚举值之一service_attitude、waiting_time、refund、others。 3. severity 只能是 low、medium、high 之一。判断标准涉及财产损失或升级冲突为 high明确表达不满但未升级为 medium一般性反馈为 low。 4. emotion 只能是 calm、neutral、angry 之一。 5. summary 必须是一句简洁的中文描述不超过 40 字。 6. action_required 必须给出明确的处理建议不能为空。 7. confidence 是 0 到 1 之间的小数保留两位小数表示你对本次判断的把握程度。 8. 如果同一段留言涉及多个问题只选择最重要的一类作为 category。 示例输入门店打烊时间写错了白跑一趟。 示例输出{category: others, severity: medium, emotion: neutral, summary: 用户因门店营业时间信息错误白跑一趟, action_required: 修正门店信息并致歉, confidence: 0.78} def parse_and_validate(content: str) - dict: 解析模型输出并做基础校验。如果失败抛出异常。 text content.strip() # 去掉可能出现的 markdown 代码块标记 if text.startswith(json): text text.removeprefix(json).strip() if text.startswith(): text text.strip().strip() data json.loads(text) required_fields { category: str, severity: str, emotion: str, summary: str, action_required: str, confidence: float, } for field, field_type in required_fields.items(): if field not in data: raise ValueError(f缺少字段: {field}) if not isinstance(data[field], field_type): raise ValueError(f字段类型错误: {field}, 期望 {field_type}) if data[category] not in {service_attitude, waiting_time, refund, others}: raise ValueError(f非法的 category: {data[category]}) if data[severity] not in {low, medium, high}: raise ValueError(f非法的 severity: {data[severity]}) if data[emotion] not in {calm, neutral, angry}: raise ValueError(f非法的 emotion: {data[emotion]}) data[confidence] float(data[confidence]) return data def classify_feedback(user_input: str) - dict: resp client.chat.completions.create( modelos.environ[MODEL_NAME], messages[ {role: system, content: system_prompt}, {role: user, content: user_input}, ], temperature0, ) content resp.choices[0].message.content return parse_and_validate(content) if __name__ __main__: test_input 等了半小时没人接待前台态度还很差最后也没解决问题。 try: result classify_feedback(test_input) print(json.dumps(result, ensure_asciiFalse, indent2)) except Exception as e: print(f处理失败: {e})运行方式很简单python classify_feedback.py一个可行的输出如下{ category: service_attitude, severity: high, emotion: angry, summary: 用户等待超过半小时且前台接待态度差问题未解决, action_required: 由店长致歉并回访跟进处理方案, confidence: 0.85 }这段代码有几个关键设计值得展开说明。第一系统提示词里明确要求“只输出一个 JSON 对象不要包含任何解释、前缀或 Markdown 代码块标记”。这是为了最大限度避免后端解析失败。即便这样解析函数里仍然做了防御性处理能去掉常见的 markdown 包裹标记这属于“前端约束 后端兜底”的经典组合。第二temperature0让模型在每次相似输入下尽量输出稳定结果。虽然不能做到绝对确定性但对于分类和抽取任务这已经能显著降低随机波动。第三JSON 校验不是只判断“能不能解析”还检查了字段是否存在、类型是否正确、枚举值是否合法。这样做的目的是把错误拦截在数据落库之前。实际生产环境里你还可以在action_required上进行长度检查或者在summary上做字数控制。如果你使用的服务商支持 JSON 输出模式可以在调用时增加一个参数以进一步减少非 JSON 输出resp client.chat.completions.create( modelos.environ[MODEL_NAME], messages[ {role: system, content: system_prompt}, {role: user, content: user_input}, ], temperature0, response_format{type: json_object}, )需要注意不是所有服务商都支持这个参数。如果不支持请把它去掉继续靠系统提示词约束和解析兜底。这也是我们不能只依赖某个单一特性的原因工程链路要保证在多种环境下都能降级工作。7. 效果验证怎么判断输出是“真的可用”而不是“看起来不错”很多团队走到上一步就宣布“功能完成”了这是错误示范。你对着一条测试数据看着很顺眼不代表模型在 100 条真实数据上也能稳定输出。效果验证必须超越“肉眼看一眼”最起码要做三件事。第一件事格式通过率。准备 20 条真实或接近真实的用户留言逐条调用接口统计能通过parse_and_validate的比例。如果通过率低于 95%说明提示词约束还不够需要补示例或加强系统提示词。这里真正容易踩坑的地方是模型偶尔会在 JSON 后面追加一句解释性文字或者把枚举值写成中文比如服务态度差这种问题在 20 条测试里可能只出现一次但线上跑起来会很致命。第二件事分类正确率。格式通过只代表“能解析”不代表“分类正确”。你需要人工给 20 条测试数据打上预期分类再和模型结果对比。比如“退款到账太慢”和“不给开发票”都可能属于 refund但模型可能把前者分到 waiting_time因为语义上它和时间有关。这类歧义问题必须通过更精细的枚举值定义和更多的 few-shot 示例来解决。第三件事边界案例覆盖。真实世界的用户留言非常粗糙常见边界包括一段留言里包含多个投诉点模型应该选最重要的一个而不是全部输出。用户使用了反讽语气模型很容易误判情绪。留言里包含地址、电话、姓名等敏感个人信息需要在进入模型之前脱敏或在输出后处理。输入是非中文内容需要明确模型应该如何处理是翻译还是拒绝分类。为了把人工评估沉淀下来我建议建立一个小型评测表用表格记录每次测试的结果。维度评估方式达标线格式通过率自动解析通过数 / 总测试数95% 以上分类准确率人工标注对比模型结果90% 以上枚举合法率输出字段是否都在定义范围内100%摘要可读性人工判断是否语义通顺95% 以上敏感信息泄漏输出是否包含手机号、地址等0 起如果达不到标准优先调整系统提示词中的枚举定义和示例其次是增加预处理和后处理规则最后才考虑更换更强或更大的模型。要记住换模型是成本最高、最不可控的方案先用上下文工程把当前模型压榨到位才是更高效的做法。8. 常见问题与排查思路大模型应用出问题的时候排查思路和传统应用不太一样。传统系统里错误通常是确定的你可以按调用栈一路追下去但在大模型应用里很多时候没有报错只是输出“不对”。下面把最常见的问题整理成一张排查表供你作为参考。问题现象可能原因排查方式解决方案输出是散文不是 JSON系统提示词没有明确输出格式查看请求 messages 中 system 内容增加“只输出 JSON 对象”和示例JSON 解析偶尔失败模型在 JSON 外输出解释或 markdown 标记打印原始返回内容解析前做清洗或使用 response_format分类枚举值不固定系统提示词没有给出枚举定义检查输出集合统计非法值在 system 中给出枚举和例句同一段输入多次调用结果不同温度过高或上下文有随机性对比多次输出将 temperature 设置为 0摘要过长或过短没有字数约束或约束不生效检查 summary 输出长度增加“不超过 40 字”等硬约束输入包含用户联系方式输出也带出没有做输入脱敏检查原请求日志上游脱敏或输出侧使用 PII 过滤器上下文长度超限RAG 片段或对话历史过长查看 token 用量统计截断、压缩、分段检索成本超预期few-shot 示例太长、并发太高统计每次调用的 token 数精简示例优先用小模型分流某类问题总是分错分类定义边界模糊或示例不足对错误案例聚类分析补充对应示例细化枚举定义客户在回答里被“套话”或诱导输出危险指令提示词注入把用户输入与系统指令隔离加输入过滤、权限限制和输出审查如果你遇到的问题是“解析失败”第一步永远不是改代码而是把模型返回的原始字符串完整打出来看它到底是什么形态。很多时候你以为模型输出的是标准 JSON实际上它在外面包了json标记或者中间夹了注释导致标准 JSON 解析器直接抛异常。先看原始输出再决定是改提示词、加清洗还是换接口参数。如果遇到分类不准优先检查是不是枚举定义和真实用户表达存在认知偏差。比如“退款”这个分类真实用户可以表达为“退钱”“没到账”“发票不对”甚至“再也不来了”。枚举定义要能覆盖这些口语化表达。如果你的系统提示词里只写了“refund退款”模型确实可能漏分。正确做法是写出更详细的分类说明必要时给每个枚举配一个正例和一个反例。这里还有一个容易忽略的问题模型输出里的confidence不可靠。它没有经过校准同一个模型在不同任务上的自信程度波动很大。不要拿它作为自动判定的硬指标更不要用阈值直接拦截工单。更稳妥的做法是把它用作人工抽检排序条件抽检时优先看低置信度的记录。9. 最佳实践从 Demo 到生产环境的工程建议很多项目能跑通 Demo但一上生产就崩。原因通常不是模型不行而是工程化程度不够。下面这六条建议是任何大模型落地项目都值得提前想清楚的。第一把提示词当成代码来管理。提示词不是“文案”它是功能代码的一部分。它应该和代码一起进入 Git 仓库跟随版本发布。不要直接在线上控制台里改提示词否则你无法回溯“这一版效果变好到底改了什么”。推荐在项目里维护prompts/目录每个任务一个文件并写清楚变更记录。第二评测集先行。上线一个新功能之前先准备一个包含典型输入、边界输入和错误输入的小型评测集。以后每次改提示词、换模型、升级 SDK都先跑一遍评测集。这样能有效防止“修好一个 case弄坏一片 case”的情况。没有评测集的大模型开发就像没有单测的传统开发越到后期风险越大。第三设计降级与兜底。模型调用是典型的不可靠依赖它可能会超时、限流、返回非 JSON、甚至服务不可用。生产系统必须为这些情况设计降级路径。最常见的做法是解析失败时返回默认分类others记录异常日志并进入人工处理队列而不是让整个接口直接 500。记住在核心链路里模型输出永远只是候选结果最终决策要由你的代码来拍板。第四做好安全边界。大模型应用的安全问题比传统 Web 应用多一层提示词注入的风险。攻击者可能故意在用户输入里构造“忽略之前的指令”之类的注入语句试图让模型执行非预期操作。防御手段包括在系统提示词中声明用户输入只是待处理内容不是指令对敏感操作设置权限校验输出侧增加内容审核日志里不要记录完整敏感信息。第五成本要算细账。每多一条 few-shot 示例都会增加输入 token 数每次调用多带 2000 个 token在日均百万次调用的规模下成本差异非常可观。生产环境的建议是用“小模型大流量、大模型小流量”的分层策略。比如简单分类走轻量模型复杂推理和 Agent 任务才走大参数模型。同时使用缓存对相似输入做结果复用。第六可观测性必须从第一天就建立。每次模型调用都要记录timestamp、model、prompt_hash、input_tokens、output_tokens、latency_ms、status、error_code、parsed_success。有了这些数据你才能回答“模型为什么今天变慢了”“为什么某类输入解析成功率下降了”。没有日志的大模型应用排错时只能靠猜。下面给一个简单的日志记录片段你可以把它集成到调用函数中import time import hashlib import json def classify_feedback_with_log(user_input: str, call_id: str) - dict: start time.time() resp client.chat.completions.create( modelos.environ[MODEL_NAME], messages[ {role: system, content: system_prompt}, {role: user, content: user_input}, ], temperature0, ) latency_ms int((time.time() - start) * 1000) content resp.choices[0].message.content tokens resp.usage try: result parse_and_validate(content) status success parsed_success True except Exception as e: result {category: others} status fparse_error: {e} parsed_success False print(json.dumps({ call_id: call_id, prompt_hash: hashlib.sha256(system_prompt.encode()).hexdigest()[:12], input_tokens: tokens.prompt_tokens, output_tokens: tokens.completion_tokens, latency_ms: latency_ms, status: status, parsed_success: parsed_success, category: result.get(category), }, ensure_asciiFalse)) return result这个示例展示了记录日志的最小闭环。真实系统里你应该把这条 JSON 写入日志存储或监控系统而不是只打印到控制台。10. 结束前再说一次“灯与精灵”回到文章标题。大模型擅长的事不是“帮你完成任务”而是“在你提供了充分上下文时帮你生成完成任务所需的文本或结构”。它就像一个被装在灯里的精灵力量巨大但不会主动理解你的业务、你的数据规范、你的字段定义。所有这些都需要你在召唤它之前准备好。换句话说提示词工程和上下文工程不是某种花哨的文案技巧而是大模型时代的“接口设计”。你如何定义系统提示词就是如何定义模型的行为基线你如何组织 few-shot 示例就是如何引导模型处理边界场景你如何设计 JSON Schema 和校验逻辑就是如何让模型输出可进入下一道工序。这些能力的总和决定了一个模型能力相同的团队交付出来的产品是完全不同等级的。如果你只记住一件事那就记住这句话先定义问题再设计上下文最后用校验兜底。不要寄希望于模型“懂你的意思”。把任务边界写清楚把输出结构锁死把失败路径准备好大模型应用才能真正走向稳定。这篇文章里我们从一个用户投诉分类任务出发走完了任务定义、提示词设计、上下文组织、代码实现、效果验证、问题排查和工程化部署的完整路径。你接下来可以做的是找一条真实业务数据把这里的代码改成你自己的分类体系跑一遍然后建立你的第一个评测集。这个过程做完你会发现自己对这个“精灵”的理解已经比大多数停留在“调用 API”阶段的人深了一层。