Flue 接入 Sentry 可观测性:错误 Issue、日志与 GenAI 追踪的完整落地指南

发布时间:2026/9/17 0:38:35
Flue 接入 Sentry 可观测性:错误 Issue、日志与 GenAI 追踪的完整落地指南 Flue 接入 Sentry 可观测性错误 Issue、日志与 GenAI 追踪的完整落地指南【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue导读本文以 blueprints/tooling--sentry.md 为核心骨架系统讲解如何在 Flue 沙箱 Agent 框架项目中接入 Sentry如何按 Node / Cloudflare 两种目标运行时选择并初始化 SDK如何将 Flue 的事件流log.*调用、operation、submission_settled、submission_recovery映射为 Sentry 的 Issues、Logs 与 Breadcrumbs以及如何借助flue/opentelemetry将invoke_agent → chat / execute_tool的 span 层级与 token 用量送入 Sentry 追踪。读完本文你将获得一份可直接复制运行的双端Node / Cloudflare Durable Object集成源码、完整的配置参数表、数据出站安全策略以及 10 步可操作的验证清单。适用前提本文面向 Flue 项目的 AI 编码 Agent或开发者执行的“添加/更新 Sentry 集成”任务代码示例基于本仓库 packages/opentelemetry/package.json 中flue/opentelemetry2.0.6与opentelemetry/api^1.9.0的 API 编写Sentry SDK 版本以sentry/node^10.64.0与sentry/cloudflare^10.64.0为准。集成目标把 Flue 的运行时语义翻译成 Sentry 的四类信号在动手写代码前必须先理解这个 blueprint 定义的核心映射模型——它决定了哪些 Flue 事件变成 Sentry 的“问题”哪些只作为“日志”或“面包屑”Flue 事件FlueObservation.typeSentry 信号触发条件operation且isErrorIssueterminal failure顶层 Agent 操作失败携带原始错误、throw-site 堆栈、durationMs与operationKindsubmission_settled且outcome failedIssue仅当未被前置捕获持久化 submission 结算失败若该 submission 的operation已捕获则跳过避免重复 Issuesubmission_recoveryBreadcrumb绝不作为 Issue协调器重试/对账失败submission 卡在重试、尚未或永不结算因为deferred/agent_unavailable会在每次重试唤醒时反复出现loglog.info/log.error等Sentry Logs按事件级别路由携带flue.*关联标签与 scrub 后的属性永不提升为 Issue其设计哲学非常明确Issue 只留给“终态失败”失败的顶层 Agent 操作或失败的持久化 submission 结算Agent 记录并能继续前进的可恢复错误只进入 Sentry Logs协调器的恢复/对账失败作为 Breadcrumb 附着在下次捕获的事件上。一个会话conversation的 spans、logs、issues 共享同一条 trace。另外模型与工具内容prompt、completion、工具参数与结果默认完全不出站只有用户显式开启下面的 record 开关后才会进入 trace span且经过脱敏与截断处理。第一步检查项目确定目标运行时与依赖集成开始前AI Agent 需要按序完成项目勘察阅读AGENTS.md本仓库根目录 AGENTS.md及项目本地指令。检测包管理器本仓库为 pnpm 工作区见 pnpm-workspace.yaml。按顺序选择第一个存在的源码根目录root/.flue/→root/src/→root/。检查flue.config.ts、vite.config.ts、部署命令、app.ts、agents/下所有模块、环境类型与密钥约定。随后安装依赖安装flue/opentelemetry版本与项目所用 Flue 版本匹配和opentelemetry/api^1.9.0flue/opentelemetry的 peer 依赖见 packages/opentelemetry/package.json。再根据目标运行时安装 Sentry 包Nodesentry/node^10.64.0Cloudflaresentry/cloudflare^10.64.0严禁在 Cloudflare 上使用sentry/node。如果无法确定目标必须询问用户。不要为了“一份静态源码兼容双端”而同时安装两个 SDK——Node 与 Cloudflare 的初始化方式差异巨大见下文强行兼容只会制造脆弱的集成。第二步配置 Sentry 环境变量除非项目已有既定的 Sentry 约定否则使用以下环境变量变量用途说明SENTRY_DSN项目 DSN必须通过部署环境配置绝不可硬编码进应用源码也不可凭空捏造SENTRY_ENVIRONMENT环境名可选如production、stagingSENTRY_RELEASE发布标识可选如 commit SHASENTRY_TRACES_SAMPLE_RATE采样率0到1默认0只发送错误与日志大于0时额外发送 Flue span 层级SENTRY_AI_RECORD_INPUTS输入记录开关true时在 trace span 中纳入 prompt、系统指令、工具定义/参数SENTRY_AI_RECORD_OUTPUTS输出记录开关true时在 trace span 中纳入模型输出、工具结果、异常消息/堆栈注意Sentry DSN 只允许提交事件并不授予读取项目数据的权限因此它可以安全地放在客户端可见的部署配置中。集成时应更新项目维护的.env.example、环境类型或部署文档并沿用其部署配置约定。若需要选择性生产采样Sentry 的tracesSampler选项可替代扁平采样率——但仅在用户明确要求时才添加。第三步决定什么可以离开应用数据出站边界出站策略由两个 record 开关与三层防线共同决定默认两个开关均为false集成向 Flue 的 OpenTelemetry 仪器化传入content: false模型/工具内容完全无法到达 Sentry。任一开关开启内容经过一个transform管道——只放行被启用的方向isInputContent/isOutputContent分流见下文代码、对敏感键做scrub脱敏、并用truncateContent把适配器内置的每 span 56 KiB 内容预算收紧到每属性 16 KiB。所谓“总是出站”的元数据仅限时序、token 用量、模型标识、错误类型、flue.*关联 ID。其余一切皆受策略约束。Sentry Logs 转发出去的日志属性同样经过同一个scrub脱敏。此外blueprint 建议对照应用自身的密钥命名审查SENSITIVE_KEY正则/api[-_]?key|authorization|cookie|dsn|password|secret|token/i当项目涉及受监管或可识别用户的数据时需要扩展该模式。第四步创建 Flue 集成Node 版在源码根目录创建source-dir/sentry.ts。Node 与 Cloudflare 两个变体共享同一套桥接逻辑与辅助函数区别仅在 Sentry SDK 的初始化方式。以下为 Node 完整实现关键点见行内注释// flue-blueprint: tooling/sentry1 import { type ContentOption, createOpenTelemetryInstrumentation, type GenAIContentType, truncateContent, } from flue/opentelemetry; import { type FlueObservation, instrument } from flue/runtime; import * as Sentry from sentry/node; const recordInputs process.env.SENTRY_AI_RECORD_INPUTS true; const recordOutputs process.env.SENTRY_AI_RECORD_OUTPUTS true; const tracesSampleRate clampRate(process.env.SENTRY_TRACES_SAMPLE_RATE, 0); // Sentry 自带会直接 patch AI 供应商 SDK 的 integrations。Flue 的仪器化 // 已经为每次模型回合发射一个 chat span若再启用这些集成会导致每次 // 模型调用被重复计数。 const SENTRY_AI_PROVIDER_INTEGRATIONS new Set([ Anthropic_AI, OpenAI, Google_GenAI, LangChain, LangGraph, VercelAI, ]); Sentry.init({ dsn: process.env.SENTRY_DSN, enabled: Boolean(process.env.SENTRY_DSN), environment: process.env.SENTRY_ENVIRONMENT ?? process.env.NODE_ENV, release: process.env.SENTRY_RELEASE, tracesSampleRate, // 以流式方式在 span 结束时就发送到 Sentry这样在父 span 之后才完成的 // gen_ai 子 span 不会丢失。 traceLifecycle: stream, streamGenAiSpans: true, enableLogs: true, integrations: (defaults) defaults.filter((integration) !SENTRY_AI_PROVIDER_INTEGRATIONS.has(integration.name)), }); // Sentry.init 已把 Sentry 注册为全局 OTel tracer provider因此 Flue 的 // span 无需额外接线即可流入 Sentry。内容捕获在适配器里默认开启 // contentPolicy() 将其收窄到 record 开关允许的范围。仪器化是带 key 的 // dev 重载会替换之前的注册而非叠加重复项。 if (tracesSampleRate 0) { instrument(createOpenTelemetryInstrumentation({ content: contentPolicy() })); } // 失败的 submission 会先发射一个内容丰富的 operation 失败事件携带原始 // 错误与 throw-site 堆栈随后再发射 submission_settled——后者的持久化 // error 会把非 Flue 原因折叠成通用的 internal-error 负载。先捕获 operation // 并记下其 submissionId使随后的 settlement 被跳过一个没有前置捕获的 // settlement崩溃后对账产生则从其自身的 errorInfo 捕获。 const capturedFailedSubmissions new Setstring(); // 关闭时尽力 flush 缓冲事件尤其是 SDK 会批量发送的 Sentry Logs。绝不在 // 这里调用 process.exit()——Flue 生成的服务器自行处理 SIGINT/SIGTERM、等待 // 生命周期停止并以正确退出码退出此监听器只在那个窗口内 flush。它不是投递 // 保证服务器在 stop 解析后立即退出Node 不会等待信号监听器发起的 promise // 进行中的 flush 可能被截断。trace 与 issue 在运行期间已发送只有最近才缓冲 // 的日志有风险。 const flush () void Sentry.flush(2000); if (process.env.SENTRY_DSN) { process.on(SIGINT, flush); process.on(SIGTERM, flush); } instrument({ // 带 key 的注册dev 重载时本模块重新求值而运行时注册表仍保留最新安装 // 者胜出——之前的桥接及其信号监听器被 dispose因此任何事件都不会被 // 重复上报。 key: Symbol.for(flue.sentry.bridge), observe(event) { if (event.type operation event.isError) { captureTerminalFailure(event.errorInfo ?? event.error, correlationTags(event), { durationMs: event.durationMs, operationKind: event.operationKind, }); if (event.submissionId) capturedFailedSubmissions.add(event.submissionId); return; } if (event.type submission_settled) { const alreadyCaptured capturedFailedSubmissions.delete(event.submissionId); if (event.outcome failed !alreadyCaptured) { captureTerminalFailure(event.errorInfo ?? event.error, correlationTags(event)); } return; } if (event.type submission_recovery) { recordRecoveryBreadcrumb(event); return; } if (event.type log) { Sentry.loggerevent.level); } }, interceptor: (_operation, _ctx, next) next(), async dispose() { process.off(SIGINT, flush); process.off(SIGTERM, flush); await Sentry.flush(2000); }, }); // 协调器重试或对账卡住的 submission——还不是终态结果且 deferred/ // agent_unavailable 会在每次重试唤醒时反复出现因此保留为 breadcrumb // 而非捕获 issue。唯一终态 terminated 总是与 submission_settled 的 // outcome:failed 事件同时出现上面的分支已捕获在此再记录会重复。 function recordRecoveryBreadcrumb(event: ExtractFlueObservation, { type: submission_recovery }): void { Sentry.addBreadcrumb({ category: flue.submission_recovery, level: event.outcome terminated ? error : warning, message: ${event.operation}: ${event.outcome}, data: { ...correlationTags(event), flue.recovery.operation: event.operation, flue.recovery.outcome: event.outcome, ...(event.attemptCount ! undefined ? { flue.recovery.attempt_count: event.attemptCount } : {}), ...(event.maxAttempts ! undefined ? { flue.recovery.max_attempts: event.maxAttempts } : {}), ...(event.errorInfo ? { error.type: event.errorInfo.type } : {}), }, }); } function captureTerminalFailure( error: unknown, tags: Recordstring, string, context?: Recordstring, unknown, ): void { Sentry.withScope((scope) { scope.setTags(tags); scope.setLevel(error); if (context) scope.setContext(flue.incident, context); Sentry.captureException(toError(error)); }); } // 标签键使用 flue.* 前缀——与 trace span 携带的键同名——因此在 Sentry // 搜索中以 flue.instance.id 为支点能一次找到单个 agent 实例的全部 issue、 // 日志和 span。 function correlationTags(event: FlueObservation): Recordstring, string { const tags: Recordstring, string {}; if (event.instanceId) tags[flue.instance.id] event.instanceId; if (event.agentName) tags[flue.agent.name] event.agentName; if (event.conversationId) tags[flue.conversation.id] event.conversationId; if (event.submissionId) tags[flue.submission.id] event.submissionId; if (event.harness) tags[flue.harness] event.harness; if (event.session) tags[flue.session] event.session; if (event.parentSession) tags[flue.parent_session] event.parentSession; if (event.operationId) tags[flue.operation.id] event.operationId; if (event.taskId) tags[flue.task.id] event.taskId; return tags; } type LogAttribute string | number | boolean; function logAttributes(event: ExtractFlueObservation, { type: log }): Recordstring, LogAttribute { const attributes: Recordstring, LogAttribute {}; for (const [key, value] of Object.entries(correlationTags(event))) attributes[key] value; for (const [key, value] of Object.entries(event.attributes ?? {})) { const scrubbed scrub(value); attributes[flue.log.${key}] typeof scrubbed string || typeof scrubbed number || typeof scrubbed boolean ? scrubbed : stringify(scrubbed); } return attributes; } // trace span 的内容策略。两个 record 开关都关时模型/工具内容完全不达 // Sentrycontent: false。任一开关开启时transform 只放行被启用方向 // 脱敏敏感键并把适配器默认的 56 KiB 预算收紧为每属性 16 KiB。 function contentPolicy(): ContentOption { if (!recordInputs !recordOutputs) return false; return { transform(content, scope) { if (isInputContent(scope.contentType) !recordInputs) return undefined; if (isOutputContent(scope.contentType) !recordOutputs) return undefined; return truncateContent(scrub(content), { maxBytes: 16_384 }); }, }; } function isInputContent(contentType: GenAIContentType): boolean { return ( contentType input_messages || contentType system_instructions || contentType tool_definitions || contentType tool_description || contentType tool_arguments ); } function isOutputContent(contentType: GenAIContentType): boolean { return ( contentType output_messages || contentType tool_result || contentType exception_message || contentType exception_stacktrace ); } const SENSITIVE_KEY /api[-_]?key|authorization|cookie|dsn|password|secret|token/i; function scrub(value: unknown, seen new WeakSetobject(), depth 0): unknown { if (depth 8) return [truncated]; if (value instanceof Error) return { name: value.name, message: value.message }; if (value null || typeof value ! object) return value; if (seen.has(value)) return [circular]; seen.add(value); if (Array.isArray(value)) return value.map((item) scrub(item, seen, depth 1)); return Object.fromEntries( Object.entries(value).map(([key, nested]) [ key, SENSITIVE_KEY.test(key) ? [redacted] : scrub(nested, seen, depth 1), ]), ); } function toError(value: unknown): Error { if (value instanceof Error) return value; if (value typeof value object) { const source value as { name?: unknown; message?: unknown; stack?: unknown }; const error new Error(typeof source.message string ? source.message : stringify(value)); if (typeof source.name string) error.name source.name; if (typeof source.stack string) error.stack source.stack; return error; } return new Error(typeof value string ? value : stringify(value)); } function stringify(value: unknown): string { try { return JSON.stringify(value) ?? String(value); } catch { return String(value); } } function clampRate(value: string | undefined, fallback: number): number { if (value undefined) return fallback; const parsed Number(value); return Number.isFinite(parsed) parsed 0 parsed 1 ? parsed : fallback; }关于模块级初始化的边界说明上述模块级初始化仅支撑本 blueprint 中的捕获。Sentry 完整的 Node 自动插桩HTTP、数据库驱动要求其preload hook 在应用导入之前运行如果用户需要完整自动插桩必须把生产 Node 命令配置为 Sentry 当前推荐的 preload并针对构建后的 Flue 服务器验证。不要声称仅凭后期加载的sentry.ts初始化就拥有了完整自动插桩。底层原理instrument()与observe()如何工作这段桥接代码依赖 packages/runtime/src/instrumentation.ts 中的instrument()它把observe订阅到运行时事件总线、把interceptor注册为执行拦截器并返回一个幂等的 dispose 函数。带key的注册在开发模式重载时遵循“最新安装者胜出、旧实例被 dispose”的语义InstrumentationAlreadyInstalledError只在非 dev 模式的重复安装时抛出这正是Symbol.for(flue.sentry.bridge)防止事件重复上报的机制来源。而observe()回调由 packages/runtime/src/runtime/events.ts 的订阅表驱动收到的FlueObservation对象见 packages/runtime/src/observation.ts是深度克隆并冻结的快照可安全地在回调内读取。底层原理内容预算与 span 语义contentPolicy()中的truncateContent与 16 KiB 阈值对应 packages/runtime/src/telemetry/content.ts 定义的ContentOption与CONTENT_BUDGET_BYTES 57_344约 56 KiB每个 span 的内容属性共享同一个内容池workerd 对 span 的总属性体积设有硬上限预留的 8 KiB 余量用于保障 usage、ID、错误类别等运维属性必然落地。集成把单属性预算收紧到 16 KiB是比适配器默认更保守的出站策略。GenAI 语义约定span 命名、operationName、token 用量属性等的实现位于 packages/opentelemetry/src/index.ts其turn_request→chat model、tool_start→execute_tool tool、task_start/operation_start→invoke_agent AgentName的 span 命名正是 blueprint 验证清单第 3 步要确认的层级结构。第五步创建 Flue 集成Cloudflare 版Cloudflare 变体只替换captureTerminalFailure辅助函数以上的部分辅助函数captureTerminalFailure到clampRate与 Node 文件完全一致。完整实现如下// flue-blueprint: tooling/sentry1 import { type ContentOption, createOpenTelemetryInstrumentation, type GenAIContentType, truncateContent, } from flue/opentelemetry; import { type FlueObservation, instrument } from flue/runtime; import { extend } from flue/runtime/cloudflare; import * as Sentry from sentry/cloudflare; interface Env { SENTRY_DSN?: string; SENTRY_ENVIRONMENT?: string; SENTRY_RELEASE?: string; SENTRY_TRACES_SAMPLE_RATE?: string; } const recordInputs process.env.SENTRY_AI_RECORD_INPUTS true; const recordOutputs process.env.SENTRY_AI_RECORD_OUTPUTS true; // 每个 isolate 的 env 绑定只在下面的 DO 包装器内部可用模块作用域的 // instrument(...) 门读取 process.env。两条路径都走 clampRate // 使无效采样率在两端都归零。 const tracesSampleRate clampRate(process.env.SENTRY_TRACES_SAMPLE_RATE, 0); const SENTRY_AI_PROVIDER_INTEGRATIONS new Set([ Anthropic_AI, OpenAI, Google_GenAI, LangChain, LangGraph, VercelAI, ]); export const cloudflare extend({ wrap: (Final) Sentry.instrumentDurableObjectWithSentry( (env: Env) ({ dsn: env.SENTRY_DSN, enabled: Boolean(env.SENTRY_DSN), environment: env.SENTRY_ENVIRONMENT, release: env.SENTRY_RELEASE, tracesSampleRate: clampRate(env.SENTRY_TRACES_SAMPLE_RATE, 0), traceLifecycle: stream, streamGenAiSpans: true, enableLogs: true, integrations: (defaults) defaults.filter((integration) !SENTRY_AI_PROVIDER_INTEGRATIONS.has(integration.name)), }), Final, ), }); if (tracesSampleRate 0) { instrument(createOpenTelemetryInstrumentation({ content: contentPolicy() })); } // 参见 Node 变体了解失败 submission 为何需要这本账。 const capturedFailedSubmissions new Setstring(); instrument({ // 带 key 的注册生产 isolate 本模块只求值一次dev 重载时最新安装胜出、 // 旧实例被 dispose。 key: Symbol.for(flue.sentry.bridge), observe(event) { if (event.type operation event.isError) { captureTerminalFailure(event.errorInfo ?? event.error, correlationTags(event), { durationMs: event.durationMs, operationKind: event.operationKind, }); if (event.submissionId) capturedFailedSubmissions.add(event.submissionId); return; } if (event.type submission_settled) { const alreadyCaptured capturedFailedSubmissions.delete(event.submissionId); if (event.outcome failed !alreadyCaptured) { captureTerminalFailure(event.errorInfo ?? event.error, correlationTags(event)); } return; } if (event.type submission_recovery) { recordRecoveryBreadcrumb(event); return; } if (event.type log) { Sentry.loggerevent.level); } }, interceptor: (_operation, _ctx, next) next(), async dispose() { await Sentry.flush(2000); }, }); // 参见 Node 变体了解为何它保持为 breadcrumb 而非 issue。 function recordRecoveryBreadcrumb(event: ExtractFlueObservation, { type: submission_recovery }): void { Sentry.addBreadcrumb({ category: flue.submission_recovery, level: event.outcome terminated ? error : warning, message: ${event.operation}: ${event.outcome}, data: { ...correlationTags(event), flue.recovery.operation: event.operation, flue.recovery.outcome: event.outcome, ...(event.attemptCount ! undefined ? { flue.recovery.attempt_count: event.attemptCount } : {}), ...(event.maxAttempts ! undefined ? { flue.recovery.max_attempts: event.maxAttempts } : {}), ...(event.errorInfo ? { error.type: event.errorInfo.type } : {}), }, }); }在 Cloudflare 上绝不要调用Sentry.init()Durable Object 包装器在每个 isolate 内初始化 SDK桥接中的Sentry.logger/Sentry.captureException会解析到该 isolate 自己的 client。Cloudflare 上没有信号监听器——最终 flush 依赖平台事件生命周期属于尽力而为。为什么 Cloudflare 必须走extend({ wrap })每个 agent 在 Cloudflare 上运行于自己的 Durable Object 中即独立的 V8 isolate与外层 Worker 及其他 agent 相互隔离。模块图从而sentry.ts在每个 isolate 中各求值一次因此桥接与仪器化在每个 isolate 内独立运行、各自上报。SDK 则通过 Durable Object 类初始化这正是extend({ wrap })的意义Flue 在生成最终 Durable Object 类时调用扩展的wrapSentry.instrumentDurableObjectWithSentry返回的是保留原型的构造器代理——恰好满足wrap的要求。实现位于 packages/runtime/src/cloudflare/extension.tsextend()只接受含可选base与wrap回调的对象resolveCloudflareExtension会把模块的cloudflare具名导出解析为对最终类的包装。第六步接线应用app.ts与 Cloudflare Durable Objects应用入口接线在源码根目录的app.ts中导入一次集成import ./sentry.ts;保留应用现有的 imports、中间件、路由与默认导出。如果项目没有app.ts则新建一个导入./sentry.ts、创建 Hono 应用、用app.route(/agents/name, createAgentRouter(AgentFn))来自flue/runtime/routing挂载每个 HTTP 可达的 agent并默认导出 app。编写该文件时需要安装直接的hono依赖。observe(...)与instrument(...)是isolate 局部的会收到当前 isolate 处理的所有 agent 的每个事件。捕获通过 agent 实例、agent 名、会话、操作与 submission 字段关联在 Sentry 搜索中以flue.instance.id为支点可找到单次会话的全部 issue、日志与 spanflue.submission.id可精确定位某次 submission。捕获纪律务必遵守防止重复与噪音只把上文“集成目标”中的终态信号捕获为 Issue。submission_recovery是唯一例外作为 breadcrumb 而非 issue大多数 outcome 在每次重试时反复出现唯一终态terminated会与配对的submission_settledissue 重复。不要捕获更低层级的失败 tool / task / turn / compaction 事件——它们可能是可恢复的且会与选定的终态信号重复。不要把log.error提升为 issue——error 级日志会带着属性与 trace 关联进入 Sentry Logs。不要在内容策略之外转发 prompt、模型输出、工具参数或任意事件负载。Cloudflare为每个 agent 模块导出扩展Node 端跳过本节。在 Cloudflare 上每个 agent 模块若希望其 agent 向 Sentry 上报需从agents/下的每个相关模块重新导出cloudflare扩展export { cloudflare } from ../sentry.ts;Flue 会把该扩展的wrap应用到该模块导出的每个 agent 的最终生成 Durable Object 类上。注意包装器覆盖的是agent Durable Object不覆盖外层 Worker 或自建的 Hono 应用——如果用户还需要app.ts的 HTTP 请求插桩需另行研究并添加 Sentry 当前的 Workers 或 Hono 中间件。配置SENTRY_DSN时按项目策略使用 Worker secret 或环境绑定本地 Wrangler 开发遵循已有的.dev.vars或.env约定。DSN 必须留在应用源码之外以便无需改代码即可轮换或禁用。Environment、release 与采样率值可用 Wranglervars。第七步验证清单10 项完成集成后按以下顺序验证更新既有集成时需对照本完整 blueprint 逐项检查、应用所有相关变更并保留自定义缺失 marker 时必须执行此对比类型检查与构建Type-check 项目并用vite build按配置的 Flue 目标构建。真实运行时启动用非生产 Sentry 项目与SENTRY_TRACES_SAMPLE_RATE1启动真实目标运行时。trace 结构验证向使用工具的 agent 发一条消息确认存在一条 trace包含invoke_agent AgentName及其chat model、execute_tool tool子 span、chatspan 上的 token 用量且两个 record 开关关闭时无任何模型/工具内容。日志路由验证从工具内调用log.info(...)与log.error(...)确认各自按级别进入 Sentry Logs、带flue.instance.id标签、与其会话在同一 trace 上——且两者都不产生 issue。终态失败验证向一个终态失败的 agent 发消息确认恰好产生一个携带原始错误名、消息与 throw-site 堆栈、带flue.instance.id与flue.submission.id标签的 Sentry issue——而不是两个settlement 重复必须被跳过。内容策略验证将两个 record 开关设为true重复第 3 步确认消息与工具内容出现且敏感键被脱敏。Cloudflare isolate 验证在 workerd 下至少演练一个被包装的 agent Durable Object确认该 isolate 投递了 span、日志与一个 issue。无 DSN 退化验证移除 DSN 后确认应用仍能启动且所有 Sentry 调用均为 no-op。dev 热重载验证在vite dev下编辑sentry.ts强制重载重复第 4 步确认事件恰好上报一次。恢复面包屑验证强制一次协调器恢复条件例如让某个 agent Durable Object 对被派发的 submission 短暂不可用确认出现携带flue.recovery.operation与flue.recovery.outcome的flue.submission_recoverybreadcrumb、附着在该实例的下一次捕获事件上且没有为其产生单独 issue。附升级与维护本 blueprint 的通用型基线见 blueprints/tooling.mdflue add tooling url/flue update tooling url的入口文档其中强调了与本文一致的集成要点observe(...)必须在模块作用域注册一次、回调同步且廉价、不得抛错遥测被视为导出边界只收集集成所需数据并脱敏敏感字段。Sentry 专属 blueprint 的版本历史如下Version 1 — 2026-06-15初始版本。更新既有集成时sentry.ts中的// flue-blueprint: tooling/sentry1标记即版本指纹对比发现版本落后时应用所有相关变更并保留项目自定义再更新标记。这样每次升级都能在保留业务定制的前提下与官方 blueprint 保持同步。【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询