Compare commits

...

1 Commits

Author SHA1 Message Date
vowelparrot
bf92f71037 temp 2023-06-26 15:58:21 -07:00
12 changed files with 178 additions and 37 deletions

View File

@@ -5,11 +5,9 @@ import logging
import os
from concurrent.futures import Future, ThreadPoolExecutor, wait
from datetime import datetime
from typing import Any, Dict, List, Optional, Set, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
from uuid import UUID
from langchainplus_sdk import LangChainPlusClient
from langchain.callbacks.tracers.base import BaseTracer
from langchain.callbacks.tracers.schemas import (
Run,
@@ -19,11 +17,27 @@ from langchain.callbacks.tracers.schemas import (
from langchain.env import get_runtime_environment
from langchain.schema import BaseMessage, messages_to_dict
if TYPE_CHECKING:
import langsmith
from langsmith import Client as LangSmithClient
logger = logging.getLogger(__name__)
_LOGGED = set()
_TRACERS: List[LangChainTracer] = []
def _lazy_import_langsmith() -> langsmith:
try:
import langsmith
except ImportError:
raise ImportError(
"Please install langsmith to use the LangChainTracer."
" You can do this by running `pip install langsmith`."
)
return langsmith
def log_error_once(method: str, exception: Exception) -> None:
"""Log an error once."""
global _LOGGED
@@ -46,7 +60,7 @@ class LangChainTracer(BaseTracer):
self,
example_id: Optional[Union[UUID, str]] = None,
project_name: Optional[str] = None,
client: Optional[LangChainPlusClient] = None,
client: Optional[LangSmithClient] = None,
**kwargs: Any,
) -> None:
"""Initialize the LangChain tracer."""
@@ -60,7 +74,11 @@ class LangChainTracer(BaseTracer):
)
# set max_workers to 1 to process tasks in order
self.executor = ThreadPoolExecutor(max_workers=1)
self.client = client or LangChainPlusClient()
if client is not None:
self.client = client
else:
langsmith = _lazy_import_langsmith()
self.client = langsmith.Client()
self._futures: Set[Future] = set()
global _TRACERS
_TRACERS.append(self)

View File

@@ -2,11 +2,10 @@
from __future__ import annotations
import datetime
from typing import Any, Dict, List, Optional
from enum import Enum
from typing import Any, Dict, List, Optional, Union
from uuid import UUID
from langchainplus_sdk.schemas import RunBase as BaseRunV2
from langchainplus_sdk.schemas import RunTypeEnum
from pydantic import BaseModel, Field, root_validator
from langchain.schema import LLMResult
@@ -88,13 +87,58 @@ class ToolRun(BaseRun):
# Begin V2 API Schemas
class Run(BaseRunV2):
class RunTypeEnum(str, Enum):
"""Enum for run types."""
tool = "tool"
chain = "chain"
llm = "llm"
class Run(BaseModel):
"""Run schema for the V2 API in the Tracer."""
id: UUID
"""The UUID of the run."""
name: str
"""The name of the run, usually taken from the serialized object's ID."""
start_time: datetime.datetime
"""The start time of the run."""
run_type: Union[RunTypeEnum, str]
"""The type of run."""
inputs: dict
"""The inputs to the run."""
execution_order: int
"""The order in which this run was executed in a run tree."""
child_execution_order: int
"""The next execution order of child runs."""
end_time: Optional[datetime.datetime] = None
"""The end time of the run."""
extra: Optional[dict] = None
"""Extra information about the run."""
error: Optional[str] = None
"""The error message of the run, if any."""
serialized: dict = Field(default_factory=dict)
"""The serialized object that was run."""
events: Optional[List[Dict]] = None
"""The events that occurred during the run."""
outputs: Optional[dict] = None
"""The outputs of the run."""
reference_example_id: Optional[UUID] = None
"""The ID of the reference example that was used to run the run, if this
run was performed during an evaluation."""
parent_run_id: Optional[UUID] = None
"""The ID of the parent run if this is not a root."""
tags: List[str] = Field(default_factory=list)
"""Any tags assigned to the run."""
session_id: Optional[UUID] = None
"""The Project / Session ID this run belongs to."""
child_run_ids: Optional[List[UUID]] = None
"""The IDs of the child runs."""
child_runs: List[Run] = Field(default_factory=list)
tags: Optional[List[str]] = Field(default_factory=list)
"""The child runs. These are used during initial tracing."""
feedback_stats: Optional[Dict[str, Any]] = None
"""Any feedback statistics for this run."""
@root_validator(pre=True)
def assign_name(cls, values: dict) -> dict:

View File

@@ -104,12 +104,13 @@ def _convert_tool_run_to_wb_span(trace_tree: Any, run: Run) -> trace_tree.Span:
def _convert_run_to_wb_span(trace_tree: Any, run: Run) -> trace_tree.Span:
attributes = {**run.extra} if run.extra else {}
attributes["execution_order"] = run.execution_order
end_time = run.end_time if run.end_time is not None else run.start_time
return trace_tree.Span(
span_id=str(run.id) if run.id is not None else None,
name=run.serialized.get("name"),
start_time_ms=int(run.start_time.timestamp() * 1000),
end_time_ms=int(run.end_time.timestamp() * 1000),
end_time_ms=int(end_time.timestamp() * 1000),
status_code=trace_tree.StatusCode.SUCCESS
if run.error is None
else trace_tree.StatusCode.ERROR,

View File

@@ -6,6 +6,7 @@ import functools
import logging
from datetime import datetime
from typing import (
TYPE_CHECKING,
Any,
Callable,
Coroutine,
@@ -16,9 +17,6 @@ from typing import (
Union,
)
from langchainplus_sdk import LangChainPlusClient
from langchainplus_sdk.schemas import Example
from langchain.base_language import BaseLanguageModel
from langchain.callbacks.base import BaseCallbackHandler
from langchain.callbacks.manager import Callbacks
@@ -35,6 +33,11 @@ from langchain.schema import (
messages_from_dict,
)
if TYPE_CHECKING:
import langsmith
from langsmith import Client as LangSmithClient
from langsmith.schemas import Example
logger = logging.getLogger(__name__)
MODEL_OR_CHAIN_FACTORY = Union[Callable[[], Chain], BaseLanguageModel]
@@ -44,6 +47,17 @@ class InputFormatError(Exception):
"""Raised when input format is invalid."""
def _lazy_import_langsmith() -> langsmith:
try:
import langsmith
except ImportError:
raise ImportError(
"Please install langsmith to use the langchain runner utils."
" You can do this by running `pip install langsmith`."
)
return langsmith
def _get_prompts(inputs: Dict[str, Any]) -> List[str]:
"""Get prompts from inputs."""
if not inputs:
@@ -448,7 +462,7 @@ async def arun_on_dataset(
num_repetitions: int = 1,
project_name: Optional[str] = None,
verbose: bool = False,
client: Optional[LangChainPlusClient] = None,
client: Optional[LangSmithClient] = None,
tags: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""
@@ -474,7 +488,11 @@ async def arun_on_dataset(
Returns:
A dictionary containing the run's project name and the resulting model outputs.
"""
client_ = client or LangChainPlusClient()
if client is not None:
client_ = client
else:
langsmith = _lazy_import_langsmith()
client = langsmith.Client()
project_name = _get_project_name(project_name, llm_or_chain_factory, dataset_name)
dataset = client_.read_dataset(dataset_name=dataset_name)
examples = client_.list_examples(dataset_id=str(dataset.id))
@@ -501,7 +519,7 @@ def run_on_dataset(
num_repetitions: int = 1,
project_name: Optional[str] = None,
verbose: bool = False,
client: Optional[LangChainPlusClient] = None,
client: Optional[LangSmithClient] = None,
tags: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""Run the chain on a dataset and store traces to the specified project name.
@@ -525,7 +543,11 @@ def run_on_dataset(
Returns:
A dictionary containing the run's project name and the resulting model outputs.
"""
client_ = client or LangChainPlusClient()
if client is not None:
client_ = client
else:
langsmith = _lazy_import_langsmith()
client = langsmith.Client()
project_name = _get_project_name(project_name, llm_or_chain_factory, dataset_name)
dataset = client_.read_dataset(dataset_name=dataset_name)
examples = client_.list_examples(dataset_id=str(dataset.id))

View File

@@ -1,10 +1,7 @@
from __future__ import annotations
from abc import abstractmethod
from typing import Any, Dict, List, Optional
from langchainplus_sdk import EvaluationResult, RunEvaluator
from langchainplus_sdk.schemas import Example, Run
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from langchain.callbacks.manager import (
AsyncCallbackManagerForChainRun,
@@ -13,6 +10,18 @@ from langchain.callbacks.manager import (
from langchain.chains.base import Chain
from langchain.schema import RUN_KEY, BaseOutputParser
if TYPE_CHECKING:
from langsmith import EvaluationResult, RunEvaluator
from langsmith.schemas import Example, Run
else:
try:
from langsmith import EvaluationResult, RunEvaluator
from langsmith.schemas import Example, Run
except ImportError:
from pydantic import BaseModel
EvaluationResult = BaseModel
class RunEvaluatorInputMapper:
"""Map the inputs of a run to the inputs of an evaluation."""

View File

@@ -1,7 +1,7 @@
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Sequence, Union
from langchainplus_sdk.evaluation import EvaluationResult
from langchainplus_sdk.schemas import Example, Run, RunTypeEnum
from pydantic import BaseModel, Field
from langchain.base_language import BaseLanguageModel
@@ -28,6 +28,16 @@ from langchain.prompts.prompt import PromptTemplate
from langchain.schema import OutputParserException
from langchain.tools.base import BaseTool
if TYPE_CHECKING:
from langsmith import EvaluationResult
from langsmith.schemas import Example, Run, RunTypeEnum
else:
try:
from langsmith import EvaluationResult
from langsmith.schemas import Example, Run, RunTypeEnum
except ImportError:
pass
_QA_PROMPTS = {
"qa": QA_DEFAULT_PROMPT,
"sql": SQL_PROMPT,

View File

@@ -1,8 +1,33 @@
"""Script to run langchain-server locally using docker-compose."""
import subprocess
from pathlib import Path
from typing import List
from langchainplus_sdk.cli.main import get_docker_compose_command
def get_docker_compose_command() -> List[str]:
"""Get the correct docker compose command for this system."""
try:
subprocess.check_call(
["docker", "compose", "--version"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return ["docker", "compose"]
except (subprocess.CalledProcessError, FileNotFoundError):
try:
subprocess.check_call(
["docker-compose", "--version"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return ["docker-compose"]
except (subprocess.CalledProcessError, FileNotFoundError):
raise ValueError(
"Neither 'docker compose' nor 'docker-compose'"
" commands are available. Please install the Docker"
" server following the instructions for your operating"
" system at https://docs.docker.com/engine/install/"
)
def main() -> None:

View File

@@ -106,7 +106,7 @@ pyspark = {version = "^3.4.0", optional = true}
clarifai = {version = "9.1.0", optional = true}
tigrisdb = {version = "^1.0.0b6", optional = true}
nebula3-python = {version = "^3.4.0", optional = true}
langchainplus-sdk = ">=0.0.17"
langsmith = {version = ">=0.0.17", optional = true}
awadb = {version = "^0.3.3", optional = true}
azure-search-documents = {version = "11.4.0a20230509004", source = "azure-sdk-dev", optional = true}
openllm = {version = ">=0.1.6", optional = true}
@@ -333,7 +333,8 @@ extended_testing = [
"scikit-learn",
"streamlit",
"pyspark",
"openai"
"openai",
"langsmith"
]
[[tool.poetry.source]]

View File

@@ -583,7 +583,7 @@ def test_convert_run(
child_execution_order=1,
start_time=datetime.utcnow(),
end_time=datetime.utcnow(),
session_id=TEST_SESSION_ID,
session_id=uuid4(),
inputs={"prompts": []},
outputs=LLMResult(generations=[[]]).dict(),
serialized={},

View File

@@ -5,8 +5,6 @@ from typing import Any, Dict, List, Optional, Union
from unittest import mock
import pytest
from langchainplus_sdk.client import LangChainPlusClient
from langchainplus_sdk.schemas import Dataset, Example
from langchain.base_language import BaseLanguageModel
from langchain.chains.base import Chain
@@ -104,8 +102,12 @@ def test_run_chat_model_all_formats(inputs: Dict[str, Any]) -> None:
run_llm(llm, inputs, mock.MagicMock())
@pytest.mark.requires("langsmith")
@pytest.mark.asyncio
async def test_arun_on_dataset(monkeypatch: pytest.MonkeyPatch) -> None:
from langsmith import Client as LangSmithClient
from langsmith.schemas import Dataset, Example
dataset = Dataset(
id=uuid.uuid4(),
name="test",
@@ -180,15 +182,15 @@ async def test_arun_on_dataset(monkeypatch: pytest.MonkeyPatch) -> None:
pass
with mock.patch.object(
LangChainPlusClient, "read_dataset", new=mock_read_dataset
LangSmithClient, "read_dataset", new=mock_read_dataset
), mock.patch.object(
LangChainPlusClient, "list_examples", new=mock_list_examples
LangSmithClient, "list_examples", new=mock_list_examples
), mock.patch(
"langchain.client.runner_utils._arun_llm_or_chain", new=mock_arun_chain
), mock.patch.object(
LangChainPlusClient, "create_project", new=mock_create_project
LangSmithClient, "create_project", new=mock_create_project
):
client = LangChainPlusClient(api_url="http://localhost:1984", api_key="123")
client = LangSmithClient(api_url="http://localhost:1984", api_key="123")
chain = mock.MagicMock()
num_repetitions = 3
results = await arun_on_dataset(

View File

@@ -1,16 +1,22 @@
"""Test run evaluator implementations basic functionality."""
from __future__ import annotations
from typing import TYPE_CHECKING
from uuid import UUID
import pytest
from langchainplus_sdk.schemas import Example, Run
from langchain.evaluation.run_evaluators import get_criteria_evaluator, get_qa_evaluator
from tests.unit_tests.llms.fake_llm import FakeLLM
if TYPE_CHECKING:
from langsmith.schemas import Example, Run
@pytest.fixture
def run() -> Run:
from langsmith.schemas import Run
return Run(
id=UUID("f77cd087-48f7-4c62-9e0e-297842202107"),
name="My Run",
@@ -25,6 +31,8 @@ def run() -> Run:
@pytest.fixture
def example() -> Example:
from langsmith.schemas import Example
return Example(
id=UUID("f77cd087-48f7-4c62-9e0e-297842202106"),
dataset_id=UUID("f77cd087-48f7-4c62-9e0e-297842202105"),
@@ -34,6 +42,7 @@ def example() -> Example:
)
@pytest.mark.requires("langsmith")
def test_get_qa_evaluator(run: Run, example: Example) -> None:
"""Test get_qa_evaluator."""
eval_llm = FakeLLM(
@@ -45,6 +54,7 @@ def test_get_qa_evaluator(run: Run, example: Example) -> None:
assert res.score == 1
@pytest.mark.requires("langsmith")
def test_get_criteria_evaluator(run: Run, example: Example) -> None:
"""Get a criteria evaluator."""
eval_llm = FakeLLM(queries={"a": "This checks out.\nY"}, sequential_responses=True)

View File

@@ -38,7 +38,6 @@ def test_required_dependencies(poetry_conf: Mapping[str, Any]) -> None:
"aiohttp",
"async-timeout",
"dataclasses-json",
"langchainplus-sdk",
"numexpr",
"numpy",
"openapi-schema-pydantic",