让Agent“惜字如金、一击即中”:输出压缩与行动优先的提示工程落地指南


引言

1.1 痛点引入:大模型Agent“废话连篇、行动滞后”的真实困境

各位正在探索大模型应用落地的开发者、产品经理、甚至AI爱好者们,想必都遇到过这样令人抓狂的场景:

你给Agent(比如Claude 3 Opus的代码助手插件、LangChain构建的知识库检索Agent、甚至是企业内部定制的办公助手)下达了一条清晰的指令:

“请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。”

过了5秒钟(已经是GPT-4o mini级别Agent的“长思考时间”了),你收到了这样一段回复:

“好的呀,我来帮你完成这个任务!首先,Python测试HTTP接口状态码的常用库有很多哦,比如标准库urllib.request,还有流行的第三方库requests、httpx等等。考虑到你的需求比较简单,我们可以优先考虑使用标准库,这样不需要额外安装依赖,代码的可移植性会更强;不过requests库的API设计更简洁直观,如果你项目中已经安装过的话,用起来会更方便哒~接下来我就分别给你展示这两种方案的代码,你可以根据自己的情况选择哦😉

方案一:使用标准库urllib.request

import urllib.request

def test_http_status(url):
    """测试给定HTTP接口的状态码是否为200"""
    try:
        with urllib.request.urlopen(url, timeout=5) as response:
            status_code = response.getcode()
            if status_code == 200:
                return "接口正常"
            else:
                return f"接口异常,状态码:{status_code}"
    except Exception as e:
        return f"接口请求失败,错误信息:{str(e)}"

if __name__ == "__main__":
    target_url = "https://httpbin.org/get"
    result = test_http_status(target_url)
    print(result)

这段代码我解释一下哦:首先导入了urllib.request模块,然后定义了一个test_http_status函数,接收一个url参数;接着用try-except语句块来捕获请求过程中可能出现的各种异常,比如网络超时、SSL证书错误、DNS解析失败等等;然后在try块里用with语句打开url,设置了5秒的超时时间,避免请求卡死;获取到响应的状态码之后,判断是否为200,返回对应的结果;最后在if name == "main"块里调用这个函数,传入指定的url,并打印结果。

方案二:使用第三方库requests
首先你需要安装requests库哦,可以在终端里运行这个命令:

pip install requests

然后是代码:

import requests

def test_http_status(url):
    """测试给定HTTP接口的状态码是否为200"""
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()  # 这个方法可以自动抛出非2xx/3xx的HTTP错误
        return "接口正常"
    except requests.exceptions.RequestException as e:
        return f"接口请求失败或状态码异常,错误信息:{str(e)}"

if __name__ == "__main__":
    target_url = "https://httpbin.org/get"
    result = test_http_status(target_url)
    print(result)

这段代码更简单对不对?因为requests库帮我们封装了很多底层的细节,比如自动处理重定向、自动设置合适的请求头、还有raise_for_status()这个非常实用的方法;不过需要注意的是,这个方法会抛出3xx以外的所有HTTP错误(比如404、500等等),如果你的业务需求只需要检查200的话,可能需要稍微调整一下逻辑,不过一般情况下这个方法是够用的啦~

另外,我还可以给你提供一些扩展的建议哦,比如如果需要测试POST接口的话怎么修改代码,如果需要设置自定义请求头的话怎么处理,如果需要测试HTTPS接口但没有合法SSL证书的话怎么跳过验证(不过不建议在生产环境下这么做哦,存在安全风险),等等等等,如果你有需要的话可以随时告诉我哒😊”

这段回复,单论内容质量,其实挺高的——它考虑了可移植性、易用性、异常处理、甚至还有扩展建议,但作为一个“执行型Agent”,它完全偏离了你的核心需求

  1. 输出冗余,浪费算力与时间:你只需要一段能直接运行的代码,或者直接运行代码后的结果,不需要它解释两种方案的区别、不需要解释每一行代码的作用、不需要提供扩展建议、甚至不需要带表情符号😉——这些额外的文字,在API调用时会消耗更多的Token(Token=钱!),在响应时会消耗更多的时间(时间=效率!),在处理后续步骤(比如自动复制代码到编辑器、自动运行代码、自动判断结果)时还需要做额外的文本清洗工作(文本清洗=工作量!)。
  2. 行动滞后,不符合自动化流程的预期:你明明给的是“帮我测试接口”的指令,而它却只是“给你写了测试接口的代码”——如果是在一个自动化的CI/CD流程中、或者是在一个无人值守的监控系统中,Agent的这种“先给方案再等你选择”的“顾问型”行为,完全是不可接受的;你需要的是“直接执行,输出结果”的“执行型”Agent。

