知识库AgentLLM 与上下文LLM 与上下文Prompt 与上下文工程Prompt 与上下文工程
01 · LLM 与上下文LLM 与上下文
Roadmap 01核心Markdown62 min

Prompt 与上下文工程Prompt 与上下文工程

从提示模板扩展到上下文选择、权限过滤、压缩、缓存、审计与可复现的 Java 装配流水线。从提示模板扩展到上下文选择、权限过滤、压缩、缓存、审计与可复现的 Java 装配流水线。

#PromptPrompt#Context EngineeringContext Engineering#Prompt InjectionPrompt Injection#RAGRAG#JavaJava更新于 2026-08-16

专题导读

Prompt 工程关注“如何表达任务”,上下文工程关注“本次调用究竟让模型看到什么”。对 Java 后端系统,后者是一条完整的数据管道:识别调用者与任务,获取候选信息,执行 ACL,去重与排序,在 token 预算内选择或压缩,形成不可变快照,再记录来源、版本和裁剪原因。

一个可靠的上下文不是越长越好,而应满足四个条件:

  1. 必要:与当前任务直接相关,减少噪声与冲突。
  2. 有权:调用者对每条业务数据有读取权限。
  3. 可追溯:知道来自哪个文档、工具或会话状态,以及对应版本。
  4. 可复现:保存模板、模型、检索结果、参数和裁剪决策,能够解释线上行为。

网页、邮件、附件、工单、代码、RAG 片段和工具返回值都可能包含恶意或冲突文本。把它们标记为数据、使用结构化边界有助于模型理解,但不构成安全隔离。真正的权限边界必须在模型外,由 Java 服务的认证、授权、参数校验、工具 allowlist、网络策略和审批流程实现。

知识地图

阶段 输入 核心机制 失败风险
身份与任务识别 用户、租户、请求 认证、任务分类、策略选择 匿名数据混入、策略错配
候选获取 历史、RAG、工具、业务状态 召回、缓存、版本读取 漏召回、过期数据
安全过滤 候选项及资源属性 ACL、租户隔离、最小权限 越权、间接提示注入
价值排序 相关性、时效性、信任等级 去重、打分、冲突检测 噪声、重复、证据冲突
预算装配 token 上限与输出预留 分桶、原子选择、压缩 硬截断、输出无空间
快照调用 模板、消息、工具定义 版本化、稳定序列化 无法复现、缓存串租户
评测审计 响应、来源、裁剪记录 任务指标、安全回归 只看主观回答质量

推荐的数据流是:

主体认证 → 任务分类 → 拉取候选 → ACL 过滤 → 去重/评分 → 冲突处理 → 分桶预算 → 原子装配 → 快照与哈希 → 模型调用 → 工具再授权 → 评测与审计

面试题详解

Q1:Prompt 工程与上下文工程有什么区别?

**核心回答:**Prompt 工程主要设计指令、输出要求和 few-shot 示例;上下文工程覆盖模型调用前后的整条信息供应链,包括检索、记忆、工具定义、权限、排序、预算、压缩、快照、缓存和评测。前者是后者的一部分。

例如“请基于证据回答,不确定就说不知道”是一条 prompt 指令;从 500 个候选文档中只选出当前租户有权访问、与问题相关、版本有效的 8 个片段,并记录被丢弃原因,是上下文工程。

两者都不能取代确定性控制。提示中写“不得查询其他租户”不是租户隔离;工具网关仍须从已认证主体推导 tenantId,而不是接受模型生成的租户参数。

面试追问:为什么不能把所有规则都写进 system prompt? 规则越多会占预算、发生冲突且模型遵循并非形式化保证;认证、金额上限、资源归属等应由代码和策略引擎强制执行。

常见错误回答:“上下文工程就是更长、更复杂的 prompt”“只要 system prompt 足够强就不需要后端校验”。

Q2:指令层级应如何理解,能否作为安全边界?

