feat(telemetry): capture agent-runner model turns via central telemetry service
This commit is contained in:
45
vibn-agent-runner/src/llm/telemetry-db.ts
Normal file
45
vibn-agent-runner/src/llm/telemetry-db.ts
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user