langhost 0.11.1__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.
- langhost-0.11.1/.gitignore +15 -0
- langhost-0.11.1/LICENSE +21 -0
- langhost-0.11.1/PKG-INFO +64 -0
- langhost-0.11.1/README.md +29 -0
- langhost-0.11.1/pyproject.toml +72 -0
- langhost-0.11.1/src/langhost/__init__.py +10 -0
- langhost-0.11.1/src/langhost/__main__.py +4 -0
- langhost-0.11.1/src/langhost/cli.py +357 -0
langhost-0.11.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohankumar Ramachandran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
langhost-0.11.1/PKG-INFO
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langhost
|
|
3
|
+
Version: 0.11.1
|
|
4
|
+
Summary: Minimal CLI for self-hosted LangGraph Agent Server on langgraph-runtime-pg
|
|
5
|
+
Project-URL: Homepage, https://github.com/langhost/langhost
|
|
6
|
+
Project-URL: Repository, https://github.com/langhost/langhost
|
|
7
|
+
Project-URL: Issues, https://github.com/langhost/langhost/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/langhost/langhost/releases
|
|
9
|
+
Author-email: Mohankumar Ramachandran <mail@mohanram.dev>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent-protocol,agent-server,agents,ai-agents,cli,langchain,langgraph,langgraph-api,langgraph-sdk,langsmith,llm,open-source,postgres,redis,self-hosted,uvicorn
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Requires-Dist: click>=8.1.7
|
|
29
|
+
Requires-Dist: langgraph-cli<0.5,>=0.4.0
|
|
30
|
+
Requires-Dist: langgraph-runtime-pg==0.11.1
|
|
31
|
+
Requires-Dist: pyfiglet>=1.0.0
|
|
32
|
+
Requires-Dist: python-dotenv>=0.8.0
|
|
33
|
+
Requires-Dist: uvicorn[standard]>=0.51.0
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# langhost
|
|
37
|
+
|
|
38
|
+
Minimal CLI for running the official LangGraph Agent Server on the open
|
|
39
|
+
[`langgraph-runtime-pg`](https://pypi.org/project/langgraph-runtime-pg/) backend
|
|
40
|
+
(Postgres + Redis).
|
|
41
|
+
|
|
42
|
+
## Command
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
langhost serve [OPTIONS]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires a `langgraph.json` in the working directory (same format as
|
|
49
|
+
[`langgraph-cli`](https://github.com/langchain-ai/langgraph/tree/main/libs/cli))
|
|
50
|
+
and `DATABASE_URI` / `REDIS_URI` (see repo `.env.example`). Bring your own
|
|
51
|
+
Postgres + Redis (for example via the repo `docker-compose.yml`).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cp .env.example .env
|
|
55
|
+
docker compose up -d postgres redis
|
|
56
|
+
|
|
57
|
+
# Dev (hot reload)
|
|
58
|
+
langhost serve --reload -c langgraph.json
|
|
59
|
+
|
|
60
|
+
# Prod (multi-process)
|
|
61
|
+
langhost serve --host 0.0.0.0 --workers 4 -c langgraph.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Run `langhost serve --help` for the full option list.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# langhost
|
|
2
|
+
|
|
3
|
+
Minimal CLI for running the official LangGraph Agent Server on the open
|
|
4
|
+
[`langgraph-runtime-pg`](https://pypi.org/project/langgraph-runtime-pg/) backend
|
|
5
|
+
(Postgres + Redis).
|
|
6
|
+
|
|
7
|
+
## Command
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
langhost serve [OPTIONS]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires a `langgraph.json` in the working directory (same format as
|
|
14
|
+
[`langgraph-cli`](https://github.com/langchain-ai/langgraph/tree/main/libs/cli))
|
|
15
|
+
and `DATABASE_URI` / `REDIS_URI` (see repo `.env.example`). Bring your own
|
|
16
|
+
Postgres + Redis (for example via the repo `docker-compose.yml`).
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cp .env.example .env
|
|
20
|
+
docker compose up -d postgres redis
|
|
21
|
+
|
|
22
|
+
# Dev (hot reload)
|
|
23
|
+
langhost serve --reload -c langgraph.json
|
|
24
|
+
|
|
25
|
+
# Prod (multi-process)
|
|
26
|
+
langhost serve --host 0.0.0.0 --workers 4 -c langgraph.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run `langhost serve --help` for the full option list.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "langhost"
|
|
3
|
+
# Lockstep with langgraph-runtime-pg: same X.Y.Z / X.Y.Z.postN (tracks langgraph-api).
|
|
4
|
+
version = "0.11.1"
|
|
5
|
+
description = "Minimal CLI for self-hosted LangGraph Agent Server on langgraph-runtime-pg"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
authors = [{ name = "Mohankumar Ramachandran", email = "mail@mohanram.dev" }]
|
|
11
|
+
keywords = [
|
|
12
|
+
"langgraph",
|
|
13
|
+
"langsmith",
|
|
14
|
+
"langchain",
|
|
15
|
+
"langgraph-api",
|
|
16
|
+
"langgraph-sdk",
|
|
17
|
+
"agent-server",
|
|
18
|
+
"agent-protocol",
|
|
19
|
+
"ai-agents",
|
|
20
|
+
"agents",
|
|
21
|
+
"llm",
|
|
22
|
+
"self-hosted",
|
|
23
|
+
"open-source",
|
|
24
|
+
"cli",
|
|
25
|
+
"postgres",
|
|
26
|
+
"redis",
|
|
27
|
+
"uvicorn",
|
|
28
|
+
]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Development Status :: 4 - Beta",
|
|
31
|
+
"Environment :: Console",
|
|
32
|
+
"Intended Audience :: Developers",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Operating System :: OS Independent",
|
|
35
|
+
"Programming Language :: Python",
|
|
36
|
+
"Programming Language :: Python :: 3",
|
|
37
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
38
|
+
"Programming Language :: Python :: 3.11",
|
|
39
|
+
"Programming Language :: Python :: 3.12",
|
|
40
|
+
"Programming Language :: Python :: 3.13",
|
|
41
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
42
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
43
|
+
"Typing :: Typed",
|
|
44
|
+
]
|
|
45
|
+
dependencies = [
|
|
46
|
+
"click>=8.1.7",
|
|
47
|
+
"python-dotenv>=0.8.0",
|
|
48
|
+
"pyfiglet>=1.0.0",
|
|
49
|
+
"langgraph-cli>=0.4.0,<0.5",
|
|
50
|
+
"uvicorn[standard]>=0.51.0",
|
|
51
|
+
# Exact same version as this package / langgraph-runtime-pg (lockstep releases).
|
|
52
|
+
"langgraph-runtime-pg==0.11.1",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://github.com/langhost/langhost"
|
|
57
|
+
Repository = "https://github.com/langhost/langhost"
|
|
58
|
+
Issues = "https://github.com/langhost/langhost/issues"
|
|
59
|
+
Changelog = "https://github.com/langhost/langhost/releases"
|
|
60
|
+
|
|
61
|
+
[project.scripts]
|
|
62
|
+
langhost = "langhost.cli:cli"
|
|
63
|
+
|
|
64
|
+
[build-system]
|
|
65
|
+
requires = ["hatchling"]
|
|
66
|
+
build-backend = "hatchling.build"
|
|
67
|
+
|
|
68
|
+
[tool.hatch.build.targets.wheel]
|
|
69
|
+
packages = ["src/langhost"]
|
|
70
|
+
|
|
71
|
+
[tool.uv.sources]
|
|
72
|
+
langgraph-runtime-pg = { workspace = true }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Minimal CLI for self-hosted LangGraph Agent Server (edition=pg)."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("langhost")
|
|
7
|
+
except PackageNotFoundError: # pragma: no cover
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""langhost CLI — thin wrappers around langgraph-cli + langgraph_api.cli.run_server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from dotenv import load_dotenv
|
|
14
|
+
from langgraph_api.cli import run_server
|
|
15
|
+
from langgraph_cli.config import validate_config_file
|
|
16
|
+
from langgraph_cli.constants import DEFAULT_CONFIG
|
|
17
|
+
from pyfiglet import figlet_format
|
|
18
|
+
|
|
19
|
+
from langhost import __version__
|
|
20
|
+
|
|
21
|
+
# Always the open Postgres+Redis runtime.
|
|
22
|
+
RUNTIME_EDITION = "pg"
|
|
23
|
+
DEFAULT_PORT = 31296
|
|
24
|
+
LOG_LEVELS = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
|
25
|
+
DEFAULT_STUDIO_ORIGIN = "https://smith.langchain.com"
|
|
26
|
+
|
|
27
|
+
# Upstream run_server always logs an inmem-oriented welcome; replace it for langhost.
|
|
28
|
+
_UPSTREAM_WELCOME_MARKER = "This in-memory server is designed for development and testing"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _display_host(host: str) -> str:
|
|
32
|
+
if host in {"0.0.0.0", "::"}:
|
|
33
|
+
return "127.0.0.1"
|
|
34
|
+
if ":" in host and not host.startswith("["):
|
|
35
|
+
return f"[{host}]"
|
|
36
|
+
return host
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _server_base_url(
|
|
40
|
+
host: str,
|
|
41
|
+
port: int,
|
|
42
|
+
*,
|
|
43
|
+
ssl: bool,
|
|
44
|
+
mount_prefix: str | None,
|
|
45
|
+
) -> str:
|
|
46
|
+
scheme = "https" if ssl else "http"
|
|
47
|
+
url = f"{scheme}://{_display_host(host)}:{port}"
|
|
48
|
+
if mount_prefix:
|
|
49
|
+
url += mount_prefix
|
|
50
|
+
return url
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _langhost_welcome(
|
|
54
|
+
*,
|
|
55
|
+
host: str,
|
|
56
|
+
port: int,
|
|
57
|
+
ssl: bool,
|
|
58
|
+
studio_origin: str | None,
|
|
59
|
+
mount_prefix: str | None,
|
|
60
|
+
) -> str:
|
|
61
|
+
api_url = _server_base_url(host, port, ssl=ssl, mount_prefix=mount_prefix)
|
|
62
|
+
origin = (studio_origin or DEFAULT_STUDIO_ORIGIN).rstrip("/")
|
|
63
|
+
studio_url = f"{origin}/studio/?baseUrl={api_url}"
|
|
64
|
+
agent_chat_url = f"https://agentchat.vercel.app/?apiUrl={api_url}&assistantId=agent"
|
|
65
|
+
title = figlet_format("langhost", font="standard").rstrip()
|
|
66
|
+
return f"""
|
|
67
|
+
|
|
68
|
+
{title}
|
|
69
|
+
|
|
70
|
+
- 🚀 API: \033[36m{api_url}\033[0m
|
|
71
|
+
- 🎨 Studio UI: \033[36m{studio_url}\033[0m
|
|
72
|
+
- 📚 API Docs: \033[36m{api_url}/docs\033[0m
|
|
73
|
+
- 💬 Agent Chat UI: \033[36m{agent_chat_url}\033[0m
|
|
74
|
+
|
|
75
|
+
Self-hosted, production-grade LangGraph Agent Server (Postgres + Redis).
|
|
76
|
+
langhost {__version__}
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _ReplaceWelcomeBanner(logging.Filter):
|
|
82
|
+
"""Swap the upstream inmem welcome for a prebuilt langhost banner."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, welcome: str) -> None:
|
|
85
|
+
super().__init__()
|
|
86
|
+
self._welcome = welcome
|
|
87
|
+
|
|
88
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
89
|
+
if _UPSTREAM_WELCOME_MARKER not in record.getMessage():
|
|
90
|
+
return True
|
|
91
|
+
record.msg = self._welcome
|
|
92
|
+
record.args = ()
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@click.group()
|
|
97
|
+
@click.version_option(version=__version__, prog_name="langhost")
|
|
98
|
+
def cli() -> None:
|
|
99
|
+
"""Self-hosted LangGraph Agent Server on Postgres + Redis (edition=pg)."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@cli.command(
|
|
103
|
+
"serve",
|
|
104
|
+
help=(
|
|
105
|
+
"Run the Agent Server (LANGGRAPH_RUNTIME_EDITION=pg). "
|
|
106
|
+
"Use --reload for local development; --workers for production."
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
@click.option(
|
|
110
|
+
"--host",
|
|
111
|
+
default="127.0.0.1",
|
|
112
|
+
show_default=True,
|
|
113
|
+
help=(
|
|
114
|
+
"Bind address. Prefer 127.0.0.1 for local work; use 0.0.0.0 only on "
|
|
115
|
+
"trusted networks / behind a reverse proxy."
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--port",
|
|
120
|
+
"-p",
|
|
121
|
+
default=DEFAULT_PORT,
|
|
122
|
+
show_default=True,
|
|
123
|
+
type=int,
|
|
124
|
+
help="Port to bind the Agent Server to.",
|
|
125
|
+
)
|
|
126
|
+
@click.option(
|
|
127
|
+
"--config",
|
|
128
|
+
"-c",
|
|
129
|
+
default=DEFAULT_CONFIG,
|
|
130
|
+
show_default=True,
|
|
131
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
132
|
+
help="Path to langgraph.json (graphs, deps, env, auth, http, …).",
|
|
133
|
+
)
|
|
134
|
+
@click.option(
|
|
135
|
+
"--env-file",
|
|
136
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
137
|
+
default=None,
|
|
138
|
+
help="Optional dotenv file to load before starting (default: .env if present).",
|
|
139
|
+
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--database-uri",
|
|
142
|
+
default=None,
|
|
143
|
+
help="Postgres URI (overrides DATABASE_URI). Required unless set in the environment.",
|
|
144
|
+
)
|
|
145
|
+
@click.option(
|
|
146
|
+
"--redis-uri",
|
|
147
|
+
default=None,
|
|
148
|
+
help="Redis URI (overrides REDIS_URI). Required unless set in the environment.",
|
|
149
|
+
)
|
|
150
|
+
@click.option(
|
|
151
|
+
"--reload",
|
|
152
|
+
is_flag=True,
|
|
153
|
+
help="Auto-reload on code changes (dev). Incompatible with --workers > 1.",
|
|
154
|
+
)
|
|
155
|
+
@click.option(
|
|
156
|
+
"--reload-include",
|
|
157
|
+
"reload_includes",
|
|
158
|
+
multiple=True,
|
|
159
|
+
help="Glob(s) to watch when --reload is set (repeatable).",
|
|
160
|
+
)
|
|
161
|
+
@click.option(
|
|
162
|
+
"--reload-exclude",
|
|
163
|
+
"reload_excludes",
|
|
164
|
+
multiple=True,
|
|
165
|
+
help="Glob(s) to ignore when --reload is set (repeatable).",
|
|
166
|
+
)
|
|
167
|
+
@click.option(
|
|
168
|
+
"--workers",
|
|
169
|
+
default=1,
|
|
170
|
+
show_default=True,
|
|
171
|
+
type=click.IntRange(min=1),
|
|
172
|
+
help="Uvicorn worker processes (prod). Incompatible with --reload.",
|
|
173
|
+
)
|
|
174
|
+
@click.option(
|
|
175
|
+
"--n-jobs-per-worker",
|
|
176
|
+
default=None,
|
|
177
|
+
type=int,
|
|
178
|
+
help=(
|
|
179
|
+
"Max concurrent background jobs per server process "
|
|
180
|
+
"(sets N_JOBS_PER_WORKER; default inside run_server is 1)."
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
@click.option(
|
|
184
|
+
"--browser/--no-browser",
|
|
185
|
+
default=False,
|
|
186
|
+
help="Open LangSmith Studio in the browser when the server is ready.",
|
|
187
|
+
)
|
|
188
|
+
@click.option(
|
|
189
|
+
"--studio-url",
|
|
190
|
+
default=None,
|
|
191
|
+
help="LangSmith Studio base URL (default: https://smith.langchain.com).",
|
|
192
|
+
)
|
|
193
|
+
@click.option(
|
|
194
|
+
"--tunnel",
|
|
195
|
+
is_flag=True,
|
|
196
|
+
help="Expose the server via a Cloudflare tunnel (remote Studio access).",
|
|
197
|
+
)
|
|
198
|
+
@click.option(
|
|
199
|
+
"--debug-port",
|
|
200
|
+
default=None,
|
|
201
|
+
type=int,
|
|
202
|
+
help="Listen for a remote debugger (debugpy) on this port.",
|
|
203
|
+
)
|
|
204
|
+
@click.option(
|
|
205
|
+
"--wait-for-client",
|
|
206
|
+
is_flag=True,
|
|
207
|
+
help="With --debug-port, block until a debugger attaches.",
|
|
208
|
+
)
|
|
209
|
+
@click.option(
|
|
210
|
+
"--allow-blocking",
|
|
211
|
+
is_flag=True,
|
|
212
|
+
help="Do not raise on synchronous/blocking I/O in graph code.",
|
|
213
|
+
)
|
|
214
|
+
@click.option(
|
|
215
|
+
"--server-log-level",
|
|
216
|
+
default="INFO",
|
|
217
|
+
show_default=True,
|
|
218
|
+
type=click.Choice(LOG_LEVELS, case_sensitive=False),
|
|
219
|
+
help="Log level for uvicorn / langgraph_api.server.",
|
|
220
|
+
)
|
|
221
|
+
@click.option(
|
|
222
|
+
"--ssl-certfile",
|
|
223
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
224
|
+
default=None,
|
|
225
|
+
help="TLS certificate file (serve over HTTPS).",
|
|
226
|
+
)
|
|
227
|
+
@click.option(
|
|
228
|
+
"--ssl-keyfile",
|
|
229
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
230
|
+
default=None,
|
|
231
|
+
help="TLS private key file (serve over HTTPS).",
|
|
232
|
+
)
|
|
233
|
+
def serve(
|
|
234
|
+
host: str,
|
|
235
|
+
port: int,
|
|
236
|
+
config: pathlib.Path,
|
|
237
|
+
env_file: pathlib.Path | None,
|
|
238
|
+
database_uri: str | None,
|
|
239
|
+
redis_uri: str | None,
|
|
240
|
+
reload: bool,
|
|
241
|
+
reload_includes: tuple[str, ...],
|
|
242
|
+
reload_excludes: tuple[str, ...],
|
|
243
|
+
workers: int,
|
|
244
|
+
n_jobs_per_worker: int | None,
|
|
245
|
+
browser: bool,
|
|
246
|
+
studio_url: str | None,
|
|
247
|
+
tunnel: bool,
|
|
248
|
+
debug_port: int | None,
|
|
249
|
+
wait_for_client: bool,
|
|
250
|
+
allow_blocking: bool,
|
|
251
|
+
server_log_level: str,
|
|
252
|
+
ssl_certfile: pathlib.Path | None,
|
|
253
|
+
ssl_keyfile: pathlib.Path | None,
|
|
254
|
+
) -> None:
|
|
255
|
+
if reload and workers > 1:
|
|
256
|
+
raise click.UsageError("Cannot combine --reload with --workers > 1.")
|
|
257
|
+
if (ssl_certfile is None) != (ssl_keyfile is None):
|
|
258
|
+
raise click.UsageError("Both --ssl-certfile and --ssl-keyfile are required for HTTPS.")
|
|
259
|
+
if ssl_certfile and ssl_keyfile and tunnel:
|
|
260
|
+
raise click.UsageError("Cannot combine --tunnel with SSL options.")
|
|
261
|
+
if wait_for_client and debug_port is None:
|
|
262
|
+
raise click.UsageError("--wait-for-client requires --debug-port.")
|
|
263
|
+
|
|
264
|
+
load_dotenv(env_file or ".env", override=False)
|
|
265
|
+
os.environ["LANGGRAPH_RUNTIME_EDITION"] = RUNTIME_EDITION
|
|
266
|
+
|
|
267
|
+
database_uri = database_uri or os.environ.get("DATABASE_URI")
|
|
268
|
+
redis_uri = redis_uri or os.environ.get("REDIS_URI")
|
|
269
|
+
if not database_uri:
|
|
270
|
+
raise click.UsageError(
|
|
271
|
+
"DATABASE_URI is required. Please set it in the environment or pass it to the command via --database-uri."
|
|
272
|
+
)
|
|
273
|
+
if not redis_uri:
|
|
274
|
+
raise click.UsageError(
|
|
275
|
+
"REDIS_URI is required. Please set it in the environment or pass it to the command via --redis-uri."
|
|
276
|
+
)
|
|
277
|
+
if n_jobs_per_worker is None:
|
|
278
|
+
raw = os.environ.get("N_JOBS_PER_WORKER")
|
|
279
|
+
if raw:
|
|
280
|
+
n_jobs_per_worker = int(raw)
|
|
281
|
+
|
|
282
|
+
config_json = validate_config_file(config)
|
|
283
|
+
if config_json.get("node_version"):
|
|
284
|
+
raise click.UsageError(
|
|
285
|
+
"JS graphs are not supported by langhost. Remove the 'node_version' field from the config."
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
cwd = pathlib.Path.cwd()
|
|
289
|
+
sys.path.append(str(cwd))
|
|
290
|
+
for dep in config_json.get("dependencies", []):
|
|
291
|
+
dep_path = cwd / dep
|
|
292
|
+
if dep_path.is_dir() and dep_path.exists():
|
|
293
|
+
sys.path.append(str(dep_path))
|
|
294
|
+
|
|
295
|
+
includes: Sequence[str] | None = list(reload_includes) or None
|
|
296
|
+
excludes: Sequence[str] | None = list(reload_excludes) or None
|
|
297
|
+
uvicorn_kwargs: dict[str, Any] = {}
|
|
298
|
+
if workers > 1:
|
|
299
|
+
uvicorn_kwargs["workers"] = workers
|
|
300
|
+
|
|
301
|
+
http_cfg = config_json.get("http")
|
|
302
|
+
mount_prefix = None
|
|
303
|
+
if isinstance(http_cfg, dict):
|
|
304
|
+
mount_prefix = http_cfg.get("mount_prefix")
|
|
305
|
+
mount_prefix = (
|
|
306
|
+
os.environ.get("MOUNT_PREFIX") or os.environ.get("LANGGRAPH_MOUNT_PREFIX") or mount_prefix
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
welcome = _langhost_welcome(
|
|
310
|
+
host=host,
|
|
311
|
+
port=port,
|
|
312
|
+
ssl=ssl_certfile is not None and ssl_keyfile is not None,
|
|
313
|
+
studio_origin=studio_url,
|
|
314
|
+
mount_prefix=mount_prefix,
|
|
315
|
+
)
|
|
316
|
+
api_cli_logger = logging.getLogger("langgraph_api.cli")
|
|
317
|
+
banner_filter = _ReplaceWelcomeBanner(welcome)
|
|
318
|
+
api_cli_logger.addFilter(banner_filter)
|
|
319
|
+
try:
|
|
320
|
+
run_server(
|
|
321
|
+
host,
|
|
322
|
+
port,
|
|
323
|
+
reload,
|
|
324
|
+
config_json.get("graphs", {}),
|
|
325
|
+
n_jobs_per_worker=n_jobs_per_worker,
|
|
326
|
+
open_browser=browser,
|
|
327
|
+
tunnel=tunnel,
|
|
328
|
+
debug_port=debug_port,
|
|
329
|
+
wait_for_client=wait_for_client,
|
|
330
|
+
env=config_json.get("env"),
|
|
331
|
+
reload_includes=includes,
|
|
332
|
+
reload_excludes=excludes,
|
|
333
|
+
store=config_json.get("store"),
|
|
334
|
+
auth=config_json.get("auth"),
|
|
335
|
+
http=http_cfg,
|
|
336
|
+
ui=config_json.get("ui"),
|
|
337
|
+
ui_config=config_json.get("ui_config"),
|
|
338
|
+
webhooks=config_json.get("webhooks"),
|
|
339
|
+
checkpointer=config_json.get("checkpointer"),
|
|
340
|
+
studio_url=studio_url,
|
|
341
|
+
disable_persistence=config_json.get("disable_persistence", False),
|
|
342
|
+
allow_blocking=allow_blocking,
|
|
343
|
+
server_level=server_log_level,
|
|
344
|
+
ssl_certfile=ssl_certfile,
|
|
345
|
+
ssl_keyfile=ssl_keyfile,
|
|
346
|
+
runtime_edition=RUNTIME_EDITION,
|
|
347
|
+
__database_uri__=database_uri,
|
|
348
|
+
__redis_uri__=redis_uri,
|
|
349
|
+
__migrations_path__=None,
|
|
350
|
+
**uvicorn_kwargs,
|
|
351
|
+
)
|
|
352
|
+
finally:
|
|
353
|
+
api_cli_logger.removeFilter(banner_filter)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
if __name__ == "__main__":
|
|
357
|
+
cli()
|