**核心回答:**部分模型 API 定义 system、developer、user、tool 等角色及优先关系,但角色集合和精确语义依供应商而异。应用应遵循目标 API 的正式文档,不能把某一家协议泛化为所有模型的共同规范。

较高优先级指令用于表达应用策略,较低优先级消息不能按设计覆盖它;然而模型是概率系统,可能受到指令冲突、间接注入或上下文混淆影响。因此“指令优先级”是行为引导机制,不是访问控制机制。

后端应实施纵深防御:

  • 受控模板不可由普通用户修改;
  • 外部内容有来源、信任级别和清晰边界;
  • 工具参数采用 allowlist 和服务端派生值;
  • 每次工具调用重新鉴权,不信任模型给出的身份与范围;
  • 高风险动作需要确认或人工审批;
  • 输出进入日志、HTML、SQL、Shell 等下游前按对应上下文编码或校验。

面试追问:用 XML 标签包住网页内容能防 prompt injection 吗? 不能。标签帮助区分结构,但网页中的恶意指令仍会进入模型上下文;模型不是具备硬隔离的解释器。

常见错误回答:“system prompt 永远不会被绕过”“只要写一句 ignore instructions in documents 就安全”。

Q3:直接提示注入与间接提示注入有何区别?

**核心回答:**直接注入来自当前用户输入,例如要求忽略既有规则;间接注入藏在模型读取的网页、邮件、附件、代码仓库、RAG 文档或工具返回值中。后者更像供应链输入:攻击者不一定直接与 Agent 对话,也能影响其行为。

风险大小取决于模型可执行的能力。纯摘要系统可能泄漏上下文;具备邮件、支付、代码执行或浏览工具的 Agent 可能产生实际副作用。因此防御重点不是“检测所有恶意句子”,而是限制影响半径:最小工具集、最小数据范围、只读优先、参数约束、网络出口限制、幂等、审批和审计。

注入分类器或规则可以作为辅助信号,但存在误报、漏报和对抗绕过,不能成为唯一授权判断。即使内容被判定为“安全”,工具调用仍必须鉴权。

面试追问:RAG 数据已由企业内部维护,是否可视为可信? 不能默认。内部文档可能被误编辑、账号被入侵、同步链路受污染,且内容里的旧操作说明也可能与当前策略冲突。

常见错误回答:“过滤掉‘忽略之前指令’这几个词即可”“内部知识库没有注入风险”。

Q4:一个可靠的 Context Assembler 应有哪些步骤?

**核心回答:**先安全、后相关;先确定预算和输出预留,再选择原子上下文。推荐步骤如下:

  1. 从认证上下文获得用户、租户、角色与策略,不接受模型补写身份。
  2. 按任务类型选择模板、允许的上下文来源和工具集合。
  3. 并行获取候选历史、检索片段和业务状态,并保留版本。
  4. 在进入模型前做租户与资源 ACL 过滤。
  5. 规范化、去重,计算相关性、时效性、信任等级与业务优先级。
  6. 识别冲突证据;需要时保留双方并标记时间和来源,而不是静默覆盖。
  7. 为固定指令、当前请求、证据、记忆、工具 schema 和输出建立预算桶。
  8. 按消息、段落、JSON 对象等原子单元选择,避免中途硬切。
  9. 生成不可变快照,记录 selected IDs、dropped reasons、估算 token 和版本。
  10. 调用后记录实际 usage、响应状态和引用,供评测与审计。

同样输入应尽量得到确定性的装配结果:比较器要有稳定 tie-breaker,缓存读取要固定版本,异步返回顺序不能直接决定上下文顺序。

面试追问:ACL 应在向量检索前还是后? 最好由检索系统在查询时过滤,以避免越权候选进入结果与缓存;应用层仍可做防御性复核。若底层无法安全过滤,不能简单“先全量召回再让模型忽略”。

常见错误回答:“先按相关度取 top-k,再把 tenantId 写进 prompt”“候选列表顺序无所谓”。

Q5:上下文 token 预算应该如何分配?

