正在学习
Example usage
Map source paths to tests. In real projects you might parse import graphs.
mapping = { "app/app.py": ["tests/test_app.py"] }
changed_json = sys.stdin.read() changed = json.loads(changed_json)["changed"] selected = set()
for path in changed: if path in mapping: for t in mapping[path]: if Path(t).exists(): selected.add(t)
Fallback to full suite if we didn't match anything
if not selected: selected = {"tests"}
print(" ".join(sorted(selected)))
scripts/build_image.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="opt-pipeline-demo"
IMAGE="{IMAGE:-ghcr.io/{GITHUB_REPOSITORY}/${APP_NAME}}"
TAG="{TAG:-{GITHUB_SHA::7}}"
echo "[build] Building TAG with cache"
docker build \
--file - \
--tag "TAG" \
--tag "$IMAGE:latest" \
--cache-from "$IMAGE:latest" \
. <<'DOCKER'
FROM python:3.11-slim
WORKDIR /app
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
DOCKER
.github/workflows/ci.yml
name: Optimized CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch: {}
concurrency:
group: opt-ci-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.11"
CLAUDE_MODEL: claude-3.5-sonnet
CLAUDE_BUDGET_USD: "0.05"
EXPECTED_OUTPUT_TOKENS: "1800"
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.diff.outputs.changed }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- id: diff
run: |
python3 scripts/changed_paths.py > changed.json
echo "changed=GITHUB_OUTPUT
test:
needs: prepare
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-python@v5
with: { python-version: ${{ env.PYTHON_VERSION }} }
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-{{ env.PYTHON_VERSION }}-${{ hashFiles('app/requirements.txt') }}
restore-keys: pip-{{ env.PYTHON_VERSION }}-
- name: Install deps + pytest
run: |
python -m pip install --upgrade pip
pip install -r app/requirements.txt pytest
- name: Selective test list
id: select
run: |
echo '{{ needs.prepare.outputs.changed }}' '{changed:$changed}' \
| python scripts/selective_tests.py > tests_to_run.txt
echo "tests=GITHUB_OUTPUT
- name: Run tests
run: |
echo "Running: ${{ steps.select.outputs.tests }}"
pytest -q ${{ steps.select.outputs.tests }}
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
uses: actions/checkout@v4
name: Login GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
name: Pull latest for cache run: | docker pull ghcr.io/${{ github.repository }}/opt-pipeline-demo:latest || true
name: Build with cache + push env: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_SHA: ${{ github.sha }} run: | bash scripts/build_image.sh docker push ghcr.io/${{ github.repository }}/opt-pipeline-demo:latest docker push ghcr.io/{GITHUB_SHA::7}
docs_with_claude: needs: [test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Prepare prompt
run: |
cat > .prompt.txt <<'PROMPT'
Summarize the FastAPI service and generate a concise README section with:
- Purpose and endpoints
- Local run instructions
- Health check example
PROMPT
- name: Estimate Claude cost and gate
id: estimate
env:
CLAUDE_MODEL: ${{ env.CLAUDE_MODEL }}
CLAUDE_BUDGET_USD: ${{ env.CLAUDE_BUDGET_USD }}
EXPECTED_OUTPUT_TOKENS: ${{ env.EXPECTED_OUTPUT_TOKENS }}
run: |
python3 scripts/estimate_tokens.py .prompt.txt > estimate.json || exit_code=$?
cat estimate.json
echo "decision=GITHUB_OUTPUT
echo "model=GITHUB_OUTPUT
echo "est_cost=GITHUB_OUTPUT
exit ${exit_code:-0}
- name: Generate docs with Claude (mock)
if: steps.estimate.outputs.decision == 'allow'
run: |
echo "## README (Auto) " > README.md
echo "" >> README.md
echo "- Model: ${{ steps.estimate.outputs.model }}" >> README.md
echo "- Est. Cost: $${{ steps.estimate.outputs.est_cost }}" >> README.md
echo "" >> README.md
echo "### Service" >> README.md
echo "FastAPI app exposes /health and runs with Uvicorn." >> README.md
- name: Fallback to cheaper model result (mock)
if: steps.estimate.outputs.decision == 'switch'
run: |
echo "## README (Auto, Budget Fallback to Haiku)" > README.md
echo "FastAPI app exposes /health and runs with Uvicorn." >> README.md
- uses: actions/upload-artifact@v4
with:
name: generated-readme
path: README.md
This workflow demonstrates the combined optimizations:
- Deterministic dependency cache keyed by requirements.txt.
- Docker layer reuse by pulling :latest before build and tagging deterministically.
- Change-based test selection that falls back to the full suite when necessary.
- Token-cost estimation that gates or downgrades Claude usage automatically.
- Concurrency cancellation so only the latest commit’s pipeline runs to completion.
Clarification Table: Optimization, Mechanism, and Effect
| Optimization | Mechanism | Where Implemented | Expected Impact |
|---|---|---|---|
| Deterministic dependency caching | Cache by hash of requirements | actions/cache in test job | 2–10× faster installs |
| Docker layer caching | Pull latest image and reuse layers | build job pull + cache-from | 30–80% less build time |
| Change-based execution | Map changed files to tests | selective_tests.py and changed_paths.py | Skips unrelated tests, faster feedback |
| Concurrency cancel | Cancel previous runs on same ref | concurrency block | Saves runner minutes and cost |
| AI spend gating | Estimate tokens and compare to budget | estimate_tokens.py in docs_with_claude | Predictable Claude costs |
| Fallback strategy | Switch to cheaper model or stub | docs_with_claude decision branch | Keeps pipeline green under budget |
| Artifact reuse | Upload generated docs | upload-artifact | Clear outputs without reruns |
By layering cache reuse, path-aware execution, Docker layer caching, parallelization, and Claude token gating, you turned a costly, slow pipeline into a predictable, fast, and budget-respecting system. The approach scales: add more mappings to refine selective testing, promote image caching to a remote registry cache, or expand the cost gate to choose among multiple Claude models and maximum output sizes. With these patterns, your CI remains both developer-friendly and finance-friendly while preserving the same quality bars for code and documentation.
Chapter 13 – Troubleshooting and Fine-Tuning Claude Code
练习题
In the CI workflow, what is the primary purpose of the scripts/estimate_tokens.py script when integrated with the docs_with_claude job?
Which of the following are best practices for AI observability as mentioned in the prior knowledge points and are applicable to the CI workflow described?
The build job in the CI workflow depends on the successful completion of the test job to ensure that only tested and validated code is built and pushed to the registry.
In the CI workflow, the ___ job is responsible for preparing a list of changed files to determine which tests need to be run selectively.
登录后解锁笔记、知识点解析、AI 问答
立即登录