作者运营短租平台经验:Agent 表现差不是因为模型差,而是给了错误的状态数据;应将上下文视为应用数据而非文本。
大多数 AI Agent 演示都以模型为起点。
我们实际上花了更多时间思考该把什么东西放在模型周围。
我运营着 23 套度假短租房,一直在这些日常运营中引入 AI Agent。客人消息听起来是相对简单的问题之一:
客人发送一条消息。
把消息交给 LLM。
返回答案。
这在客人问出这类问题之前都运行得很好:
我可以停第二辆车吗?
现在,答案取决于房源本身、预订情况、停车安排、客人当前是否已入住、自房源信息编写以来是否有变动,还可能涉及到团队成员二十分钟前说过的一句话。
模型突然变成了其中最简单的部分。
在构建 Zugrow 的过程中,有一条经验反复出现:
一个 Agent 可能用了非常好的模型,却仍然做出错误的决策——因为你传给它的状态是错的。
于是我们不再把上下文当作一坨巨大的文本来处理,而是开始把它当作应用数据来对待。
我最初的反应很简单。
上下文越多 = 答案越好。
所以如果一位客人发消息咨询预订,为什么不给 Agent 呢:
房源描述
整段对话记录
以往客人提过的问题
结果也制造了一团乱麻。
重要的信息淹没在大量与当前问题毫不相关的内容中。
相反,Agent 应该得到最小有用的现实视图。
type GuestContext = {
property: {
name: string;
checkInTime: string;
checkOutTime: string;
parking: ParkingPolicy;
};
booking: {
arrivalDate: string;
departureDate: string;
guestCount: number;
status: BookingStatus;
};
conversation: {
recentMessages: Message[];
};
};
如果有人问停车的事,Agent 不需要 Wi-Fi 密码、锅炉使用说明和六个月的价格历史。
给它回答当前问题所需的东西就好。
在 Agent 系统中,这道理意外地容易被遗忘。
这比我预想的带来了更大的改变。
Parking is available behind the building.
Guests should normally use Bay 14.
Sometimes another space may be available.
Do not guarantee a second space.
这里发生了两件完全不同的事。
前三句描述的是客观世界。
最后一句描述的是 Agent 被允许做什么。
把这两者混在一起会让提示词变得难以推理。
我们现在把它们分开来考虑:
const facts = {
parkingType: "allocated",
primaryBay: "14",
additionalSpacePossible: true
};
const policy = {
mayGuaranteeAdditionalSpace: false
};
这种区分很重要,因为事实是会变化的。
策略通常变化慢得多。
这也意味着应用程序可以在不依赖模型记住规则的情况下强制执行某些规则。
房源文案是写给人看的。
Agent 需要的是结构化状态。
假设房源写道:
宾客可使用停车位。
非常合理的营销文案。
但 Agent 需要知道的是:
{
"parkingAvailable": true,
"guaranteedSpaces": 1,
"extraSpacesRequireApproval": true
}
这两者对人类传达的信息大致相同。
但对软件来说,是非常不同的输入。
我们加的 Agent 越多,我就越发现自己把模糊的房源信息转换成明确的状态。
Early check-in may sometimes be available.
{
"standardCheckIn": "15:00",
"earlyCheckInAllowed": true,
"earliestPossibleTime": "13:00",
"requiresTeamApproval": true
}
现在 Agent 可以在更接近现实的基础上进行推理了。
更重要的是,我们的应用程序可以阻止它做出不该做的承诺。
还有一个问题。
一个事实可能是正确的,但同时也是错误的。
wifi.status = "working";
但今天路由器坏了。
数据库在技术层面包含了一个事实。
所以有用的 Agent 上下文需要某种新鲜度的概念:
type ContextValue<T> = {
value: T;
updatedAt: Date;
source: "host" | "system" | "channel" | "agent";
};
这带来了更好的行为。
应用程序可以说:
if (hoursSince(wifi.updatedAt) > 72) {
requireVerification();
}
或者 Agent 可以谨慎地回应,而不是把事情当作确定无疑的来陈述。
这成了我一个重要的思维模型:
Agent 上下文不是知识,而是一张快照。
一旦 Agent 能够执行操作,这就更加重要了。
想象这样一个时序:
10:00:00 客人请求提前入住
10:00:02 Agent 读取可用性
10:00:08 保洁员更改了排班
10:00:11 Agent 确认提前入住
模型根据它掌握的信息做出了正确的决定。
但系统仍然做出了错误的决定。
这是一个戴着 AI 帽子的普通软件并发问题。
const suggestion = await agent.decide(context);
const latestState = await bookings.getCurrent(bookingId);
if (!stillValid(suggestion, latestState)) {
return requireHumanReview();
}
return execute(suggestion);
我们用模型来决定它想做什么。
然后应用程序检查这样做是否仍然被允许。
这第二个检查比把提示词再写长 500 字重要得多。
对话历史同样会造成这个问题。
很容易忍不住把每一条历史消息都塞进上下文窗口。
但想象一位客人在两周的入住期间发了 70 条消息。
当他们问:
明天退房时间是几点?
时,大部分对话历史都是无关的。
不要把历史当作一段巨大的对话记录,而是可以将其压缩成状态和近期事件。
const context = {
booking: currentBooking,
property: relevantPropertyFacts,
recentEvents: [
{
type: "guest_message",
text: "What time is checkout tomorrow?"
},
{
type: "late_checkout_request",
status: "not_requested"
}
]
};
模型得到的信息量少了很多。
但它得到的信息更重要。
这通常是我想要的权衡。
如果让我重新开始,我会更早构建这个部分。
当 Agent 给出一个奇怪的答案时,仅知道输出是不够的。
它在当时认为什么是真的?
所以每一个决策都应该有一条追溯链。
interface AgentTrace {
agent: string;
contextVersion: string;
input: unknown;
decision: unknown;
model: string;
timestamp: Date;
}
然后当有人问:
为什么 Agent 告诉这位客人他们有两个停车位?
你不需要猜测。
你可以检查提供给模型的精确状态。
大量看似"AI 错误"的问题,最终被证明是上游的普通软件错误。
比如预订状态不正确。
模型只是对我们意外传给它的现实给出了一个完全合理的答案。
我们的 Agent 流程越来越像这样:
Guest message
↓
Intent / task
↓
Context builder
↓
Relevant current state
↓
AI decision
↓
Deterministic validation
↓
State recheck
↓
Human approval or action
↓
Audit log
LLM 坐落在中间。
它不是应用程序。
这个区别写下来似乎很明显,但很多 Agent 原型把它们混为一谈。
data → enormous prompt → model → action
然后试图通过把那个巨大的提示词做得更大来提高可靠性。
最终你是在让一个概率模型来弥补缺失的应用程序架构。
这并不是特别好的扩展方式。
如果我现在从零开始构建一个 Agent 系统:
给模型完成当前任务所需的最少上下文。
把重要事实存为结构化数据,而不是散文。
把事实和 Agent 权限分开。
追踪重要上下文的上次更新时间。
在 Agent 执行操作之前立即重新检查状态。
优先选择近期事件和当前状态,而不是庞大的对话历史。
精确记录模型做出决策时看到了什么上下文。
构建 AI Agent 的奇怪之处在于,我做的时间越长,花在模型上的心思就越少。
模型很重要。
但大多数可靠性来自它周围相当普通的软件工程。
说实话,我认为这是个好消息。
我构建了 Zugrow,一个 AI 优先的物业管理平台,并在我所运营的度假短租房中使用了同一套系统。我特别感兴趣的是其他人在 Agent 系统中是如何处理上下文构建、过期状态和操作前验证的。