在 AI 代理日志到达数据库之前对其进行编辑
您的代理会记录每个提示和工具调用。 成绩单中遗漏的 SSN 会变成 GDPR 披露。 三行中间件在写入行之前修复了它。
您的 AI 代理会记录提示。 工具调用输入。 工具结果。 最后的回应。 这曾经是调试数据的金矿。 今天,这是一项 GDPR 披露,正在等待支持票以携带 SSN 通过验证。
您无法阻止用户粘贴敏感数据。 您可以阻止原始文本到达日志存储、可观察性供应商和每周评估导出。 一个小型中间件可以一次性完成此操作。
大多数代理设置已经存在的泄漏
// before: every raw prompt, tool call, and tool result lands in the log row
logger.info({
event: 'agent.turn',
prompt: userInput,
tool_calls: toolCalls,
tool_results: toolResults,
});
// one support ticket later: "My SSN is 123-45-6789 and card 4111 1111 1111 1111"
// sits in the logs, the observability vendor, and the weekly eval export.
现在每一行都有一个卡号。 日志存储将其保留 30 天。 可观测性 SDK 将其发送给第三方。 两天后,eval 导出会拾取相同的字符串。 一张卡五份; 所有这些都超出了静态加密策略的范围。
通过一次调用即可检测 PII
波托伊 /v1/pii/detect 端点扫描文本中的电子邮件、电话号码、SSN、信用卡(经过 Luhn 验证)、IP 地址和出生日期。 它返回每个结果,其中包含起始偏移量、结束偏移量和可以放置在适当位置的屏蔽值。
要求
curl -X POST https://api.botoi.com/v1/pii/detect \\
-H "Content-Type: application/json" \\
-d '{"text": "Reach me at alice@example.com or 555-123-4567. Card: 4111 1111 1111 1111."}'
回复
{
"found": true,
"count": 3,
"findings": [
{ "type": "email", "value": "alice@example.com", "start": 12, "end": 29, "masked": "al***@example.com" },
{ "type": "phone", "value": "555-123-4567", "start": 33, "end": 45, "masked": "***-***-4567" },
{ "type": "credit_card", "value": "4111 1111 1111 1111", "start": 53, "end": 72, "masked": "************1111" }
]
}
三场比赛,三场蒙面替换,位置可以拼接干净。 无需维护正则表达式库,无需保持最新的 SSN 前缀表,无需自己编写 Luhn 通行证。
写入前进行编辑的日志中间件
正确的位置是行离开进程之前的最后一跳。 每个上游组件仍然可以看到它需要的原始文本; 持久副本已被清理。
// log-redact.ts
import type { LogRecord } from './types';
const PII_FIELDS = ['prompt', 'tool_calls', 'tool_results', 'output'] as const;
export async function redactPii(record: LogRecord): Promise<LogRecord> {
const clone = structuredClone(record);
for (const field of PII_FIELDS) {
const value = clone[field];
if (!value) continue;
clone[field] = await scrub(JSON.stringify(value));
}
return clone;
}
async function scrub(text: string): Promise<string> {
const res = await fetch('https://api.botoi.com/v1/pii/detect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: \`Bearer \${process.env.BOTOI_API_KEY}\`,
},
body: JSON.stringify({ text }),
});
const data = await res.json();
if (!data.found) return text;
// Replace from the end of the string so offsets stay valid.
const sorted = [...data.findings].sort((a, b) => b.start - a.start);
let scrubbed = text;
for (const f of sorted) {
scrubbed = scrubbed.slice(0, f.start) + f.masked + scrubbed.slice(f.end);
}
return scrubbed;
}
该片段中的三个细节很重要。 首先,它遍历已知的 PII 密集字段,而不是整个记录; 您无需清除请求 ID。 其次,它在发送到 API 之前将每个字段序列化为单个字符串,因此一次调用即可覆盖整个工具结果。 第三,它从字符串末端拼接替换,因此偏移不会在字符串下方移动。
将其连接到记录器中
// logger.ts
import { redactPii } from './log-redact';
export async function logTurn(raw: LogRecord) {
const safe = await redactPii(raw);
await logStore.write(safe);
}
// anywhere in your agent loop:
await logTurn({
event: 'agent.turn',
prompt: userInput,
tool_calls: toolCalls,
tool_results: toolResults,
});
称呼 logTurn 代替您现有的 logger.info 在转弯边界处。 上游的一切都保持不变。
失败关闭,而非失败静默
检测端点通常会在 20 毫秒内做出响应。 当超时时,您仍然可以选择:记录原始行(泄漏风险)或删除敏感字段并记录标记。 对于合规性敏感的工作负载,删除是更安全的默认设置。
async function redactPiiSafe(record: LogRecord): Promise<LogRecord> {
try {
return await Promise.race([
redactPii(record),
new Promise<LogRecord>((_, reject) =>
setTimeout(() => reject(new Error('pii-detect timeout')), 250)
),
]);
} catch (err) {
// Fail closed: drop the sensitive fields rather than logging them raw.
return { ...record, prompt: '[REDACT_FAILED]', tool_calls: [], tool_results: [] };
}
}
将超时设置为较小的值。 250 毫秒足以吸收区域减速,而不会阻塞健康的请求路径。
Python版本
# log_redact.py
import os, json, httpx
PII_FIELDS = ('prompt', 'tool_calls', 'tool_results', 'output')
API = 'https://api.botoi.com/v1/pii/detect'
async def scrub(text: str) -> str:
async with httpx.AsyncClient(timeout=0.25) as client:
r = await client.post(
API,
headers={'Authorization': f"Bearer {os.environ['BOTOI_API_KEY']}"},
json={'text': text},
)
data = r.json()
if not data.get('found'):
return text
out = text
for f in sorted(data['findings'], key=lambda x: x['start'], reverse=True):
out = out[:f['start']] + f['masked'] + out[f['end']:]
return out
降低 scrub 进入代理框架的日志挂钩。 FastAPI 中间件、LangChain 回调和 OpenInference span 导出器都接受异步函数。
这个中间件不做什么
- 它不会捕获看起来不像任何受支持类型的名称、地址或帐号。 这些需要一个命名实体模型和一个策略决策(掩码?丢弃?编辑整个回合?)。
- 它不能保护您的模型供应商看到原始提示。 For that, run the same detect call on the client before you send to the model.
- 它不会取代数据保留政策。 不管怎样,缩短日志 TTL。
这属于两个地方
| 层 | 保护 | 通话时间 |
|---|---|---|
| 模型请求之前 | 模型供应商、训练数据、评估泄漏 | 阻塞、用户可见的延迟 |
| 写入日志之前 | 日志存储、可观测性供应商、导出 | 带外,对用户不可见 |
首先发送日志写入中间件。 它在热路径之外运行并阻止最常见的泄漏模式。 一旦覆盖原木面,就添加预模型版本。
获取 API 密钥并开始
匿名访问每分钟为您提供 5 个请求,足以针对示例日志尝试端点。 对于生产中间件,请获取免费密钥: botoi.com/api/signup。 免费套餐涵盖每天 1,000 次擦洗呼叫,无需信用卡。
请参阅以下位置的完整端点参考: PII 检测 API 页面 或浏览 api.botoi.com/docs 对于其他 149 个端点。
FAQ
- 为什么 AI 代理日志比普通服务器日志泄露更多 PII?
- 代理记录整个提示、每个工具调用输入以及每个工具输出。 曾经存在于“不记录”标志后面的支持记录现在出现在五个地方:协调器、工具服务器、可观察性供应商、模型提供者和训练评估集。
- 编辑步骤应该在哪里运行?
- 在行发送到日志存储之前,在日志写入器边界运行它。 这样,每个上游组件(编排器、工具、可观察性 SDK)都可以看到它需要的原始文本,并且只有持久的副本会被清理。
- 正则表达式编辑器能捕获所有内容吗?
- 不会。自行编写的正则表达式会错过间距异常的信用卡号、看起来像其他 9 位数字的 SSN 以及人名。 像 /v1/pii/detect 这样的 API 在卡片上运行 Luhn、过滤 SSN 前缀并返回位置,这样您就可以只删除匹配项,而不是整行。
- Botoi PII 检测 API 会增加多少延迟?
- 端点在边缘运行,并在 20 毫秒内返回 500 个令牌的有效负载。 您可以在日志中间件中同步调用它,而不影响用户可见的响应时间; 发送响应后发生日志记录。
- 在发送给模型之前我可以在客户端上进行编辑吗?
- 是的,这是一个很好的第二层。 在服务器中间件中进行编辑可以保护您的日志存储; 在客户端中进行编辑可以防止您的模型供应商看到原始 PII。 两者都是 GDPR 友好的设置。
开始使用 botoi 构建
150+ 个 API 端点,涵盖查询、文本处理、图片生成和开发者工具。免费套餐,无需信用卡。