@osohq/langchain
Version:
Oso integration for LangChain agents
283 lines • 13.5 kB
JavaScript
"use strict";
/**
* LangChain callback handler for Oso integration.
*
* Example usage:
* import { OsoCallback } from '@osohq/langchain';
*
* const callback = new OsoCallback({
* apiKey: "your-oso-api-key",
* agentId: "my-support-agent",
* users: [{ type: "User", id: "user-123" }]
* });
*
* const agent = createAgent({ llm, tools, prompt });
* const result = await agent.invoke({ input: "Hello" }, { callbacks: [callback] });
*/
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.OsoCallback = 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.
*/
class OsoCallback extends base_1.BaseCallbackHandler {
constructor(config = {}) {
super();
this.name = "OsoCallback";
this.logger = config.logger || (() => { });
this.url = new URL("api/agents/event", config.url || process.env.OSO_URL || "https://cloud.osohq.com");
this.apiKey = config.apiKey || process.env.OSO_AUTH;
if (!this.apiKey) {
this.logger("warn", "No API key provided for OsoCallback -- no events will be sent.");
}
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (config.sessionId && uuidRegex.test(config.sessionId)) {
this.sessionId = config.sessionId;
}
else {
if (config.sessionId) {
this.logger("warn", `Invalid UUID provided for sessionId: ${config.sessionId}. Generating a new one.`);
}
this.sessionId = (0, crypto_1.randomUUID)();
}
this.metadata = config.metadata || {};
this.agentId = config.agentId || "default-agent";
this.users = config.users || [];
this.toolNames = new Map();
this.logger("debug", `OsoCallback initialized: url=${this.url}, ` +
`agentId=${this.agentId}, sessionId=${this.sessionId}`);
}
getEventBase() {
return Object.assign({ agent_id: this.agentId, session_id: this.sessionId, users: this.users, timestamp: new Date().toISOString() }, this.metadata);
}
// Tool callbacks
handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
return __awaiter(this, void 0, void 0, function* () {
const toolName = (tool === null || tool === void 0 ? void 0 : tool.name) || runName || "unknown";
this.toolNames.set(runId, toolName);
let toolParameters;
try {
toolParameters = JSON.parse(input);
}
catch (_a) {
toolParameters = { input };
}
yield this.sendEvent("tool_call_request", Object.assign(Object.assign({}, this.getEventBase()), { tool_name: toolName, tool_parameters: toolParameters }));
});
}
handleToolEnd(output, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const toolName = this.toolNames.get(runId) || "unknown";
let toolResult;
try {
toolResult = JSON.parse(output);
}
catch (_a) {
toolResult = output;
}
yield this.sendEvent("tool_call_response", Object.assign(Object.assign({}, this.getEventBase()), { tool_name: toolName, tool_result: toolResult }));
this.toolNames.delete(runId);
});
}
handleToolError(error, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
const toolName = this.toolNames.get(runId) || "unknown";
yield this.sendEvent("tool_call_error", Object.assign(Object.assign({}, this.getEventBase()), { tool_name: toolName, error: error.message }));
this.toolNames.delete(runId);
});
}
// Chain callbacks
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
return __awaiter(this, void 0, void 0, function* () {
if (!parentRunId) {
this.rootRunId = runId;
let message = "";
if (typeof inputs === "string") {
message = inputs;
}
else if (inputs.input) {
message =
typeof inputs.input === "string"
? inputs.input
: JSON.stringify(inputs.input);
}
else if (inputs.question) {
message =
typeof inputs.question === "string"
? inputs.question
: JSON.stringify(inputs.question);
}
else if (inputs.messages && Array.isArray(inputs.messages)) {
const lastMessage = inputs.messages[inputs.messages.length - 1];
message = (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.content) || JSON.stringify(lastMessage);
}
else {
message = JSON.stringify(inputs);
}
this.logger("debug", `handleChainStart inputs: ${JSON.stringify(inputs)}`);
try {
const event = JSON.parse(message);
if (event &&
typeof event === "object" &&
event.userInput &&
typeof event.userInput === "object") {
const userInput = event.userInput;
if (!("text" in userInput)) {
return;
}
message =
typeof userInput.text === "string"
? userInput.text
: JSON.stringify(userInput.text);
}
}
catch (_a) { }
yield this.sendEvent("message_from_user", Object.assign(Object.assign({}, this.getEventBase()), { message }));
}
});
}
handleChainEnd(outputs, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
if (runId === this.rootRunId) {
let message = "";
if (typeof outputs === "string") {
message = outputs;
}
else if (outputs.output) {
message =
typeof outputs.output === "string"
? outputs.output
: JSON.stringify(outputs.output);
}
else if (outputs.text) {
message =
typeof outputs.text === "string"
? outputs.text
: JSON.stringify(outputs.text);
}
else if (outputs.response) {
message =
typeof outputs.response === "string"
? outputs.response
: JSON.stringify(outputs.response);
}
else if (outputs.messages && Array.isArray(outputs.messages)) {
const lastMessage = outputs.messages[outputs.messages.length - 1];
message = (lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.content) || JSON.stringify(lastMessage);
}
else {
message = JSON.stringify(outputs);
}
this.logger("debug", `handleChainEnd outputs: ${JSON.stringify(outputs)}`);
try {
const event = JSON.parse(message);
if (event &&
typeof event === "object" &&
event.userInput &&
typeof event.userInput === "object") {
if ("messageHistory" in event &&
Array.isArray(event.messageHistory) &&
"userInput" in event &&
event.userInput !== null &&
typeof event.userInput === "object" &&
"toolCallId" in event.userInput &&
event.userInput.toolCallId &&
event.userInput.toolCallId !== null) {
const toolCallId = event.userInput.toolCallId;
const toolCallMessage = event.messageHistory.find((message) => "toolCalls" in message &&
Array.isArray(message.toolCalls) &&
message.toolCalls.find((toolCall) => "id" in toolCall && toolCall.id === toolCallId));
if (toolCallMessage) {
const toolCall = toolCallMessage.toolCalls.find((toolCall) => "id" in toolCall && toolCall.id === toolCallId);
if (toolCall && "name" in toolCall && "input" in toolCall) {
yield this.sendEvent("tool_call_request", Object.assign(Object.assign({}, this.getEventBase()), { tool_name: toolCall.name, tool_parameters: toolCall.input }));
if ("content" in event.userInput) {
yield this.sendEvent("tool_call_response", Object.assign(Object.assign({}, this.getEventBase()), { tool_name: toolCall.name, tool_result: typeof event.userInput.content === "string"
? JSON.parse(event.userInput.content)
: event.userInput.content }));
}
}
}
}
if ("finalResult" in event) {
if (event.finalResult === null) {
return;
}
message =
typeof event.finalResult === "string"
? event.finalResult
: JSON.stringify(event.finalResult);
}
}
}
catch (_a) { }
yield this.sendEvent("message_to_user", Object.assign(Object.assign({}, this.getEventBase()), { message }));
this.rootRunId = undefined;
}
});
}
handleChainError(error, runId, parentRunId) {
return __awaiter(this, void 0, void 0, function* () {
if (runId === this.rootRunId) {
this.rootRunId = undefined;
}
});
}
// Internal methods
sendEvent(path, payload) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.apiKey) {
this.logger("warn", "Not sending event because no API key is set", {
path,
payload,
});
return;
}
const url = `${this.url}/${path}`;
try {
this.logger("debug", `Sending event to ${path}`);
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
};
const response = yield (0, cross_fetch_1.default)(url, {
method: "POST",
headers,
body: JSON.stringify(payload),
});
if (![200, 201, 202].includes(response.status)) {
this.logger("warn", `Oso backend returned ${response.status} for ${path}`);
const text = yield response.text();
this.logger("error", `Response body: ${text}`);
}
else {
this.logger("debug", `Event sent successfully to ${path}`);
}
}
catch (error) {
if (error instanceof Error) {
this.logger("error", `Error sending event to ${path}: ${error.constructor.name}: ${error.message}`);
}
else {
this.logger("error", `Error sending event to ${path}: ${error}`);
}
}
});
}
}
exports.OsoCallback = OsoCallback;
//# sourceMappingURL=callbacks.js.map