Skip to content

Commit b01be87

Browse files
authored
feat: support rubric_instructions for annotation queues in the SDK (#1705)
Fixes LS-3628
1 parent 5a37ad3 commit b01be87

File tree

10 files changed

+235
-23
lines changed

10 files changed

+235
-23
lines changed

js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "langsmith",
3-
"version": "0.3.22",
3+
"version": "0.3.23",
44
"description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
55
"packageManager": "[email protected]",
66
"files": [

js/src/client.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
AttachmentInfo,
4242
AttachmentData,
4343
DatasetVersion,
44+
AnnotationQueueWithDetails,
4445
} from "./schemas.js";
4546
import {
4647
convertLangChainMessageToExample,
@@ -3801,12 +3802,14 @@ export class Client implements LangSmithTracingClientInterface {
38013802
name: string;
38023803
description?: string;
38033804
queueId?: string;
3804-
}): Promise<AnnotationQueue> {
3805-
const { name, description, queueId } = options;
3805+
rubricInstructions?: string;
3806+
}): Promise<AnnotationQueueWithDetails> {
3807+
const { name, description, queueId, rubricInstructions } = options;
38063808
const body = {
38073809
name,
38083810
description,
38093811
id: queueId || uuid.v4(),
3812+
rubric_instructions: rubricInstructions,
38103813
};
38113814

38123815
const response = await this.caller.call(
@@ -3832,17 +3835,24 @@ export class Client implements LangSmithTracingClientInterface {
38323835
/**
38333836
* Read an annotation queue with the specified queue ID.
38343837
* @param queueId - The ID of the annotation queue to read
3835-
* @returns The AnnotationQueue object
3838+
* @returns The AnnotationQueueWithDetails object
38363839
*/
3837-
public async readAnnotationQueue(queueId: string): Promise<AnnotationQueue> {
3838-
// TODO: Replace when actual endpoint is added
3839-
const queueIteratorResult = await this.listAnnotationQueues({
3840-
queueIds: [queueId],
3841-
}).next();
3842-
if (queueIteratorResult.done) {
3843-
throw new Error(`Annotation queue with ID ${queueId} not found`);
3844-
}
3845-
return queueIteratorResult.value;
3840+
public async readAnnotationQueue(
3841+
queueId: string
3842+
): Promise<AnnotationQueueWithDetails> {
3843+
const response = await this.caller.call(
3844+
_getFetchImplementation(this.debug),
3845+
`${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`,
3846+
{
3847+
method: "GET",
3848+
headers: this.headers,
3849+
signal: AbortSignal.timeout(this.timeout_ms),
3850+
...this.fetchOptions,
3851+
}
3852+
);
3853+
await raiseForStatus(response, "read annotation queue");
3854+
const data = await response.json();
3855+
return data as AnnotationQueueWithDetails;
38463856
}
38473857

38483858
/**
@@ -3857,16 +3867,21 @@ export class Client implements LangSmithTracingClientInterface {
38573867
options: {
38583868
name: string;
38593869
description?: string;
3870+
rubricInstructions?: string;
38603871
}
38613872
): Promise<void> {
3862-
const { name, description } = options;
3873+
const { name, description, rubricInstructions } = options;
38633874
const response = await this.caller.call(
38643875
_getFetchImplementation(this.debug),
38653876
`${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`,
38663877
{
38673878
method: "PATCH",
38683879
headers: { ...this.headers, "Content-Type": "application/json" },
3869-
body: JSON.stringify({ name, description }),
3880+
body: JSON.stringify({
3881+
name,
3882+
description,
3883+
rubric_instructions: rubricInstructions,
3884+
}),
38703885
signal: AbortSignal.timeout(this.timeout_ms),
38713886
...this.fetchOptions,
38723887
}

js/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export { RunTree, type RunTreeConfig } from "./run_trees.js";
1818
export { overrideFetchImplementation } from "./singletons/fetch.js";
1919

2020
// Update using yarn bump-version
21-
export const __version__ = "0.3.22";
21+
export const __version__ = "0.3.23";

js/src/schemas.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,11 @@ export interface AnnotationQueue {
567567
tenant_id: string;
568568
}
569569

570+
export interface AnnotationQueueWithDetails extends AnnotationQueue {
571+
/** The rubric instructions for the annotation queue. */
572+
rubric_instructions?: string;
573+
}
574+
570575
export interface RunWithAnnotationQueueInfo extends BaseRun {
571576
/** The last time this run was reviewed. */
572577
last_reviewed_time?: string;

js/src/tests/client.int.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,92 @@ test("annotationqueue crud", async () => {
12571257
}
12581258
});
12591259

1260+
test("annotationqueue crud with rubric instructions", async () => {
1261+
const client = new Client();
1262+
const queueName = `test-queue-${uuidv4().substring(0, 8)}`;
1263+
const projectName = `test-project-${uuidv4().substring(0, 8)}`;
1264+
const queueId = uuidv4();
1265+
1266+
try {
1267+
// 1. Create an annotation queue
1268+
const queue = await client.createAnnotationQueue({
1269+
name: queueName,
1270+
description: "Initial description",
1271+
queueId,
1272+
rubricInstructions: "This is a rubric instruction",
1273+
});
1274+
expect(queue).toBeDefined();
1275+
expect(queue.name).toBe(queueName);
1276+
1277+
// 1a. Get the annotation queue
1278+
const fetchedQueue = await client.readAnnotationQueue(queue.id);
1279+
expect(fetchedQueue).toBeDefined();
1280+
expect(fetchedQueue.name).toBe(queueName);
1281+
expect(fetchedQueue.rubric_instructions).toBe(
1282+
"This is a rubric instruction"
1283+
);
1284+
1285+
// 1b. Update the annotation queue rubric instructions
1286+
const newInstructions = "Updated rubric instructions";
1287+
await client.updateAnnotationQueue(queue.id, {
1288+
name: queueName,
1289+
rubricInstructions: newInstructions,
1290+
});
1291+
const updatedQueue = await client.readAnnotationQueue(queue.id);
1292+
expect(updatedQueue.rubric_instructions).toBe(newInstructions);
1293+
} finally {
1294+
// 6. Delete the annotation queue
1295+
await client.deleteAnnotationQueue(queueId);
1296+
1297+
// Clean up the project
1298+
if (await client.hasProject({ projectName })) {
1299+
await client.deleteProject({ projectName });
1300+
}
1301+
}
1302+
});
1303+
1304+
test("annotationqueue crud with rubric instructions 2", async () => {
1305+
const client = new Client();
1306+
const queueName = `test-queue-${uuidv4().substring(0, 8)}`;
1307+
const projectName = `test-project-${uuidv4().substring(0, 8)}`;
1308+
const queueId = uuidv4();
1309+
1310+
try {
1311+
// 1. Create an annotation queue
1312+
const queue = await client.createAnnotationQueue({
1313+
name: queueName,
1314+
description: "Initial description",
1315+
queueId,
1316+
});
1317+
expect(queue).toBeDefined();
1318+
expect(queue.name).toBe(queueName);
1319+
expect(queue.rubric_instructions).toBeUndefined();
1320+
1321+
// 1a. Get the annotation queue
1322+
const fetchedQueue = await client.readAnnotationQueue(queue.id);
1323+
expect(fetchedQueue).toBeDefined();
1324+
expect(fetchedQueue.name).toBe(queueName);
1325+
expect(fetchedQueue.rubric_instructions).toBeNull();
1326+
1327+
// 1b. Update the annotation queue rubric instructions
1328+
const newInstructions = "Updated rubric instructions";
1329+
await client.updateAnnotationQueue(queue.id, {
1330+
name: queueName,
1331+
rubricInstructions: newInstructions,
1332+
});
1333+
const updatedQueue = await client.readAnnotationQueue(queue.id);
1334+
expect(updatedQueue.rubric_instructions).toBe(newInstructions);
1335+
} finally {
1336+
// 6. Delete the annotation queue
1337+
await client.deleteAnnotationQueue(queueId);
1338+
1339+
// Clean up the project
1340+
if (await client.hasProject({ projectName })) {
1341+
await client.deleteProject({ projectName });
1342+
}
1343+
}
1344+
});
1345+
12601346
test("upload examples multipart", async () => {
12611347
const client = new Client();
12621348
const datasetName = `__test_upload_examples_multipart${uuidv4().slice(0, 4)}`;

python/langsmith/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from langsmith.utils import ContextThreadPoolExecutor
2121

2222
# Avoid calling into importlib on every call to __version__
23-
__version__ = "0.3.38"
23+
__version__ = "0.3.39"
2424
version = __version__ # for backwards compatibility
2525

2626

python/langsmith/client.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6259,7 +6259,8 @@ def create_annotation_queue(
62596259
name: str,
62606260
description: Optional[str] = None,
62616261
queue_id: Optional[ID_TYPE] = None,
6262-
) -> ls_schemas.AnnotationQueue:
6262+
rubric_instructions: Optional[str] = None,
6263+
) -> ls_schemas.AnnotationQueueWithDetails:
62636264
"""Create an annotation queue on the LangSmith API.
62646265
62656266
Args:
@@ -6269,6 +6270,8 @@ def create_annotation_queue(
62696270
The description of the annotation queue.
62706271
queue_id (Optional[Union[UUID, str]]):
62716272
The ID of the annotation queue.
6273+
rubric_instructions (Optional[str]):
6274+
The rubric instructions for the annotation queue.
62726275
62736276
Returns:
62746277
AnnotationQueue: The created annotation queue object.
@@ -6277,14 +6280,15 @@ def create_annotation_queue(
62776280
"name": name,
62786281
"description": description,
62796282
"id": str(queue_id) if queue_id is not None else str(uuid.uuid4()),
6283+
"rubric_instructions": rubric_instructions,
62806284
}
62816285
response = self.request_with_retries(
62826286
"POST",
62836287
"/annotation-queues",
62846288
json={k: v for k, v in body.items() if v is not None},
62856289
)
62866290
ls_utils.raise_for_status_with_text(response)
6287-
return ls_schemas.AnnotationQueue(
6291+
return ls_schemas.AnnotationQueueWithDetails(
62886292
**response.json(),
62896293
)
62906294

@@ -6297,11 +6301,22 @@ def read_annotation_queue(self, queue_id: ID_TYPE) -> ls_schemas.AnnotationQueue
62976301
Returns:
62986302
AnnotationQueue: The annotation queue object.
62996303
"""
6300-
# TODO: Replace when actual endpoint is added
6301-
return next(self.list_annotation_queues(queue_ids=[queue_id]))
6304+
base_url = f"/annotation-queues/{_as_uuid(queue_id, 'queue_id')}"
6305+
response = self.request_with_retries(
6306+
"GET",
6307+
f"{base_url}",
6308+
headers=self._headers,
6309+
)
6310+
ls_utils.raise_for_status_with_text(response)
6311+
return ls_schemas.AnnotationQueueWithDetails(**response.json())
63026312

63036313
def update_annotation_queue(
6304-
self, queue_id: ID_TYPE, *, name: str, description: Optional[str] = None
6314+
self,
6315+
queue_id: ID_TYPE,
6316+
*,
6317+
name: str,
6318+
description: Optional[str] = None,
6319+
rubric_instructions: Optional[str] = None,
63056320
) -> None:
63066321
"""Update an annotation queue with the specified queue_id.
63076322
@@ -6310,6 +6325,8 @@ def update_annotation_queue(
63106325
name (str): The new name for the annotation queue.
63116326
description (Optional[str]): The new description for the
63126327
annotation queue. Defaults to None.
6328+
rubric_instructions (Optional[str]): The new rubric instructions for the
6329+
annotation queue. Defaults to None.
63136330
63146331
Returns:
63156332
None
@@ -6320,6 +6337,7 @@ def update_annotation_queue(
63206337
json={
63216338
"name": name,
63226339
"description": description,
6340+
"rubric_instructions": rubric_instructions,
63236341
},
63246342
)
63256343
ls_utils.raise_for_status_with_text(response)

python/langsmith/schemas.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,13 @@ class AnnotationQueue(BaseModel):
790790
"""The ID of the tenant associated with the annotation queue."""
791791

792792

793+
class AnnotationQueueWithDetails(AnnotationQueue):
794+
"""Represents an annotation queue with details."""
795+
796+
rubric_instructions: Optional[str] = None
797+
"""The rubric instructions for the annotation queue."""
798+
799+
793800
class BatchIngestConfig(TypedDict, total=False):
794801
"""Configuration for batch ingestion."""
795802

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "langsmith"
3-
version = "0.3.38"
3+
version = "0.3.39"
44
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
55
authors = ["LangChain <[email protected]>"]
66
license = "MIT"

python/tests/integration_tests/test_client.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3110,3 +3110,84 @@ def _get_run(run_id: ID_TYPE) -> bool:
31103110

31113111
# Clean up
31123112
langchain_client.delete_annotation_queue(queue.id)
3113+
3114+
3115+
def test_annotation_queue_with_rubric_instructions(langchain_client: Client):
3116+
"""Test CRUD operations on annotation queue with rubric instructions."""
3117+
queue_name = f"test-queue-{str(uuid.uuid4())[:8]}"
3118+
project_name = f"test-project-{str(uuid.uuid4())[:8]}"
3119+
queue_id = uuid.uuid4()
3120+
3121+
try:
3122+
# 1. Create an annotation queue
3123+
queue = langchain_client.create_annotation_queue(
3124+
name=queue_name,
3125+
description="Initial description",
3126+
queue_id=queue_id,
3127+
rubric_instructions="This is a rubric instruction",
3128+
)
3129+
assert queue is not None
3130+
assert queue.name == queue_name
3131+
3132+
# 1a. Get the annotation queue
3133+
fetched_queue = langchain_client.read_annotation_queue(queue.id)
3134+
assert fetched_queue is not None
3135+
assert fetched_queue.name == queue_name
3136+
assert fetched_queue.rubric_instructions == "This is a rubric instruction"
3137+
3138+
# 1b. Update the annotation queue rubric instructions
3139+
new_instructions = "Updated rubric instructions"
3140+
langchain_client.update_annotation_queue(
3141+
queue.id,
3142+
name=queue_name,
3143+
rubric_instructions=new_instructions,
3144+
)
3145+
updated_queue = langchain_client.read_annotation_queue(queue.id)
3146+
assert updated_queue.rubric_instructions == new_instructions
3147+
finally:
3148+
# 6. Delete the annotation queue
3149+
langchain_client.delete_annotation_queue(queue_id)
3150+
3151+
# Clean up the project
3152+
if langchain_client.has_project(project_name=project_name):
3153+
langchain_client.delete_project(project_name=project_name)
3154+
3155+
3156+
def test_annotation_queue_with_rubric_instructions_2(langchain_client: Client):
3157+
"""Test CRUD operations on annotation queue with rubric instructions."""
3158+
queue_name = f"test-queue-{str(uuid.uuid4())[:8]}"
3159+
project_name = f"test-project-{str(uuid.uuid4())[:8]}"
3160+
queue_id = uuid.uuid4()
3161+
3162+
try:
3163+
# 1. Create an annotation queue without rubric instructions
3164+
queue = langchain_client.create_annotation_queue(
3165+
name=queue_name,
3166+
description="Initial description",
3167+
queue_id=queue_id,
3168+
)
3169+
assert queue is not None
3170+
assert queue.name == queue_name
3171+
3172+
# 1a. Get the annotation queue
3173+
fetched_queue = langchain_client.read_annotation_queue(queue.id)
3174+
assert fetched_queue is not None
3175+
assert fetched_queue.name == queue_name
3176+
assert fetched_queue.rubric_instructions is None
3177+
3178+
# 1b. Update the annotation queue rubric instructions
3179+
new_instructions = "Updated rubric instructions"
3180+
langchain_client.update_annotation_queue(
3181+
queue.id,
3182+
name=queue_name,
3183+
rubric_instructions=new_instructions,
3184+
)
3185+
updated_queue = langchain_client.read_annotation_queue(queue.id)
3186+
assert updated_queue.rubric_instructions == new_instructions
3187+
finally:
3188+
# 6. Delete the annotation queue
3189+
langchain_client.delete_annotation_queue(queue_id)
3190+
3191+
# Clean up the project
3192+
if langchain_client.has_project(project_name=project_name):
3193+
langchain_client.delete_project(project_name=project_name)

0 commit comments

Comments
 (0)