npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-plugin-langfuse

v0.7.0

Published

Langfuse observability for DeepSeek Harness: OpenTelemetry traces, compaction, feedback Scores, and fork lineage

Downloads

1,233

Readme

dsh-plugin-langfuse

English | 中文

为 DeepSeek Harness(dsh)提供 Langfuse 可观测性:把每个 turn 导出为 OpenTelemetry trace(模型 step → generation、工具调用 → tool span),按 session 分组,把 canonical feedback 记录成 Langfuse Score,并保留 fork/subagent 血缘。

这是一个社区插件(dsh-plugin topic),不属于官方仓库。它实现 harness 的公开 telemetry seam(@deepseek-ai/dsh-session-telemetry),作为官方 OTLP-logs 导出器之外的另一个后端。

安装

以下命令假设已安装 dsh CLI。如果你是从官方仓库源码 checkout 运行 harness,把每条命令改为在 checkout 根目录执行 pnpm dsh …(先跑它的 pnpm run build)—— 命令相同,profile 也是同一个 web。

作为 profile bundle 安装(包内附带 cordis.patch.yml patch 层):

dsh plugin --profile web add dsh-plugin-langfuse
export LANGFUSE_PUBLIC_KEY=pk-lf-…
export LANGFUSE_SECRET_KEY=sk-lf-…
# 可选,默认 https://cloud.langfuse.com(EU 区);注意插件读取的是
# LANGFUSE_HOST,而不是 Langfuse SDK 惯用的 LANGFUSE_BASE_URL
export LANGFUSE_HOST=https://us.cloud.langfuse.com
dsh web                        # 即 dsh --profile web 的别名

附带的 patch 会禁用 base profile 的 session-telemetry-otel 行(telemetry seam 每个 context 只接受一个后端;重复加载会抛错),并在存在 Langfuse key 时以 FULL 模式挂载本后端,否则为 DISABLED。两个项目密钥都存在时,它还会启用 feedback Score。设置 LANGFUSE_TELEMETRY_MODE=FEEDBACK_ONLY 可将共享收窄为反馈门控释放。

bundle 层和环境变量都在启动时读取:安装后必须重启已在运行的实例,且启动 shell 里要带上这些变量。dsh --profile web --dump-config 可以不启动就查看组合结果 —— 应出现一个 # == dsh-plugin-langfuse 层,它 patch 掉 base 的 telemetry 行并新增 env 驱动模式的 session-telemetry-langfuse 行。跑完下一轮对话后,trace 出现在 LANGFUSE_HOST 所指区域的 Langfuse 控制台 —— 密钥按区域隔离,US 区项目在 EU 控制台上什么都看不到。dsh plugin --profile web remove dsh-plugin-langfuse 会同时移除依赖和对应的层。

也可以作为显式 cordis.yml 行挂载:

- id: session-telemetry-langfuse
  name: dsh-plugin-langfuse
  config:
    mode: FULL                 # FULL | FEEDBACK_ONLY | DISABLED(默认)
    exporter:                  # 原样透传给 SDK 的 OTLP/HTTP trace exporter
      url: https://cloud.langfuse.com/api/public/otel/v1/traces
    auth:
      publicKey: !!js process.env.LANGFUSE_PUBLIC_KEY
      secretKey: !!js process.env.LANGFUSE_SECRET_KEY
    feedbackScores:             # 可选;显式配置时默认关闭
      enabled: true
      url: https://cloud.langfuse.com/api/public/scores
      maxQueueSize: 256
      requestTimeoutMillis: 3000
    content:                    # 可选:隐私/内容控制
      turnInputMode: user       # none | user | user-and-context
      cwdMode: omit             # omit | basename | full
      toolMetaAllowlist: []     # 精确匹配 tool-result meta 顶层键
    metadata:                   # 可选:Langfuse 静态分组
      environment: production
      tags: [dsh]
    health:
      warningIntervalMillis: 60000
      maxErrorChars: 1000
    processor: {}              # 可选;原样透传给 BatchSpanProcessor
    shutdownTimeoutMillis: 3000

配置

