Agent上下文三级压缩

Agent上下文三级压缩


核心问题:Agent 的上下文窗口不是无限的

LLM 的上下文窗口是有限的(4K/128K/200K token),但用户对话可以无限长。

复制代码
问题演进过程:
  用户第 1 轮:"我想买特斯拉股票"                    → 上下文:1K token
  用户第 5 轮:"刚才说的股票现在怎么样了"              → 上下文:5K token
  用户第 20 轮:讨论新能源/美股/技术分析...            → 上下文:20K token
  用户第 50 轮:上下文超出 LLM 上限                   → ❌ 开始丢信息

当上下文满了,Agent 会丢失:
  1. 用户早期说过的偏好("我偏好稳健投资")
  2. 之前查过的信息("特斯拉股价 180 美元")
  3. 之前的决策结论("决定先买 100 股试试")

三级压缩的目标:
  在保证 Agent 信息完整的前提下,最大限度节省 token

三级压缩的设计哲学

复制代码
面试官问:"为什么需要三级压缩,一级不够吗?"

答:
  一级不够,因为不同类型的信息适合不同的压缩方式:

  - 最近对话(5 轮内):信息完整,语义连贯
    → 用滑动窗口直接丢弃最旧的,保留全部细节(第一级)

  - 中期对话(5-20 轮前):太长不适合全量保留
    → 但有关键决策和偏好不能丢
    → 用 LLM 生成摘要,保留因果链(第二级)

  - 长期偏好(跨会话):用户说了"我总是选最便宜的物流"
    → 这类偏好太重要,不能因一次对话结束就消失
    → 存入向量库,按需检索(第三级)

三层互相配合,按需触发,不浪费计算资源。

第一级:滑动窗口压缩(会话级)

原理

复制代码
对话队列:[msg1, msg2, msg3, ..., msgN]

滑动窗口大小 = 5 轮(经验值)

当第 6 轮结束时:
  队列左边出队 msg1(被丢弃)
  队列右边进队 msg6(新消息)

  保留:[msg2, msg3, msg4, msg5, msg6]

"被丢弃的 msg1 里的信息去哪了?" → 进入第二级压缩

为什么是 5 轮(面试高频追问,必须会答)

复制代码
面试官追问:"为什么是 5 轮?3 轮或 7 轮行不行?"

标准答案:
  "5 轮是工程经验值,来自实际业务数据的统计。

  3 轮的问题:
    - 对话太短,Agent 丢失太多上下文
    - 很多问题需要 3 轮以上才能完整描述(用户描述情况 → AI 追问细节 → 用户补充)
    - 3 轮 Agent 容易反复问用户已经说过的信息,体验差

  7 轮的问题:
    - Token 消耗显著增加,LLM 对中间部分注意力下降(Lost in the Middle)
    - 成本上涨,上下文越长 LLM 调用延迟越高
    - 7 轮以后信息密度往往下降(闲聊部分增多)

  5 轮的本质是'信息完整性和成本控制'的平衡点,
  实际项目中会根据平均对话长度和 LLM 调用成本动态调优。"

Java 对照:
  LinkedList 的窗口淘汰(超出容量时 removeFirst())
  或者 Guava Cache 的 LRU(最近最少使用)

代码实现

python 复制代码
"""
第一级压缩器:滑动窗口

业务背景:
  多轮对话进行时,超出窗口的历史消息直接丢弃。
  这是最快、最低成本的压缩方式。

设计思路:
  1. system prompt 永远保留(Agent 的角色锚定)
  2. 对话历史按轮次(user+assistant 配对)计算,不按单条消息
  3. 超出窗口的部分送去第二级压缩(不直接丢弃)

Java 对照:
  LinkedList.removeFirst() + 容量检查
  或者 Java NIO 的 ByteBuffer 循环缓冲区
"""


