大模型应用的开发方式正在经历一次静默但深刻的转向——把精力从"怎么把模型本身训得更好"挪到了"怎么把模型周围的上下文和协作流程搭得更合理"。JetBrains 数据科学家兼 Python Advocacy Team Lead Jodie Burchell 在 Real Python Podcast 第 291 期中梳理了这股趋势:行业正从 post-training 优化走向 context engineering 与 multi-agent orchestration。这不是概念炒作,而是工程团队在真实项目中踩过坑后的务实选择。
为什么 post-training 不再是主角
Post-training 指的是在基础模型之上做进一步训练——微调(fine-tuning)、RLHF、DPO 等。这套方法在 2023 年是主流叙事:你有领域数据,你微调一个模型,它就变聪明了。
现实很快给出反馈:
- 数据门槛高:高质量标注数据的获取成本远超预期,很多团队攒了几千条就发现不够。
- 迭代周期长:一次微调从数据准备到评估跑完,动辄数天;业务需求却要求按周迭代。
- 效果边际递减:在通用基座模型已经很强的前提下,微调带来的增量往往不如把 prompt 和检索链路调好。
Burchell 指出,越来越多团队意识到:与其花两周微调一个模型只提升 3% 的准确率,不如花两天把 RAG 的检索策略和 prompt 结构调到位,效果可能更显著。这不是否定微调的价值,而是说在大多数应用场景里,模型本身已经不是瓶颈,上下文的组织方式才是。
Context Engineering:把"喂什么"当工程问题做
Context engineering 的核心思路:模型的能力是固定的,但你给它什么信息、以什么格式、在什么时机给——这些是可以系统化优化的工程变量。
它至少覆盖三个层面:
1. 检索策略
不是"搜到文档丢给模型"就完了。你需要考虑:
- 检索的粒度:段落级还是文档级?
- 检索的排序:用向量相似度还是混合 BM25 + 向量?
- 检索的过滤:要不要按元数据(时间、来源、权限)做二次筛选?
2. 上下文窗口的编排
模型窗口有限(即使 128K 也不够挥霍),放进来的内容要有结构:
- 系统指令放在哪、用户问题放在哪、检索结果放在哪,顺序影响输出质量。
- 长文档要做摘要压缩,而不是全文塞入。
- 多轮对话的历史需要截断或摘要,而不是无限制堆叠。
3. Prompt 的模板化与版本管理
Prompt 不是一次性写死的文本,而是需要迭代、测试、版本控制的工程产物。把它当代码一样对待——有模板、有变量、有 A/B 测试。
下面是一个把 context engineering 落地为代码的简化示例,使用 LangChain 搭建一个带结构化检索和 prompt 模板的 RAG 链路:
# rag_context_engine.py — 最小可运行的 context engineering 示例
# 依赖: pip install langchain langchain-openai langchain-community chromadb
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 1. 构建向量库(用几条模拟文档)
docs = [
"Python 3.12 引入了更快的 CPython 解释器,f-string 支持嵌套表达式。",
"JetBrains 的 DataSpell 专为数据科学家设计,支持 Jupyter 和 DataFrame 可视化。",
"LangChain 0.2 重构了核心抽象,Runnable 接口统一了链式调用。",
"RAG 系统的检索质量取决于分块策略和嵌入模型的选择,而非仅靠向量相似度。",
]
vectorstore = Chroma.from_texts(docs, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 2. 结构化 prompt 模板——明确区分指令、上下文、问题
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名技术顾问。只根据下方【参考资料】回答问题。"
"如果资料中没有相关信息,直接说'我不确定',不要编造。"),
("system", "【参考资料】\n{context}"),
("human", "{question}"),
])
# 3. 检索结果的格式化:编号 + 分隔,方便模型引用
def format_docs(docs):
return "\n\n".join(f"[{i+1}] {d.page_content}" for i, d in enumerate(docs))
# 4. 组装链路
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 5. 运行
if __name__ == "__main__":
answer = chain.invoke("RAG 系统怎么提升检索质量?")
print(answer)
运行前把 OPENAI_API_KEY 设到环境变量。这个示例虽然小,但已经体现了 context engineering 的几个关键决策:检索数量限制为 2 条、上下文编号格式化、prompt 里显式标注参考资料边界、temperature 设 0 降低随机性。每一个都是可以单独调优的工程参数。
Multi-Agent Orchestration:让多个专长模型协作
另一个明显趋势是多智能体编排。单一 LLM 试图包揽所有任务——规划、检索、编码、审查——效果往往不稳定。更靠谱的做法是让不同 agent 各司其职,通过流程编排串联。
Burchell 在节目中提到,这种思路借鉴了软件工程中"关注点分离"的原则:一个 agent 负责检索,一个负责生成,一个负责校验,彼此之间通过消息传递协作,而不是把所有逻辑塞进一个超长 prompt。
一个典型的三 agent 流程:
| Agent | 职责 | 输入 | 输出 |
|---|---|---|---|
| Planner | 拆解任务、制定步骤 | 用户原始需求 | 步骤列表 |
| Executor | 按步骤执行(检索/生成代码) | 单个步骤 + 上下文 | 中间结果 |
| Reviewer | 检查结果是否符合要求 | Executor 输出 + 原始需求 | 通过/打回修改 |
下面用纯 Python 实现一个最小多 agent 编排框架,不依赖任何 agent 库,方便理解核心逻辑后自行扩展:
# multi_agent_orchestrator.py — 三 agent 协作的最小实现
# 依赖: pip install openai
import os
from openai import OpenAI
client = OpenAI() # 需要设置 OPENAI_API_KEY 环境变量
def call_agent(role: str, instruction: str, input_text: str) -> str:
"""通用 agent 调用:角色 + 指令 + 输入 → 输出"""
response = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{"role": "system", "content": f"你是{role}。{instruction}"},
{"role": "user", "content": input_text},
],
)
return response.choices[0].message.content
# --- 三个 agent 的定义 ---
PLANNER_ROLE = "任务规划师"
PLANNER_INSTR = "将用户需求拆解为 2-4 个可执行的步骤,每步一句话描述。只输出步骤列表,不加解释。"
EXECUTOR_ROLE = "执行者"
EXECUTOR_INSTR = "根据给定的步骤和上下文,完成该步骤的任务。输出简洁的结果文本。"
REVIEWER_ROLE = "审查者"
REVIEWER_INSTR = ("对比最终结果与原始需求,判断是否满足。"
"如果满足,输出'PASS';否则输出'FAIL: <具体原因>'。")
def orchestrate(user_request: str, max_retries: int = 2) -> str:
"""三 agent 编排主流程"""
# Step 1: Planner 拆解任务
steps_text = call_agent(PLANNER_ROLE, PLANNER_INSTR, user_request)
print("=== 规划步骤 ===")
print(steps_text)
# Step 2: Executor 逐步执行
context = f"原始需求: {user_request}\n步骤列表:\n{steps_text}"
results = []
for line in steps_text.strip().split("\n"):
step = line.strip()
if not step:
continue
result = call_agent(EXECUTOR_ROLE, EXECUTOR_INSTR,
f"当前步骤: {step}\n上下文: {context}")
results.append(result)
print(f"=== 执行 {step} ===\n{result}\n")
# Step 3: Reviewer 审查
final_output = "\n".join(results)
review_input = f"原始需求: {user_request}\n最终结果:\n{final_output}"
review = call_agent(REVIEWER_ROLE, REVIEWER_INSTR, review_input)
print(f"=== 审查结果 ===\n{review}\n")
# 如果审查失败且还有重试次数,重新规划
if review.startswith("FAIL") and max_retries > 0:
print("审查未通过,重新编排...")
return orchestrate(
f"{user_request}\n上次失败原因: {review}\n请调整步骤。",
max_retries=max_retries - 1,
)
return final_output
if __name__ == "__main__":
result = orchestrate("写一个 Python 函数,从 CSV 文件中筛选出金额大于 1000 的记录并按日期排序。")
print("=== 最终输出 ===")
print(result)
这个示例的关键设计点:
- 每个 agent 有独立的角色和指令,职责边界清晰。
- Reviewer 可以打回结果,触发重新规划——这是单 agent prompt 很难做到的闭环反馈。
max_retries控制重试次数,防止无限循环。
实际项目中,你会把每个 agent 的指令做得更具体,可能给 Executor 加检索工具,给 Reviewer 加规则校验函数。但骨架就是这个。
落地前的几个务实判断
在把 context engineering 和多 agent 编排引入项目之前,有几件事值得先想清楚:
先确认瓶颈在哪。 如果你的应用连 prompt 都没调好、检索结果全是噪音,上多 agent 只会增加复杂度。先用单 agent + 结构化 prompt 把基线跑稳,再考虑拆分。
微调不是废了,而是退到特定场景。 当你需要模型输出格式高度固定(比如特定领域的 JSON schema)、或者基座模型在某类任务上确实能力不足,微调仍然是最直接的手段。只是它不再是一上来就做的默认选项。
多 agent 的编排成本是真实的。 每个 agent 调用都是一次 API 请求,三 agent 流程至少三次调用,加上重试可能五到八次。延迟和费用都要算进去。对于简单任务,单 agent + 好上下文往往更划算。
可观测性要提前建。 多 agent 流程出了问题,调试难度远高于单 prompt。从第一天就给每个 agent 的输入输出加日志,否则两周后你会完全不知道哪个环节在拖后腿。
LLM 应用优化正在从"炼模型"转向"搭管线"。Context engineering 让你把喂给模型的信息当工程参数来调;多 agent 编排让你把任务拆给专长角色来协作。两者都不是银弹,但它们代表了一种更可控、更可迭代的工作方式——而这正是工程团队最需要的。