面试-Agent如何压缩上下文

0 背景

https://fairy-study.blog.csdn.net/article/details/159253372?spm=1001.2014.3001.5502

https://fairy-study.blog.csdn.net/article/details/159253478?spm=1001.2014.3001.5502

按需加载上下文:
https://fairy-study.blog.csdn.net/article/details/159253494?spm=1001.2014.3001.5502

一、核心问题:上下文「无限膨胀」导致Agent崩溃

第五季的「按需技能加载」解决了系统提示的代币浪费,但新的致命问题出现:

Agent在执行复杂任务时(比如读取30个Python文件、运行20次bash命令),所有工具调用的原始结果都会永久留在上下文列表中,导致:

  1. 代币超限:上下文窗口有固定上限(比如100K代币),大量原始文件内容/命令输出会快速占满窗口,API调用直接失败;
  2. 推理效率低:模型需要在海量冗余信息中找核心,响应变慢、易出错;
  3. 无法长会话:几轮交互后上下文就满了,Agent无法完成跨数十轮的复杂任务(比如重构整个代码库)。

简单说:第五季只解决了「输入侧(系统提示)」的代币浪费,没解决「输出侧(上下文积累)」的膨胀问题------S06的核心就是补这个缺口。

二、核心解决方案:三层上下文压缩策略(渐进式瘦身)

核心逻辑是「轻量→自动→手动 三层递进压缩」,在不丢失关键信息的前提下,把上下文「瘦身」到窗口限额内,支持无限长会话:

复制代码
┌─────────────────────────────────────┐
│ 原始上下文(含大量工具原始结果)    │
└─────────────────────────────────────┘
        ↓ 每轮必执行(Layer 1:微压缩)
┌─────────────────────────────────────┐
│ 替换旧工具结果为占位符              │
│ 例:[Previous: used read_file]      │
│ 保留最近N轮工具结果,旧的只留标识   │
└─────────────────────────────────────┘
        ↓ 代币超阈值触发(Layer 2:自动压缩)
┌─────────────────────────────────────┐
│ 1. 完整对话保存到.transcripts/磁盘  │
│ 2. LLM摘要整个对话(保留核心信息)  │
│ 3. 上下文替换为「摘要+确认消息」    │
│ 例:[Compressed] 已读取agents/下15个文件,均为Python脚本... │
└─────────────────────────────────────┘
        ↓ 模型手动调用(Layer 3:手动压缩)
┌─────────────────────────────────────┐
│ 模型主动调用compact工具,触发和Layer2相同的摘要逻辑 │
│ 用于代币未超限但模型想主动瘦身的场景 │
└─────────────────────────────────────┘

三、核心代码逻辑(三层压缩的落地)

1. Layer 1:Micro-Compact(微压缩,每轮必执行)
python 复制代码
import json
from pathlib import Path
import time
from anthropic import Anthropic

# 配置:保留最近3轮工具结果,旧的替换为占位符
KEEP_RECENT = 3
client = Anthropic()

def micro_compact(messages: list) -> list:
    # 第一步:找出所有tool_result类型的消息片段
    tool_results = []
    for msg_idx, msg in enumerate(messages):
        # 只处理user角色的tool_result(工具执行结果都是user角色返回给模型的)
        if msg["role"] == "user" and isinstance(msg.get("content"), list):
            for part_idx, part in enumerate(msg["content"]):
                # 定位tool_result类型的片段
                if isinstance(part, dict) and part.get("type") == "tool_result":
                    # 提取工具名称(比如read_file/bash)
                    tool_name = part.get("content", "").split(":")[0]  # 简化逻辑,实际会解析block.name
                    tool_results.append((msg_idx, part_idx, part, tool_name))
    
    # 第二步:如果工具结果超过3轮,替换旧的为占位符
    if len(tool_results) <= KEEP_RECENT:
        return messages  # 最近3轮内,不压缩
    
    # 只保留最后KEEP_RECENT个,前面的旧结果替换
    for msg_idx, part_idx, part, tool_name in tool_results[:-KEEP_RECENT]:
        # 只压缩长内容(短内容没必要)
        if len(part.get("content", "")) > 100:
            # 核心:用简短占位符替换海量原始内容
            messages[msg_idx]["content"][part_idx]["content"] = f"[Previous: used {tool_name}]"
    return messages

