Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
This commit is contained in:
Erick Friis 2023-10-25 11:06:58 -07:00 committed by GitHub
parent 07c2649753
commit 47070b8314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 5549 additions and 0 deletions

3
libs/cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
dist
__pycache__
.venv

213
libs/cli/DOCS.md Normal file
View File

@ -0,0 +1,213 @@
# `langchain`
**Usage**:
```console
$ langchain [OPTIONS] COMMAND [ARGS]...
```
**Options**:
* `--help`: Show this message and exit.
**Commands**:
* `hub`: Manage installable hub packages.
* `serve`: Manage LangServe application projects.
* `start`: Start the LangServe instance, whether it's...
## `langchain hub`
Manage installable hub packages.
**Usage**:
```console
$ langchain hub [OPTIONS] COMMAND [ARGS]...
```
**Options**:
* `--help`: Show this message and exit.
**Commands**:
* `new`: Creates a new hub package.
* `start`: Starts a demo LangServe instance for this...
### `langchain hub new`
Creates a new hub package.
**Usage**:
```console
$ langchain hub new [OPTIONS] NAME
```
**Arguments**:
* `NAME`: [required]
**Options**:
* `--help`: Show this message and exit.
### `langchain hub start`
Starts a demo LangServe instance for this hub package.
**Usage**:
```console
$ langchain hub start [OPTIONS]
```
**Options**:
* `--port INTEGER`
* `--host TEXT`
* `--help`: Show this message and exit.
## `langchain serve`
Manage LangServe application projects.
**Usage**:
```console
$ langchain serve [OPTIONS] COMMAND [ARGS]...
```
**Options**:
* `--help`: Show this message and exit.
**Commands**:
* `add`: Adds the specified package to the current...
* `install`
* `list`: Lists all packages in the current...
* `new`: Create a new LangServe application.
* `remove`: Removes the specified package from the...
* `start`: Starts the LangServe instance.
### `langchain serve add`
Adds the specified package to the current LangServe instance.
e.g.:
langchain serve add simple-pirate
langchain serve add git+ssh://git@github.com/efriis/simple-pirate.git
langchain serve add git+https://github.com/efriis/hub.git#devbranch#subdirectory=mypackage
**Usage**:
```console
$ langchain serve add [OPTIONS] DEPENDENCIES...
```
**Arguments**:
* `DEPENDENCIES...`: [required]
**Options**:
* `--api-path TEXT`
* `--project-dir PATH`
* `--help`: Show this message and exit.
### `langchain serve install`
**Usage**:
```console
$ langchain serve install [OPTIONS]
```
**Options**:
* `--help`: Show this message and exit.
### `langchain serve list`
Lists all packages in the current LangServe instance.
**Usage**:
```console
$ langchain serve list [OPTIONS]
```
**Options**:
* `--help`: Show this message and exit.
### `langchain serve new`
Create a new LangServe application.
**Usage**:
```console
$ langchain serve new [OPTIONS] NAME
```
**Arguments**:
* `NAME`: [required]
**Options**:
* `--package TEXT`
* `--help`: Show this message and exit.
### `langchain serve remove`
Removes the specified package from the current LangServe instance.
**Usage**:
```console
$ langchain serve remove [OPTIONS] API_PATHS...
```
**Arguments**:
* `API_PATHS...`: [required]
**Options**:
* `--help`: Show this message and exit.
### `langchain serve start`
Starts the LangServe instance.
**Usage**:
```console
$ langchain serve start [OPTIONS]
```
**Options**:
* `--port INTEGER`
* `--host TEXT`
* `--help`: Show this message and exit.
## `langchain start`
Start the LangServe instance, whether it's a hub package or a serve project.
**Usage**:
```console
$ langchain start [OPTIONS]
```
**Options**:
* `--port INTEGER`
* `--host TEXT`
* `--help`: Show this message and exit.

43
libs/cli/README.md Normal file
View File

