
Agent 的工程化难点不在单个组件的能力,而在组件之间的契约。提示词决定模型如何理解任务,工具调用把理解转化为行动,记忆让行动跨轮次累积,多 Agent 协作则把单一智能体的能力扩展到分工体系。这四个环节每一个都能单独工作,但组合在一起时,失效以“静默错位”的形式出现:提示词里的约束被工具返回值覆盖,工具执行成功但参数在语义上非法,记忆里的事实和推测混在一起注入下一轮,Agent 之间传递的消息在交接处丢失上下文。下面从四个位置给出可运行的检查代码。
系统提示词通常被当作不可变的字符串拼接进每次请求。问题在于,当上下文组装把检索块、历史对话、工具返回值一起塞进窗口时,系统提示可能被截断,而截断不会报错——模型只是开始表现得“忘了规则”。OpenAI 的 prompt caching 机制也加剧了这个问题的隐蔽性:只要前缀有任何一个字符变化,整个缓存失效,成本和延迟同时上升,但调用本身仍然成功。
工程上的第一个检查点是在组装上下文之前,先量化系统提示的 token 占用并锁定前缀:
import tiktoken
ENC = tiktoken.get_encoding("cl100k_base")
def build_prompt(system: str, few_shot: str, dynamic: str,
model_limit: int = 8192, max_output: int = 1024) -> dict:
static = system + "\n" + few_shot # 可缓存前缀
static_tokens = len(ENC.encode(static))
budget = model_limit - max_output - static_tokens
dyn_tokens = len(ENC.encode(dynamic))
if dyn_tokens > budget:
return {
"ok": False,
"error": "dynamic_exceeds_budget",
"static_tokens": static_tokens,
"dynamic_tokens": dyn_tokens,
"budget": budget,
"truncated_by": dyn_tokens - budget,
}
return {"ok": True, "prompt": static + "\n" + dynamic,
"cacheable_prefix_tokens": static_tokens}truncated_by 字段是关键:它让“动态部分超预算”从一次静默的模型行为漂移,变成一个可以在日志里聚合的指标。cacheable_prefix_tokens 则提醒开发者,静态部分越长,缓存命中的收益越大,但它的前提是静态部分逐字节不变——任何日期、UUID、会话 ID 混进前缀,缓存就废了。
工具返回错误时,常见的框架行为是让模型“看到错误后重试”。这个机制在偶发故障下是有效的,但它有一个失效模式:模型用完全相同的参数重试,而工具因为同一个原因持续失败,循环在步数上限内耗尽预算。更糟的是,如果工具是有副作用的(发通知、扣库存),重试可能产生重复执行。
正确的重试应该由执行层控制,而不是交给模型判断。一个带错误分类和幂等键的执行包装器:
import hashlib, time
RETRYABLE = {"timeout", "rate_limit", "transient_io"}
def execute(name: str, args: dict, fn, max_retries: int = 2) -> dict:
key = hashlib.sha256(f"{name}:{sorted(args.items())}".encode()).hexdigest()[:16]
attempts = 0
while attempts <= max_retries:
try:
result = fn(**args)
return {"ok": True, "result": result, "attempts": attempts + 1,
"idem_key": key}
except Exception as e:
err = classify(e) # 返回 timeout / rate_limit / ...
attempts += 1
if err not in RETRYABLE or attempts > max_retries:
return {"ok": False, "error": err, "detail": str(e),
"attempts": attempts, "idem_key": key}
time.sleep(min(2 ** attempts * 0.2, 2.0))
return {"ok": False, "error": "exhausted", "idem_key": key}idem_key 由工具名和排序后的参数决定,它同时解决了两个问题:重试时下游可以按 key 去重,避免重复副作用;日志里可以用 key 聚合“同一逻辑操作被调用了几次”。classify 把异常映射到错误类别,RETRYABLE 集合决定了哪些错误值得重试。参数不变的重试应该被限制次数,而不是被无限允许——模型没有“这个错误重试也不会好”的判断力,执行层必须有。
Agent 的记忆系统通常用向量检索找“最相关的历史片段”。但相关性高的片段可能来自很久以前,而业务规则已经变了。一个用户三个月前说“预算上限 5000”,今天说“预算上限 8000”,向量检索可能把两条都召回,模型看到两个冲突的数字,倾向于选更显眼的那条。
工程上的缓解是在记忆条目上同时记录时间戳和有效区间,检索时按时间做衰减或显式过滤:
import time
def retrieve_memory(query_vec, store: list, top_k: int = 5,
half_life_days: float = 30) -> list:
scored = []
now = time.time()
for item in store:
sim = cosine(query_vec, item["vec"])
age_days = (now - item["ts"]) / 86400
decay = 0.5 ** (age_days / half_life_days)
scored.append({
"content": item["content"],
"score": sim * decay,
"age_days": round(age_days, 1),
"superseded": item.get("superseded_by"),
})
fresh = [s for s in scored if not s["superseded"]]
return sorted(fresh, key=lambda x: x["score"], reverse=True)[:top_k]superseded_by 字段需要在写入新记忆时显式维护:当用户更新了某个事实,旧条目被标记为“已被替代”而不是删除。删除会丢失审计线索,保留但不标记会让冲突内容同时被召回。记忆的工程问题不是“记住多少”,而是“冲突时信哪个”,superseded_by 就是这个决策的落点。
多 Agent 系统的经典失效是“交接丢上下文”:规划 Agent 把子任务交给执行 Agent 时,只传了一句自然语言描述,执行 Agent 不知道约束条件、输入数据和验收标准,只能自由发挥。任务完成得“看起来像那么回事”,但和上游期望的不一致。
正确的交接应该传递一个结构化的任务契约,而不是一段描述:
from pydantic import BaseModel
class TaskHandoff(BaseModel):
task_id: str
objective: str # 要达成什么
constraints: list[str] # 不能违反的规则
inputs: dict # 必需的输入数据
acceptance: list[str] # 验收标准,逐条可检查
deadline_sec: float
return_schema: dict # 下游必须按此结构返回
def delegate(handoff: TaskHandoff, executor) -> dict:
missing = [k for k in handoff.inputs if handoff.inputs[k] is None]
if missing:
return {"ok": False, "error": "incomplete_handoff",
"missing_inputs": missing}
result = executor(handoff)
failed = [a for a in handoff.acceptance
if not check_acceptance(a, result)]
return {"ok": not failed, "failed_criteria": failed,
"result": result}acceptance 的每一条必须是可被程序检查的,而不是“质量要好”这种描述。incomplete_handoff 检查在委托之前执行,防止下游 Agent 在缺少关键输入时自行猜测。return_schema 让上游可以校验返回值结构,而不是解析自由文本。多 Agent 协作的可靠性,取决于每次交接是否携带了足够让下游独立完成任务的完整信息——自然语言描述适合给人看,结构化契约才适合给 Agent 执行。
这四个断层的共同逻辑是:Agent 全路径上的每一个环节都在把上一环的输出当作自己的输入,而输出的“合法”不等于“完整”。提示词要检查 token 预算和可缓存前缀,工具调用要控制重试次数和幂等性,记忆要区分相关性和时效性,交接要传递结构化契约而非自然语言。每一层都加一道可执行的检查,Agent 才能从“能跑”走到“可交付”。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。