Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
client.py11118 linesDownload Raw Back to langsmith
1"""Client for interacting with the LangSmith API.2 3Use the client to customize API keys / workspace connections, SSL certs,4etc. for tracing.5 6Also used to create, read, update, and delete LangSmith resources7such as runs (~trace spans), datasets, examples (~records),8feedback (~metrics), projects (tracer sessions/groups), etc.9 10For detailed API documentation, visit the [LangSmith docs](https://docs.langchain.com/langsmith/home).11"""12 13from __future__ import annotations14 15import atexit16import base6417import collections18import concurrent.futures as cf19import contextlib20import datetime21import functools22import importlib23import importlib.metadata24import io25import itertools26import json27import logging28import os29import random30import threading31import time32import traceback33import typing34import uuid35import warnings36import weakref37from collections.abc import AsyncIterable, Iterable, Iterator, Mapping, Sequence38from functools import lru_cache, partial39from inspect import signature40from pathlib import Path41from queue import Full, PriorityQueue42from typing import (43    TYPE_CHECKING,44    Annotated,45    Any,46    Callable,47    Literal,48    Optional,49    TypedDict,50    Union,51    cast,52    get_args,53)54from urllib import parse as urllib_parse55 56import packaging.version57import requests58from pydantic import Field59from requests import adapters as requests_adapters60from requests_toolbelt import (  # type: ignore[import-untyped]61    multipart as rqtb_multipart,62)63from typing_extensions import TypeGuard, overload64from urllib3.poolmanager import PoolKey  # type: ignore[attr-defined, import-untyped]65from urllib3.util import Retry  # type: ignore[import-untyped]66 67import langsmith68from langsmith import env as ls_env69from langsmith import schemas as ls_schemas70from langsmith import utils as ls_utils71from langsmith._internal import _orjson, _profiles72from langsmith._internal._background_thread import (73    TracingQueueItem,74)75from langsmith._internal._background_thread import (76    tracing_control_thread_func as _tracing_control_thread_func,77)78from langsmith._internal._beta_decorator import warn_beta79from langsmith._internal._compressed_traces import CompressedTraces80from langsmith._internal._constants import (81    _AUTO_SCALE_UP_NTHREADS_LIMIT,82    _BLOCKSIZE_BYTES,83    _BOUNDARY,84    _SIZE_LIMIT_BYTES,85    _TRACING_QUEUE_MAX_SIZE,86)87from langsmith._internal._hub import (88    HUB,89    PLATFORM_HUB,90    REPO_HANDLE_PATTERN,91    build_commit_url,92    resolve_owner_for_url,93    validate_parent_commit,94)95from langsmith._internal._multipart import (96    MultipartPart,97    MultipartPartsAndContext,98    join_multipart_parts_and_context,99)100from langsmith._internal._operations import (101    SerializedFeedbackOperation,102    SerializedRunOperation,103    combine_serialized_queue_operations,104    compress_multipart_parts_and_context,105    serialize_feedback_dict,106    serialize_run_dict,107    serialized_feedback_operation_to_multipart_parts_and_context,108    serialized_run_operation_to_multipart_parts_and_context,109)110from langsmith._internal._serde import dumps_json as _dumps_json111from langsmith._internal._uuid import uuid7112from langsmith.prompt_cache import PromptCache, prompt_cache_singleton113from langsmith.schemas import AttachmentInfo, ExampleWithRuns114 115logger = logging.getLogger(__name__)116 117_TRACING_DROP_LOG_INTERVAL_S = 60118_tracing_drops_count = 0119_tracing_drops_last_log_time = 0.0120_tracing_drops_lock = threading.Lock()121TracingMode = Literal["langsmith", "otel", "hybrid"]122_VALID_TRACING_MODES: frozenset[str] = frozenset(get_args(TracingMode))123 124 125def _log_tracing_drop(reason: str) -> None:126    """Rate-limited logging for dropped trace data (once per 60s)."""127    global _tracing_drops_count, _tracing_drops_last_log_time128    with _tracing_drops_lock:129        _tracing_drops_count += 1130        now = time.time()131        if now - _tracing_drops_last_log_time >= _TRACING_DROP_LOG_INTERVAL_S:132            count = _tracing_drops_count133            _tracing_drops_count = 0134            _tracing_drops_last_log_time = now135            logger.warning(136                "Dropped %d trace data item(s) in the last %ds: %s",137                count,138                _TRACING_DROP_LOG_INTERVAL_S,139                reason,140            )141 142 143def _reset_tracing_drop_log() -> None:144    """Reset rate-limit state for drop logging. Used in tests."""145    global _tracing_drops_count, _tracing_drops_last_log_time146    with _tracing_drops_lock:147        _tracing_drops_count = 0148        _tracing_drops_last_log_time = 0.0149 150 151_TRACING_SEND_TIMEOUT = (3, 10)  # (connect, read) seconds for background sends152 153_OPENAI_API_KEY = "OPENAI_API_KEY"154_ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY"155 156 157def _resolve_tracing_mode(158    tracing_mode: Optional[TracingMode],159    *,160    otel_enabled: Optional[bool] = None,161) -> TracingMode:162    """Resolve the effective tracing mode from the constructor arg and env vars.163 164    Priority: explicit ``tracing_mode`` argument >165    deprecated ``otel_enabled`` argument >166    ``LANGSMITH_TRACING_MODE`` env var >167    legacy ``OTEL_ENABLED`` / ``OTEL_ONLY`` env vars >168    default ``"langsmith"``.169    """170    mode_envvar_name = "TRACING_MODE"171    otel_enabled_envvar_name = "OTEL_ENABLED"172    otel_only_envvar_name = "OTEL_ONLY"173 174    env_mode = ls_utils.get_env_var(mode_envvar_name)175 176    if tracing_mode is not None:177        tracing_mode = tracing_mode.lower()  # type: ignore[assignment]178        if tracing_mode not in _VALID_TRACING_MODES:179            raise ls_utils.LangSmithUserError(180                f"Invalid tracing_mode={tracing_mode!r}. "181                f"Must be one of: {', '.join(sorted(_VALID_TRACING_MODES))}"182            )183        return tracing_mode  # type: ignore[return-value]184 185    if otel_enabled is not None:186        warnings.warn(187            "The 'otel_enabled' parameter is deprecated and will be removed "188            "in the next minor version. Use 'tracing_mode' instead, e.g. "189            'Client(tracing_mode="hybrid") or Client(tracing_mode="otel").',190            FutureWarning,191            stacklevel=3,192        )193        if otel_enabled:194            if ls_utils.is_env_var_truish(otel_only_envvar_name):195                return "otel"196            return "hybrid"197        return "langsmith"198 199    if env_mode is not None:200        env_mode = env_mode.lower()201        if env_mode not in _VALID_TRACING_MODES:202            raise ls_utils.LangSmithUserError(203                f"Invalid LANGSMITH_TRACING_MODE={env_mode!r}. "204                f"Must be one of: {', '.join(sorted(_VALID_TRACING_MODES))}"205            )206        legacy_otel = ls_utils.is_env_var_truish(otel_enabled_envvar_name)207        legacy_only = ls_utils.is_env_var_truish(otel_only_envvar_name)208        if legacy_otel or legacy_only:209            warnings.warn(210                f"Both LANGSMITH_{mode_envvar_name} and the legacy "211                f"LANGSMITH_{otel_enabled_envvar_name} / "212                f"LANGSMITH_{otel_only_envvar_name} env vars are set. "213                f"LANGSMITH_{mode_envvar_name} takes precedence.",214                stacklevel=3,215            )216        return env_mode  # type: ignore[return-value]217 218    # Fall back to legacy env vars219    if ls_utils.is_env_var_truish(otel_only_envvar_name):220        return "otel"221    if ls_utils.is_env_var_truish("OTEL_ENABLED"):222        return "hybrid"223    return "langsmith"224 225 226def _import_otel():227    """Dynamically import OTEL modules when needed."""228    try:229        from opentelemetry import trace as otel_trace  # type: ignore[import]230        from opentelemetry.trace import set_span_in_context  # type: ignore[import]231 232        from langsmith._internal.otel._otel_client import (233            get_otlp_tracer_provider,234        )235        from langsmith._internal.otel._otel_exporter import OTELExporter236 237        return otel_trace, set_span_in_context, get_otlp_tracer_provider, OTELExporter238    except ImportError:239        raise ImportError(240            "To use OTEL tracing, you must install it with `pip install langsmith[otel]`"241        )242 243 244try:245    from zoneinfo import ZoneInfo  # type: ignore[import-not-found]246except ImportError:247 248    class ZoneInfo:  # type: ignore[no-redef]249        """Introduced in python 3.9."""250 251 252try:253    from opentelemetry.sdk.trace import TracerProvider  # type: ignore[import-not-found]254except ImportError:255 256    class TracerProvider:  # type: ignore[no-redef]257        """Used for optional OTEL tracing."""258 259 260if TYPE_CHECKING:261    import pandas as pd  # type: ignore262    from langchain_core.runnables import Runnable263 264    from langsmith import schemas265 266    # OTEL imports for type hints267    try:268        from opentelemetry import trace as otel_trace  # type: ignore[import]269 270        from langsmith._internal.otel._otel_exporter import OTELExporter271    except ImportError:272        otel_trace = Any  # type: ignore[assignment, misc]273        OTELExporter = Any  # type: ignore[assignment, misc]274    from langsmith.evaluation import evaluator as ls_evaluator275    from langsmith.evaluation._arunner import (276        AEVALUATOR_T,277        ATARGET_T,278        AsyncExperimentResults,279    )280    from langsmith.evaluation._runner import (281        COMPARATIVE_EVALUATOR_T,282        DATA_T,283        EVALUATOR_T,284        EXPERIMENT_T,285        SUMMARY_EVALUATOR_T,286        TARGET_T,287        ComparativeExperimentResults,288        ExperimentResults,289    )290 291 292logger = logging.getLogger(__name__)293_urllib3_logger = logging.getLogger("urllib3.connectionpool")294 295X_API_KEY = "x-api-key"296EMPTY_SEQ: tuple[dict, ...] = ()297_UNSET = object()298URLLIB3_SUPPORTS_BLOCKSIZE = "key_blocksize" in signature(PoolKey).parameters299DEFAULT_INSTRUCTIONS = "How are people using my agent? What are they asking about?"300 301_fallback_dirs_created: set[str] = set()302 303 304@lru_cache(maxsize=1)305def _lc_load_allowed_objects_arg_supported() -> bool:306    """Check if the installed `langchain_core.load.load` supports the `allowed_objects` parameter.307 308    Returns `True` if `langchain-core >= 0.3.81` and `< 1.0`, or `>= 1.2.5`.309    """310    allowed_objects_supported = False311    try:312        from langchain_core import __version__313 314        lc_version = packaging.version.parse(__version__)315        # allowed_objects supported in langchain-core >= 0.3.81 and < 1.0, or >= 1.2.5316        allowed_objects_supported = (317            lc_version >= packaging.version.parse("0.3.81")318            and lc_version < packaging.version.parse("1.0.0")319        ) or (lc_version >= packaging.version.parse("1.2.5"))320    except (ImportError, ValueError, TypeError) as exc:321        # If version checking fails, default to False322        logger.debug(323            "Failed to determine langchain-core version for allowed_objects "324            "support, defaulting to disabled: %s",325            exc,326        )327    return allowed_objects_supported328 329 330def _manifest_has_secrets(331    manifest: dict | list, *, depth: int = 0, max_depth: int = 10, max_width: int = 50332) -> bool:333    """Recursively check if a manifest contains any secret objects."""334    if max_depth < 1:335        raise ValueError("max_depth must be positive.")336    if max_width < 1:337        raise ValueError("max_width must be positive.")338    if depth >= max_depth:339        return False340    if (341        isinstance(manifest, dict)342        and set(manifest) == {"lc", "type", "id"}343        and manifest["type"] == "secret"344    ):345        return True346    elif depth + 1 == max_depth:  # skip extra layer of function calls.347        return False348    elif isinstance(manifest, dict):349        return any(350            _manifest_has_secrets(351                x, depth=depth + 1, max_depth=max_depth, max_width=max_width352            )353            for x in itertools.islice(manifest.values(), max_width)354        )355    elif isinstance(manifest, (tuple, list)):356        return any(357            _manifest_has_secrets(358                x, depth=depth + 1, max_depth=max_depth, max_width=max_width359            )360            for x in manifest[:max_width]361        )362    else:363        return False364 365 366def _validate_public_prompt_pull(367    prompt_identifier: str, *, dangerously_pull_public_prompt: bool368) -> None:369    owner, _, _ = ls_utils.parse_prompt_identifier(prompt_identifier)370    if owner != "-" and not dangerously_pull_public_prompt:371        raise ValueError(372            "Pulling a public prompt by owner/name is disabled by default because "373            "prompts may contain untrusted serialized LangChain objects. If you "374            "trust this prompt, set `dangerously_pull_public_prompt=True` to "375            "acknowledge the risk."376        )377 378 379def _process_prompt_manifest(380    prompt_object: Any,381    *,382    include_model: bool | None,383    secrets: dict[str, str] | None,384    secrets_from_env: bool,385) -> Any:386    """Process a prompt manifest into a LangChain prompt object.387 388    This is the common logic shared between `Client.pull_prompt()` and389    `AsyncClient.pull_prompt()`.390 391    Args:392        prompt_object: The prompt commit object containing the manifest.393        include_model: Whether to include model information.394        secrets: Map of secrets to use when loading.395        secrets_from_env: Whether to load secrets from environment variables.396 397    Returns:398        The processed prompt object.399 400    Raises:401        ImportError: If `langchain-core` is not installed.402        ValueError: If secrets are required but not provided.403    """404    try:405        from langchain_core.language_models.base import BaseLanguageModel406        from langchain_core.load.load import load407        from langchain_core.output_parsers import BaseOutputParser408        from langchain_core.prompts import BasePromptTemplate409        from langchain_core.prompts.structured import StructuredPrompt410        from langchain_core.runnables.base import RunnableBinding, RunnableSequence411    except ImportError:412        raise ImportError(413            "The client.pull_prompt function requires the langchain-core "414            "package to run.\nInstall with `pip install langchain-core`"415        )416    try:417        from langchain_core._api import suppress_langchain_beta_warning418    except ImportError:419 420        @contextlib.contextmanager421        def suppress_langchain_beta_warning():422            yield423 424    load_kwargs: dict = {}425    if _lc_load_allowed_objects_arg_supported():426        load_kwargs["allowed_objects"] = "all" if include_model else "core"427 428    with suppress_langchain_beta_warning():429        try:430            prompt = load(431                prompt_object.manifest,432                secrets_map=secrets,433                secrets_from_env=secrets_from_env,434                **load_kwargs,435            )436        except Exception as e:437            if (438                _manifest_has_secrets(prompt_object.manifest)439                and not secrets_from_env440                and not secrets441            ):442                raise ValueError(443                    "Failed to load prompt. The prompt manifest contains secrets "444                    "(like API keys or access tokens) but no secrets were provided. "445                    "This is due to a security patch in langsmith 0.5.1 that "446                    "disabled reading secrets from environment variables by default.\n\n"447                    "To resolve this:\n"448                    "- Recommended: Pass secrets directly via `secrets={'KEY_NAME': 'value'}`\n"449                    "- If this is a trusted prompt: Set `secrets_from_env=True` to read "450                    "secrets from environment variables\n\n"451                    f"Underlying error:\n{e}"452                )453            raise e454 455    if (456        isinstance(prompt, BasePromptTemplate)457        or isinstance(prompt, RunnableSequence)458        and isinstance(prompt.first, BasePromptTemplate)459    ):460        prompt_template = (461            prompt462            if isinstance(prompt, BasePromptTemplate)463            else (464                prompt.first465                if isinstance(prompt, RunnableSequence)466                and isinstance(prompt.first, BasePromptTemplate)467                else None468            )469        )470        if prompt_template is None:471            raise ls_utils.LangSmithError(472                "Prompt object is not a valid prompt template."473            )474 475        if prompt_template.metadata is None:476            prompt_template.metadata = {}477        prompt_template.metadata.update(478            {479                "lc_hub_owner": prompt_object.owner,480                "lc_hub_repo": prompt_object.repo,481                "lc_hub_commit_hash": prompt_object.commit_hash,482            }483        )484 485    # Transform 2-step RunnableSequence to 3-step for structured prompts486    # See create_commit for the reverse transformation487    if (488        include_model489        and isinstance(prompt, RunnableSequence)490        and isinstance(prompt.first, StructuredPrompt)491        # Make forward-compatible in case we let update the response type492        and (len(prompt.steps) == 2 and not isinstance(prompt.last, BaseOutputParser))493    ):494        if isinstance(prompt.last, RunnableBinding) and isinstance(495            prompt.last.bound, BaseLanguageModel496        ):497            seq = cast(RunnableSequence, prompt.first | prompt.last.bound)498            if len(seq.steps) == 3:  # prompt | bound llm | output parser499                rebound_llm = seq.steps[1]500                prompt = RunnableSequence(501                    prompt.first,502                    rebound_llm.bind(**{**prompt.last.kwargs}),503                    seq.last,504                )505            else:506                prompt = seq  # Not sure507 508        elif isinstance(prompt.last, BaseLanguageModel):509            prompt: RunnableSequence = prompt.first | prompt.last  # type: ignore[no-redef, assignment]510        else:511            pass512 513    return prompt514 515 516def _parse_token_or_url(517    url_or_token: Union[str, uuid.UUID],518    api_url: str,519    num_parts: int = 2,520    kind: str = "dataset",521) -> tuple[str, str]:522    """Parse a public dataset URL or share token."""523    try:524        if isinstance(url_or_token, uuid.UUID) or uuid.UUID(url_or_token):525            return api_url, str(url_or_token)526    except ValueError:527        pass528 529    # Then it's a URL530    parsed_url = urllib_parse.urlparse(str(url_or_token))531    # Extract the UUID from the path532    path_parts = parsed_url.path.split("/")533    if len(path_parts) >= num_parts:534        token_uuid = path_parts[-num_parts]535        _as_uuid(token_uuid, var="token parts")536    else:537        raise ls_utils.LangSmithUserError(f"Invalid public {kind} URL: {url_or_token}")538    if parsed_url.netloc == "smith.langchain.com":539        api_url = "https://api.smith.langchain.com"540    elif parsed_url.netloc == "beta.smith.langchain.com":541        api_url = "https://beta.api.smith.langchain.com"542    return api_url, token_uuid543 544 545def _is_langchain_hosted(url: str) -> bool:546    """Check if the URL is langchain hosted.547 548    Args:549        url: The URL to check.550 551    Returns:552        `True` if the URL is langchain hosted, `False` otherwise.553    """554    try:555        netloc = urllib_parse.urlsplit(url).netloc.split(":")[0]556        return netloc == "langchain.com" or netloc.endswith(".langchain.com")557    except Exception:558        return False559 560 561ID_TYPE = Union[uuid.UUID, str]562RUN_TYPE_T = Literal[563    "tool", "chain", "llm", "retriever", "embedding", "prompt", "parser"564]565 566 567@functools.lru_cache(maxsize=1)568def _default_retry_config() -> Retry:569    """Get the default retry configuration.570 571    If `urllib3` version is `1.26` or greater, retry on all methods.572 573    Returns:574        The default retry configuration.575    """576    retry_params = dict(577        total=3,578        status_forcelist=[502, 503, 504, 408, 425],579        backoff_factor=0.5,580        # Sadly urllib3 1.x doesn't support backoff_jitter581        raise_on_redirect=False,582        raise_on_status=False,583        respect_retry_after_header=True,584    )585 586    # the `allowed_methods` keyword is not available in urllib3 < 1.26587 588    # check to see if urllib3 version is 1.26 or greater589    urllib3_version = packaging.version.parse(importlib.metadata.version("urllib3"))590    use_allowed_methods = urllib3_version >= packaging.version.parse("1.26")591 592    if use_allowed_methods:593        # Retry on all methods594        retry_params["allowed_methods"] = None595 596    return ls_utils.LangSmithRetry(**retry_params)  # type: ignore597 598 599def close_session(session: requests.Session) -> None:600    """Close the session.601 602    Args:603        session: The session to close.604    """605    logger.debug("Closing Client.session")606    session.close()607 608 609def _get_langsmith_env_var_uncached(name: str) -> Optional[str]:610    for namespace in ("LANGSMITH", "LANGCHAIN"):611        value = os.environ.get(f"{namespace}_{name}")612        if value is not None and value.strip() != "":613            return value614    return None615 616 617def _validate_api_key_if_hosted(618    api_url: str,619    api_key: Optional[str],620    *,621    tracing_mode: TracingMode = "langsmith",622) -> None:623    """Verify API key is provided if url not localhost.624 625    Args:626        api_url: The API URL.627        api_key: The API key.628        tracing_mode: Resolved tracing mode; when ``"otel"`` the warning is629            suppressed because no LangSmith REST calls are made.630 631    Raises:632        LangSmithUserError: If the API key is not provided when using the hosted service.633    """634    if not api_key:635        if (636            _is_langchain_hosted(api_url)637            and tracing_mode != "otel"638            and ls_utils.tracing_is_enabled()639        ):640            warnings.warn(641                "API key must be provided when using hosted LangSmith API",642                ls_utils.LangSmithMissingAPIKeyWarning,643            )644 645 646def _format_feedback_score(score: Union[float, int, bool, None]):647    """Format a feedback score by truncating numerical values to 4 decimal places.648 649    Args:650        score: The score to format, can be a number or any other type651 652    Returns:653        The formatted score654    """655    if isinstance(score, float):656        # Truncate at 4 decimal places657        return round(score, 4)658    return score659 660 661def _get_tracing_sampling_rate(662    tracing_sampling_rate: Optional[float] = None,663) -> float | None:664    """Get the tracing sampling rate.665 666    Returns:667        The tracing sampling rate.668    """669    if tracing_sampling_rate is None:670        sampling_rate_str = ls_utils.get_env_var("TRACING_SAMPLING_RATE")671        if not sampling_rate_str:672            return None673    else:674        sampling_rate_str = str(tracing_sampling_rate)675    sampling_rate = float(sampling_rate_str)676    if sampling_rate < 0 or sampling_rate > 1:677        raise ls_utils.LangSmithUserError(678            "LANGSMITH_TRACING_SAMPLING_RATE must be between 0 and 1 if set."679            f" Got: {sampling_rate}"680        )681    return sampling_rate682 683 684def _get_write_api_urls(_write_api_urls: Optional[dict[str, str]]) -> dict[str, str]:685    # Note: LANGSMITH_RUNS_ENDPOINTS is now handled via replicas, not _write_api_urls686    _write_api_urls = _write_api_urls or {}687    processed_write_api_urls = {}688    for url, api_key in _write_api_urls.items():689        processed_url = url.strip()690        if not processed_url:691            raise ls_utils.LangSmithUserError("LangSmith runs API URL cannot be empty")692        processed_url = processed_url.strip().strip('"').strip("'").rstrip("/")693        processed_api_key = api_key.strip().strip('"').strip("'")694        _validate_api_key_if_hosted(processed_url, processed_api_key)695        processed_write_api_urls[processed_url] = processed_api_key696 697    return processed_write_api_urls698 699 700def _as_uuid(value: ID_TYPE, var: Optional[str] = None) -> uuid.UUID:701    try:702        return uuid.UUID(value) if not isinstance(value, uuid.UUID) else value703    except ValueError as e:704        var = var or "value"705        raise ls_utils.LangSmithUserError(706            f"{var} must be a valid UUID or UUID string. Got {value}"707        ) from e708 709 710@typing.overload711def _ensure_uuid(value: Optional[Union[str, uuid.UUID]]) -> uuid.UUID: ...712 713 714@typing.overload715def _ensure_uuid(716    value: Optional[Union[str, uuid.UUID]], *, accept_null: bool = True717) -> Optional[uuid.UUID]: ...718 719 720def _ensure_uuid(value: Optional[Union[str, uuid.UUID]], *, accept_null: bool = False):721    if value is None:722        if accept_null:723            return None724        return uuid7()725    return _as_uuid(value)726 727 728@functools.lru_cache(maxsize=1)729def _parse_url(url):730    parsed_url = urllib_parse.urlparse(url)731    host = parsed_url.netloc.split(":")[0]732    return host733 734 735class _LangSmithHttpAdapter(requests_adapters.HTTPAdapter):736    __attrs__ = [737        "max_retries",738        "config",739        "_pool_connections",740        "_pool_maxsize",741        "_pool_block",742        "_blocksize",743    ]744 745    def __init__(746        self,747        pool_connections: int = requests_adapters.DEFAULT_POOLSIZE,748        pool_maxsize: int = requests_adapters.DEFAULT_POOLSIZE,749        max_retries: Union[Retry, int, None] = requests_adapters.DEFAULT_RETRIES,750        pool_block: bool = requests_adapters.DEFAULT_POOLBLOCK,751        blocksize: int = 16384,  # default from urllib3.BaseHTTPSConnection752    ) -> None:753        self._blocksize = blocksize754        super().__init__(pool_connections, pool_maxsize, max_retries, pool_block)755 756    def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):757        if URLLIB3_SUPPORTS_BLOCKSIZE:758            # urllib3 before 2.0 doesn't support blocksize759            pool_kwargs["blocksize"] = self._blocksize760        try:761            return super().init_poolmanager(connections, maxsize, block, **pool_kwargs)762        except TypeError:763            if "blocksize" in pool_kwargs:764                logger.warning(765                    "An intermediate HTTPAdapter does not accept the 'blocksize' "766                    "kwarg. Retrying without it."767                )768                pool_kwargs.pop("blocksize")769                return super().init_poolmanager(770                    connections, maxsize, block, **pool_kwargs771                )772            raise773 774 775class ListThreadsItem(TypedDict):776    """Item returned by :meth:`Client.list_threads`."""777 778    thread_id: str779    runs: list[ls_schemas.Run]780    count: int781    min_start_time: Optional[str]782    max_start_time: Optional[str]783 784 785class Client:786    """Client for interacting with the LangSmith API."""787 788    __slots__ = [789        "__weakref__",790        "api_url",791        "_api_key",792        "_oauth_access_token",793        "_workspace_id",794        "_headers",795        "_custom_headers",796        "retry_config",797        "timeout_ms",798        "_timeout",799        "session",800        "_get_data_type_cached",801        "_web_url",802        "_tenant_id",803        "tracing_sample_rate",804        "_filtered_post_uuids",805        "tracing_queue",806        "_anonymizer",807        "_hide_inputs",808        "_hide_outputs",809        "_hide_metadata",810        "_omit_traced_runtime_info",811        "_process_buffered_run_ops",812        "_run_ops_buffer_size",813        "_run_ops_buffer_timeout_ms",814        "_run_ops_buffer_last_flush_time",815        "_info",816        "_write_api_urls",817        "_settings",818        "_manual_cleanup",819        "_pyo3_client",820        "compressed_traces",821        "_data_available_event",822        "_futures",823        "_run_ops_buffer",824        "_run_ops_buffer_lock",825        "otel_exporter",826        "_otel_trace",827        "_set_span_in_context",828        "_max_batch_size_bytes",829        "_use_daemon_threads",830        "_tracing_error_callback",831        "_multipart_disabled",832        "_cache",833        "_failed_traces_dir",834        "_failed_traces_max_bytes",835        "_tracing_mode",836        "_profile_auth",837        "_profile_auth_headers",838    ]839 840    _api_key: Optional[str]841    _oauth_access_token: Optional[str]842    _headers: dict[str, str]843    _custom_headers: dict[str, str]844    _timeout: tuple[float, float]845    _manual_cleanup: bool846    _profile_auth: Optional[_profiles.ProfileAuth]847    _profile_auth_headers: dict[str, str]848 849    def __init__(850        self,851        api_url: Optional[str] = None,852        *,853        api_key: Optional[str] = None,854        retry_config: Optional[Retry] = None,855        timeout_ms: Optional[Union[int, tuple[int, int]]] = None,856        web_url: Optional[str] = None,857        session: Optional[requests.Session] = None,858        auto_batch_tracing: bool = True,859        anonymizer: Optional[Callable[[dict], dict]] = None,860        hide_inputs: Optional[Union[Callable[[dict], dict], bool]] = None,861        hide_outputs: Optional[Union[Callable[[dict], dict], bool]] = None,862        hide_metadata: Optional[Union[Callable[[dict], dict], bool]] = None,863        omit_traced_runtime_info: bool = False,864        process_buffered_run_ops: Optional[865            Callable[[Sequence[dict]], Sequence[dict]]866        ] = None,867        run_ops_buffer_size: Optional[int] = None,868        run_ops_buffer_timeout_ms: Optional[float] = None,869        info: Optional[Union[dict, ls_schemas.LangSmithInfo]] = None,870        api_urls: Optional[dict[str, str]] = None,871        otel_tracer_provider: Optional[TracerProvider] = None,872        tracing_mode: Optional[TracingMode] = None,873        otel_enabled: Optional[bool] = None,874        tracing_sampling_rate: Optional[float] = None,875        workspace_id: Optional[str] = None,876        max_batch_size_bytes: Optional[int] = None,877        headers: Optional[dict[str, str]] = None,878        tracing_error_callback: Optional[Callable[[Exception], None]] = None,879        disable_prompt_cache: bool = False,880        cache: Optional[Union[bool, PromptCache]] = None,881    ) -> None:882        """Initialize a `Client` instance.883 884        Args:885            api_url: URL for the LangSmith API.886 887                Defaults to the `LANGCHAIN_ENDPOINT` environment variable or888                `https://api.smith.langchain.com` if not set.889            api_key: API key for the LangSmith API.890 891                Defaults to the `LANGCHAIN_API_KEY` environment variable.892            retry_config: Retry configuration for the `HTTPAdapter`.893            timeout_ms: Timeout for the `HTTPAdapter`.894 895                Can also be a 2-tuple of `(connect timeout, read timeout)` to set them896                separately.897            web_url: URL for the LangSmith web app.898 899                Default is auto-inferred from the `ENDPOINT`.900            session: The session to use for requests.901 902                If `None`, a new session will be created.903            auto_batch_tracing: Whether to automatically batch tracing.904            anonymizer: A function applied for masking serialized run inputs and905                outputs, before sending to the API.906            hide_inputs: Whether to hide run inputs when tracing with this client.907 908                If `True`, hides the entire inputs.909 910                If a function, applied to all run inputs when creating runs.911            hide_outputs: Whether to hide run outputs when tracing with this client.912 913                If `True`, hides the entire outputs.914 915                If a function, applied to all run outputs when creating runs.916            hide_metadata: Whether to hide run metadata when tracing with this client.917 918                If `True`, hides the entire metadata.919 920                If a function, applied to all run metadata when creating runs.921            omit_traced_runtime_info: Whether to omit runtime information from traced922                runs.923 924                If `True`, runtime information (SDK version, platform, Python version,925                etc.) will not be stored in the `extra.runtime` field of runs.926 927                Defaults to `False`.928            process_buffered_run_ops: A function applied to buffered run operations that929                allows for modification of the raw run dicts before they are converted930                to multipart and compressed.931 932                Useful specifically for high throughput tracing where you need to apply933                a rate-limited API or other costly process to the runs before they are934                sent to the API.935 936                Note that the buffer will only flush automatically when937                `run_ops_buffer_size` is reached or a new run is added to the buffer938                after `run_ops_buffer_timeout_ms` has elapsed - it will not flush939                outside of these conditions unless you manually call `client.flush()`,940                so be sure to do this before your code exits.941            run_ops_buffer_size: Maximum number of run operations to collect in the942                buffer before applying `process_buffered_run_ops` and sending to the943                API.944 945                Required when `process_buffered_run_ops` is provided.946            run_ops_buffer_timeout_ms: Maximum time in milliseconds to wait before947                flushing the run ops buffer when new runs are added.948 949                Defaults to `5000`.950 951                Only used when `process_buffered_run_ops` is provided.952            info: The information about the LangSmith API.953 954                If not provided, it will be fetched from the API.955            api_urls: A dictionary of write API URLs and their corresponding API keys.956 957                Useful for multi-tenant setups.958 959                Data is only read from the first URL in the dictionary. However, ONLY960                Runs are written (`POST` and `PATCH`) to all URLs in the dictionary.961                Feedback, sessions, datasets, examples, annotation queues and evaluation962                results are only written to the first.963            otel_tracer_provider: Optional tracer provider for OpenTelemetry964                integration.965 966                If not provided, a LangSmith-specific tracer provider will be used.967            tracing_mode: Where to send traces.  One of:968 969                - ``"langsmith"`` (default) — LangSmith only.970                - ``"otel"`` — OpenTelemetry export only.971                - ``"hybrid"`` — both OTel and LangSmith.972 973                Falls back to the ``LANGSMITH_TRACING_MODE`` env var, then to974                the legacy ``OTEL_ENABLED`` / ``OTEL_ONLY`` env vars, then to975                ``"langsmith"``.976            otel_enabled: *Deprecated.* Use ``tracing_mode`` instead.977 978                When ``True``, interpreted as ``tracing_mode="hybrid"``979                (or ``"otel"`` if the ``OTEL_ONLY`` env var is set).980                Will be removed in the next minor version.981            tracing_sampling_rate: The sampling rate for tracing.982 983                If provided, overrides the `LANGCHAIN_TRACING_SAMPLING_RATE` environment984                variable.985 986                Should be a float between `0` and `1`, where `1` means trace everything987                and `0` means trace nothing.988            workspace_id: The workspace ID.989 990                Required for org-scoped API keys.991            max_batch_size_bytes: The maximum size of a batch of runs in bytes.992 993                If not provided, the default is set by the server.994            headers: Additional HTTP headers to include in all requests.995 996                These headers will be merged with the default headers (User-Agent,997                Accept, x-api-key, etc.). Custom headers will not override the default998                required headers.999            tracing_error_callback (Optional[Callable[[Exception], None]]): Optional callback function to handle errors.1000 1001                Called when exceptions occur during tracing operations.1002            disable_prompt_cache: Disable prompt caching for this client.1003 1004                By default, prompt caching is enabled globally using a singleton cache.1005                Set this to `True` to disable caching for this specific client instance.1006 1007                To configure the global cache, use `configure_global_prompt_cache()`.1008 1009                !!! example1010 1011                    ```python1012                    from langsmith import Client, configure_global_prompt_cache1013 1014                    # Use default global cache1015                    client = Client()1016 1017                    # Disable caching for this client1018                    client_no_cache = Client(disable_prompt_cache=True)1019 1020                    # Configure global cache settings1021                    configure_global_prompt_cache(max_size=200, ttl_seconds=7200)1022                    ```1023            cache: **[Deprecated]** Control prompt caching behavior.1024 1025                This parameter is deprecated. Use `configure_global_prompt_cache()` to1026                configure caching, or `disable_prompt_cache=True` to disable it.1027 1028                - `True`: Enable caching with the global singleton (default behavior)1029                - `False`: Disable caching (equivalent to `disable_prompt_cache=True`)1030                - `Cache(...)`/`PromptCache(...)`: Use a custom cache instance1031 1032                !!! example1033 1034                    ```python1035                    from langsmith import Client, Cache, configure_global_prompt_cache1036 1037                    # Old API (deprecated but still supported)1038                    client = Client(cache=True)  # Use global cache1039                    client = Client(cache=False)  # Disable cache1040 1041                    # Use custom cache instance1042                    my_cache = Cache(max_size=100, ttl_seconds=3600)1043                    client = Client(cache=my_cache)1044 1045                    # New API (recommended)1046                    client = Client()  # Use global cache (default)1047 1048                    # Configure global cache for all clients1049                    configure_global_prompt_cache(max_size=200, ttl_seconds=7200)1050 1051                    # Or disable for a specific client1052                    client = Client(disable_prompt_cache=True)1053                    ```1054 1055        Raises:1056            LangSmithUserError: If the API key is not provided when using the hosted service.1057            LangSmithUserError: If both `api_url` and `api_urls` are provided.1058        """1059        if api_url and api_urls:1060            raise ls_utils.LangSmithUserError(1061                "You cannot provide both api_url and api_urls."1062            )1063 1064        if (1065            os.getenv("LANGSMITH_ENDPOINT") or os.getenv("LANGCHAIN_ENDPOINT")1066        ) and os.getenv("LANGSMITH_RUNS_ENDPOINTS"):1067            raise ls_utils.LangSmithUserError(1068                "You cannot provide both LANGSMITH_ENDPOINT / LANGCHAIN_ENDPOINT "1069                "and LANGSMITH_RUNS_ENDPOINTS."1070            )1071 1072        resolved_mode = _resolve_tracing_mode(tracing_mode, otel_enabled=otel_enabled)1073        self._tracing_mode: TracingMode = resolved_mode1074        env_api_url = _get_langsmith_env_var_uncached("ENDPOINT")1075        env_api_key = _get_langsmith_env_var_uncached("API_KEY")1076        env_workspace_id = _get_langsmith_env_var_uncached("WORKSPACE_ID")1077        profile_config = _profiles.load_profile_client_config()1078        api_url_ = (1079            api_url if api_url is not None else env_api_url or profile_config.api_url1080        )1081        explicit_or_env_api_key = api_key if api_key is not None else env_api_key1082        profile_auth_enabled = api_key is None and env_api_key is None1083        use_profile_oauth = profile_auth_enabled and profile_config.has_oauth1084        api_key_ = (1085            explicit_or_env_api_key1086            if explicit_or_env_api_key is not None1087            else None1088            if use_profile_oauth1089            else profile_config.api_key1090        )1091        workspace_id_ = (1092            workspace_id1093            if workspace_id is not None1094            else env_workspace_id or profile_config.workspace_id1095        )1096        self._oauth_access_token = (1097            profile_config.oauth_access_token if use_profile_oauth else None1098        )1099        self._profile_auth: Optional[_profiles.ProfileAuth] = None1100        self._profile_auth_headers: dict[str, str] = {}1101 1102        self.tracing_sample_rate = _get_tracing_sampling_rate(tracing_sampling_rate)1103        self._filtered_post_uuids: set[uuid.UUID] = set()1104        self._write_api_urls: Mapping[str, Optional[str]] = _get_write_api_urls(1105            api_urls1106        )1107        # Initialize workspace attribute first1108        self._workspace_id = ls_utils.get_workspace_id(workspace_id_)1109        # Store custom headers1110        self._custom_headers = headers or {}1111 1112        if self._write_api_urls:1113            self.api_url = next(iter(self._write_api_urls))1114            self._oauth_access_token = None1115            self._profile_auth = None1116            self._profile_auth_headers = {}1117            self.api_key = self._write_api_urls[self.api_url]1118        else:1119            self.api_url = ls_utils.get_api_url(api_url_)1120            if use_profile_oauth:1121                self._profile_auth = _profiles.ProfileAuth(1122                    profile_config,1123                    api_key_header=X_API_KEY,1124                )1125                self._profile_auth_headers = self._profile_auth.current_auth_headers()1126                self._oauth_access_token = self._profile_auth.oauth_access_token1127            self.api_key = ls_utils.get_api_key(api_key_)1128            _validate_api_key_if_hosted(1129                self.api_url,1130                self.api_key1131                or self._oauth_access_token1132                or (1133                    "profile-auth"1134                    if self._profile_auth is not None and self._profile_auth.has_auth1135                    else None1136                ),1137                tracing_mode=resolved_mode,1138            )1139            self._write_api_urls = {self.api_url: self.api_key}1140        self.retry_config = retry_config or _default_retry_config()1141        self.timeout_ms = (1142            (timeout_ms, timeout_ms)1143            if isinstance(timeout_ms, int)1144            else (timeout_ms or (10_000, 60_000))1145        )1146        self._timeout = (self.timeout_ms[0] / 1000, self.timeout_ms[1] / 1000)1147        self._web_url = web_url1148        self._tenant_id: Optional[uuid.UUID] = None1149        # Create a session and register a finalizer to close it1150        session_ = session if session else requests.Session()1151        self.session = session_1152        self._info = (1153            info1154            if info is None or isinstance(info, ls_schemas.LangSmithInfo)1155            else ls_schemas.LangSmithInfo(**info)1156        )1157        weakref.finalize(self, close_session, self.session)1158        atexit.register(close_session, session_)1159        self.compressed_traces: Optional[CompressedTraces] = None1160        self._data_available_event: Optional[threading.Event] = None1161        self._futures: Optional[weakref.WeakSet[cf.Future]] = None1162        self._run_ops_buffer: list[tuple[str, dict, dict[str, Optional[str]]]] = []1163        self._run_ops_buffer_lock = threading.Lock()1164        self.otel_exporter: Optional[OTELExporter] = None1165        self._max_batch_size_bytes = max_batch_size_bytes1166        self._multipart_disabled: bool = False1167        self._use_daemon_threads = ls_utils.get_env_var("USE_DAEMON") == "true"1168 1169        if resolved_mode in ("otel", "hybrid"):1170            try:1171                (1172                    otel_trace,1173                    set_span_in_context,1174                    get_otlp_tracer_provider,1175                    OTELExporter,1176                ) = _import_otel()1177 1178                existing_provider = otel_trace.get_tracer_provider()1179                tracer = existing_provider.get_tracer(__name__)1180                if otel_tracer_provider is None:1181                    if not (1182                        isinstance(existing_provider, otel_trace.ProxyTracerProvider)1183                        and hasattr(tracer, "_tracer")1184                        and isinstance(1185                            cast(1186                                otel_trace.ProxyTracer,  # type: ignore[attr-defined, name-defined]1187                                tracer,1188                            )._tracer,1189                            otel_trace.NoOpTracer,1190                        )1191                    ):1192                        otel_tracer_provider = cast(TracerProvider, existing_provider)1193                    else:1194                        otel_tracer_provider = get_otlp_tracer_provider()1195                        otel_trace.set_tracer_provider(otel_tracer_provider)1196 1197                self.otel_exporter = OTELExporter(tracer_provider=otel_tracer_provider)1198                self._otel_trace = otel_trace1199                self._set_span_in_context = set_span_in_context1200 

Showing the first 1,200 of 11118 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai