mirror of
https://github.com/langchain-ai/langchain.git
synced 2026-10-05 09:25:14 +03:00
feat(core): accept address inside LangChainTracer
This commit is contained in:
7 files changed
+636
-65
No files matched your search
@@ -59,6 +59,7 @@ def trace_as_chain_group(
|
||||
*,
|
||||
inputs: dict[str, Any] | None = None,
|
||||
project_name: str | None = None,
|
||||
address: str | None = None,
|
||||
example_id: str | UUID | None = None,
|
||||
run_id: UUID | None = None,
|
||||
tags: list[str] | None = None,
|
||||
@@ -74,6 +75,10 @@ def trace_as_chain_group(
|
||||
callback_manager: The callback manager to use.
|
||||
inputs: The inputs to the chain group.
|
||||
project_name: The name of the project.
|
||||
address: The agent address to send traces to instead of a project.
|
||||
|
||||
!!! warning "Beta"
|
||||
Agent addressing must be enabled for the LangSmith workspace.
|
||||
example_id: The ID of the example.
|
||||
run_id: The ID of the run.
|
||||
tags: The inheritable tags to apply to all runs.
|
||||
@@ -101,7 +106,7 @@ def trace_as_chain_group(
|
||||
)
|
||||
|
||||
cb = _get_trace_callbacks(
|
||||
project_name, example_id, callback_manager=callback_manager
|
||||
project_name, example_id, callback_manager=callback_manager, address=address
|
||||
)
|
||||
cm = CallbackManager.configure(
|
||||
inheritable_callbacks=cb,
|
||||
@@ -139,6 +144,7 @@ async def atrace_as_chain_group(
|
||||
*,
|
||||
inputs: dict[str, Any] | None = None,
|
||||
project_name: str | None = None,
|
||||
address: str | None = None,
|
||||
example_id: str | UUID | None = None,
|
||||
run_id: UUID | None = None,
|
||||
tags: list[str] | None = None,
|
||||
@@ -155,6 +161,10 @@ async def atrace_as_chain_group(
|
||||
other callback behavior.
|
||||
inputs: The inputs to the chain group.
|
||||
project_name: The name of the project.
|
||||
address: The agent address to send traces to instead of a project.
|
||||
|
||||
!!! warning "Beta"
|
||||
Agent addressing must be enabled for the LangSmith workspace.
|
||||
example_id: The ID of the example.
|
||||
run_id: The ID of the run.
|
||||
tags: The inheritable tags to apply to all runs.
|
||||
@@ -184,7 +194,7 @@ async def atrace_as_chain_group(
|
||||
)
|
||||
|
||||
cb = _get_trace_callbacks(
|
||||
project_name, example_id, callback_manager=callback_manager
|
||||
project_name, example_id, callback_manager=callback_manager, address=address
|
||||
)
|
||||
cm = AsyncCallbackManager.configure(
|
||||
inheritable_callbacks=cb, inheritable_tags=tags, inheritable_metadata=metadata
|
||||
@@ -2427,7 +2437,7 @@ def _configure(
|
||||
|
||||
from langchain_core.tracers.context import ( # noqa: PLC0415
|
||||
_configure_hooks,
|
||||
_get_tracer_project,
|
||||
_get_tracer_kwargs,
|
||||
_tracing_v2_is_enabled,
|
||||
tracing_v2_callback_var,
|
||||
)
|
||||
@@ -2505,7 +2515,6 @@ def _configure(
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
tracer_project = _get_tracer_project()
|
||||
debug = _get_debug()
|
||||
if verbose or debug or tracing_v2_enabled_:
|
||||
if verbose and not any(
|
||||
@@ -2529,16 +2538,7 @@ def _configure(
|
||||
callback_manager.add_handler(tracer_v2)
|
||||
else:
|
||||
try:
|
||||
handler = LangChainTracer(
|
||||
project_name=tracer_project,
|
||||
client=(
|
||||
run_tree.client
|
||||
if run_tree is not None
|
||||
else tracing_context["client"]
|
||||
),
|
||||
tags=tracing_tags,
|
||||
metadata=tracing_metadata,
|
||||
)
|
||||
handler = LangChainTracer(**_get_tracer_kwargs())
|
||||
callback_manager.add_handler(handler)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
|
||||
@@ -43,16 +43,22 @@ def tracing_v2_enabled(
|
||||
example_id: str | UUID | None = None,
|
||||
tags: list[str] | None = None,
|
||||
client: LangSmithClient | None = None,
|
||||
address: str | None = None,
|
||||
) -> Generator[LangChainTracer, None, None]:
|
||||
"""Instruct LangChain to log all runs in context to LangSmith.
|
||||
|
||||
Args:
|
||||
project_name: The name of the project.
|
||||
|
||||
Defaults to `'default'`.
|
||||
Resolved together with `address` from LangSmith config and the
|
||||
environment when no destination is supplied.
|
||||
example_id: The ID of the example.
|
||||
tags: The tags to add to the run.
|
||||
client: The client of the langsmith.
|
||||
address: The agent address to send traces to instead of a project.
|
||||
|
||||
!!! warning "Beta"
|
||||
Agent addressing must be enabled for the LangSmith workspace.
|
||||
|
||||
Yields:
|
||||
The LangChain tracer.
|
||||
@@ -72,6 +78,7 @@ def tracing_v2_enabled(
|
||||
cb = LangChainTracer(
|
||||
example_id=example_id,
|
||||
project_name=project_name,
|
||||
address=address,
|
||||
tags=tags,
|
||||
client=client,
|
||||
)
|
||||
@@ -106,12 +113,13 @@ def _get_trace_callbacks(
|
||||
project_name: str | None = None,
|
||||
example_id: str | UUID | None = None,
|
||||
callback_manager: CallbackManager | AsyncCallbackManager | None = None,
|
||||
*,
|
||||
address: str | None = None,
|
||||
) -> Callbacks:
|
||||
if _tracing_v2_is_enabled():
|
||||
project_name_ = project_name or _get_tracer_project()
|
||||
tracer = tracing_v2_callback_var.get() or LangChainTracer(
|
||||
project_name=project_name_,
|
||||
example_id=example_id,
|
||||
**_get_tracer_kwargs(project_name, address=address),
|
||||
)
|
||||
if callback_manager is None:
|
||||
cb = cast("Callbacks", [tracer])
|
||||
@@ -135,27 +143,29 @@ def _tracing_v2_is_enabled() -> bool | Literal["local"]:
|
||||
return ls_utils.tracing_is_enabled()
|
||||
|
||||
|
||||
def _get_tracer_project() -> str:
|
||||
tracing_context = ls_rh.get_tracing_context()
|
||||
run_tree = tracing_context["parent"]
|
||||
if run_tree is None and tracing_context["project_name"] is not None:
|
||||
return cast("str", tracing_context["project_name"])
|
||||
return getattr(
|
||||
run_tree,
|
||||
"session_name",
|
||||
getattr(
|
||||
# Note, if people are trying to nest @traceable functions and the
|
||||
# tracing_v2_enabled context manager, this will likely mess up the
|
||||
# tree structure.
|
||||
tracing_v2_callback_var.get(),
|
||||
"project",
|
||||
# Have to set this to a string even though it always will return
|
||||
# a string because `get_tracer_project` technically can return
|
||||
# None, but only when a specific argument is supplied.
|
||||
# Therefore, this just tricks the mypy type checker
|
||||
str(ls_utils.get_tracer_project()),
|
||||
),
|
||||
)
|
||||
def _get_tracer_kwargs(
|
||||
project_name: str | None = None, *, address: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Get tracer defaults from the current LangSmith context.
|
||||
|
||||
Forward project and address together so an addressed parent never picks up
|
||||
an ambient project. Leave environment defaults and validation to RunTree.
|
||||
"""
|
||||
context = ls_rh.get_tracing_context()
|
||||
parent = context["parent"]
|
||||
if project_name is not None or address is not None:
|
||||
project = project_name
|
||||
elif parent is not None:
|
||||
project, address = parent.session_name, parent.address
|
||||
else:
|
||||
project, address = context["project_name"], context["address"]
|
||||
return {
|
||||
"project_name": project,
|
||||
"address": address,
|
||||
"client": parent.client if parent is not None else context["client"],
|
||||
"tags": context["tags"],
|
||||
"metadata": context["metadata"],
|
||||
}
|
||||
|
||||
|
||||
_configure_hooks: list[
|
||||
|
||||
@@ -174,6 +174,14 @@ class _TracerCore(ABC):
|
||||
self.order_map[run.id] = (run.trace_id, run.dotted_order)
|
||||
self.run_map[str(run.id)] = run
|
||||
|
||||
def _get_run_kwargs(
|
||||
self,
|
||||
parent_run_id: UUID | None, # noqa: ARG002
|
||||
extra: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Return construction arguments for a run."""
|
||||
return {"extra": extra}
|
||||
|
||||
def _get_run(self, run_id: UUID, run_type: str | set[str] | None = None) -> Run:
|
||||
try:
|
||||
run = self.run_map[str(run_id)]
|
||||
@@ -226,7 +234,7 @@ class _TracerCore(ABC):
|
||||
parent_run_id=parent_run_id,
|
||||
serialized=serialized,
|
||||
inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]},
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
# WARNING: This is valid ONLY for streaming_events.
|
||||
@@ -258,7 +266,7 @@ class _TracerCore(ABC):
|
||||
serialized=serialized,
|
||||
# TODO: Figure out how to expose kwargs here
|
||||
inputs={"prompts": prompts},
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
run_type="llm",
|
||||
@@ -408,7 +416,7 @@ class _TracerCore(ABC):
|
||||
parent_run_id=parent_run_id,
|
||||
serialized=serialized,
|
||||
inputs=self._get_chain_inputs(inputs),
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
child_runs=[],
|
||||
@@ -504,7 +512,7 @@ class _TracerCore(ABC):
|
||||
serialized=serialized,
|
||||
# Wrapping in dict since Run requires a dict object.
|
||||
inputs=inputs,
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
child_runs=[],
|
||||
@@ -561,7 +569,7 @@ class _TracerCore(ABC):
|
||||
parent_run_id=parent_run_id,
|
||||
serialized=serialized,
|
||||
inputs={"query": query},
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
tags=tags,
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, cast
|
||||
from uuid import UUID
|
||||
|
||||
from langsmith import Client, get_tracing_context
|
||||
from langsmith import address as ls_address
|
||||
from langsmith import run_trees as rt
|
||||
from langsmith import utils as ls_utils
|
||||
from tenacity import (
|
||||
@@ -144,6 +145,7 @@ class LangChainTracer(BaseTracer):
|
||||
tags: list[str] | None = None,
|
||||
*,
|
||||
metadata: Mapping[str, str] | None = None,
|
||||
address: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize the LangChain tracer.
|
||||
@@ -152,7 +154,8 @@ class LangChainTracer(BaseTracer):
|
||||
example_id: The example ID.
|
||||
project_name: The project name.
|
||||
|
||||
Defaults to the tracer project.
|
||||
Resolved together with `address` from LangSmith config and the
|
||||
environment when no destination is supplied.
|
||||
client: The client.
|
||||
|
||||
Defaults to the global client.
|
||||
@@ -162,13 +165,19 @@ class LangChainTracer(BaseTracer):
|
||||
metadata: Additional metadata to include if it isn't already in the run.
|
||||
|
||||
Defaults to None.
|
||||
address: The agent address to send traces to instead of a project.
|
||||
|
||||
!!! warning "Beta"
|
||||
Agent addressing is in beta and must be enabled for the
|
||||
LangSmith workspace.
|
||||
**kwargs: Additional keyword arguments.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.example_id = (
|
||||
UUID(example_id) if isinstance(example_id, str) else example_id
|
||||
)
|
||||
self.project_name = project_name or ls_utils.get_tracer_project()
|
||||
self.project_name = project_name
|
||||
self.address = ls_address.parse(address) if address is not None else None
|
||||
self.client = client or get_client()
|
||||
self.tags = tags or []
|
||||
self.latest_run: Run | None = None
|
||||
@@ -209,6 +218,7 @@ class LangChainTracer(BaseTracer):
|
||||
return self.__class__(
|
||||
example_id=self.example_id,
|
||||
project_name=self.project_name,
|
||||
address=self.address,
|
||||
client=self.client,
|
||||
tags=merged_tags,
|
||||
metadata=merged_metadata,
|
||||
@@ -217,9 +227,25 @@ class LangChainTracer(BaseTracer):
|
||||
_external_run_ids=self._external_run_ids,
|
||||
)
|
||||
|
||||
def _get_run_kwargs(
|
||||
self, parent_run_id: UUID | None, extra: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Forward explicit or inherited destinations to RunTree."""
|
||||
parent = self.run_map.get(str(parent_run_id)) if parent_run_id else None
|
||||
if parent is not None:
|
||||
return {
|
||||
"session_name": parent.session_name,
|
||||
"address": parent.address,
|
||||
"replicas": parent.replicas,
|
||||
"extra": extra,
|
||||
}
|
||||
return {
|
||||
"session_name": self.project_name,
|
||||
"address": self.address,
|
||||
"extra": extra,
|
||||
}
|
||||
|
||||
def _start_trace(self, run: Run) -> None:
|
||||
if self.project_name:
|
||||
run.session_name = self.project_name
|
||||
if self.tags is not None:
|
||||
if run.tags:
|
||||
run.tags = sorted(set(run.tags + self.tags))
|
||||
@@ -267,7 +293,7 @@ class LangChainTracer(BaseTracer):
|
||||
parent_run_id=parent_run_id,
|
||||
serialized=serialized,
|
||||
inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]},
|
||||
extra=kwargs,
|
||||
**self._get_run_kwargs(parent_run_id, kwargs),
|
||||
events=[{"name": "start", "time": start_time}],
|
||||
start_time=start_time,
|
||||
run_type="llm",
|
||||
|
||||
@@ -24,7 +24,7 @@ classifiers = [
|
||||
version = "1.6.6"
|
||||
requires-python = ">=3.10.0,<4.0.0"
|
||||
dependencies = [
|
||||
"langsmith>=0.3.45,<1.0.0",
|
||||
"langsmith>=0.14.4,<1.0.0",
|
||||
"httpx>=0.23.0,<1.0.0",
|
||||
"tenacity!=8.4.0,>=8.1.0,<10.0.0",
|
||||
"jsonpatch>=1.33.0,<2.0.0",
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
"""Destination resolution for LangSmith tracing."""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import nullcontext
|
||||
from itertools import product
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from langsmith import Client, address, configure, tracing_context
|
||||
from langsmith.address import EnvAddressError
|
||||
from langsmith.run_trees import RunTree, WriteReplica
|
||||
from langsmith.utils import LangSmithUserError, get_env_var, get_tracer_project
|
||||
|
||||
from langchain_core.callbacks.manager import (
|
||||
CallbackManager,
|
||||
atrace_as_chain_group,
|
||||
trace_as_chain_group,
|
||||
)
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.outputs import LLMResult
|
||||
from langchain_core.tracers.context import (
|
||||
_get_trace_callbacks,
|
||||
_get_tracer_kwargs,
|
||||
tracing_v2_enabled,
|
||||
)
|
||||
from langchain_core.tracers.langchain import LangChainTracer
|
||||
|
||||
ADDRESS = address.agent("code-agent", "production")
|
||||
ENV_ADDRESS = address.agent("env-agent", "staging")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_destination(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||
for name in (
|
||||
"HOSTED_LANGSERVE_PROJECT_NAME",
|
||||
"LANGSMITH_PROJECT",
|
||||
"LANGCHAIN_PROJECT",
|
||||
"LANGCHAIN_SESSION",
|
||||
"LANGSMITH_AGENT_ID",
|
||||
"LANGSMITH_AGENT_ENVIRONMENT",
|
||||
):
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
get_env_var.cache_clear() # type: ignore[attr-defined]
|
||||
get_tracer_project.cache_clear()
|
||||
yield
|
||||
get_env_var.cache_clear() # type: ignore[attr-defined]
|
||||
get_tracer_project.cache_clear()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("code_project", "code_address", "env_project", "env_address"),
|
||||
list(product((False, True), repeat=4)),
|
||||
)
|
||||
@pytest.mark.parametrize("run_type", ["llm", "chat", "chain", "tool", "retriever"])
|
||||
def test_destination_matrix(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
*,
|
||||
code_project: bool,
|
||||
code_address: bool,
|
||||
env_project: bool,
|
||||
env_address: bool,
|
||||
run_type: str,
|
||||
) -> None:
|
||||
if env_project:
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "env-project")
|
||||
if env_address:
|
||||
monkeypatch.setenv("LANGSMITH_AGENT_ID", "env-agent")
|
||||
monkeypatch.setenv("LANGSMITH_AGENT_ENVIRONMENT", "staging")
|
||||
get_env_var.cache_clear() # type: ignore[attr-defined]
|
||||
get_tracer_project.cache_clear()
|
||||
client = MagicMock(spec=Client)
|
||||
kwargs: dict[str, Any] = {
|
||||
"client": client,
|
||||
"project_name": "code-project" if code_project else None,
|
||||
"address": ADDRESS if code_address else None,
|
||||
}
|
||||
tracer = LangChainTracer(**kwargs)
|
||||
run_id = uuid4()
|
||||
if code_project and code_address:
|
||||
with pytest.raises(LangSmithUserError, match="not both"):
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
return
|
||||
invalid_env = not (code_project or code_address) and env_project and env_address
|
||||
if invalid_env:
|
||||
with pytest.raises(EnvAddressError):
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
return
|
||||
if run_type == "llm":
|
||||
tracer.on_llm_start({}, ["hello"], run_id=run_id)
|
||||
tracer.on_llm_end(LLMResult(generations=[]), run_id=run_id)
|
||||
elif run_type == "chat":
|
||||
tracer.on_chat_model_start({}, [[HumanMessage("hello")]], run_id=run_id)
|
||||
tracer.on_llm_end(LLMResult(generations=[]), run_id=run_id)
|
||||
elif run_type == "chain":
|
||||
tracer.on_chain_start({}, {"input": "hello"}, run_id=run_id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
elif run_type == "tool":
|
||||
tracer.on_tool_start({}, "hello", run_id=run_id)
|
||||
tracer.on_tool_end("ok", run_id=run_id)
|
||||
else:
|
||||
tracer.on_retriever_start({}, "hello", run_id=run_id)
|
||||
tracer.on_retriever_end([], run_id=run_id)
|
||||
expected_address = (
|
||||
ADDRESS
|
||||
if code_address
|
||||
else (ENV_ADDRESS if env_address and not code_project else None)
|
||||
)
|
||||
expected_project = (
|
||||
None
|
||||
if expected_address
|
||||
else (
|
||||
"code-project"
|
||||
if code_project
|
||||
else "env-project"
|
||||
if env_project
|
||||
else "default"
|
||||
)
|
||||
)
|
||||
client.create_run.assert_called_once()
|
||||
client.update_run.assert_called_once()
|
||||
for payload in (
|
||||
client.create_run.call_args.kwargs,
|
||||
client.update_run.call_args.kwargs,
|
||||
):
|
||||
assert payload.get("session_name") == expected_project
|
||||
assert payload.get("address") == expected_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"destination", [{"address": ADDRESS}, {"project_name": "configured"}]
|
||||
)
|
||||
@pytest.mark.parametrize("mode", ["context", "configure", "parent"])
|
||||
def test_ambient_destination(destination: dict[str, str], mode: str) -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
if mode == "configure":
|
||||
configure(
|
||||
enabled=True,
|
||||
client=client,
|
||||
project_name=destination.get("project_name"),
|
||||
address=destination.get("address"),
|
||||
)
|
||||
parent = (
|
||||
RunTree(name="parent", ls_client=client, **destination)
|
||||
if mode == "parent"
|
||||
else None
|
||||
)
|
||||
context = {**destination} if mode == "context" else {}
|
||||
try:
|
||||
scope = (
|
||||
nullcontext()
|
||||
if mode == "configure"
|
||||
else tracing_context(
|
||||
enabled=True,
|
||||
client=client,
|
||||
parent=parent,
|
||||
project_name=context.get("project_name"),
|
||||
address=context.get("address"),
|
||||
)
|
||||
)
|
||||
with scope:
|
||||
manager = CallbackManager.configure()
|
||||
run = manager.on_chain_start({}, {})
|
||||
run.on_chain_end({})
|
||||
payload = client.create_run.call_args.kwargs
|
||||
assert payload.get("address") == destination.get("address")
|
||||
assert payload.get("session_name") == destination.get("project_name")
|
||||
finally:
|
||||
if mode == "configure":
|
||||
configure(project_name=None, address=None, enabled=None, client=None)
|
||||
|
||||
|
||||
def test_copy_and_context_address() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
with tracing_v2_enabled(address=ADDRESS, client=client) as tracer:
|
||||
copied = tracer.copy_with_metadata_defaults(metadata={"foo": "bar"})
|
||||
assert copied.address == ADDRESS
|
||||
assert copied.project_name is None
|
||||
manager = CallbackManager.configure()
|
||||
run = manager.on_chain_start({}, {})
|
||||
run.on_chain_end({})
|
||||
assert client.create_run.call_args.kwargs["address"] == ADDRESS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("address", ["invalid", {"agent_id": "agent"}])
|
||||
def test_invalid_address(address: Any) -> None:
|
||||
with pytest.raises(LangSmithUserError):
|
||||
LangChainTracer(address=address, client=MagicMock(spec=Client))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["LANGSMITH_AGENT_ID", "LANGSMITH_AGENT_ENVIRONMENT"])
|
||||
def test_incomplete_environment(monkeypatch: pytest.MonkeyPatch, name: str) -> None:
|
||||
monkeypatch.setenv(name, "staging")
|
||||
get_env_var.cache_clear() # type: ignore[attr-defined]
|
||||
get_tracer_project.cache_clear()
|
||||
client = MagicMock(spec=Client)
|
||||
tracer = LangChainTracer(client=client)
|
||||
run_id = uuid4()
|
||||
with pytest.raises(EnvAddressError):
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
|
||||
|
||||
def test_parent_destination_overrides_tracer() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
tracer = LangChainTracer(project_name="other-project", client=client)
|
||||
parent = RunTree(name="parent", address=ADDRESS, ls_client=client)
|
||||
tracer.run_map[str(parent.id)] = parent
|
||||
tracer.order_map[parent.id] = (parent.trace_id, parent.dotted_order)
|
||||
run_id = uuid4()
|
||||
tracer.on_chain_start({}, {}, run_id=run_id, parent_run_id=parent.id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
assert parent.child_runs[0].address == ADDRESS
|
||||
assert parent.child_runs[0].session_name is None
|
||||
assert client.create_run.call_args.kwargs["address"] == ADDRESS
|
||||
|
||||
|
||||
def test_context_conflicting_arguments() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
with (
|
||||
tracing_v2_enabled(
|
||||
project_name="project", address=ADDRESS, client=client
|
||||
) as tracer,
|
||||
pytest.raises(LangSmithUserError, match="not both"),
|
||||
):
|
||||
tracer.on_chain_start({}, {}, run_id=uuid4())
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
|
||||
|
||||
def test_trace_callbacks_resolve_environment_address(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("LANGSMITH_TRACING", "true")
|
||||
monkeypatch.setenv("LANGSMITH_AGENT_ID", "env-agent")
|
||||
monkeypatch.setenv("LANGSMITH_AGENT_ENVIRONMENT", "staging")
|
||||
get_env_var.cache_clear() # type: ignore[attr-defined]
|
||||
get_tracer_project.cache_clear()
|
||||
client = MagicMock(spec=Client)
|
||||
with tracing_context(client=client):
|
||||
callbacks = _get_trace_callbacks()
|
||||
assert isinstance(callbacks, list)
|
||||
tracer = callbacks[0]
|
||||
assert isinstance(tracer, LangChainTracer)
|
||||
run_id = uuid4()
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
assert client.create_run.call_args.kwargs["address"] == ENV_ADDRESS
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"destination", [{"address": ADDRESS}, {"project_name": "parent"}]
|
||||
)
|
||||
def test_tracer_kwargs_parent_destination(destination: dict[str, str]) -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
parent = RunTree(name="parent", ls_client=client, **destination)
|
||||
with tracing_context(
|
||||
parent=parent, project_name="ambient", tags=["tag"], metadata={"key": "value"}
|
||||
):
|
||||
kwargs = _get_tracer_kwargs()
|
||||
assert kwargs["project_name"] == destination.get("project_name")
|
||||
assert kwargs["address"] == destination.get("address")
|
||||
assert kwargs["client"] is client
|
||||
assert kwargs["tags"] == ["tag"]
|
||||
assert kwargs["metadata"] == {"key": "value"}
|
||||
explicit = _get_tracer_kwargs("explicit")
|
||||
assert explicit["project_name"] == "explicit"
|
||||
assert explicit["address"] is None
|
||||
|
||||
|
||||
def test_tracer_kwargs_leave_environment_to_sdk(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("LANGSMITH_PROJECT", "env-project")
|
||||
with tracing_context():
|
||||
kwargs = _get_tracer_kwargs()
|
||||
assert kwargs["project_name"] is None
|
||||
assert kwargs["address"] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project_name", [None, "explicit"])
|
||||
def test_explicit_address_kwargs(project_name: str | None) -> None:
|
||||
with tracing_context(project_name="ambient"):
|
||||
kwargs = _get_tracer_kwargs(project_name, address=ADDRESS)
|
||||
assert kwargs["project_name"] == project_name
|
||||
assert kwargs["address"] == ADDRESS
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project_name", [None, "conflicting"])
|
||||
def test_trace_callbacks_explicit_address(project_name: str | None) -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
with tracing_context(enabled=True, client=client, project_name="ambient"):
|
||||
callbacks = _get_trace_callbacks(project_name, address=ADDRESS)
|
||||
assert isinstance(callbacks, list)
|
||||
tracer = callbacks[0]
|
||||
assert isinstance(tracer, LangChainTracer)
|
||||
if project_name is not None:
|
||||
with pytest.raises(LangSmithUserError, match="not both"):
|
||||
tracer.on_chain_start({}, {}, run_id=uuid4())
|
||||
client.create_run.assert_not_called()
|
||||
else:
|
||||
run_id = uuid4()
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
assert client.create_run.call_args.kwargs["address"] == ADDRESS
|
||||
|
||||
|
||||
def test_chain_group_explicit_address() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
with (
|
||||
tracing_context(enabled=True, client=client, project_name="ambient"),
|
||||
trace_as_chain_group("group", address=ADDRESS) as manager,
|
||||
):
|
||||
run = manager.on_chain_start({}, {})
|
||||
run.on_chain_end({})
|
||||
assert client.create_run.call_count == 2
|
||||
for call in client.create_run.call_args_list:
|
||||
assert call.kwargs["address"] == ADDRESS
|
||||
assert call.kwargs.get("session_name") is None
|
||||
|
||||
|
||||
async def test_async_chain_group_explicit_address() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
with tracing_context(enabled=True, client=client, project_name="ambient"):
|
||||
async with atrace_as_chain_group("group", address=ADDRESS) as manager:
|
||||
run = await manager.on_chain_start({}, {})
|
||||
await run.on_chain_end({})
|
||||
assert client.create_run.call_count == 2
|
||||
for call in client.create_run.call_args_list:
|
||||
assert call.kwargs["address"] == ADDRESS
|
||||
assert call.kwargs.get("session_name") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("root_address", [False, True])
|
||||
@pytest.mark.parametrize("replica_mode", ["inherit", "project", "address"])
|
||||
def test_replica_destinations(*, root_address: bool, replica_mode: str) -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
replica_client = MagicMock(spec=Client)
|
||||
replica: WriteReplica = {"client": replica_client, "primary": False}
|
||||
replica_address = address.agent("replica-agent", "staging")
|
||||
if replica_mode == "project":
|
||||
replica["project_name"] = "replica-project"
|
||||
elif replica_mode == "address":
|
||||
replica["address"] = replica_address
|
||||
tracer = LangChainTracer(
|
||||
project_name=None if root_address else "root-project",
|
||||
address=ADDRESS if root_address else None,
|
||||
client=client,
|
||||
)
|
||||
parent_id, child_id = uuid4(), uuid4()
|
||||
with tracing_context(replicas=[replica]):
|
||||
tracer.on_chain_start({}, {}, run_id=parent_id)
|
||||
tracer.on_chain_start({}, {}, run_id=child_id, parent_run_id=parent_id)
|
||||
tracer.on_chain_end({}, run_id=child_id)
|
||||
tracer.on_chain_end({}, run_id=parent_id)
|
||||
expected_address = (
|
||||
replica_address
|
||||
if replica_mode == "address"
|
||||
else ADDRESS
|
||||
if replica_mode == "inherit" and root_address
|
||||
else None
|
||||
)
|
||||
expected_project = (
|
||||
"replica-project"
|
||||
if replica_mode == "project"
|
||||
else "root-project"
|
||||
if replica_mode == "inherit" and not root_address
|
||||
else None
|
||||
)
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
assert replica_client.create_run.call_count == 2
|
||||
assert replica_client.update_run.call_count == 2
|
||||
creates = [call.kwargs for call in replica_client.create_run.call_args_list]
|
||||
updates = [call.kwargs for call in replica_client.update_run.call_args_list]
|
||||
for payload in creates + updates:
|
||||
assert payload.get("address") == expected_address
|
||||
assert payload.get("session_name") == expected_project
|
||||
parent, child = creates
|
||||
assert parent["id"] != parent_id
|
||||
assert child["id"] != child_id
|
||||
assert child["parent_run_id"] == parent["id"]
|
||||
assert child["trace_id"] == parent["trace_id"]
|
||||
assert child["dotted_order"].startswith(parent["dotted_order"] + ".")
|
||||
assert {payload["run_id"] for payload in updates} == {
|
||||
payload["id"] for payload in creates
|
||||
}
|
||||
|
||||
|
||||
def test_replica_preserves_primary_ids() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
primary_client = MagicMock(spec=Client)
|
||||
secondary_client = MagicMock(spec=Client)
|
||||
secondary_address = address.agent("secondary-agent", "staging")
|
||||
run_id = uuid4()
|
||||
tracer = LangChainTracer(address=ADDRESS, client=client)
|
||||
with tracing_context(
|
||||
replicas=[
|
||||
{"client": primary_client, "primary": True},
|
||||
{"client": secondary_client, "address": secondary_address},
|
||||
]
|
||||
):
|
||||
tracer.on_chain_start({}, {}, run_id=run_id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
assert primary_client.create_run.call_args.kwargs["id"] == run_id
|
||||
assert primary_client.update_run.call_args.kwargs["run_id"] == run_id
|
||||
assert primary_client.create_run.call_args.kwargs["address"] == ADDRESS
|
||||
secondary_id = secondary_client.create_run.call_args.kwargs["id"]
|
||||
assert secondary_id != run_id
|
||||
assert secondary_client.update_run.call_args.kwargs["run_id"] == secondary_id
|
||||
assert secondary_client.create_run.call_args.kwargs["address"] == secondary_address
|
||||
client.create_run.assert_not_called()
|
||||
|
||||
|
||||
def test_replica_conflicting_destination() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
tracer = LangChainTracer(address=ADDRESS, client=client)
|
||||
with (
|
||||
tracing_context(replicas=[{"project_name": "conflict", "address": ADDRESS}]),
|
||||
pytest.raises(LangSmithUserError, match="not both"),
|
||||
):
|
||||
tracer.on_chain_start({}, {}, run_id=uuid4())
|
||||
client.create_run.assert_not_called()
|
||||
client.update_run.assert_not_called()
|
||||
|
||||
|
||||
def test_addressed_parent_replica_inherited_outside_context() -> None:
|
||||
client = MagicMock(spec=Client)
|
||||
replica_client = MagicMock(spec=Client)
|
||||
parent = RunTree(
|
||||
name="parent",
|
||||
address=ADDRESS,
|
||||
ls_client=client,
|
||||
replicas=[{"client": replica_client, "project_name": "replica-project"}],
|
||||
)
|
||||
tracer = LangChainTracer(client=client)
|
||||
tracer.run_map[str(parent.id)] = parent
|
||||
tracer.order_map[parent.id] = (parent.trace_id, parent.dotted_order)
|
||||
run_id = uuid4()
|
||||
tracer.on_chain_start({}, {}, run_id=run_id, parent_run_id=parent.id)
|
||||
tracer.on_chain_end({}, run_id=run_id)
|
||||
replica_client.create_run.assert_called_once()
|
||||
replica_client.update_run.assert_called_once()
|
||||
assert (
|
||||
replica_client.create_run.call_args.kwargs["session_name"] == "replica-project"
|
||||
)
|
||||
assert replica_client.create_run.call_args.kwargs.get("address") is None
|
||||
assert parent.child_runs[0].address == ADDRESS
|
||||
client.create_run.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_address", [False, True])
|
||||
def test_chain_group_tracing_disabled(*, use_address: bool) -> None:
|
||||
with (
|
||||
tracing_context(enabled=False),
|
||||
trace_as_chain_group("group", address=ADDRESS if use_address else None),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_address", [False, True])
|
||||
async def test_async_chain_group_tracing_disabled(*, use_address: bool) -> None:
|
||||
with tracing_context(enabled=False):
|
||||
async with atrace_as_chain_group(
|
||||
"group", address=ADDRESS if use_address else None
|
||||
):
|
||||
pass
|
||||
Generated
+70
-16
@@ -8,8 +8,10 @@ resolution-markers = [
|
||||
"python_full_version == '3.14.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten')",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten')",
|
||||
"python_full_version < '3.11' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version < '3.11' and platform_python_implementation != 'PyPy'",
|
||||
]
|
||||
@@ -618,6 +620,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore2"
|
||||
version = "2.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
{ name = "truststore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
@@ -634,12 +649,38 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.15"
|
||||
name = "httpx2"
|
||||
version = "2.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
|
||||
dependencies = [
|
||||
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "httpcore2", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" },
|
||||
{ name = "idna" },
|
||||
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx2-jsfetch"
|
||||
version = "1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -713,8 +754,10 @@ resolution-markers = [
|
||||
"python_full_version == '3.14.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten')",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten')",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -1127,7 +1170,7 @@ requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
|
||||
{ name = "jsonpatch", specifier = ">=1.33.0,<2.0.0" },
|
||||
{ name = "langchain-protocol", specifier = ">=0.0.17" },
|
||||
{ name = "langsmith", specifier = ">=0.3.45,<1.0.0" },
|
||||
{ name = "langsmith", specifier = ">=0.14.4,<1.0.0" },
|
||||
{ name = "packaging", specifier = ">=23.2.0" },
|
||||
{ name = "pydantic", specifier = ">=2.7.4,<3.0.0" },
|
||||
{ name = "pyyaml", specifier = ">=5.3.0,<7.0.0" },
|
||||
@@ -1227,7 +1270,7 @@ typing = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-text-splitters"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = { directory = "../text-splitters" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -1269,12 +1312,12 @@ typing = [
|
||||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.11.1"
|
||||
version = "0.14.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "distro" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx2" },
|
||||
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
@@ -1287,9 +1330,9 @@ dependencies = [
|
||||
{ name = "xxhash" },
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/57/7b6c11080c9e082ebf1456a2e2372fae8f23a85a5ae2869bdd5ab9a6507d/langsmith-0.11.1.tar.gz", hash = "sha256:47998977366acb3ba3093881fd465cbf11a5f8c2f4e87e40a17dda203f6dedf4", size = 4814724, upload-time = "2026-08-19T15:47:44.116Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/b9/10243797b942e7475ae21545fde1be484958672ae5c2f377f781c761a375/langsmith-0.14.4.tar.gz", hash = "sha256:a340199fcd2af60358f09dd1d7011bf7282f0d46a98aaa101bc7a521746da764", size = 4968005, upload-time = "2026-10-02T17:16:37.074Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/85/0ad6df25588122760b2b40ec182eafc55532c9e66015c83e16675bd34a29/langsmith-0.11.1-py3-none-any.whl", hash = "sha256:cfc3437a9cf27440cd0095c24df945edbceb6df10b579bc8b3980b4ad367835f", size = 744589, upload-time = "2026-08-19T15:47:42.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/1f/5c2919fef6e3cf15167699abeb069a371d68da14a0e5cab89eeea11261d7/langsmith-0.14.4-py3-none-any.whl", hash = "sha256:ae0d98c2b0f9292ef7e12451d480937d79a0e7dcf2412ba847de6f053391b44a", size = 813097, upload-time = "2026-10-02T17:16:34.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1798,8 +1841,10 @@ resolution-markers = [
|
||||
"python_full_version == '3.14.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version == '3.13.*' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy'",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten')",
|
||||
"python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'",
|
||||
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten')",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" }
|
||||
wheels = [
|
||||
@@ -3002,6 +3047,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truststore"
|
||||
version = "0.10.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20260408"
|
||||
|
||||
Reference in new issue
Block a user