大模型开发 - 手写Manus之消息相关性过滤:06 用LLM管理Agent的上下文记忆

文章目录

Pre

大模型开发 - 用纯Java手写一个多功能AI Agent:01 从零实现类Manus智能体

大模型开发 - 手写Manus之基础架构:02 用纯Java从零搭建AI Agent骨架

大模型开发 - 手写Manus之Sandbox执行代码:03 用Docker为AI Agent打造安全沙箱

大模型开发 - 手写Manus之Tavily搜索工具:04 让AI Agent接入互联网

大模型开发 - 手写Manus之浏览器操作工具:05 基于Playwright实现网页自动化与多模态交互

手写Manus之消息相关性过滤:用LLM管理Agent的上下文记忆

引言

随着Agent执行步骤增多,Memory中的消息会不断累积------系统提示词、用户输入、每次大模型的推理回复、每次工具调用请求、每次工具执行结果......几个复杂任务下来,消息列表可能膨胀到数十条甚至上百条。

这带来两个问题:

  1. Token消耗:每次调用LLM都会传入所有历史消息,Token消耗快速增长
  2. 上下文窗口溢出:当消息总量超过模型的上下文窗口限制,API会直接报错

解决方案是:不是所有历史消息都与当前步骤相关,应该只保留最相关的消息

传统方案通常使用"滑动窗口"(保留最近N条消息)或基于向量的语义相似度。本文采用一个更创新的方法------用LLM自身来评估每条消息与当前查询的相关性,按得分高低保留Top-N。

一、设计接口

首先定义RelevanceFilter接口,作为可插拔的过滤策略:

java 复制代码
public interface RelevanceFilter {
    // 从消息列表中过滤出最相关的maxMessages条
    List<Message> filter(List<Message> messages, String currentQuery, int maxMessages);

    // 计算单条消息与查询的相关性得分(0.0-1.0)
    double calculateRelevance(Message message, String currentQuery);
}

接口注释中提到了三种可能的实现方式:

java 复制代码
// 关键字匹配:简单但不够精确
// 语义相似度:向量模型,需要额外的Embedding服务
// 大语言模型:最智能,但成本最高

本文实现第三种------基于LLM的方式。

二、改造Memory

Memory中集成相关性过滤器:

java 复制代码
@Data
public class Memory {
    private List<Message> messages;
    private RelevanceFilter relevanceFilter;

    public Memory() {
        this.messages = new ArrayList<>();
    }

    public void addMessage(Message message) {
        messages.add(message);
    }

    // 新增:带过滤的消息获取
    public List<Message> getMessages(String currentQuery) {
        if (relevanceFilter != null) {
            return relevanceFilter.filter(messages, currentQuery, 5);  // 最多保留5条
        }
        return messages;
    }
}

三、改造Agent调用链

BaseAgentstep()方法签名变化------传入当前查询以支持过滤:

java 复制代码
// Before
protected abstract StepResult step();

// After
protected abstract StepResult step(String currentQuery);

ToolCallAgent中使用新的API:

java 复制代码
@Override
protected StepResult step(String currentQuery) {
    // 带相关性过滤的消息获取
    List<Message> contextMessages = memory.getMessages(currentQuery);
    List<ToolDefinition> toolDefinitions = toolCollection.getToolDefinitions();
    ModelResponse modelResponse = openAIClient.chat(contextMessages, toolDefinitions);
    // ...
}

四、LLMRelevanceFilter核心实现

4.1 过滤逻辑

java 复制代码
@Slf4j
public class LLMRelevanceFilter implements RelevanceFilter {

    private final OpenAIClient openAIClient;

