Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/python/ess-langsmith-client/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copy to .env and fill in. Every value is read from the environment, so
# exporting these instead of using a file works too.

# LangSmith API key. Key and tracing-project operations need an admin key.
LANGSMITH_API_KEY=

# Workspace to scope tenant-specific calls (keys, projects). Find yours with
# `langsmith-client workspaces list`.
LANGSMITH_WORKSPACE_ID=

# Environment suffix in canonical `<service>-<env>` deployment names. Consulted
# only when --env is not passed; falls back to `dev`.
APP_ENV=

# Default for `deploy docker --listener-id` (hybrid/self-hosted deployments).
LANGSMITH_LISTENER_ID=

# Default for `deploy github --integration-id`.
GITHUB_INTEGRATION_ID=
1 change: 1 addition & 0 deletions packages/python/ess-langsmith-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
87 changes: 87 additions & 0 deletions packages/python/ess-langsmith-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# ess-langsmith-client

A shared LangSmith Control Plane client and CLI. One `langsmith-client` command manages API keys, workspaces, deployments, images, listeners, and tracing projects.

```bash
uv sync --all-packages

# List your workspaces, then list the keys in one of them
uv run langsmith-client workspaces list
uv run langsmith-client keys list --workspace-id <workspace-id>
```

## Installation

In a `uv` workspace, declare the dependency in your `pyproject.toml`:

```toml
[project]
dependencies = ["ess-langsmith-client"]

[tool.uv.sources]
ess-langsmith-client = { workspace = true }
```

Then run `uv sync --all-packages` from the workspace root and use the CLI via
`uv run langsmith-client ...`.

The `test-deployed` command needs the `agent-test` extra:
`ess-langsmith-client[agent-test]`.

## Configuration

Most commands read credentials from the environment (a `.env` file in this package is auto-loaded). Copy [`.env.example`](.env.example) to `.env` and fill it in:

| Variable | Description | Required |
| --- | --- | --- |
| `LANGSMITH_API_KEY` | LangSmith API key. Key and project operations need an admin key. | Yes |
| `LANGSMITH_WORKSPACE_ID` | Workspace to scope tenant-specific calls (keys, projects). | For workspace-scoped commands |
| `APP_ENV` | Environment suffix in canonical `<service>-<env>` deployment names. Consulted only when `--env` is not passed; falls back to `dev`. | No |
| `LANGSMITH_LISTENER_ID` | Default for `deploy docker --listener-id`. See [docs/listeners.md](docs/listeners.md). | For hybrid deployments |
| `GITHUB_INTEGRATION_ID` | Default for `deploy github --integration-id`. | For GitHub deployments |

Most commands also accept `--region` (defaults to the US control plane).

> **Workspace scoping gotcha:** API keys and tracing projects are per-workspace. If you run `keys list` or `projects list` without `--workspace-id` (or `LANGSMITH_WORKSPACE_ID`), you may get zero results even when keys exist. Run `langsmith-client workspaces list` first to find the ID.

## Commands

Each subcommand has its own `--help` and a detailed guide in [`docs/`](docs/):

- `keys` — manage API keys — [docs/api-keys.md](docs/api-keys.md)
- `workspaces` — query workspaces and their IDs — [docs/workspaces.md](docs/workspaces.md)
- `deploy docker` / `deploy github` — deploy agents — [docs/deploying-agents.md](docs/deploying-agents.md)
- `build` — build LangGraph Docker images — [docs/building-images.md](docs/building-images.md)
- `listeners` — list hybrid (self-hosted) listeners — [docs/listeners.md](docs/listeners.md)
- `projects` / `control-plane` — tracing projects and control-plane records — [docs/projects.md](docs/projects.md)
- `test-deployed` — smoke-test a deployed agent — [docs/testing-agents.md](docs/testing-agents.md)

## Library

`ControlPlaneClient` and the naming, secrets, and project helpers import directly from `ess_langsmith_client`:

```python
from ess_langsmith_client import ControlPlaneClient, get_project_info
```

### Deployment secrets

`deploy docker` and `deploy github` send only the secrets you name with
`--secret NAME=VALUE`; nothing is read from your environment implicitly. To layer
on convenience defaults, pass your own key list to `merge_secrets`:

```python
from ess_langsmith_client import merge_secrets

secrets = merge_secrets(cli_secrets, auto_detect_keys=["OPENAI_API_KEY"])
```

## Running Tests

```bash
uv run pytest packages/python/ess-langsmith-client
```

## License

Apache License 2.0.
11 changes: 11 additions & 0 deletions packages/python/ess-langsmith-client/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# langsmith-client docs

Detailed guides for each `langsmith-client` subcommand. Start with the [package README](../README.md) for install and configuration, then dive into the area you need:

