Skip to content

Commit

Permalink
feat(sdk): client, annotations (#498)
Browse files Browse the repository at this point in the history
  • Loading branch information
doronkopit5 authored Jan 13, 2025
1 parent 9d0f0c0 commit 6e4192e
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 48 deletions.
13 changes: 12 additions & 1 deletion packages/sample-app/src/sample_decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ traceloop.withAssociationProperties(
const completion = await sampleOpenAI.completion("TypeScript");
console.log(completion);

await traceloop.reportScore({ chat_id: "789" }, 1);
const client = traceloop.getClient();
await client.userFeedback.create({
annotationTask: "sample-annotation-task",
entity: {
id: "12345",
},
tags: {
sentiment: "positive",
score: 0.85,
tones: ["happy", "sad"],
},
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TraceloopClient } from "../traceloop-client";
import { AnnotationCreateOptions } from "../../interfaces/annotations.interface";

export type AnnotationFlow = "user_feedback";

/**
* Base class for handling annotation operations with the Traceloop API.
* @internal
*/
export class BaseAnnotation {
constructor(
protected client: TraceloopClient,
protected flow: AnnotationFlow,
) {}

/**
* Creates a new annotation.
*
* @param options - The annotation creation options
* @returns Promise resolving to the fetch Response
*/
async create(options: AnnotationCreateOptions) {
return await this.client.post(
`/v2/annotation-tasks/${options.annotationTask}/annotations`,
{
entity_instance_id: options.entity.id,
tags: options.tags,
source: "sdk",
flow: this.flow,
actor: {
type: "service",
id: this.client.appName,
},
},
);
}
}
37 changes: 37 additions & 0 deletions packages/traceloop-sdk/src/lib/client/annotation/user-feedback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { BaseAnnotation } from "./base-annotation";
import { TraceloopClient } from "../traceloop-client";
import { AnnotationCreateOptions } from "../../interfaces/annotations.interface";

/**
* Handles user feedback annotations with the Traceloop API.
*/
export class UserFeedback extends BaseAnnotation {
constructor(client: TraceloopClient) {
super(client, "user_feedback");
}

/**
* Creates a new annotation for a specific task and entity.
*
* @param options - The options for creating an annotation
* @returns Promise resolving to the fetch Response
*
* @example
* ```typescript
* await client.annotation.create({
* annotationTask: 'sample-annotation-task',
* entity: {
* id: '123456',
* },
* tags: {
* sentiment: 'positive',
* score: 0.85,
* tones: ['happy', 'surprised']
* }
* });
* ```
*/
override async create(options: AnnotationCreateOptions) {
return await super.create(options);
}
}
51 changes: 51 additions & 0 deletions packages/traceloop-sdk/src/lib/client/traceloop-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { TraceloopClientOptions } from "../interfaces";
import { version } from "../../../package.json";
import { UserFeedback } from "./annotation/user-feedback";

/**
* The main client for interacting with Traceloop's API.
* This client can be used either directly or through the singleton pattern via configuration.
*
* @example
* // Direct usage
* const client = new TraceloopClient('your-api-key');
*
* @example
* // Through configuration (recommended)
* initialize({ apiKey: 'your-api-key', appName: 'your-app' });
* const client = getClient();
*/
export class TraceloopClient {
private version: string = version;
public appName: string;
private baseUrl: string;
private apiKey: string;

/**
* Creates a new instance of the TraceloopClient.
*
* @param options - Configuration options for the client
*/
constructor(options: TraceloopClientOptions) {
this.apiKey = options.apiKey;
this.appName = options.appName;
this.baseUrl =
options.baseUrl ||
process.env.TRACELOOP_BASE_URL ||
"https://api.traceloop.com";
}

userFeedback = new UserFeedback(this);

async post(path: string, body: Record<string, unknown>) {
return await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
"X-Traceloop-SDK-Version": this.version,
},
body: JSON.stringify(body),
});
}
}
45 changes: 44 additions & 1 deletion packages/traceloop-sdk/src/lib/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,26 @@ import { validateConfiguration } from "./validation";
import { startTracing } from "../tracing";
import { initializeRegistry } from "../prompts/registry";
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
import { TraceloopClient } from "../client/traceloop-client";

export let _configuration: InitializeOptions | undefined;
let _client: TraceloopClient | undefined;