| 字段 | 含义 | |---|---| | mode | FULL 实时导出每个会话;FEEDBACK_ONLY 仅在用户记录反馈时重放并导出 canonical 会话日志;DISABLED(默认)不构造任何东西,没有数据离开进程。FULL 是本插件的持续追踪模式;Harness 0.1.7 官方 OTLP-logs 后端仅提供 FEEDBACK_ONLY 和 DISABLED。 | | exporter | 完整的 OTLPExporterNodeConfigBase 对象,传给 OTLP/HTTP trace exporter。非 DISABLED 模式下 url 必填,且必须是完整的 traces 路径(…/api/public/otel/v1/traces)。插件默认附带 x-langfuse-ingestion-version: 4 请求头——缺少它新 span 不会实时进入 Langfuse 的 v4 数据模型。无论显式条目来自普通 exporter.headers 对象还是 HeadersFactory 的返回值,均按任意大小写识别并优先采用。 | | auth | Langfuse 项目密钥对,转换为端点的 Basic-auth 请求头。与显式的 exporter.headers authorization 互斥;上传模式要求两者恰好提供其一。 | | correlation | 宿主身份关联:userId/sessionId 以 langfuse.user.id/langfuse.session.id 盖在每个导出 span 上,让嵌入方宿主的 trace 和本插件的 trace 归入同一个 Langfuse user/session。见与嵌入宿主关联。 | | feedbackScores | 可选:把 feedback/record 导出为会话级 TEXT Score,把 feedback/message-put 导出为 CATEGORICAL 消息评价,并同步修改与删除。enabled 默认 false;url 必须是完整的 …/api/public/scores 路径。maxQueueSize 默认 256,requestTimeoutMillis 默认 3000。有界内存队列与 trace 故障隔离,并在 shutdown 时 best-effort 排空。bundle profile 在两个项目密钥都存在时启用它。 | | content | 导出内容策略。turnInputMode 默认 user(只聚合真人消息);user-and-context 还包括插件注入上下文,none 省略根 input。cwdMode 默认 omit;basename 只导出末级目录,full 导出完整路径。toolMetaAllowlist 默认为空,只允许明确列出的 tool/result.meta 顶层键。 | | metadata | 可选的 Langfuse 静态 environment 与 tags,传播到每个 observation 以支持 v4 查询。environment 遵循 Langfuse 的小写 a-z0-9-_ 格式、不能以 langfuse 开头且最长 40 字符;最多 50 个 tag,每个最长 200 字符。 | | health | 投递诊断。warningIntervalMillis 默认 60000,用于持续失败告警限频(0 表示首次告警后不再重复);maxErrorChars 默认 1000,作用于凭据/URL 清洗后的错误文本。这些设置不会增加重试或改变 SDK 缓冲语义。 | | processor | 原样透传给 BatchSpanProcessor(scheduledDelayMillis、maxQueueSize、maxExportBatchSize 等);批处理、重试、丢失策略均为 SDK 的文档化行为。 | | maxAttributeChars | 每个 span 属性的序列化 payload 上限(默认 32768);超长部分以 …[clipped] 标记裁剪,canonical 会话日志保留完整字节。 | | shutdownTimeoutMillis | 插件持有的 SDK shutdown 排水外层截止时间(默认 3000)。 |

错误配置在插件加载时即失败:exporter URL 缺失/畸形/非 http(s)、凭据缺失、双重鉴权歧义、非正的 maxExportBatchSize(SDK 会在 shutdown 时挂死)、非法的 correlation/content/metadata/health、启用 Score 却没有合法 URL/队列/超时、未知 mode,全部在构造任何传输之前抛出。

投递状态

LangfuseSessionTelemetryBackend.status() 同步返回脱离内部状态的快照:整体与分通道状态、trace 批次/span 成功失败数、连续失败数、最近时间、已清洗的最近错误,以及 Score 的 queued/delivered/dropped/skipped/failed 计数。状态包括 disabled、starting、healthy、degraded、stopped;Score 始终是独立通道。OTel SDK 不公开 BatchSpanProcessor 队列深度,因此 traces.queuedBySdk 明确为 unknown。首次失败、限频后的持续失败和恢复也会写日志,且不会带 Authorization 或 Langfuse key。

在标准 Harness 交互 profile 中,可以直接在对话界面查看同一份快照:

/langfuse status
/langfuse status --json

