tma1-ai/

DSH OTel

Telemetría de DeepSeek Harness, como OpenTelemetry puro.

Sin collector. Sin sidecar. Sin forkear DSH. Se instala como un plugin común de DeepSeek Harness, y cada turno, llamada al modelo y ejecución de herramienta pasa a ser una fila consultable en GreptimeDB.

INSTALACIÓN

dsh plugin --profile web add @tma1-ai/dsh-plugin-greptimedb

El paquete trae un bundle patch, así que ese comando es toda la instalación. Requiere pnpm 10 o superior. Los valores por defecto ya apuntan a un GreptimeDB local.

Apuntalo a tu propia base de datos, o levantá una local
$DSH_HOME/profiles/<name>/cordis.patch.yml
- id: greptimedb-otel
name: '@tma1-ai/dsh-plugin-greptimedb'
config:
endpoint: https://<host>/v1/otlp
database: <dbname>
username: <user>
password: <password>
greptimedb local
docker run -p 127.0.0.1:4000-4003:4000-4003 \
-v "$(pwd)/greptimedb_data:/greptimedb_data" \
--name greptime --rm greptime/greptimedb:v1.2.1 standalone start \
--http-addr 0.0.0.0:4000 --rpc-bind-addr 0.0.0.0:4001 \
--mysql-addr 0.0.0.0:4002 --postgres-addr 0.0.0.0:4003
greptimedb · sql
-- Las llamadas a herramientas más lentas, con el modelo que las pidió.
SELECT tool.span_name,
chat."span_attributes.gen_ai.request.model" AS model,
tool.duration_nano / 1000000 AS ms
FROM opentelemetry_traces AS tool
JOIN opentelemetry_traces AS chat
ON chat.trace_id = tool.trace_id
AND chat."span_attributes.dsh.step" = tool."span_attributes.dsh.step"
AND chat.span_name LIKE 'chat%'
WHERE tool.span_name LIKE 'execute_tool%'
ORDER BY tool.duration_nano DESC
LIMIT 10;

Los spans de chat y los de herramienta comparten trace y dsh.step, así que correlacionarlos es un join de SQL común. Los timestamps vienen de los propios eventos de sesión, no del momento en que el plugin los procesó.

Tres señales, un plugin

Traces, métricas y logs. signals acepta cualquier subconjunto — una señal desactivada no construye exporter alguno.

Por defecto no sale nada

El valor por defecto content: none exporta solo estructura y contabilidad. Sin prompts, sin mensajes, sin argumentos de herramientas, sin resultados.

Falla al cargar, no al exportar

Una configuración inválida falla cuando el plugin carga, nombrando el campo culpable — no en silencio durante la primera exportación.

Señales

Qué llega en cada señal

Traces para la forma, métricas para retención larga y percentiles a prueba de sampleo, logs para los eventos de sesión crudos.

[01]

Turno, chat, herramienta

Los spans de turno son raíces. Los de chat y herramienta cuelgan de ellos como hermanos, correlacionados por dsh.step. Todo span de chat tiene un fin real, incluido el caso de caída.

árbol de spans
invoke_agent dsh turn/start → turn/end
├── chat deepseek-chat step/start → assistant/message
├── execute_tool bash tool/call → tool/result
└── chat deepseek-chat
[02]

Contabilidad de tokens

Los conteos de DSH son disjuntos: inputTokens es solo entrada sin caché, y las lecturas y escrituras de caché son campos aparte. gen_ai.usage.input_tokens es el total facturado, así que el plugin los suma.

atributos de tokens
gen_ai.usage.input_tokens = inputTokens
+ cacheReadTokens
+ cacheWriteTokens
gen_ai.usage.output_tokens = outputTokens
 
# el desglose sigue siendo consultable
dsh.usage.uncached_input_tokens
dsh.usage.cache_read_tokens
dsh.usage.cache_write_tokens
dsh.usage.reasoning_tokens
[03]

Siete dashboards de Grafana

Overview, Cost, Sessions, Agent loop, Trace explorer, Log explorer y Metrics. Vienen en grafana/ junto a un stack de compose que levanta GreptimeDB y Grafana a la vez, y cada consulta de panel se verifica contra una base real en CI.