核心作用

  • 「静默压缩」:用户/模型无感知,每轮调用模型前自动执行;
  • 「最小侵入」:只替换旧的、长的工具结果,保留最近3轮的完整结果,不影响模型当前执行;
  • 「低成本」:纯字符串替换,无需调用LLM,不额外消耗代币。
2. Layer 2:Auto-Compact(自动压缩,代币超限触发)
python 复制代码
# 配置:代币阈值(比如50000)
TOKEN_THRESHOLD = 50000
TRANSCRIPT_DIR = Path(".transcripts")
TRANSCRIPT_DIR.mkdir(exist_ok=True)

# 辅助函数:估算上下文代币数(简化版,实际用Anthropic的token计算器)
def estimate_tokens(messages: list) -> int:
    text = json.dumps(messages)
    return len(text) // 4  # 粗略估算:1token≈4字符

def auto_compact(messages: list) -> list:
    # 第一步:保存完整对话到磁盘(防止信息丢失)
    timestamp = int(time.time())
    transcript_path = TRANSCRIPT_DIR / f"transcript_{timestamp}.jsonl"
    with open(transcript_path, "w", encoding="utf-8") as f:
        for msg in messages:
            # 每行一个JSON,方便后续恢复/查看
            f.write(json.dumps(msg, ensure_ascii=False, default=str) + "\n")
    
    # 第二步:调用LLM生成对话摘要(保留核心信息)
    summary_prompt = f"""
    Summarize this conversation for continuity, keep all key information:
    1. What tasks is the agent working on?
    2. Key results of tool calls (e.g., read 15 Python files in agents/)
    3. Unfinished steps or decisions
    Do NOT include full file contents or long command outputs.
    
    Conversation:
    {json.dumps(messages, ensure_ascii=False, default=str)[:80000]}  # 避免超长输入
    """
    response = client.messages.create(
        model=os.environ["MODEL_ID"],
        messages=[{"role": "user", "content": summary_prompt}],
        max_tokens=2000,  # 摘要长度限制
    )
    summary = response.content[0].text if hasattr(response.content[0], "text") else "(no summary)"
    
    # 第三步:替换整个上下文为「压缩标记+摘要」
    compressed_messages = [
        {"role": "user", "content": f"[Compressed]\n\n{summary}"},
        {"role": "assistant", "content": "Understood. Continuing."}
    ]
    return compressed_messages

核心作用

  • 「兜底压缩」:当上下文代币超阈值时,强制瘦身到几千代币;
  • 「信息保全」:完整对话保存到磁盘,摘要保留核心逻辑,不影响后续执行;
  • 「彻底瘦身」:把数万行的上下文替换为几百字的摘要,释放90%以上的窗口空间。
3. Layer 3:Manual-Compact(手动压缩,模型主动触发)
python 复制代码
# 工具映射中新增compact工具
TOOL_HANDLERS = {
    # ... 基础工具(bash/read/write/load_skill)
    "compact": lambda **kw: "Compressed conversation successfully."
}

