.github/workflows/_release.yml YAML 733 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.