localhost:3000 · overview
Dashboard overview de DSH OTel: turnos, llamadas al modelo, tokens facturados, caché, latencia
[04]

Un turno, span por span

Cada tabla enlaza hacia adelante: un trace id abre el waterfall de ese turno, un session id salta entre la vista de traces y la de logs.

localhost:3000 · trace explorer
Dashboard trace explorer de DSH OTel
[05]

Una fila por evento de sesión

Sesión, tipo de evento, turno y step son columnas reales: filtrar una sesión no implica desarmar JSON.

greptimedb · sql
SELECT session_id, event_type, turn, step, body
FROM dsh_logs
WHERE session_id = '...' AND event_type = 'tool/result'
ORDER BY timestamp;

Métricas

Instrumentos

La misma actividad que los traces, vista con PromQL — para retención más larga y percentiles que sobreviven al sampleo.

InstrumentoTipoDimensiones
gen_ai.client.token.usageHistogramgen_ai.token.type (solo input/output), model, provider
gen_ai.client.operation.durationHistogramgen_ai.operation.name, model, provider
gen_ai.invoke_agent.durationHistogramgen_ai.operation.name
gen_ai.execute_tool.durationHistogramgen_ai.operation.name, gen_ai.tool.name
dsh.token.detailHistogramdsh.token.detail_kind (cache_read/cache_write/reasoning)
dsh.tool.invocationsCountergen_ai.tool.name, dsh.tool.outcome
dsh.turns / dsh.stepsCounter—

Configuración

Las claves que realmente vas a tocar

Un patch de profile reemplaza todo el config de esa fila en vez de fusionarse con él, así que reescribí cada campo que quieras conservar.

ClavePor defectoNotas
endpointrequeridoURL base de OTLP. El sufijo /v1/{traces,metrics,logs} lo agrega el plugin.
databasepublicSe envía como X-Greptime-DB-Name.
username / passwordningunoBasic auth. Los dos o ninguno.
signalslas tresCualquier subconjunto de traces, metrics, logs.
contentnoneCuánto payload puede salir del proceso.
ttl180dRetención de las tablas que crea el plugin. También acepta forever.

Batching, timeouts, nombre de servicio y overrides de tabla tienen valores razonables; la tabla completa está en el README.

Qué sale de la máquina

Lo decide content

El valor por defecto retiene todos los payloads. Subilo a propósito, por profile.

ModoSe exporta
none (por defecto)Estructura y contabilidad: tipos de evento, números de turno y step, conteos de tokens, nombres de herramientas, duraciones, resultados, name y code del error.
fullSuma el contenido de los mensajes de usuario y asistente, argumentos de herramientas y resultados.
full+promptSuma request/header: el system prompt completo y el schema de cada herramienta.

En ningún modo salen el payload privado meta de una herramienta ni el mensaje y stack de una petición fallida. La proyección es una allowlist, así que un tipo de evento que el plugin no conoce exporta su identidad y nada más.

Con TMA1

Apuntalo a TMA1

TMA1 hace de proxy OTLP hacia un GreptimeDB que él mismo administra. Cambiás una línea y DSH aparece en su vista OTel GenAI.

Las flow tables ya coinciden

Las flow tables tma1_token_usage_1m, cost_1m, latency_1m y status_1m de TMA1 derivan de span_attributes.gen_ai.*, que este plugin completa por convención. No hay nada más que configurar.

cordis.patch.yml
endpoint: http://localhost:14318/v1/otlp

Limitaciones conocidas

Leé esto antes

La lista completa está en el README.

DSH es pre-release

Renombra y reempaqueta libremente hasta su primer tag, así que el rango de peer queda fijado a la versión contra la que corre CI.

Las convenciones GenAI son experimentales

Los nombres de atributo vienen de @opentelemetry/semantic-conventions/incubating y se mueven con él.

ttl no llega a las tablas de métricas

En el metric engine la retención es propiedad de la tabla física, así que hay que definirla ahí (greptimedb#8951).

La exportación es por lotes

No hay flush por turno, y los registros en vuelo al apagar pueden perderse.