@osohq/langchain
Version:
Oso observability integration for LangChain agents
297 lines • 12.7 kB
JavaScript
"use strict";
/**
* LangChain callback handler for Oso observability integration.
*
* Example usage:
* import { OsoObservabilityCallback } from '@osohq/langchain';
*
* const callback = new OsoObservabilityCallback({
* authToken: "your-oso-auth-token",
* agentId: "my-support-agent"
* });
*
* const agent = createAgent({ callbacks: [callback] });
* const result = await agent.invoke({ input: "Hello" });
* await callback.close();
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OsoObservabilityCallback = void 0;
const base_1 = require("@langchain/core/callbacks/base");
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const crypto_1 = require("crypto");
/**
* LangChain callback handler that sends agent events to Oso observability.
*
* Automatically captures:
* - LLM calls (model, prompts, responses, token usage)
* - Tool executions (name, inputs, outputs, duration, errors)
* - Agent reasoning (decisions, thoughts, intermediate steps)
* - Chain executions (starts, ends, inputs, outputs)
* - Errors at any stage
*/
class OsoObservabilityCallback extends base_1.BaseCallbackHandler {
constructor(config = {}) {
super();
this.name = "OsoObservabilityCallback";
// Accumulate data for summary event
this.llmCalls = [];
this.toolCalls = [];
this.agentSteps = [];
this.errors = [];
this.endpoint =
config.endpoint ||
process.env.OSO_ENDPOINT ||
"https://cloud.osohq.com/api/events";
this.authToken = config.authToken || process.env.OSO_AUTH_TOKEN;
this.enabled =
config.enabled !== undefined
? config.enabled
: (process.env.OSO_OBSERVABILITY_ENABLED || "true").toLowerCase() ===
"true";
this.sessionId = config.sessionId || (0, crypto_1.randomUUID)();
this.metadata = config.metadata || {};
this.agentId = config.agentId || "default-agent";
this.executionId = (0, crypto_1.randomUUID)();
this.executionStartTime = Date.now();
this.toolStartTimes = new Map();
console.debug(`OsoObservabilityCallback initialized: endpoint=${this.endpoint}, ` +
`agentId=${this.agentId}, sessionId=${this.sessionId}, enabled=${this.enabled}`);
}
// LLM callbacks
handleLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const modelName = (llm === null || llm === void 0 ? void 0 : llm.name) || ((_a = llm === null || llm === void 0 ? void 0 : llm.id) === null || _a === void 0 ? void 0 : _a[llm.id.length - 1]) || "unknown";
yield this.sendEvent("llm.started", {
model: modelName,
prompts,
num_prompts: prompts.length,
});
});
}
handleLLMEnd(output, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
try {
const llmOutput = output.llmOutput || {};
const tokenUsage = llmOutput.tokenUsage || {};
const generations = [];
if (output.generations) {
for (const genList of output.generations) {
for (const gen of genList) {
generations.push({
text: gen.text || String(gen),
metadata: gen.generationInfo || {},
});
}
}
}
const callData = {
model: llmOutput.modelName || "unknown",
generations,
token_usage: {
prompt_tokens: tokenUsage.promptTokens || 0,
completion_tokens: tokenUsage.completionTokens || 0,
total_tokens: tokenUsage.totalTokens || 0,
},
};
this.llmCalls.push(callData);
yield this.sendEvent("llm.completed", callData);
}
catch (error) {
console.error(`Error in handleLLMEnd: ${error}`);
yield this.sendEvent("llm.error", { error: String(error) });
}
});
}
handleLLMError(error, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const errorData = {
error_type: error.constructor.name,
error_message: error.message,
};
this.errors.push(Object.assign({ type: "llm_error" }, errorData));
yield this.sendEvent("llm.error", errorData);
});
}
// Tool callbacks
handleToolStart(tool, input, runId, parentRunId, tags, metadata) {
return __awaiter(this, void 0, void 0, function* () {
const toolName = (tool === null || tool === void 0 ? void 0 : tool.name) || "unknown";
const toolDescription = (tool === null || tool === void 0 ? void 0 : tool.description) || "";
this.toolStartTimes.set(runId, Date.now());
yield this.sendEvent("tool.started", {
tool_name: toolName,
tool_description: toolDescription,
input,
run_id: runId,
});
});
}
handleToolEnd(output, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const startTime = this.toolStartTimes.get(runId);
const durationMs = startTime ? Date.now() - startTime : null;
const toolData = {
output,
duration_ms: durationMs,
run_id: runId,
};
this.toolCalls.push(toolData);
yield this.sendEvent("tool.completed", toolData);
this.toolStartTimes.delete(runId);
});
}
handleToolError(error, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const startTime = this.toolStartTimes.get(runId);
const durationMs = startTime ? Date.now() - startTime : null;
const errorData = {
error_type: error.constructor.name,
error_message: error.message,
duration_ms: durationMs,
run_id: runId,
};
this.errors.push(Object.assign({ type: "tool_error" }, errorData));
yield this.sendEvent("tool.error", errorData);
this.toolStartTimes.delete(runId);
});
}
// Agent callbacks
handleAgentAction(action, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const stepData = {
step_type: "action",
tool: action.tool,
tool_input: action.toolInput,
reasoning: action.log,
};
this.agentSteps.push(stepData);
yield this.sendEvent("agent.action", stepData);
});
}
handleAgentEnd(action, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const finishData = {
return_values: action.returnValues,
final_reasoning: action.log || null,
};
yield this.sendEvent("agent.finished", finishData);
yield this.sendExecutionSummary();
});
}
// Chain callbacks
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const chainName = (chain === null || chain === void 0 ? void 0 : chain.name) || ((_a = chain === null || chain === void 0 ? void 0 : chain.id) === null || _a === void 0 ? void 0 : _a[chain.id.length - 1]) || "unknown";
yield this.sendEvent("chain.started", {
chain_name: chainName,
inputs,
});
});
}
handleChainEnd(outputs, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
yield this.sendEvent("chain.completed", { outputs });
});
}
handleChainError(error, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const errorData = {
error_type: error.constructor.name,
error_message: error.message,
};
this.errors.push(Object.assign({ type: "chain_error" }, errorData));
yield this.sendEvent("chain.error", errorData);
});
}
// Internal methods
sendEvent(eventType, data) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.enabled) {
return;
}
try {
const payload = {
event_type: eventType,
execution_id: this.executionId,
session_id: this.sessionId,
timestamp: new Date().toISOString().replace("+00:00", "Z"),
data,
metadata: this.metadata,
agent_id: this.agentId,
};
console.debug(`Sending event: ${eventType}`);
const headers = {
"Content-Type": "application/json",
};
if (this.authToken) {
headers["Authorization"] = `Bearer ${this.authToken}`;
}
const response = yield (0, cross_fetch_1.default)(this.endpoint, {
method: "POST",
headers,
body: JSON.stringify(payload),
});
if (![200, 201, 202].includes(response.status)) {
console.warn(`Oso backend returned ${response.status} for event ${eventType}`);
}
else {
console.debug(`Event ${eventType} sent successfully`);
}
}
catch (error) {
if (error instanceof Error) {
console.warn(`Error sending event ${eventType}: ${error.constructor.name}: ${error.message}`);
}
else {
console.error(`Error sending event ${eventType}:`, error);
}
}
});
}
sendExecutionSummary() {
return __awaiter(this, void 0, void 0, function* () {
const durationMs = Date.now() - this.executionStartTime;
const totalTokens = this.llmCalls.reduce((sum, call) => sum + (call.token_usage.total_tokens || 0), 0);
const summary = {
execution_id: this.executionId,
session_id: this.sessionId,
duration_ms: durationMs,
llm_calls_count: this.llmCalls.length,
tool_calls_count: this.toolCalls.length,
agent_steps_count: this.agentSteps.length,
errors_count: this.errors.length,
total_tokens: totalTokens,
llm_calls: this.llmCalls,
tool_calls: this.toolCalls,
agent_steps: this.agentSteps,
errors: this.errors,
};
yield this.sendEvent("execution.summary", summary);
});
}
/**
* Clean up resources. Call this when you're done using the callback.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
console.debug("OsoObservabilityCallback closed");
// No persistent connections to close in this implementation
});
}
}
exports.OsoObservabilityCallback = OsoObservabilityCallback;
//# sourceMappingURL=callbacks.js.map