diff --git a/python/README.md b/python/README.md index d0e0b82e1..f1297177b 100644 --- a/python/README.md +++ b/python/README.md @@ -322,7 +322,7 @@ See [this documentation](https://docs.smith.langchain.com/tracing/faq/logging_an ```python from langsmith import traceable -@traceable() +@traceable(name="Call OpenAI") def my_function(text: str): return client.chat.completions.create( model="gpt-4", diff --git a/python/langsmith/client.py b/python/langsmith/client.py index 62ee0f31c..8529d3104 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -1077,20 +1077,23 @@ def batch_ingest_runs( self._insert_runtime_env(body["post"]) logger.debug(f"Batch ingesting {len(body['post'])}, {len(body['patch'])} runs") - self.request_with_retries( - "post", - f"{self.api_url}/runs/batch", - request_kwargs={ - "data": json.dumps(body, default=_serialize_json), - "timeout": self.timeout_ms / 1000, - "headers": { - **self._headers, - "Accept": "application/json", - "Content-Type": "application/json", + try: + self.request_with_retries( + "post", + f"{self.api_url}/runs/batch", + request_kwargs={ + "data": json.dumps(body, default=_serialize_json), + "timeout": self.timeout_ms / 1000, + "headers": { + **self._headers, + "Accept": "application/json", + "Content-Type": "application/json", + }, }, - }, - to_ignore=(ls_utils.LangSmithConflictError,), - ) + to_ignore=(ls_utils.LangSmithConflictError,), + ) + except Exception as e: + logger.warning(f"Failed to batch ingest runs: {repr(e)}") def update_run( self, diff --git a/python/langsmith/run_helpers.py b/python/langsmith/run_helpers.py index c369e44b4..e1ff89de0 100644 --- a/python/langsmith/run_helpers.py +++ b/python/langsmith/run_helpers.py @@ -223,8 +223,10 @@ def _setup_run( tags=tags_, client=client_, ) - - new_run.post() + try: + new_run.post() + except Exception as e: + logger.error(f"Failed to post run {new_run.id}: {e}") response_container = _TraceableContainer( new_run=new_run, project_name=project_name_, @@ -239,6 +241,16 @@ def _setup_run( R = TypeVar("R", covariant=True) +_VALID_RUN_TYPES = { + "tool", + "chain", + "llm", + "retriever", + "embedding", + "prompt", + "parser", +} + @runtime_checkable class SupportsLangsmithExtra(Protocol, Generic[R]): @@ -298,6 +310,17 @@ def traceable( if args and isinstance(args[0], str) else (kwargs.get("run_type") or "chain") ) + if run_type not in _VALID_RUN_TYPES: + warnings.warn( + f"Unrecognized run_type: {run_type}. Must be one of: {_VALID_RUN_TYPES}." + f" Did you mean @traceable(name='{run_type}')?" + ) + if len(args) > 1: + warnings.warn( + "The `traceable()` decorator only accepts one positional argument, " + "which should be the run_type. All other arguments should be passed " + "as keyword arguments." + ) extra_outer = kwargs.get("extra") or {} name = kwargs.get("name") metadata = kwargs.get("metadata") diff --git a/python/pyproject.toml b/python/pyproject.toml index 5a0e9d7b0..ed66eee04 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langsmith" -version = "0.1.0" +version = "0.1.1" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." authors = ["LangChain "] license = "MIT" diff --git a/python/tests/unit_tests/test_run_helpers.py b/python/tests/unit_tests/test_run_helpers.py index 1a21fabae..7644de121 100644 --- a/python/tests/unit_tests/test_run_helpers.py +++ b/python/tests/unit_tests/test_run_helpers.py @@ -4,6 +4,7 @@ import json import os import time +import warnings from typing import Any from unittest.mock import MagicMock, patch @@ -348,3 +349,50 @@ def __call__(self, a: int, b: int) -> None: pass assert not is_traceable_function(Foo()) + + +def test_traceable_warning() -> None: + with warnings.catch_warnings(record=True) as warning_records: + warnings.simplefilter("always") + + @traceable(run_type="invalid_run_type") + def my_function() -> None: + pass + + assert len(warning_records) == 1 + assert issubclass(warning_records[0].category, UserWarning) + assert "Unrecognized run_type: invalid_run_type" in str( + warning_records[0].message + ) + assert "Did you mean @traceable(name='invalid_run_type')?" in str( + warning_records[0].message + ) + + +def test_traceable_wrong_run_type_pos_arg() -> None: + with warnings.catch_warnings(record=True) as warning_records: + warnings.simplefilter("always") + + @traceable("my_run_type") + def my_function() -> None: + pass + + assert len(warning_records) == 1 + assert issubclass(warning_records[0].category, UserWarning) + assert "Unrecognized run_type: my_run_type" in str(warning_records[0].message) + assert "Did you mean @traceable(name='my_run_type')?" in str( + warning_records[0].message + ) + + +def test_traceable_too_many_pos_args() -> None: + with warnings.catch_warnings(record=True) as warning_records: + warnings.simplefilter("always") + + @traceable("chain", "my_function") # type: ignore + def my_function() -> None: + pass + + assert len(warning_records) == 1 + assert issubclass(warning_records[0].category, UserWarning) + assert "only accepts one positional argument" in str(warning_records[0].message)