1# Builds and publishes LangChain packages to PyPI.2#3# Manually triggered, though can be used as a reusable workflow (workflow_call).4#5# Handles version bumping, building, and publishing to PyPI with authentication.67name: "🚀 Package Release"8# Run title resolves dropdown values to the published package name (e.g.9# `core` -> `langchain-core`, `openai` -> `langchain-openai`). Falls back to10# the raw input for override and `workflow_call` cases, which already pass11# a full path. Three dropdown values don't follow `langchain-{name}`:12# `langchain` -> `langchain-classic`, `langchain_v1` -> `langchain`,13# `standard-tests` -> `langchain-tests`.14run-name: >-15 Release ${{ inputs.working-directory-override ||16 (startsWith(inputs.working-directory, 'libs/') && inputs.working-directory) ||17 (inputs.working-directory == 'langchain' && 'langchain-classic') ||18 (inputs.working-directory == 'langchain_v1' && 'langchain') ||19 (inputs.working-directory == 'standard-tests' && 'langchain-tests') ||20 format('langchain-{0}', inputs.working-directory) }} ${{21 inputs.release-version }}22on:23 workflow_call:24 inputs:25 working-directory:26 required: true27 type: string28 description: "From which folder this pipeline executes"29 allow-prereleases:30 required: false31 type: boolean32 default: false33 description: "Pass `--prerelease=allow` to wheel-install steps so34 transitive prerelease deps (e.g. langgraph-checkpoint>=4.1.0a3 pulled35 in by an alpha langgraph) resolve. Use only when the release itself36 is a prerelease and at least one dep is also a prerelease."37 workflow_dispatch:38 inputs:39 working-directory:40 required: true41 type: choice42 description: "From which folder this pipeline executes"43 default: "langchain_v1"44 # Short names only — `EFFECTIVE_WORKING_DIR` below re-adds the `libs/`45 # or `libs/partners/` prefix. When adding a new option, also update the46 # non-partner allowlist in `EFFECTIVE_WORKING_DIR` if it isn't a partner47 # package (partners are the default branch).48 options:49 - core50 - langchain51 - langchain_v152 - text-splitters53 - standard-tests54 - model-profiles55 - anthropic56 - chroma57 - deepseek58 - exa59 - fireworks60 - groq61 - huggingface62 - mistralai63 - nomic64 - ollama65 - openai66 - openrouter67 - perplexity68 - qdrant69 - xai70 working-directory-override:71 required: false72 type: string73 description: "Manual override — takes precedence over dropdown (e.g.74 libs/partners/partner-xyz)"75 release-version:76 required: true77 type: string78 default: "0.1.0"79 description: "New version of package being released"80 dangerous-nonmaster-release:81 required: false82 type: boolean83 default: false84 description: "Release from a non-master branch (danger!) - Only use for hotfixes"85 allow-prereleases:86 required: false87 type: boolean88 default: false89 description: "Pass `--prerelease=allow` to wheel-install steps so90 transitive prerelease deps (e.g. langgraph-checkpoint>=4.1.0a3 pulled91 in by an alpha langgraph) resolve. Use only when the release itself92 is a prerelease and at least one dep is also a prerelease."9394env:95 PYTHON_VERSION: "3.11"96 UV_FROZEN: "true"97 UV_NO_SYNC: "true"98 # Resolves to a full path. Accepts either:99 # - `working-directory-override` as a full path (e.g. `libs/partners/partner-xyz`)100 # - `working-directory` as a full path (from `workflow_call` callers)101 # - `working-directory` as a short dropdown name (from `workflow_dispatch`)102 EFFECTIVE_WORKING_DIR: >-103 ${{104 inputs.working-directory-override105 || (startsWith(inputs.working-directory, 'libs/') && inputs.working-directory)106 || (contains(fromJSON('["core","langchain","langchain_v1","text-splitters","standard-tests","model-profiles"]'), inputs.working-directory) && format('libs/{0}', inputs.working-directory))107 || format('libs/partners/{0}', inputs.working-directory)108 }}109110permissions:111 contents: read # Job-level overrides grant write only where needed (mark-release)112113jobs:114 # Build the distribution package and extract version info115 # Runs in isolated environment with minimal permissions for security116 build:117 name: 📦 Build distribution118 if: github.ref == 'refs/heads/master' || inputs.dangerous-nonmaster-release119 environment: Release120 runs-on: ubuntu-latest121 permissions:122 contents: read123124 outputs:125 pkg-name: ${{ steps.check-version.outputs.pkg-name }}126 version: ${{ steps.check-version.outputs.version }}127128 steps:129 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6130131 - name: Set up Python + uv132 uses: "./.github/actions/uv_setup"133 with:134 python-version: ${{ env.PYTHON_VERSION }}135 enable-cache: "false"136137 # We want to keep this build stage *separate* from the release stage,138 # so that there's no sharing of permissions between them.139 # (Release stage has trusted publishing and GitHub repo contents write access,140 # which the build stage must not have access to.)141 #142 # Otherwise, a malicious `build` step (e.g. via a compromised dependency)143 # could get access to our GitHub or PyPI credentials.144 #145 # Per the trusted publishing GitHub Action:146 # > It is strongly advised to separate jobs for building [...]147 # > from the publish job.148 # https://github.com/pypa/gh-action-pypi-publish#non-goals149 - name: Build project for distribution150 run: uv build151 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}152153 - name: Upload build154 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7155 with:156 name: dist157 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/158159 - name: Check version160 id: check-version161 shell: python162 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}163 run: |164 import os165 import tomllib166 with open("pyproject.toml", "rb") as f:167 data = tomllib.load(f)168 pkg_name = data["project"]["name"]169 version = data["project"]["version"]170 with open(os.environ["GITHUB_OUTPUT"], "a") as f:171 f.write(f"pkg-name={pkg_name}\n")172 f.write(f"version={version}\n")173 release-notes:174 name: 📝 Generate release notes175 # release-notes must run before publishing because its check-tags step176 # validates version/tag state — do not remove this dependency.177 needs:178 - build179 runs-on: ubuntu-latest180 permissions:181 contents: read182 outputs:183 release-body: ${{ steps.generate-release-body.outputs.release-body }}184 steps:185 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6186 with:187 repository: langchain-ai/langchain188 path: langchain189 sparse-checkout: | # this only grabs files for relevant dir190 ${{ env.EFFECTIVE_WORKING_DIR }}191 ref: ${{ github.ref }} # this scopes to just ref'd branch192 fetch-depth: 0 # this fetches entire commit history193 - name: Check tags194 id: check-tags195 shell: bash196 working-directory: langchain/${{ env.EFFECTIVE_WORKING_DIR }}197 env:198 PKG_NAME: ${{ needs.build.outputs.pkg-name }}199 VERSION: ${{ needs.build.outputs.version }}200 run: |201 # Handle regular versions and pre-release versions differently202 if [[ "$VERSION" == *"-"* ]]; then203 # This is a pre-release version (contains a hyphen)204 # Extract the base version without the pre-release suffix205 BASE_VERSION=${VERSION%%-*}206 # Look for the latest release of the same base version207 REGEX="^$PKG_NAME==$BASE_VERSION\$"208 PREV_TAG=$(git tag --sort=-creatordate | (grep -P "$REGEX" || true) | head -1)209210 # If no exact base version match, look for the latest release of any kind211 if [ -z "$PREV_TAG" ]; then212 REGEX="^$PKG_NAME==\\d+\\.\\d+\\.\\d+\$"213 PREV_TAG=$(git tag --sort=-creatordate | (grep -P "$REGEX" || true) | head -1)214 fi215 else216 # Regular version handling217 PREV_TAG="$PKG_NAME==${VERSION%.*}.$(( ${VERSION##*.} - 1 ))"; [[ "${VERSION##*.}" -eq 0 ]] && PREV_TAG=""218219 # backup case if releasing e.g. 0.3.0, looks up last release220 # note if last release (chronologically) was e.g. 0.1.47 it will get221 # that instead of the last 0.2 release222 if [ -z "$PREV_TAG" ]; then223 REGEX="^$PKG_NAME==\\d+\\.\\d+\\.\\d+\$"224 echo $REGEX225 PREV_TAG=$(git tag --sort=-creatordate | (grep -P $REGEX || true) | head -1)226 fi227 fi228229 # if PREV_TAG is empty or came out to 0.0.0, let it be empty230 if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "$PKG_NAME==0.0.0" ]; then231 echo "No previous tag found - first release"232 else233 # confirm prev-tag actually exists in git repo with git tag234 GIT_TAG_RESULT=$(git tag -l "$PREV_TAG")235 if [ -z "$GIT_TAG_RESULT" ]; then236 echo "Previous tag $PREV_TAG not found in git repo"237 exit 1238 fi239 fi240241242 TAG="${PKG_NAME}==${VERSION}"243 if [ "$TAG" == "$PREV_TAG" ]; then244 echo "No new version to release"245 exit 1246 fi247 echo tag="$TAG" >> $GITHUB_OUTPUT248 echo prev-tag="$PREV_TAG" >> $GITHUB_OUTPUT249 - name: Generate release body250 id: generate-release-body251 working-directory: langchain252 env:253 WORKING_DIR: ${{ env.EFFECTIVE_WORKING_DIR }}254 PKG_NAME: ${{ needs.build.outputs.pkg-name }}255 TAG: ${{ steps.check-tags.outputs.tag }}256 PREV_TAG: ${{ steps.check-tags.outputs.prev-tag }}257 run: |258 PREAMBLE="Changes since $PREV_TAG"259 # if PREV_TAG is empty or 0.0.0, then we are releasing the first version260 if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "$PKG_NAME==0.0.0" ]; then261 PREAMBLE="Initial release"262 PREV_TAG=$(git rev-list --max-parents=0 HEAD)263 fi264 {265 echo 'release-body<<EOF'266 echo $PREAMBLE267 echo268 git log --format="%s" "$PREV_TAG"..HEAD -- $WORKING_DIR269 echo EOF270 } >> "$GITHUB_OUTPUT"271272 pre-release-checks:273 name: ✅ Pre-release checks274 needs:275 - build276 - release-notes277 environment: Release278 runs-on: ubuntu-latest279 permissions:280 contents: read281 timeout-minutes: 20282 steps:283 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6284285 # We explicitly *don't* set up caching here. This ensures our tests are286 # maximally sensitive to catching breakage.287 #288 # For example, here's a way that caching can cause a falsely-passing test:289 # - Make the langchain package manifest no longer list a dependency package290 # as a requirement. This means it won't be installed by `pip install`,291 # and attempting to use it would cause a crash.292 # - That dependency used to be required, so it may have been cached.293 # When restoring the venv packages from cache, that dependency gets included.294 # - Tests pass, because the dependency is present even though it wasn't specified.295 # - The package is published, and it breaks on the missing dependency when296 # used in the real world.297298 - name: Set up Python + uv299 uses: "./.github/actions/uv_setup"300 id: setup-python301 with:302 python-version: ${{ env.PYTHON_VERSION }}303 enable-cache: "false"304305 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8306 with:307 name: dist308 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/309310 - name: Import dist package311 shell: bash312 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}313 env:314 PKG_NAME: ${{ needs.build.outputs.pkg-name }}315 VERSION: ${{ needs.build.outputs.version }}316 PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}317 # Install directly from the locally-built wheel (no index resolution needed).318 # `PRERELEASE_FLAG` is empty by default; opt-in via the `allow-prereleases`319 # workflow input lets transitive prerelease deps resolve during alpha320 # release cycles. Stable-release safety is still enforced by the321 # `Check for prerelease versions` step below.322 run: |323 uv venv324 VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG dist/*.whl325326 # Replace all dashes in the package name with underscores,327 # since that's how Python imports packages with dashes in the name.328 # also remove _official suffix329 IMPORT_NAME="$(echo "$PKG_NAME" | sed s/-/_/g | sed s/_official//g)"330331 uv run python -c "import $IMPORT_NAME; print(dir($IMPORT_NAME))"332333 - name: Import test dependencies334 run: uv sync --group test335 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}336337 # Overwrite the local version of the package with the built version338 - name: Import published package (again)339 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}340 shell: bash341 env:342 PKG_NAME: ${{ needs.build.outputs.pkg-name }}343 VERSION: ${{ needs.build.outputs.version }}344 PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}345 run: |346 VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG dist/*.whl347348 - name: Check for prerelease versions349 # Block release if any dependencies allow prerelease versions350 # (unless this is itself a prerelease version)351 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}352 run: |353 uv run python $GITHUB_WORKSPACE/.github/scripts/check_prerelease_dependencies.py pyproject.toml354355 - name: Run unit tests356 run: make tests357 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}358359 - name: Get minimum versions360 # Find the minimum published versions that satisfies the given constraints361 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}362 id: min-version363 run: |364 VIRTUAL_ENV=.venv uv pip install packaging requests365 python_version="$(uv run python --version | awk '{print $2}')"366 min_versions="$(uv run python $GITHUB_WORKSPACE/.github/scripts/get_min_versions.py pyproject.toml release $python_version)"367 echo "min-versions=$min_versions" >> "$GITHUB_OUTPUT"368 echo "min-versions=$min_versions"369370 - name: Run unit tests with minimum dependency versions371 if: ${{ steps.min-version.outputs.min-versions != '' }}372 env:373 MIN_VERSIONS: ${{ steps.min-version.outputs.min-versions }}374 PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}375 run: |376 VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG --force-reinstall --editable .377 VIRTUAL_ENV=.venv uv pip install $PRERELEASE_FLAG --force-reinstall $MIN_VERSIONS378 make tests PYTEST_EXTRA="-q -k 'not test_serdes'"379 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}380381 - name: Import integration test dependencies382 run: uv sync --group test --group test_integration383 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}384385 - name: Run integration tests386 # Uses the Makefile's `integration_tests` target for the specified package387 if: ${{ startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/partners/') }}388 env:389 AI21_API_KEY: ${{ secrets.AI21_API_KEY }}390 GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}391 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}392 MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}393 TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}394 OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}395 AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}396 AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}397 AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}398 AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}399 AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME }}400 AZURE_OPENAI_LLM_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LLM_DEPLOYMENT_NAME }}401 AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME }}402 NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}403 GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }}404 GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }}405 GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}406 HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HUGGINGFACEHUB_API_TOKEN }}407 EXA_API_KEY: ${{ secrets.EXA_API_KEY }}408 NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}409 WATSONX_APIKEY: ${{ secrets.WATSONX_APIKEY }}410 WATSONX_PROJECT_ID: ${{ secrets.WATSONX_PROJECT_ID }}411 ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}412 ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}413 ASTRA_DB_KEYSPACE: ${{ secrets.ASTRA_DB_KEYSPACE }}414 ES_URL: ${{ secrets.ES_URL }}415 ES_CLOUD_ID: ${{ secrets.ES_CLOUD_ID }}416 ES_API_KEY: ${{ secrets.ES_API_KEY }}417 MONGODB_ATLAS_URI: ${{ secrets.MONGODB_ATLAS_URI }}418 UPSTAGE_API_KEY: ${{ secrets.UPSTAGE_API_KEY }}419 FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}420 XAI_API_KEY: ${{ secrets.XAI_API_KEY }}421 DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}422 PPLX_API_KEY: ${{ secrets.PPLX_API_KEY }}423 OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }}424 OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}425 LANGCHAIN_TESTS_USER_AGENT: ${{ secrets.LANGCHAIN_TESTS_USER_AGENT }}426 run: make integration_tests427 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}428429 test-pypi-publish:430 name: 🧪 Publish to TestPyPI431 # release-notes must run before publishing because its check-tags step432 # validates version/tag state — do not remove this dependency.433 needs:434 - build435 - release-notes436 - pre-release-checks437 environment: Release438 runs-on: ubuntu-latest439 permissions:440 # This permission is used for trusted publishing:441 # https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/442 #443 # Trusted publishing has to also be configured on PyPI for each package:444 # https://docs.pypi.org/trusted-publishers/adding-a-publisher/445 id-token: write446447 steps:448 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6449450 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8451 with:452 name: dist453 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/454455 - name: Publish to test PyPI456 uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1457 with:458 packages-dir: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/459 verbose: true460 print-hash: true461 repository-url: https://test.pypi.org/legacy/462 # We overwrite any existing distributions with the same name and version.463 # This is *only for CI use* and is *extremely dangerous* otherwise!464 # https://github.com/pypa/gh-action-pypi-publish#tolerating-release-package-file-duplicates465 skip-existing: true466 # Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0467 attestations: false468469 # Test select published packages against new core470 # Done when code changes are made to langchain-core471 test-prior-published-packages-against-new-core:472 name: 🔄 Test prior partners against new core473 # Installs the new core with old partners: Installs the new unreleased core474 # alongside the previously published partner packages and runs integration tests475 needs:476 - build477 - release-notes478 - test-pypi-publish479 - pre-release-checks480 environment: Release481 runs-on: ubuntu-latest482 permissions:483 contents: read484 strategy:485 matrix:486 partner: [ anthropic, openai ]487 fail-fast: false # Continue testing other partners if one fails488 env:489 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}490 ANTHROPIC_FILES_API_IMAGE_ID: ${{ secrets.ANTHROPIC_FILES_API_IMAGE_ID }}491 ANTHROPIC_FILES_API_PDF_ID: ${{ secrets.ANTHROPIC_FILES_API_PDF_ID }}492 OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}493 AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}494 AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}495 AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}496 AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}497 AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LEGACY_CHAT_DEPLOYMENT_NAME }}498 AZURE_OPENAI_LLM_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LLM_DEPLOYMENT_NAME }}499 AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME }}500 LANGCHAIN_TESTS_USER_AGENT: ${{ secrets.LANGCHAIN_TESTS_USER_AGENT }}501 steps:502 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6503504 # We implement this conditional as Github Actions does not have good support505 # for conditionally needing steps. https://github.com/actions/runner/issues/491506 # TODO: this seems to be resolved upstream, so we can probably remove this workaround507 - name: Check if libs/core508 run: |509 if [ "${{ startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core') }}" != "true" ]; then510 echo "Not in libs/core. Exiting successfully."511 exit 0512 fi513514 - name: Set up Python + uv515 if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')516 uses: "./.github/actions/uv_setup"517 with:518 python-version: ${{ env.PYTHON_VERSION }}519 enable-cache: "false"520521 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8522 if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')523 with:524 name: dist525 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/526527 - name: Test against ${{ matrix.partner }}528 if: startsWith(env.EFFECTIVE_WORKING_DIR, 'libs/core')529 env:530 PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}531 run: |532 # Identify latest tag, excluding pre-releases533 LATEST_PACKAGE_TAG="$(534 git ls-remote --tags origin "langchain-${{ matrix.partner }}*" \535 | awk '{print $2}' \536 | sed 's|refs/tags/||' \537 | grep -E '[0-9]+\.[0-9]+\.[0-9]+$' \538 | sort -Vr \539 | head -n 1540 )"541 echo "Latest package tag: $LATEST_PACKAGE_TAG"542543 # Shallow-fetch just that single tag544 git fetch --depth=1 origin tag "$LATEST_PACKAGE_TAG"545546 # Checkout the latest package files547 rm -rf $GITHUB_WORKSPACE/libs/partners/${{ matrix.partner }}/*548 rm -rf $GITHUB_WORKSPACE/libs/standard-tests/*549 cd $GITHUB_WORKSPACE/libs/550 git checkout "$LATEST_PACKAGE_TAG" -- standard-tests/551 git checkout "$LATEST_PACKAGE_TAG" -- partners/${{ matrix.partner }}/552 cd partners/${{ matrix.partner }}553554 # Print as a sanity check555 echo "Version number from pyproject.toml: "556 cat pyproject.toml | grep "version = "557558 # Run tests559 uv sync --group test --group test_integration560 uv pip install $PRERELEASE_FLAG ../../core/dist/*.whl561 make integration_tests562563 # Test external packages that depend on langchain-core/langchain against the new release564 # Only runs for core and langchain_v1 releases to catch breaking changes before publish565 test-dependents:566 name: "🐍 Test dependent: ${{ matrix.package.path }} (Python ${{567 matrix.python-version }})"568 needs:569 - build570 - release-notes571 - test-pypi-publish572 - pre-release-checks573 runs-on: ubuntu-latest574 permissions:575 contents: read576 # Only run for core or langchain_v1 releases.577 # Job-level 'if' does not support env context, so EFFECTIVE_WORKING_DIR is578 # unavailable; must use inputs directly and match both forms: short dropdown579 # names (workflow_dispatch, e.g. 'core') and full 'libs/' paths580 # (workflow_call / working-directory-override).581 if: >-582 contains(fromJSON('["core","langchain_v1"]'),583 inputs.working-directory-override || inputs.working-directory) ||584 startsWith(inputs.working-directory-override || inputs.working-directory,585 'libs/core') || startsWith(inputs.working-directory-override ||586 inputs.working-directory, 'libs/langchain_v1')587 strategy:588 fail-fast: false589 matrix:590 python-version: [ "3.11", "3.13" ]591 package:592 - name: deepagents593 repo: langchain-ai/deepagents594 path: libs/deepagents595 # No API keys needed for now - deepagents `make test` only runs unit tests596597 steps:598 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6599 with:600 path: langchain601602 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6603 with:604 repository: ${{ matrix.package.repo }}605 path: ${{ matrix.package.name }}606607 - name: Set up Python + uv608 uses: "./langchain/.github/actions/uv_setup"609 with:610 python-version: ${{ matrix.python-version }}611 enable-cache: "false"612613 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8614 with:615 name: dist616 path: dist/617618 - name: Install ${{ matrix.package.name }} with local packages619 # External dependents don't have [tool.uv.sources] pointing to this repo,620 # so we install the package normally then override with the built wheel.621 env:622 PRERELEASE_FLAG: ${{ inputs.allow-prereleases && '--prerelease=allow' || '' }}623 run: |624 cd ${{ matrix.package.name }}/${{ matrix.package.path }}625626 # Install the package with test dependencies627 uv sync --group test628629 # Override with the built wheel from this release630 uv pip install $PRERELEASE_FLAG $GITHUB_WORKSPACE/dist/*.whl631632 - name: Run ${{ matrix.package.name }} tests633 run: |634 cd ${{ matrix.package.name }}/${{ matrix.package.path }}635 make test636637 publish:638 name: 🚀 Publish to PyPI639 # Publishes the package to PyPI640 needs:641 - build642 - release-notes643 - test-pypi-publish644 - pre-release-checks645 - test-dependents646 - test-prior-published-packages-against-new-core647 # Run if all needed jobs succeeded or were skipped (test-dependents and648 # test-prior-published-packages-against-new-core only run for core/langchain_v1)649 if: ${{ !cancelled() && !failure() }}650 environment: Release651 runs-on: ubuntu-latest652 permissions:653 # This permission is used for trusted publishing:654 # https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/655 #656 # Trusted publishing has to also be configured on PyPI for each package:657 # https://docs.pypi.org/trusted-publishers/adding-a-publisher/658 id-token: write659660 defaults:661 run:662 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}663664 steps:665 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6666667 - name: Set up Python + uv668 uses: "./.github/actions/uv_setup"669 with:670 python-version: ${{ env.PYTHON_VERSION }}671 enable-cache: "false"672673 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8674 with:675 name: dist676 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/677678 - name: Publish package distributions to PyPI679 uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1680 with:681 packages-dir: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/682 verbose: true683 print-hash: true684 # Temp workaround since attestations are on by default as of gh-action-pypi-publish v1.11.0685 attestations: false686687 mark-release:688 name: 🏷️ Tag GitHub release689 # Marks the GitHub release with the new version tag690 needs:691 - build692 - release-notes693 - test-pypi-publish694 - pre-release-checks695 - publish696 # Run if all needed jobs succeeded or were skipped697 if: ${{ !cancelled() && !failure() }}698 environment: Release699 runs-on: ubuntu-latest700 permissions:701 # This permission is needed by `ncipollo/release-action` to702 # create the GitHub release/tag703 contents: write704705 defaults:706 run:707 working-directory: ${{ env.EFFECTIVE_WORKING_DIR }}708709 steps:710 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6711712 - name: Set up Python + uv713 uses: "./.github/actions/uv_setup"714 with:715 python-version: ${{ env.PYTHON_VERSION }}716 enable-cache: "false"717718 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8719 with:720 name: dist721 path: ${{ env.EFFECTIVE_WORKING_DIR }}/dist/722723 - name: Create Tag724 uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1725 with:726 artifacts: "dist/*"727 token: ${{ secrets.GITHUB_TOKEN }}728 generateReleaseNotes: false729 tag: ${{needs.build.outputs.pkg-name}}==${{ needs.build.outputs.version }}730 body: ${{ needs.release-notes.outputs.release-body }}731 commit: ${{ github.sha }}732 makeLatest: ${{ needs.build.outputs.pkg-name == 'langchain-core'}}
Findings
✓ No findings reported for this file.