Skip to content

Commit 6e4192e

Browse files
authored
feat(sdk): client, annotations (#498)
1 parent 9d0f0c0 commit 6e4192e

File tree

11 files changed

+365
-48
lines changed

11 files changed

+365
-48
lines changed

packages/sample-app/src/sample_decorators.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ traceloop.withAssociationProperties(
4747
const completion = await sampleOpenAI.completion("TypeScript");
4848
console.log(completion);
4949

50-
await traceloop.reportScore({ chat_id: "789" }, 1);
50+
const client = traceloop.getClient();
51+
await client.userFeedback.create({
52+
annotationTask: "sample-annotation-task",
53+
entity: {
54+
id: "12345",
55+
},
56+
tags: {
57+
sentiment: "positive",
58+
score: 0.85,
59+
tones: ["happy", "sad"],
60+
},
61+
});
5162
},
5263
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { TraceloopClient } from "../traceloop-client";
2+
import { AnnotationCreateOptions } from "../../interfaces/annotations.interface";
3+
4+
export type AnnotationFlow = "user_feedback";
5+
6+
/**
7+
* Base class for handling annotation operations with the Traceloop API.
8+
* @internal
9+
*/
10+
export class BaseAnnotation {
11+
constructor(
12+
protected client: TraceloopClient,
13+
protected flow: AnnotationFlow,
14+
) {}
15+
16+
/**
17+
* Creates a new annotation.
18+
*
19+
* @param options - The annotation creation options
20+
* @returns Promise resolving to the fetch Response
21+
*/
22+
async create(options: AnnotationCreateOptions) {
23+
return await this.client.post(
24+
`/v2/annotation-tasks/${options.annotationTask}/annotations`,
25+
{
26+
entity_instance_id: options.entity.id,
27+
tags: options.tags,
28+
source: "sdk",
29+
flow: this.flow,
30+
actor: {
31+
type: "service",
32+
id: this.client.appName,
33+
},
34+
},
35+
);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { BaseAnnotation } from "./base-annotation";
2+
import { TraceloopClient } from "../traceloop-client";
3+
import { AnnotationCreateOptions } from "../../interfaces/annotations.interface";
4+
5+
/**
6+
* Handles user feedback annotations with the Traceloop API.
7+
*/
8+
export class UserFeedback extends BaseAnnotation {
9+
constructor(client: TraceloopClient) {
10+
super(client, "user_feedback");
11+
}
12+
13+
/**
14+
* Creates a new annotation for a specific task and entity.
15+
*
16+
* @param options - The options for creating an annotation
17+
* @returns Promise resolving to the fetch Response
18+
*
19+
* @example
20+
* ```typescript
21+
* await client.annotation.create({
22+
* annotationTask: 'sample-annotation-task',
23+
* entity: {
24+
* id: '123456',
25+
* },
26+
* tags: {
27+
* sentiment: 'positive',
28+
* score: 0.85,
29+
* tones: ['happy', 'surprised']
30+
* }
31+
* });
32+
* ```
33+
*/
34+
override async create(options: AnnotationCreateOptions) {
35+
return await super.create(options);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { TraceloopClientOptions } from "../interfaces";
2+
import { version } from "../../../package.json";
3+
import { UserFeedback } from "./annotation/user-feedback";
4+
5+
/**
6+
* The main client for interacting with Traceloop's API.
7+
* This client can be used either directly or through the singleton pattern via configuration.
8+
*
9+
* @example
10+
* // Direct usage
11+
* const client = new TraceloopClient('your-api-key');
12+
*
13+
* @example
14+
* // Through configuration (recommended)
15+
* initialize({ apiKey: 'your-api-key', appName: 'your-app' });
16+
* const client = getClient();
17+
*/
18+
export class TraceloopClient {
19+
private version: string = version;
20+
public appName: string;
21+
private baseUrl: string;
22+
private apiKey: string;
23+
24+
/**
25+
* Creates a new instance of the TraceloopClient.
26+
*
27+
* @param options - Configuration options for the client
28+
*/
29+
constructor(options: TraceloopClientOptions) {
30+
this.apiKey = options.apiKey;
31+
this.appName = options.appName;
32+
this.baseUrl =
33+
options.baseUrl ||
34+
process.env.TRACELOOP_BASE_URL ||
35+
"https://api.traceloop.com";
36+
}
37+
38+
userFeedback = new UserFeedback(this);
39+
40+
async post(path: string, body: Record<string, unknown>) {
41+
return await fetch(`${this.baseUrl}${path}`, {
42+
method: "POST",
43+
headers: {
44+
"Content-Type": "application/json",
45+
Authorization: `Bearer ${this.apiKey}`,
46+
"X-Traceloop-SDK-Version": this.version,
47+
},
48+
body: JSON.stringify(body),
49+
});
50+
}
51+
}

packages/traceloop-sdk/src/lib/configuration/index.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@ import { validateConfiguration } from "./validation";
33
import { startTracing } from "../tracing";
44
import { initializeRegistry } from "../prompts/registry";
55
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
6+
import { TraceloopClient } from "../client/traceloop-client";
67

78
export let _configuration: InitializeOptions | undefined;
9+
let _client: TraceloopClient | undefined;
810

911
/**
10-
* Initializes the Traceloop SDK.
12+
* Initializes the Traceloop SDK and creates a singleton client instance if API key is provided.
1113
* Must be called once before any other SDK methods.
1214
*
1315
* @param options - The options to initialize the SDK. See the {@link InitializeOptions} for details.
16+
* @returns TraceloopClient - The singleton client instance if API key is provided, otherwise undefined.
1417
* @throws {InitializationError} if the configuration is invalid or if failed to fetch feature data.
18+
*
19+
* @example
20+
* ```typescript
21+
* initialize({
22+
* apiKey: 'your-api-key',
23+
* appName: 'your-app',
24+
* });
25+
* ```
1526
*/
1627
export const initialize = (options: InitializeOptions) => {
1728
if (_configuration) {
@@ -77,6 +88,15 @@ export const initialize = (options: InitializeOptions) => {
7788

7889
startTracing(_configuration);
7990
initializeRegistry(_configuration);
91+
if (options.apiKey) {
92+
_client = new TraceloopClient({
93+
apiKey: options.apiKey,
94+
baseUrl: options.baseUrl,
95+
appName: options.appName!,
96+
});
97+
return _client;
98+
}
99+
return;
80100
};
81101

82102
const logLevelToOtelLogLevel = (
@@ -93,3 +113,26 @@ const logLevelToOtelLogLevel = (
93113
return DiagLogLevel.ERROR;
94114
}
95115
};
116+
117+
/**
118+
* Gets the singleton instance of the TraceloopClient.
119+
* The SDK must be initialized with an API key before calling this function.
120+
*
121+
* @returns The TraceloopClient singleton instance
122+
* @throws {Error} if the SDK hasn't been initialized or was initialized without an API key
123+
*
124+
* @example
125+
* ```typescript
126+
* const client = getClient();
127+
* await client.annotation.create({ annotationTask: 'taskId', entityInstanceId: 'entityId', tags: { score: 0.9 } });
128+
* ```
129+
*/
130+
export const getClient = (): TraceloopClient => {
131+
if (!_client) {
132+
throw new Error(
133+
"Traceloop must be initialized before getting client, Call initialize() first." +
134+
"If you already called initialize(), make sure you have an api key.",
135+
);
136+
}
137+
return _client;
138+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Represents an entity in the system that can be annotated.
3+
* An entity is typically a unit of content or interaction that needs to be tracked or evaluated.
4+
* It is reported as an association property before the annotation is created.
5+
*/
6+
export interface Entity {
7+
/**
8+
* Unique identifier for the entity.
9+
* This could be a user ID, conversation ID, or any other unique identifier in your system.
10+
*/
11+
id: string;
12+
}
13+
14+
/**
15+
* Configuration options for creating a new annotation.
16+
* Annotations are used to attach metadata, feedback, or evaluation results to specific entities.
17+
*/
18+
export interface AnnotationCreateOptions {
19+
/**
20+
* The identifier of the annotation task.
21+
* The ID or slug of the annotation task, Can be found at app.traceloop.com/annotation_tasks/:annotationTaskId */
22+
annotationTask: string;
23+
24+
/**
25+
* The entity to be annotated.
26+
* Contains the entity's identifier and optional type information.
27+
* Be sure to report the entity instance ID as the association property before
28+
* in order to correctly correlate the annotation to the relevant context.
29+
*/
30+
entity: Entity;
31+
32+
/**
33+
* Key-value pairs of annotation data, should match the tags defined in the annotation task
34+
*/
35+
tags: Record<string, TagValue>;
36+
}
37+
38+
export type TagValue = string | number | string[];
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
export * from "./initialize-options.interface";
22
export * from "./prompts.interface";
3+
export * from "./annotations.interface";
4+
export * from "./traceloop-client.interface";
5+
6+
export interface TraceloopClientOptions {
7+
apiKey: string;
8+
appName: string;
9+
baseUrl?: string;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface TraceloopClientOptions {
2+
apiKey: string;
3+
appName: string;
4+
baseUrl?: string;
5+
}

packages/traceloop-sdk/src/lib/node-server-sdk.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { initInstrumentations } from "./tracing";
22

33
export * from "./errors";
4-
export { InitializeOptions } from "./interfaces";
5-
export { initialize } from "./configuration";
4+
export {
5+
InitializeOptions,
6+
TraceloopClientOptions,
7+
AnnotationCreateOptions,
8+
} from "./interfaces";
9+
export { TraceloopClient } from "./client/traceloop-client";
10+
export { initialize, getClient } from "./configuration";
611
export { forceFlush } from "./tracing";
712
export * from "./tracing/decorators";
813
export * from "./tracing/manual";
914
export * from "./tracing/association";
10-
export * from "./tracing/score";
1115
export * from "./tracing/custom-metric";
1216
export * from "./prompts";
1317

packages/traceloop-sdk/src/lib/tracing/score.ts

-43
This file was deleted.

0 commit comments

Comments
 (0)