langgraph-cli 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/PKG-INFO +1 -1
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/langgraph_cli/cli.py +39 -14
- langgraph_cli-0.1.2/langgraph_cli/config.py +110 -0
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/langgraph_cli/docker.py +1 -1
- langgraph_cli-0.1.2/langgraph_cli/initdb/init.sql +1 -0
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/pyproject.toml +1 -1
- langgraph_cli-0.1.0/langgraph_cli/config.py +0 -81
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/README.md +0 -0
- {langgraph_cli-0.1.0 → langgraph_cli-0.1.2}/langgraph_cli/__init__.py +0 -0
|
@@ -67,8 +67,9 @@ async def gather(*coros: Coroutine):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
OPT_O = click.option(
|
|
70
|
-
"--
|
|
71
|
-
"-
|
|
70
|
+
"--docker-compose",
|
|
71
|
+
"-d",
|
|
72
|
+
help="Advanced: Path to docker-compose.yml file with additional services to launch",
|
|
72
73
|
type=click.Path(
|
|
73
74
|
exists=True,
|
|
74
75
|
file_okay=True,
|
|
@@ -80,6 +81,23 @@ OPT_O = click.option(
|
|
|
80
81
|
OPT_C = click.option(
|
|
81
82
|
"--config",
|
|
82
83
|
"-c",
|
|
84
|
+
help="""Path to configuration file declaring dependencies, graphs and environment variables.
|
|
85
|
+
|
|
86
|
+
\b
|
|
87
|
+
Example:
|
|
88
|
+
{
|
|
89
|
+
"dependencies": [
|
|
90
|
+
"langchain_openai",
|
|
91
|
+
"./your_package"
|
|
92
|
+
],
|
|
93
|
+
"graphs": {
|
|
94
|
+
"my_graph_id": "./your_package/your_file.py:variable"
|
|
95
|
+
},
|
|
96
|
+
"env": "./.env"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Defaults to looking for langgraph.json in the current directory.""",
|
|
100
|
+
default="langgraph.json",
|
|
83
101
|
type=click.Path(
|
|
84
102
|
exists=True,
|
|
85
103
|
file_okay=True,
|
|
@@ -88,7 +106,9 @@ OPT_C = click.option(
|
|
|
88
106
|
path_type=pathlib.Path,
|
|
89
107
|
),
|
|
90
108
|
)
|
|
91
|
-
OPT_PORT = click.option(
|
|
109
|
+
OPT_PORT = click.option(
|
|
110
|
+
"--port", "-p", type=int, default=8123, show_default=True, help="Port to expose"
|
|
111
|
+
)
|
|
92
112
|
|
|
93
113
|
|
|
94
114
|
@click.group()
|
|
@@ -96,21 +116,26 @@ def cli():
|
|
|
96
116
|
pass
|
|
97
117
|
|
|
98
118
|
|
|
119
|
+
@click.option(
|
|
120
|
+
"--recreate/--no-recreate",
|
|
121
|
+
default=False,
|
|
122
|
+
show_default=True,
|
|
123
|
+
help="Clear previous data",
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"--pull/--no-pull", default=True, show_default=True, help="Pull latest images"
|
|
127
|
+
)
|
|
128
|
+
@OPT_PORT
|
|
99
129
|
@OPT_O
|
|
100
130
|
@OPT_C
|
|
101
|
-
@
|
|
102
|
-
@click.option("--recreate", is_flag=True, default=False)
|
|
103
|
-
@click.option("--pull", is_flag=True, default=False)
|
|
104
|
-
@cli.command()
|
|
131
|
+
@cli.command(help="Start langgraph API server")
|
|
105
132
|
def up(
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
config: pathlib.Path,
|
|
134
|
+
docker_compose: Optional[pathlib.Path],
|
|
108
135
|
port: int,
|
|
109
136
|
recreate: bool,
|
|
110
137
|
pull: bool,
|
|
111
138
|
):
|
|
112
|
-
if not override and not config:
|
|
113
|
-
raise click.UsageError("Must provide either --override or --config")
|
|
114
139
|
with asyncio.Runner() as runner:
|
|
115
140
|
# check docker available
|
|
116
141
|
try:
|
|
@@ -126,13 +151,13 @@ def up(
|
|
|
126
151
|
stdin = langgraph_cli.docker.compose(port=port)
|
|
127
152
|
args = [
|
|
128
153
|
"--project-directory",
|
|
129
|
-
|
|
154
|
+
config.parent,
|
|
130
155
|
"-f",
|
|
131
156
|
"-", # stdin
|
|
132
157
|
]
|
|
133
158
|
# apply options
|
|
134
|
-
if
|
|
135
|
-
args.extend(["-f", str(
|
|
159
|
+
if docker_compose:
|
|
160
|
+
args.extend(["-f", str(docker_compose)])
|
|
136
161
|
args.append("up")
|
|
137
162
|
if config:
|
|
138
163
|
with open(config) as f:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import TypedDict, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Config(TypedDict):
|
|
8
|
+
dependencies: list[str]
|
|
9
|
+
graphs: dict[str, str]
|
|
10
|
+
env: Union[dict[str, str], str]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def config_to_compose(config_path: pathlib.Path, config: Config):
|
|
14
|
+
pypi_deps = [dep for dep in config["dependencies"] if not dep.startswith(".")]
|
|
15
|
+
local_pkgs: dict[pathlib.Path, str] = {}
|
|
16
|
+
faux_pkgs: dict[pathlib.Path, str] = {}
|
|
17
|
+
pkg_names = set()
|
|
18
|
+
|
|
19
|
+
for local_dep in config["dependencies"]:
|
|
20
|
+
if local_dep.startswith("."):
|
|
21
|
+
resolved = config_path.parent / local_dep
|
|
22
|
+
|
|
23
|
+
# validate local dependency
|
|
24
|
+
if not resolved.exists():
|
|
25
|
+
raise FileNotFoundError(f"Could not find local dependency: {resolved}")
|
|
26
|
+
elif not resolved.is_dir():
|
|
27
|
+
raise NotADirectoryError(
|
|
28
|
+
f"Local dependency must be a directory: {resolved}"
|
|
29
|
+
)
|
|
30
|
+
elif resolved.name in pkg_names:
|
|
31
|
+
raise ValueError(f"Duplicate local dependency: {resolved}")
|
|
32
|
+
else:
|
|
33
|
+
pkg_names.add(resolved.name)
|
|
34
|
+
|
|
35
|
+
# if it's installable, add it to local_pkgs
|
|
36
|
+
# otherwise, add it to faux_pkgs, and create a pyproject.toml
|
|
37
|
+
files = os.listdir(resolved)
|
|
38
|
+
if "pyproject.toml" in files:
|
|
39
|
+
local_pkgs[resolved] = local_dep
|
|
40
|
+
elif "setup.py" in files:
|
|
41
|
+
local_pkgs[resolved] = local_dep
|
|
42
|
+
else:
|
|
43
|
+
faux_pkgs[resolved] = local_dep
|
|
44
|
+
|
|
45
|
+
for graph_id, import_str in config["graphs"].items():
|
|
46
|
+
module_str, _, attr_str = import_str.partition(":")
|
|
47
|
+
if not module_str or not attr_str:
|
|
48
|
+
message = (
|
|
49
|
+
'Import string "{import_str}" must be in format "<module>:<attribute>".'
|
|
50
|
+
)
|
|
51
|
+
raise ValueError(message.format(import_str=import_str))
|
|
52
|
+
if module_str.startswith("."):
|
|
53
|
+
resolved = config_path.parent / module_str
|
|
54
|
+
if not resolved.exists():
|
|
55
|
+
raise FileNotFoundError(f"Could not find local module: {resolved}")
|
|
56
|
+
elif not resolved.is_file():
|
|
57
|
+
raise IsADirectoryError(f"Local module must be a file: {resolved}")
|
|
58
|
+
else:
|
|
59
|
+
for local_pkg in local_pkgs:
|
|
60
|
+
if resolved.is_relative_to(local_pkg):
|
|
61
|
+
resolved = resolved.relative_to(local_pkg)
|
|
62
|
+
break
|
|
63
|
+
else:
|
|
64
|
+
for faux_pkg in faux_pkgs:
|
|
65
|
+
if resolved.is_relative_to(faux_pkg):
|
|
66
|
+
resolved = resolved.relative_to(faux_pkg.parent)
|
|
67
|
+
break
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Module '{import_str}' not found in 'dependencies' list"
|
|
71
|
+
"Add its containing package to 'dependencies' list."
|
|
72
|
+
)
|
|
73
|
+
# rewrite module_str to be a python import path
|
|
74
|
+
module_str = f"{'.'.join(resolved.parts[:-1])}"
|
|
75
|
+
if resolved.stem == "__init__":
|
|
76
|
+
pass
|
|
77
|
+
else:
|
|
78
|
+
module_str = f"{module_str}.{resolved.stem}"
|
|
79
|
+
# update the config
|
|
80
|
+
config["graphs"][graph_id] = f"{module_str}:{attr_str}"
|
|
81
|
+
|
|
82
|
+
faux_pkgs_str = "\n\n".join(
|
|
83
|
+
f"ADD {relpath} /tmp/{fullpath.name}/{fullpath.name}\n RUN touch /tmp/{fullpath.name}/pyproject.toml"
|
|
84
|
+
for fullpath, relpath in faux_pkgs.items()
|
|
85
|
+
)
|
|
86
|
+
local_pkgs_str = f"ADD {' '.join(local_pkgs.values())} /tmp/" if local_pkgs else ""
|
|
87
|
+
env_vars_str = (
|
|
88
|
+
"\n".join(f" {k}: {v}" for k, v in config["env"].items())
|
|
89
|
+
if isinstance(config["env"], dict)
|
|
90
|
+
else ""
|
|
91
|
+
)
|
|
92
|
+
env_file_str = (
|
|
93
|
+
f"env_file: {config['env']}" if isinstance(config["env"], str) else ""
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return f"""
|
|
97
|
+
LANGSERVE_GRAPHS: '{json.dumps(config["graphs"])}'
|
|
98
|
+
{env_vars_str}
|
|
99
|
+
{env_file_str}
|
|
100
|
+
pull_policy: build
|
|
101
|
+
build:
|
|
102
|
+
dockerfile_inline: |
|
|
103
|
+
FROM langchain/langserve
|
|
104
|
+
|
|
105
|
+
{local_pkgs_str}
|
|
106
|
+
|
|
107
|
+
{faux_pkgs_str}
|
|
108
|
+
|
|
109
|
+
RUN pip install {' '.join(pypi_deps)} /tmp/*
|
|
110
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
create schema if not exists langserve;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import pathlib
|
|
4
|
-
from typing import TypedDict, Union
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Config(TypedDict):
|
|
8
|
-
dependencies: list[str]
|
|
9
|
-
graphs: dict[str, str]
|
|
10
|
-
env: Union[dict[str, str], str]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def config_to_compose(config_path: pathlib.Path, config: Config):
|
|
14
|
-
pypi_deps = [dep for dep in config["dependencies"] if not dep.startswith(".")]
|
|
15
|
-
local_pkgs = []
|
|
16
|
-
faux_pkgs = {}
|
|
17
|
-
locals_set = set()
|
|
18
|
-
|
|
19
|
-
for local_dep in config["dependencies"]:
|
|
20
|
-
if local_dep.startswith("."):
|
|
21
|
-
resolved = config_path.parent / local_dep
|
|
22
|
-
|
|
23
|
-
# validate local dependency
|
|
24
|
-
if not resolved.exists():
|
|
25
|
-
raise FileNotFoundError(f"Could not find local dependency: {resolved}")
|
|
26
|
-
elif not resolved.is_dir():
|
|
27
|
-
raise NotADirectoryError(
|
|
28
|
-
f"Local dependency must be a directory: {resolved}"
|
|
29
|
-
)
|
|
30
|
-
elif resolved.name in locals_set:
|
|
31
|
-
raise ValueError(f"Duplicate local dependency: {resolved}")
|
|
32
|
-
else:
|
|
33
|
-
locals_set.add(resolved.name)
|
|
34
|
-
|
|
35
|
-
# if it's installable, add it to local_pkgs
|
|
36
|
-
# otherwise, add it to faux_pkgs, and create a pyproject.toml
|
|
37
|
-
files = os.listdir(resolved)
|
|
38
|
-
if "pyproject.toml" in files:
|
|
39
|
-
local_pkgs.append(local_dep)
|
|
40
|
-
elif "setup.py" in files:
|
|
41
|
-
local_pkgs.append(local_dep)
|
|
42
|
-
else:
|
|
43
|
-
faux_pkgs[resolved.name] = local_dep
|
|
44
|
-
|
|
45
|
-
for _, import_str in config["graphs"].items():
|
|
46
|
-
module_str, _, attrs_str = import_str.partition(":")
|
|
47
|
-
if not module_str or not attrs_str:
|
|
48
|
-
message = (
|
|
49
|
-
'Import string "{import_str}" must be in format "<module>:<attribute>".'
|
|
50
|
-
)
|
|
51
|
-
raise ValueError(message.format(import_str=import_str))
|
|
52
|
-
|
|
53
|
-
faux_pkgs_str = "\n\n".join(
|
|
54
|
-
f"ADD {path} /tmp/{name}/{name}\n RUN touch /tmp/{name}/pyproject.toml"
|
|
55
|
-
for name, path in faux_pkgs.items()
|
|
56
|
-
)
|
|
57
|
-
local_pkgs_str = f"ADD {' '.join(local_pkgs)} /tmp/" if local_pkgs else ""
|
|
58
|
-
env_vars_str = (
|
|
59
|
-
"\n".join(f" {k}: {v}" for k, v in config["env"].items())
|
|
60
|
-
if isinstance(config["env"], dict)
|
|
61
|
-
else ""
|
|
62
|
-
)
|
|
63
|
-
env_file_str = (
|
|
64
|
-
f"env_file: {config['env']}" if isinstance(config["env"], str) else ""
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
return f"""
|
|
68
|
-
LANGSERVE_GRAPHS: '{json.dumps(config["graphs"])}'
|
|
69
|
-
{env_vars_str}
|
|
70
|
-
{env_file_str}
|
|
71
|
-
pull_policy: build
|
|
72
|
-
build:
|
|
73
|
-
dockerfile_inline: |
|
|
74
|
-
FROM langchain/langserve
|
|
75
|
-
|
|
76
|
-
{local_pkgs_str}
|
|
77
|
-
|
|
78
|
-
{faux_pkgs_str}
|
|
79
|
-
|
|
80
|
-
RUN pip install {' '.join(pypi_deps)} /tmp/*
|
|
81
|
-
"""
|
|
File without changes
|
|
File without changes
|