From db1816853722185007040fd3709f3b5fd0b86a3c Mon Sep 17 00:00:00 2001 From: mawkone Date: Wed, 10 Jun 2026 16:43:14 -0700 Subject: [PATCH] feat(telemetry): capture agent-runner model turns via central telemetry service --- vibn-agent-runner/src/llm/telemetry-db.ts | 45 ++++++++++++++++++++ vibn-agent-runner/src/llm/vibn-chat-model.ts | 45 +++++++++++++++++--- 2 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 vibn-agent-runner/src/llm/telemetry-db.ts diff --git a/vibn-agent-runner/src/llm/telemetry-db.ts b/vibn-agent-runner/src/llm/telemetry-db.ts new file mode 100644 index 00000000..61addc0a --- /dev/null +++ b/vibn-agent-runner/src/llm/telemetry-db.ts @@ -0,0 +1,45 @@ +/** + * Fire-and-forget telemetry: sends each model turn to the central + * telemetry microservice (TELEMETRY_SERVICE_URL) which persists it to + * Postgres for diagnostics and as fine-tuning training data. + * + * Never blocks or throws into the agent loop. + */ + +export interface TelemetryPayload { + projectId?: string; + model: string; + systemPrompt: string; + messages: unknown[]; + response: { + text: string; + thoughts: string; + toolCalls: unknown[]; + }; + metrics: { + promptTokens?: number; + completionTokens?: number; + totalTokens?: number; + durationMs: number; + }; +} + +export function logTrainingTelemetry(data: TelemetryPayload): void { + setTimeout(async () => { + try { + const telemetryUrl = process.env.TELEMETRY_SERVICE_URL; + if (!telemetryUrl) return; // silently skip when not configured + + await fetch(`${telemetryUrl.replace(/\/$/, "")}/ingest`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data), + }); + } catch (error) { + console.error( + "[Telemetry] Failed to send training data to microservice:", + error instanceof Error ? error.message : String(error), + ); + } + }, 0); +} diff --git a/vibn-agent-runner/src/llm/vibn-chat-model.ts b/vibn-agent-runner/src/llm/vibn-chat-model.ts index 254497c5..98d966d0 100644 --- a/vibn-agent-runner/src/llm/vibn-chat-model.ts +++ b/vibn-agent-runner/src/llm/vibn-chat-model.ts @@ -12,9 +12,10 @@ * Optional: VIBN_OPENAI_COMPATIBLE_MODEL (default deepseek-chat) */ -import type { ChatMessage, ToolDefinition } from './gemini-chat'; -import { callGeminiChat } from './gemini-chat'; -import { callOpenAiCompatibleChat } from './openai-compatible-chat'; +import type { ChatMessage, ToolDefinition } from "./gemini-chat"; +import { callGeminiChat } from "./gemini-chat"; +import { callOpenAiCompatibleChat } from "./openai-compatible-chat"; +import { logTrainingTelemetry } from "./telemetry-db"; export type VibnChatCallOpts = { systemPrompt: string; @@ -22,12 +23,42 @@ export type VibnChatCallOpts = { tools?: ToolDefinition[]; temperature?: number; includeThoughts?: boolean; + // Optional metadata for telemetry; not used by the model call itself. + projectId?: string; }; export async function callVibnChat(opts: VibnChatCallOpts) { - const p = (process.env.VIBN_CHAT_PROVIDER || 'gemini').toLowerCase().trim(); - if (p === 'deepseek' || p === 'openai_compatible') { - return callOpenAiCompatibleChat(opts); + const p = (process.env.VIBN_CHAT_PROVIDER || "gemini").toLowerCase().trim(); + const model = + p === "deepseek" || p === "openai_compatible" + ? process.env.VIBN_OPENAI_COMPATIBLE_MODEL || "deepseek-chat" + : process.env.VIBN_CHAT_MODEL || "gemini-3.1-pro-preview"; + + const startedAt = Date.now(); + const result = + p === "deepseek" || p === "openai_compatible" + ? await callOpenAiCompatibleChat(opts) + : await callGeminiChat(opts); + const durationMs = Date.now() - startedAt; + + // Fire-and-forget: capture this turn as training/diagnostic telemetry. + // Never let logging interfere with the agent loop. + try { + logTrainingTelemetry({ + projectId: opts.projectId, + model, + systemPrompt: opts.systemPrompt, + messages: opts.messages, + response: { + text: result.text ?? "", + thoughts: result.thoughts ?? "", + toolCalls: result.toolCalls ?? [], + }, + metrics: { durationMs }, + }); + } catch { + // ignore } - return callGeminiChat(opts); + + return result; }