Prompt工程与上下文管理实战:从模板到Agent Loop的演进

Prompt工程与上下文管理实战:从模板到Agent Loop的演进

做 LLM 应用开发一段时间之后,我发现一个很有意思的现象:刚入门的时候,大家把精力全花在"怎么写好一条 Prompt"上------角色设定怎么写、Few-shot 怎么放、CoT 怎么引导。但真正上线之后,踩的坑几乎都跟 Prompt 本身没关系,而是跟上下文管理有关。

Token 超了怎么办?对话历史太长模型"忘"了早期内容怎么办?不同信息的优先级怎么排?这些问题才是生产级 LLM 应用的核心工程挑战。

今天就从我做 nvc-guide的实战经验出发,聊聊 Prompt 工程和上下文管理的那些事儿。

Prompt 工程:不是玄学,是工程

Prompt 的本质

LLM 本质上是一个条件概率模型------给定上文(Prompt),预测下一个 token 的概率分布。Prompt Engineering 的目标就是构造最优的上文条件,让模型的输出集中在我们期望的区域。

一条完整的 Prompt 通常由这几部分组成:

组成部分 作用 示例
System Prompt 定义角色、行为边界、输出格式 "你是 NVC 评估专家"
User Prompt 用户的具体任务指令 "请评估以下对话"
Context 上下文信息(RAG、历史对话、用户档案) 检索到的知识库片段
Output Format 约束输出结构 "以 JSON 格式输出"
Examples Few-shot 示例 2-3 个评估案例

几个实战技巧

负面约束前置。这是我在项目里验证过最有效的技巧。LLM 的注意力机制对开头的 token 权重更高,所以"不要编造""不要乱调工具"这类约束要放在 System Prompt 的最前面,而不是藏在最后。

复制代码
差:(在 Prompt 末尾)...另外,请不要编造内容。
好:(在 System Prompt 开头)
# 核心原则
1. 绝对不要编造用户没有提到的内容
2. 工具调用失败时如实告知,不要换工具继续调用

工具触发条件具象化。不要只说"搜索笔记时调用 wiki_search",而是列出具体的触发词------"我的笔记""之前的记录""搜索笔记"。模糊的描述会让模型误判,具象化的触发词能大幅减少错误调用。

任务分解。查询改写和最终问答用不同的 Prompt,而不是在一条 Prompt 里同时完成。每个子任务有专门的 Prompt 优化空间,效果比"一锅炖"好得多。

模板化管理

项目里我用 StringTemplate(.st 文件)管理所有 Prompt 模板,一共 16 个。每个模板对应一个专职 Agent,职责单一------观察教练只关注"引导用户区分观察和评论",不会混入感受、需求等内容。

模板里用 {变量名} 占位符,运行时通过 PromptBuilder 动态注入用户档案、上下文摘要等信息。这样做的好处是:改 Prompt 不用改代码,改 .st 文件就行,迭代速度很快。

java 复制代码
public String buildSystemPrompt(Long userId, String contextSummary) {
    String template = getOrLoadTemplate();  // double-checked locking 缓存
    template = template.replace("{userProfileSummary}",
        profileService.getUserProfilePrompt(userId));
    template = template.replace("{contextSummary}",
        contextSummary != null ? contextSummary : "");
    template = template.replace("{currentTime}",
        LocalDateTime.now().format(TIME_FORMATTER));
    return template;
}

Token 管理:多轮对话的核心工程问题

LLM 是无状态的------每次 API 调用都是独立的请求/响应。所有需要 LLM "知道"的信息,都必须在每次调用时作为上下文显式传入。多轮对话中,Token 数量持续增长,带来三个问题:

  1. Token 溢出:超出上下文窗口限制,LLM 直接报错
  2. 注意力稀释:上下文过长,LLM 对早期信息的注意力下降
  3. 成本失控:输入 Token 直接影响 API 调用费用

三层压缩方案

我在项目里用了滑动窗口 + LLM 摘要压缩 + 降级截断的三层方案:

复制代码
消息数 ≤ 20 → 直接返回所有消息(不压缩)
消息数 > 20 → 触发压缩
  ├── 早期消息(第 1 到 N-10 条)→ LLM 摘要(不超过 500 字)
  ├── 最近消息(最后 10 条)→ 保持不变
  └── 摘要注入 System Prompt 的 {contextSummary} 占位符

为什么是 20 轮?20 轮对话约消耗 6000-8000 tokens,加上 System Prompt(1500 tokens)和用户画像(300 tokens),总计约 10K tokens,留出充足的输出空间。

为什么保留 10 轮而不是 5 轮?NVC 练习是深度对话------用户可能在最近 5 轮内连续探讨一个 NVC 步骤,只保留 5 轮可能丢失当前步骤的上下文。10 轮能覆盖用户最近 2-3 个步骤的讨论。

降级截断是兜底方案:LLM 摘要调用失败时,取前 5 条消息,每条截断到 200 字符。保证即使 LLM 服务不可用,对话也能继续。

java 复制代码
private String generateSummary(List<MessageEntity> messages) {
    try {
        String summary = client.prompt().user(prompt).call().content();
        if (summary != null && !summary.isBlank()) return summary.trim();
        return buildFallbackSummary(messages);
    } catch (Exception e) {
        return buildFallbackSummary(messages);  // 降级
    }
}