@ -0,0 +1,43 @@
# langchain-cli
Install CLI
`pip install -U --pre langchain-cli`
Create new langchain app
`langchain serve new my-app`
Go into app
`cd my-app`
Install a package
`langchain serve add extraction-summary`
(Activate virtualenv)
Install langserve
`pip install "langserve[all]"`
Install the langchain package
`pip install -e packages/extraction-summary`
Edit `app/server.py` to add that package to the routes
```markdown
from fastapi import FastAPI
from langserve import add_routes
from extraction_summary.chain import chain
app = FastAPI()
add_routes(app, chain)
```
Run the app
`python app/server.py`

View File

View File

@ -0,0 +1,36 @@
import typer
import subprocess
from typing import Optional
from typing_extensions import Annotated
from langchain_cli.namespaces import hub
from langchain_cli.namespaces import serve
app = typer.Typer(no_args_is_help=True, add_completion=False)
app.add_typer(hub.hub, name="hub", help=hub.__doc__)
app.add_typer(serve.serve, name="serve", help=serve.__doc__)
@app.command()
def start(
*,
port: Annotated[
Optional[int], typer.Option(help="The port to run the server on")
] = None,
host: Annotated[
Optional[str], typer.Option(help="The host to run the server on")
] = None,
) -> None:
"""
Start the LangServe instance, whether it's a hub package or a serve project.
"""
cmd = ["poetry", "run", "poe", "start"]
if port is not None:
cmd += ["--port", str(port)]
if host is not None:
cmd += ["--host", host]
subprocess.run(cmd)
if __name__ == "__main__":
app()

View File

@ -0,0 +1,2 @@
DEFAULT_GIT_REPO = "https://github.com/langchain-ai/langchain.git"
DEFAULT_GIT_SUBDIRECTORY = "templates"

View File

@ -0,0 +1,17 @@
"""
Development Scripts for Hub Packages
"""
from fastapi import FastAPI
from langserve.packages import add_package_route
from langchain_cli.utils.packages import get_package_root
def create_demo_server():
"""
Creates a demo server for the current hub package.
"""
app = FastAPI()
package_root = get_package_root()
add_package_route(app, package_root, "")
return app

View File

@ -0,0 +1,89 @@
"""
Manage installable hub packages.
"""
import typer
from typing import Optional
from typing_extensions import Annotated
from pathlib import Path
import shutil
import subprocess
import re
hub = typer.Typer(no_args_is_help=True, add_completion=False)
@hub.command()
def new(
name: Annotated[str, typer.Argument(help="The name of the folder to create")],
with_poetry: Annotated[
bool,
typer.Option(
"--with-poetry/--no-poetry", help="Don't run poetry install"
),
] = False,
):
"""
Creates a new hub package.
"""
computed_name = name if name != "." else Path.cwd().name
destination_dir = Path.cwd() / name if name != "." else Path.cwd()
# copy over template from ../package_template
project_template_dir = Path(__file__).parent.parent.parent / "package_template"
shutil.copytree(project_template_dir, destination_dir, dirs_exist_ok=name == ".")
package_name_split = computed_name.split("/")
package_name_last = (
package_name_split[-2]
if len(package_name_split) > 1 and package_name_split[-1] == ""
else package_name_split[-1]
)
default_package_name = re.sub(
r"[^a-zA-Z0-9_]",
"_",
package_name_last,
)
# replace template strings
pyproject = destination_dir / "pyproject.toml"
pyproject_contents = pyproject.read_text()
pyproject.write_text(
pyproject_contents.replace("__package_name__", default_package_name)
)
# move module folder
package_dir = destination_dir / default_package_name
shutil.move(destination_dir / "package_template", package_dir)
# replace readme
readme = destination_dir / "README.md"
readme_contents = readme.read_text()
readme.write_text(
readme_contents.replace("__package_name_last__", package_name_last)
)
# poetry install
if with_poetry:
subprocess.run(["poetry", "install"], cwd=destination_dir)
@hub.command()
def start(
*,
port: Annotated[
Optional[int], typer.Option(help="The port to run the server on")
] = None,
host: Annotated[
Optional[str], typer.Option(help="The host to run the server on")
] = None,
) -> None:
"""
Starts a demo LangServe instance for this hub package.
"""
cmd = ["poetry", "run", "poe", "start"]
if port is not None:
cmd += ["--port", str(port)]
if host is not None:
cmd += ["--host", host]
subprocess.run(cmd)

View File

