netra-sdk 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ import { CompositePropagator, W3CTraceContextPropagator, W3CBaggagePropagator, E
10
10
  import * as http from 'http';
11
11
  import * as https from 'https';
12
12
  import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer';
13
+ import pLimit from 'p-limit';
14
+ import axios from 'axios';
13
15
 
14
16
  // src/api/http-client.ts
15
17
  var NetraHttpClient = class {
@@ -348,6 +350,8 @@ var Measure = /* @__PURE__ */ ((Measure2) => {
348
350
  Measure2["TOTAL_COST"] = "Total Cost";
349
351
  Measure2["VIOLATIONS"] = "Violations";
350
352
  Measure2["TOTAL_TOKENS"] = "Total Tokens";
353
+ Measure2["AUDIO_DURATION"] = "Audio Duration";
354
+ Measure2["CHARACTER_COUNT"] = "Character Count";
351
355
  return Measure2;
352
356
  })(Measure || {});
353
357
  var Aggregation = /* @__PURE__ */ ((Aggregation2) => {
@@ -8821,6 +8825,445 @@ function uninstrumentAll() {
8821
8825
  console.debug("Failed to uninstrument TypeORM:", e);
8822
8826
  }
8823
8827
  }
8828
+ var LOG_PREFIX = "netra.simulation";
8829
+ var DEFAULT_TIMEOUT = 1e4;
8830
+ var SimulationHttpClient = class {
8831
+ constructor(config2) {
8832
+ this.client = null;
8833
+ this.client = this._createClient(config2);
8834
+ }
8835
+ /**
8836
+ * Create and configure the HTTP client.
8837
+ */
8838
+ _createClient(config2) {
8839
+ const endpoint = (config2.otlpEndpoint || "").trim();
8840
+ if (!endpoint) {
8841
+ console.error(`${LOG_PREFIX}: NETRA_OTLP_ENDPOINT is required`);
8842
+ return null;
8843
+ }
8844
+ const baseURL = this._resolveBaseUrl(endpoint);
8845
+ const headers = this._buildHeaders(config2);
8846
+ const timeout = this._getTimeout();
8847
+ try {
8848
+ return axios.create({
8849
+ baseURL,
8850
+ headers,
8851
+ timeout
8852
+ });
8853
+ } catch (error) {
8854
+ console.error(`${LOG_PREFIX}: Failed to create HTTP client:`, error);
8855
+ return null;
8856
+ }
8857
+ }
8858
+ /**
8859
+ * Extract base URL, removing telemetry suffix if present.
8860
+ */
8861
+ _resolveBaseUrl(endpoint) {
8862
+ let baseUrl = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
8863
+ if (baseUrl.endsWith("/telemetry")) {
8864
+ baseUrl = baseUrl.slice(0, -"/telemetry".length);
8865
+ }
8866
+ return baseUrl;
8867
+ }
8868
+ /**
8869
+ * Build request headers from configuration.
8870
+ */
8871
+ _buildHeaders(config2) {
8872
+ const headers = { ...config2.headers };
8873
+ if (config2.apiKey) {
8874
+ headers["x-api-key"] = config2.apiKey;
8875
+ }
8876
+ return headers;
8877
+ }
8878
+ /**
8879
+ * Get timeout from environment or use default.
8880
+ */
8881
+ _getTimeout() {
8882
+ const timeoutStr = process.env.NETRA_SIMULATION_TIMEOUT;
8883
+ if (!timeoutStr) {
8884
+ return DEFAULT_TIMEOUT;
8885
+ }
8886
+ const timeout = parseFloat(timeoutStr);
8887
+ if (isNaN(timeout)) {
8888
+ console.warn(
8889
+ `${LOG_PREFIX}: Invalid timeout '${timeoutStr}', using default ${DEFAULT_TIMEOUT}ms`
8890
+ );
8891
+ return DEFAULT_TIMEOUT;
8892
+ }
8893
+ return timeout * 1e3;
8894
+ }
8895
+ /**
8896
+ * Create a new simulation run for the specified dataset.
8897
+ */
8898
+ async createRun(name, datasetId, context13) {
8899
+ if (!this.client) {
8900
+ console.error(`${LOG_PREFIX}: Client not initialized`);
8901
+ return null;
8902
+ }
8903
+ try {
8904
+ const url = "/evaluations/test_run/multi-turn";
8905
+ const payload = {
8906
+ name,
8907
+ datasetId,
8908
+ context: context13 || {}
8909
+ };
8910
+ const response = await this.client.post(url, payload);
8911
+ const data = response.data;
8912
+ const responseData = data.data || {};
8913
+ const userMessages = responseData.userMessages || [];
8914
+ if (userMessages.length === 0) {
8915
+ console.warn(`${LOG_PREFIX}: No user messages returned from create_run`);
8916
+ return null;
8917
+ }
8918
+ const runId = responseData.id || "";
8919
+ const simulationItems = userMessages.map(
8920
+ (msg) => ({
8921
+ runItemId: msg.testRunItemId || "",
8922
+ message: msg.userMessage || "",
8923
+ turnId: msg.turnId || ""
8924
+ })
8925
+ );
8926
+ return {
8927
+ runId,
8928
+ simulationItems
8929
+ };
8930
+ } catch (error) {
8931
+ const errorMsg = this._extractErrorMessage(error);
8932
+ console.error(`${LOG_PREFIX}: Failed to create simulation run:`, errorMsg);
8933
+ return null;
8934
+ }
8935
+ }
8936
+ /**
8937
+ * Send a conversation turn to the backend and get the next response.
8938
+ */
8939
+ async triggerConversation(message, turnId, sessionId, traceId) {
8940
+ if (!this.client) {
8941
+ console.error(`${LOG_PREFIX}: Client not initialized`);
8942
+ return null;
8943
+ }
8944
+ try {
8945
+ const url = "/evaluations/turn/agent-response";
8946
+ const payload = {
8947
+ turnId,
8948
+ agentResponse: { message },
8949
+ sessionId,
8950
+ traceId
8951
+ };
8952
+ const response = await this.client.post(url, payload);
8953
+ const data = response.data;
8954
+ const responseData = data.data || {};
8955
+ const decision = responseData.decision || "continue";
8956
+ if (decision === "stop") {
8957
+ return {
8958
+ decision,
8959
+ reason: responseData.reason || ""
8960
+ };
8961
+ }
8962
+ const userMessages = responseData.userMessages || [];
8963
+ if (userMessages.length === 0) {
8964
+ console.warn(`${LOG_PREFIX}: No user messages in continue response`);
8965
+ return null;
8966
+ }
8967
+ const nextMsg = userMessages[0];
8968
+ return {
8969
+ decision,
8970
+ nextTurnId: nextMsg.turnId || "",
8971
+ nextUserMessage: nextMsg.userMessage || "",
8972
+ nextRunItemId: nextMsg.testRunItemId || ""
8973
+ };
8974
+ } catch (error) {
8975
+ const errorMsg = this._extractErrorMessage(error);
8976
+ console.error(`${LOG_PREFIX}: Failed to trigger conversation:`, errorMsg);
8977
+ return null;
8978
+ }
8979
+ }
8980
+ /**
8981
+ * Report a task execution failure to the backend.
8982
+ */
8983
+ async reportFailure(runId, runItemId, error) {
8984
+ if (!this.client) {
8985
+ console.error(`${LOG_PREFIX}: Client not initialized`);
8986
+ return;
8987
+ }
8988
+ try {
8989
+ const url = `/evaluations/run/${runId}/item/${runItemId}/status`;
8990
+ const payload = { status: "failed", failureReason: error };
8991
+ await this.client.patch(url, payload);
8992
+ console.info(`${LOG_PREFIX}: Reported failure - ${error}`);
8993
+ } catch (err) {
8994
+ const errorMsg = this._extractErrorMessage(err);
8995
+ console.error(`${LOG_PREFIX}: Failed to report failure:`, errorMsg);
8996
+ }
8997
+ }
8998
+ /**
8999
+ * Submit the run status.
9000
+ */
9001
+ async postRunStatus(runId, status) {
9002
+ if (!this.client) {
9003
+ console.error(
9004
+ `${LOG_PREFIX}: Client not initialized; cannot post run status`
9005
+ );
9006
+ return { success: false };
9007
+ }
9008
+ try {
9009
+ const url = `/evaluations/run/${runId}/status`;
9010
+ const payload = { status };
9011
+ const response = await this.client.post(url, payload);
9012
+ const data = response.data;
9013
+ if (data && typeof data === "object" && "data" in data) {
9014
+ console.info(`${LOG_PREFIX}: Completed test run successfully`);
9015
+ return data.data || {};
9016
+ }
9017
+ return data;
9018
+ } catch (error) {
9019
+ const errorMsg = this._extractErrorMessage(error);
9020
+ console.error(
9021
+ `${LOG_PREFIX}: Failed to post run status for run '${runId}':`,
9022
+ errorMsg
9023
+ );
9024
+ return { success: false };
9025
+ }
9026
+ }
9027
+ /**
9028
+ * Extract error message from response or exception.
9029
+ */
9030
+ _extractErrorMessage(error) {
9031
+ if (axios.isAxiosError(error)) {
9032
+ const axiosError = error;
9033
+ if (axiosError.response?.data) {
9034
+ const responseData = axiosError.response.data;
9035
+ if (typeof responseData === "object" && responseData.error && typeof responseData.error === "object") {
9036
+ return responseData.error.message || error.message;
9037
+ }
9038
+ }
9039
+ return axiosError.message;
9040
+ }
9041
+ return error?.message || String(error);
9042
+ }
9043
+ };
9044
+
9045
+ // src/simulation/task.ts
9046
+ var BaseTask = class {
9047
+ };
9048
+
9049
+ // src/simulation/utils.ts
9050
+ var LOG_PREFIX2 = "netra.simulation";
9051
+ function validateSimulationInputs(datasetId, task2) {
9052
+ if (!datasetId) {
9053
+ console.error(`${LOG_PREFIX2}: dataset_id is required`);
9054
+ return false;
9055
+ }
9056
+ if (!(task2 instanceof BaseTask)) {
9057
+ console.error(`${LOG_PREFIX2}: task must be a BaseTask instance`);
9058
+ return false;
9059
+ }
9060
+ return true;
9061
+ }
9062
+ async function executeTask2(task2, message, sessionId) {
9063
+ const result = task2.run(message, sessionId);
9064
+ const resolvedResult = result instanceof Promise ? await result : result;
9065
+ if (typeof resolvedResult === "object" && resolvedResult !== null && "message" in resolvedResult && "sessionId" in resolvedResult) {
9066
+ return [resolvedResult.message, resolvedResult.sessionId];
9067
+ }
9068
+ throw new Error(
9069
+ `Task must return TaskResult, got ${typeof resolvedResult}`
9070
+ );
9071
+ }
9072
+
9073
+ // src/simulation/api.ts
9074
+ var LOG_PREFIX3 = "netra.simulation";
9075
+ var SPAN_NAME = "Netra.Simulation.TestRun";
9076
+ var Simulation = class {
9077
+ constructor(config2) {
9078
+ this._config = config2;
9079
+ this._client = new SimulationHttpClient(config2);
9080
+ }
9081
+ /**
9082
+ * Run a multi-turn conversation simulation.
9083
+ *
9084
+ * @param options - Simulation configuration options
9085
+ * @returns Dictionary with simulation results, or null on failure
9086
+ */
9087
+ async runSimulation(options) {
9088
+ const {
9089
+ name,
9090
+ datasetId,
9091
+ task: task2,
9092
+ context: context13,
9093
+ maxConcurrency = 5
9094
+ } = options;
9095
+ if (!validateSimulationInputs(datasetId, task2)) {
9096
+ return null;
9097
+ }
9098
+ const startTime = Date.now();
9099
+ const runResult = await this._client.createRun(name, datasetId, context13);
9100
+ if (!runResult) {
9101
+ return null;
9102
+ }
9103
+ const { runId, simulationItems } = runResult;
9104
+ if (!simulationItems || simulationItems.length === 0) {
9105
+ console.error(`${LOG_PREFIX3}: No items returned from create_run`);
9106
+ return null;
9107
+ }
9108
+ let interrupted = false;
9109
+ const proc = typeof process !== "undefined" ? process : void 0;
9110
+ const finalizeFailure = (signal) => {
9111
+ if (interrupted) {
9112
+ return;
9113
+ }
9114
+ interrupted = true;
9115
+ proc?.removeListener("SIGINT", handleSignal);
9116
+ proc?.removeListener("SIGTERM", handleSignal);
9117
+ proc?.removeListener("uncaughtException", handleException);
9118
+ proc?.removeListener("unhandledRejection", handleRejection);
9119
+ void this._client.postRunStatus(runId, "failed").finally(() => {
9120
+ if (signal) {
9121
+ proc?.kill(proc.pid, signal);
9122
+ }
9123
+ });
9124
+ };
9125
+ const handleSignal = (signal) => {
9126
+ finalizeFailure(signal);
9127
+ };
9128
+ const handleException = () => {
9129
+ finalizeFailure();
9130
+ };
9131
+ const handleRejection = () => {
9132
+ finalizeFailure();
9133
+ };
9134
+ if (proc && typeof proc.once === "function") {
9135
+ proc.once("SIGINT", handleSignal);
9136
+ proc.once("SIGTERM", handleSignal);
9137
+ proc.once("uncaughtException", handleException);
9138
+ proc.once("unhandledRejection", handleRejection);
9139
+ }
9140
+ console.info(
9141
+ `${LOG_PREFIX3}: Starting simulation with ${simulationItems.length} items`
9142
+ );
9143
+ try {
9144
+ const result = await this._runSimulationAsync(
9145
+ runId,
9146
+ simulationItems,
9147
+ task2,
9148
+ maxConcurrency
9149
+ );
9150
+ const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
9151
+ console.info(
9152
+ `${LOG_PREFIX3}: Simulation completed in ${elapsedTime} seconds`
9153
+ );
9154
+ return result;
9155
+ } catch (error) {
9156
+ console.error(`${LOG_PREFIX3}: Run simulation failed`);
9157
+ await this._client.postRunStatus(runId, "failed");
9158
+ throw error;
9159
+ } finally {
9160
+ if (proc && typeof proc.removeListener === "function") {
9161
+ proc.removeListener("SIGINT", handleSignal);
9162
+ proc.removeListener("SIGTERM", handleSignal);
9163
+ proc.removeListener("uncaughtException", handleException);
9164
+ proc.removeListener("unhandledRejection", handleRejection);
9165
+ }
9166
+ }
9167
+ }
9168
+ /**
9169
+ * Async implementation of run_simulation with concurrency control.
9170
+ */
9171
+ async _runSimulationAsync(runId, runItems, task2, maxConcurrency) {
9172
+ const results = {
9173
+ success: true,
9174
+ completed: [],
9175
+ failed: [],
9176
+ totalItems: runItems.length
9177
+ };
9178
+ let processedCount = 0;
9179
+ const limit = pLimit(Math.min(5, maxConcurrency));
9180
+ const promises = runItems.map(
9181
+ (runItem) => limit(async () => {
9182
+ const result = await this._executeConversation(runId, runItem, task2);
9183
+ if (result.success) {
9184
+ results.completed.push(result);
9185
+ } else {
9186
+ results.failed.push(result);
9187
+ }
9188
+ processedCount++;
9189
+ console.info(
9190
+ `${LOG_PREFIX3}: ${processedCount}/${runItems.length} processed (run_item_id=${runItem.runItemId})`
9191
+ );
9192
+ return result;
9193
+ })
9194
+ );
9195
+ await Promise.all(promises);
9196
+ console.info(
9197
+ `${LOG_PREFIX3}: Completed=${results.completed.length}, Failed=${results.failed.length}`
9198
+ );
9199
+ await this._client.postRunStatus(runId, "completed");
9200
+ return results;
9201
+ }
9202
+ /**
9203
+ * Execute a multi-turn conversation for a single simulation item.
9204
+ */
9205
+ async _executeConversation(runId, runItem, task2) {
9206
+ const { runItemId, message: initialMessage, turnId: initialTurnId } = runItem;
9207
+ let message = initialMessage;
9208
+ let turnId = initialTurnId;
9209
+ let sessionId = null;
9210
+ while (true) {
9211
+ try {
9212
+ const span2 = new SpanWrapper(SPAN_NAME, {}, LOG_PREFIX3);
9213
+ span2.start();
9214
+ const traceId = span2.getCurrentSpan()?.spanContext().traceId ?? "";
9215
+ const [responseMessage, taskSessionId] = await executeTask2(
9216
+ task2,
9217
+ message,
9218
+ sessionId
9219
+ );
9220
+ if (taskSessionId) {
9221
+ sessionId = taskSessionId;
9222
+ }
9223
+ span2.end();
9224
+ const response = await this._client.triggerConversation(
9225
+ responseMessage,
9226
+ turnId,
9227
+ sessionId || "",
9228
+ traceId
9229
+ );
9230
+ if (response === null) {
9231
+ const errorMsg = "Failed to get conversation response";
9232
+ return {
9233
+ runItemId,
9234
+ success: false,
9235
+ error: errorMsg,
9236
+ turnId
9237
+ };
9238
+ }
9239
+ if (response.decision === "stop") {
9240
+ console.info(
9241
+ `${LOG_PREFIX3}: Completed run_item_id=${runItemId} reason=${response.reason}`
9242
+ );
9243
+ return {
9244
+ runItemId,
9245
+ success: true,
9246
+ finalTurnId: turnId
9247
+ };
9248
+ }
9249
+ message = response.nextUserMessage;
9250
+ turnId = response.nextTurnId;
9251
+ } catch (error) {
9252
+ const errorMsg = error instanceof Error ? error.message : String(error);
9253
+ console.error(
9254
+ `${LOG_PREFIX3}: Task failed run_item_id=${runItemId}, turn_id=${turnId}: ${errorMsg}`
9255
+ );
9256
+ await this._client.reportFailure(runId, runItemId, errorMsg);
9257
+ return {
9258
+ runItemId,
9259
+ success: false,
9260
+ error: errorMsg,
9261
+ turnId
9262
+ };
9263
+ }
9264
+ }
9265
+ }
9266
+ };
8824
9267
  function serializeValue(value) {
8825
9268
  try {
8826
9269
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
@@ -9016,6 +9459,15 @@ function span(targetOrOptions, options) {
9016
9459
  // src/index.ts
9017
9460
  var _rootSpan;
9018
9461
  var Netra = class {
9462
+ /**
9463
+ * Get the current Netra configuration
9464
+ */
9465
+ static getConfig() {
9466
+ if (!this._config) {
9467
+ throw new Error("Netra SDK not initialized. Call Netra.init() first.");
9468
+ }
9469
+ return this._config;
9470
+ }
9019
9471
  /**
9020
9472
  * Check if Netra has been initialized
9021
9473
  */
@@ -9061,6 +9513,7 @@ var Netra = class {
9061
9513
  this.usage = new Usage(cfg);
9062
9514
  this.evaluation = new Evaluation(cfg);
9063
9515
  this.dashboard = new Dashboard(cfg);
9516
+ this.simulation = new Simulation(cfg);
9064
9517
  this._initialized = true;
9065
9518
  console.info("Netra successfully initialized.");
9066
9519
  if (cfg.debugMode) {
@@ -9349,6 +9802,6 @@ Netra._initialized = false;
9349
9802
  Netra.withBlockedSpansLocal = withBlockedSpansLocal;
9350
9803
  var index_default = Netra;
9351
9804
 
9352
- export { Aggregation, ChartType, Config, ConversationType, Dashboard, DimensionField, EntryStatus, Evaluation, FilterField, FilterType, FilteringSpanExporter, GroupBy, InstrumentationSpanProcessor, Measure, Netra, NetraInstruments, Operator, RunEntryContext, RunStatus, Scope, ScrubbingSpanProcessor, SessionSpanProcessor, SpanType, TrialAwareOTLPExporter, Usage, agent, index_default as default, metadataField, mistralAIInstrumentor, span, task, workflow };
9805
+ export { Aggregation, BaseTask, ChartType, Config, ConversationType, Dashboard, DimensionField, EntryStatus, Evaluation, FilterField, FilterType, FilteringSpanExporter, GroupBy, InstrumentationSpanProcessor, Measure, Netra, NetraInstruments, Operator, RunEntryContext, RunStatus, Scope, ScrubbingSpanProcessor, SessionSpanProcessor, Simulation, SpanType, TrialAwareOTLPExporter, Usage, agent, index_default as default, metadataField, mistralAIInstrumentor, span, task, workflow };
9353
9806
  //# sourceMappingURL=index.js.map
9354
9807
  //# sourceMappingURL=index.js.map