sync changes

This commit is contained in:
vbarda
2024-06-18 20:19:55 -04:00
parent 7e936c3078
commit e791f7ef5a
10 changed files with 200 additions and 44 deletions
+109 -25
View File
@@ -13,8 +13,9 @@ from langgraph_cli.config import Config
from langgraph_cli.docker import DockerCapabilities
from langgraph_cli.exec import Runner, subp_exec
from langgraph_cli.progress import Progress
from langgraph_cli.util import clean_empty_lines
OPT_O = click.option(
OPT_DOCKER_COMPOSE = click.option(
"--docker-compose",
"-d",
help="Advanced: Path to docker-compose.yml file with additional services to launch",
@@ -26,7 +27,7 @@ OPT_O = click.option(
path_type=pathlib.Path,
),
)
OPT_C = click.option(
OPT_CONFIG = click.option(
"--config",
"-c",
help="""Path to configuration file declaring dependencies, graphs and environment variables.
@@ -127,11 +128,21 @@ OPT_VERBOSE = click.option(
default=False,
help="Show more output from the server logs",
)
OPT_WATCH = click.option("--watch", is_flag=True, help="Restart on file changes")
OPT_LANGGRAPH_API_PATH = click.option(
"--langgraph-api-path",
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
hidden=True,
)
OPT_DEBUGGER_PORT = click.option(
"--debugger-port",
type=int,
help="Pull the debugger image locally and serve the UI on specified port",
)
OPT_POSTGRES_URI = click.option(
"--postgres-uri",
help="Postgres URI to use for the database. Defaults to launching a local database",
)
@click.group()
@@ -142,16 +153,13 @@ def cli():
@OPT_RECREATE
@OPT_PULL
@OPT_PORT
@OPT_O
@OPT_C
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@OPT_VERBOSE
@OPT_DEBUGGER_PORT
@click.option("--watch", is_flag=True, help="Restart on file changes")
@click.option(
"--langgraph-api-path",
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
hidden=True,
)
@OPT_WATCH
@OPT_LANGGRAPH_API_PATH
@OPT_POSTGRES_URI
@click.option(
"--wait",
is_flag=True,
@@ -169,6 +177,7 @@ def up(
wait: bool,
verbose: bool,
debugger_port: Optional[int],
postgres_uri: Optional[str],
):
with Runner() as runner, Progress(message="Pulling...") as set:
capabilities = langgraph_cli.docker.check_capabilities(runner)
@@ -183,6 +192,7 @@ def up(
langgraph_api_path=langgraph_api_path,
verbose=verbose,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
)
# add up + options
args.extend(["up", "--remove-orphans"])
@@ -207,7 +217,7 @@ def up(
debugger_origin = (
f"http://localhost:{debugger_port}"
if debugger_port
else "https://dev.smith.langchain.com"
else "https://smith.langchain.com"
)
set("")
sys.stdout.write(
@@ -237,8 +247,8 @@ def up(
@OPT_PORT
@OPT_O
@OPT_C
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@OPT_VERBOSE
@OPT_DEBUGGER_PORT
@cli.command(help="Stop langgraph API server")
@@ -274,8 +284,8 @@ def down(
runner.run(subp_exec(*compose_cmd, *args, input=stdin, verbose=verbose))
@OPT_O
@OPT_C
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@click.option("--follow", "-f", is_flag=True, help="Follow logs")
@cli.command(help="Show langgraph API server logs")
def logs(
@@ -309,7 +319,7 @@ def logs(
runner.run(subp_exec(*compose_cmd, *args, input=stdin, verbose=True))
@OPT_C
@OPT_CONFIG
@OPT_PULL
@click.option(
"--tag",
@@ -374,33 +384,101 @@ def build(
)
@cli.group(help="Export langgraph compose files")
def export():
pass
@click.option(
"--output",
"-o",
help="Output path to write the docker compose file to",
type=click.Path(
exists=False,
file_okay=True,
dir_okay=False,
resolve_path=True,
path_type=pathlib.Path,
),
required=True,
)
@OPT_CONFIG
@OPT_PORT
@OPT_O
@OPT_C
@cli.command(help="Build a helm chart to deploy to a Kubernetes cluster", hidden=True)
def helm(
@OPT_WATCH
@OPT_LANGGRAPH_API_PATH
@export.command(name="compose", help="Export docker compose file")
def export_compose(
output: pathlib.Path,
config: pathlib.Path,
port: int,
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
):
with Runner() as runner:
capabilities = langgraph_cli.docker.check_capabilities(runner)
_, stdin = prepare(
runner,
capabilities=capabilities,
config_path=config,
docker_compose=None,
pull=False,
watch=watch,
langgraph_api_path=langgraph_api_path,
port=port,
verbose=False,
)
with open(output, "w") as f:
f.write(clean_empty_lines(stdin))
@click.option(
"--output",
"-o",
help="Output path (directory) to write the helm chart to",
type=click.Path(
exists=False,
file_okay=False,
dir_okay=True,
resolve_path=True,
path_type=pathlib.Path,
),
required=True,
)
@OPT_PORT
@OPT_DOCKER_COMPOSE
@OPT_CONFIG
@export.command(
name="helm",
help="Build and export a helm chart to deploy to a Kubernetes cluster",
hidden=True,
)
def export_helm(
output: pathlib.Path,
config: pathlib.Path,
docker_compose: Optional[pathlib.Path],
port: int,
):
with open(config) as f:
config_json = langgraph_cli.config.validate_config(json.load(f))
with Runner() as runner:
# check docker available
capabilities = langgraph_cli.docker.check_capabilities(runner)
# prepare args
stdin = langgraph_cli.docker.compose(capabilities, port=port)
args = [
"convert",
"--chart",
"-o=./helm",
"-o",
str(output),
"-v",
"-f",
"-", # stdin
]
# apply options
if docker_compose:
args.extend(["-f", str(docker_compose)])
args.append("convert")
args.extend(["-f", "-"]) # stdin
# apply config
stdin += langgraph_cli.config.config_to_compose(config, config_json)
# run kompose convert
@@ -417,10 +495,14 @@ def prepare_args_and_stdin(
watch: bool,
langgraph_api_path: Optional[pathlib.Path],
debugger_port: Optional[int] = None,
postgres_uri: Optional[str] = None,
):
# prepare args
stdin = langgraph_cli.docker.compose(
capabilities, port=port, debugger_port=debugger_port
capabilities,
port=port,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
)
args = [
"--project-directory",
@@ -449,6 +531,7 @@ def prepare(
langgraph_api_path: Optional[pathlib.Path],
verbose: bool,
debugger_port: Optional[int] = None,
postgres_uri: Optional[str] = None,
):
with open(config_path) as f:
config = langgraph_cli.config.validate_config(json.load(f))
@@ -472,5 +555,6 @@ def prepare(
watch=watch,
langgraph_api_path=langgraph_api_path,
debugger_port=debugger_port,
postgres_uri=postgres_uri,
)
return args, stdin
+12 -2
View File
@@ -77,12 +77,18 @@ def _parse_version(version: str) -> Version:
def check_capabilities(runner) -> DockerCapabilities:
# check docker available
if shutil.which("docker") is None:
raise click.UsageError("Docker not installed") from None
try:
stdout, _ = runner.run(subp_exec("docker", "info", "-f", "json", collect=True))
info = json.loads(stdout)
except (click.exceptions.Exit, json.JSONDecodeError):
raise click.UsageError("Docker not installed or not running") from None
if not info["ServerVersion"]:
raise click.UsageError("Docker not running") from None
compose_type: DockerComposeType
try:
compose = next(
@@ -146,15 +152,19 @@ def compose(
langgraph-api:
restart: on-failure
ports:
- "{port}:8000"
- "{port}:8000\""""
if include_db:
compose_str += """
depends_on:
langgraph-postgres:
condition: service_healthy
condition: service_healthy"""
compose_str += f"""
environment:
POSTGRES_URI: {postgres_uri}
"""
if capabilities.healthcheck_start_interval:
compose_str += """ healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s"""
+2
View File
@@ -0,0 +1,2 @@
def clean_empty_lines(input_str: str):
return "\n".join(filter(None, input_str.splitlines()))
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langgraph-cli"
version = "0.1.35"
version = "0.1.37"
description = "CLI for interacting with LangGraph API"
authors = ["Nuno Campos <nuno@langchain.dev>"]
readme = "README.md"
+2 -2
View File
@@ -3,8 +3,7 @@ import pathlib
from langgraph_cli.cli import prepare_args_and_stdin
from langgraph_cli.config import Config, validate_config
from langgraph_cli.docker import DEFAULT_POSTGRES_URI, DockerCapabilities, Version
from .helpers import clean_empty_lines
from langgraph_cli.util import clean_empty_lines
DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
version_docker=Version(26, 1, 1),
@@ -81,6 +80,7 @@ services:
environment:
POSTGRES_URI: {DEFAULT_POSTGRES_URI}
healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s
+1 -2
View File
@@ -5,8 +5,7 @@ import click
import pytest
from langgraph_cli.config import config_to_compose, config_to_docker, validate_config
from .helpers import clean_empty_lines
from langgraph_cli.util import clean_empty_lines
PATH_TO_CONFIG = pathlib.Path("tests/unit_tests/test_config.json")
+2 -10
View File
@@ -4,7 +4,7 @@ from langgraph_cli.docker import (
Version,
compose,
)
from tests.unit_tests.helpers import clean_empty_lines
from langgraph_cli.util import clean_empty_lines
DEFAULT_DOCKER_CAPABILITIES = DockerCapabilities(
version_docker=Version(26, 1, 1),
@@ -24,9 +24,6 @@ def test_compose_with_no_debugger_and_custom_db():
restart: on-failure
ports:
- "{port}:8000"
depends_on:
langgraph-postgres:
condition: service_healthy
environment:
POSTGRES_URI: {custom_postgres_uri}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
@@ -45,12 +42,10 @@ def test_compose_with_no_debugger_and_custom_db_with_healthcheck():
restart: on-failure
ports:
- "{port}:8000"
depends_on:
langgraph-postgres:
condition: service_healthy
environment:
POSTGRES_URI: {custom_postgres_uri}
healthcheck:
test: python /api/healthcheck.py
interval: 60s
start_interval: 1s
start_period: 10s"""
@@ -70,9 +65,6 @@ def test_compose_with_debugger_and_custom_db():
restart: on-failure
ports:
- "{port}:8000"
depends_on:
langgraph-postgres:
condition: service_healthy
environment:
POSTGRES_URI: {custom_postgres_uri}"""
assert clean_empty_lines(actual_compose_str) == expected_compose_str
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.1-rc.11",
"version": "0.0.1-rc.12",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
+1 -1
View File
@@ -97,7 +97,7 @@ class BaseClient {
const response = await this.asyncCaller.fetch(
...this.prepareFetchOptions(path, options),
);
if (response.status == 202) {
if (response.status === 202 || response.status === 204) {
return undefined as T;
}
return response.json() as T;
+69
View File
@@ -67,6 +67,7 @@ class LangGraphClient:
self.assistants = AssistantsClient(self.http)
self.threads = ThreadsClient(self.http)
self.runs = RunsClient(self.http)
self.crons = CronClient(self.http)
class HttpClient:
@@ -620,6 +621,74 @@ class RunsClient:
await self.http.delete(f"/threads/{thread_id}/runs/{run_id}")
class CronClient:
def __init__(self, http_client: HttpClient) -> None:
self.http = http_client
async def create_for_thread(
self,
thread_id: str,
assistant_id: str,
*,
schedule: str,
input: Optional[dict] = None,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
interrupt_before: Optional[list[str]] = None,
interrupt_after: Optional[list[str]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[str] = None,
) -> Run:
"""Create a background run."""
payload = {
"schedule": schedule,
"input": input,
"config": config,
"metadata": metadata,
"assistant_id": assistant_id,
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
"webhook": webhook,
}
if multitask_strategy:
payload["multitask_strategy"] = multitask_strategy
payload = {k: v for k, v in payload.items() if v is not None}
return await self.http.post(f"/threads/{thread_id}/runs/crons", json=payload)
async def create(
self,
assistant_id: str,
*,
schedule: str,
input: Optional[dict] = None,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
interrupt_before: Optional[list[str]] = None,
interrupt_after: Optional[list[str]] = None,
webhook: Optional[str] = None,
multitask_strategy: Optional[str] = None,
) -> Run:
"""Create a background run."""
payload = {
"schedule": schedule,
"input": input,
"config": config,
"metadata": metadata,
"assistant_id": assistant_id,
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
"webhook": webhook,
}
if multitask_strategy:
payload["multitask_strategy"] = multitask_strategy
payload = {k: v for k, v in payload.items() if v is not None}
return await self.http.post("/runs/crons", json=payload)
async def delete(self, cron_id: str) -> None:
"""Delete a cron."""
await self.http.delete(f"/runs/crons/{cron_id}")
def _get_api_key(api_key: Optional[str] = None) -> Optional[str]:
"""Get the API key from the environment.
Precedence: