Skip to content

Commit 1f9a3ae

Browse files
release(ragbits-core): update to v1.2.0 (#762)
Co-authored-by: ds-ragbits-robot <[email protected]> Co-authored-by: Mateusz Hordyński <[email protected]>
1 parent 13b191b commit 1f9a3ae

File tree

26 files changed

+501
-45
lines changed

26 files changed

+501
-45
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# GitHub Actions Scripts
2+
3+
## Example Caching System
4+
5+
This directory contains scripts that implement a smart caching mechanism for running examples in CI:
6+
7+
### How it works
8+
9+
1. **`list_examples.sh`** - Discovers all runnable Python examples and generates a matrix that includes skip information
10+
2. **`example_cache.sh`** - Consolidated script with subcommands:
11+
- `check <example_file>` - Checks if an example should be skipped based on previous successful run and file modifications
12+
- `record <example_file>` - Records successful example runs with the current commit hash
13+
3. **`run_single_example.sh`** - Runs individual examples and records success when they complete
14+
15+
### Cache behavior
16+
17+
- Examples that passed on the current branch and haven't been modified since will be skipped
18+
- Cache is stored per branch using GitHub Actions cache
19+
- Cache keys include the branch name and commit hash
20+
- Fallback to main branch cache if current branch cache doesn't exist
21+
22+
### Benefits
23+
24+
- Dramatically reduces CI time when most examples are already passing
25+
- Only runs examples that are new, modified, or previously failed
26+
- Maintains full test coverage while optimizing for common scenarios
27+
28+
### Cache storage
29+
30+
Cache files are stored in `.example-cache/` with the format:
31+
- `{branch-name}-{example-path-normalized}.success` - Contains the commit hash of the last successful run

.github/scripts/example_cache.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if [ $# -lt 2 ]; then
6+
echo "Usage: $0 <command> <example_file>"
7+
echo "Commands:"
8+
echo " record <example_file> - Record successful example run"
9+
echo " check <example_file> - Check if example should be skipped"
10+
exit 1
11+
fi
12+
13+
COMMAND="$1"
14+
EXAMPLE_FILE="$2"
15+
CACHE_DIR=".example-cache"
16+
BRANCH_REF="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-main}}"
17+
18+
# Normalize the branch name for cache key (replace / with -)
19+
CACHE_KEY=$(echo "$BRANCH_REF" | sed 's/\//-/g')
20+
SUCCESS_FILE="$CACHE_DIR/${CACHE_KEY}-$(echo "$EXAMPLE_FILE" | sed 's/\//-/g').success"
21+
22+
mkdir -p "$CACHE_DIR"
23+
24+
case "$COMMAND" in
25+
"record")
26+
# Record the current commit hash as successful
27+
echo "$GITHUB_SHA" > "$SUCCESS_FILE"
28+
echo "Recorded success for $EXAMPLE_FILE at commit $GITHUB_SHA"
29+
;;
30+
31+
"check")
32+
# Check if we have a previous success record
33+
if [ ! -f "$SUCCESS_FILE" ]; then
34+
echo "false" # No previous success, must run
35+
exit 0
36+
fi
37+
38+
# Get the commit hash from the success file
39+
LAST_SUCCESS_COMMIT=$(cat "$SUCCESS_FILE")
40+
41+
# Check if the example file has been modified since the last success
42+
if git diff --quiet "$LAST_SUCCESS_COMMIT" HEAD -- "$EXAMPLE_FILE"; then
43+
echo "true" # File unchanged since last success, can skip
44+
else
45+
echo "false" # File modified, must run
46+
fi
47+
;;
48+
49+
*)
50+
echo "Unknown command: $COMMAND"
51+
echo "Available commands: record, check"
52+
exit 1
53+
;;
54+
esac

