面试官问上下文工程?这份答案能扛追问

2 阅读8分钟

面试官问上下文工程?这份答案能扛追问

上个月帮一个朋友模拟面试,他做 RAG 做了半年,项目讲得头头是道。面试官最后轻飘飘问了一句:「你这个系统,一次调用里上下文是怎么分配的?」他卡了十几秒,最后挤出一句「就是把 prompt 写好一点」。

上下文工程(Context Engineering)这个词,2026 年已经从 Prompt Engineering 里被单独拎出来考。考的不是定义背诵,是你会不会算账——一次调用里,system prompt、工具定义、检索片段、历史对话各占多少 token,超预算了先砍谁。这篇给你一份能扛住追问的答案。

先看这题现在有多热。 招聘板 InterviewStack.io 统计了 5,590 条在招 AI Engineer 岗位,明确写出「context engineering」这个词的有 272 条,占 4.9%;而且按首次出现日期看,占比从 4 月的 2.5% 涨到了 9 月的 4.9%,半年翻了一倍。

但这里有个坑,面试时也值得说:4.9% 是「显性提到这个词」的下限,不是「多少团队在乎」的度量。招聘里写「build agents with tool calling and retrieval」的岗位占 56.5%,那些活干的就是上下文工程,只是没用这个词。所以真问题是:岗位描述里不写,面试官照样问。

数据来源:InterviewStack.io 博客《Context Engineering Interview Questions and Answers》(其自建招聘板统计,非全网样本;趋势受该站来源覆盖增长影响,引用需一并说明局限)。

一、面试题原文

你们现在还在调 prompt 吗?如果让你负责一个 Agent 系统的上下文管理,你会怎么设计?上下文超预算了怎么办?

二、考察点

这题看着像考概念,其实考三件事:

  • 你能不能区分 Prompt Engineering 和 Context Engineering——分不清,说明你停留在「写咒语」阶段
  • 你有没有「上下文是有限资源」的量化意识——会算 token 账的人,做的系统才可维护
  • 你懂不懂位置偏见——东西塞进去了 ≠ 模型会看

三、分层参考答案

基础层:先纠正概念

一句话:Prompt Engineering 研究「这句话怎么写」,Context Engineering 研究「模型眼前应该出现哪些信息」。

维度Prompt EngineeringContext Engineering
关注点怎么写这句话模型眼前应该有哪些信息
操作对象一段提示词文本system + tools + 检索结果 + 历史 + 当前 query 的组合
变化频率静态(写好就固定)动态(每一轮都在变)

(微信不支持复杂表格,上表已精简。)

关键点:上下文是动态的。第 1 轮和第 50 轮,模型眼前的东西完全不一样。写死一段 system prompt 只是上下文工程的 10%。

原理层:把上下文当预算管

一次调用的上下文就五块,每块都吃 token:

  1. system prompt:角色与规则,通常占 10% 左右,短而稳定
  2. 工具定义:每个工具的 JSON schema,工具一多这块能吃掉 20% 以上
  3. 检索片段(RAG):从知识库召回的内容,通常是最大头
  4. 历史对话:随轮次线性增长,是超预算的第一嫌疑人
  5. 当前 query:用户这次的问题,占比小但优先级最高

超预算的裁剪顺序(面试直接说这个):先压历史(摘要在前、原文丢弃)→ 再降检索片段条数 → 最后才动 system 和 query。原因是 system 定义行为、query 是用户意图,砍这俩等于改需求。

实践层:位置也是信息

这是加分项,大部分候选人答不出来:模型对上下文开头和结尾的内容注意力最强,中间部分显著减弱。把关键信息放在中间 30%–70% 的位置,召回率会明显下降。

所以生产里的标准做法不是按相关度从高到低平铺,而是把最相关的放首尾,最弱的塞中间。

四、代码示例:预算分配 + 位置重排

纯离线可跑,不依赖 API Key。核心就是「算账」和「摆位置」:

def estimate_tokens(text):
    """离线 token 估算:中英文混合的经验近似。

    英文约 4 字符/token,中文约 1.5 字符/token,按字符类别加权。
    面试里先问一句「你用的什么 tokenizer」再给近似,比张口就报数稳。
    """
    ascii_chars = sum(1 for c in text if ord(c) < 128)
    cjk_chars = len(text) - ascii_chars
    return int(ascii_chars / 4 + cjk_chars / 1.5)

def truncate_to_tokens(text, cap):
    """按估算 token 数裁剪文本:保留前 cap 个 token 对应的字符。

    为什么不用 text[:n]:中英文每 token 的字符数差 6 倍,一刀切会切飞。
    """
    if estimate_tokens(text) <= cap:
        return text
    kept, used = [], 0
    for ch in text:
        cost = 0.25 if ord(ch) < 128 else 1 / 1.5
        if used + cost > cap:
            break
        kept.append(ch)
        used += cost
    return "".join(kept) + " …[已裁剪]"

