diff --git a/langchain/callbacks/tracers/langchain.py b/langchain/callbacks/tracers/langchain.py index e38490d3..de89f3aa 100644 --- a/langchain/callbacks/tracers/langchain.py +++ b/langchain/callbacks/tracers/langchain.py @@ -8,61 +8,16 @@ from datetime import datetime from typing import Any, Dict, List, Optional, Union from uuid import UUID -import requests -from requests.exceptions import HTTPError -from tenacity import ( - before_sleep_log, - retry, - retry_if_exception_type, - stop_after_attempt, - wait_exponential, -) +from langchainplus_sdk import LangChainPlusClient from langchain.callbacks.tracers.base import BaseTracer -from langchain.callbacks.tracers.schemas import ( - Run, - RunCreate, - RunTypeEnum, - RunUpdate, - TracerSession, -) +from langchain.callbacks.tracers.schemas import Run, RunTypeEnum, TracerSession +from langchain.env import get_runtime_environment from langchain.schema import BaseMessage, messages_to_dict logger = logging.getLogger(__name__) -def get_headers() -> Dict[str, Any]: - """Get the headers for the LangChain API.""" - headers: Dict[str, Any] = {"Content-Type": "application/json"} - if os.getenv("LANGCHAIN_API_KEY"): - headers["x-api-key"] = os.getenv("LANGCHAIN_API_KEY") - return headers - - -def get_endpoint() -> str: - return os.getenv("LANGCHAIN_ENDPOINT", "http://localhost:1984") - - -class LangChainTracerAPIError(Exception): - """An error occurred while communicating with the LangChain API.""" - - -class LangChainTracerUserError(Exception): - """An error occurred while communicating with the LangChain API.""" - - -class LangChainTracerError(Exception): - """An error occurred while communicating with the LangChain API.""" - - -retry_decorator = retry( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=4, max=10), - retry=retry_if_exception_type(LangChainTracerAPIError), - before_sleep=before_sleep_log(logger, logging.WARNING), -) - - class LangChainTracer(BaseTracer): """An implementation of the SharedTracer that POSTS to the langchain endpoint.""" @@ -70,19 +25,19 @@ class LangChainTracer(BaseTracer): self, example_id: Optional[Union[UUID, str]] = None, session_name: Optional[str] = None, + client: Optional[LangChainPlusClient] = None, **kwargs: Any, ) -> None: """Initialize the LangChain tracer.""" super().__init__(**kwargs) self.session: Optional[TracerSession] = None - self._endpoint = get_endpoint() - self._headers = get_headers() self.example_id = ( UUID(example_id) if isinstance(example_id, str) else example_id ) self.session_name = session_name or os.getenv("LANGCHAIN_SESSION", "default") # set max_workers to 1 to process tasks in order self.executor = ThreadPoolExecutor(max_workers=1) + self.client = client or LangChainPlusClient() def on_chat_model_start( self, @@ -114,60 +69,19 @@ class LangChainTracer(BaseTracer): def _persist_run(self, run: Run) -> None: """The Langchain Tracer uses Post/Patch rather than persist.""" - @retry_decorator def _persist_run_single(self, run: Run) -> None: """Persist a run.""" if run.parent_run_id is None: run.reference_example_id = self.example_id - run_dict = run.dict() - del run_dict["child_runs"] - run_create = RunCreate(**run_dict, session_name=self.session_name) - response = None - try: - # TODO: Add retries when async - response = requests.post( - f"{self._endpoint}/runs", - data=run_create.json(), - headers=self._headers, - ) - response.raise_for_status() - except HTTPError as e: - if response is not None and response.status_code == 500: - raise LangChainTracerAPIError( - f"Failed to upsert persist run to LangChain API. {e}" - ) - else: - raise LangChainTracerUserError( - f"Failed to persist run to LangChain API. {e}" - ) - except Exception as e: - raise LangChainTracerError( - f"Failed to persist run to LangChain API. {e}" - ) from e + run_dict = run.dict(exclude={"child_runs"}) + extra = run_dict.get("extra", {}) + extra["runtime"] = get_runtime_environment() + run_dict["extra"] = extra + run = self.client.create_run(**run_dict, session_name=self.session_name) - @retry_decorator def _update_run_single(self, run: Run) -> None: """Update a run.""" - run_update = RunUpdate(**run.dict()) - response = None - try: - response = requests.patch( - f"{self._endpoint}/runs/{run.id}", - data=run_update.json(), - headers=self._headers, - ) - response.raise_for_status() - except HTTPError as e: - if response is not None and response.status_code == 500: - raise LangChainTracerAPIError( - f"Failed to update run to LangChain API. {e}" - ) - else: - raise LangChainTracerUserError(f"Failed to run to LangChain API. {e}") - except Exception as e: - raise LangChainTracerError( - f"Failed to update run to LangChain API. {e}" - ) from e + self.client.update_run(run.id, **run.dict()) def _on_llm_start(self, run: Run) -> None: """Persist an LLM run.""" diff --git a/langchain/callbacks/tracers/langchain_v1.py b/langchain/callbacks/tracers/langchain_v1.py index bbd14ff2..171983c3 100644 --- a/langchain/callbacks/tracers/langchain_v1.py +++ b/langchain/callbacks/tracers/langchain_v1.py @@ -2,12 +2,11 @@ from __future__ import annotations import logging import os -from typing import Any, Optional, Union +from typing import Any, Dict, Optional, Union import requests from langchain.callbacks.tracers.base import BaseTracer -from langchain.callbacks.tracers.langchain import get_headers from langchain.callbacks.tracers.schemas import ( ChainRun, LLMRun, @@ -21,6 +20,14 @@ from langchain.schema import get_buffer_string from langchain.utils import raise_for_status_with_text +def get_headers() -> Dict[str, Any]: + """Get the headers for the LangChain API.""" + headers: Dict[str, Any] = {"Content-Type": "application/json"} + if os.getenv("LANGCHAIN_API_KEY"): + headers["x-api-key"] = os.getenv("LANGCHAIN_API_KEY") + return headers + + def _get_endpoint() -> str: return os.getenv("LANGCHAIN_ENDPOINT", "http://localhost:8000") diff --git a/langchain/env.py b/langchain/env.py index e898de0d..b76dab95 100644 --- a/langchain/env.py +++ b/langchain/env.py @@ -10,6 +10,7 @@ def get_runtime_environment() -> dict: return { "library_version": __version__, + "library": "langchain", "platform": platform.platform(), "runtime": "python", "runtime_version": platform.python_version(), diff --git a/poetry.lock b/poetry.lock index 87eb98e8..be30d063 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "absl-py" @@ -4005,14 +4005,14 @@ tests = ["pytest", "pytest-mock"] [[package]] name = "langchainplus-sdk" -version = "0.0.6" +version = "0.0.7" description = "Client library to connect to the LangChainPlus LLM Tracing and Evaluation Platform." category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchainplus_sdk-0.0.6-py3-none-any.whl", hash = "sha256:43fe01c66442b88403c969b8812f6be81e023c0d2a6d5d3572a8d87961438658"}, - {file = "langchainplus_sdk-0.0.6.tar.gz", hash = "sha256:c911a98fd2d02baa48f742b7d700fd6a55f11c9a545ee5d66b08825940c9a32e"}, + {file = "langchainplus_sdk-0.0.7-py3-none-any.whl", hash = "sha256:aefc471058648bf9fc51f659117d33ef905d25a304d5a021f7e32c30f5921076"}, + {file = "langchainplus_sdk-0.0.7.tar.gz", hash = "sha256:b58565bdcaf301d2e6e7dd8898f0b8ccf549a35476258e0c14d871d6de02d210"}, ] [package.dependencies] @@ -11340,13 +11340,13 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ cffi = ["cffi (>=1.11)"] [extras] -all = ["anthropic", "cohere", "openai", "nlpcloud", "huggingface_hub", "jina", "manifest-ml", "elasticsearch", "opensearch-py", "google-search-results", "faiss-cpu", "sentence-transformers", "transformers", "spacy", "nltk", "wikipedia", "beautifulsoup4", "tiktoken", "torch", "jinja2", "pinecone-client", "pinecone-text", "pymongo", "weaviate-client", "redis", "google-api-python-client", "google-auth", "wolframalpha", "qdrant-client", "tensorflow-text", "pypdf", "networkx", "nomic", "aleph-alpha-client", "deeplake", "pgvector", "psycopg2-binary", "pyowm", "pytesseract", "html2text", "atlassian-python-api", "gptcache", "duckduckgo-search", "arxiv", "azure-identity", "clickhouse-connect", "azure-cosmos", "lancedb", "langkit", "lark", "pexpect", "pyvespa", "O365", "jq", "docarray", "steamship", "pdfminer-six", "lxml", "requests-toolbelt", "neo4j", "openlm", "azure-ai-formrecognizer", "azure-ai-vision", "azure-cognitiveservices-speech", "momento", "singlestoredb", "tigrisdb", "nebula3-python"] -azure = ["azure-identity", "azure-cosmos", "openai", "azure-core", "azure-ai-formrecognizer", "azure-ai-vision", "azure-cognitiveservices-speech"] +all = ["O365", "aleph-alpha-client", "anthropic", "arxiv", "atlassian-python-api", "azure-ai-formrecognizer", "azure-ai-vision", "azure-cognitiveservices-speech", "azure-cosmos", "azure-identity", "beautifulsoup4", "clickhouse-connect", "cohere", "deeplake", "docarray", "duckduckgo-search", "elasticsearch", "faiss-cpu", "google-api-python-client", "google-auth", "google-search-results", "gptcache", "html2text", "huggingface_hub", "jina", "jinja2", "jq", "lancedb", "langkit", "lark", "lxml", "manifest-ml", "momento", "nebula3-python", "neo4j", "networkx", "nlpcloud", "nltk", "nomic", "openai", "openlm", "opensearch-py", "pdfminer-six", "pexpect", "pgvector", "pinecone-client", "pinecone-text", "psycopg2-binary", "pymongo", "pyowm", "pypdf", "pytesseract", "pyvespa", "qdrant-client", "redis", "requests-toolbelt", "sentence-transformers", "singlestoredb", "spacy", "steamship", "tensorflow-text", "tigrisdb", "tiktoken", "torch", "transformers", "weaviate-client", "wikipedia", "wolframalpha"] +azure = ["azure-ai-formrecognizer", "azure-ai-vision", "azure-cognitiveservices-speech", "azure-core", "azure-cosmos", "azure-identity", "openai"] cohere = ["cohere"] docarray = ["docarray"] embeddings = ["sentence-transformers"] -extended-testing = ["beautifulsoup4", "bibtexparser", "chardet", "jq", "pdfminer-six", "pypdf", "pymupdf", "pypdfium2", "tqdm", "lxml", "atlassian-python-api", "beautifulsoup4", "pandas", "telethon", "psychicapi", "zep-python", "gql", "requests-toolbelt", "html2text", "py-trello", "scikit-learn", "pyspark"] -llms = ["anthropic", "cohere", "openai", "openlm", "nlpcloud", "huggingface_hub", "manifest-ml", "torch", "transformers"] +extended-testing = ["atlassian-python-api", "beautifulsoup4", "beautifulsoup4", "bibtexparser", "chardet", "gql", "html2text", "jq", "lxml", "pandas", "pdfminer-six", "psychicapi", "py-trello", "pymupdf", "pypdf", "pypdfium2", "pyspark", "requests-toolbelt", "scikit-learn", "telethon", "tqdm", "zep-python"] +llms = ["anthropic", "cohere", "huggingface_hub", "manifest-ml", "nlpcloud", "openai", "openlm", "torch", "transformers"] openai = ["openai", "tiktoken"] qdrant = ["qdrant-client"] text-helpers = ["chardet"] @@ -11354,4 +11354,4 @@ text-helpers = ["chardet"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "0da3585d7f3216764f396c162c8f9456423b9f80a6dc9af46040c3e5eec0b79e" +content-hash = "dbbaa2907bf2ac09ed111ce712772bba0fe56901627f41c53aef71ae5a38d1c6" diff --git a/pyproject.toml b/pyproject.toml index d7f4d3cd..9aa7ff3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ singlestoredb = {version = "^0.6.1", optional = true} pyspark = {version = "^3.4.0", optional = true} tigrisdb = {version = "^1.0.0b6", optional = true} nebula3-python = {version = "^3.4.0", optional = true} -langchainplus-sdk = ">=0.0.6" +langchainplus-sdk = ">=0.0.7" [tool.poetry.group.docs.dependencies]