聊天钩子
Docsbook 聊天钩子是属于您的 HTTPS 端点,AI 聊天 会在每次回答前后调用它们。您可以使用它们来强制执行合规团队制定的规则,将只有您的系统知道的事实提供给模型,或将每个问题和答案镜像到您自己的存储中——无需分叉聊天功能。
您将获得#
三个钩子,每个都有自己的 URL,且可独立设置:
| 钩子 | 运行时机 | 能否更改答案? | 用途 |
|---|---|---|---|
| 前置钩子 | 模型调用前,以阻塞方式运行 | 可以 — 阻止请求,或将上下文注入提示词 | 拒绝问题;添加读者的套餐、地区或功能标志 |
| 后置钩子 | 答案完成后 | 不可以 | 将问题/答案对记录到您自己的存储中 |
| 流式钩子 | 与后置钩子同时运行 | 不可以 | 为实时仪表板或告警渠道提供数据 |
只有前置钩子会更改任何内容,因为它是 Docsbook 唯一会等待的钩子。其他两个钩子会在读者已经获得答案后才被调度,并且系统永远不会读取它们的响应 — 它们无法对已显示的内容进行删减、重写或重新格式化。
所有套餐均可使用钩子,调用钩子不会消耗您的余额。URL 必须是 https://;保存时,http:// URL 会被拒绝。
问题如何被拦截或增强?#
设置一个预钩子 URL。Docsbook 会将读者的问题以 JSON 形式 POST 到该 URL 并等待响应,然后根据回复中的两个可选字段执行相应操作。
Docsbook 发送的内容:
{
"question": "What's the price for team@acme.com?",
"session_id": "sess_YOUR_SESSION_ID",
"workspace_id": 42
}Docsbook 能够理解的返回内容:
{
"block": true,
"reason": "Ask your account manager for account-specific pricing",
"inject_context": "The reader is on the Acme account, locale en-GB."
}block: true会停止请求。不会调用模型,也不会消耗 token。数据流会携带一个blocked_by_hook错误以及你的reason——但请注意下面关于读者实际看到内容的限制。inject_context会作为一条额外的系统消息添加到提示词中,仅对本次问题有效,位于你自己的系统提示词之后、问题本身之前。实时信息应放在这里:读者的套餐、所在地区、功能开关。- 其他任何情况——非 2xx 状态、无法解析的 JSON、空响应正文,或在超时时间内没有回复——聊天都会继续,效果与未设置钩子时完全相同。钩子故障会降低聊天体验,但不会导致聊天中断。
三个钩子共享5 秒超时,超时通过中止请求来强制执行。预钩子的超时会让读者额外等待这几秒;另外两个钩子不会让读者付出任何等待时间,因为答案已经完成流式传输。
后置钩子接收的内容#
一次 POST,此时读者已经看到了答案:
{
"question": "How do I rotate an API key?",
"answer": "Rotate an API key in Workspace settings…",
"tool_calls": [{ "tool": "read_page", "path": "guides/keys.md" }],
"latency_ms": 2840,
"workspace_id": 42,
"session_id": "sess_YOUR_SESSION_ID"
}tool_calls 是服务器针对这个问题实际获取的每个页面各对应一项,顺序与读取顺序一致——也就是读者以 Reading <page> 行形式看到的同一列表。这是检索记录,而不是模型自身工具调用的记录。
流式钩子接收 event: "message"、question、answer、refs(经过筛选后保留的引用)、workspace_id、session_id 和 latency_ms。它不携带 tool_calls;负责执行此操作的是后置钩子。
哪种钩子适用于哪种任务#
| 场景 | 钩子 | 原因 |
|---|---|---|
| 拒绝回答有关其他客户账户的问题 | 前置钩子 | 只有前置钩子可以阻止请求 |
| 向模型提供读者的方案和区域设置 | 前置钩子 (inject_context) |
模型需要在回答前获取这些信息 |
| 将每次交互镜像到你自己的分析存储中 | 后置钩子 | 需要已完成的答案,但不会更改任何内容 |
| 当回答耗时过长时向频道发出警报 | 流式或后置钩子 | 两者都携带 latency_ms |
| 对两种提示语措辞进行 A/B 测试 | 前置钩子 | 逐个问题修改提示语 |
| 确保某个字符串永远不会到达读者那里 | 前置钩子或系统提示 | 后置钩子会在读者获得该字符串后才运行 |
聊天钩子是否经过签名?#
否。Docsbook 发送的是一个普通的 POST,其中包含 Content-Type: application/json,且没有 HMAC 标头,因此你的端点不得将该负载视为来源证明。请对 URL 保密,在其路径或查询字符串中放置令牌,将其限制为仅接受来自 Docsbook 出站流量的请求,并将正文视为不受信任的输入。
Docsbook 的网络钩子是另一种机制,确实经过签名:对 X-Docsbook-Signature-256 中的原始正文使用 HMAC-SHA256,具体如 sha256=<hex> 所示。不要将网络钩子的验证代码照搬到聊天钩子中,并假定它可以验证任何内容——对于任何人都可以发送的正文,该代码也会验证通过。
从 MCP 客户端管理钩子#
三个工具可从 Claude Code、Cursor 或任何 MCP 客户端配置钩子:
set_chat_hooks # register pre / post / streaming hook URLs
test_chat_hook # send a test ping to one hook and report its status
get_chat_system_prompt # inspect the current system prompt向 set_chat_hooks 传递空字符串即可清除单个钩子。test_chat_hook 会发送 { test: true, hook_type, workspace_id, timestamp, message },并报告状态码和往返时间,使用与实时路径相同的 5 秒超时设置。管理面板中也可以编辑相同的字段。
为什么这是正确的方式(证据)#
| 规则 | 有效原因 | 来源 |
|---|---|---|
| 通过预钩子注入实时事实,而不是让模型回忆这些事实 | 检索增强生成产生的语言“比最先进的仅参数模型基线更加具体、多样且符合事实”——放入提示词中的事实有依据;回忆出的事实则没有 | Lewis 等,2020 — RAG |
| 在预钩子处进行阻止,而不是通过后处理 | 仅靠指令并不能可靠地阻止模型回答:普通调优会“强制模型完成句子,无论模型是否掌握相关知识”。能够保证的拒答,是根本不会传达到模型的拒答 | Zhang 等,2023 — R-Tuning |
| 将未签名的钩子负载视为不可信 | 签名可以证明来源:“为了确保你的服务器只处理由 GitHub 发送的 Webhook 交付,并确保交付内容未被篡改,你应验证 Webhook 签名”。聊天钩子不带签名,因此需要自行对其进行身份验证 | GitHub — 验证 Webhook 交付 |
| 以恒定时间比较 Docsbook 的 Webhook 签名 | “绝不要使用普通的 == 运算符。相反,应考虑使用诸如 secure_compare 或 crypto.timingSafeEqual 这样的方法” |
GitHub — 验证 Webhook 交付 |
限制#
- 读者看不到你的阻止原因。
reason字符串会在响应流中传递,但随附的 docs-site 小组件会将其替换为通用提示“出了点问题。请重试。”问题在于:该值会在线路上传输,自定义前端可以读取它,但开箱即用的小组件不会显示它。请将reason视为日志所用的值,并将读者必须看到的任何内容放入系统提示中。 - 匿名预览路径不会运行钩子。 在仓库拥有项目记录之前对其进行预览时,系统会在没有工作区的情况下回答问题,并跳过预钩子以及所有其他按项目执行的分支。
- 没有重试,也没有投递日志。 后置钩子和流式钩子只会分发一次,其结果不会被记录。如果你需要带重试的至少一次投递,以及可见的投递历史记录,请使用Webhooks,它们同时具备这两项功能。
- 没有签名,并且在复用 Webhooks 的方案之前没有添加签名的计划。 请参阅上文。
set_chat_hooks和test_chat_hook仍将自身描述为需要 Pro。 它们检查的功能在所有套餐中均已开放,因此过时的是工具描述,而不是实际行为。在这些字符串得到修正之前,该问题仍存疑。- 缓慢的预钩子由读者承担等待时间。 上限为五秒,并且发生在第一个令牌之前。请保持端点快速响应,或者不返回任何内容,让聊天继续进行。