/**
* Initializes the Traceloop SDK.
* Initializes the Traceloop SDK and creates a singleton client instance if API key is provided.
* Must be called once before any other SDK methods.
*
* @param options - The options to initialize the SDK. See the {@link InitializeOptions} for details.
* @returns TraceloopClient - The singleton client instance if API key is provided, otherwise undefined.
* @throws {InitializationError} if the configuration is invalid or if failed to fetch feature data.
*
* @example
* ```typescript
* initialize({
* apiKey: 'your-api-key',
* appName: 'your-app',
* });
* ```
*/
export const initialize = (options: InitializeOptions) => {
if (_configuration) {
Expand Down Expand Up @@ -77,6 +88,15 @@ export const initialize = (options: InitializeOptions) => {

startTracing(_configuration);
initializeRegistry(_configuration);
if (options.apiKey) {
_client = new TraceloopClient({
apiKey: options.apiKey,
baseUrl: options.baseUrl,
appName: options.appName!,
});
return _client;
}
return;
};

const logLevelToOtelLogLevel = (
Expand All @@ -93,3 +113,26 @@ const logLevelToOtelLogLevel = (
return DiagLogLevel.ERROR;
}
};

/**
* Gets the singleton instance of the TraceloopClient.
* The SDK must be initialized with an API key before calling this function.
*
* @returns The TraceloopClient singleton instance
* @throws {Error} if the SDK hasn't been initialized or was initialized without an API key
*
* @example
* ```typescript
* const client = getClient();
* await client.annotation.create({ annotationTask: 'taskId', entityInstanceId: 'entityId', tags: { score: 0.9 } });
* ```
*/
export const getClient = (): TraceloopClient => {
if (!_client) {
throw new Error(
"Traceloop must be initialized before getting client, Call initialize() first." +
"If you already called initialize(), make sure you have an api key.",
);
}
return _client;
};
38 changes: 38 additions & 0 deletions packages/traceloop-sdk/src/lib/interfaces/annotations.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Represents an entity in the system that can be annotated.
* An entity is typically a unit of content or interaction that needs to be tracked or evaluated.
* It is reported as an association property before the annotation is created.
*/
export interface Entity {
/**
* Unique identifier for the entity.
* This could be a user ID, conversation ID, or any other unique identifier in your system.
*/
id: string;
}

/**
* Configuration options for creating a new annotation.
* Annotations are used to attach metadata, feedback, or evaluation results to specific entities.
*/
export interface AnnotationCreateOptions {
/**
* The identifier of the annotation task.
* The ID or slug of the annotation task, Can be found at app.traceloop.com/annotation_tasks/:annotationTaskId */
annotationTask: string;

/**
* The entity to be annotated.
* Contains the entity's identifier and optional type information.
* Be sure to report the entity instance ID as the association property before
* in order to correctly correlate the annotation to the relevant context.
*/
entity: Entity;

/**
* Key-value pairs of annotation data, should match the tags defined in the annotation task
*/
tags: Record<string, TagValue>;
}

export type TagValue = string | number | string[];
8 changes: 8 additions & 0 deletions packages/traceloop-sdk/src/lib/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export * from "./initialize-options.interface";
export * from "./prompts.interface";
export * from "./annotations.interface";
export * from "./traceloop-client.interface";

export interface TraceloopClientOptions {
apiKey: string;
appName: string;
baseUrl?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface TraceloopClientOptions {
apiKey: string;
appName: string;
baseUrl?: string;
}
10 changes: 7 additions & 3 deletions packages/traceloop-sdk/src/lib/node-server-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { initInstrumentations } from "./tracing";

export * from "./errors";
export { InitializeOptions } from "./interfaces";
export { initialize } from "./configuration";
export {
InitializeOptions,
TraceloopClientOptions,
AnnotationCreateOptions,
} from "./interfaces";
export { TraceloopClient } from "./client/traceloop-client";
export { initialize, getClient } from "./configuration";
export { forceFlush } from "./tracing";
export * from "./tracing/decorators";
export * from "./tracing/manual";
export * from "./tracing/association";
export * from "./tracing/score";
export * from "./tracing/custom-metric";
export * from "./prompts";

Expand Down
43 changes: 0 additions & 43 deletions packages/traceloop-sdk/src/lib/tracing/score.ts

This file was deleted.

Loading

0 comments on commit 6e4192e

Please sign in to comment.