@ -0,0 +1,218 @@
"""
Manage LangServe application projects.
"""
import typer
from typing import Optional, List
from typing_extensions import Annotated
from pathlib import Path
import shutil
import subprocess
from langchain_cli.utils.git import copy_repo, update_repo
from langchain_cli.utils.packages import get_package_root
from langchain_cli.utils.events import create_events
from langserve.packages import list_packages, get_langserve_export
import tomli
REPO_DIR = Path(typer.get_app_dir("langchain")) / "git_repos"
serve = typer.Typer(no_args_is_help=True, add_completion=False)
@serve.command()
def new(
name: Annotated[str, typer.Argument(help="The name of the folder to create")],
*,
package: Annotated[
Optional[List[str]],
typer.Option(help="Packages to seed the project with"),
] = None,
with_poetry: Annotated[
bool,
typer.Option(
"--with-poetry/--no-poetry", help="Run poetry install"
),
] = False,
):
"""
Create a new LangServe application.
"""
# copy over template from ../project_template
project_template_dir = Path(__file__).parent.parent.parent / "project_template"
destination_dir = Path.cwd() / name if name != "." else Path.cwd()
shutil.copytree(project_template_dir, destination_dir, dirs_exist_ok=name == ".")
# poetry install
if with_poetry:
subprocess.run(["poetry", "install"], cwd=destination_dir)
# add packages if specified
if package is not None and len(package) > 0:
add(package, project_dir=destination_dir, with_poetry=with_poetry)
@serve.command()
def install():
package_root = get_package_root() / "packages"
for package_path in list_packages(package_root):
try:
pyproject_path = package_path / "pyproject.toml"
langserve_export = get_langserve_export(pyproject_path)
typer.echo(f"Installing {langserve_export['package_name']}...")
subprocess.run(["poetry", "add", "--editable", package_path])
except Exception as e:
typer.echo(f"Skipping installing {package_path} due to error: {e}")
@serve.command()
def add(
dependencies: Annotated[
Optional[List[str]], typer.Argument(help="The dependency to add")
] = None,
*,
api_path: Annotated[List[str], typer.Option(help="API paths to add")] = [],
project_dir: Annotated[
Optional[Path], typer.Option(help="The project directory")
] = None,
repo: Annotated[
List[str], typer.Option(help="Shorthand for installing a GitHub Repo")
] = [],
with_poetry: Annotated[
bool,
typer.Option(
"--with-poetry/--no-poetry", help="Run poetry install"
),
] = False,
):
"""
Adds the specified package to the current LangServe instance.
e.g.:
langchain serve add simple-pirate
langchain serve add git+ssh://git@github.com/efriis/simple-pirate.git
langchain serve add git+https://github.com/efriis/hub.git#devbranch#subdirectory=mypackage
"""
project_root = get_package_root(project_dir)
if dependencies is None:
dependencies = []
# cannot have both repo and dependencies
if len(repo) != 0:
if len(dependencies) != 0:
raise typer.BadParameter(
"Cannot specify both repo and dependencies. Please specify one or the other."
)
dependencies = [f"git+https://github.com/{r}" for r in repo]
if len(api_path) != 0 and len(api_path) != len(dependencies):
raise typer.BadParameter(
"The number of API paths must match the number of dependencies."
)
# get installed packages from pyproject.toml
root_pyproject_path = project_root / "pyproject.toml"
with open(root_pyproject_path, "rb") as pyproject_file:
pyproject = tomli.load(pyproject_file)
installed_packages = (
pyproject.get("tool", {}).get("poetry", {}).get("dependencies", {})
)
installed_names = set(installed_packages.keys())
package_dir = project_root / "packages"
create_events(
[{"event": "serve add", "properties": {"package": d}} for d in dependencies]
)
for i, dependency in enumerate(dependencies):
# update repo
typer.echo(f"Adding {dependency}...")
source_path = update_repo(dependency, REPO_DIR)
pyproject_path = source_path / "pyproject.toml"
langserve_export = get_langserve_export(pyproject_path)
# detect name conflict
if langserve_export["package_name"] in installed_names:
typer.echo(
f"Package with name {langserve_export['package_name']} already installed. Skipping...",
)
continue
inner_api_path = (
api_path[i] if len(api_path) != 0 else langserve_export["package_name"]
)
destination_path = package_dir / inner_api_path
if destination_path.exists():
typer.echo(
f"Endpoint {langserve_export['package_name']} already exists. Skipping...",
)
continue
copy_repo(source_path, destination_path)
# poetry install
if with_poetry:
subprocess.run(
["poetry", "add", "--editable", destination_path], cwd=project_root
)
@serve.command()
def remove(
api_paths: Annotated[List[str], typer.Argument(help="The API paths to remove")],
with_poetry: Annotated[
bool,
typer.Option(
"--with_poetry/--no-poetry", help="Don't run poetry remove"
),
] = False,
):
"""
Removes the specified package from the current LangServe instance.
"""
for api_path in api_paths:
package_dir = Path.cwd() / "packages" / api_path
if not package_dir.exists():
typer.echo(f"Endpoint {api_path} does not exist. Skipping...")
continue
pyproject = package_dir / "pyproject.toml"
langserve_export = get_langserve_export(pyproject)
typer.echo(f"Removing {langserve_export['package_name']}...")
if with_poetry:
subprocess.run(["poetry", "remove", langserve_export["package_name"]])
shutil.rmtree(package_dir)
@serve.command()
def list():
"""
Lists all packages in the current LangServe instance.
"""
package_root = get_package_root() / "packages"
for package_path in list_packages(package_root):
relative = package_path.relative_to(package_root)
pyproject_path = package_path / "pyproject.toml"
langserve_export = get_langserve_export(pyproject_path)
typer.echo(
f"{relative}: ({langserve_export['module']}.{langserve_export['attr']})"
)
@serve.command()
def start(
*,
port: Annotated[
Optional[int], typer.Option(help="The port to run the server on")
] = None,
host: Annotated[
Optional[str], typer.Option(help="The host to run the server on")
] = None,
) -> None:
"""
Starts the LangServe instance.
"""
cmd = ["poetry", "run", "poe", "start"]
if port is not None:
cmd += ["--port", str(port)]
if host is not None:
cmd += ["--host", host]
subprocess.run(cmd)