第一种格式适合人工阅读;--json 返回稳定 envelope,包含插件版本、会话分享策略和完整的 status() 快照。该命令只读本地状态:不会访问 Langfuse、重试投递、检查凭据或强制执行 SDK flush。只要 profile 组合了 Harness commands 服务(包括标准 web profile)就会注册该命令;未组合这一可选服务的纯 telemetry/headless context 仍能正常加载后端,只是不注册命令。starting 表示尚无 trace 导出批次完成,并不表示命令正在探测 endpoint。即使处于 DISABLED 模式,只要命令服务存在也会注册,因此可以用 /langfuse status 确认当前没有分享任何数据。

与嵌入宿主关联

把 dsh 运行时嵌入自身、且已向同一 Langfuse 项目发送自有 trace 的宿主应用,可以操控本插件的身份标识,让两套视图归入同一个 Langfuse user/session——宿主通常在 spawn 运行时进程时以环境变量注入自己的 id:

config:
  correlation:
    userId: !!js process.env.HOST_USER_ID
    sessionId: !!js process.env.HOST_SESSION_ID
  • 解析出的 langfuse.session.id/langfuse.user.id 会盖在每个导出 span 上——turn、generation、tool、compaction——因为 Langfuse v4 的查询模型按 observation 而非仅按 trace 过滤与聚合(属性传播合约)。
  • sessionId 默认取 dsh session id;原始 dsh session id 始终以 dsh.session.id 留在每个逻辑根上——这是回查 $DSH_HOME/sessions 本地日志的指针。
  • 按轮动态覆盖:turn/start record 上携带的 langfuse.user.id/langfuse.session.id 属性覆盖该轮的静态配置——部署方通过 session-telemetry/record waterfall listener 注入。快照在 turn/start 时锁定;之后 record 上的身份属性一律忽略。优先级:record 属性 > correlation 配置 > dsh session id。
  • 动态映射必须可从 dsh session id 确定性重建,且至少存活到该会话不再可能触发 FEEDBACK_ONLY 重放为止——否则重放出的树会带上与实时捕获不同的身份。
  • 静态 correlation 值不经过脱敏 waterfall:waterfall 只变换 record,而这些值从不途经 record。
  • 投递语义不变:correlation 是身份而非去重——重复仍然可能(见决策 5)。

Langfuse 中会看到什么

| dsh 会话事件 | Langfuse 概念 | |---|---| | session(session.id) | session(每个导出的 observation/span 都带 langfuse.session.id) | | turn/start / turn/end | trace 根 observation(root span;错误结束原因置 span 状态为 ERROR) | | step/start / step/end + request/header + request/context + assistant/message | generation —— 模型、provider、安全的请求参数/context window、输出、规范的 gen_ai.usage.* token(input/output/cache-read/cache-creation/reasoning);最新一条 assistant message 同时成为根 observation 的整体输出。中断输出会保留部分内容,并在两层 observation 标记 dsh.assistant.interrupted=true,但不会把中断误判为错误 | | llm/retry / llm/retry-started | 现有 generation 上的结构化 scheduled/started event,包含 retry id/attempt/policy/delay 与裁剪后的失败详情;当前 Harness 事件合约没有逐 attempt usage 生命周期,因此不伪造 generation | | assistant/message.stream | 从已提交消息的原始流时间提取首 token,写入 langfuse.observation.completion_start_time | | assistant/attempt | 在 step generation 上记录有限的 attempt 元数据;已知 usage 仅累计一次,并保留最终结果 | | system/message / developer/message | 仅记录事件类型和序号,不导出系统提示词、developer 正文或工具定义 | | tool/call + tool/result | tool span(参数为 input,完整 result content 数组为 output,结构化 error name/code/outcome,isError → 状态 ERROR;私有 meta 除非 allowlist 明确允许,否则省略) | | approval/asked + approval/decided | 可计时的 approval 内部 span;callId 能解析时挂在对应 tool 下,否则挂在当前 generation/turn;未闭合审批强制以 ERROR 收尾 | | user/message | 按 content.turnInputMode 聚合为根 observation input;同时保留已弃用的 trace input,以兼容旧版 evaluator | | session/title / subagent/descriptor / agent-preset/selected | 会话语义状态,用于当前/后续 observation 的 langfuse.trace.name 与安全浏览 metadata,不改变稳定 span name 或 ID | | session/end-seed | 在 seed 边界关闭继承但缺少配对 end 的 compaction,并标记 incomplete/ERROR | | feedback/record | 把 waterfall 后的文本写为会话级 dsh_user_feedback TEXT Score,保留分类;无文本反馈仍可触发回放,但不生成 TEXT Score | | feedback/message-put / feedback/message-delete | 按所属 session 和 message 派生稳定 ID,导出会话级 dsh_message_feedback CATEGORICAL 评价(positive / negative),修改与删除沿用相同 ID | | fork child session | 独立的 child turn trace,并带可查询的 parent/seed metadata;进程内仍保留父 turn context 时附加指向它的 OTel Link | | agent-error ops 记录 | 开放 turn 上的 agent-error span event + 状态 ERROR | | compaction/start + compaction/summary + compaction/end | 一个覆盖完整压缩事务的 generation;能找到所属 turn 时作为其子节点,否则成为稳定的独立 trace;包含 provider/model/usage 与被遮蔽范围、事件数、token 数统计 | | compaction/prune | 带裁剪范围、事件数和 token 数统计的时间点 span event | | 其他所有事件类型(todo、plan、hooks、插件事件) | 开放 turn 上的时间点 span event |

