# Reusable workflow: refreshes model profile data for any repo that uses the # `langchain-profiles` CLI. Creates (or updates) a pull request with the # resulting changes. # # Callers MUST set `permissions: { contents: write, pull-requests: write }` — # reusable workflows cannot escalate the caller's token permissions. # # ── Example: external repo (langchain-google) ────────────────────────── # # jobs: # refresh-profiles: # uses: langchain-ai/langchain/.github/workflows/_refresh_model_profiles.yml@master # with: # providers: >- # [ # {"provider":"google", "data_dir":"libs/genai/langchain_google_genai/data"}, # ] # secrets: # MODEL_PROFILE_BOT_APP_ID: ${{ secrets.MODEL_PROFILE_BOT_APP_ID }} # MODEL_PROFILE_BOT_PRIVATE_KEY: ${{ secrets.MODEL_PROFILE_BOT_PRIVATE_KEY }} name: "Refresh Model Profiles (reusable)" on: workflow_call: inputs: providers: description: >- JSON array of objects, each with `provider` (models.dev provider ID) and `data_dir` (path relative to repo root where `_profiles.py` and `profile_augmentations.toml` live). required: true type: string cli-path: description: >- Path (relative to workspace) to an existing `libs/model-profiles` checkout. When set the workflow skips cloning the langchain repo and uses this directory for the CLI instead. Useful when the caller IS the langchain monorepo. required: false type: string default: "" cli-ref: description: >- Git ref of langchain-ai/langchain to checkout for the CLI. Ignored when `cli-path` is set. required: false type: string default: master add-paths: description: "Glob for files to stage in the PR commit." required: false type: string default: "**/_profiles.py" pr-branch: description: "Branch name for the auto-created PR." required: false type: string default: bot/refresh-model-profiles pr-title: description: "PR / commit title." required: false type: string default: "chore(model-profiles): refresh model profile data" pr-body: description: "PR body." required: false type: string default: | Automated refresh of model profile data via `langchain-profiles refresh`. 🤖 Generated by the `refresh_model_profiles` workflow. pr-labels: description: "Comma-separated labels to apply to the PR." required: false type: string default: bot secrets: MODEL_PROFILE_BOT_APP_ID: required: true MODEL_PROFILE_BOT_PRIVATE_KEY: required: true permissions: contents: write pull-requests: write jobs: refresh-profiles: name: refresh model profiles runs-on: ubuntu-latest steps: - name: "📋 Checkout" uses: actions/checkout@v6 - name: "📋 Checkout langchain-profiles CLI" if: inputs.cli-path == '' uses: actions/checkout@v6 with: repository: langchain-ai/langchain ref: ${{ inputs.cli-ref }} sparse-checkout: libs/model-profiles path: _langchain-cli - name: "🔧 Resolve CLI directory" id: cli env: CLI_PATH: ${{ inputs.cli-path }} run: | if [ -n "${CLI_PATH}" ]; then resolved="${GITHUB_WORKSPACE}/${CLI_PATH}" if [ ! -d "${resolved}" ]; then echo "::error::cli-path '${CLI_PATH}' does not exist at ${resolved}" exit 1 fi echo "dir=${CLI_PATH}" >> "$GITHUB_OUTPUT" else echo "dir=_langchain-cli/libs/model-profiles" >> "$GITHUB_OUTPUT" fi - name: "🐍 Set up Python + uv" uses: astral-sh/setup-uv@0ca8f610542aa7f4acaf39e65cf4eb3c35091883 # v7 with: version: "0.5.25" python-version: "3.12" enable-cache: true cache-dependency-glob: "**/model-profiles/uv.lock" - name: "📦 Install langchain-profiles CLI" working-directory: ${{ steps.cli.outputs.dir }} run: uv sync --frozen --no-group test --no-group dev --no-group lint - name: "✅ Validate providers input" env: PROVIDERS_JSON: ${{ inputs.providers }} run: | echo "${PROVIDERS_JSON}" | jq -e 'type == "array" and length > 0' > /dev/null || { echo "::error::providers input must be a non-empty JSON array" exit 1 } echo "${PROVIDERS_JSON}" | jq -e 'all(has("provider") and has("data_dir"))' > /dev/null || { echo "::error::every entry in providers must have 'provider' and 'data_dir' keys" exit 1 } - name: "🔄 Refresh profiles" env: PROVIDERS_JSON: ${{ inputs.providers }} run: | cli_dir="${GITHUB_WORKSPACE}/${{ steps.cli.outputs.dir }}" failed="" mapfile -t rows < <(echo "${PROVIDERS_JSON}" | jq -c '.[]') for row in "${rows[@]}"; do provider=$(echo "${row}" | jq -r '.provider') data_dir=$(echo "${row}" | jq -r '.data_dir') echo "--- Refreshing ${provider} -> ${data_dir} ---" if ! echo y | uv run --frozen --project "${cli_dir}" \ langchain-profiles refresh \ --provider "${provider}" \ --data-dir "${GITHUB_WORKSPACE}/${data_dir}"; then echo "::error::Failed to refresh provider: ${provider}" failed="${failed} ${provider}" fi done if [ -n "${failed}" ]; then echo "::error::The following providers failed:${failed}" exit 1 fi - name: "🔑 Generate GitHub App token" id: app-token uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.MODEL_PROFILE_BOT_APP_ID }} private-key: ${{ secrets.MODEL_PROFILE_BOT_PRIVATE_KEY }} - name: "🔀 Create pull request" id: create-pr uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8 with: token: ${{ steps.app-token.outputs.token }} branch: ${{ inputs.pr-branch }} commit-message: ${{ inputs.pr-title }} title: ${{ inputs.pr-title }} body: ${{ inputs.pr-body }} labels: ${{ inputs.pr-labels }} add-paths: ${{ inputs.add-paths }} - name: "📝 Summary" if: always() env: PR_OP: ${{ steps.create-pr.outputs.pull-request-operation }} PR_URL: ${{ steps.create-pr.outputs.pull-request-url }} JOB_STATUS: ${{ job.status }} run: | if [ "${PR_OP}" = "created" ] || [ "${PR_OP}" = "updated" ]; then echo "### ✅ PR ${PR_OP}: ${PR_URL}" >> "$GITHUB_STEP_SUMMARY" elif [ -z "${PR_OP}" ] && [ "${JOB_STATUS}" = "success" ]; then echo "### ⏭️ Skipped: profiles already up to date" >> "$GITHUB_STEP_SUMMARY" elif [ "${JOB_STATUS}" = "failure" ]; then echo "### ❌ Job failed — check step logs for details" >> "$GITHUB_STEP_SUMMARY" fi