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

Add python annotation queue async methods #1354

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
224 changes: 224 additions & 0 deletions python/langsmith/async_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""The Async LangSmith Client."""

Check notice on line 1 in python/langsmith/async_client.py

View workflow job for this annotation

GitHub Actions / benchmark

Benchmark results

........... create_5_000_run_trees: Mean +- std dev: 654 ms +- 56 ms ........... create_10_000_run_trees: Mean +- std dev: 1.38 sec +- 0.12 sec ........... WARNING: the benchmark result may be unstable * the standard deviation (133 ms) is 10% of the mean (1.33 sec) Try to rerun the benchmark with more runs, values and/or loops. Run 'python -m pyperf system tune' command to reduce the system jitter. Use pyperf stats, pyperf dump and pyperf hist to analyze results. Use --quiet option to hide these warnings. create_20_000_run_trees: Mean +- std dev: 1.33 sec +- 0.13 sec ........... dumps_class_nested_py_branch_and_leaf_200x400: Mean +- std dev: 715 us +- 11 us ........... dumps_class_nested_py_leaf_50x100: Mean +- std dev: 25.6 ms +- 0.2 ms ........... dumps_class_nested_py_leaf_100x200: Mean +- std dev: 106 ms +- 4 ms ........... dumps_dataclass_nested_50x100: Mean +- std dev: 25.9 ms +- 0.3 ms ........... WARNING: the benchmark result may be unstable * the standard deviation (20.8 ms) is 26% of the mean (79.8 ms) Try to rerun the benchmark with more runs, values and/or loops. Run 'python -m pyperf system tune' command to reduce the system jitter. Use pyperf stats, pyperf dump and pyperf hist to analyze results. Use --quiet option to hide these warnings. dumps_pydantic_nested_50x100: Mean +- std dev: 79.8 ms +- 20.8 ms ........... dumps_pydanticv1_nested_50x100: Mean +- std dev: 200 ms +- 2 ms

Check notice on line 1 in python/langsmith/async_client.py

View workflow job for this annotation

GitHub Actions / benchmark

Comparison against main

+-----------------------------------------------+----------+------------------------+ | Benchmark | main | changes | +===============================================+==========+========================+ | dumps_pydanticv1_nested_50x100 | 215 ms | 200 ms: 1.08x faster | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_branch_and_leaf_200x400 | 721 us | 715 us: 1.01x faster | +-----------------------------------------------+----------+------------------------+ | create_5_000_run_trees | 647 ms | 654 ms: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_leaf_100x200 | 105 ms | 106 ms: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | dumps_dataclass_nested_50x100 | 25.5 ms | 25.9 ms: 1.02x slower | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_leaf_50x100 | 25.2 ms | 25.6 ms: 1.02x slower | +-----------------------------------------------+----------+------------------------+ | create_20_000_run_trees | 1.29 sec | 1.33 sec: 1.03x slower | +-----------------------------------------------+----------+------------------------+ | create_10_000_run_trees | 1.28 sec | 1.38 sec: 1.08x slower | +-----------------------------------------------+----------+------------------------+ | dumps_pydantic_nested_50x100 | 65.4 ms | 79.8 ms: 1.22x slower | +-----------------------------------------------+----------+------------------------+ | Geometric mean | (ref) | 1.03x slower | +-----------------------------------------------+----------+------------------------+

Check notice on line 1 in python/langsmith/async_client.py

View workflow job for this annotation

GitHub Actions / benchmark

Benchmark results

........... WARNING: the benchmark result may be unstable * the standard deviation (65.7 ms) is 10% of the mean (651 ms) Try to rerun the benchmark with more runs, values and/or loops. Run 'python -m pyperf system tune' command to reduce the system jitter. Use pyperf stats, pyperf dump and pyperf hist to analyze results. Use --quiet option to hide these warnings. create_5_000_run_trees: Mean +- std dev: 651 ms +- 66 ms ........... create_10_000_run_trees: Mean +- std dev: 1.38 sec +- 0.09 sec ........... create_20_000_run_trees: Mean +- std dev: 1.33 sec +- 0.09 sec ........... dumps_class_nested_py_branch_and_leaf_200x400: Mean +- std dev: 710 us +- 9 us ........... dumps_class_nested_py_leaf_50x100: Mean +- std dev: 25.7 ms +- 0.4 ms ........... dumps_class_nested_py_leaf_100x200: Mean +- std dev: 105 ms +- 3 ms ........... dumps_dataclass_nested_50x100: Mean +- std dev: 25.9 ms +- 0.3 ms ........... WARNING: the benchmark result may be unstable * the standard deviation (18.5 ms) is 24% of the mean (77.3 ms) Try to rerun the benchmark with more runs, values and/or loops. Run 'python -m pyperf system tune' command to reduce the system jitter. Use pyperf stats, pyperf dump and pyperf hist to analyze results. Use --quiet option to hide these warnings. dumps_pydantic_nested_50x100: Mean +- std dev: 77.3 ms +- 18.5 ms ........... dumps_pydanticv1_nested_50x100: Mean +- std dev: 200 ms +- 2 ms

Check notice on line 1 in python/langsmith/async_client.py

View workflow job for this annotation

GitHub Actions / benchmark

Comparison against main

+-----------------------------------------------+----------+------------------------+ | Benchmark | main | changes | +===============================================+==========+========================+ | dumps_pydanticv1_nested_50x100 | 215 ms | 200 ms: 1.08x faster | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_branch_and_leaf_200x400 | 721 us | 710 us: 1.01x faster | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_leaf_100x200 | 105 ms | 105 ms: 1.00x slower | +-----------------------------------------------+----------+------------------------+ | create_5_000_run_trees | 647 ms | 651 ms: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | dumps_dataclass_nested_50x100 | 25.5 ms | 25.9 ms: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_leaf_50x100 | 25.2 ms | 25.7 ms: 1.02x slower | +-----------------------------------------------+----------+------------------------+ | create_20_000_run_trees | 1.29 sec | 1.33 sec: 1.03x slower | +-----------------------------------------------+----------+------------------------+ | create_10_000_run_trees | 1.28 sec | 1.38 sec: 1.07x slower | +-----------------------------------------------+----------+------------------------+ | dumps_pydantic_nested_50x100 | 65.4 ms | 77.3 ms: 1.18x slower | +-----------------------------------------------+----------+------------------------+ | Geometric mean | (ref) | 1.02x slower | +-----------------------------------------------+----------+------------------------+

from __future__ import annotations

Expand All @@ -25,6 +25,8 @@
from langsmith import utils as ls_utils
from langsmith._internal import _beta_decorator as ls_beta

ID_TYPE = Union[uuid.UUID, str]


class AsyncClient:
"""Async Client for interacting with the LangSmith API."""
Expand Down Expand Up @@ -95,6 +97,14 @@
) -> httpx.Response:
"""Make an async HTTP request with retries."""
max_retries = cast(int, self._retry_config.get("max_retries", 3))

# Python requests library used by the normal Client filters out params with None values
# The httpx library does not. Filter them out here to keep behavior consistent
if "params" in kwargs:
params = kwargs["params"]
filtered_params = {k: v for k, v in params.items() if v is not None}
kwargs["params"] = filtered_params

for attempt in range(max_retries):
try:
response = await self._client.request(method, endpoint, **kwargs)
Expand Down Expand Up @@ -826,6 +836,220 @@
if limit is not None and ix >= limit:
break

async def delete_feedback(self, feedback_id: ID_TYPE) -> None:
"""Delete a feedback by ID.

