系列收官。前九篇我们沿着「一条消息的旅程」纵向穿透了从平台到流水线、从模型到沙箱、从 API 到持久化的每一层。但有一些东西不属于任何单独一层,而是横向贯穿所有层——可观测性、国际化、配置、RAG、安全。这一篇把它们收齐,最后给你一张「想改某个功能该从哪入手」的改码地图,让整个系列闭环。
回看第一篇的分层图:Platform → Pipeline → Provider,旁挂 Plugin/Box/Persistence/API。这些是纵向的功能层。而下面这些能力,每一层都要用到、却又不该塞进任何一层的业务逻辑里:
flowchart LR
subgraph layers["纵向功能层"]
direction TB
P1["Platform"]
P2["Pipeline"]
P3["Provider"]
P4["Persistence / API"]
end
OBS["可观测性<br>monitoring + telemetry"]
I18N["国际化 i18n"]
CFG["配置 instance_config"]
SEC["安全 鉴权/脱敏/沙箱边界"]
OBS -.贯穿.-> layers
I18N -.贯穿.-> layers
CFG -.贯穿.-> layers
SEC -.贯穿.-> layers
这些就是横切关注点。LangBot 处理它们的方式,恰恰最能体现它「toC + toB 双场景」的工程取向。
LangBot 把「可观测性」拆成两条互不相关的线,理解这个区分很关键:
| <strong>Monitoring(监控)</strong> | <strong>Telemetry(遥测)</strong> | |
|---|---|---|
| 数据归谁 | 留在<strong>本地数据库</strong> | 匿名上报到 LangBot Space |
| 看什么 | 这个实例自己的消息、查询、错误 | 跨实例的功能<strong>采纳率</strong>统计 |
| 谁看 | 部署者(Web 面板) | 项目维护方 |
| 可关闭 | — | <code>space.disable_telemetry</code> |
监控数据在「主线剧情」中被反复写入。回顾第四篇:RuntimePipeline.process_query() 在查询开始/成功/出错时都会记录监控;第五篇:RuntimeProvider 把每次模型调用的耗时、token 用量写进监控。这些记录最终落到 monitoring_messages 等表。
为了不让监控数据无限膨胀,第二篇提到的 monitoring-cleanup 后台任务会定期调用:
# src/langbot/pkg/api/http/service/monitoring.py:23
async def cleanup_expired_records(self, retention_days: int, batch_size: int = 1000) -> dict[str, int]:
"""Delete monitoring records older than the specified retention period."""
if retention_days < 1:
raise ValueError('retention_days must be >= 1')
# ... 按表分批删除早于 cutoff 的记录 ...
默认保留 30 天、每小时检查一次(可经 monitoring.auto_cleanup 配置)——这正是「toB 场景下长期运行不爆库」的考量。
遥测心跳(第二篇的 telemetry-heartbeat 任务)启动后延迟 30 秒发一次、之后每天一次:
# src/langbot/pkg/telemetry/heartbeat.py:119
async def heartbeat_loop(ap: core_app.Application) -> None:
"""Send one heartbeat shortly after startup, then daily."""
# Small delay so managers (platform, skills, plugins) finish loading first
await asyncio.sleep(30)
while True:
try:
payload = await build_heartbeat_payload(ap)
await ap.telemetry.start_send_task(payload)
except Exception as e:
...
await asyncio.sleep(HEARTBEAT_INTERVAL_SECONDS) # 24h
build_heartbeat_payload 收集的全是配置类别与对象计数——数据库类型、向量库类型、Box 后端、各类资源数量、适配器类型名(不是账号):
# src/langbot/pkg/telemetry/heartbeat.py:84
features['pipeline_count'] = await _count(ap, persistence_pipeline.LegacyPipeline)
features['mcp_server_count'] = await _count(ap, persistence_mcp.MCPServer)
features['knowledge_base_count'] = await _count(ap, persistence_rag.KnowledgeBase)
文件头的 docstring 把边界写得很死:
The snapshot contains only configuration categories and object counts — never names of user resources … never message content, never credentials.