Token 计量遵循 OpenTelemetry GenAI 的 inclusive-total 契约。DSH 报告的是互斥输入 buckets(inputTokens 仅包含未缓存输入),因此导出的 gen_ai.usage.input_tokens 会重建为 inputTokens + cacheReadTokens + cacheWriteTokens;cache read/write 与 reasoning 继续作为规范的明细属性。Langfuse 随后只需执行一次归一化,即可得到互斥 usage buckets。

架构决策

1. 实现 telemetry seam 后端,而非在 agent-loop 或 LLM 层埋点

Harness 的规则是 model-visible ⟺ logged:所有进入模型请求的内容都可以从 canonical 会话日志重建,且新行为以插件形式落在文档化扩展点上,绝不改 agent-loop。telemetry seam(@deepseek-ai/dsh-session-telemetry)正是为"把会话记录交给上报 SDK"而建的扩展点。实现它的 SessionTelemetryBackend,免费且保证一致地获得:

  • 捕获所有模型可见内容 —— 包括本包从未听说过的 subagent、workflow、compaction 和插件事件;这里指采集完整性,不代表导出每个 body 字段;
  • session-telemetry/record 脱敏 waterfall(部署自装的清洗规则作用于导出副本;canonical 日志永不改写);
  • FEEDBACK_ONLY 同意语义(用户记录反馈前不出境任何数据,且只有已提交的 canonical 事件才算同意);
  • handoff cursor、adoption 扫描和 teardown 排水。

直接在 LLM adapter 或 agent loop 埋点意味着重复实现上述全部、与日志漂移,并在 loop 变更时立即破裂。

2. 用原生 OTel traces SDK,而非 Langfuse SDK —— 根源是信号类型不匹配

官方 session-telemetry-otel 后端喂不了 Langfuse:它导出 OTLP logs,而 Langfuse 的 OTLP 端点(/api/public/otel)只接受 traces,走 OTLP/HTTP(JSON 或 protobuf;不支持 gRPC),Basic 鉴权。这个不匹配——而不是缺一个 URL——正是本插件存在的原因。

Trace 管线使用原生 OTel traces SDK(BasicTracerProvider → BatchSpanProcessor → OTLPTraceExporter),与官方后端同一 SDK 家族、同一配置面;属性遵循 OTel GenAI 语义约定加 Langfuse 文档化的 langfuse.* 属性映射。Feedback Score 使用小型原生 HTTP transport,而不再初始化第二套 tracing SDK,因此可以复用异步/自定义鉴权合约,并隔离 trace 与 Score 的故障。将来可在不改变 telemetry seam 或公开配置的前提下,把该内部 transport 换成 Langfuse SDK。

3. 折叠投影 —— 因为 seam 交来扁平流,而 Langfuse 需要树

