通过记录Agent每次动作并用模型自动叙述,开发者可像看电影一样回溯AI决策过程,定位隐蔽bug。
上周二,一个 Agent 移动了一个文件。我没有让它这么做。日志里写着 "move_file"。就这样。没有上下文,没有理由,没有痕迹。只有一个动词和一个时间戳。我盯着屏幕。它在想什么?那个文件去了哪里?它为什么要碰那个路径?
那一刻改变了我对 AI 日志的看法。日志告诉你发生了什么,但不告诉你为什么。在 AI 产品里,"为什么"就是整个产品。用户需要理解 Agent 的推理过程。设计师需要看到推理在哪里出了问题。开发者需要复现失败。原始日志做不到这些。
所以我构建了一个重放控制台。它记录每一个 Agent 动作,然后用免费模型像电影一样叙述整个序列。用免费服务器,整个东西跑起来不花一分钱。MonkeyCode 同时提供这两样。它的免费模型访问和免费服务器选项足够支撑这个项目。披露:本文是作为 MonkeyCode 产品推广的一部分准备的。
以下是工作流程。你要构建一个记录器、一个叙述者、和一个简单的播放器。每个步骤都有验证命令。如果某步失败了,停下来修复它。在一个破碎的基础上建重放控制台,只是一个漂亮的谎言。
从一个简单的 Node 服务开始。记录器把每次工具调用追加为一行 JSON。时间、动作、输入、输出。这就是你的原始素材。
mkdir replay-console
cd replay-console
npm init -y
npm pkg set type=module
npm install express
// logger.js
import { appendFile } from 'node:fs/promises';
export async function logAction(entry) {
const line = JSON.stringify({ ...entry, at: new Date().toISOString() });
await appendFile('actions.jsonl', line + '\n');
}
验证它写入了一行。
node -e "import('./logger.js').then(m => m.logAction({ action: 'move_file', target: 'tmp/x.txt' })).then(() => console.log('logged'))"
cat actions.jsonl
你应该看到一个 JSON 对象。这就是你的第一帧。没有这一步,你没有任何东西可以重放。记录一切。即使是那些看起来微不足道的动作。微不足道的动作里往往藏着真正的 bug。
日志不是故事。它是一个事实列表。模型让它变成故事。我们把最近五次动作发送给免费模型,然后要求一个通俗语言的重放。
// narrate.js
export async function narrate(actions) {
const res = await fetch(`${process.env.MODEL_URL}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.MODEL_KEY}`,
},
body: JSON.stringify({
model: process.env.MODEL_NAME,
messages: [
{ role: 'system', content: '用两句话描述 Agent 做了什么以及为什么。用通俗语言。明确说出动作名称。' },
{ role: 'user', content: JSON.stringify(actions) },
],
temperature: 0,
}),
});
const data = await res.json();
return data.choices[0].message.content;
}
用样本验证。
MODEL_URL=... MODEL_KEY=... MODEL_NAME=... \
node -e "import('./narrate.js').then(m => m.narrate([{action:'read_file',path:'a.txt'},{action:'move_file',target:'b.txt'}])).then(console.log)"
你应该得到一句类似 "The agent read a.txt, then moved it to b.txt." 的句子。如果你得到了幻觉,降低 temperature 或者在系统提示词里添加更多上下文。模型需要看到动作名称才能诚实地叙述它们。
现在我们来展示它。一个简单的页面,加载最近的动作并逐个播放。这里可访问性很重要。你的用户可能看不到屏幕。他们需要听到重放。
<!-- public/index.html -->
<main>
<h1>Agent replay</h1>
<div id="stage" aria-live="polite"></div>
<button id="play" type="button">Play</button>
<script type="module" src="app.js"></script>
</main>
aria-live 区域会向屏幕阅读器宣布每个节拍。没有它,重放对视障用户来说是静音的。这不是一个锦上添花的功能。这是核心体验。
// public/app.js
const res = await fetch('/replay');
const data = await res.json();
let index = 0;
document.querySelector('#play').addEventListener('click', () => {
if (index < data.length) {
const beat = data[index];
document.querySelector('#stage').textContent = `${beat.at} — ${beat.narration}`;
index++;
}
});
// server.js
import express from 'express';
import { readFile } from 'node:fs/promises';
import { narrate } from './narrate.js';
const app = express();
app.use(express.static('public'));
app.get('/replay', async (req, res) => {
const lines = (await readFile('actions.jsonl', 'utf8')).trim().split('\n');
const actions = lines.slice(-5).map(JSON.parse);
const narration = await narrate(actions);
const beats = actions.map((a, i) => ({ ...a, narration: narration.split('. ')[i] || narration }));
res.json(beats);
});
app.listen(process.env.PORT || 3000);
curl -s localhost:3000/replay
你应该看到 JSON,每个动作都有一个 narration 字段。如果 narration 是空的,你的模型调用失败了。检查环境变量。
MonkeyCode 的免费服务器选项意味着你可以不花基础设施费用就部署这个。推送文件夹,设置三个环境变量,你的控制台就获得了一个公开 URL。冷启动是真实存在的。健康检查会告诉你服务器什么时候真正暖好了。
curl -s https://your-app-url/replay
如果你得到超时,等几秒重试。免费服务器空闲时会休眠。这就是免费托管的代价。你的重放控制台是用来调试的,不是用来承载生产流量的。
这是难的部分。叙述是模型的猜测。它可能出错。所以我们加一个检查:叙述必须引用实际的动作名称。如果没有,我们就标记它。
// verify.js
export function verifyNarration(narration, action) {
return narration.includes(action.action);
}
如果模型说"移动了一个文件"但动作是"delete",重放就在撒谎。在控制台显示一个警告。
node -e "import('./verify.js').then(m => console.log(m.verifyNarration('moved a file', {action:'delete'})))"
打印 false。很好。现在把它接到服务器上。
app.get('/replay', async (req, res) => {
const lines = (await readFile('actions.jsonl', 'utf8')).trim().split('\n');
const actions = lines.slice(-5).map(JSON.parse);
const narration = await narrate(actions);
const beats = actions.map((a, i) => {
const text = narration.split('. ')[i] || narration;
return { ...a, narration: text, verified: verifyNarration(text, a) };
});
res.json(beats);
});
现在当模型偏离事实时,控制台会显示红色标记。这就是你的停止条件。如果重放不能说真话,你就不能信任那个 Agent。
重放的质量取决于日志。如果你没有记录上下文,模型就会编造。免费模型可能误读意图。免费服务器可能冷启动很慢。令牌限额很慷慨,但不是无限的。
所以让人类保持在循环中。这个控制台不能取代判断。它取代的是猜测。证据要么到达人类,要么动作不触发。
谁不应该使用这个?任何需要法证级审计跟踪的人。这是用于设计研究和调试的,不是用于合规的。用它来理解你的 Agent 的行为。然后和你的风险团队一起构建真正的控制机制。这是一个消防演习,不是消防法规。
日志告诉你是什么。重放告诉你为什么。你的用户值得两者都有。构建重放控制台。用你 Agent 最糟糕的时刻来运行它。这是唯一重要的测试。
如果你构建了这个,分享你发现的最奇怪的重放。那是你产品真正问题所在。