From 918fa2e833cc6c26671ab71885838858f55b4caa Mon Sep 17 00:00:00 2001 From: JlincFM Date: Wed, 3 Sep 2025 19:52:41 +0000 Subject: [PATCH] npm package distribtion --- .gitignore | 1 - README.md | 5 - dist/tracer.d.ts | 127 ++++++++++++++++++++++++ dist/tracer.js | 241 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 2 +- package.json | 4 +- 6 files changed, 371 insertions(+), 9 deletions(-) create mode 100644 dist/tracer.d.ts create mode 100644 dist/tracer.js diff --git a/.gitignore b/.gitignore index 76add87..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ node_modules -dist \ No newline at end of file diff --git a/README.md b/README.md index 38de553..a731548 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,3 @@ -

- -

-
- # JLINC Langchain Tracer The JLINC Langchain Tracer is the official way to implement the zero-knowledge third-party auditing provided by the [JLINC Server](https://gitea.jlinc.io/jlinc-labs/jlinc-server) inside any Langchain-based infrastructure. diff --git a/dist/tracer.d.ts b/dist/tracer.d.ts new file mode 100644 index 0000000..38a8bf4 --- /dev/null +++ b/dist/tracer.d.ts @@ -0,0 +1,127 @@ +export type JLINCTracerFields = { + dataStoreApiUrl?: string | undefined; + dataStoreApiKey?: string | undefined; + archiveApiUrl?: string | undefined; + archiveApiKey?: string | undefined; + systemPrefix?: string | undefined; + agreementId?: string | null | undefined; + debug?: boolean | undefined; +}; +export type SerializedRun = { + name?: string | undefined; + start_time?: number | undefined; + end_time?: number | undefined; + trace_id?: string | undefined; + inputs?: any; + outputs?: any; + extra?: { + metadata?: { + ls_provider?: string; + ls_model_name?: string; + }; + } | undefined; +}; +/** + * @typedef {Object} JLINCTracerFields + * @property {string} [dataStoreApiUrl] + * @property {string} [dataStoreApiKey] + * @property {string} [archiveApiUrl] + * @property {string} [archiveApiKey] + * @property {string} [systemPrefix] + * @property {string|null} [agreementId] + * @property {boolean} [debug] + */ +/** + * @typedef {Object} SerializedRun + * @property {string} [name] + * @property {number} [start_time] + * @property {number} [end_time] + * @property {string} [trace_id] + * @property {any} [inputs] + * @property {any} [outputs] + * @property {{ metadata?: { ls_provider?: string, ls_model_name?: string } }} [extra] + */ +/** + * @extends {LangChainTracer} + */ +export class JLINCTracer extends LangChainTracer { + /** + * @param {JLINCTracerFields} [fields={}] + */ + constructor(fields?: JLINCTracerFields); + /** @type {string} */ + dataStoreApiUrl: string; + /** @type {string} */ + dataStoreApiKey: string; + /** @type {string} */ + archiveApiUrl: string; + /** @type {string} */ + archiveApiKey: string; + /** @type {string} */ + systemPrefix: string; + /** @type {string|null} */ + agreementId: string | null; + /** @type {boolean} */ + debug: boolean; + /** @type {string|null} */ + domain: string | null; + /** @type {Set} */ + entities: Set; + /** + * @param {any} run + * @returns {Promise} + */ + onRunCreate(run: any): Promise; + /** + * @param {any} run + * @returns {Promise} + */ + onRunUpdate(run: any): Promise; + /** + * @returns {Promise} + */ + getDefaultProjectName(): Promise; + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + onLLMStart(serialized: SerializedRun): Promise; + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + onLLMEnd(serialized: SerializedRun): Promise; + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + onToolStart(serialized: SerializedRun): Promise; + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + onToolEnd(serialized: SerializedRun): Promise; + /** + * @param {string} dir + * @param {any} payload + * @returns {Promise} + */ + _postToApi(dir: string, payload: any): Promise; + /** + * @param {string} entityShortName + * @returns {Promise} + */ + _setEntity(entityShortName: string): Promise; + /** + * @param {string} input + * @returns {string} + */ + _sanitize(input: string): string; + /** + * @param {any} payload + * @param {'in' | 'out'} direction + * @returns {Promise} + */ + _jlincTrace(payload: any, direction: "in" | "out"): Promise; +} +import { LangChainTracer } from "@langchain/core/dist/tracers/tracer_langchain"; diff --git a/dist/tracer.js b/dist/tracer.js new file mode 100644 index 0000000..ef78b85 --- /dev/null +++ b/dist/tracer.js @@ -0,0 +1,241 @@ +const { LangChainTracer } = require("@langchain/core/tracers/tracer_langchain"); +const axios = require("axios"); + +/** + * @typedef {Object} JLINCTracerFields + * @property {string} [dataStoreApiUrl] + * @property {string} [dataStoreApiKey] + * @property {string} [archiveApiUrl] + * @property {string} [archiveApiKey] + * @property {string} [systemPrefix] + * @property {string|null} [agreementId] + * @property {boolean} [debug] + */ + +/** + * @typedef {Object} SerializedRun + * @property {string} [name] + * @property {number} [start_time] + * @property {number} [end_time] + * @property {string} [trace_id] + * @property {any} [inputs] + * @property {any} [outputs] + * @property {{ metadata?: { ls_provider?: string, ls_model_name?: string } }} [extra] + */ + +/** + * @extends {LangChainTracer} + */ +class JLINCTracer extends LangChainTracer { + /** + * @param {JLINCTracerFields} [fields={}] + */ + constructor(fields = {}) { + super(fields); + const defaultDataStoreApiUrl = "https://api-test.jlinc.io"; + const defaultDataStoreApiKey = ""; + const defaultArchiveApiUrl = "https://archive-test.jlinc.io"; + const defaultArchiveApiKey = ""; + const defaultSystemPrefix = "MyProject"; + + /** @type {string} */ + this.dataStoreApiUrl = fields.dataStoreApiUrl ?? defaultDataStoreApiUrl; + /** @type {string} */ + this.dataStoreApiKey = fields.dataStoreApiKey ?? defaultDataStoreApiKey; + /** @type {string} */ + this.archiveApiUrl = fields.archiveApiUrl ?? defaultArchiveApiUrl; + /** @type {string} */ + this.archiveApiKey = fields.archiveApiKey ?? defaultArchiveApiKey; + /** @type {string} */ + this.systemPrefix = this._sanitize(fields.systemPrefix ?? defaultSystemPrefix); + /** @type {string|null} */ + this.agreementId = fields.agreementId ?? null; + /** @type {boolean} */ + this.debug = fields.debug ?? false; + + /** @type {string|null} */ + this.domain = null; + /** @type {Set} */ + this.entities = new Set(); + } + + /** + * @param {any} run + * @returns {Promise} + */ + async onRunCreate(run) { } + + /** + * @param {any} run + * @returns {Promise} + */ + async onRunUpdate(run) { } + + /** + * @returns {Promise} + */ + async getDefaultProjectName() { + return this.systemPrefix; + } + + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + async onLLMStart(serialized) { + if (this.debug) { + console.log(`\nJLINC: onLLMStart`); + console.log(`---------------------------------------------`); + } + await this._jlincTrace({ + event: "llm_start", + name: serialized?.name || "unknown", + ts: serialized?.start_time, + provider: serialized?.extra?.metadata, + traceId: serialized?.trace_id, + inputs: serialized?.inputs, + }, 'out'); + } + + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + async onLLMEnd(serialized) { + if (this.debug) { + console.log(`\nJLINC: onLLMEnd`); + console.log(`---------------------------------------------`); + } + await this._jlincTrace({ + event: "llm_end", + name: serialized?.name || "unknown", + ts: serialized?.end_time, + provider: serialized?.extra?.metadata, + traceId: serialized?.trace_id, + outputs: serialized?.outputs, + }, 'in'); + } + + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + async onToolStart(serialized) { + if (this.debug) { + console.log(`\nJLINC: onToolStart`); + console.log(`---------------------------------------------`); + } + await this._jlincTrace({ + event: "tool_start", + name: serialized?.name || "unknown", + ts: serialized?.start_time, + provider: serialized?.extra?.metadata, + traceId: serialized?.trace_id, + inputs: serialized?.inputs, + }, 'out'); + } + + /** + * @param {SerializedRun} serialized + * @returns {Promise} + */ + async onToolEnd(serialized) { + if (this.debug) { + console.log(`\nJLINC: onToolEnd`); + console.log(`---------------------------------------------`); + } + await this._jlincTrace({ + event: "tool_end", + name: serialized?.name || "unknown", + ts: serialized?.end_time, + provider: serialized?.extra?.metadata, + traceId: serialized?.trace_id, + outputs: serialized?.outputs, + }, 'in'); + } + + /** + * @param {string} dir + * @param {any} payload + * @returns {Promise} + */ + async _postToApi(dir, payload) { + const response = await axios.post( + `${this.dataStoreApiUrl}/api/v1/data/${dir}`, + payload, + { + headers: { + 'Authorization': `Bearer ${this.dataStoreApiKey}`, + } + } + ); + return response.data; + } + + /** + * @param {string} entityShortName + * @returns {Promise} + */ + async _setEntity(entityShortName) { + if (!this.entities.has(entityShortName)) { + let recipient = null; + try { + recipient = await this._postToApi(`entity/get`, { shortName: entityShortName }); + } catch (e) { + recipient = await this._postToApi(`entity/create`, { shortName: entityShortName }); + } + this.entities.add(entityShortName); + } + } + + /** + * @param {string} input + * @returns {string} + */ + _sanitize(input) { + return input.replace(/[^a-zA-Z0-9._-]/g, '_'); + } + + /** + * @param {any} payload + * @param {'in' | 'out'} direction + * @returns {Promise} + */ + async _jlincTrace(payload, direction) { + try { + if (!this.domain) { + const domains = await this._postToApi(`entity/domains/get`, {}); + this.domain = domains[0]; + } + let entityShortName = `${this.systemPrefix}-${this._sanitize(payload.name)}`; + payload?.provider?.ls_provider && (entityShortName += `-${this._sanitize(payload.provider.ls_provider)}`); + payload?.provider?.ls_model_name && (entityShortName += `-${this._sanitize(payload.provider.ls_model_name)}`); + entityShortName += `@${this.domain}`; + await this._setEntity(entityShortName); + const systemShortName = `${this.systemPrefix}-system@${this.domain}`; + await this._setEntity(systemShortName); + const recipientShortName = direction === 'in' ? systemShortName : entityShortName; + const senderShortName = direction === 'out' ? systemShortName : entityShortName; + const data = { + type: 'data', + senderShortName, + recipientShortName, + agreementId: this.agreementId, + data: payload, + archive: { + url: this.archiveApiUrl, + key: this.archiveApiKey, + } + }; + const event = await this._postToApi(`event/produce`, data); + if (this.debug) { + console.log(JSON.stringify({ event }, null, 2)); + } + } catch (error) { + console.error("[Tracer] Failed to log event:", error.message); + } + } + +} + +module.exports = { JLINCTracer } diff --git a/package-lock.json b/package-lock.json index 80a1fd3..c9bb9e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "jlinc-tracer", + "name": "@jlinc/langchain", "version": "0.1.0", "lockfileVersion": 3, "requires": true, diff --git a/package.json b/package.json index cfc46ce..36c7e12 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "jlinc-tracer", + "name": "@jlinc/langchain", "version": "0.1.0", - "description": "A LangChain callback plugin that logs events to the JLINC API.", + "description": "A LangChain plugin that logs events to the JLINC API.", "main": "dist/tracer.js", "types": "dist/tracer.d.ts", "scripts": {