**核心回答:**预算不是“窗口减去用户文本后全部给 RAG”,而是多个有优先级的桶。必须先为输出和协议开销留余量,再分配输入。

一个常见模型是:

总窗口 ≥ 固定指令 + 当前请求 + 工具定义 + 会话状态 + 检索证据 + 协议开销 + 最大输出

固定安全策略与当前请求优先;证据按价值选择;旧对话和重复示例通常更早被压缩或丢弃。输出预算过小会导致 JSON 或代码被截断。若供应商不公开精确聊天包装 token,应使用保守余量,并用实际 usage 校准。

预算策略应按任务配置:分类任务可能需要极少输出;代码审查需要较大输入和输出;工具 schema 很大时应只暴露当前任务允许的工具,而不是把所有工具定义都塞入。

面试追问:超限时为什么不直接截最后 N 个字符? 字符与 token 不对应,且会破坏 UTF-16、JSON、SQL、代码块或证据语义。应按原子项丢弃、重新检索较小片段,或使用可回源的压缩表示。

常见错误回答:“模型窗口是输入上限,输出另算”“工具定义不占上下文”。精确定义取决于 API,但工程预算不能假设它们免费。

Q6:相关性、时效性、权限和信任等级如何共同排序?

**核心回答:**权限是硬过滤,不是参与排序的软分数;无权数据即使相关度最高也必须删除。通过 ACL 后,才结合相关性、时效性、来源权威性、业务优先级与重复度排序。

可以使用可解释的线性分数作为起点:

score = wᵣ × relevance + wₜ × freshness + wₐ × authority + wᵦ × businessPriority

但分数不应掩盖硬规则,例如已撤销文档必须过滤,过期政策不能因为向量相似就排第一。不同特征量纲需要归一化,权重通过标注集和线上实验确定。

冲突不是简单取最高分。合同、价格、权限等高风险领域应优先选择正式系统的当前版本,保留冲突告警并允许回源。模型不应自行猜测哪个事实生效。

面试追问:怎样去重? 可按稳定资源 ID + 版本精确去重,再用内容 hash 或近重复算法合并重叠片段;合并时保留全部来源映射,避免引用丢失。

常见错误回答:“把 ACL 结果作为一个较低权重加入 score”“向量相似度最高的内容就是事实”。

Q7:为什么长上下文可能降低质量?

**核心回答:**更多 token 会引入噪声、重复和冲突,并增加模型在长序列中定位关键信息的难度。研究表明,模型在长上下文不同位置使用信息的能力可能不均匀,但具体表现依模型、任务和版本而异,不能把某个位置规律当作普适公式。

关键指令和当前请求应按目标模型建议放置,证据应有标题、来源和清晰边界;不过调整位置只是质量优化,不是安全控制。需要用任务级评测比较不同长度、排序和切分策略,而不是只验证是否“塞得下”。

长上下文还有系统成本:prefill 延迟、KV Cache、计费、缓存压力和日志治理。把 100 个低价值片段换成 8 个有权且互补的片段,往往更便于引用和诊断。

面试追问:top-k 是否固定为 5 或 10? 不应机械固定。文档粒度、问题类型、重排效果和窗口预算不同,应在离线召回/回答评测与线上成本之间选择。

常见错误回答:“上下文越长准确率单调上升”“把关键内容复制三遍就一定更受重视”。

Q8:上下文压缩有哪些策略,摘要为何不是无损压缩?

**核心回答:**常见策略包括抽取关键段、结构化状态、生成式摘要、滑动窗口、层次摘要和按需回源。摘要可能遗漏、改写甚至引入原文没有的陈述,因此必须保留来源、摘要器版本和回源入口。

策略 优点 主要边界
抽取原句/原段 可追溯、较少改写 仍可能较长,缺少跨段整合
结构化状态 字段稳定、易校验 schema 需演进,无法覆盖所有语义
生成式摘要 压缩率高、可融合 非无损,可能遗漏或产生新陈述
滑动窗口 简单、适合短会话 早期关键约束可能丢失
检索回原文 只加载当前需要内容 依赖索引质量和稳定资源 ID