seam 的记录与会话日志事件一一对应;Langfuse 需要 trace → observation 层级。SessionSpanFolder 是按 (session.id, turn, step, compactionId) 键控的状态机,把记录折叠进开放的 OTel span。契约关键的选择:

  • 时间戳取 canonical 记录或内嵌 stream 的原始时间,绝不取回放时的墙钟,因此实时捕获与 FEEDBACK_ONLY 的 canonical 日志重放产出完全相同的树(span 起止时间显式指定 —— OTel API 支持历史时间戳)。
  • V4 capture 与 canonical 事件一一对应。原始 delta 保存在 assistant/message.stream 或 assistant/attempt.stream 中;插件读取时间、结束状态和 usage,不导出完整原始流。completion_start_time 对应已提交消息,失败 attempt 的首 token 时间单独保留为事件元数据。空或被脱敏的流不补造时间。
  • 边界及工具事件使用 seam 映射的 severity。模型 attempt 的结果从内嵌 finish 记录读取,只有最后一次结算决定 generation 是否失败,成功重试不会保留中间失败状态。
  • tool span 是其 step 的 generation span 的子节点:harness 定义 step 为一次模型请求加上它调用的工具 —— tool/call 与 tool/result 都落在 step 边界之内 —— 因此 generation span 在时间上包含它的工具执行。step 已不再开放的调用(崩溃窗口重放)回退挂到 turn span。
  • 整轮 input/output 按 Langfuse v4 合约放在根 observation 上:user/message 提供 input;每条完成的 assistant message 覆盖 output,因此 turn 结束时保留最后一条回复。已弃用的 langfuse.trace.input/output 别名仅用于兼容旧版 trace-level evaluator。
  • 未知事件类型落为开放 turn 上的 span event —— 事件词汇表是 merge-extensible 的,丢弃未知类型会悄悄稀释时间线。
  • Compaction 是从 compaction/start 到 compaction/end 的单个事务 Generation。它的时长有意包含 provider 调用外围的编排时间,不标成纯模型延迟。compaction/summary 用压缩摘要、provider/model/usage 与聚合后的 shadow 统计补全 span;provider rawOutput 和完整 shadowedSeqs 列表永不导出。与其配对的替换型 user/message(source.plugin=compact)仍是模型可见上下文,但不会覆盖 turn 的真人输入。所属 turn 缺失时生成稳定的独立 trace;生命周期记录缺失或畸形时降级为时间点事件或 ERROR span,不伪造时长。
  • 强制收尾扫描在三处关闭仍开放的 span(标记 dsh.force_ended):新 turn/start 到来而前一个 turn 未闭合、会话的 ops shutdown 记录、后端 shutdown —— teardown 绝不把已开始的 span 遗弃在 SDK 队列里。

4. 稳定身份、feedback Score 与 fork 血缘

  • 分别对 (dsh session id, turn) 与 (dsh session id, compaction id) 做带版本的 SHA-256 派生,使实时导出与 FEEDBACK_ONLY 重放得到稳定的 32 位十六进制 Trace ID。合法的 W3C traceparent 仍优先用于分布式追踪;确定性 ID 保留为可查询 metadata。
  • 会话反馈成为 dsh_user_feedback TEXT Score,消息评价成为携带 dshMessageId 元数据的 dsh_message_feedback CATEGORICAL Score。两者共用有界单 worker 队列;修改与删除排在先前重试之后。评价关联到解析出的 Langfuse session,不伪造 observation。备注截断至 500 字符,保留分类。
  • 每个 child turn 都带直接父 session、seed boundary 和可解析的父 Trace ID metadata。若有界进程内 registry 仍保存 fork 边界处父 turn 的根 SpanContext,child root 还会携带一个 OTel Link。父 context 缺失、淘汰或跨进程时降级为 metadata 和 dsh.lineage.linked=false,绝不伪造 context。

5. 投递语义:at-most-once handoff,可能重复

采集 cursor 标记的是已交接而非已送达;崩溃时留在 SDK 批处理队列里的数据会丢失;无 cursor 的重新收养(热重载)可能重发前缀,产生重复 span。接收端以 langfuse.session.id + dsh.turn + dsh.event.seq 关联。持久化 outbox 有意不做,与 seam 自身的立场一致。

6. 什么数据离开本机

上传模式下,span 属性携带用户与助手消息内容、工具参数与结果、compaction 摘要与聚合 shadow 统计、模型/用量元数据,以 session-telemetry/record waterfall 的返回值为准。Compaction provider rawOutput 和完整 shadowedSeqs 列表会被明确排除。本插件不带任何脱敏规则;导出跨越信任边界的部署需自行挂载 waterfall listener。插件不会主动添加 provider 凭据字段,但任意用户或工具正文仍需部署侧脱敏。序列化 payload 每属性按 maxAttributeChars 裁剪(默认 32768);canonical 日志保留完整字节。