View File

View File

@ -0,0 +1,52 @@
import urllib3
import json
from typing import List, Dict, Any, Optional, TypedDict
WRITE_KEY = "310apTK0HUFl4AOv"
class EventDict(TypedDict):
event: str
properties: Optional[Dict[str, Any]]
def create_event(event: EventDict) -> None:
"""
Creates a new event with the given type and payload.
"""
data = {
"write_key": WRITE_KEY,
"event": event["event"],
"properties": event.get("properties"),
}
try:
urllib3.request(
"POST",
"https://app.firstpartyhq.com/events/v1/track",
body=json.dumps(data),
headers={"Content-Type": "application/json"},
)
except Exception:
pass
def create_events(events: List[EventDict]) -> None:
data = {
"events": [
{
"write_key": WRITE_KEY,
"event": event["event"],
"properties": event.get("properties"),
}
for event in events
]
}
try:
urllib3.request(
"POST",
"https://app.firstpartyhq.com/events/v1/track/bulk",
body=json.dumps(data),
headers={"Content-Type": "application/json"},
)
except Exception:
pass

View File

@ -0,0 +1,130 @@
from typing import Optional, TypedDict
from pathlib import Path
import shutil
import re
from langchain_cli.constants import DEFAULT_GIT_REPO, DEFAULT_GIT_SUBDIRECTORY
import hashlib
from git import Repo
class DependencySource(TypedDict):
git: str
ref: Optional[str]
subdirectory: Optional[str]
def _get_main_branch(repo: Repo) -> Optional[str]:
"""
Get the name of the main branch of a git repo.
From https://stackoverflow.com/questions/69651536/how-to-get-master-main-branch-from-gitpython
"""
try:
# replace "origin" with your remote name if differs
show_result = repo.git.remote("show", "origin")
# The show_result contains a wall of text in the language that
# is set by your locales. Now you can use regex to extract the
# default branch name, but if your language is different
# from english, you need to adjust this regex pattern.
matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result)
if matches:
default_branch = matches.group(1)
return default_branch
except Exception:
pass
# fallback to main/master
if "main" in repo.heads:
return "main"
if "master" in repo.heads:
return "master"
raise ValueError("Could not find main branch")
# use poetry dependency string format
def _parse_dependency_string(package_string: str) -> DependencySource:
if package_string.startswith("git+"):
# remove git+
remaining = package_string[4:]
# split main string from params
gitstring, *params = remaining.split("#")
# parse params
params_dict = {}
for param in params:
if not param:
# ignore empty entries
continue
if "=" in param:
key, value = param.split("=")
if key in params_dict:
raise ValueError(
f"Duplicate parameter {key} in dependency string {package_string}"
)
params_dict[key] = value
else:
if "ref" in params_dict:
raise ValueError(
f"Duplicate parameter ref in dependency string {package_string}"
)
params_dict["ref"] = param
return DependencySource(
git=gitstring,
ref=params_dict.get("ref"),
subdirectory=params_dict.get("subdirectory"),
)
elif package_string.startswith("https://"):
raise NotImplementedError("url dependencies are not supported yet")
else:
# it's a default git repo dependency
gitstring = DEFAULT_GIT_REPO
subdirectory = str(Path(DEFAULT_GIT_SUBDIRECTORY) / package_string)
return DependencySource(git=gitstring, ref=None, subdirectory=subdirectory)
def _get_repo_path(dependency: DependencySource, repo_dir: Path) -> Path:
# only based on git for now
gitstring = dependency["git"]
hashed = hashlib.sha256(gitstring.encode("utf-8")).hexdigest()[:8]
removed_protocol = gitstring.split("://")[-1]
removed_basename = re.split(r"[/:]", removed_protocol, 1)[-1]
removed_extras = removed_basename.split("#")[0]
foldername = re.sub(r"[^a-zA-Z0-9_]", "_", removed_extras)
directory_name = f"{foldername}_{hashed}"
return repo_dir / directory_name
def update_repo(gitpath: str, repo_dir: Path) -> Path:
# see if path already saved
dependency = _parse_dependency_string(gitpath)
repo_path = _get_repo_path(dependency, repo_dir)
if not repo_path.exists():
repo = Repo.clone_from(dependency["git"], repo_path)
else:
repo = Repo(repo_path)
# pull it
ref = dependency.get("ref") if dependency.get("ref") else _get_main_branch(repo)
repo.git.checkout(ref)
repo.git.pull()
return (
repo_path
if dependency["subdirectory"] is None
else repo_path / dependency["subdirectory"]
)
def copy_repo(
source: Path,
destination: Path,
) -> None:
def ignore_func(_, files):
return [f for f in files if f == ".git"]
shutil.copytree(source, destination, ignore=ignore_func)