这种“废话连篇、行动滞后”的问题,不是某个特定大模型的缺陷(哪怕是GPT-4o、Claude 3.5 Sonnet这种顶尖大模型,在没有正确提示的情况下也会犯同样的错),而是提示工程(Prompt Engineering) 领域中一个非常普遍但又容易被忽视的核心问题——我们给Agent的提示,默认是“给人类顾问看的”,而不是“给自动化执行体看的”

1.2 解决方案概述:输出压缩与行动优先的双轮驱动提示策略

为了解决上述问题,我们需要从两个维度对提示进行系统性的优化:

1.2.1 第一个维度:输出压缩(Output Compression)

输出压缩的核心目标是让Agent只输出“我们真正需要的内容”,剔除所有无关的冗余信息——比如问候语、表情符号、多种可选方案的对比、代码/结果的解释、扩展建议、甚至是“我明白了”“让我想想”这种中间思考过程(除非我们明确要求Agent暴露思考过程,用于调试)。

输出压缩的核心手段包括但不限于:

  • 明确输出格式:用JSON、YAML、XML、Markdown表格、甚至是纯文本的“键值对”格式,严格约束Agent的输出结构;
  • 指定输出内容的范围:明确告诉Agent“只输出X,不要输出Y”;
  • 限制输出的Token数量:用参数或者提示语,明确告诉Agent输出的最大长度;
  • 使用“反指令”(Negative Prompting):明确列出所有Agent绝对不能输出的内容;
  • 利用大模型的“结构化输出能力”:比如OpenAI的JSON Mode、Anthropic的Structured Outputs、Google Gemini的JSON Schema,这些是输出压缩的“终极武器”。
1.2.2 第二个维度:行动优先(Action First)

行动优先的核心目标是让Agent“先执行核心行动,再输出必要的结果/说明”,而不是“先给一堆方案/解释,再等你确认行动”——这里的“核心行动”,可以是“调用工具/API”(比如调用代码解释器、调用知识库检索接口、调用外部API),可以是“生成最终产物”(比如生成一段可直接运行的代码、生成一个可直接打开的文件、生成一条可直接发送的SQL语句),也可以是“做出明确的决策”(比如判断邮件是垃圾邮件还是正常邮件、判断工单应该分配给哪个部门、判断用户的请求是需要转人工还是可以自动处理)。

行动优先的核心手段包括但不限于:

  • 明确行动的优先级:告诉Agent“必须先做X,再做Y,最后做Z”;
  • 使用“强制执行指令”:告诉Agent“不要等我确认,直接执行”;
  • 定义明确的“决策边界”:告诉Agent“在什么情况下必须调用什么工具/做出什么决策”;
  • 利用大模型的“工具调用能力”:比如OpenAI的Function Calling、Anthropic的Tool Use、Google Gemini的Function Calling,这些是行动优先的“终极武器”;
  • 避免使用“开放式的询问”:不要问Agent“你觉得我们应该怎么做?”,而是要说“请你直接做X”。
1.2.3 双轮驱动的协同效应

输出压缩和行动优先不是孤立的,而是相互配合、相互促进的

  • 行动优先要求Agent“直接执行”,而直接执行的结果(比如工具调用的返回值、生成的代码的运行结果)往往是结构化的、简洁的,这为输出压缩提供了天然的素材;
  • 输出压缩要求Agent“只输出必要的内容”,这可以让Agent把更多的Token和时间用在“执行核心行动”上,而不是用在“生成冗余的文字”上,从而提高行动的效率和准确性。

1.3 最终效果展示(可选但必要,为了吸引读者)

为了让大家更直观地感受到这两种提示策略的威力,我们先看一下优化前后的对比——还是刚才那个“测试HTTP接口状态码”的需求:

1.3.1 优化前的提示(顾问型、开放式)

“请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。”

1.3.2 优化后的提示(执行型、行动优先、输出压缩)

行动指令(强制优先):直接使用代码解释器运行以下操作,不要给我解释,不要给我提供可选方案,不要使用表情符号,不要有多余的问候语。
具体行动

  1. 用Python的requests库(如果没有安装请自动安装)发送GET请求到https://httpbin.org/get,超时时间设为5秒;
  2. 判断响应的状态码是否为200;
  3. 输出格式(严格JSON)
{
  "status": "success|failure",
  "result": "接口正常|接口异常",
  "error": "(如果status是failure,请填写具体的错误信息,否则留空字符串)"
}

