diff --git a/.github/workflows/_refresh_model_profiles.yml b/.github/workflows/_refresh_model_profiles.yml new file mode 100644 index 00000000000..afb8ea1c65c --- /dev/null +++ b/.github/workflows/_refresh_model_profiles.yml @@ -0,0 +1,202 @@ +# 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 + + - 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 --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 diff --git a/.github/workflows/refresh_model_profiles.yml b/.github/workflows/refresh_model_profiles.yml index 802af3c3dc1..9e61a0dd47b 100644 --- a/.github/workflows/refresh_model_profiles.yml +++ b/.github/workflows/refresh_model_profiles.yml @@ -18,55 +18,28 @@ permissions: jobs: refresh-profiles: - name: "refresh all partner profiles" - runs-on: ubuntu-latest - steps: - - name: "📋 Checkout" - uses: actions/checkout@v6 + uses: ./.github/workflows/_refresh_model_profiles.yml + with: + providers: >- + [ + {"provider":"anthropic", "data_dir":"libs/partners/anthropic/langchain_anthropic/data"}, + {"provider":"deepseek", "data_dir":"libs/partners/deepseek/langchain_deepseek/data"}, + {"provider":"fireworks-ai", "data_dir":"libs/partners/fireworks/langchain_fireworks/data"}, + {"provider":"groq", "data_dir":"libs/partners/groq/langchain_groq/data"}, + {"provider":"huggingface", "data_dir":"libs/partners/huggingface/langchain_huggingface/data"}, + {"provider":"mistral", "data_dir":"libs/partners/mistralai/langchain_mistralai/data"}, + {"provider":"openai", "data_dir":"libs/partners/openai/langchain_openai/data"}, + {"provider":"openrouter", "data_dir":"libs/partners/openrouter/langchain_openrouter/data"}, + {"provider":"perplexity", "data_dir":"libs/partners/perplexity/langchain_perplexity/data"}, + {"provider":"xai", "data_dir":"libs/partners/xai/langchain_xai/data"} + ] + cli-path: libs/model-profiles + add-paths: libs/partners/**/data/_profiles.py + pr-body: | + Automated refresh of model profile data for all in-monorepo partner + integrations via `langchain-profiles refresh`. - - name: "🐍 Set up Python + uv" - uses: ./.github/actions/uv_setup - with: - python-version: "3.12" - working-directory: libs/model-profiles - - - name: "📦 Install langchain-profiles CLI" - working-directory: libs/model-profiles - run: uv sync - - - name: "🔄 Refresh profiles" - working-directory: libs/model-profiles - run: make refresh-profiles - - - name: "🔑 Generate GitHub App token" - id: app-token - uses: actions/create-github-app-token@v2 - 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: bot/refresh-model-profiles - commit-message: "chore(model-profiles): refresh model profile data" - title: "chore(model-profiles): refresh model profile data" - body: | - Automated refresh of model profile data for all in-monorepo partner - integrations via `langchain-profiles refresh`. - - 🤖 Generated by the `refresh_model_profiles` workflow. - labels: bot - add-paths: libs/partners/**/data/_profiles.py - - - name: "📝 Summary" - run: | - op="${{ steps.create-pr.outputs.pull-request-operation }}" - url="${{ steps.create-pr.outputs.pull-request-url }}" - if [ "$op" = "created" ] || [ "$op" = "updated" ]; then - echo "### ✅ PR ${op}: ${url}" >> "$GITHUB_STEP_SUMMARY" - else - echo "### ⏭️ Skipped: profiles already up to date" >> "$GITHUB_STEP_SUMMARY" - fi + 🤖 Generated by the `refresh_model_profiles` workflow. + secrets: + MODEL_PROFILE_BOT_APP_ID: ${{ secrets.MODEL_PROFILE_BOT_APP_ID }} + MODEL_PROFILE_BOT_PRIVATE_KEY: ${{ secrets.MODEL_PROFILE_BOT_PRIVATE_KEY }}