.github/scripts/list_examples.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Find all Python files with script sections and output as JSON array
6+
examples=()
7+
8+
while IFS= read -r -d '' file; do
9+
if grep -q "^# /// script" "$file"; then
10+
examples+=("$file")
11+
fi
12+
done < <(find examples -name '*.py' -type f -print0)
13+
14+
# Convert to JSON format for GitHub Actions matrix with skip information
15+
printf '{"include":['
16+
for i in "${!examples[@]}"; do
17+
if [ $i -gt 0 ]; then
18+
printf ','
19+
fi
20+
21+
# Check if this example should be skipped
22+
should_skip="false"
23+
if [ -f ".github/scripts/example_cache.sh" ]; then
24+
should_skip=$(bash .github/scripts/example_cache.sh check "${examples[$i]}" || echo "false")
25+
fi
26+
27+
printf '{"example":"%s","skip":%s}' "${examples[$i]}" "$should_skip"
28+
done
29+
printf ']}'
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if [ $# -ne 1 ]; then
6+
echo "Usage: $0 <example_file>"
7+
exit 1
8+
fi
9+
10+
EXAMPLE_FILE="$1"
11+
12+
echo "Export env variables required for the examples..."
13+
export GOOGLE_CLOUD_PROJECT
14+
export GOOGLE_APPLICATION_CREDENTIALS
15+
export OPENAI_API_KEY
16+
export GEMINI_API_KEY
17+
export ANTHROPIC_API_KEY
18+
export LOGFIRE_TOKEN
19+
20+
GIT_URL_TEMPLATE="git+https://github.com/deepsense-ai/ragbits.git@%s#subdirectory=packages/%s"
21+
# Default to the current Git branch when PR_BRANCH is not provided
22+
PR_BRANCH="${PR_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
23+
24+
has_script_section() {
25+
local file="$1"
26+
grep -q "^# /// script" "$file"
27+
}
28+
29+
patch_dependencies() {
30+
local file="$1"
31+
local branch="$2"
32+
local tmp_file
33+
tmp_file=$(mktemp)
34+
local in_script_block=false
35+
36+
while IFS= read -r line || [[ -n "$line" ]]; do
37+
if [[ "$line" == "# /// script"* ]]; then
38+
in_script_block=true
39+
echo "$line" >> "$tmp_file"
40+
continue
41+
fi
42+
43+
if [[ "$line" == "# ///"* && "$in_script_block" == true ]]; then
44+
in_script_block=false
45+
echo "$line" >> "$tmp_file"
46+
continue
47+
fi
48+
49+
if [[ "$in_script_block" == true && "$line" == *ragbits* ]]; then
50+
if [[ "$line" =~ \"([^\"]+)\" ]]; then
51+
full_pkg="${BASH_REMATCH[1]}"
52+
pkg_base="${full_pkg%%[*}"
53+
git_url=$(printf "$GIT_URL_TEMPLATE" "$branch" "$pkg_base")
54+
echo "# \"${full_pkg} @ ${git_url}\"," >> "$tmp_file"
55+
else
56+
echo "$line" >> "$tmp_file"
57+
fi
58+
else
59+
echo "$line" >> "$tmp_file"
60+
fi
61+
62+
done < "$file"
63+
mv "$tmp_file" "$file"
64+
}
65+
66+
if ! has_script_section "$EXAMPLE_FILE"; then
67+
echo "Skipping $EXAMPLE_FILE (no script section found)"
68+
exit 0
69+
fi
70+
71+
echo "Running the script: $EXAMPLE_FILE"
72+
patch_dependencies "$EXAMPLE_FILE" "$PR_BRANCH"
73+
74+
set +e
75+
timeout 120s uv run "$EXAMPLE_FILE"
76+
exit_code=$?
77+
set -e
78+
79+
if [[ $exit_code -eq 124 ]]; then
80+
echo "Script timed out after 120 seconds"
81+
exit 1
82+
elif [[ $exit_code -ne 0 ]]; then
83+
echo "Script failed"
84+
exit $exit_code
85+
else
86+
echo "Script completed successfully."
87+
# Record success in cache
88+
if [ -f ".github/scripts/example_cache.sh" ]; then
89+
bash .github/scripts/example_cache.sh record "$EXAMPLE_FILE"
90+
fi
91+
fi

.github/workflows/pull-request-checks.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ jobs:
2222
runs-on: ubuntu-latest
2323
steps:
2424
- uses: amannn/action-semantic-pull-request@v5
25+
with:
26+
types: |
27+
feat
28+
fix
29+
docs
30+
style
31+
refactor
32+
perf
33+
test
34+
build
35+
ci
36+
chore
37+
revert
38+
release
2539
env:
2640
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2741

.github/workflows/run-examples.yml

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,59 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
jobs:
14+
# First job: Generate matrix of examples
15+
generate-matrix:
16+
name: Generate examples matrix
17+
runs-on: ubuntu-latest
18+
outputs:
19+
matrix: ${{ steps.generate.outputs.matrix }}
20+
examples-count: ${{ steps.generate.outputs.count }}
21+
total-count: ${{ steps.generate.outputs.total-count }}
22+
steps:
23+
- uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0 # Need full history for git diff
26+
27+
- name: Restore example cache
28+
uses: actions/cache@v4
29+
with:
30+
path: .example-cache
31+
# Use branch-specific key (no per-example key here, since we want a shared cache for all examples)
32+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
33+
restore-keys: |
34+
examples-cache-${{ github.head_ref || github.ref_name }}-
35+
examples-cache-main-
36+
37+
- name: Generate matrix
38+
id: generate
39+
run: |
40+
matrix=$(bash .github/scripts/list_examples.sh)
41+
echo "matrix=$matrix" >> $GITHUB_OUTPUT
42+
total_count=$(echo "$matrix" | jq '.include | length')
43+
skipped_count=$(echo "$matrix" | jq '[.include[] | select(.skip == true)] | length')
44+
run_count=$((total_count - skipped_count))
45+
echo "total-count=$total_count" >> $GITHUB_OUTPUT
46+
echo "count=$run_count" >> $GITHUB_OUTPUT
47+
echo "Found $total_count total examples ($run_count to run, $skipped_count cached)"
48+
echo "$matrix" | jq .
49+
50+
# Second job: Run examples in parallel
1451
examples:
15-
name: Check examples job
52+
name: Run example
1653
runs-on: ubuntu-latest
54+
needs: generate-matrix
1755
timeout-minutes: 15
1856
permissions:
1957
checks: write
2058
pull-requests: write
2159
contents: read
2260

61+
# Use matrix strategy to run examples in parallel
62+
strategy:
63+
fail-fast: false # Don't stop other examples if one fails
64+
max-parallel: 10 # Limit concurrency to avoid overwhelming resources
65+
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
66+
2367
services:
2468
postgres:
2569
image: pgvector/pgvector:${{ vars.PGVECTOR_IMAGE_TAG || '0.8.0-pg17' }}
@@ -61,13 +105,62 @@ jobs:
61105

62106
steps:
63107
- uses: actions/checkout@v4
108+
with:
109+
fetch-depth: 0 # Need full history for git diff
64110

65111
- name: Install uv
66112
uses: astral-sh/setup-uv@v2
67113
with:
68114
version: ${{ vars.UV_VERSION || '0.6.9' }}
115+
enable-cache: true
116+
117+
- name: Cache uv dependencies
118+
uses: actions/cache@v4
119+
with:
120+
path: ~/.cache/uv
121+
key: uv-${{ runner.os }}-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }}
122+
restore-keys: |
123+
uv-${{ runner.os }}-
124+
125+
- name: Restore example cache
126+
uses: actions/cache@v4
127+
with:
128+
path: .example-cache
129+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ matrix.example }}-${{ github.sha }}
130+
restore-keys: |
131+
examples-cache-${{ github.head_ref || github.ref_name }}-${{ matrix.example }}-
132+
examples-cache-${{ github.head_ref || github.ref_name }}-
133+
examples-cache-main-
69134
70-
- name: Run examples
135+
- name: Check if example should be skipped
136+
id: check-skip
137+
run: |
138+
if [[ "${{ matrix.skip }}" == "true" ]]; then
139+
echo "skip=true" >> $GITHUB_OUTPUT
140+
echo "✅ Skipping ${{ matrix.example }} (cached from previous successful run)"
141+
else
142+
echo "skip=false" >> $GITHUB_OUTPUT
143+
fi
144+
145+
- name: Pre-install common dependencies
146+
if: steps.check-skip.outputs.skip == 'false'
147+
run: |
148+
# Create a temporary environment with common ragbits packages to cache them
149+
uv venv --python 3.10 .temp-env
150+
source .temp-env/bin/activate
151+
# Install the most commonly used packages across examples
152+
uv pip install \
153+
ragbits-core \
154+
ragbits-document-search \
155+
ragbits-agents \
156+
ragbits-chat \
157+
ragbits-evaluate \
158+
ragbits-guardrails
159+
deactivate
160+
rm -rf .temp-env
161+
162+
- name: Run example - ${{ matrix.example }}
163+
if: steps.check-skip.outputs.skip == 'false'
71164
env:
72165
PR_BRANCH: ${{ github.head_ref }}
73166
GOOGLE_CLOUD_PROJECT: ${{ secrets.GCP_PROJECT_ID }}
@@ -79,4 +172,43 @@ jobs:
79172
LOGFIRE_TOKEN: ${{ secrets.LOGFIRE_TOKEN }}
80173
run: |
81174
echo "$GCP_KEY" | base64 --decode > "$GOOGLE_APPLICATION_CREDENTIALS"
82-
./.github/scripts/run_examples.sh
175+
chmod +x .github/scripts/run_single_example.sh
176+
./.github/scripts/run_single_example.sh "${{ matrix.example }}"
177+
178+
- name: Save example cache
179+
if: always()
180+
uses: actions/cache/save@v4
181+
with:
182+
path: .example-cache
183+
key: examples-cache-${{ github.head_ref || github.ref_name }}-${{ matrix.example }}-${{ github.sha }}
184+
185+
# Summary job: Report overall results
186+
examples-summary:
187+
name: Examples summary
188+
runs-on: ubuntu-latest
189+
needs: [generate-matrix, examples]
190+
if: always()
191+
steps:
192+
- name: Check results
193+
env:
194+
TOTAL_COUNT: ${{ needs.generate-matrix.outputs.total-count }}
195+
RUN_COUNT: ${{ needs.generate-matrix.outputs.examples-count }}
196+
run: |
197+
echo "Examples matrix generation: ${{ needs.generate-matrix.result }}"
198+
echo "Examples execution: ${{ needs.examples.result }}"
199+
echo "Total examples: $TOTAL_COUNT"
200+
echo "Examples run: $RUN_COUNT"
201+
cached_count=$((TOTAL_COUNT - RUN_COUNT))
202+
echo "Examples cached: $cached_count"
203+
204+
if [[ "${{ needs.generate-matrix.result }}" != "success" ]]; then
205+
echo "❌ Failed to generate examples matrix"
206+
exit 1
207+
fi
208+
209+
if [[ "${{ needs.examples.result }}" != "success" ]]; then
210+
echo "❌ Some examples failed"
211+
exit 1
212+
fi
213+
214+
echo "✅ All examples completed successfully ($RUN_COUNT run, $cached_count cached)"

0 commit comments

Comments
 (0)