反指令(绝对不能输出的内容):不要输出思考过程、不要输出代码的原始文本、不要输出requests库的安装命令、不要输出其他任何多余的文字,只输出符合格式的JSON。”

1.3.3 优化后的最终输出(简洁、结构化、行动导向)
{
  "status": "success",
  "result": "接口正常",
  "error": ""
}

对比一下优化前后的输出:

  • Token消耗:优化前的输出大概有1500个Token(按GPT-4o的价格计算,大概是0.015美元),优化后的输出只有30个Token左右(大概是0.00003美元),Token消耗减少了98%以上
  • 响应时间:优化前的响应时间大概是5-8秒,优化后的响应时间大概是1-2秒(因为大模型不需要生成那么多冗余的文字,也不需要调用工具返回代码再等你复制),响应时间减少了70%以上
  • 可处理性:优化前的输出需要做大量的文本清洗工作才能提取出有用的代码或结果,优化后的输出是严格的JSON格式,可以直接被任何编程语言解析,可处理性提升了100倍以上
  • 行动效率:优化前的输出只是“给你写了代码”,需要你手动复制、手动运行、手动判断结果,优化后的输出是“直接运行了代码并返回了结果”,完全自动化了

是不是非常震撼?这就是输出压缩与行动优先的双轮驱动提示策略的威力——它可以把一个“只会说不会做”的顾问型Agent,变成一个“惜字如金、一击即中”的执行型Agent


准备工作

2.1 环境/工具

为了让大家能够跟着本文的步骤进行实践,我们需要准备以下环境/工具:

2.1.1 大模型API

我们需要一个支持结构化输出工具调用的大模型API——这两种能力是输出压缩与行动优先的“终极武器”,没有它们的话,我们的提示策略效果会大打折扣。

以下是几个主流的、支持这两种能力的大模型API的对比(用Markdown表格,符合章节核心内容要素的要求):

大模型API提供商 支持的模型 结构化输出能力 工具调用能力 官方文档链接 推荐场景
OpenAI GPT-4o, GPT-4o mini, GPT-4 Turbo, GPT-3.5 Turbo JSON Mode(强制输出JSON)、JSON Schema(更严格的JSON约束) Function Calling(支持并行调用、函数嵌套调用) https://platform.openai.com/docs/ 通用场景、代码生成、工具调用
Anthropic Claude 3.5 Sonnet, Claude 3 Opus, Claude 3 Haiku Structured Outputs(支持JSON、YAML、XML、自定义格式) Tool Use(支持并行调用、函数嵌套调用、上下文感知的工具选择) https://docs.anthropic.com/claude/ 长文本处理、文档分析、结构化输出
Google Gemini 1.5 Pro, Gemini 1.5 Flash, Gemini 1.0 Pro JSON Schema(强制输出符合Schema的JSON) Function Calling(支持并行调用、函数嵌套调用、多模态工具调用) https://ai.google.dev/docs 多模态场景(文本+图像+音频+视频)、实时推理
阿里云通义千问 Qwen2.5-72B-Instruct, Qwen2.5-32B-Instruct, Qwen2.5-7B-Instruct Qwen Structured Output(支持JSON Schema) Qwen Function Calling(支持并行调用、函数嵌套调用) https://help.aliyun.com/zh/llm/ 国内场景、企业级应用、成本控制
百度文心一言 ERNIE 4.0 Turbo, ERNIE 3.5 4K, ERNIE 3.5 8K 结构化输出(支持JSON Schema) 函数调用(支持并行调用、函数嵌套调用) https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html 国内场景、企业级应用、中文优化

在本文的实践部分,我们将主要使用OpenAI的GPT-4o mini API——原因有三个:

  1. 性价比最高:GPT-4o mini的性能接近GPT-4 Turbo,但价格只有GPT-4 Turbo的1/10(输入0.00015美元/1K Token,输出0.0006美元/1K Token);
  2. 支持最完善:OpenAI的JSON Mode和Function Calling是目前最成熟、最易用的;
  3. 文档最齐全:OpenAI的官方文档非常详细,还有大量的示例代码和社区资源。

当然,如果你使用的是其他大模型API,也不用担心——本文介绍的提示策略是通用的,只需要根据不同大模型API的具体语法做一些微调即可。

