1# Reusable workflow: refreshes model profile data for any repo that uses the2# `langchain-profiles` CLI. Creates (or updates) a pull request with the3# resulting changes.4#5# Callers MUST set `permissions: { contents: write, pull-requests: write }` —6# reusable workflows cannot escalate the caller's token permissions.7#8# ── Example: external repo (langchain-google) ──────────────────────────9#10# jobs:11# refresh-profiles:12# uses: langchain-ai/langchain/.github/workflows/_refresh_model_profiles.yml@master13# with:14# providers: >-15# [16# {"provider":"google", "data_dir":"libs/genai/langchain_google_genai/data"},17# ]18# secrets:19# MODEL_PROFILE_BOT_CLIENT_ID: ${{ secrets.MODEL_PROFILE_BOT_CLIENT_ID }}20# MODEL_PROFILE_BOT_PRIVATE_KEY: ${{ secrets.MODEL_PROFILE_BOT_PRIVATE_KEY }}2122name: "Refresh Model Profiles (reusable)"2324on:25 workflow_call:26 inputs:27 providers:28 description: >-29 JSON array of objects, each with `provider` (models.dev provider ID)30 and `data_dir` (path relative to repo root where `_profiles.py` and31 `profile_augmentations.toml` live).32 required: true33 type: string34 cli-path:35 description: >-36 Path (relative to workspace) to an existing `libs/model-profiles`37 checkout. When set the workflow skips cloning the langchain repo and38 uses this directory for the CLI instead. Useful when the caller IS39 the langchain monorepo.40 required: false41 type: string42 default: ""43 cli-ref:44 description: >-45 Git ref of langchain-ai/langchain to checkout for the CLI.46 Ignored when `cli-path` is set.47 required: false48 type: string49 default: master50 add-paths:51 description: "Glob for files to stage in the PR commit."52 required: false53 type: string54 default: "**/_profiles.py"55 pr-branch:56 description: "Branch name for the auto-created PR."57 required: false58 type: string59 default: bot/refresh-model-profiles60 pr-title:61 description: "PR / commit title."62 required: false63 type: string64 default: "chore(model-profiles): refresh model profile data"65 pr-body:66 description: "PR body."67 required: false68 type: string69 default: |70 Automated refresh of model profile data via `langchain-profiles refresh`.7172 🤖 Generated by the [`refresh_model_profiles` workflow](https://github.com/langchain-ai/langchain/blob/master/.github/workflows/refresh_model_profiles.yml).73 pr-labels:74 description: "Comma-separated labels to apply to the PR."75 required: false76 type: string77 default: bot78 secrets:79 MODEL_PROFILE_BOT_CLIENT_ID:80 required: true81 MODEL_PROFILE_BOT_PRIVATE_KEY:82 required: true8384permissions:85 contents: write86 pull-requests: write8788jobs:89 refresh-profiles:90 name: refresh model profiles91 runs-on: ubuntu-latest92 steps:93 - name: "📋 Checkout"94 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v69596 - name: "📋 Checkout langchain-profiles CLI"97 if: inputs.cli-path == ''98 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v699 with:100 repository: langchain-ai/langchain101 ref: ${{ inputs.cli-ref }}102 sparse-checkout: libs/model-profiles103 path: _langchain-cli104105 - name: "🔧 Resolve CLI directory"106 id: cli107 env:108 CLI_PATH: ${{ inputs.cli-path }}109 run: |110 if [ -n "${CLI_PATH}" ]; then111 resolved="${GITHUB_WORKSPACE}/${CLI_PATH}"112 if [ ! -d "${resolved}" ]; then113 echo "::error::cli-path '${CLI_PATH}' does not exist at ${resolved}"114 exit 1115 fi116 echo "dir=${CLI_PATH}" >> "$GITHUB_OUTPUT"117 else118 echo "dir=_langchain-cli/libs/model-profiles" >> "$GITHUB_OUTPUT"119 fi120121 - name: "🐍 Set up Python + uv"122 uses: astral-sh/setup-uv@0ca8f610542aa7f4acaf39e65cf4eb3c35091883 # v7123 with:124 version: "0.5.25"125 python-version: "3.12"126 enable-cache: true127 cache-dependency-glob: "**/model-profiles/uv.lock"128129 - name: "📦 Install langchain-profiles CLI"130 working-directory: ${{ steps.cli.outputs.dir }}131 run: uv sync --frozen --no-group test --no-group dev --no-group lint132133 - name: "✅ Validate providers input"134 env:135 PROVIDERS_JSON: ${{ inputs.providers }}136 run: |137 echo "${PROVIDERS_JSON}" | jq -e 'type == "array" and length > 0' > /dev/null || {138 echo "::error::providers input must be a non-empty JSON array"139 exit 1140 }141 echo "${PROVIDERS_JSON}" | jq -e 'all(has("provider") and has("data_dir"))' > /dev/null || {142 echo "::error::every entry in providers must have 'provider' and 'data_dir' keys"143 exit 1144 }145146 - name: "🔄 Refresh profiles"147 env:148 PROVIDERS_JSON: ${{ inputs.providers }}149 run: |150 cli_dir="${GITHUB_WORKSPACE}/${{ steps.cli.outputs.dir }}"151 failed=""152 mapfile -t rows < <(echo "${PROVIDERS_JSON}" | jq -c '.[]')153 for row in "${rows[@]}"; do154 provider=$(echo "${row}" | jq -r '.provider')155 data_dir=$(echo "${row}" | jq -r '.data_dir')156 echo "--- Refreshing ${provider} -> ${data_dir} ---"157 if ! echo y | uv run --frozen --project "${cli_dir}" \158 langchain-profiles refresh \159 --provider "${provider}" \160 --data-dir "${GITHUB_WORKSPACE}/${data_dir}"; then161 echo "::error::Failed to refresh provider: ${provider}"162 failed="${failed} ${provider}"163 fi164 done165 if [ -n "${failed}" ]; then166 echo "::error::The following providers failed:${failed}"167 exit 1168 fi169170 - name: "📝 Build PR body with change summary"171 id: pr-body172 env:173 PROVIDERS_JSON: ${{ inputs.providers }}174 PR_BODY: ${{ inputs.pr-body }}175 run: |176 # The refresh step modified the working tree without committing, so177 # comparing against HEAD yields exactly the refresh's changes.178 cli_dir="${GITHUB_WORKSPACE}/${{ steps.cli.outputs.dir }}"179 body_file="${RUNNER_TEMP}/pr_body.md"180 printf '%s\n\n' "${PR_BODY}" > "${body_file}"181 # `summarize` builds the whole summary in memory and prints it once,182 # so a failure exits non-zero before any stdout reaches the append —183 # the body keeps only the static note, never a half-written summary.184 if ! uv run --frozen --project "${cli_dir}" \185 langchain-profiles summarize \186 --providers "${PROVIDERS_JSON}" \187 --base-ref HEAD \188 --repo-root "${GITHUB_WORKSPACE}" >> "${body_file}"; then189 echo "::warning::Could not generate change summary; see job log."190 # Surface the degradation in the PR body too: the warning above only191 # lands in the Actions log, which a PR reviewer won't see.192 printf '\n> [!NOTE]\n> Automated change summary unavailable — see the workflow run log.\n' >> "${body_file}"193 fi194 echo "path=${body_file}" >> "$GITHUB_OUTPUT"195196 - name: "🔑 Generate GitHub App token"197 id: app-token198 uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3199 with:200 client-id: ${{ secrets.MODEL_PROFILE_BOT_CLIENT_ID }}201 private-key: ${{ secrets.MODEL_PROFILE_BOT_PRIVATE_KEY }}202203 - name: "🔀 Create pull request"204 id: create-pr205 uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8206 with:207 token: ${{ steps.app-token.outputs.token }}208 branch: ${{ inputs.pr-branch }}209 commit-message: ${{ inputs.pr-title }}210 title: ${{ inputs.pr-title }}211 body-path: ${{ steps.pr-body.outputs.path }}212 labels: ${{ inputs.pr-labels }}213 add-paths: ${{ inputs.add-paths }}214215 - name: "📝 Summary"216 if: always()217 env:218 PR_OP: ${{ steps.create-pr.outputs.pull-request-operation }}219 PR_URL: ${{ steps.create-pr.outputs.pull-request-url }}220 JOB_STATUS: ${{ job.status }}221 run: |222 if [ "${PR_OP}" = "created" ] || [ "${PR_OP}" = "updated" ]; then223 echo "### ✅ PR ${PR_OP}: ${PR_URL}" >> "$GITHUB_STEP_SUMMARY"224 elif [ -z "${PR_OP}" ] && [ "${JOB_STATUS}" = "success" ]; then225 echo "### ⏭️ Skipped: profiles already up to date" >> "$GITHUB_STEP_SUMMARY"226 elif [ "${JOB_STATUS}" = "failure" ]; then227 echo "### ❌ Job failed — check step logs for details" >> "$GITHUB_STEP_SUMMARY"228 fi
Findings
✓ No findings reported for this file.