def allocate_budget(system, tools, history, query, budget=8000):
    """按比例分配上下文预算,超限部分按优先级裁剪。"""
    blocks = [
        ("system", system, 0.10),
        ("tools", tools, 0.20),
        ("history", history, 0.50),
        ("query", query, 0.20),
    ]
    messages, report = [], []
    for name, text, ratio in blocks:
        cap = int(budget * ratio)
        actual = estimate_tokens(text)
        kept = truncate_to_tokens(text, cap)
        messages.append({"role": name, "content": kept})
        report.append(
            {"block": name, "budget": cap, "actual": actual, "truncated": actual > cap}
        )
    return messages, report

def place_by_relevance(chunks):
    """对抗 Lost in the Middle:相关度最高的放首尾,最弱的塞中间。

    chunks: [{"id": "c1", "score": 0.82}, ...],score 越高越相关。
    """
    ordered = sorted(chunks, key=lambda c: c["score"], reverse=True)
    result = [None] * len(ordered)
    left, right = 0, len(ordered) - 1
    for i, chunk in enumerate(ordered):
        if i % 2 == 0:
            result[left] = chunk
            left += 1
        else:
            result[right] = chunk
            right -= 1
    return result

实跑输出(budget=2000,history 故意灌超长,全部本地实跑非编造):

=== 1. 离线 token 估算 ===
英文 72 字符 -> 约 18 tokens
中文 32 字符 -> 约 21 tokens

=== 2. 上下文预算分配(budget=2000,演示裁剪)===
  system   预算   200 / 实际    14  未超限
  tools    预算   400 / 实际    20  未超限
  history  预算  1000 / 实际  1333  已裁剪
  query    预算   400 / 实际     8  未超限

=== 3. 按相关度重排(首尾放最相关)===
  重排后顺序(位置 -> 相关度):
    位置 0(开头)-> c2 score=0.91
    位置 1(中间)-> c3 score=0.67
    位置 2(中间)-> c4 score=0.15
    位置 3(中间)-> c1 score=0.42
    位置 4(结尾)-> c5 score=0.78

注意 history 那行:实际 1333 超了 1000 的预算,被裁掉了。面试时把这个 report 画出来,比说十句「我们做了上下文优化」都管用。

五、追问与应答

追问 1:为什么不用最贵的那个模型、上下文窗口那么大?

答:窗口大不等于用得好。一是贵,长上下文按 token 计费,塞满 128K 每次调用都是真金白银;二是「大海捞针」研究表明长上下文中间位置的信息召回会掉。长上下文是能力,不是策略。

追问 2:历史压缩具体怎么做?

答:常见三档。① 滑动窗口:只保留最近 N 轮原文,更早的丢掉;② 滚动摘要:把旧对话让模型总结成一段,替换原文;③ 分层记忆:短期放上下文、长期进向量库按需检索。工程上一般是三档混用,短会话用窗口,长会话上摘要。

追问 3:怎么知道你的上下文策略生效了?

答:这是最关键的一问。要有可观测性:记录每次调用的各块 token 占比、裁剪命中率、以及最终任务完成率。没有这三个数,所谓「优化」都是玄学。可以补一句项目话术:我们上线上下文压缩后,同样任务的 token 成本降了约四成(数字需按你自己的项目填)。

追问 4:Context Engineering 是噱头吗?

答:可以这么答——名字是新词,事是老事。检索、摘要、缓存这些一直都在做,单独拎出来是因为 Agent 把「上下文组装」从一次性变成每轮动态决策,复杂度上了一个量级。承认这点反而显得你想过。

六、加分项与踩坑点

加分项(说这些):

  • 「上下文是预算,不是垃圾桶」——一句话立住工程思维
  • 主动提 Prompt Caching:system 和工具定义放前面且保持稳定,才能吃到缓存的折扣
  • 主动提位置偏见和重排策略
  • 提「策略要能被评估」,并说得出三个指标

踩坑点(别说):

  • 把 Context Engineering 说成「就是写 system prompt」——直接暴露没做过 Agent
  • 说「我们窗口无限所以不用管」——既贵又不可靠
  • 只背定义不给数字——面试官会认为你没落地过
  • 吹「解决了幻觉」——上下文工程降低噪声,不解决幻觉本身

七、小结

  1. 概念分清:Prompt Engineering 管「怎么写」,Context Engineering 管「放什么」,后者是动态的
  2. 上下文当预算管:system 10% / tools 20% / history 50% / query 20% 是起步配比,超限先砍历史
  3. 位置也是信息:最相关的放首尾,别平铺在中间
  4. 策略要可评估:token 占比、裁剪命中率、任务完成率,三个数缺一不可