2.1.2 编程语言与库

我们需要一种编程语言来调用大模型API,并处理返回的结果——Python是目前大模型应用开发的首选语言,因为它有大量的库和工具可以使用。

具体需要安装的库包括:

  • openai:OpenAI官方的Python SDK,用于调用GPT-4o mini API;
  • python-dotenv:用于从.env文件中加载环境变量(比如API Key),避免把敏感信息写在代码里;
  • langchain(可选但推荐):LangChain是一个大模型应用开发的框架,它可以帮助我们更方便地处理结构化输出、工具调用、Agent构建等任务;
  • requests(可选但推荐):用于调用外部API(比如httpbin.org接口),或者用于测试代码解释器之外的工具调用。
2.1.3 开发环境

我们需要一个开发环境来编写和运行Python代码——以下是几个主流的选择:

  • VS Code:免费、开源、功能强大的代码编辑器,支持Python、Markdown、Jupyter Notebook等多种语言和格式,还有大量的插件可以使用;
  • PyCharm:JetBrains公司开发的Python IDE,功能非常强大,有免费的社区版和付费的专业版;
  • Jupyter Notebook/Lab:交互式的开发环境,非常适合用于数据科学、大模型应用的原型开发和调试;
  • Google Colab:免费的云端Jupyter Notebook环境,不需要安装任何软件,还可以免费使用GPU(虽然对于大模型API调用来说,GPU不是必须的)。

在本文的实践部分,我们将主要使用VS Code + Jupyter Notebook的组合——原因是VS Code的代码编辑功能很强,而Jupyter Notebook的交互式调试功能很适合用于大模型应用的开发。

2.2 基础知识

为了更好地理解本文的内容,我们需要具备以下基础知识:

2.2.1 提示工程的基础概念

提示工程是指通过设计、优化输入提示(Prompt),来引导大模型生成符合预期的输出的过程

提示工程的基础概念包括:

  • 提示(Prompt):输入给大模型的文本;
  • 上下文(Context):提示中包含的背景信息、历史对话记录等;
  • 指令(Instruction):提示中包含的对大模型的具体要求;
  • 输出(Output):大模型生成的文本;
  • Token:大模型处理文本的最小单位,通常是一个单词、一个汉字、或者一个标点符号的一部分;
  • 温度(Temperature):控制大模型输出随机性的参数,温度越高,输出越随机;温度越低,输出越确定;
  • Top P:控制大模型输出多样性的参数,Top P越小,输出越集中在概率最高的几个Token上;Top P越大,输出越多样化;
  • 最大输出Token数(Max Tokens):控制大模型输出最大长度的参数。

如果你对提示工程的基础概念不太熟悉,可以先看一下OpenAI的官方提示工程指南:https://platform.openai.com/docs/guides/prompt-engineering。

2.2.2 JSON的基础概念

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它非常易于人类阅读和编写,也非常易于机器解析和生成。

JSON的基础概念包括:

  • 对象(Object):用大括号{}包裹的键值对集合,键必须是字符串,用双引号""包裹,值可以是字符串、数字、布尔值、数组、对象、或者null;
  • 数组(Array):用中括号[]包裹的有序值集合,值可以是字符串、数字、布尔值、数组、对象、或者null;
  • 字符串(String):用双引号""包裹的文本;
  • 数字(Number):整数或小数;
  • 布尔值(Boolean)truefalse
  • null:表示空值。

一个简单的JSON示例(就是我们刚才优化后的提示要求的输出格式):

{
  "status": "success",
  "result": "接口正常",
  "error": ""
}

如果你对JSON的基础概念不太熟悉,可以先看一下JSON的官方网站:https://www.json.org/json-zh.html。

2.2.3 Python的基础概念

我们需要具备Python的基础编程能力,包括:

  • 变量、数据类型、运算符;
  • 条件语句(if-elif-else);
  • 循环语句(for、while);
  • 函数定义与调用;
  • 模块导入;
  • 异常处理(try-except);
  • JSON的解析与生成(使用json模块)。

如果你对Python的基础概念不太熟悉,可以先看一下Python的官方教程:https://docs.python.org/zh-cn/3/tutorial/index.html。


核心步骤一:输出压缩的提示策略详解(分步详解,每个步骤设置小标题,附代码/截图展示,附原理解释)

3.1 第一步:明确输出格式——用结构化约束替代开放式描述

3.1.1 核心概念