View File

@ -0,0 +1,16 @@
from pathlib import Path
from typing import Set, Optional
def get_package_root(cwd: Optional[Path] = None) -> Path:
# traverse path for routes to host (any directory holding a pyproject.toml file)
package_root = Path.cwd() if cwd is None else cwd
visited: Set[Path] = set()
while package_root not in visited:
visited.add(package_root)
pyproject_path = package_root / "pyproject.toml"
if pyproject_path.exists():
return package_root
package_root = package_root.parent
raise FileNotFoundError("No pyproject.toml found")

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
# __package_name_last__

View File

@ -0,0 +1,18 @@
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful assistant who speaks like a pirate",
),
("human", "{text}"),
]
)
_model = ChatOpenAI()
# if you update this, you MUST also update ../pyproject.toml
# with the new `tool.langserve.export_attr`
chain = _prompt | _model

1452
libs/cli/package_template/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
[tool.poetry]
name = "__package_name__"
version = "0.0.1"
description = ""
authors = []
readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.8.1,<4.0"
langchain = ">=0.0.313, <0.1"
openai = "^0.28.1"
fastapi = "^0.104.0"
sse-starlette = "^1.6.5"
[tool.poetry.group.dev.dependencies]
langchain-cli = {git = "https://github.com/langchain-ai/langchain.git", rev = "erick/cli", subdirectory = "libs/cli"}
poethepoet = "^0.24.1"
[tool.langserve]
export_module = "__package_name__.chain"
export_attr = "chain"
[tool.poe.tasks.start]
cmd="uvicorn langchain_cli.dev_scripts:create_demo_server --reload --port $port --host $host"
args = [
{name = "port", help = "port to run on", default = "8000"},
{name = "host", help = "host to run on", default = "127.0.0.1"}
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

1491
libs/cli/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
# LangServeHub Project Template
## Installation
Install the LangChain CLI if you haven't yet
```bash
pip install --user --upgrade git+https://github.com/pingpong-templates/cli.git
```
And install this package's dependencies
```bash
poetry install
```
## Adding packages
```bash
# if you have problems with `poe`, try `poetry run poe`
# adding packages from https://github.com/pingpong-templates/hub
langchain serve add simple-pirate
# adding custom GitHub repo packages
langchain serve add git+https://github.com/hwchase17/chain-of-verification
# with a custom api mount point (defaults to `/{package_name}`)
poe add simple-translator --api_path=/my/custom/path/translator
```
## Removing packages
Note: you remove packages by their api path
```bash
langchain serve remove pirate
```

View File

@ -0,0 +1,12 @@
from fastapi import FastAPI
from langserve import add_routes
app = FastAPI()
# Edit this to add the chain you want to add
add_routes(app, NotImplemented)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)