    @Override
    public List<Message> filter(List<Message> messages, String currentQuery, int maxMessages) {
        if (messages == null || messages.isEmpty()) {
            return new ArrayList<>();
        }

        // 消息数量不超过限制,直接返回
        if (messages.size() <= maxMessages) {
            return new ArrayList<>(messages);
        }

        // 系统消息始终保留!
        List<Message> systemMessages = messages.stream()
                .filter(msg -> msg.getRole() == Role.SYSTEM)
                .toList();

        List<Message> nonSystemMessages = messages.stream()
                .filter(msg -> msg.getRole() != Role.SYSTEM)
                .toList();

        // 为每条非系统消息计算相关性得分
        List<MessageScore> scoredMessages = nonSystemMessages.stream()
                .map(msg -> new MessageScore(msg, calculateRelevance(msg, currentQuery)))
                .sorted((a, b) -> Double.compare(b.score, a.score))
                .toList();

        // 组合:系统消息 + 得分最高的N条非系统消息
        List<Message> result = new ArrayList<>(systemMessages);
        int remainingSlots = maxMessages - systemMessages.size();

        if (remainingSlots > 0) {
            result.addAll(scoredMessages.stream()
                    .limit(remainingSlots)
                    .map(ms -> ms.message)
                    .toList());
        }

        log.debug("过滤前消息数量: {}, 过滤后消息数量: {}", messages.size(), result.size());
        return result;
    }
}

关键设计:系统消息(System Prompt)始终保留------它定义了Agent的角色和行为规则,任何时候都不能丢弃。

4.2 基于LLM的相关性评分

对每条消息调用LLM进行语义相关性评估:

java 复制代码
@Override
public double calculateRelevance(Message message, String query) {
    if (message == null || message.getContent() == null || query == null) {
        return 0.0;
    }

    try {
        String relevancePrompt = buildRelevancePrompt(message.getContent(), query);

        List<Message> promptMessages = Arrays.asList(
            Message.systemMessage("你是一个专业的语义相关性评估专家。"
                + "请严格按照要求评估消息的相关性,只返回数字评分。"),
            Message.userMessage(relevancePrompt)
        );

        ModelResponse response = openAIClient.chat(promptMessages);
        String content = response.getContent();

        if (content != null) {
            double relevance = parseRelevanceScore(content);
            return Math.max(0.0, Math.min(1.0, relevance));  // 限制在[0,1]范围
        }
    } catch (Exception e) {
        log.warn("LLM相关性评估失败,使用默认评分", e);
    }
    return 0.0;
}

4.3 评分提示词设计

提示词提供了明确的评分标准,引导LLM返回准确的数值:

java 复制代码
private String buildRelevancePrompt(String messageContent, String query) {
    return String.format(
        "请评估以下消息内容与查询的相关性,返回0.0到1.0之间的数字评分:\n\n" +
        "查询:%s\n\n" +
        "消息内容:%s\n\n" +
        "评估标准:\n" +
        "1.0 - 高度相关:消息直接回答查询或包含查询的核心信息\n" +
        "0.7-0.9 - 相关:消息与查询主题相关,包含有用信息\n" +
        "0.4-0.6 - 部分相关:消息与查询有一定关联,但不是核心相关\n" +
        "0.1-0.3 - 微弱相关:消息与查询只有很少关联\n" +
        "0.0 - 不相关:消息与查询完全无关\n\n" +
        "请只返回数字评分,不要包含其他文字说明:",
        query, messageContent
    );
}

4.4 健壮的评分解析

LLM不一定总是返回纯数字,我们需要健壮的解析逻辑:

java 复制代码
private double parseRelevanceScore(String content) {
    if (content == null || content.trim().isEmpty()) {
        return 0.0;
    }

    String trimmed = content.trim();

    // 优先尝试直接解析数字
    try {
        String numberStr = trimmed.replaceAll("[^0-9.].*", "");
        if (!numberStr.isEmpty()) {
            double score = Double.parseDouble(numberStr);
            return Math.max(0.0, Math.min(1.0, score));
        }
    } catch (NumberFormatException e) {
        log.warn("无法解析LLM返回的相关性得分: {}", content);
    }

    // 回退方案:从文本关键词推断
    String lowerContent = content.toLowerCase();
    if (lowerContent.contains("高度相关") || lowerContent.contains("very relevant")) {
        return 0.9;
    } else if (lowerContent.contains("相关") || lowerContent.contains("relevant")) {
        return 0.7;
    } else if (lowerContent.contains("部分") || lowerContent.contains("partial")) {
        return 0.5;
    } else if (lowerContent.contains("微弱") || lowerContent.contains("weak")) {
        return 0.2;
    } else if (lowerContent.contains("不相关") || lowerContent.contains("not relevant")) {
        return 0.0;
    }

    return 0.0;
}