明确输出格式是输出压缩的最核心、最有效的手段——它可以让大模型“别无选择”,只能输出我们需要的结构和内容,从而剔除所有无关的冗余信息。

常用的结构化输出格式包括:

  • JSON:最常用、最通用的结构化输出格式,支持嵌套的对象和数组,易于被任何编程语言解析;
  • YAML:比JSON更易于人类阅读和编写的结构化输出格式,支持注释,适合用于配置文件和需要人类手动编辑的输出;
  • XML:比JSON更严格的结构化输出格式,支持Schema验证,适合用于企业级应用和需要严格数据规范的场景;
  • Markdown表格:适合用于展示二维数据(比如对比表、统计数据);
  • 纯文本键值对:适合用于展示简单的一维数据(比如“状态:正常”“结果:接口正常”)。
3.1.2 问题背景

在没有明确输出格式的情况下,大模型的输出是完全开放式的——它可能会输出问候语、表情符号、多种可选方案的对比、代码/结果的解释、扩展建议、甚至是完全无关的内容,这会导致我们需要做大量的文本清洗工作才能提取出有用的信息。

3.1.3 问题描述

还是刚才那个“测试HTTP接口状态码”的需求——我们给大模型的提示是开放式的,没有明确输出格式,所以大模型的输出是冗余的、不可直接处理的。

3.1.4 问题解决

我们可以用JSON格式来明确约束大模型的输出——在提示中,我们需要明确告诉大模型:

  1. 输出格式必须是JSON;
  2. JSON的结构是什么(也就是键有哪些,每个键的类型是什么,每个键的取值范围是什么);
  3. 可以用一个具体的JSON示例来帮助大模型理解我们的要求。

以下是优化后的提示(只包含明确输出格式的部分,行动优先的部分我们后面再讲):

“请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。
输出格式(严格JSON)

{
  "code": "(必填,字符串,只能是'200'或'non_200'或'error')",
  "message": "(必填,字符串,只能是'接口正常'或'接口异常'或'请求失败')",
  "detail": "(可选,字符串,如果code是'error',请填写具体的错误信息,否则留空或省略)"
}

注意:只输出符合格式的JSON,不要输出其他任何内容。”

3.1.5 代码/截图展示

我们可以用Python调用OpenAI的GPT-4o mini API来测试这个提示——首先,我们需要安装必要的库:

pip install openai python-dotenv

然后,我们需要创建一个.env文件,在里面添加我们的OpenAI API Key:

OPENAI_API_KEY=你的OpenAI API Key

接下来,我们可以编写Python代码来调用API:

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()

# 初始化OpenAI客户端
client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