class SlidingWindowCompressor:
    """
    滑动窗口压缩器

    核心思想:
      用一个固定大小的窗口"扫描"对话历史,
      窗口内的是当前上下文,窗口外的是待压缩历史。
    """

    def __init__(self, window_size: int = 5):
        """
        Args:
            window_size: 窗口大小(单位:对话轮次)
                        为什么用"轮次"而不是"消息条数"?
                        因为一个完整的问题-回答才算一轮,
                        按消息条数会导致半轮对话被切分
        """
        self.window_size = window_size

    def compress(self, all_messages: list) -> dict:
        """
        执行第一级压缩

        Args:
            all_messages: 原始完整对话历史,格式:
                [
                    {"role": "system", "content": "你是客服助手"},
                    {"role": "user", "content": "查下我的订单"},
                    {"role": "assistant", "content": "好的,请提供订单号"},
                    ...
                ]

        Returns:
            {
                "recent_turns": [...],  # 保留的最近 N 轮(进入 LLM 上下文)
                "to_summarize": [...],  # 更早的历史(送去第二级压缩)
                "system_prompt": {...}   # system prompt(永不压缩)
            }

        处理步骤:
          1. 分离 system prompt(永远保留)
          2. 计算当前轮次(按 user+assistant 配对算一轮)
          3. 轮次 <= 窗口大小 → 不压缩,直接返回
          4. 轮次 > 窗口大小 → 保留最近 N 轮,其余送去第二级
        """
        # ----------------------------------------------------------
        # 步骤 1:分离 system prompt
        # 原因:system prompt 是 Agent 的"行为锚",压缩它会导致角色漂移
        #       且 system prompt 通常很短(< 1K token),压缩收益极低
        # ----------------------------------------------------------
        system_msgs = [m for m in all_messages if m["role"] == "system"]
        dialog_msgs = [m for m in all_messages if m["role"] != "system"]

        # ----------------------------------------------------------
        # 步骤 2:按轮次计算(每对 user + assistant = 一轮)
        # 坑点:不要按消息条数算,要按 user+assistant 配对算
        # ----------------------------------------------------------
        turns = self._group_into_turns(dialog_msgs)
        total_turns = len(turns)

        if total_turns <= self.window_size:
            # 对话轮次还没超过窗口,不需要压缩
            # 🔥 优化点:这里不需要调用 LLM,直接返回,节省成本
            return {
                "recent_turns": dialog_msgs,       # 全部保留
                "to_summarize": [],               # 没有要压缩的
                "system_prompt": system_msgs[0] if system_msgs else None
            }

        # ----------------------------------------------------------
        # 步骤 3:超过窗口大小,执行压缩
        # ----------------------------------------------------------
        # 最近 N 轮保留(进入 LLM 上下文)
        recent_turns = self._flatten_turns(turns[-self.window_size:])
        # 更早的历史送去第二级(不直接丢弃!)
        old_turns = self._flatten_turns(turns[:-self.window_size])

        return {
            "recent_turns": recent_turns,
            "to_summarize": old_turns,
            "system_prompt": system_msgs[0] if system_msgs else None
        }

    def _group_into_turns(self, messages: list) -> list:
        """
        将消息列表按轮次分组

        为什么按轮次分组:
          因为第二轮 user 的问题和第一轮 user 的问题是独立的,
          不能把第一轮 assistant 的尾巴留到第二轮的窗口里。

        算法:
          遍历消息,遇到 user 消息 → 开始新的一轮
          轮次结构:[{"user": "...", "assistant": "..."}, ...]
        """
        turns = []
        current_turn = {}

        for msg in messages:
            role = msg["role"]
            if role == "user":
                # user 消息标志新的一轮开始
                if current_turn and "user" in current_turn:
                    # 上一轮只有 user 没有 assistant(异常情况),保存它
                    turns.append(current_turn)
                current_turn = {"user": msg["content"]}
            elif role == "assistant" and "user" in current_turn:
                # assistant 消息紧跟当前的 user,进入同一轮
                current_turn["assistant"] = msg["content"]
            else:
                # 没有 user 消息就直接来的 assistant(异常),单独成轮
                if current_turn and "user" in current_turn:
                    turns.append(current_turn)
                current_turn = {"assistant": msg["content"]}

        if current_turn and current_turn.get("user"):
            turns.append(current_turn)

        return turns

    def _flatten_turns(self, turns: list) -> list:
        """
        将轮次列表还原为消息列表(方便后续拼接 prompt)

        Args:
            turns: [{"user": "...", "assistant": "..."}, ...]

        Returns:
            [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}, ...]
        """
        result = []
        for turn in turns:
            if "user" in turn:
                result.append({"role": "user", "content": turn["user"]})
            if "assistant" in turn:
                result.append({"role": "assistant", "content": turn["assistant"]})
        return result

第二级:LLM 摘要压缩(摘要级)

原理

复制代码
第一级被丢弃的历史(msg1):

  "用户:我想买特斯拉股票"
  "助手:特斯拉(TSLA)目前股价 180 美元,市盈率 65"
  "用户:我比较保守,先观望一下"
  "助手:了解,特斯拉近期波动较大,建议关注季报数据"
  "用户:好的,那苹果呢"
  ...