两层解析策略:

  1. 先尝试提取数字(理想情况:LLM直接返回"0.8")
  2. 失败则通过关键词推断(退化情况:LLM返回"这条消息高度相关")

五、启用相关性过滤

ManusAgent中添加设置方法,并在入口处启用:

java 复制代码
// ManusAgent
public void setRelevanceFilter(RelevanceFilter relevanceFilter) {
    memory.setRelevanceFilter(relevanceFilter);
}

// ManusApplication
ManusAgent manusAgent = new ManusAgent(openAIClient);
manusAgent.setRelevanceFilter(new LLMRelevanceFilter(openAIClient));

六、过滤效果示意

假设Memory中有12条消息,当前查询是"给打开的页面截图":

java 复制代码
消息1  [SYSTEM] 角色定义...                → 系统消息,始终保留
消息2  [USER]   创建HTML并截图...           → 相关性: 0.8
消息3  [ASSISTANT] 调用write_file          → 相关性: 0.3
消息4  [TOOL]   文件写入成功               → 相关性: 0.2
消息5  [ASSISTANT] 调用browser.navigate    → 相关性: 0.7
消息6  [TOOL]   导航成功                   → 相关性: 0.6
消息7  [ASSISTANT] 调用sandbox.execute     → 相关性: 0.1  ← 被过滤
消息8  [TOOL]   Python执行结果             → 相关性: 0.1  ← 被过滤
消息9  [ASSISTANT] 调用tavily_search       → 相关性: 0.1  ← 被过滤
消息10 [TOOL]   搜索结果                   → 相关性: 0.1  ← 被过滤
...

过滤结果(maxMessages=5):
  消息1 [SYSTEM]  - 始终保留
  消息2 [USER]    - 得分 0.8
  消息5 [ASSISTANT] - 得分 0.7
  消息6 [TOOL]    - 得分 0.6
  消息3 [ASSISTANT] - 得分 0.3

与截图无关的代码执行、搜索等消息被过滤掉,LLM只看到最相关的上下文。

七、权衡与思考

优势

  • 语义理解:LLM能理解消息的深层含义,比关键词匹配或简单的向量相似度更准确
  • 零额外基础设施:不需要向量数据库或Embedding模型

代价

  • 额外API调用:每条消息需要一次LLM调用来评分,N条消息就是N次调用
  • 延迟增加:评分过程增加了每个step的响应时间

优化方向

  • 可以使用更轻量的模型(如qwen-turbo)来做评分
  • 可以设置消息条数阈值,消息不多时跳过过滤
  • 可以缓存评分结果,避免重复计算

总结

消息相关性过滤解决了Agent长期运行中的上下文管理问题。通过LLM驱动的语义评估,Agent可以在每一步只关注与当前任务最相关的历史信息,既节省了Token消耗,又提高了决策质量。

核心设计原则:

  • 系统消息永不过滤------角色定义和行为规则是Agent的"灵魂"
  • 按相关性排序保留Top-N------最相关的信息优先保留
  • 健壮的评分解析------双层策略应对LLM不确定的输出格式
相关推荐
分享牛4 小时前
大模型结合BPMN语言,下一代BPM产品的雏形
人工智能·搜索引擎·llm·bpmn
带刺的坐椅10 小时前
赋予 AI Agent “无限续航”:语义保护型上下文压缩技术解析
ai·llm·reactor·agent·solon·solon-ai
A小码哥12 小时前
MiniMax M2.5深度评测详解:更快更强更智能
llm
XLYcmy13 小时前
智能体大赛 核心功能 惊喜生成”——创新灵感的催化器
数据库·ai·llm·prompt·agent·检索·万方
无聊的小坏坏14 小时前
大语言模型应用快速了解
语言模型·自然语言处理·llm
合天网安实验室1 天前
某LLM问答系统安全测试报告:提示词注入与越狱攻击分析
llm·安全测试·ai安全·问答大模型
数据智能老司机1 天前
Agentic Mesh——Agent 架构
人工智能·llm·agent
华农DrLai1 天前
向量嵌入入门:给每个词分配一个“数字指纹“
大数据·人工智能·ai·llm·rag
数据智能老司机1 天前
Agentic Mesh——智能体的前世今生与未来
人工智能·llm·agent