# Agent循环整合三层压缩
def agent_loop(messages: list):
    while True:
        # Layer 1:每轮微压缩
        messages = micro_compact(messages)
        
        # Layer 2:代币超限则自动压缩
        if estimate_tokens(messages) > TOKEN_THRESHOLD:
            messages = auto_compact(messages)
        
        # 调用模型
        response = client.messages.create(
            model=os.environ["MODEL_ID"],
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        
        # 执行工具调用(省略基础逻辑)
        results = []
        manual_compact = False
        for block in response.content:
            if block.type == "tool_use":
                if block.name == "compact":
                    manual_compact = True  # 模型调用了compact工具
                # ... 执行其他工具 ...
        
        # Layer 3:模型手动触发压缩
        if manual_compact:
            messages = auto_compact(messages)
        
        # ... 后续逻辑 ...

核心作用

  • 「主动可控」:模型可在代币未超限但上下文杂乱时,主动调用compact工具瘦身;
  • 「复用逻辑」:和自动压缩共用auto_compact函数,保证摘要格式一致。

四、从第五季到第六季的核心变化(面试重点)

组成部分 第五季(按需技能加载) 第六季(三层压缩)
工具数量 5(基础+load_skill) 5(新增compact工具,总数不变)
上下文管理 无压缩,任由上下文膨胀 三层渐进式压缩(微/自动/手动)
旧工具结果处理 永久保留在上下文 微压缩替换为占位符
代币控制 仅控制技能加载的代币 全链路控制(微压缩+阈值自动压缩)
历史记录 仅存在内存,重启丢失 保存完整转录到.transcripts/磁盘
核心目标 减少「输入侧」代币浪费 解决「输出侧」上下文膨胀

五、执行流程示例(直观理解)

用户指令:「逐个读取agents/目录下的所有Python文件」

  1. 初始阶段 :模型调用read_file读取第1-3个文件,上下文保留完整内容(微压缩只保留最近3轮);
  2. 微压缩触发 :读取第4个文件时,第1个文件的read_file结果被替换为[Previous: used read_file]
  3. 自动压缩触发 :读取第20个文件后,上下文代币超50000,触发auto_compact
    • 完整对话保存到.transcripts/transcript_171xxx.jsonl
    • LLM生成摘要:「已读取agents/下20个Python文件,均为Agent相关脚本,包含s01-s05的工具调用逻辑」;
    • 上下文被替换为摘要+确认消息,代币从60000降到2000;
  4. 继续执行:模型基于摘要继续读取剩余文件,上下文始终保持在阈值内。

六、核心价值(面试答法)

第六季的三层压缩解决了Agent「长会话能力」的核心问题:

  1. 无限会话:通过渐进式压缩,上下文永远不会占满窗口,支持数十轮甚至上百轮的复杂任务;
  2. 信息不丢:微压缩保留最近结果,自动/手动压缩保存完整转录到磁盘,摘要保留核心逻辑;
  3. 成本可控:微压缩无额外代币消耗,自动/手动压缩的摘要消耗远低于保留原始内容;
  4. 无感知体验:压缩对用户/模型透明,不影响任务执行的连续性。

这也是工业级Agent的必备能力------面试时重点说清「三层压缩的递进逻辑」「微压缩的静默替换」「自动压缩的磁盘保全+摘要瘦身」,就能体现你对Agent内存/上下文管理的深度理解。

总结

第六季的核心是「上下文全生命周期管理」:

  1. 微压缩:「日常瘦身」,处理旧的长工具结果,低成本且无感知;
  2. 自动压缩:「应急兜底」,代币超限时彻底瘦身,保全核心信息;
  3. 手动压缩:「主动优化」,模型按需触发,适配复杂场景;
  4. 磁盘转录:「安全网」,所有历史不丢失,可追溯/可恢复。

对比第五季:第五季是「少拿」(按需加载技能),第六季是「少存」(压缩上下文),两者结合实现了代币和上下文的双重高效管理。

相关推荐
chushiyunen2 小时前
python3和python2的区别
开发语言·python
张张123y2 小时前
AI Agent Memory:从理论到实战,掌握长短期记忆的核心技术【2】
人工智能·python·langchain·transformer
漫随流水2 小时前
旅游推荐系统(基于用户的协同过滤recommendation.py)
python·django·旅游·协同过滤
沐硕2 小时前
Dietify 智能饮食推荐系统全解析 —— 当协同过滤遇上营养科学,构建你的私人饮食管家
spring boot·python·fastapi·多目标优化·饮食推荐·改进协同过滤
欣然~2 小时前
基于Python的自动化数据采集与语音播报系统设计与实现
python
小付爱coding2 小时前
跟着官网学LangChain【第02章:提示词和消息】
windows·python·langchain
菜鸡儿齐5 小时前
Unsafe方法学习
java·python·学习
老师好,我是刘同学8 小时前
Python执行命令并保存输出到文件
python
啵啵鱼爱吃小猫咪10 小时前
机械臂阻抗控制github项目-mujoco仿真
开发语言·人工智能·python·机器人