Gallery Agent #2135
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Gallery Agent | |
| on: | |
| schedule: | |
| - cron: '0 */12 * * *' # Run every 4 hours | |
| workflow_dispatch: | |
| inputs: | |
| search_term: | |
| description: 'Search term for models' | |
| required: false | |
| default: 'GGUF' | |
| type: string | |
| limit: | |
| description: 'Maximum number of models to process' | |
| required: false | |
| default: '15' | |
| type: string | |
| quantization: | |
| description: 'Preferred quantization format' | |
| required: false | |
| default: 'Q4_K_M' | |
| type: string | |
| max_models: | |
| description: 'Maximum number of models to add to the gallery' | |
| required: false | |
| default: '1' | |
| type: string | |
| jobs: | |
| gallery-agent: | |
| if: github.repository == 'mudler/LocalAI' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.21' | |
| - name: Proto Dependencies | |
| run: | | |
| # Install protoc | |
| curl -L -s https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip -o protoc.zip && \ | |
| unzip -j -d /usr/local/bin protoc.zip bin/protoc && \ | |
| rm protoc.zip | |
| go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2 | |
| go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@1958fcbe2ca8bd93af633f11e97d44e567e945af | |
| PATH="$PATH:$HOME/go/bin" make protogen-go | |
| - name: Process gallery-agent PR commands | |
| env: | |
| GH_TOKEN: ${{ secrets.UPDATE_BOT_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SEARCH: 'gallery agent in:title' | |
| run: | | |
| # Walk gallery-agent PRs and act on maintainer comments: | |
| # /gallery-agent blacklist → label `gallery-agent/blacklisted` + close (never repropose) | |
| # /gallery-agent recreate → close without label (next run may repropose) | |
| # Only comments from OWNER / MEMBER / COLLABORATOR are honored so | |
| # random users can't drive the bot. | |
| # | |
| # We scan both open PRs AND recently-closed PRs that don't already | |
| # carry the blacklist label. This covers the common flow where a | |
| # maintainer writes /gallery-agent blacklist and immediately clicks | |
| # Close — without this, the next scheduled run wouldn't see the | |
| # command (PR is already closed) and would repropose the model. | |
| gh label create gallery-agent/blacklisted \ | |
| --repo "$REPO" --color ededed \ | |
| --description "gallery-agent must not repropose this model" 2>/dev/null || true | |
| prs_open=$(gh pr list --repo "$REPO" --state open --search "$SEARCH" \ | |
| --json number --jq '.[].number') | |
| # Closed PRs from the last 14 days that don't yet have the blacklist label. | |
| # Bounded window keeps the scan cheap while covering late-applied commands. | |
| since=$(date -u -d '14 days ago' +%Y-%m-%d) | |
| prs_closed=$(gh pr list --repo "$REPO" --state closed \ | |
| --search "$SEARCH closed:>=$since -label:gallery-agent/blacklisted" \ | |
| --json number --jq '.[].number') | |
| prs=$(printf '%s\n%s\n' "$prs_open" "$prs_closed" | sort -u | sed '/^$/d') | |
| for pr in $prs; do | |
| state=$(gh pr view "$pr" --repo "$REPO" --json state --jq '.state') | |
| cmds=$(gh pr view "$pr" --repo "$REPO" --json comments \ | |
| --jq '.comments[] | select(.authorAssociation=="OWNER" or .authorAssociation=="MEMBER" or .authorAssociation=="COLLABORATOR") | .body') | |
| if echo "$cmds" | grep -qE '(^|[[:space:]])/gallery-agent[[:space:]]+blacklist([[:space:]]|$)'; then | |
| echo "PR #$pr: blacklist command found (state=$state)" | |
| gh pr edit "$pr" --repo "$REPO" --add-label gallery-agent/blacklisted || true | |
| if [ "$state" = "OPEN" ]; then | |
| gh pr close "$pr" --repo "$REPO" --comment "Blacklisted via \`/gallery-agent blacklist\`. This model will not be reproposed." || true | |
| fi | |
| elif [ "$state" = "OPEN" ] && echo "$cmds" | grep -qE '(^|[[:space:]])/gallery-agent[[:space:]]+recreate([[:space:]]|$)'; then | |
| echo "PR #$pr: recreate command found" | |
| gh pr close "$pr" --repo "$REPO" --comment "Closed via \`/gallery-agent recreate\`. The next scheduled run will propose this model again." || true | |
| fi | |
| done | |
| - name: Collect skip URLs for the gallery agent | |
| id: open_prs | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SEARCH: 'gallery agent in:title' | |
| run: | | |
| # Skip set = | |
| # URLs from any open gallery-agent PR (avoid duplicate PRs for the same model while one is pending) | |
| # + URLs from closed PRs carrying the `gallery-agent/blacklisted` label (hard blacklist) | |
| # Plain-closed PRs without the label are ignored — closing a PR is | |
| # not by itself a "never propose again" signal; maintainers must | |
| # opt in via the /gallery-agent blacklist comment command. | |
| urls_open=$(gh pr list --repo "$REPO" --state open --search "$SEARCH" \ | |
| --json body --jq '[.[].body] | join("\n")' \ | |
| | grep -oE 'https://huggingface\.co/[^ )]+' || true) | |
| urls_blacklist=$(gh pr list --repo "$REPO" --state closed --search "$SEARCH" \ | |
| --label gallery-agent/blacklisted \ | |
| --json body --jq '[.[].body] | join("\n")' \ | |
| | grep -oE 'https://huggingface\.co/[^ )]+' || true) | |
| urls=$(printf '%s\n%s\n' "$urls_open" "$urls_blacklist" | sort -u | sed '/^$/d') | |
| echo "Skip URLs:" | |
| echo "$urls" | |
| { | |
| echo "urls<<EOF" | |
| echo "$urls" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Run gallery agent | |
| env: | |
| SEARCH_TERM: ${{ github.event.inputs.search_term || 'GGUF' }} | |
| LIMIT: ${{ github.event.inputs.limit || '15' }} | |
| QUANTIZATION: ${{ github.event.inputs.quantization || 'Q4_K_M' }} | |
| MAX_MODELS: ${{ github.event.inputs.max_models || '1' }} | |
| EXTRA_SKIP_URLS: ${{ steps.open_prs.outputs.urls }} | |
| run: | | |
| export GALLERY_INDEX_PATH=$PWD/gallery/index.yaml | |
| go run ./.github/gallery-agent | |
| - name: Check for changes | |
| id: check_changes | |
| run: | | |
| if git diff --quiet gallery/index.yaml; then | |
| echo "changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes detected in gallery/index.yaml" | |
| else | |
| echo "changes=true" >> $GITHUB_OUTPUT | |
| echo "Changes detected in gallery/index.yaml" | |
| git diff gallery/index.yaml | |
| fi | |
| - name: Read gallery agent summary | |
| id: read_summary | |
| if: steps.check_changes.outputs.changes == 'true' | |
| run: | | |
| if [ -f "./gallery-agent-summary.json" ]; then | |
| echo "summary_exists=true" >> $GITHUB_OUTPUT | |
| # Extract summary data using jq | |
| echo "search_term=$(jq -r '.search_term' ./gallery-agent-summary.json)" >> $GITHUB_OUTPUT | |
| echo "total_found=$(jq -r '.total_found' ./gallery-agent-summary.json)" >> $GITHUB_OUTPUT | |
| echo "models_added=$(jq -r '.models_added' ./gallery-agent-summary.json)" >> $GITHUB_OUTPUT | |
| echo "quantization=$(jq -r '.quantization' ./gallery-agent-summary.json)" >> $GITHUB_OUTPUT | |
| echo "processing_time=$(jq -r '.processing_time' ./gallery-agent-summary.json)" >> $GITHUB_OUTPUT | |
| # Create a formatted list of added models with URLs | |
| added_models=$(jq -r 'range(0; .added_model_ids | length) as $i | "- [\(.added_model_ids[$i])](\(.added_model_urls[$i]))"' ./gallery-agent-summary.json | tr '\n' '\n') | |
| echo "added_models<<EOF" >> $GITHUB_OUTPUT | |
| echo "$added_models" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| rm -f ./gallery-agent-summary.json | |
| else | |
| echo "summary_exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create Pull Request | |
| if: steps.check_changes.outputs.changes == 'true' | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.UPDATE_BOT_TOKEN }} | |
| push-to-fork: ci-forks/LocalAI | |
| commit-message: 'chore(model gallery): :robot: add new models via gallery agent' | |
| title: 'chore(model gallery): :robot: add ${{ steps.read_summary.outputs.models_added || 0 }} new models via gallery agent' | |
| # Branch has to be unique so PRs are not overriding each other | |
| branch-suffix: timestamp | |
| body: | | |
| This PR was automatically created by the gallery agent workflow. | |
| **Summary:** | |
| - **Search Term:** ${{ steps.read_summary.outputs.search_term || github.event.inputs.search_term || 'GGUF' }} | |
| - **Models Found:** ${{ steps.read_summary.outputs.total_found || 'N/A' }} | |
| - **Models Added:** ${{ steps.read_summary.outputs.models_added || '0' }} | |
| - **Quantization:** ${{ steps.read_summary.outputs.quantization || github.event.inputs.quantization || 'Q4_K_M' }} | |
| - **Processing Time:** ${{ steps.read_summary.outputs.processing_time || 'N/A' }} | |
| **Added Models:** | |
| ${{ steps.read_summary.outputs.added_models || '- No models added' }} | |
| ### Bot commands | |
| Maintainers (owner / member / collaborator) can control this PR | |
| by leaving a comment with one of: | |
| - `/gallery-agent recreate` — close this PR; the next scheduled | |
| run will propose this model again (useful if the entry needs | |
| to be regenerated with fresh metadata). | |
| - `/gallery-agent blacklist` — close this PR and permanently | |
| prevent the gallery agent from ever reproposing this model. | |
| Plain "Close" (without a command) is treated as a no-op: the | |
| model may be reproposed by a future run. | |
| **Workflow Details:** | |
| - Triggered by: `${{ github.event_name }}` | |
| - Run ID: `${{ github.run_id }}` | |
| - Commit: `${{ github.sha }}` | |
| signoff: true | |
| delete-branch: true |