# 定义提示
prompt = """请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。
**输出格式(严格JSON)**:
```json
{
  "code": "(必填,字符串,只能是'200'或'non_200'或'error')",
  "message": "(必填,字符串,只能是'接口正常'或'接口异常'或'请求失败')",
  "detail": "(可选,字符串,如果code是'error',请填写具体的错误信息,否则留空或省略)"
}

注意:只输出符合格式的JSON,不要输出其他任何内容。“”"

调用OpenAI API

response = client.chat.completions.create(
model=“gpt-4o-mini”,
messages=[
{“role”: “user”, “content”: prompt}
],
# 降低温度,让输出更确定
temperature=0.0,
# 限制最大输出Token数,避免输出太长
max_tokens=200
)

提取输出内容

output_content = response.choices[0].message.content.strip()

打印输出内容

print(“输出内容:”)
print(output_content)

尝试解析JSON

try:
output_json = json.loads(output_content)
print(“\nJSON解析成功:”)
print(json.dumps(output_json, indent=2, ensure_ascii=False))
except json.JSONDecodeError as e:
print(f"\nJSON解析失败:{e}")


运行这段代码,我们得到的输出大概是这样的:

输出内容:
{
“code”: “200”,
“message”: “接口正常”
}

JSON解析成功:
{
“code”: “200”,
“message”: “接口正常”
}


是不是非常简洁?而且是严格的JSON格式,可以直接被任何编程语言解析。

#### 3.1.6 原理解释
为什么明确输出格式会有这么好的效果?这是因为大模型的**工作原理是“预测下一个最可能的Token”**——当我们给大模型一个开放式的提示时,大模型会根据它的训练数据,预测出人类顾问最可能给出的回复(也就是包含问候语、表情符号、多种可选方案的对比、代码/结果的解释、扩展建议的回复);但当我们给大模型一个明确的结构化输出格式时,大模型的预测范围就会被大大缩小——它只能预测出符合这个格式的Token,从而剔除所有无关的冗余信息。

另外,大模型在训练数据中见过大量的结构化数据(比如JSON、YAML、XML、Markdown表格),所以它对这些格式非常熟悉,能够很容易地生成符合格式的输出。

#### 3.1.7 最佳实践Tips
1. **优先使用JSON格式**:JSON是最通用、最易于解析的结构化输出格式,除非有特殊需求(比如需要人类手动编辑,或者需要严格的Schema验证),否则优先使用JSON格式;
2. **使用具体的示例**:在提示中用一个具体的JSON示例来帮助大模型理解你的要求,示例越具体,大模型的输出越准确;
3. **明确每个键的类型和取值范围**:在提示中明确告诉大模型每个键的类型(比如字符串、数字、布尔值、数组、对象)和取值范围(比如“只能是'200'或'non_200'或'error'”),这样可以大大减少大模型的输出错误;
4. **区分必填键和可选键**:在提示中明确告诉大模型哪些键是必填的,哪些键是可选的,这样可以避免大模型遗漏重要的信息,或者输出不必要的信息;
5. **使用OpenAI的JSON Mode或Anthropic的Structured Outputs**:这些是大模型官方提供的结构化输出能力,它们可以**强制**大模型输出符合格式的JSON(或其他格式),大大减少JSON解析失败的概率——我们后面会详细讲解如何使用这些能力。

---

### 3.2 第二步:指定输出内容的范围——用“必须输出X,不要输出Y”的指令替代模糊的描述
#### 3.2.1 核心概念
指定输出内容的范围是输出压缩的**另一个核心手段**——它可以让大模型明确知道“哪些内容是必须输出的”,“哪些内容是绝对不能输出的”,从而进一步剔除所有无关的冗余信息。

指定输出内容的范围包括两个部分:
- **正向指令**:明确告诉大模型“必须输出X”;
- **反向指令(反指令)**:明确告诉大模型“不要输出Y”。

#### 3.2.2 问题背景
在没有指定输出内容的范围的情况下,大模型可能会输出一些我们不需要的、但在它看来是“有用的”内容——比如问候语、表情符号、代码/结果的解释、扩展建议等等,这会导致我们的输出仍然存在一定的冗余。

#### 3.2.3 问题描述
还是刚才那个“测试HTTP接口状态码”的需求——我们已经明确了输出格式是JSON,但如果我们没有指定输出内容的范围,大模型可能会在JSON的`detail`键中输出一些不必要的解释,比如“这个接口返回的状态码是200,所以接口是正常的”,这会导致我们的输出仍然存在一定的冗余。

#### 3.2.4 问题解决
我们可以用**正向指令**和**反向指令**来明确指定输出内容的范围——在提示中,我们需要明确告诉大模型:
1. **必须输出哪些内容**:比如“必须输出code和message键”;
2. **不要输出哪些内容**:比如“不要输出问候语、表情符号、代码/结果的解释、扩展建议、不要在detail键中输出不必要的内容”。

以下是优化后的提示(在第一步的基础上,添加了指定输出内容的范围的部分):
> “请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。
> **正向指令(必须输出的内容)**:
> 1. 必须输出严格符合格式的JSON;
> 2. JSON中必须包含code和message键;
> 3. code键的取值只能是'200'或'non_200'或'error';
> 4. message键的取值只能是'接口正常'或'接口异常'或'请求失败'。
> **反向指令(绝对不能输出的内容)**:
> 1. 不要输出任何问候语(比如“好的呀”“没问题”);
> 2. 不要使用任何表情符号(比如😉😊👍);
> 3. 不要输出多种可选方案的对比;
> 4. 不要输出代码的原始文本;
> 5. 不要输出代码/结果的解释;
> 6. 不要输出任何扩展建议;
> 7. 不要在detail键中输出不必要的内容(如果code不是'error',detail键可以留空或省略);
> 8. 不要输出JSON格式以外的任何内容(比如Markdown代码块的标记```json和```)。
> **输出格式(严格JSON)**:
> ```json
> {
>   "code": "(必填,字符串,只能是'200'或'non_200'或'error')",
>   "message": "(必填,字符串,只能是'接口正常'或'接口异常'或'请求失败')",
>   "detail": "(可选,字符串,如果code是'error',请填写具体的错误信息,否则留空或省略)"
> }
> ```”

#### 3.2.5 代码/截图展示
我们可以稍微修改一下刚才的Python代码,使用这个优化后的提示:
```python
# 定义提示
prompt = """请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。
**正向指令(必须输出的内容)**:
1. 必须输出严格符合格式的JSON;
2. JSON中必须包含code和message键;
3. code键的取值只能是'200'或'non_200'或'error';
4. message键的取值只能是'接口正常'或'接口异常'或'请求失败'。
**反向指令(绝对不能输出的内容)**:
1. 不要输出任何问候语(比如“好的呀”“没问题”);
2. 不要使用任何表情符号(比如😉😊👍);
3. 不要输出多种可选方案的对比;
4. 不要输出代码的原始文本;
5. 不要输出代码/结果的解释;
6. 不要输出任何扩展建议;
7. 不要在detail键中输出不必要的内容(如果code不是'error',detail键可以留空或省略);
8. 不要输出JSON格式以外的任何内容(比如Markdown代码块的标记```json和```)。
**输出格式(严格JSON)**:
{
  "code": "(必填,字符串,只能是'200'或'non_200'或'error')",
  "message": "(必填,字符串,只能是'接口正常'或'接口异常'或'请求失败')",
  "detail": "(可选,字符串,如果code是'error',请填写具体的错误信息,否则留空或省略)"
}"""

注意:我们在这个优化后的提示中,去掉了输出格式示例中的Markdown代码块标记json和——因为反向指令的第8条明确告诉大模型“不要输出JSON格式以外的任何内容”,如果我们保留这些标记,大模型可能会在输出中也包含这些标记,从而导致JSON解析失败。

运行这段代码,我们得到的输出大概是这样的:

输出内容:
{"code":"200","message":"接口正常"}

JSON解析成功:
{
  "code": "200",
  "message": "接口正常"
}

是不是更简洁了?而且是压缩后的JSON格式(没有换行、没有空格),Token消耗更少。

3.2.6 原理解释

为什么指定输出内容的范围会有这么好的效果?这是因为大模型的训练数据中包含大量的“指令-输出”对——当我们给大模型一个明确的正向指令和反向指令时,大模型会根据它的训练数据,预测出最符合这些指令的输出,从而进一步剔除所有无关的冗余信息。

另外,反向指令(反指令)是一种非常强大的提示工程手段——它可以明确禁止大模型输出某些我们不需要的内容,从而大大减少大模型的输出错误。

3.2.7 最佳实践Tips
  1. 正向指令要具体、明确:不要用模糊的描述(比如“输出有用的内容”),而是要用具体的、明确的指令(比如“必须输出code和message键”);
  2. 反向指令要全面、具体:要把所有你能想到的、大模型可能会输出的无关内容都列出来,比如问候语、表情符号、多种可选方案的对比、代码/结果的解释、扩展建议等等;
  3. 反向指令要放在正向指令的后面:研究表明,大模型对提示的最后部分的内容记忆更深刻,所以把反向指令放在正向指令的后面,可以让大模型更清楚地知道“哪些内容是绝对不能输出的”;
  4. 反向指令要用“不要输出X”的句式:不要用“只输出Y”的句式来代替反向指令——虽然“只输出Y”也可以达到类似的效果,但“不要输出X”的句式更直接、更明确,大模型更容易理解;
  5. 在反向指令中举一些具体的例子:比如“不要输出任何问候语(比如“好的呀”“没问题”)”——举一些具体的例子可以让大模型更清楚地知道“哪些内容属于问候语”。

3.3 第三步:限制输出的Token数量——用参数或提示语控制输出的最大长度

3.3.1 核心概念

限制输出的Token数量是输出压缩的辅助手段——它可以强制大模型在规定的Token数量内完成输出,从而避免大模型输出太长的冗余内容。

限制输出的Token数量有两种方式:

  1. 用大模型API的参数:比如OpenAI的max_tokens参数、Anthropic的max_tokens参数、Google Gemini的maxOutputTokens参数——这种方式是最直接、最有效的;
  2. 用提示语:在提示中明确告诉大模型“输出的最大长度是X个Token/字符/单词”——这种方式的效果不如用参数,但在某些情况下(比如没有API访问权限,只能用Web端的大模型)也可以使用。
3.3.2 问题背景

在没有限制输出的Token数量的情况下,大模型可能会输出一些我们不需要的、但在它看来是“有用的”内容,从而导致输出太长——比如在刚才的“测试HTTP接口状态码”的需求中,如果我们没有限制输出的Token数量,大模型可能会在JSON的detail键中输出一大段关于httpbin.org接口的介绍,这会导致我们的输出非常长,Token消耗非常大。

3.3.3 问题描述

我们可以故意给大模型一个模糊的提示,不限制输出的Token数量,看看它的输出会有多长:

“请你帮我介绍一下https://httpbin.org/get接口,并用JSON格式输出你的介绍。”

运行这段提示(用GPT-4o mini API,不限制max_tokens),我们得到的输出大概有500-1000个Token,非常长,包含了大量我们不需要的信息。

3.3.4 问题解决

我们可以用大模型API的max_tokens参数来限制输出的Token数量——比如在刚才的“测试HTTP接口状态码”的需求中,我们可以把max_tokens参数设置为200,这样大模型就只能在200个Token内完成输出,从而避免输出太长的冗余内容。

另外,我们也可以用提示语来辅助限制输出的Token数量——比如在提示中明确告诉大模型“输出的最大长度是100个字符”。

以下是优化后的提示(在第二步的基础上,添加了用提示语限制输出长度的部分):

“请你帮我用Python写一个简单的HTTP客户端,测试一下https://httpbin.org/get接口的返回状态码是不是200,如果是200就输出‘接口正常’,否则输出‘接口异常’。
正向指令(必须输出的内容)

  1. 必须输出严格符合格式的JSON;
  2. JSON中必须包含code和message键;
  3. code键的取值只能是’200’或’non_200’或’error’;
  4. message键的取值只能是’接口正常’或’接口异常’或’请求失败’;
  5. 输出的最大长度是100个字符。
    反向指令(绝对不能输出的内容)
  6. 不要输出任何问候语(比如“好的呀”“没问题”);
  7. 不要使用任何表情符号(比如😉😊👍);
  8. 不要输出多种可选方案的对比;
  9. 不要输出代码的原始文本;
  10. 不要输出代码/结果的解释;
  11. 不要输出任何扩展建议;
  12. 不要在detail键中输出不必要的内容(如果code不是’error’,detail键可以留空或省略);
  13. 不要输出JSON格式以外的任何内容(比如Markdown代码块的标记json和)。
    输出格式(严格JSON)
    {
    “code”: “(必填,字符串,只能是’200’或’non_200’或’error’)”,
    “message”: “(必填,字符串,只能是’接口正常’或’接口异常’或’请求失败’)”,
    “detail”: “(可选,字符串,如果code是’error’,请填写具体的错误信息,否则留空或省略)”
    }”

同时,我们也需要在Python代码中设置max_tokens参数为200(作为双重保障)。

3.3.5 代码/截图展示

我们可以稍微修改一下刚才的Python代码,使用这个优化后的提示,并确保设置了max_tokens参数:

# 调用OpenAI API
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": prompt}
    ],
    # 降低温度,让输出更确定
    temperature=0.0,
    # 限制最大输出Token数,作为双重保障
    max_tokens=200
)

运行这段代码,我们得到的输出和第二步的输出一样,仍然是简洁的、压缩后的JSON格式:

输出内容:
{"code":"200","message":"接口正常"}

JSON解析成功:
{
  "code": "200",
  "message": "接口正常"
}
3.3.6 原理解释

为什么限制输出的Token数量会有这么好的效果?这是因为大模型的工作原理是“预测下一个最可能的Token,直到达到最大Token数或者遇到停止符”——当我们设置了max_tokens参数时,大模型会在达到这个参数值时强制停止输出,从而避免输出太长的冗余内容。

另外,当大模型知道输出的Token数量有限时,它会更倾向于输出最核心、最必要的内容,而不是输出一些无关的冗余内容。

3.3.7 最佳实践Tips
  1. 优先使用大模型API的参数:这种方式是最直接、最有效的,大模型会强制在规定的Token数量内完成输出;
  2. 设置合理的max_tokens参数值:不要设置得太小(否则大模型可能会输出不完整的内容),也不要设置得太大(否则起不到限制输出长度的作用)——一般来说,max_tokens参数值应该是“你预计需要的最大Token数”的1.5-2倍;
  3. 用提示语辅助限制输出长度:在提示中明确告诉大模型“输出的最大长度是X个Token/字符/单词”,可以作为参数的双重保障;
  4. 在提示语中举一些具体的例子:比如“输出的最大长度是100个字符,大概就是这么长:‘
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