第一级的处理结果:
  - recent_turns:保留最近 5 轮
  - to_summarize:[msg1, msg2, ...] (被丢弃的历史)

第二级的任务:
  把 to_summarize 的内容压缩成一段摘要,
  保留关键信息(偏好、决策、结论),丢弃细节(闲聊、追问等)

为什么需要第二级(而不是直接存入向量库)

复制代码
面试追问:"直接用向量库存不行吗?为什么要摘要?"

答:
  1. 向量检索适合"按需查询",无法压缩"顺序信息"
     例:"用户先说想买特斯拉,助手推荐后,用户说先观望"
     这两句话有因果顺序,向量检索会丢失这个关系,摘要不会

  2. 向量检索的召回有误差(可能召回到相似但不相关的记忆)
     摘要里的信息是 LLM 确认过的"关键点",更可靠

  3. 第二级摘要作为第三级向量库的"索引"
     第三级存储的是摘要而非原始对话,这样:
     - 检索快(向量维度更低)
     - 召回准(摘要内容已经是精炼过的关键点)

  两层配合:摘要做浓缩,向量做检索,各司其职。

代码实现

python 复制代码
"""
第二级压缩器:LLM 摘要

业务背景:
  第一级滑动窗口丢弃了"更早的对话",
  但这些对话里可能有重要信息(用户偏好、决策结论、未完成的任务)。
  用 LLM 把这些对话压缩成一段摘要,保留关键信息。

设计思路:
  1. 摘要 prompt 必须规定格式(方便后续解析)
  2. 摘要必须包含"未完成的任务"(用户问过但还没解决的事)
  3. 如果对话中没有重要信息,返回空(避免引入无关内容)

Java 对照:
  MapReduce 的 Combiner 阶段(在 map 和 reduce 之间做局部汇总)
  或者 Java 日志框架的 log compaction(只保留最新状态)
"""

from langchain_openai import ChatOpenAI


class SummaryCompressor:
    """
    LLM 摘要压缩器

    为什么用 LLM 做摘要(而不是规则提取):
      1. 用户的表达多种多样,规则难以覆盖("我比较保守" vs "我求稳" vs "我风险承受低")
      2. LLM 能理解同义词和隐含意图
      3. 摘要质量高,且能捕捉"因果关系"
    """

    # ----------------------------------------------------------
    # 摘要 Prompt(这是核心,prompt 质量决定摘要质量)
    # ----------------------------------------------------------
    # 坑点:这个 prompt 是面试高频追问点,要能解释每一部分的作用
    SYSTEM_PROMPT = """你是一个对话摘要专家。将对话压缩成摘要。

必须保留的 4 类信息(按优先级排序):
1. 关键决策(用户做了什么选择/决定)
2. 已查到的信息(助手返回过什么重要答案)
3. 用户偏好(用户明确说过的喜好/习惯/限制)
4. 未完成的任务(用户问过但还没解决的事)

格式要求:
<summary>
意图:用户问了什么
决策:用户做了什么决定(或未决定)
偏好:用户的偏好是什么
未完成任务:用户还有哪些问题未解决
</summary>

重要规则:
- 如果对话中没有重要信息,只保留意图,其他字段写"无"
- 不要捏造信息,只写对话中明确说过的内容
- 不要写"助手建议..."(那是助手的,不是用户的偏好)
"""

    def __init__(self, llm: ChatOpenAI):
        """
        Args:
            llm: 用于生成摘要的 LLM 实例
                 为什么用 LLM 而不是轻量模型?
                 摘要需要理解语义("保守"≈"求稳"≈"风险承受低"),
                 轻量模型做不好这个
        """
        self.llm = llm

    def compress(self, old_messages: list) -> str:
        """
        执行第二级压缩

        Args:
            old_messages: 第一级过滤出来的"待压缩历史"
                         格式:[{"role": "user", "content": "..."}, ...]

        Returns:
            摘要字符串,格式:<summary>...</summary>
            如果对话中没有重要信息,返回空字符串 ""

        坑点 1:如果 old_messages 本身就很长,可能超出 LLM 输入限制
                解法:检查 token 数,超阈值时递归压缩(先压缩再总结)

        坑点 2:LLM 输出的格式可能不标准(忘了加 </summary>)
                解法:解析时做容错处理
        """
        if not old_messages:
            # 没有需要压缩的内容
            return ""

        # ----------------------------------------------------------
        # 坑点 3:LLM 的输入有 token 限制
        # 如果 old_messages 太长,需要先截断或递归压缩
        # ----------------------------------------------------------
        MAX_INPUT_TOKENS = 3000  # 安全阈值(留余量给 prompt)

        # 简单检查:消息条数过多时,截断最旧的部分
        # 真实场景:用 tiktoken 精确算 token 数
        if len(old_messages) > 30:
            old_messages = old_messages[-30:]  # 最多保留最近 30 条

        # 拼装待压缩内容
        content_parts = []
        for msg in old_messages:
            role_cn = "用户" if msg["role"] == "user" else "助手"
            content_parts.append(f"{role_cn}:{msg['content']}")

        compressed_text = "\n".join(content_parts)

        # 调用 LLM 生成摘要
        # 🔥 关键:不要在 system prompt 里加太多约束,
        #         让 LLM 专注于"提取信息"而不是"写格式"
        response = self.llm.invoke([
            {"role": "system", "content": self.SYSTEM_PROMPT},
            {"role": "user", "content": f"请压缩以下对话:\n{compressed_text}"}
        ])

        # ----------------------------------------------------------
        # 解析摘要(容错处理)
        # ----------------------------------------------------------
        raw_content = response.content or ""

        # 尝试提取 <summary>...</summary> 标签内容
        if "<summary>" in raw_content and "</summary>" in raw_content:
            start = raw_content.index("<summary>") + len("<summary>")
            end = raw_content.index("</summary>")
            summary = raw_content[start:end].strip()
        else:
            # LLM 没有按格式输出,容错处理:取全部内容
            summary = raw_content.strip()

        # ----------------------------------------------------------
        # 坑点 4:摘要可能为空(全篇都是闲聊)
        # 空摘要不要加入上下文(会干扰 LLM)
        # ----------------------------------------------------------
        # 检查是否只有"无"
        if all(line.split(":", 1)[-1].strip() in ["", "无"]
               for line in summary.split("\n") if ":" in line):
            return ""  # 没有实质内容,返回空

        return summary