- [API keys](api-keys.md) — `keys` list/create/delete, the `--all` duplicate safeguard, workspace scoping.
- [Workspaces](workspaces.md) — `workspaces` list/get; find the workspace IDs other commands need.
- [Deploying agents](deploying-agents.md) — `deploy docker` and `deploy github`: canonical naming, idempotent upsert, git-SHA rescue, secrets, scale.
- [Building images](building-images.md) — `build`: LangGraph image build, tagging, `--push`.
- [Listeners](listeners.md) — `listeners list` for hybrid (self-hosted) deployments.
- [Projects](projects.md) — `projects` (tracing) vs `control-plane` (control-plane records).
- [Testing agents](testing-agents.md) — `test-deployed` resolution flags.
65 changes: 65 additions & 0 deletions packages/python/ess-langsmith-client/docs/api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# API keys

Manage LangSmith API keys with `langsmith-client keys`. Listing, creating, and deleting keys all require an admin `LANGSMITH_API_KEY`.

```bash
# List keys in a workspace
uv run langsmith-client keys list --workspace-id <workspace-id>

# Create a service key (the full value is shown only once)
uv run langsmith-client keys create "LangSmith Deployment: hello-world-graph"

# Delete a key by its description (name)
uv run langsmith-client keys delete "my-old-key"
```

> **Workspace scoping:** keys are per-workspace. Pass `--workspace-id` (or set `LANGSMITH_WORKSPACE_ID`); otherwise you may see zero results. Run `langsmith-client workspaces list` to find the ID.

## `keys list`

List API keys for the current workspace.

| Option | Description |
| --- | --- |
| `--api-key` | LangSmith API key (defaults to `LANGSMITH_API_KEY`). |
| `--workspace-id` | Target workspace (defaults to `LANGSMITH_WORKSPACE_ID`). |
| `--expired` | Show only expired keys. |
| `--older-than N` | Show only keys older than `N` days (by `created_at`). |
| `--format {table,json}` | Output format (default: `table`). |

The table shows description, short key, age in days, expiry (with an `EXPIRED` marker), and the key ID.

## `keys create`

Create a new service API key.

```bash
uv run langsmith-client keys create "my-service-key" --format json
```

- Argument: `DESCRIPTION` — the human-readable name shown in the LangSmith UI.
- The **full key value is displayed once, at creation time only.** Copy it immediately.

## `keys delete`

Delete one or more keys by exact description.

```bash
# Delete several keys
uv run langsmith-client keys delete "key-1" "key-2" "key-3"

# Skip the confirmation prompt
uv run langsmith-client keys delete "key-1" --yes

# Delete every key sharing a duplicated name
uv run langsmith-client keys delete "duplicated-name" --all
```

| Option | Description |
| --- | --- |
| `--all` | Delete every key matching a description, even when one description matches multiple keys. |
| `--yes` | Skip the confirmation prompt. |

### Duplicate safeguard

By default, if any description matches **more than one** key, `delete` refuses to act and lists the conflicting keys (short key, ID, expiry) so you can inspect them. This prevents accidentally wiping multiple keys that happen to share a name. Re-run with `--all` to delete every matching key deliberately.
41 changes: 41 additions & 0 deletions packages/python/ess-langsmith-client/docs/building-images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Building images

Build a Docker image for a LangGraph agent with `langsmith-client build`. It wraps `langgraph build`, deriving a consistent image tag from `pyproject.toml` and the current git SHA.

```bash
# Build with defaults from pyproject.toml (tag: name:version-<git-sha>)
uv run langsmith-client build

# Build and push to a registry
uv run langsmith-client build --push --registry gcr.io/my-project
```

## Tagging

When `--tag` is not given, the tag is `name:version-<git-sha>` (falling back to `name:version` when the SHA is unavailable), read from `[project]` in `pyproject.toml`. A changing tag per commit is what makes each deploy roll out a fresh image.

## Options

| Option | Description |
| --- | --- |
| `-t, --tag` | Override the image tag (default: `name:version` from pyproject). |
| `--push` | Push to the registry after build. |
| `--registry` | Registry prefix for push (e.g. `gcr.io/my-project`). |
| `--platform` | Docker platform (default: `linux/amd64`; use `linux/arm64` for local Apple Silicon testing). |
| `-C, --project-dir` | Project dir containing `pyproject.toml` and `langgraph.json` (default: `.`). |
| `LANGGRAPH_ARGS` | Any trailing arguments are passed straight through to `langgraph build`. |

## Examples

```bash
# Build a specific project directory
uv run langsmith-client build -C path/to/my-agent

# Custom tag
uv run langsmith-client build -t my-image:v2

# Build for local ARM testing
uv run langsmith-client build --platform linux/arm64
```

Once pushed, deploy the image with [`deploy docker`](deploying-agents.md).
105 changes: 105 additions & 0 deletions packages/python/ess-langsmith-client/docs/deploying-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Deploying agents

Deploy LangGraph agents to LangSmith with `langsmith-client deploy`. Two sources are supported:

- `deploy docker` — deploy a prebuilt Docker image (self-hosted / hybrid clusters).
- `deploy github` — deploy from a GitHub repo (LangSmith Cloud).

Both require `LANGSMITH_API_KEY` and `LANGSMITH_WORKSPACE_ID`.

```bash
# Docker: build & push first, then deploy
uv run langsmith-client build --push --registry <registry>
uv run langsmith-client deploy docker create --listener-id <id> --wait

# GitHub: deploy from a repo (one-time integration setup required)
uv run langsmith-client deploy github create --repo-url <url> --integration-id <id>
```

