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

Send collected outputs for generators #1082

Merged
merged 2 commits into from
Oct 10, 2024
Merged
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
54 changes: 27 additions & 27 deletions python/langsmith/run_helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Decorator for creating a run tree from functions."""

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

View workflow job for this annotation

GitHub Actions / benchmark

Benchmark results

......................................... create_5_000_run_trees: Mean +- std dev: 581 ms +- 49 ms ......................................... create_10_000_run_trees: Mean +- std dev: 1.14 sec +- 0.06 sec ......................................... create_20_000_run_trees: Mean +- std dev: 1.14 sec +- 0.06 sec ......................................... dumps_class_nested_py_branch_and_leaf_200x400: Mean +- std dev: 766 us +- 12 us ......................................... dumps_class_nested_py_leaf_50x100: Mean +- std dev: 27.4 ms +- 0.3 ms ......................................... dumps_class_nested_py_leaf_100x200: Mean +- std dev: 113 ms +- 3 ms ......................................... dumps_dataclass_nested_50x100: Mean +- std dev: 27.8 ms +- 0.4 ms ......................................... WARNING: the benchmark result may be unstable * the standard deviation (16.3 ms) is 26% of the mean (62.6 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: 62.6 ms +- 16.3 ms ......................................... WARNING: the benchmark result may be unstable * the standard deviation (31.8 ms) is 14% of the mean (220 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_pydanticv1_nested_50x100: Mean +- std dev: 220 ms +- 32 ms

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

View workflow job for this annotation

GitHub Actions / benchmark

Comparison against main

+-----------------------------------------------+----------+------------------------+ | Benchmark | main | changes | +===============================================+==========+========================+ | dumps_dataclass_nested_50x100 | 27.9 ms | 27.8 ms: 1.00x faster | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_leaf_50x100 | 27.3 ms | 27.4 ms: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | dumps_class_nested_py_branch_and_leaf_200x400 | 762 us | 766 us: 1.01x slower | +-----------------------------------------------+----------+------------------------+ | create_20_000_run_trees | 1.12 sec | 1.14 sec: 1.02x slower | +-----------------------------------------------+----------+------------------------+ | create_10_000_run_trees | 1.12 sec | 1.14 sec: 1.02x slower | +-----------------------------------------------+----------+------------------------+ | Geometric mean | (ref) | 1.01x slower | +-----------------------------------------------+----------+------------------------+ Benchmark hidden because not significant (4): dumps_pydantic_nested_50x100, dumps_class_nested_py_leaf_100x200, dumps_pydanticv1_nested_50x100, create_5_000_run_trees

from __future__ import annotations

Expand Down Expand Up @@ -570,22 +570,18 @@
yield item
except BaseException as e:
await asyncio.shield(
aitertools.aio_to_thread(_on_run_end, run_container, error=e)
aitertools.aio_to_thread(
_on_run_end,
run_container,
error=e,
outputs=_get_function_result(results, reduce_fn),
)
)
raise e
if results:
if reduce_fn:
try:
function_result = reduce_fn(results)
except BaseException as e:
LOGGER.error(e)
function_result = results
else:
function_result = results
else:
function_result = None
await aitertools.aio_to_thread(
_on_run_end, run_container, outputs=function_result
_on_run_end,
run_container,
outputs=_get_function_result(results, reduce_fn),
)

@functools.wraps(func)
Expand Down Expand Up @@ -652,21 +648,13 @@
results.append(function_return)

except BaseException as e:
_on_run_end(run_container, error=e)
_on_run_end(
run_container,
error=e,
outputs=_get_function_result(results, reduce_fn),
)
raise e

if results:
if reduce_fn:
try:
function_result = reduce_fn(results)
except BaseException as e:
LOGGER.error(e)
function_result = results
else:
function_result = results
else:
function_result = None
_on_run_end(run_container, outputs=function_result)
_on_run_end(run_container, outputs=_get_function_result(results, reduce_fn))
return function_return

# "Stream" functions (used in methods like OpenAI/Anthropic's SDKs)
Expand Down Expand Up @@ -1709,3 +1697,15 @@
return await self.__ls_stream__.__aexit__(exc_type, exc_val, exc_tb)
finally:
await self._aend_trace()


def _get_function_result(results: list, reduce_fn: Callable) -> Any:
if results:
if reduce_fn is not None:
try:
return reduce_fn(results)
except BaseException as e:
LOGGER.error(e)
return results
else:
return results
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langsmith"
version = "0.1.132"
version = "0.1.133"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
authors = ["LangChain <[email protected]>"]
license = "MIT"
Expand Down
59 changes: 58 additions & 1 deletion python/tests/unit_tests/test_run_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import time
import uuid
import warnings
from typing import Any, AsyncGenerator, Generator, Optional, Set, cast
from typing import Any, AsyncGenerator, Generator, List, Optional, Set, Tuple, cast
from unittest.mock import MagicMock, patch

import pytest
Expand Down Expand Up @@ -50,6 +50,17 @@ def _get_calls(
return calls


def _get_datas(mock_calls: List[Any]) -> List[Tuple[str, dict]]:
datas = []
for call_ in mock_calls:
data = json.loads(call_.kwargs["data"])
for verb in ("post", "patch"):
for payload in data.get(verb) or []:
datas.append((verb, payload))

return datas


def test__get_inputs_with_no_args() -> None:
def foo() -> None:
pass
Expand Down Expand Up @@ -1466,7 +1477,53 @@ async def my_function(a: int) -> AsyncGenerator[int, None]:
mock_calls = _get_calls(
mock_client, verbs={"POST", "PATCH", "GET"}, minimum=num_calls
)

assert len(mock_calls) == num_calls
if auto_batch_tracing:
datas = _get_datas(mock_calls)
outputs = [p["outputs"] for _, p in datas if p.get("outputs")]
assert len(outputs) == 1
assert outputs[0]["output"] == list(range(5))


@pytest.mark.parametrize("auto_batch_tracing", [True, False])
async def test_traceable_gen_exception(auto_batch_tracing: bool):
mock_client = _get_mock_client(
auto_batch_tracing=auto_batch_tracing,
info=ls_schemas.LangSmithInfo(
batch_ingest_config=ls_schemas.BatchIngestConfig(
size_limit_bytes=None, # Note this field is not used here
size_limit=100,
scale_up_nthreads_limit=16,
scale_up_qsize_trigger=1000,
scale_down_nempty_trigger=4,
)
),
)

@traceable
def my_function(a: int) -> Generator[int, None, None]:
for i in range(5):
yield i
raise ValueError("foo")

with tracing_context(enabled=True):
with pytest.raises(ValueError, match="foo"):
for _ in my_function(1, langsmith_extra={"client": mock_client}):
pass

# Get ALL the call args for the mock_client
num_calls = 1 if auto_batch_tracing else 2
mock_calls = _get_calls(
mock_client, verbs={"POST", "PATCH", "GET"}, minimum=num_calls
)

assert len(mock_calls) == num_calls
if auto_batch_tracing:
datas = _get_datas(mock_calls)
outputs = [p["outputs"] for _, p in datas if p.get("outputs")]
assert len(outputs) == 1
assert outputs[0]["output"] == list(range(5))


@pytest.mark.parametrize("env_var", [True, False])
Expand Down
Loading