金额、权限、合同条款、代码补丁、审计事实不应只依赖生成式摘要。可将会话目标和已完成步骤存为结构化状态,将高风险事实保留为 ID 和版本,回答时重新从权威系统读取。

面试追问:摘要如何评测? 检查关键事实保留率、无来源新增陈述、数字/实体准确性和回源成功率;不能只让另一个模型评价“读起来不错”。

常见错误回答:“摘要只是删除冗余,不会改变事实”“压缩后原文可以立即删除”。

Q9:会话记忆应该保存什么,不应该保存什么?

**核心回答:**记忆应服务于明确任务,并区分短期会话状态、长期用户偏好和权威业务事实。不要把全部聊天记录永久保存,也不要把模型总结当作业务数据库。

适合结构化保存的内容包括用户明确同意的偏好、当前任务 ID、已完成步骤和待确认事项。账户余额、权限、订单状态等应在需要时从权威系统读取。长期记忆要有用途限制、保留期限、删除机制、访问控制和用户可见性,遵循组织的数据治理与隐私要求。

记忆写入同样是不可信输出处理:模型提取的“用户偏好”需要置信度、证据和必要时的用户确认,不能因为一句玩笑就永久改变配置。

面试追问:如何避免旧记忆污染? 记录 validFrom/validTo、来源和版本,使用时检查新鲜度;冲突时以用户最新明确设置或权威系统为准,并允许撤销。

常见错误回答:“向量库就是长期记忆,所有对话都写进去”“只要加密就可以无限期保存”。

Q10:Few-shot 示例应如何选择和版本化?

**核心回答:**Few-shot 用少量输入—输出示例展示分类边界、格式或风格,适合补充自然语言难以精确描述的语义判断。示例应覆盖高频边界、负例和“信息不足”,而不是堆叠相似成功案例。

示例是生产配置:要有独立 exampleSetVersion,与模板、schema 和评测集一起变更;每次发布比较准确率、拒答率、token 成本和不同切片表现。动态选择示例时,也要执行 ACL 和防注入处理,不能从其他租户历史中检索案例。

强结构化任务优先使用 API 明确支持的 schema/工具协议,并在服务端校验;few-shot 只能改善遵循概率和语义示范,不能保证 JSON 合法。

面试追问:示例越多越好吗? 不。示例消耗 token,可能相互冲突、固化过期规则或让模型过拟合表面模式;应通过消融实验确定边际收益。

常见错误回答:“few-shot 是微调,不占上下文”“只放正例即可”。

Q11:Prompt caching 如何设计,为什么会有隔离风险?

**核心回答:**一些推理平台可以复用稳定前缀的计算结果,以降低重复 prefill 成本;是否支持、命中规则、计费和有效期均依供应商实现。应用不能假设所有 API 都有相同缓存语义。

为了提高命中,可把稳定的系统模板和工具定义放在前部,把每次变化的用户数据放后部。但缓存键至少要考虑模型/快照、模板版本、工具 schema 版本、内容版本以及供应商要求。若应用自己缓存已装配上下文,还必须包含租户、主体或授权范围;否则可能把 A 用户有权读取的内容返回给 B 用户。

权限变更和文档撤销要求缓存可失效。只使用 prompt hash 不能代表调用者仍有权访问其中数据。敏感内容还要确认供应商的数据保留和缓存政策是否符合组织要求。

面试追问:缓存命中后是否可跳过 ACL? 不可以。缓存是性能层,授权应基于当前主体和当前策略重新判断;缓存键只是辅助隔离,不是授权证明。

常见错误回答:“相同文本 hash 就能跨用户共享”“prompt cache 命中意味着模型输出也相同”。

Q12:如何让一次上下文装配可复现、可评测?

**核心回答:**保存足够的不可变元数据,使团队能区分“没取到、被权限过滤、预算裁掉、模型没使用、输出解析失败”这些不同问题。