## Canonical naming and idempotent upsert

Deployments use a stable canonical name of the form `<service>-<env>`:

- `service` defaults to `[project].name` in `pyproject.toml` (override with `--name`).
- `env` comes from `--env`, then `$APP_ENV`, then `dev`.
- For Docker you can also pass `--deployment` to use a full base name as-is (e.g. `my-agent-prod-dev`) without the service/env split.

Re-running `create` is an **idempotent upsert**:

- updates the live deployment in place if one exists,
- creates the canonical name if none exists,
- creates a **git-SHA rescue name** (`<service>-<env>-<sha>`) only when the canonical name is stuck/orphaned.

## `deploy docker`

### `create`

Deploy (or upsert) a Docker image.

| Option | Description |
| --- | --- |
| `--name` | Service name for `<service>-<env>` (default: project name). |
| `--deployment` | Full base name used as-is (skips service/env split). |
| `--env` | Environment component (default: `$APP_ENV` or `dev`). |
| `-C, --project-dir` | Project dir containing `pyproject.toml` (default: `.`). |
| `--image-uri` | Docker image URI (default: `name:version` from pyproject). |
| `--listener-id` | Listener for hybrid deployments (or `LANGSMITH_LISTENER_ID`). See [listeners](listeners.md). |
| `--namespace` | Kubernetes namespace (default: `default`). |
| `--secret NAME=VALUE` | Secret (repeatable); `NAME=$ENV_VAR` reads from the environment. |
| `--min-scale` / `--max-scale` | Instance bounds (default: 1 / 3). |
| `--cpu` / `--memory` | CPU cores / memory MB per instance (default: 1 / 1024). |
| `--wait` | Wait for the deployment to complete. |

### `update`

Update a specific deployment by ID with a new image (and secrets).

| Option | Description |
| --- | --- |
| `--deployment-id` | **Required.** Deployment to update. |
| `-C, --project-dir` | Project dir for deriving the image URI. |
| `--image-uri` | New image URI (default: `name:version` from pyproject). |
| `--secret NAME=VALUE` | Secret (repeatable). |
| `--wait` | Wait for completion. |

### `list`

List deployments; `--filter` matches names (contains), `--docker-only` shows only Docker deployments.

### `delete`

Delete by `--deployment-id`, or by the resolved `<service>-<env>` base name (via `--name`/`--deployment`/`-C` + `--env`). `--if-exists` exits 0 when nothing matches; `--yes`/`-y` skips confirmation.

## `deploy github`

### `create`

Deploy (or upsert) from a GitHub repository. Requires a one-time GitHub integration (LangSmith UI → Deployments → Import from GitHub), then a `GITHUB_INTEGRATION_ID`.

| Option | Description |
| --- | --- |
| `--name` | Service name for `<service>-<env>` (default: project name). |
| `--env` | Environment component (default: `$APP_ENV` or `dev`). |
| `-C, --project-dir` | Project dir containing `pyproject.toml`. |
| `--repo-url` | **Required.** GitHub repository URL. |
| `--branch` | Branch to deploy (default: `main`). |
| `--config-path` | Path to `langgraph.json` (default: `langgraph.json`). |
| `--integration-id` | GitHub integration ID (or `GITHUB_INTEGRATION_ID`). |
| `--type {dev,prod}` | Deployment type (default: `dev`). |
| `--auto-build/--no-auto-build` | Rebuild on push (default: on). |
| `--shareable` | Make shareable via Studio. |
| `--secret NAME=VALUE` | Secret (repeatable). |
| `--min-scale` / `--max-scale` / `--cpu` / `--memory` | Resource spec. |
| `--wait` | Wait for completion. |

### `update`

Update a deployment by `--deployment-id` (creates a new revision). Optional `--branch`, `--config-path`, `--auto-build/--no-auto-build`, `--wait`.

### `list`

List deployments; `--filter` matches names, `--github-only` shows only GitHub deployments.

### `delete`

Delete by `--deployment-id` (prompts for confirmation).
20 changes: 20 additions & 0 deletions packages/python/ess-langsmith-client/docs/listeners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Listeners

List LangSmith listeners with `langsmith-client listeners`. Listeners connect the LangSmith control plane to a self-hosted Kubernetes cluster; their ID is required when creating Docker-based deployments against that cluster.

```bash
uv run langsmith-client listeners list
```

Requires `LANGSMITH_API_KEY` and `LANGSMITH_WORKSPACE_ID`.

## `listeners list`

| Option | Description |
| --- | --- |
| `--api-key` | LangSmith API key (defaults to `LANGSMITH_API_KEY`). |
| `--workspace-id` | Target workspace (defaults to `LANGSMITH_WORKSPACE_ID`). |
| `--region {us,eu}` | Control plane region (default: `us`). |
| `--format {table,json}` | Output format (default: `table`). |

The table shows each listener's ID, name, and status. Feed the ID into [`deploy docker create --listener-id <id>`](deploying-agents.md) for hybrid deployments.
Loading
Loading