mirror of
https://github.com/hwchase17/langchain.git
synced 2026-05-16 20:26:54 +00:00
Disable `uv` dependency caching throughout the release workflow so build, validation, PyPI publish, and GitHub release jobs do not restore shared cache state. Release jobs already pass the built distributions through explicit artifacts; keeping dependency caches out of this path avoids cache-poisoning risk and makes the existing pre-release validation comment match the actual workflow behavior.
722 lines
29 KiB
YAML
722 lines
29 KiB
YAML
# Builds and publishes LangChain packages to PyPI.
|
|
#
|
|
# Manually triggered, though can be used as a reusable workflow (workflow_call).
|
|
#
|
|
# Handles version bumping, building, and publishing to PyPI with authentication.
|
|
|
|
name: "🚀 Package Release"
|
|
# Run title resolves dropdown values to the published package name (e.g.
|
|
# `core` -> `langchain-core`, `openai` -> `langchain-openai`). Falls back to
|
|
# the raw input for override and `workflow_call` cases, which already pass
|
|
# a full path. Three dropdown values don't follow `langchain-{name}`:
|
|
# `langchain` -> `langchain-classic`, `langchain_v1` -> `langchain`,
|
|
# `standard-tests` -> `langchain-tests`.
|
|
run-name: >-
|
|
Release ${{ inputs.working-directory-override ||
|
|
(startsWith(inputs.working-directory, 'libs/') && inputs.working-directory) ||
|
|
(inputs.working-directory == 'langchain' && 'langchain-classic') ||
|
|
(inputs.working-directory == 'langchain_v1' && 'langchain') ||
|
|
(inputs.working-directory == 'standard-tests' && 'langchain-tests') ||
|
|
format('langchain-{0}', inputs.working-directory) }} ${{
|
|
inputs.release-version }}
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
working-directory:
|
|
required: true
|
|
type: string
|
|
description: "From which folder this pipeline executes"
|
|
allow-prereleases:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
description: "Pass `--prerelease=allow` to wheel-install steps so
|
|
transitive prerelease deps (e.g. langgraph-checkpoint>=4.1.0a3 pulled
|
|
in by an alpha langgraph) resolve. Use only when the release itself
|
|
is a prerelease and at least one dep is also a prerelease."
|
|
workflow_dispatch:
|
|
inputs:
|
|
working-directory:
|
|
required: true
|
|
type: choice
|
|
description: "From which folder this pipeline executes"
|
|
default: "langchain_v1"
|
|
# Short names only — `EFFECTIVE_WORKING_DIR` below re-adds the `libs/`
|
|
# or `libs/partners/` prefix. When adding a new option, also update the
|
|
# non-partner allowlist in `EFFECTIVE_WORKING_DIR` if it isn't a partner
|
|
# package (partners are the default branch).
|
|
options:
|
|
- core
|
|
- langchain
|
|
- langchain_v1
|
|
- text-splitters
|
|
- standard-tests
|
|
- model-profiles
|
|
- anthropic
|
|
- chroma
|
|
- deepseek
|
|
- exa
|
|
- fireworks
|
|
- groq
|
|
- huggingface
|
|
- mistralai
|
|
- nomic
|
|
- ollama
|
|
- openai
|
|
- openrouter
|
|
- perplexity
|
|
- qdrant
|
|
- xai
|
|
working-directory-override:
|
|
required: false
|
|
type: string
|
|
description: "Manual override — takes precedence over dropdown (e.g.
|
|
libs/partners/partner-xyz)"
|
|
release-version:
|
|
required: true
|
|
type: string
|
|
default: "0.1.0"
|
|
description: "New version of package being released"
|
|
dangerous-nonmaster-release:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
description: "Release from a non-master branch (danger!) - Only use for hotfixes"
|
|
allow-prereleases:
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
description: "Pass `--prerelease=allow` to wheel-install steps so
|
|
transitive prerelease deps (e.g. langgraph-checkpoint>=4.1.0a3 pulled
|
|
in by an alpha langgraph) resolve. Use only when the release itself
|
|
is a prerelease and at least one dep is also a prerelease."
|
|
|
|
env:
|
|
PYTHON_VERSION: "3.11"
|
|
UV_FROZEN: "true"
|
|
UV_NO_SYNC: "true"
|
|
# Resolves to a full path. Accepts either:
|
|
# - `working-directory-override` as a full path (e.g. `libs/partners/partner-xyz`)
|
|
# - `working-directory` as a full path (from `workflow_call` callers)
|
|
# - `working-directory` as a short dropdown name (from `workflow_dispatch`)
|
|
EFFECTIVE_WORKING_DIR: >-
|
|
${{
|
|
inputs.working-directory-override
|
|
|| (startsWith(inputs.working-directory, 'libs/') && inputs.working-directory)
|
|
|| (contains(fromJSON('["core","langchain","langchain_v1","text-splitters","standard-tests","model-profiles"]'), inputs.working-directory) && format('libs/{0}', inputs.working-directory))
|
|
|| format('libs/partners/{0}', inputs.working-directory)
|
|
}}
|
|
|
|
permissions:
|
|
contents: read # Job-level overrides grant write only where needed (mark-release)
|
|
|
|
jobs:
|
|
# Build the distribution package and extract version info
|
|
# Runs in isolated environment with minimal permissions for security
|
|
build:
|
|
name: 📦 Build distribution
|
|
if: github.ref == 'refs/heads/master' || inputs.dangerous-nonmaster-release
|
|
environment: Scheduled testing
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
outputs:
|
|
pkg-name: ${{ steps.check-version.outputs.pkg-name }}
|
|
version: ${{ steps.check-version.outputs.version }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Set up Python + uv
|
|
uses: "./.github/actions/uv_setup"
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
enable-cache: "false"
|
|
|
|
# We want to keep this build stage *separate* from the release stage,
|
|
# so that there's no sharing of permissions between them.
|
|
# (Release stage has trusted publishing and GitHub repo contents write access,
|
|
# which the build stage must not have access to.)
|
|
#
|
|
# Otherwise, a malicious `build` step (e.g. via a compromised dependency)
|
|
# could get access to our GitHub or PyPI credentials.
|
|
#
|
|
# Per the trusted publishing GitHub Action:
|
|
# > It is strongly advised to separate jobs for building [...]
|
|
# > from the publish job.
|
|
# https://github.com/pypa/gh-action-pypi-publish#non-goals
|
|
- name: Build project for distribution
|
|
run: uv build
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
- name: Upload build
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Check version
|
|
id: check-version
|
|
shell: python
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
run: |
|
|
import os
|
|
import tomllib
|
|
with open("pyproject.toml", "rb") as f:
|
|
data = tomllib.load(f)
|
|
pkg_name = data["project"]["name"]
|
|
version = data["project"]["version"]
|
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
|
f.write(f"pkg-name={pkg_name}\n")
|
|
f.write(f"version={version}\n")
|
|
release-notes:
|
|
name: 📝 Generate release notes
|
|
# release-notes must run before publishing because its check-tags step
|
|
# validates version/tag state — do not remove this dependency.
|
|
needs:
|
|
- build
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
release-body: ${{ steps.generate-release-body.outputs.release-body }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
repository: langchain-ai/langchain
|
|
path: langchain
|
|
sparse-checkout: | # this only grabs files for relevant dir
|
|
${{ env.EFFECTIVE_WORKING_DIR }}
|
|
ref: ${{ github.ref }} # this scopes to just ref'd branch
|
|
fetch-depth: 0 # this fetches entire commit history
|
|
- name: Check tags
|
|
id: check-tags
|
|
shell: bash
|
|
working-directory: langchain/${{ env.EFFECTIVE_WORKING_DIR }}
|
|
env:
|
|
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
|
|
VERSION: ${{ needs.build.outputs.version }}
|
|
run: |
|
|
# Handle regular versions and pre-release versions differently
|
|
if [[ "$VERSION" == *"-"* ]]; then
|
|
# This is a pre-release version (contains a hyphen)
|
|
# Extract the base version without the pre-release suffix
|
|
BASE_VERSION=${VERSION%%-*}
|
|
# Look for the latest release of the same base version
|
|
REGEX="^$PKG_NAME==$BASE_VERSION\$"
|
|
PREV_TAG=$(git tag --sort=-creatordate | (grep -P "$REGEX" || true) | head -1)
|
|
|
|
# If no exact base version match, look for the latest release of any kind
|
|
if [ -z "$PREV_TAG" ]; then
|
|
REGEX="^$PKG_NAME==\\d+\\.\\d+\\.\\d+\$"
|
|
PREV_TAG=$(git tag --sort=-creatordate | (grep -P "$REGEX" || true) | head -1)
|
|
fi
|
|
else
|
|
# Regular version handling
|
|
PREV_TAG="$PKG_NAME==${VERSION%.*}.$(( ${VERSION##*.} - 1 ))"; [[ "${VERSION##*.}" -eq 0 ]] && PREV_TAG=""
|
|
|
|
# backup case if releasing e.g. 0.3.0, looks up last release
|
|
# note if last release (chronologically) was e.g. 0.1.47 it will get
|
|
# that instead of the last 0.2 release
|
|
if [ -z "$PREV_TAG" ]; then
|
|
REGEX="^$PKG_NAME==\\d+\\.\\d+\\.\\d+\$"
|
|
echo $REGEX
|
|
PREV_TAG=$(git tag --sort=-creatordate | (grep -P $REGEX || true) | head -1)
|
|
fi
|
|
fi
|
|
|
|
# if PREV_TAG is empty or came out to 0.0.0, let it be empty
|
|
if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "$PKG_NAME==0.0.0" ]; then
|
|
echo "No previous tag found - first release"
|
|
else
|
|
# confirm prev-tag actually exists in git repo with git tag
|
|
GIT_TAG_RESULT=$(git tag -l "$PREV_TAG")
|
|
if [ -z "$GIT_TAG_RESULT" ]; then
|
|
echo "Previous tag $PREV_TAG not found in git repo"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
|
|
TAG="${PKG_NAME}==${VERSION}"
|
|
if [ "$TAG" == "$PREV_TAG" ]; then
|
|
echo "No new version to release"
|
|
exit 1
|
|
fi
|
|
echo tag="$TAG" >> $GITHUB_OUTPUT
|
|
echo prev-tag="$PREV_TAG" >> $GITHUB_OUTPUT
|
|
- name: Generate release body
|
|
id: generate-release-body
|
|
working-directory: langchain
|
|
env:
|
|
WORKING_DIR: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
|
|
TAG: ${{ steps.check-tags.outputs.tag }}
|
|
PREV_TAG: ${{ steps.check-tags.outputs.prev-tag }}
|
|
run: |
|
|
PREAMBLE="Changes since $PREV_TAG"
|
|
# if PREV_TAG is empty or 0.0.0, then we are releasing the first version
|
|
if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "$PKG_NAME==0.0.0" ]; then
|
|
PREAMBLE="Initial release"
|
|
PREV_TAG=$(git rev-list --max-parents=0 HEAD)
|
|
fi
|
|
{
|
|
echo 'release-body<<EOF'
|
|
echo $PREAMBLE
|
|
echo
|
|
git log --format="%s" "$PREV_TAG"..HEAD -- $WORKING_DIR
|
|
echo EOF
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
pre-release-checks:
|
|
name: ✅ Pre-release checks
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
# We explicitly *don't* set up caching here. This ensures our tests are
|
|
# maximally sensitive to catching breakage.
|
|
#
|
|
# For example, here's a way that caching can cause a falsely-passing test:
|
|
# - Make the langchain package manifest no longer list a dependency package
|
|
# as a requirement. This means it won't be installed by `pip install`,
|
|
# and attempting to use it would cause a crash.
|
|
# - That dependency used to be required, so it may have been cached.
|
|
# When restoring the venv packages from cache, that dependency gets included.
|
|
# - Tests pass, because the dependency is present even though it wasn't specified.
|
|
# - The package is published, and it breaks on the missing dependency when
|
|
# used in the real world.
|
|
|
|
- name: Set up Python + uv
|
|
uses: "./.github/actions/uv_setup"
|
|
id: setup-python
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
enable-cache: "false"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Import dist package
|
|
shell: bash
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
env:
|
|
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
|
|
VERSION: ${{ needs.build.outputs.version }}
|
|
PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}
|
|
# Install directly from the locally-built wheel (no index resolution needed).
|
|
# `PRERELEASE_FLAG` is empty by default; opt-in via the `allow-prereleases`
|
|
# workflow input lets transitive prerelease deps resolve during alpha
|
|
# release cycles. Stable-release safety is still enforced by the
|
|
# `Check for prerelease versions` step below.
|
|
run: |
|
|
uv venv
|
|
VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG dist/*.whl
|
|
|
|
# Replace all dashes in the package name with underscores,
|
|
# since that's how Python imports packages with dashes in the name.
|
|
# also remove _official suffix
|
|
IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g | sed s/_official//g)"
|
|
|
|
uv run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"
|
|
|
|
- name: Import test dependencies
|
|
run: uv sync --group test
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
# Overwrite the local version of the package with the built version
|
|
- name: Import published package (again)
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
shell: bash
|
|
env:
|
|
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
|
|
VERSION: ${{ needs.build.outputs.version }}
|
|
PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}
|
|
run: |
|
|
VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG dist/*.whl
|
|
|
|
- name: Check for prerelease versions
|
|
# Block release if any dependencies allow prerelease versions
|
|
# (unless this is itself a prerelease version)
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
run: |
|
|
uv run python $GITHUB_WORKSPACE/.github/scripts/check_prerelease_dependencies.py pyproject.toml
|
|
|
|
- name: Run unit tests
|
|
run: make tests
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
- name: Get minimum versions
|
|
# Find the minimum published versions that satisfies the given constraints
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
id: min-version
|
|
run: |
|
|
VIRTUAL_ENV=.venv uv pip install packaging requests
|
|
python_version="$(uv run python --version | awk '{print $2}')"
|
|
min_versions="$(uv run python $GITHUB_WORKSPACE/.github/scripts/get_min_versions.py pyproject.toml release $python_version)"
|
|
echo "min-versions=$min_versions" >> "$GITHUB_OUTPUT"
|
|
echo "min-versions=$min_versions"
|
|
|
|
- name: Run unit tests with minimum dependency versions
|
|
if: ${{ steps.min-version.outputs.min-versions != '' }}
|
|
env:
|
|
MIN_VERSIONS: ${{ steps.min-version.outputs.min-versions }}
|
|
PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}
|
|
run: |
|
|
VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG --force-reinstall --editable .
|
|
VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG --force-reinstall $MIN_VERSIONS
|
|
make tests PYTEST_EXTRA="-q -k 'not test_serdes'"
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
- name: Import integration test dependencies
|
|
run: uv sync --group test --group test_integration
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
- name: Run integration tests
|
|
# Uses the Makefile's `integration_tests` target for the specified package
|
|
if: ${{ startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/partners/') }}
|
|
env:
|
|
AI21_API_KEY: ${{ secrets.AI21_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}
|
|
AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}
|
|
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
|
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_LLM_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LLM_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME }}
|
|
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
|
|
GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }}
|
|
GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HUGGINGFACEHUB_API_TOKEN }}
|
|
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
|
|
NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
|
|
WATSONX_APIKEY: ${{ secrets.WATSONX_APIKEY }}
|
|
WATSONX_PROJECT_ID: ${{ secrets.WATSONX_PROJECT_ID }}
|
|
ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
|
|
ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
|
|
ASTRA_DB_KEYSPACE: ${{ secrets.ASTRA_DB_KEYSPACE }}
|
|
ES_URL: ${{ secrets.ES_URL }}
|
|
ES_CLOUD_ID: ${{ secrets.ES_CLOUD_ID }}
|
|
ES_API_KEY: ${{ secrets.ES_API_KEY }}
|
|
MONGODB_ATLAS_URI: ${{ secrets.MONGODB_ATLAS_URI }}
|
|
UPSTAGE_API_KEY: ${{ secrets.UPSTAGE_API_KEY }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
|
PPLX_API_KEY: ${{ secrets.PPLX_API_KEY }}
|
|
OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
LANGCHAIN_TESTS_USER_AGENT: ${{ secrets.LANGCHAIN_TESTS_USER_AGENT }}
|
|
run: make integration_tests
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
test-pypi-publish:
|
|
name: 🧪 Publish to TestPyPI
|
|
# release-notes must run before publishing because its check-tags step
|
|
# validates version/tag state — do not remove this dependency.
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
- pre-release-checks
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
# This permission is used for trusted publishing:
|
|
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
|
|
#
|
|
# Trusted publishing has to also be configured on PyPI for each package:
|
|
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
|
|
id-token: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Publish to test PyPI
|
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
with:
|
|
packages-dir: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
verbose: true
|
|
print-hash: true
|
|
repository-url: https://test.pypi.org/legacy/
|
|
# We overwrite any existing distributions with the same name and version.
|
|
# This is *only for CI use* and is *extremely dangerous* otherwise!
|
|
# https://github.com/pypa/gh-action-pypi-publish#tolerating-release-package-file-duplicates
|
|
skip-existing: true
|
|
# Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0
|
|
attestations: false
|
|
|
|
# Test select published packages against new core
|
|
# Done when code changes are made to langchain-core
|
|
test-prior-published-packages-against-new-core:
|
|
name: 🔄 Test prior partners against new core
|
|
# Installs the new core with old partners: Installs the new unreleased core
|
|
# alongside the previously published partner packages and runs integration tests
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
- test-pypi-publish
|
|
- pre-release-checks
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
strategy:
|
|
matrix:
|
|
partner: [ anthropic ]
|
|
fail-fast: false # Continue testing other partners if one fails
|
|
env:
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_FILES_API_IMAGE_ID: ${{ secrets.ANTHROPIC_FILES_API_IMAGE_ID }}
|
|
ANTHROPIC_FILES_API_PDF_ID: ${{ secrets.ANTHROPIC_FILES_API_PDF_ID }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}
|
|
AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}
|
|
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
|
|
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_LLM_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LLM_DEPLOYMENT_NAME }}
|
|
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME }}
|
|
LANGCHAIN_TESTS_USER_AGENT: ${{ secrets.LANGCHAIN_TESTS_USER_AGENT }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
# We implement this conditional as Github Actions does not have good support
|
|
# for conditionally needing steps. https://github.com/actions/runner/issues/491
|
|
# TODO: this seems to be resolved upstream, so we can probably remove this workaround
|
|
- name: Check if libs/core
|
|
run: |
|
|
if [ "${{ startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core') }}" != "true" ]; then
|
|
echo "Not in libs/core. Exiting successfully."
|
|
exit 0
|
|
fi
|
|
|
|
- name: Set up Python + uv
|
|
if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')
|
|
uses: "./.github/actions/uv_setup"
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
enable-cache: "false"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Test against ${{ matrix.partner }}
|
|
if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')
|
|
env:
|
|
PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}
|
|
run: |
|
|
# Identify latest tag, excluding pre-releases
|
|
LATEST_PACKAGE_TAG="$(
|
|
git ls-remote --tags origin "langchain-${{ matrix.partner }}*" \
|
|
| awk '{print $2}' \
|
|
| sed 's|refs/tags/||' \
|
|
| grep -E '[0-9]+\.[0-9]+\.[0-9]+$' \
|
|
| sort -Vr \
|
|
| head -n 1
|
|
)"
|
|
echo "Latest package tag: $LATEST_PACKAGE_TAG"
|
|
|
|
# Shallow-fetch just that single tag
|
|
git fetch --depth=1 origin tag "$LATEST_PACKAGE_TAG"
|
|
|
|
# Checkout the latest package files
|
|
rm -rf $GITHUB_WORKSPACE/libs/partners/${{ matrix.partner }}/*
|
|
rm -rf $GITHUB_WORKSPACE/libs/standard-tests/*
|
|
cd $GITHUB_WORKSPACE/libs/
|
|
git checkout "$LATEST_PACKAGE_TAG" -- standard-tests/
|
|
git checkout "$LATEST_PACKAGE_TAG" -- partners/${{ matrix.partner }}/
|
|
cd partners/${{ matrix.partner }}
|
|
|
|
# Print as a sanity check
|
|
echo "Version number from pyproject.toml: "
|
|
cat pyproject.toml | grep "version = "
|
|
|
|
# Run tests
|
|
uv sync --group test --group test_integration
|
|
uv pip install $PRERELEASE_FLAG ../../core/dist/*.whl
|
|
make integration_tests
|
|
|
|
# Test external packages that depend on langchain-core/langchain against the new release
|
|
# Only runs for core and langchain_v1 releases to catch breaking changes before publish
|
|
test-dependents:
|
|
name: "🐍 Test dependent: ${{ matrix.package.path }} (Python ${{
|
|
matrix.python-version }})"
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
- test-pypi-publish
|
|
- pre-release-checks
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
# Only run for core or langchain_v1 releases.
|
|
# Job-level 'if' does not support env context; must use inputs directly.
|
|
if: >-
|
|
startsWith(inputs.working-directory-override || inputs.working-directory,
|
|
'libs/core') || startsWith(inputs.working-directory-override ||
|
|
inputs.working-directory, 'libs/langchain_v1')
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
python-version: [ "3.11", "3.13" ]
|
|
package:
|
|
- name: deepagents
|
|
repo: langchain-ai/deepagents
|
|
path: libs/deepagents
|
|
# No API keys needed for now - deepagents `make test` only runs unit tests
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
path: langchain
|
|
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
repository: ${{ matrix.package.repo }}
|
|
path: ${{ matrix.package.name }}
|
|
|
|
- name: Set up Python + uv
|
|
uses: "./langchain/.github/actions/uv_setup"
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
enable-cache: "false"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: dist
|
|
path: dist/
|
|
|
|
- name: Install ${{ matrix.package.name }} with local packages
|
|
# External dependents don't have [tool.uv.sources] pointing to this repo,
|
|
# so we install the package normally then override with the built wheel.
|
|
env:
|
|
PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}
|
|
run: |
|
|
cd ${{ matrix.package.name }}/${{ matrix.package.path }}
|
|
|
|
# Install the package with test dependencies
|
|
uv sync --group test
|
|
|
|
# Override with the built wheel from this release
|
|
uv pip install $PRERELEASE_FLAG $GITHUB_WORKSPACE/dist/*.whl
|
|
|
|
- name: Run ${{ matrix.package.name }} tests
|
|
run: |
|
|
cd ${{ matrix.package.name }}/${{ matrix.package.path }}
|
|
make test
|
|
|
|
publish:
|
|
name: 🚀 Publish to PyPI
|
|
# Publishes the package to PyPI
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
- test-pypi-publish
|
|
- pre-release-checks
|
|
- test-dependents
|
|
# - test-prior-published-packages-against-new-core
|
|
# Run if all needed jobs succeeded or were skipped (test-dependents only runs for core/langchain_v1)
|
|
if: ${{ !cancelled() && !failure() }}
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
# This permission is used for trusted publishing:
|
|
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
|
|
#
|
|
# Trusted publishing has to also be configured on PyPI for each package:
|
|
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
|
|
id-token: write
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Set up Python + uv
|
|
uses: "./.github/actions/uv_setup"
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
enable-cache: "false"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Publish package distributions to PyPI
|
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
with:
|
|
packages-dir: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
verbose: true
|
|
print-hash: true
|
|
# Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0
|
|
attestations: false
|
|
|
|
mark-release:
|
|
name: 🏷️ Tag GitHub release
|
|
# Marks the GitHub release with the new version tag
|
|
needs:
|
|
- build
|
|
- release-notes
|
|
- test-pypi-publish
|
|
- pre-release-checks
|
|
- publish
|
|
# Run if all needed jobs succeeded or were skipped
|
|
if: ${{ !cancelled() && !failure() }}
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
# This permission is needed by `ncipollo/release-action` to
|
|
# create the GitHub release/tag
|
|
contents: write
|
|
|
|
defaults:
|
|
run:
|
|
working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Set up Python + uv
|
|
uses: "./.github/actions/uv_setup"
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
enable-cache: "false"
|
|
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: dist
|
|
path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/
|
|
|
|
- name: Create Tag
|
|
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1
|
|
with:
|
|
artifacts: "dist/*"
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
generateReleaseNotes: false
|
|
tag: ${{needs.build.outputs.pkg-name}}==${{ needs.build.outputs.version }}
|
|
body: ${{ needs.release-notes.outputs.release-body }}
|
|
commit: ${{ github.sha }}
|
|
makeLatest: ${{ needs.build.outputs.pkg-name == 'langchain-core'}}
|