第三级:向量数据库长期记忆(向量级)

原理

复制代码
第三级解决的是"跨会话记忆"的问题:

用户 A 在上周会话中说过:"我总是选择最便宜的物流"
这个信息:
  - 不在当前会话的最近 5 轮里
  - 不在历史摘要里(上周聊的是别的)
  - 但对当前会话可能有帮助(用户又下单了,需要推荐物流)

存入向量库 → 需要时按相关性检索回来

这就是第三级的作用:跨会话共享的长期偏好记忆。

什么时候需要第三级

复制代码
面试追问:"什么时候用第三级?前两级不够吗?"

答:
  第一级(滑动窗口):覆盖最近 5 轮对话
  第二级(摘要压缩):覆盖 5 轮之前的会话(同一会话内)

  但如果用户:
  - 退出了对话(会话结束)
  - 第二天又来(新的会话)

  第一级和第二级都失效了(它们都是会话级的)。

  第三级的作用:在会话之间共享信息。
  典型应用:
    - 用户偏好("用户总是选最便宜")
    - 之前买过的商品类别("用户买过两次显卡")
    - 用户的约束条件("用户公司只能开增值税普通发票")

代码实现

python 复制代码
"""
第三级压缩器:向量数据库长期记忆

业务背景:
  用户在多轮对话或多个会话中说过的关键偏好,
  需要跨会话保留,供未来的对话检索使用。

设计思路:
  1. 每个用户有独立的向量空间(用 user_id 隔离)
  2. 存储的是"关键信息片段",不是原始对话
  3. 检索时用当前问题生成向量,找到最相似的记忆

为什么用向量检索而不是直接存数据库:
  用户的表达方式多样("便宜"≈"省钱"≈"性价比高"≈"经济实惠")
  向量检索能捕捉语义相似性,精确匹配做不到

Java 对照:
  Redis 的 sorted set(按相关度召回,但 sorted set 是精确匹配)
  这里用向量数据库(Milvus/Qdrant)做语义匹配,更接近"智能检索"
"""


