Skip to content
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

[JS] Anno crud #1010

Merged
merged 9 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
TracerSession,
TracerSessionResult,
ValueType,
AnnotationQueue,
RunWithAnnotationQueueInfo,
} from "./schemas.js";
import {
convertLangChainMessageToExample,
Expand Down Expand Up @@ -1289,7 +1291,7 @@
treeFilter?: string;
isRoot?: boolean;
dataSourceType?: string;
}): Promise<any> {

Check warning on line 1294 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
let projectIds_ = projectIds || [];
if (projectNames) {
projectIds_ = [
Expand Down Expand Up @@ -1577,7 +1579,7 @@
`Failed to list shared examples: ${response.status} ${response.statusText}`
);
}
return result.map((example: any) => ({

Check warning on line 1582 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
...example,
_hostUrl: this.getHostUrl(),
}));
Expand Down Expand Up @@ -2720,7 +2722,7 @@
}

const feedbackResult = await evaluator.evaluateRun(run_, referenceExample);
const [_, feedbacks] = await this._logEvaluationFeedback(

Check warning on line 2725 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

'_' is assigned a value but never used
feedbackResult,
run_,
sourceInfo
Expand Down Expand Up @@ -3054,7 +3056,7 @@
async _logEvaluationFeedback(
evaluatorResponse: EvaluationResult | EvaluationResults,
run?: Run,
sourceInfo?: { [key: string]: any }

Check warning on line 3059 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
): Promise<[results: EvaluationResult[], feedbacks: Feedback[]]> {
const evalResults: Array<EvaluationResult> =
this._selectEvalResults(evaluatorResponse);
Expand Down Expand Up @@ -3093,7 +3095,7 @@
public async logEvaluationFeedback(
evaluatorResponse: EvaluationResult | EvaluationResults,
run?: Run,
sourceInfo?: { [key: string]: any }

Check warning on line 3098 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
): Promise<EvaluationResult[]> {
const [results] = await this._logEvaluationFeedback(
evaluatorResponse,
Expand All @@ -3103,6 +3105,217 @@
return results;
}

/**
* API for managing annotation queues
*/

/**
* List the annotation queues on the LangSmith API.
* @param options - The options for listing annotation queues
* @param options.queueIds - The IDs of the queues to filter by
* @param options.name - The name of the queue to filter by
* @param options.nameContains - The substring that the queue name should contain
* @param options.limit - The maximum number of queues to return
* @returns An iterator of AnnotationQueue objects
*/
public async *listAnnotationQueues(
options: {
queueIds?: string[];
name?: string;
nameContains?: string;
limit?: number;
} = {}
): AsyncIterableIterator<AnnotationQueue> {
const { queueIds, name, nameContains, limit } = options;
const params: Record<string, any> = {

Check warning on line 3130 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
ids: queueIds?.map((id, i) => assertUuid(id, `queueIds[${i}]`)),
name,
name_contains: nameContains,
limit: limit !== undefined ? Math.min(limit, 100) : 100,
};

let count = 0;
for await (const queue of this._getPaginated(
"/annotation-queues",
params as URLSearchParams
)) {
yield queue as unknown as AnnotationQueue;
count++;
if (limit !== undefined && count >= limit) break;
}
}

/**
* Create an annotation queue on the LangSmith API.
* @param options - The options for creating an annotation queue
* @param options.name - The name of the annotation queue
* @param options.description - The description of the annotation queue
* @param options.queueId - The ID of the annotation queue
* @returns The created AnnotationQueue object
*/
public async createAnnotationQueue(options: {
name: string;
description?: string;
queueId?: string;
}): Promise<AnnotationQueue> {
const { name, description, queueId } = options;
const body = {
name,
description,
id: queueId || uuid.v4(),
};

const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/annotation-queues`,
{
method: "POST",
headers: { ...this.headers, "Content-Type": "application/json" },
body: JSON.stringify(
Object.fromEntries(
Object.entries(body).filter(([_, v]) => v !== undefined)
)
),
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
}
);
await raiseForStatus(response, "create annotation queue");
const data = await response.json();
return data as AnnotationQueue;
}

/**
* Read an annotation queue with the specified queue ID.
* @param queueId - The ID of the annotation queue to read
* @returns The AnnotationQueue object
*/
public async readAnnotationQueue(queueId: string): Promise<AnnotationQueue> {
// TODO: Replace when actual endpoint is added
const queue = await this.listAnnotationQueues({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming is a bit funny here - getAnnotationQueue maybe?

readAnnotationQueue sounds like it should just return the items in the queue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya.... I think for sake of consistency I'm gonna keep it though since we use read elsewhere

queueIds: [queueId],
}).next();
if (queue.done) {
throw new Error(`Annotation queue with ID ${queueId} not found`);
}
return queue.value;
}

/**
* Update an annotation queue with the specified queue ID.
* @param queueId - The ID of the annotation queue to update
* @param options - The options for updating the annotation queue
* @param options.name - The new name for the annotation queue
* @param options.description - The new description for the annotation queue
*/
public async updateAnnotationQueue(
queueId: string,
options: {
name: string;
description?: string;
}
): Promise<void> {
const { name, description } = options;
const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`,
{
method: "PATCH",
headers: { ...this.headers, "Content-Type": "application/json" },
body: JSON.stringify({ name, description }),
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
}
);
await raiseForStatus(response, "update annotation queue");
}

/**
* Delete an annotation queue with the specified queue ID.
* @param queueId - The ID of the annotation queue to delete
*/
public async deleteAnnotationQueue(queueId: string): Promise<void> {
const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`,
{
method: "DELETE",
headers: { ...this.headers, Accept: "application/json" },
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
}
);
await raiseForStatus(response, "delete annotation queue");
}

/**
* Add runs to an annotation queue with the specified queue ID.
* @param queueId - The ID of the annotation queue
* @param runIds - The IDs of the runs to be added to the annotation queue
*/
public async addRunsToAnnotationQueue(
queueId: string,
runIds: string[]
): Promise<void> {
const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/runs`,
{
method: "POST",
headers: { ...this.headers, "Content-Type": "application/json" },
body: JSON.stringify(
runIds.map((id, i) => assertUuid(id, `runIds[${i}]`).toString())
),
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
}
);
await raiseForStatus(response, "add runs to annotation queue");
}

/**
* List runs from an annotation queue with the specified queue ID.
* @param queueId - The ID of the annotation queue
* @param limit - The maximum number of runs to return
* @returns An iterator of RunWithAnnotationQueueInfo objects
*/
public async *listRunsFromAnnotationQueue(
queueId: string,
limit?: number
): AsyncIterableIterator<RunWithAnnotationQueueInfo> {
const path = `/annotation-queues/${assertUuid(queueId, "queueId")}/runs`;
const limit_ = limit !== undefined ? Math.min(limit, 100) : 100;

let offset = 0;
let count = 0;
while (true) {
const url = `${this.apiUrl}${path}?offset=${offset}&limit=${limit_}`;
const response = await this.caller.call(_getFetchImplementation(), url, {
method: "GET",
headers: this.headers,
signal: AbortSignal.timeout(this.timeout_ms),
...this.fetchOptions,
});
await raiseForStatus(response, `Failed to fetch ${path}`);
const data = await response.json();
const runs = data.runs as RunWithAnnotationQueueInfo[];

if (runs.length === 0) {
break;
}

for (const run of runs) {
yield run;
count++;
if (limit !== undefined && count >= limit) {
return;
}
}

offset += runs.length;
}
}

protected async _currentTenantIsOwner(owner: string): Promise<boolean> {
const settings = await this._getSettings();
return owner == "-" || settings.tenant_handle === owner;
Expand Down Expand Up @@ -3159,7 +3372,7 @@
promptIdentifier: string,
like: boolean
): Promise<LikePromptResponse> {
const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);

Check warning on line 3375 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

'_' is assigned a value but never used
const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/likes/${owner}/${promptName}`,
Expand Down Expand Up @@ -3264,7 +3477,7 @@
}

public async getPrompt(promptIdentifier: string): Promise<Prompt | null> {
const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);

Check warning on line 3480 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

'_' is assigned a value but never used
const response = await this.caller.call(
_getFetchImplementation(),
`${this.apiUrl}/repos/${owner}/${promptName}`,
Expand Down Expand Up @@ -3308,7 +3521,7 @@
);
}

const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);

Check warning on line 3524 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

'_' is assigned a value but never used
if (!(await this._currentTenantIsOwner(owner))) {
throw await this._ownerConflictError("create a prompt", owner);
}
Expand Down Expand Up @@ -3341,7 +3554,7 @@

public async createCommit(
promptIdentifier: string,
object: any,

Check warning on line 3557 in js/src/client.ts

View workflow job for this annotation

GitHub Actions / Check linting

Unexpected any. Specify a different type
options?: {
parentCommitHash?: string;
}
Expand Down
28 changes: 28 additions & 0 deletions js/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,31 @@ export interface LangSmithSettings {
created_at: string;
tenant_handle?: string;
}

export interface AnnotationQueue {
/** The unique identifier of the annotation queue. */
id: string;

/** The name of the annotation queue. */
name: string;

/** An optional description of the annotation queue. */
description?: string;

/** The timestamp when the annotation queue was created. */
created_at: string;

/** The timestamp when the annotation queue was last updated. */
updated_at: string;

/** The ID of the tenant associated with the annotation queue. */
tenant_id: string;
}

export interface RunWithAnnotationQueueInfo extends BaseRun {
/** The last time this run was reviewed. */
last_reviewed_time?: string;

/** The time this run was added to the queue. */
added_at?: string;
}
9 changes: 7 additions & 2 deletions js/src/utils/_uuid.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import * as uuid from "uuid";

export function assertUuid(str: string): void {
export function assertUuid(str: string, which?: string): string {
if (!uuid.validate(str)) {
throw new Error(`Invalid UUID: ${str}`);
const msg =
which !== undefined
? `Invalid UUID for ${which}: ${str}`
: `Invalid UUID: ${str}`;
throw new Error(msg);
}
return str;
}
Loading