1570
libs/cli/project_template/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
[tool.poetry]
name = "langservehub-template"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
sse-starlette = "^1.6.5"
tomli-w = "^1.0.0"
uvicorn = "^0.23.2"
fastapi = "^0.103.2"
langserve = {git = "https://github.com/langchain-ai/langserve"}
[tool.poetry.group.dev.dependencies]
poethepoet = "^0.24.1"
uvicorn = "^0.23.2"
pygithub = "^2.1.1"
[tool.poe.tasks.start]
cmd="uvicorn app.server:app --reload --port $port --host $host"
args = [
{name = "port", help = "port to run on", default = "8000"},
{name = "host", help = "host to run on", default = "127.0.0.1"}
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

38
libs/cli/pyproject.toml Normal file
View File

@ -0,0 +1,38 @@
[tool.poetry]
name = "langchain-cli"
version = "0.0.1rc2"
description = "CLI for interacting with LangChain"
authors = ["Erick Friis <erick@langchain.dev>"]
readme = "README.md"
include = [
{path="project_template/**/*", format="wheel"},
{path="package_template/**/*", format="wheel"}
]
[tool.poetry.dependencies]
python = ">=3.8.1,<4.0"
typer = {extras = ["all"], version = "^0.9.0"}
tomli = "^2.0.1"
gitpython = "^3.1.40"
langserve = "^0.0.16"
fastapi = "^0.104.0"
uvicorn = "^0.23.2"
[tool.poetry.scripts]
langchain = "langchain_cli.cli:app"
langchain-cli = "langchain_cli.cli:app"
[tool.poetry.group.dev.dependencies]
poethepoet = "^0.24.1"
pytest = "^7.4.2"
pytest-watch = "^4.2.0"
[tool.poe.tasks]
test = "poetry run pytest"
watch = "poetry run ptw"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

View File

@ -0,0 +1,32 @@
from langchain_cli.utils.git import _parse_dependency_string, DependencySource
from langchain_cli.constants import DEFAULT_GIT_REPO, DEFAULT_GIT_SUBDIRECTORY
def test_dependency_string() -> None:
assert _parse_dependency_string(
"git+ssh://git@github.com/efriis/myrepo.git"
) == DependencySource(
git="ssh://git@github.com/efriis/myrepo.git",
ref=None,
subdirectory=None,
)
assert _parse_dependency_string(
"git+https://github.com/efriis/myrepo.git#subdirectory=src"
) == DependencySource(
git="https://github.com/efriis/myrepo.git",
subdirectory="src",
ref=None,
)
assert _parse_dependency_string(
"git+ssh://git@github.com:efriis/myrepo.git#develop"
) == DependencySource(
git="ssh://git@github.com:efriis/myrepo.git", ref="develop", subdirectory=None
)
assert _parse_dependency_string("simple-pirate") == DependencySource(
git=DEFAULT_GIT_REPO,
subdirectory=f"{DEFAULT_GIT_SUBDIRECTORY}/simple-pirate",
ref=None,
)