Args:
feedback_id (Union[UUID, str]):
The ID of the feedback to delete.

Returns:
None
"""
response = await self._arequest_with_retries(
"DELETE", f"/feedback/{ls_client._as_uuid(feedback_id, 'feedback_id')}"
)
ls_utils.raise_for_status_with_text(response)

# Annotation Queue API

async def list_annotation_queues(
self,
*,
queue_ids: Optional[List[ID_TYPE]] = None,
name: Optional[str] = None,
name_contains: Optional[str] = None,
limit: Optional[int] = None,
) -> AsyncIterator[ls_schemas.AnnotationQueue]:
"""List the annotation queues on the LangSmith API.

Args:
queue_ids (Optional[List[Union[UUID, str]]]):
The IDs of the queues to filter by.
name (Optional[str]):
The name of the queue to filter by.
name_contains (Optional[str]):
The substring that the queue name should contain.
limit (Optional[int]):
The maximum number of queues to return.

Yields:
The annotation queues.
"""
params: dict = {
"ids": (
[
ls_client._as_uuid(id_, f"queue_ids[{i}]")
for i, id_ in enumerate(queue_ids)
]
if queue_ids is not None
else None
),
"name": name,
"name_contains": name_contains,
"limit": min(limit, 100) if limit is not None else 100,
}
ix = 0
async for feedback in self._aget_paginated_list(
"/annotation-queues", params=params
):
yield ls_schemas.AnnotationQueue(**feedback)
ix += 1
if limit is not None and ix >= limit:
break

async def create_annotation_queue(
self,
*,
name: str,
description: Optional[str] = None,
queue_id: Optional[ID_TYPE] = None,
) -> ls_schemas.AnnotationQueue:
"""Create an annotation queue on the LangSmith API.

