Skip to content

feat(sdk): client, annotations #498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 13, 2025
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
Loading