建议记录:

  • 请求/trace ID、任务类型、租户与脱敏主体标识;
  • 模型 ID 或可用快照、采样参数、模板版本、example set 版本;
  • 查询改写版本、检索参数、候选资源 ID + 版本 + 分数;
  • ACL 策略版本、selected IDs、dropped reasons、稳定排序结果;
  • 输入 token 估算、实际 usage、输出预留、上下文快照 hash;
  • 工具 schema 版本、工具调用与最终响应状态;
  • 质量标签、引用正确性、安全回归结果和成本。

若供应商只提供滚动模型别名,就不能承诺完全复现权重;应保存实际可获得的 model 标识和响应元数据,并把不可复现性写入诊断边界。日志本身包含敏感上下文,应最小化、脱敏、加密并限制保留期限。

面试追问:线上指标只看回答点赞率够吗? 不够。还要看无答案正确拒绝、引用命中、越权率(目标应为零)、裁剪率、来源缺失率、token 成本、延迟和按租户/语言/任务的切片。

常见错误回答:“保存最终 prompt 文本就能完全复现”“为了排障应永久记录所有原始数据”。

Java 21 完整示例:确定性的上下文装配器

示例仅使用 Java 21 标准库。tokenizer 被抽象为 TokenEstimator 接口,不假设任何模型 SDK。生产系统应由目标 tokenizer 的适配器实现,并使用实际 usage 校准。