class VectorMemory:
    """
    长期记忆向量库

    三级压缩中这一级最慢(需要调 LLM 生成向量 + 查向量库)
    但也是信息最精准的一级(按需召回,不浪费上下文)
    """

    def __init__(self, embed_model, vector_db):
        """
        Args:
            embed_model: Embedding 模型(如 text-embedding-3-small / BGE)
                        为什么不用 LLM 做 embedding:
                        - embedding 模型专门为语义匹配训练,效果更好
                        - 速度快 100 倍,成本低 1000 倍
                        - 可以离线批量处理
            vector_db: 向量数据库实例(Milvus / Qdrant / Chroma)
        """
        self.embed_model = embed_model
        self.vector_db = vector_db

    def store(self, info: dict, session_id: str):
        """
        存入长期记忆

        Args:
            info: 要存储的信息,格式:
                {
                    "content": "用户偏好稳健投资,不喜欢高风险",
                    "type": "preference",          # 信息类型:preference / fact / task
                    "source_turn": 3,             # 来源对话轮次(方便溯源)
                    "timestamp": "2026-06-09"
                }
            session_id: 归属的会话 ID

        什么时候调用 store:
          - 对话结束时(Session End 事件)
          - 用户明确表达了偏好(实时捕捉)
          - 第二级摘要生成时(摘要里的偏好信息也要存)

        坑点 1:必须带 session_id,否则不同用户的偏好会串线
                真实事故:用户 A 的偏好被用户 B 检索到 → 隐私泄露
        """
        # 1. 生成向量
        text = info["content"]
        vector = self.embed_model.encode(text)

        # 2. 存入向量库(带 metadata,用于过滤和溯源)
        self.vector_db.insert(
            vector=vector.tolist(),  # 转成 list(有些向量库不支持 numpy)
            metadata={
                **info,  # 原始内容
                "session_id": session_id,  # 🔥 坑点:忘记加这个 = 隐私泄露
                "stored_at": info.get("timestamp", "")
            }
        )

    def retrieve(self, query: str, session_id: str, user_id: str, top_k: int = 3) -> list:
        """
        从长期记忆中检索相关信息

        Args:
            query: 当前问题(用于生成检索向量)
            session_id: 只想检索"当前会话"的记忆
                       为什么不是跨会话?
                       用户 B 的偏好不应该影响用户 A 的体验
            user_id: 用户 ID(用于确认归属)
            top_k: 召回数量

        Returns:
            相关记忆列表,格式:
            [
                {"content": "用户偏好稳健投资", "type": "preference", "source": "..."},
                ...
            ]

        坑点 2:忘记做 user_id / session_id 过滤
                → 用户 A 可能搜到用户 B 的记忆(严重隐私问题)

        坑点 3:top_k 设置过大
                → 召回数太多会干扰 LLM(Context 里堆满了不相关的记忆)
                → 经验值:top_k = 3-5
        """
        # 1. 生成查询向量
        query_vector = self.embed_model.encode(query)

        # 2. 向量检索(带双重过滤:user_id + session_id)
        # 🔥 坑点 4:过滤条件必须加,否则隐私泄露
        results = self.vector_db.search(
            query_vector=query_vector.tolist(),
            top_k=top_k,
            filter_expr={
                # 只检索当前用户的记忆
                # 条件:session_id = 当前会话 OR session_id = "global"(全局偏好)
                "user_id": user_id
            }
        )

        # 3. 解析结果(只取 metadata,不取向量本身)
        memories = []
        for result in results:
            score = result.get("score", 0)

            # 🔥 坑点 5:相似度阈值(低于阈值的不采纳)
            SIMILARITY_THRESHOLD = 0.7
            if score < SIMILARITY_THRESHOLD:
                continue  # 太不相关,丢弃

            memories.append({
                "content": result["metadata"]["content"],
                "type": result["metadata"].get("type", "unknown"),
                "relevance_score": round(score, 3)
            })

        return memories

    def cleanup_outdated(self, session_id: str, keep_recent: int = 10):
        """
        清理过期记忆

        为什么需要清理:
          用户的偏好会变化(比如"以前喜欢保守,现在开始尝试激进")
          如果不清理,旧偏好会一直干扰新的对话

        Args:
            session_id: 清理哪个会话的记忆
            keep_recent: 保留最近多少条(防止把所有记忆清没了)
        """
        # 真实场景:按 timestamp 倒序,删除最旧的
        # 但要保留标记为"global"的全局偏好
        self.vector_db.delete(
            filter_expr={
                "session_id": session_id,
                "is_global": False,  # 不删除全局偏好
            },
            keep_recent=keep_recent
        )

三级压缩协同工作流程

复制代码
用户发起第 21 轮对话:
                │
                ▼