摘要注入的位置也有讲究------注入 System Prompt 而不是消息列表。System Prompt 在 LLM 的注意力机制中权重最高,不会被用户消息"挤掉"。

上下文优先级:哪些内容优先放

Token 预算有限,不可能把所有信息都塞进去。这时候就需要排优先级。

黄金法则:指令 > 状态 > 知识 > 记忆

优先级 类型 典型内容 理由
最高 指令型 System Prompt、行为规则 定义 LLM 行为,不可替代
状态型 用户画像、当前场景 "我是谁、在什么场景"比"需要什么知识"更基础
知识型 RAG 检索结果 按需检索的领域知识,辅助回复
记忆型 对话历史、反思记忆 最动态,放在消息区

这个顺序有两层依据:一是 LLM 的注意力机制------开头和结尾的信息权重最高(Lost in the Middle 效应),最稳定的指令放在最前;二是信息依赖关系------LLM 需要先知道"我是谁、在什么场景",才能正确使用 RAG 知识和对话历史。

缓解 Lost in the Middle

2023 年斯坦福的研究发现,LLM 对上下文中间部分的信息关注度显著低于开头和结尾。如果把 5 个 RAG 文档片段塞进去,模型对第 1 个和最后 1 个的关注度最高,中间的容易被忽略。

我的应对策略:

  • 用标签标注各部分([用户档案][参考资料]),帮助 LLM 定位
  • RAG 只取 Top-3,控制中间区域的长度
  • 最重要的信息放在开头或结尾

三代工程范式:Prompt → Context → Loop

LLM 应用的工程范式经历了三代演进,理解这个演进脉络对做架构设计很有帮助。

Prompt Engineering(第一代)

核心思想是通过精心设计的指令模板引导 LLM 输出。关键技术包括角色设定、Few-shot、CoT、结构化输出约束。本质是静态的、一次性的指令设计。

Context Engineering(第二代)

核心思想是动态组装 LLM 所需的完整上下文。不再只关注 Prompt 本身的措辞,而是关注"给 LLM 看什么信息"------短期记忆、长期记忆、RAG 检索、结构化状态、反思记忆,这些都需要动态组装。

Prompt 的措辞差异对输出质量的影响已经越来越小(模型越来越强),但上下文的质量和相关性直接决定了 LLM 的表现上限。

Loop Engineering(第三代)

核心思想是让 LLM 在一个受控循环中自主决策和执行。这就是 Agent 的本质------LLM 从"被动回答者"变成了"主动决策者"。

复制代码
while (turn < MAX_TURNS) {
    response = callLLM(messages)
    if (response.hasToolCalls()) {
        results = executeTools(response.toolCalls)  // Hook 链介入
        messages.add(toolResults)
        turn++
    } else {
        emit(response.content)
        break
    }
}

Loop 需要解决的关键问题:终止条件(最大轮数、超时)、Hook 链(限流、缓存、权限)、可观测性(Trace 埋点)、降级策略。

三层是包含关系

这三代不是替代关系,而是包含关系:

复制代码
┌─────────────────────────────────────────┐
│           Loop Engineering              │
│  ┌─────────────────────────────────────┐│
│  │       Context Engineering           ││
│  │  ┌─────────────────────────────────┐││
│  │  │     Prompt Engineering          │││
│  │  │  16 个 .st 模板                 │││
│  │  └─────────────────────────────────┘││
│  └─────────────────────────────────────┘│
└─────────────────────────────────────────┘

Prompt 是基础模板,Context 在 Prompt 之上动态组装完整信息,Loop 在 Context 之上构建自主执行循环。三者缺一不可。

小结

Prompt 工程不是"写好一句话"那么简单,它背后是一整套工程体系:模板化管理、动态注入、负面约束前置、任务分解。上下文管理才是生产级 LLM 应用的核心------Token 预算分配、优先级排序、压缩策略、Lost in the Middle 缓解,每一个都是工程问题。

从 Prompt 到 Context 再到 Loop,是 LLM 应用从"玩具"到"产品"的必经之路。理解这个演进脉络,才能在架构设计时做出正确的取舍。

相关推荐
Jodie同志1 小时前
第16~23天:持久化、HITL、流式、MCP与安全
前端·后端·agent
Jodie同志1 小时前
第1~15天:原生Agent、RAG与LangGraph基础(完整代码实操)
前端·后端·agent
用户469368483202 小时前
kimi-code 深度掌握系列文章-终端里的 Agent 界面:CLI/TUI 架构(十五)
agent
Oliver_NI2 小时前
RAG 检索为什么需要 Hybrid Search?从 Dense、BM25 到 Reranker
人工智能·agent
ClouGence5 小时前
从 5 天到 2 小时:AI Agent 如何搭建高效、可复用的全自动化测试体系?
agent·测试
努力搬砖的咸鱼5 小时前
AI Agent测试全景图:它到底改变了什么
人工智能·python·ai·集成测试·pytest·agent·ai编程
小七-七牛开发者5 小时前
Codex 实践系列 Vol.04:用 Goal 和 Plan 管住一个长任务
ai·大模型·agent·claude·token·工作流·claudecode·ai coding
熊猫钓鱼>_>6 小时前
AI 3D 虚拟盲盒工坊:用腾讯云混元3D + TTS Skills 打造会说话的三维收藏品
人工智能·大模型·llm·agent·tts·混元3d·多skill协同