From fa8a97a2be94c83099bf0e987e2a7cb3210f2dfd Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:17:43 -0700 Subject: [PATCH] Add additional env info --- python/langsmith/cli/main.py | 77 ++++++++++++++++-------------------- python/langsmith/utils.py | 68 ++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/python/langsmith/cli/main.py b/python/langsmith/cli/main.py index 5397e64..c5a0836 100644 --- a/python/langsmith/cli/main.py +++ b/python/langsmith/cli/main.py @@ -6,12 +6,11 @@ import shutil import subprocess from contextlib import contextmanager from pathlib import Path -from subprocess import CalledProcessError from typing import Dict, Generator, List, Mapping, Optional, Union, cast import requests - -from langsmith.utils import get_runtime_environment +from langsmith.utils import (get_docker_compose_command, + get_docker_environment, get_runtime_environment) logging.basicConfig(level=logging.INFO, format="%(message)s") logger = logging.getLogger(__name__) @@ -63,32 +62,6 @@ def pprint_services(services_status: List[Mapping[str, Union[str, List[str]]]]) logger.info("\n".join(service_message)) -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 (CalledProcessError, FileNotFoundError): - try: - subprocess.check_call( - ["docker-compose", "--version"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - return ["docker-compose"] - except (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 get_ngrok_url(auth_token: Optional[str]) -> str: """Get the ngrok URL for the LangSmith server.""" ngrok_url = "http://localhost:4040/api/tunnels" @@ -286,18 +259,27 @@ class LangSmithCommand: else: self._start_local(dev=dev) - def stop(self) -> None: + def stop(self, clear_volumes: bool = False) -> None: """Stop the LangSmith server.""" - subprocess.run( - [ - *self.docker_compose_command, - "-f", - str(self.docker_compose_file), - "-f", - str(self.ngrok_path), - "down", - ] - ) + cmd = [ + *self.docker_compose_command, + "-f", + str(self.docker_compose_file), + "-f", + str(self.ngrok_path), + "down", + ] + if clear_volumes: + confirm = input( + "You are about to delete all the locally cached LangSmith containers and volumes. " + "This operation cannot be undone. Are you sure? [y/N]" + ) + if confirm.lower() != "y": + print("Aborting.") + return + cmd.append("--volumes") + + subprocess.run(cmd) def logs(self) -> None: """Print the logs from the LangSmith server.""" @@ -346,8 +328,14 @@ class LangSmithCommand: def env() -> None: """Print the runtime environment information.""" env = get_runtime_environment() + env.update(get_docker_environment()) + + # calculate the max length of keys + max_key_length = max(len(key) for key in env.keys()) + logger.info("LangChain Environment:") - logger.info("\n".join(f"{k}:{v}" for k, v in env.items())) + for k, v in env.items(): + logger.info(f"{k:{max_key_length}}: {v}") def main() -> None: @@ -395,7 +383,12 @@ def main() -> None: server_stop_parser = subparsers.add_parser( "stop", description="Stop the LangSmith server." ) - server_stop_parser.set_defaults(func=lambda args: server_command.stop()) + server_stop_parser.add_argument( + "--clear-volumes", + action="store_true", + help="Delete all the locally cached LangSmith containers and volumes.", + ) + server_stop_parser.set_defaults(func=lambda args: server_command.stop(clear_volumes=args.clear_volumes)) server_pull_parser = subparsers.add_parser( "pull", description="Pull the latest LangSmith images." diff --git a/python/langsmith/utils.py b/python/langsmith/utils.py index a692220..26ecb5e 100644 --- a/python/langsmith/utils.py +++ b/python/langsmith/utils.py @@ -1,25 +1,26 @@ """Generic utility functions.""" import platform +import subprocess from functools import lru_cache -from typing import Any, Callable, Tuple +from typing import Any, Callable, List, Tuple from requests import HTTPError, Response class LangSmithAPIError(Exception): - """An error occurred while communicating with the LangChain API.""" + """An error occurred while communicating with the LangSmith API.""" class LangSmithUserError(Exception): - """An error occurred while communicating with the LangChain API.""" + """An error occurred while communicating with the LangSmith API.""" class LangSmithError(Exception): - """An error occurred while communicating with the LangChain API.""" + """An error occurred while communicating with the LangSmith API.""" class LangSmithConnectionError(Exception): - """Couldn't connect to the LC+ API.""" + """Couldn't connect to the LangSmith API.""" def xor_args(*arg_groups: Tuple[str, ...]) -> Callable: @@ -68,3 +69,60 @@ def get_runtime_environment() -> dict: "runtime": "python", "runtime_version": platform.python_version(), } + + +@lru_cache +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/" + ) + + +@lru_cache +def get_docker_environment() -> dict: + """Get information about the environment.""" + # Try to get the docker CLI version via subprocess + import subprocess + + try: + docker_version = ( + subprocess.check_output(["docker", "--version"]).decode("utf-8").strip() + ) + except FileNotFoundError: + docker_version = "unknown" + + compose_command = get_docker_compose_command() + try: + docker_compose_version = ( + subprocess.check_output(["docker-compose", "--version"]) + .decode("utf-8") + .strip() + ) + except FileNotFoundError: + docker_compose_version = "unknown" + + return { + "docker_version": docker_version, + "docker_compose_command": " ".join(compose_command), + "docker_compose_version": docker_compose_version, + }