feat(telemetry): capture agent-runner model turns via central telemetry service

This commit is contained in:
2026-06-10 16:43:14 -07:00
parent caab38f950
commit db18168537
2 changed files with 83 additions and 7 deletions

View File

@@ -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);
}

View File

@@ -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;
}