Args:
name (str):
The name of the annotation queue.
description (Optional[str]):
The description of the annotation queue.
queue_id (Optional[Union[UUID, str]]):
The ID of the annotation queue.

Returns:
AnnotationQueue: The created annotation queue object.
"""
body = {
"name": name,
"description": description,
"id": str(queue_id) if queue_id is not None else str(uuid.uuid4()),
}
response = await self._arequest_with_retries(
"POST",
"/annotation-queues",
json={k: v for k, v in body.items() if v is not None},
)
ls_utils.raise_for_status_with_text(response)
return ls_schemas.AnnotationQueue(
**response.json(),
)

async def read_annotation_queue(
self, queue_id: ID_TYPE
) -> ls_schemas.AnnotationQueue:
"""Read an annotation queue with the specified queue ID.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue to read.

Returns:
AnnotationQueue: The annotation queue object.
"""
# TODO: Replace when actual endpoint is added
return await self.list_annotation_queues(queue_ids=[queue_id]).__anext__()

async def update_annotation_queue(
self, queue_id: ID_TYPE, *, name: str, description: Optional[str] = None
) -> None:
"""Update an annotation queue with the specified queue_id.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue to update.
name (str): The new name for the annotation queue.
description (Optional[str]): The new description for the
annotation queue. Defaults to None.

Returns:
None
"""
response = await self._arequest_with_retries(
"PATCH",
f"/annotation-queues/{ls_client._as_uuid(queue_id, 'queue_id')}",
json={
"name": name,
"description": description,
},
)
ls_utils.raise_for_status_with_text(response)

async def delete_annotation_queue(self, queue_id: ID_TYPE) -> None:
"""Delete an annotation queue with the specified queue ID.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue to delete.

Returns:
None
"""
response = await self._arequest_with_retries(
"DELETE",
f"/annotation-queues/{ls_client._as_uuid(queue_id, 'queue_id')}",
headers={"Accept": "application/json", **self._client.headers},
)
ls_utils.raise_for_status_with_text(response)

async def add_runs_to_annotation_queue(
self, queue_id: ID_TYPE, *, run_ids: List[ID_TYPE]
) -> None:
"""Add runs to an annotation queue with the specified queue ID.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue.
run_ids (List[Union[UUID, str]]): The IDs of the runs to be added to the annotation
queue.

Returns:
None
"""
response = await self._arequest_with_retries(
"POST",
f"/annotation-queues/{ls_client._as_uuid(queue_id, 'queue_id')}/runs",
json=[
str(ls_client._as_uuid(id_, f"run_ids[{i}]"))
for i, id_ in enumerate(run_ids)
],
)
ls_utils.raise_for_status_with_text(response)

async def delete_run_from_annotation_queue(
self, queue_id: ID_TYPE, *, run_id: ID_TYPE
) -> None:
"""Delete a run from an annotation queue with the specified queue ID and run ID.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue.
run_id (Union[UUID, str]): The ID of the run to be added to the annotation
queue.

Returns:
None
"""
response = await self._arequest_with_retries(
"DELETE",
f"/annotation-queues/{ls_client._as_uuid(queue_id, 'queue_id')}/runs/{ls_client._as_uuid(run_id, 'run_id')}",
)
ls_utils.raise_for_status_with_text(response)

async def get_run_from_annotation_queue(
self, queue_id: ID_TYPE, *, index: int
) -> ls_schemas.RunWithAnnotationQueueInfo:
"""Get a run from an annotation queue at the specified index.

Args:
queue_id (Union[UUID, str]): The ID of the annotation queue.
index (int): The index of the run to retrieve.

Returns:
RunWithAnnotationQueueInfo: The run at the specified index.

Raises:
LangSmithNotFoundError: If the run is not found at the given index.
LangSmithError: For other API-related errors.
"""
base_url = f"/annotation-queues/{ls_client._as_uuid(queue_id, 'queue_id')}/run"
response = await self._arequest_with_retries("GET", f"{base_url}/{index}")
ls_utils.raise_for_status_with_text(response)
return ls_schemas.RunWithAnnotationQueueInfo(**response.json())

@ls_beta.warn_beta
async def index_dataset(
self,
Expand Down
2 changes: 1 addition & 1 deletion python/langsmith/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5699,7 +5699,7 @@ def create_annotation_queue(
body = {
"name": name,
"description": description,
"id": queue_id or str(uuid.uuid4()),
"id": str(queue_id) if queue_id is not None else str(uuid.uuid4()),
}
response = self.request_with_retries(
"POST",
Expand Down
Loading
Loading