Model Experience

无。本插件仅通过 telemetry seam 观察会话流并把折叠出的 span 交给 OTel SDK,从不向模型请求贡献任何内容。

KV Cache 影响

无。本插件既不组装也不发送 provider 请求。

测试

npm run typecheck && npm run typecheck:tests  # 源码及全部测试夹具类型检查
npm test                       # 单元:status 命令 + 折叠投影 + 配置 fail-loud 路径
npm run build && npm run test:e2e   # REAL composition:经 Loader 启动真实 dsh 应用
                               # (mock 模型 + 真实 bash 往返),断言 mock Langfuse
                               # collector 在 wire 上实际收到的 OTLP payload
npm run test:e2e:cloud         # opt-in 真实 Langfuse 往返;从 .env 加载凭据
npm run test:package           # npm pack + 空 consumer 安装/import + bundle 组合

e2e 沿用官方仓库的 REAL-composition 模式(@deepseek-ai/dsh-app-boot + @deepseek-ai/dsh-loader-smoke):fixture cordis.yml 加载构建产物 lib/index.js —— 与部署加载的是同一个文件 —— 断言针对 wire(包括 retry payload、approval/tool-error metadata、完整独立 compaction 与 seed-boundary orphan compaction),而非内部实现。

status 命令测试覆盖人工/JSON 格式、严格参数处理、Harness 命令注册、recordInput: false、disabled 模式诊断,以及缺少可选命令服务的 headless 组合。另一个本地 e2e 使用真实 OTLPTraceExporter 与 BatchSpanProcessor,依次触发 HTTP 503 和 200,并验证公开 backend status 从 degraded 恢复为 healthy;backend 单元测试还会独立锁定 observer 到 status() 的接线。

npm run test:e2e:cloud 会加载 gitignored 的 .env,并且只运行 opt-in 的 Langfuse Cloud 往返测试。先把 .env.example 复制为 .env,再填入 LANGFUSE_PUBLIC_KEY、LANGFUSE_SECRET_KEY 和匹配区域的 LANGFUSE_HOST。测试会通过 v4 Observations API 校验根 input/output、usage、逐 observation 关联、parent/child metadata、独立 compaction 的身份/摘要/usage、approval outcome 与结构化 tool error,并通过 Scores API 回读 feedback;它还会验证 retry 生命周期不会生成重复 generation。retry event payload 本身由本地 raw-OTLP wire 测试锁定,因为 Observations API 返回 observation,但不返回其内嵌的 OTel span event。通用的 npm run test:e2e 在这些变量已导出时仍会执行 Cloud case,否则自行跳过。设置 LANGFUSE_REQUIRE_TOTAL_COST=1 后,还会要求测试夹具中 deepseek-v4-flash step generation 的 totalCost 为有限正数;LANGFUSE_E2E_COST_MODEL 可选择采用相同价格的隔离测试别名。这是对测试项目 Langfuse 模型计价配置的 opt-in 验证,插件本身不会硬编码价格。

冷反馈 E2E 使用真实 JSONL persistence、message-feedback 服务及构建后的插件,验证 FULL 和 FEEDBACK_ONLY 下包含 V4 image-offload 的历史回放、HTTP 上的评价创建/修改/删除顺序、重复抑制和写句柄释放。单元回归额外覆盖空/仅分类反馈、恢复、中途 fork 及父 trace 链接、developer 正文省略、空及多块 V4 工具结果、伪造事件、waterfall 脱敏、失败/重试/取消 attempt 和包含格式版本的 Score 身份。Cloud E2E 还会读回 CATEGORICAL 消息评价的创建与修改,并验证相同稳定 Score ID 的删除。

版本兼容

DeepSeek Harness 处于 developer preview,无兼容承诺;本插件精确锁定 @deepseek-ai/dsh-* 版本。运行时使用的 DSH 包声明为 peer dependencies,使 Harness 可以在加载前拒绝不匹配的版本;开发依赖采用相同版本。