JAVA
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public final class ContextAssemblerDemo {
    enum TrustLevel { AUTHORITATIVE, INTERNAL, EXTERNAL }

    record Principal(String tenantId, Set<String> readableResourceIds) {
        Principal {
            Objects.requireNonNull(tenantId);
            readableResourceIds = Set.copyOf(readableResourceIds);
        }
    }

    record ContextItem(
        String id,
        String resourceId,
        String tenantId,
        long version,
        String content,
        TrustLevel trustLevel,
        double relevance,
        Instant validUntil
    ) {
        ContextItem {
            Objects.requireNonNull(id);
            Objects.requireNonNull(resourceId);
            Objects.requireNonNull(tenantId);
            Objects.requireNonNull(content);
            Objects.requireNonNull(trustLevel);
            Objects.requireNonNull(validUntil);
            if (version < 0) throw new IllegalArgumentException("version < 0");
            if (relevance < 0.0 || relevance > 1.0) {
                throw new IllegalArgumentException("relevance must be in [0, 1]");
            }
        }
    }

    interface TokenEstimator {
        int estimate(String text);
    }

    record Dropped(String itemId, String reason) {}

    record Assembly(List<ContextItem> selected, List<Dropped> dropped,
                    int estimatedTokens, int remainingTokens) {
        Assembly {
            selected = List.copyOf(selected);
            dropped = List.copyOf(dropped);
        }
    }

    static final class ContextAssembler {
        private static final Comparator<ContextItem> ORDER =
            Comparator.comparing(ContextItem::trustLevel)
                .thenComparing(ContextItem::relevance, Comparator.reverseOrder())
                .thenComparing(ContextItem::version, Comparator.reverseOrder())
                .thenComparing(ContextItem::id);

        private final TokenEstimator tokenEstimator;
        private final int contextWindow;
        private final int fixedPromptTokens;
        private final int outputReserve;
        private final int protocolReserve;

        ContextAssembler(TokenEstimator tokenEstimator, int contextWindow,
                         int fixedPromptTokens, int outputReserve, int protocolReserve) {
            this.tokenEstimator = Objects.requireNonNull(tokenEstimator);
            this.contextWindow = contextWindow;
            this.fixedPromptTokens = fixedPromptTokens;
            this.outputReserve = outputReserve;
            this.protocolReserve = protocolReserve;
        }

        Assembly assemble(Principal principal, String userQuestion,
                          List<ContextItem> candidates, Instant now) {
            int baseTokens = fixedPromptTokens + outputReserve + protocolReserve
                + tokenEstimator.estimate(userQuestion);
            int available = contextWindow - baseTokens;
            if (available < 0) {
                throw new IllegalArgumentException("固定内容与输出预留已超过上下文窗口");
            }

            List<Dropped> dropped = new ArrayList<>();
            Map<String, List<ContextItem>> newestByResource = new LinkedHashMap<>();

            candidates.stream()
                .sorted(Comparator.comparing(ContextItem::id))
                .forEach(item -> {
                    if (!item.tenantId().equals(principal.tenantId())
                        || !principal.readableResourceIds().contains(item.resourceId())) {
                        dropped.add(new Dropped(item.id(), "ACL_DENIED"));
                    } else if (item.validUntil().isBefore(now)) {
                        dropped.add(new Dropped(item.id(), "EXPIRED"));
                    } else {
                        List<ContextItem> currentChunks = newestByResource.get(item.resourceId());
                        if (currentChunks == null) {
                            newestByResource.put(item.resourceId(),
                                new ArrayList<>(List.of(item)));
                        } else {
                            long currentVersion = currentChunks.getFirst().version();
                            if (item.version() > currentVersion) {
                                currentChunks.forEach(old ->
                                    dropped.add(new Dropped(old.id(), "OLDER_RESOURCE_VERSION")));
                                newestByResource.put(item.resourceId(),
                                    new ArrayList<>(List.of(item)));
                            } else if (item.version() == currentVersion
                                && currentChunks.stream().noneMatch(old -> old.id().equals(item.id()))) {
                                // 同一资源的最新版本可以合法包含多个不同 chunk。
                                currentChunks.add(item);
                            } else {
                                dropped.add(new Dropped(item.id(),
                                    item.version() < currentVersion
                                        ? "OLDER_RESOURCE_VERSION"
                                        : "DUPLICATE_ITEM"));
                            }
                        }
                    }
                });

            List<ContextItem> eligible = newestByResource.values().stream()
                .flatMap(List::stream)
                .sorted(ORDER)
                .toList();
            List<ContextItem> selected = new ArrayList<>();
            int used = 0;
            for (ContextItem item : eligible) {
                int cost = tokenEstimator.estimate(item.content());
                if (cost <= available - used) {
                    selected.add(item);
                    used += cost;
                } else {
                    dropped.add(new Dropped(item.id(), "TOKEN_BUDGET"));
                }
            }
            return new Assembly(selected, dropped, baseTokens + used, available - used);
        }
    }

    public static void main(String[] args) {
        // 演示估算器仅用于让示例可运行,不代表任何真实模型 tokenizer。
        TokenEstimator conservativeEstimator = text -> Math.max(1, text.codePointCount(0, text.length()));
        var assembler = new ContextAssembler(conservativeEstimator, 200, 20, 60, 10);
        var principal = new Principal("tenant-a", Set.of("policy", "runbook"));
        Instant now = Instant.parse("2026-08-16T00:00:00Z");

        List<ContextItem> candidates = List.of(
            new ContextItem("p-v2", "policy", "tenant-a", 2,
                "退款超过一万元必须由财务复核。", TrustLevel.AUTHORITATIVE,
                0.95, now.plusSeconds(3_600)),
            new ContextItem("p-v1", "policy", "tenant-a", 1,
                "旧版退款流程。", TrustLevel.AUTHORITATIVE,
                0.90, now.plusSeconds(3_600)),
            new ContextItem("secret", "other", "tenant-b", 7,
                "其他租户数据。", TrustLevel.INTERNAL,
                0.99, now.plusSeconds(3_600)),
            new ContextItem("r-v3", "runbook", "tenant-a", 3,
                "复核失败时进入人工工单队列。", TrustLevel.INTERNAL,
                0.85, now.plusSeconds(3_600))
        );

        Assembly result = assembler.assemble(principal, "大额退款如何处理?", candidates, now);
        System.out.println("selected=" + result.selected().stream().map(ContextItem::id).toList());
        System.out.println("dropped=" + result.dropped());
        System.out.println("estimatedTokens=" + result.estimatedTokens());
    }
}

