dsh-observe
OpenTelemetry and Langfuse observability exporter for DeepSeek Harness: turn/step/tool/LLM spans, token and cost metrics, and sanitized LLM prompt/completion capture from the session/event stream, with async batching, bounded offline buffering, and retry with backoff.
安装
$
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:PerryLink/dsh-observe说明文档
阅读完整 README ↗Configuration
All tunables are Schemastery Config fields (changeable from cordis.yml). An id-targeted override replaces the whole row — restate every key you need. cordis.patch.yml documents each key inline.
| Key | Default | Meaning |
|---|---|---|
enabled | false | Master switch; true plus at least one backend is the explicit opt-in |
otlp | null | OTLP backend config, or null to disable it |
otlp.endpoint | (required) | OTLP base URL; /v1/traces and /v1/metrics are appended |
otlp.serviceName | deepseek-harness | service.name resource attribute |
otlp.serviceVersion | (none) | service.version resource attribute |
otlp.headers | {} | Extra headers merged into every export request |
otlp.timeoutMs | 10000 | Per-request timeout |
langfuse | null | Langfuse backend config, or null to disable it |
langfuse.baseUrl | https://cloud.langfuse.com | Langfuse base URL |
langfuse.publicKey | (required) | Project public key |
langfuse.secretKey | (required) | Project secret key |
langfuse.release | (none) | Release tag stamped onto traces |
langfuse.traceName | session {session} turn {turn} | Trace-name template; {session}/{turn} interpolate per trace |
langfuse.tags | [] | Static tags stamped onto every trace |
langfuse.timeoutMs | 10000 | Per-request timeout |
capture.turns | true | Turn lifecycle spans |
capture.steps | true | Step lifecycle spans |
capture.tools | true | Tool-call spans with sanitized arguments/results |
capture.llm | true | LLM generation spans |
llm.prompt | true | Capture the sanitized request prompt (false = sizes only) |
llm.completion | true | Capture the sanitized completion (false = sizes only) |
metadata.sessionId | true | Session id attribute |
metadata.cwd | false | Session working directory (a local path — off by default) |
metadata.agentPreset | true | Agent preset id attribute |
metadata.model | true | Provider/model attributes |
metrics.tokens | true | Per-provider/model token counters |
metrics.cost | true | USD cost counters (need pricing rules to match) |
metrics.contextTokens | true | Context-pressure gauge (needs ctx.tokenMeter) |
pricing | [] | Pricing table, first match wins: { provider?, model, inputPerToken, outputPerToken, cacheReadPerToken?, cacheWritePerToken? } |
sanitize.enabled | true | Redaction master switch (false disables redaction, never truncation) |
sanitize.redactKeys | [] | Extra key-name substrings (key/token/secret/password/authorization/credential/apiKey are always included) |
sanitize.redactPatterns | [] | Extra secret regular expressions |
sanitize.truncatePromptChars | 4000 | Prompt character budget |
sanitize.truncateCompletionChars | 4000 | Completion character budget |
sanitize.truncateToolInputChars | 2000 | Tool argument character budget |
sanitize.truncateToolOutputChars | 2000 | Tool result character budget |
sanitize.truncateAttributeChars | 512 | Span attribute string budget |
batch.maxRecords | 256 | Flush once the queue holds this many records |
batch.flushIntervalMs | 5000 | Timer flush interval |
batch.maxQueueRecords | 2000 | In-memory queue bound; excess spills to the buffer |
batch.maxBufferRecords | 10000 | Durable offline buffer bound; oldest records drop first |
batch.bufferRetryIntervalMs | 30000 | Offline buffer retry interval |
retry.maxAttempts | 5 | Attempts per batch, including the first try |
retry.baseDelayMs | 1000 | First backoff delay |
retry.factor | 2 | Backoff multiplier per consecutive failure |
retry.maxDelayMs | 60000 | Backoff ceiling |
remote.enabled | false | Mount the observe Typert remote (kill switch) |