| dsh-plugin-langfuse | @deepseek-ai/dsh-* | |---|---| | 0.1.x | 0.1.0-rc.6 | | 0.2.x | 0.1.0-rc.6 | | 0.3.x | 0.1.0-rc.7 | | 0.4.x | 0.1.0-rc.8 | | 0.5.0 | 0.1.0-rc.8 | | 0.5.1 | 0.1.1-rc.1 | | 0.5.2 | 0.1.2-rc.1 | | 0.6.0 | 0.1.5-rc.1 | | 0.7.0 | 0.1.7-rc.1 |

独立的 Upstream compatibility canary 工作流会把所有 @deepseek-ai/* 依赖解析到最新发布版本。Pull request 与 main push 只做预警;每周定时和手动触发严格失败,并依次运行源码及测试类型检查、单测、构建、REAL-composition e2e 与 package smoke。失败运行会保留解析后的 manifest 和 lockfile,便于复现。

从 0.6.0 升级

插件 0.7.0 面向 @deepseek-ai/dsh-* 0.1.7-rc.1(Session V4)。通过 dsh plugin --profile web add [email protected] 更新 bundle,带上 Langfuse 环境变量重启 Harness,再检查 dsh --profile web --dump-config 和 /langfuse status --json。源码用户先构建匹配的 Harness 版本,再以 pnpm dsh … 执行相同命令。Harness 0.1.5-rc.1 应继续使用插件 0.6.0,Harness 0.1.2-rc.1 应继续使用插件 0.5.2。

Harness 负责迁移 Session V4 并保留旧代日志;旧版 runtime 无法读取升级后的日志。插件仅消费当前 canonical 事件,不改写存储日志。turn/compaction Trace ID 算法保持原样。会话 TEXT Score ID 包含来源格式版本,避免迁移重排 event seq 后发生身份冲突;以前导出的反馈在迁移回放后可能出现为另一个 Score。旧的双参数 createDshFeedbackScoreId API 保留原固定向量。

FEEDBACK_ONLY 回放恢复历史直到本次新的 canonical 反馈,包括仅分类或空的会话反馈,以及消息评价变更。冷会话评价通过 feedback/committed 处理:复制借用快照,使用宿主注册的消息投影(包括 image/offload)恢复后仅排队导出,不再次打开写句柄,也不等待网络。fork 继承事件和 V4 合成收尾事件不会被导出为新的子会话 turn 或 tool observations,继承或外部会话反馈也不能授权子会话采集。FULL 跟随实时事件并处理冷反馈,但不会自动补传所有恢复日志。DISABLED 不构建 exporter。

有界的 1,024 个 session/format 交接游标用于抑制后端单次生命周期内的重复冷快照。它不持久化,只代表已交接而非已送达;淘汰、重启、HMR 或迁移后可能重放数据。消息评价 upsert/delete 以所属 session/message 派生稳定 ID。不提供持久化 outbox 或跨进程 exactly-once 保证。

Generation 时长仍覆盖整个 step,包含重试等待与工具执行。失败 attempt 与已提交消息中的已知 usage 各累计一次;消息顶层 usage 优先于内嵌流中的副本。中间失败不会把后续成功的 generation 标成 ERROR;最终模型错误或无消息结算标为 ERROR,用户取消以 aborted 记录并保留部分输出,不把正常停止视为服务故障。系统提示词正文和原始流不进入导出属性;turnInputMode: none 仅省略根 input,不会关闭助手或工具输出。

已知限制与延后工作

  • 消息评价为会话级 Score,在元数据中保留原始 message id;尚未关联到单个 Langfuse observation,不伪造 observation id。
  • 不保证 UI 渲染 OTel Link:parent/seed metadata 是稳定且可通过 API 查询的 lineage 合约;Langfuse 未必把 Link 显示成可点击边。
  • 无持久化投递(决策 5):OTel batch 与 Score 队列都在内存中,进程崩溃可能丢失已接收但未 flush 的数据。
  • 每个 context 只能有一个后端:同时运行 Langfuse 和官方 OTLP-logs 后端需要上游 seam 演进出 multi-sink。
  • 辅助 LLM 调用尚未映射为 generation:title/search 请求事件缺少完整配对的 completion/failure/usage 生命周期,本插件不会伪造 observation。
  • 通用轮次外事件仍仅留在日志:title/preset/descriptor 会保留为语义状态,但插件不会创建长寿命 session trace,也不会为任意点事件逐条创建 trace。

许可证

MIT