Copilot 最近一轮降本,真正让账单变小的不是换了更便宜的模型,而是四处不起眼的工程细节:选择性压缩工具输出、去掉 view 工具的行号前缀、压缩 task-tool 提示词、后台任务完成后直接交付结果。我沿同一思路把自家 agent 服务的 token 账单重新审计了一遍,在 4sapi(https://4sapi.com)上把四项改动全部落成了可观测、可对比的指标,这一期把整套方法完整拆开。

一、开篇:我为什么把降本盯在"每一轮 token"上

模型单价没动,套餐也没动,账单一两个月却悄悄涨了快一倍。排完所有请求明细后,问题不在某一次调用,而在 agent 的循环结构:每一次循环都要把系统提示、工具定义、历史对话全部重新带上路,上下文像滚雪球一样越滚越大。

我把一次典型 agent 任务的累计 token 和单次请求 token 对比过,差距通常在几十倍。也就是说,成本增长的主战场不在"我发了多少请求",而在"每一轮请求里带了多重的上下文"。Copilot 这轮降本改动的价值就在这:它没有动模型、没有动任务量,只是把每一轮重复带上路的载荷减了下来。

二、开篇痛点:agent 账单失控的三个信号

踩过坑之后,我总结出账单失控的三个典型信号,任何一个出现都该停下来审计:

这三个信号指向同一个事实:agent 的绝大多数 token 花在"反复运输同一批内容"上,而不是"思考新内容"上。Copilot 的四项改动,正好是这一问题的四个解法。

三、原理速览:agent 的 token 花在哪四个地方

先把一笔账理清楚:一轮 agent 循环的 token 由四部分构成。

一轮 agent 循环的 token 构成
    ├── 固定上下文:系统提示 + 工具定义(每轮都被完整带上)
    ├── 累积历史:逐轮追加,只增不减
    ├── 工具输出:文件内容、日志、搜索结果,动辄成百上千 token
    └── 本轮生成:模型回复与下一次工具调用参数

四部分里,只有"本轮生成"是真正的新增信息,其余三部分本质上都是重复运输。Copilot 的四项改动正好各打一个靶子:压缩 task-tool 提示词打"固定上下文",压缩工具输出与去掉行号打"工具输出",后台任务直接交付打"中间结果也被塞进上下文"的问题。下面逐项拆。

四、改动一:选择性压缩工具输出

agent 读文件、跑搜索、执行命令返回的内容,轻则几十行,重则整个文件或整页日志。这些文本一旦返回就被塞进历史,还会在后续轮次反复带上路,是 agent 上下文膨胀的最大来源。

Copilot 的做法不是一刀切断,而是选择性压缩:大块、低信息密度的输出(日志、搜索摘要、整文件内容)压缩成要点;小体积、决策关键的输出(退出码、错误首行、关键字段)原样保留。代码里我是按 token 阈值分级处理的:

def compress_tool_output(text: str, critical_lines: list[str] | None = None) -> str:
    """按体积分级:小输出原样保留,大输出压缩,关键行永远不压。"""
    head, tail = text.splitlines(), text.splitlines()
    if len(tail) <= 20:            # 小输出:原样
        return text
    keep = critical_lines or []
    body = head[:10] + ["..."] + tail[-5:]   # 头部 + 尾部
    return "\n".join(keep + body)

更关键的一步是时机:压缩发生在模型第一次读完之后。前一两轮保留原文让模型消化,一旦模型已经输出过基于这份内容的判断,就把历史里对应的工具输出替换成压缩版,后续轮次不再为原文重复买单。我在实测中对比过,长会话的上下文增量在这一步后明显变小。

五、改动二:去掉 view 工具的行号前缀

view 工具读文件时会给每一行加上数字前缀,像 123: def main():。行号是给人定位用的,模型主要读的是代码本身;一行一个数字前缀,读一遍万行文件就多出上万 token 的重复载荷,而且每读一次就重付一次。

把行号前缀去掉之后,同一条链路里:线下推理成本下降约 5%,线上用户日均推理成本下降约 3%。改动本身只有一行,收益却稳定落在账单上。

六、改动三:压缩 task-tool 提示词

task-tool 自带一段很长的说明性提示词,相当于每次循环都要把"这个工具怎么用"给模型完整讲一遍。说明写得太啰嗦,每轮循环就都为此付费。

把说明压缩到必要信息、删掉冗余措辞后:每轮省约 1300 token,每活跃小时归一化成本下降 2.9%。1300 token 单看不多,但 agent 任务动辄几十上百轮,乘起来就是单任务上万 token 的差距。

这给所有接入方一条通用经验:工具定义文本 = 工具名 + 参数 schema + 一句话说明。我审计自家服务时逐条检查过每个工具的 description,凡是超过三行的一律精简;不常用的工具从默认 schema 里移走,按需动态注入;稳定文本保持字节级一致,让 prompt cache 真正命中而不是被空白差异打断。

七、改动四:后台任务完成后直接交付结果

长任务(构建、批量拉取、测试)如果每步中间结果都塞回上下文,一轮比一轮贵,最后几轮的上下文里全是早已过期的中间状态。

解决思路是延迟交付:把任务丢到后台执行,完成后一次性把最终结果交付给模型和用户,中间过程不进上下文。这一项落地后:AI Credits 用量下降约 2.3%。我在自己的任务队列里照做——中间状态放进外部存储和任务队列,只在完成节点回填结果,并给每个后台任务配幂等 ID,避免重试把同一份输出反复计费。

八、四项改动的效果拆表

改动 打掉的成本点 量化效果 通用化方法
选择性压缩工具输出 大块低密度输出反复计费 长会话上下文增量明显变小 分级压缩 + 首读后替换历史
去掉 view 行号前缀 每行装饰性 token 线下推理成本降约 5%,线上日均降约 3% 只保留信息性内容,定位用独立功能
压缩 task-tool 提示词 固定上下文冗余 每轮省约 1300 token,每活跃小时归一化成本降 2.9% 精简工具描述,稳定文本吃缓存
后台任务直接交付 中间结果塞回上下文 AI Credits 用量降约 2.3% 延迟交付 + 幂等任务 ID

四个数字都不大,但方向一致:agent 成本是可以被工程手段系统性压下来的,不需要换模型。

九、接入 4sapi:用量统计与计费视角

落地的前提是能看到每一笔 token 花在哪。我在 4sapi(https://4sapi.com)统一接入上游模型,把鉴权、格式转换、限流和用量聚合收进一个网关,请求流向如下:

我的应用 / agent 循环
    │  OpenAI 兼容格式
    v
4sapi 网关(https://4sapi.com)
    │  统一鉴权 / 格式 / 限流 / 用量聚合
    v
上游大模型 API

Python 接入采用 OpenAI 兼容客户端,base_url 指向网关,并在每次响应里把 usage 逐条落库:

import os
import time
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["4SAPI_API_KEY"],
    base_url="https://4sapi.com/v1",  # 统一网关地址
)

class TokenAuditor:
    """把每一次调用的 token 明细记下来,供按任务聚合。"""
    def __init__(self):
        self.usage_log = []

    def record(self, tag: str, resp) -> dict:
        u = resp.usage
        entry = {
            "tag": tag,
            "ts": time.time(),
            "prompt_tokens": u.prompt_tokens,
            "completion_tokens": u.completion_tokens,
            "total_tokens": u.total_tokens,
        }
        self.usage_log.append(entry)
        return entry

auditor = TokenAuditor()

def run_agent_turn(tag: str, messages: list, tools: list | None = None):
    resp = client.chat.completions.create(
        model="gpt-4.1",
        messages=messages,
        tools=tools,
    )
    return resp, auditor.record(tag, resp)

任务结束后按 tag 聚合,直接得到单次 agent 运行的累计 token 与估算成本:

def summarize_task(tag: str) -> dict:
    rows = [e for e in auditor.usage_log if e["tag"] == tag]
    total = sum(r["total_tokens"] for r in rows)
    return {
        "task": tag,
        "calls": len(rows),
        "total_tokens": total,
        "est_input_元": sum(r["prompt_tokens"] for r in rows) / 1_000_000 * 15.0,
        "est_output_元": sum(r["completion_tokens"] for r in rows) / 1_000_000 * 60.0,
    }

4sapi 的用量统计面板按模型、按任务、按时间聚合,配上网关的限流与配额,我能把每一个 change 前后的账单差异精确归因到具体改动——这正是四项降本改动能否被量化的前提。

十、token 审计:把四项改动翻译成指标

没有指标,优化就是凭感觉。我固定维护五个指标,全部来自 usage 明细的二次计算:

告警规则也简单:单任务累计 token 环比上涨超过 20%、工具输出占比持续高位、每活跃小时成本偏离基线,三者命中任何一个就回查最近一次工具或提示词改动。每项改动上线前,我用同一组评测任务跑优化前后两版,同时对比累计 token 与任务完成度,确保省钱不以丢质量换。

十一、可复制的工程实践清单

十二、成本与风险提示

总结

Copilot 这轮降本改动最值得借鉴的不是某个具体数字,而是思路:agent 的账单大头在"反复运输"而不在"思考",所以省钱不一定要换模型,而是把每一轮上下文里不必要的东西减掉——压缩工具输出、去掉装饰前缀、精简工具提示词、延迟交付中间结果,四招叠加,单轮就省下约 1300 token。我在 4sapi(https://4sapi.com)上把这几招做成了可对比的指标,改一处、看一处账单,成本优化就变成了可以复盘的工程流程,而不是拍脑袋。欢迎在评论区发表想法,一起聊聊各自的 agent 账单到底花在哪、又省下了哪一笔。