┌─────────────────────────────────────────────────────────────┐
│                    第一级:滑动窗口                              │
│                                                             │
│  对话历史共 21 轮,窗口大小 = 5                               │
│  → 保留最近 5 轮(第 17-21 轮)                              │
│  → 第 1-16 轮进入"待压缩区"                                  │
└────────────────────────────┬────────────────────────────────┘
                             │ 有更早历史(> 5 轮)
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                    第二级:LLM 摘要                            │
│                                                             │
│  对第 1-16 轮生成摘要:                                       │
│  <summary>                                                   │
│  意图:用户想了解股票投资                                       │
│  偏好:用户偏好稳健投资,不喜欢高风险                           │
│  决策:用户暂缓买入,等待合适时机                               │
│  未完成任务:无                                              │
│  </summary>                                                  │
└────────────────────────────┬────────────────────────────────┘
                             │ 摘要里有偏好信息
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                    第三级:向量库存储                          │
│                                                             │
│  摘要里的偏好信息("用户偏好稳健投资")存入向量库              │
│  标注 type=preference,user_id=xxx                            │
│  → 下次新会话也可以检索到                                     │
└─────────────────────────────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                    当前会话 LLM 输入                           │
│                                                             │
│  【系统设定】                                                │
│  你是一个投资顾问                                             │
│                                                             │
│  【历史摘要】(第二级产物)                                    │
│  用户曾询问股票投资,偏好稳健投资,已暂缓买入                  │
│                                                             │
│  【用户相关偏好】(第三级产物,从向量库检索)                   │
│  - 用户偏好稳健投资,不喜欢高风险(相关度 0.92)               │
│  - 用户公司只能开增值税普通发票(相关度 0.88)                 │
│                                                             │
│  【最近对话】(第一级产物)                                    │
│  用户:现在有什么好股票推荐吗                                  │
│                                                             │
│  【当前问题】                                                │
│  帮我推荐一支适合我的股票                                      │
└─────────────────────────────────────────────────────────────┘

面试核心答案汇总

复制代码
Q1:为什么需要三级压缩?
A:一级不够。三级各有分工:滑动窗口处理最近对话(快),
   摘要压缩处理中期历史(保留因果链),向量库处理跨会话记忆(持久)。

Q2:为什么是 5 轮?
A:5 轮是经验平衡点。3 轮上下文太少信息不足,7 轮 token 消耗大且 LLM 注意力下降。

Q3:为什么第二级用 LLM 摘要而不是直接存向量?
A:向量检索会丢失顺序和因果关系。摘要能保留"先做了什么决定,再做什么",
   这是投资/客服等场景的关键信息。

Q4:第三级和第二级有什么区别?
A:第二级是"同会话内的历史压缩"(会话结束时触发)。
   第三级是"跨会话的长期记忆共享"(按需检索)。
   两者的触发时机、存储介质、召回方式都不同。

Q5:三级压缩的执行顺序?
A:按需触发,不是每次都跑全三级。
   对话未超窗口 → 只用第一级
   超窗口 → 第一级 + 第二级
   新会话开始 + 有相关偏好 → 第三级检索

Java 对照总结

复制代码
| 级别 | Agent 概念 | Java 对照 | 核心操作 |
|------|-----------|---------|---------|
| 第一级 | 滑动窗口 | LinkedList (LRU) | removeFirst() |
| 第二级 | LLM 摘要 | MapReduce Combiner | 局部汇总 |
| 第三级 | 向量检索 | Redis Sorted Set / ES | 按相似度召回 |
| 组装 | 上下文拼接 | StringBuilder | 按顺序 append |
相关推荐
xxie12379421 小时前
return与print
开发语言·python
秋921 小时前
从 Python 后端工程师转型 AI Engineer(AI 工程化)的完整补课清单(2026实战版)
开发语言·人工智能·python
humcomm21 小时前
AI编程时代新前端职位
前端·ai编程
慕木沐1 天前
Google ADK Java 1.0版本 核心机制与实战 Demo
java·开发语言·python
Tbisnic1 天前
AI大模型学习第十一天:技术选型、安全防护与金融实战
python·学习·ai·大模型·提示词工程
AI工具挖掘机1 天前
Codex 桌面版上手:从安装到自己开发首个小游戏,0 基础快速入门,手把手教学
经验分享·ai·ai编程
hboot1 天前
AI工程师第一课 - Python
前端·后端·python
坚果派·白晓明1 天前
【鸿蒙PC】SDL3 适配:AtomCode + Skills 快速集成 NAPI 测试工具
c++·华为·ai编程·harmonyos·atomcode
许彰午1 天前
30_Java Stream流操作全解
java·windows·python
秋91 天前
3年经验Python后端转AI Engineer:3个月实战转型计划(2026版)
开发语言·人工智能·python