**复杂度与成本模型:**设候选数为 n,排序为 O(n log n);ACL、过期检查、去重与预算选择为 O(n);额外空间为 O(n)。token 估算成本还与总文本长度 L 有关,通常至少需要 O(L) 扫描。示例按 TrustLevel 枚举顺序排序(权威来源优先),再按相关性、版本和 ID 稳定排序;实际权重必须通过评测确定。

**工程边界:**示例把 ACL 做在应用层是为了展示不变量,真实系统还应在检索查询阶段过滤;readableResourceIds 不适合海量 ACL,应由授权服务或检索引擎的过滤能力承载。示例不截断单个 item,因此可能留下未使用预算,这是用容量换结构完整性。若要切片,应在索引阶段生成可追溯的原子块,而不是装配时硬切字符串。

场景设计题

场景:设计一个能读取内部文档并创建工单的企业 Agent 上下文层

要求:多租户;文档来自 Wiki、邮件和工单;允许只读检索和“创建工单”工具;高风险操作需确认;需要复现一次错误回答。

推荐设计:

  1. 信任与权限模型:每个片段携带 tenant、resource ID、版本、ACL、来源、有效期和内容 hash;检索时强制按主体过滤。
  2. 数据入口治理:解析器不执行附件宏或脚本;外部内容标记为不可信数据;同步链路保留原始来源和删除事件。
  3. 装配策略:按任务选择最少工具;固定规则、当前请求、证据和输出分别分桶;去重、稳定排序、按原子块裁剪。
  4. 注入防线:文档中的指令不改变工具权限;“创建工单”参数只允许 schema 中字段,tenant/user 从服务端会话派生;目标项目也必须鉴权。
  5. 高风险确认:模型先生成可审阅计划,展示目标、摘要和引用;用户确认后服务端重新鉴权并执行,使用幂等键。
  6. 记忆边界:只保存任务状态和经确认的偏好;工单真实状态每次从业务系统读取,不依赖会话摘要。
  7. 可复现性:保存模板/example/schema 版本、模型标识、候选与选中资源版本、ACL 策略版本、裁剪原因、工具调用和 usage;敏感正文按治理要求最小化保存。
  8. 评测:构建正常问答、无答案、冲突文档、过期文档、跨租户、直接/间接注入、工具越权和确认绕过测试集;越权测试必须阻断于模型外。

**加分点:**指出引用正确不等于结论正确,仍需检查证据是否真正蕴含回答;模型拒绝执行恶意指令是质量加分,但即使模型失守,工具网关也必须拒绝越权调用。

速记总结

  • Prompt 工程设计表达;上下文工程管理进入模型的信息、权限、预算、版本和生命周期。
  • 指令层级依目标 API 而异,是行为约定,不是认证授权边界。
  • 外部文本都应视为不可信数据;分隔符和注入检测只能减风险,不能替代工具侧强制控制。
  • 装配顺序应是先认证和 ACL,再相关性排序;无权数据不能以低分形式进入模型。
  • 上下文预算必须包括指令、历史、RAG、工具 schema、协议开销和输出预留。
  • 按消息/文档/JSON 对象等原子单元裁剪,避免硬切字符串破坏语义与结构。
  • 长窗口不保证质量;噪声、冲突、重复、位置和成本都要用任务评测验证。
  • 生成式摘要不是无损压缩;高风险事实保留来源、版本并按需回源。
  • 记忆不是业务数据库;只保存必要、可撤销、有期限且经授权的信息。
  • Prompt cache 是性能优化,不是授权证明;缓存键、失效和租户隔离必须设计。
  • 可复现性来自版本化快照与装配决策,不只是保存最终 prompt 文本。
  • 最终安全边界在模型外:最小权限、参数约束、重新鉴权、幂等、确认和审计。

参考资料