banks 2.2.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- banks-2.4.0/.github/workflows/release.yml +79 -0
- {banks-2.2.0 → banks-2.4.0}/.github/workflows/test.yml +3 -3
- banks-2.4.0/AGENTS.md +218 -0
- banks-2.4.0/CLAUDE.md +3 -0
- {banks-2.2.0 → banks-2.4.0}/PKG-INFO +5 -3
- {banks-2.2.0 → banks-2.4.0}/README.md +2 -2
- {banks-2.2.0 → banks-2.4.0}/pyproject.toml +6 -2
- {banks-2.2.0 → banks-2.4.0}/src/banks/__about__.py +1 -1
- {banks-2.2.0 → banks-2.4.0}/src/banks/config.py +4 -1
- {banks-2.2.0 → banks-2.4.0}/src/banks/env.py +3 -1
- {banks-2.2.0 → banks-2.4.0}/src/banks/errors.py +1 -1
- {banks-2.2.0 → banks-2.4.0}/src/banks/extensions/completion.py +16 -2
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/__init__.py +3 -1
- banks-2.4.0/src/banks/filters/audio.py +76 -0
- banks-2.4.0/src/banks/filters/document.py +135 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/image.py +4 -2
- banks-2.4.0/src/banks/filters/video.py +108 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/prompt.py +11 -2
- banks-2.4.0/src/banks/types.py +353 -0
- banks-2.4.0/tests/data/1x1.pdf +0 -0
- banks-2.4.0/tests/data/empty.mov +0 -0
- banks-2.4.0/tests/test_audio.py +121 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_cache_control.py +3 -2
- {banks-2.2.0 → banks-2.4.0}/tests/test_directory_registry.py +1 -1
- banks-2.4.0/tests/test_document.py +167 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_image.py +35 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_redis_registry.py +1 -1
- banks-2.4.0/tests/test_video.py +126 -0
- banks-2.2.0/.github/workflows/release.yml +0 -33
- banks-2.2.0/CLAUDE.md +0 -126
- banks-2.2.0/src/banks/filters/audio.py +0 -23
- banks-2.2.0/src/banks/types.py +0 -184
- banks-2.2.0/tests/test_audio.py +0 -45
- {banks-2.2.0 → banks-2.4.0}/.github/workflows/docs.yml +0 -0
- {banks-2.2.0 → banks-2.4.0}/.gitignore +0 -0
- {banks-2.2.0 → banks-2.4.0}/CITATION.cff +0 -0
- {banks-2.2.0 → banks-2.4.0}/CODE_OF_CONDUCT.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/CONTRIBUTING.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/LICENSE.txt +0 -0
- {banks-2.2.0 → banks-2.4.0}/MANIFEST.in +0 -0
- {banks-2.2.0 → banks-2.4.0}/assets/banks.png +0 -0
- {banks-2.2.0 → banks-2.4.0}/cookbook/Prompt_Caching_with_Anthropic.ipynb +0 -0
- {banks-2.2.0 → banks-2.4.0}/cookbook/Prompt_Versioning.ipynb +0 -0
- {banks-2.2.0 → banks-2.4.0}/cookbook/in_prompt_completion.ipynb +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/config.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/examples.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/index.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/prompt.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/python.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/docs/registry.md +0 -0
- {banks-2.2.0 → banks-2.4.0}/mkdocs.yml +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/__init__.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/cache.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/extensions/__init__.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/extensions/chat.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/extensions/docs.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/cache_control.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/lemmatize.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/tool.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/filters/xml.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/registries/__init__.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/registries/directory.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/registries/file.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/registries/redis.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/src/banks/utils.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/__init__.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/conftest.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/data/1x1.png +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/data/empty.wav +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/e2e/__init__.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/e2e/conftest.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/e2e/test_completion.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/e2e/test_function_calling.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/templates/blog.jinja +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/templates/cache.jinja +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/templates/chat.jinja +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/templates/summarize.jinja +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/templates/summarize_lemma.jinja +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_cache.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_chat.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_completion.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_config.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_file_registry.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_prompt.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_tool.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_types.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_utils.py +0 -0
- {banks-2.2.0 → banks-2.4.0}/tests/test_xml.py +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: PyPI Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
bump:
|
|
7
|
+
description: "Version bump type"
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- MINOR
|
|
12
|
+
- BUGFIX
|
|
13
|
+
default: BUGFIX
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
release:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
ref: ${{ github.event.repository.default_branch }}
|
|
26
|
+
|
|
27
|
+
- name: Bump version
|
|
28
|
+
run: |
|
|
29
|
+
CURRENT=$(sed -n 's/^__version__ = "\([0-9.]*\).*/\1/p' src/banks/__about__.py)
|
|
30
|
+
IFS=. read -r major minor patch <<< "$CURRENT"
|
|
31
|
+
case "${{ github.event.inputs.bump }}" in
|
|
32
|
+
BUGFIX)
|
|
33
|
+
patch=$((patch + 1))
|
|
34
|
+
;;
|
|
35
|
+
MINOR)
|
|
36
|
+
minor=$((minor + 1))
|
|
37
|
+
patch=0
|
|
38
|
+
;;
|
|
39
|
+
*)
|
|
40
|
+
echo "Unexpected bump type: ${{ github.event.inputs.bump }}"
|
|
41
|
+
exit 1
|
|
42
|
+
;;
|
|
43
|
+
esac
|
|
44
|
+
VERSION="${major}.${minor}.${patch}"
|
|
45
|
+
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
|
46
|
+
echo "Bumped ${CURRENT} -> ${VERSION} (${{ github.event.inputs.bump }})"
|
|
47
|
+
|
|
48
|
+
- name: Update __about__.py on default branch
|
|
49
|
+
run: |
|
|
50
|
+
git config user.name "github-actions[bot]"
|
|
51
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
52
|
+
sed -i "s/^__version__ = .*$/__version__ = \"${VERSION}\"/" src/banks/__about__.py
|
|
53
|
+
git diff --quiet && exit 0
|
|
54
|
+
git add src/banks/__about__.py
|
|
55
|
+
git commit -m "chore: set __version__ to ${VERSION} [skip ci]"
|
|
56
|
+
git push origin "${{ github.event.repository.default_branch }}"
|
|
57
|
+
|
|
58
|
+
- name: Create and push tag
|
|
59
|
+
run: |
|
|
60
|
+
git tag "v${VERSION}"
|
|
61
|
+
git push origin "v${VERSION}"
|
|
62
|
+
|
|
63
|
+
- name: Install Hatch
|
|
64
|
+
run: pip install hatch
|
|
65
|
+
|
|
66
|
+
- name: Publish on PyPi
|
|
67
|
+
env:
|
|
68
|
+
HATCH_INDEX_USER: __token__
|
|
69
|
+
HATCH_INDEX_AUTH: ${{ secrets.PYPI_API_TOKEN }}
|
|
70
|
+
run: |
|
|
71
|
+
hatch build
|
|
72
|
+
hatch publish -y
|
|
73
|
+
|
|
74
|
+
- name: Create GitHub Release
|
|
75
|
+
uses: ncipollo/release-action@v1
|
|
76
|
+
with:
|
|
77
|
+
tag: v${{ env.VERSION }}
|
|
78
|
+
artifacts: "dist/*"
|
|
79
|
+
generateReleaseNotes: true
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
strategy:
|
|
35
35
|
fail-fast: false
|
|
36
36
|
matrix:
|
|
37
|
-
python-version: ["3.
|
|
37
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
38
38
|
|
|
39
39
|
steps:
|
|
40
40
|
- uses: actions/checkout@v4
|
|
@@ -72,7 +72,7 @@ jobs:
|
|
|
72
72
|
strategy:
|
|
73
73
|
fail-fast: false
|
|
74
74
|
matrix:
|
|
75
|
-
python-version: ["3.
|
|
75
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
76
76
|
|
|
77
77
|
steps:
|
|
78
78
|
- uses: actions/checkout@v4
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
strategy:
|
|
96
96
|
fail-fast: false
|
|
97
97
|
matrix:
|
|
98
|
-
python-version: ["3.
|
|
98
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
99
99
|
|
|
100
100
|
steps:
|
|
101
101
|
- uses: actions/checkout@v4
|
banks-2.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to AI coding assistants when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Banks is a Python prompt programming language and templating system for LLM applications. It provides a Jinja2-based template engine with specialized extensions and filters for creating dynamic prompts, managing chat messages, handling multimodal content (images/audio/video/documents), and integrating with various LLM providers through LiteLLM.
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Most common commands
|
|
13
|
+
hatch run test # Run unit tests
|
|
14
|
+
hatch run lint:all # Run all linting checks
|
|
15
|
+
hatch run lint:fmt # Auto-format code
|
|
16
|
+
hatch run test tests/test_foo.py # Run specific test file
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Development Commands
|
|
20
|
+
|
|
21
|
+
### Testing
|
|
22
|
+
- Run tests: `hatch run test`
|
|
23
|
+
- Run tests with coverage: `hatch run test-cov`
|
|
24
|
+
- Generate coverage report: `hatch run cov`
|
|
25
|
+
- Run specific test file: `hatch run test tests/test_foo.py`
|
|
26
|
+
- Run e2e tests: `hatch run test tests/e2e/` (requires API keys)
|
|
27
|
+
|
|
28
|
+
### Linting and Type Checking
|
|
29
|
+
- Format code: `hatch run lint:fmt`
|
|
30
|
+
- Auto-fix lint issues: `hatch run lint:fix`
|
|
31
|
+
- Check formatting: `hatch run lint:check`
|
|
32
|
+
- Run type checking: `hatch run lint:typing`
|
|
33
|
+
- Run pylint: `hatch run lint:lint`
|
|
34
|
+
- Run all lint checks: `hatch run lint:all`
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
- Build docs: `hatch run docs build`
|
|
38
|
+
- Serve docs locally: `hatch run docs serve` (available at http://127.0.0.1:8000/)
|
|
39
|
+
|
|
40
|
+
### Environment Management
|
|
41
|
+
- All commands use Hatch environments with automatic dependency management
|
|
42
|
+
- Uses `uv` as the installer for faster dependency resolution
|
|
43
|
+
- Python 3.9+ supported (tested on 3.10-3.14)
|
|
44
|
+
|
|
45
|
+
## Architecture Overview
|
|
46
|
+
|
|
47
|
+
### Core Components
|
|
48
|
+
|
|
49
|
+
**Prompt Classes** (`src/banks/prompt.py`):
|
|
50
|
+
- `BasePrompt`: Base class with template rendering, metadata, versioning, and caching
|
|
51
|
+
- `Prompt`: Synchronous prompt rendering with `text()` and `chat_messages()` methods
|
|
52
|
+
- `AsyncPrompt`: Asynchronous version (requires `BANKS_ASYNC_ENABLED=true`)
|
|
53
|
+
- `PromptRegistry`: Protocol interface for prompt storage backends
|
|
54
|
+
|
|
55
|
+
**Type System** (`src/banks/types.py`):
|
|
56
|
+
- `ChatMessage`: Core chat message structure with role and content
|
|
57
|
+
- `ContentBlock`: Handles different content types (text, image_url, audio, video, document) with optional cache control
|
|
58
|
+
- `Tool`: Function calling support with automatic schema generation from Python callables
|
|
59
|
+
- `CacheControl`: Anthropic-style prompt caching metadata
|
|
60
|
+
|
|
61
|
+
**Template Environment** (`src/banks/env.py`):
|
|
62
|
+
- Global Jinja2 environment with Banks-specific extensions and filters
|
|
63
|
+
- Async support detection and configuration
|
|
64
|
+
- Custom template loader integration
|
|
65
|
+
|
|
66
|
+
**Error Types** (`src/banks/errors.py`):
|
|
67
|
+
- `MissingDependencyError`: Optional dependencies not installed
|
|
68
|
+
- `AsyncError`: Asyncio support misconfiguration
|
|
69
|
+
- `CanaryWordError`: Canary word leaked (prompt injection detection)
|
|
70
|
+
- `PromptNotFoundError`: Prompt not found in registry
|
|
71
|
+
- `InvalidPromptError`: Invalid prompt format
|
|
72
|
+
- `LLMError`: LLM provider errors
|
|
73
|
+
|
|
74
|
+
### Extensions System
|
|
75
|
+
|
|
76
|
+
**Chat Extension** (`src/banks/extensions/chat.py`):
|
|
77
|
+
- `{% chat role="..." %}...{% endchat %}` blocks for structured message creation
|
|
78
|
+
- Automatic conversion to `ChatMessage` objects during rendering
|
|
79
|
+
|
|
80
|
+
**Completion Extension** (`src/banks/extensions/completion.py`):
|
|
81
|
+
- `{% completion model="..." %}...{% endcompletion %}` for in-prompt LLM calls
|
|
82
|
+
- Integrated with LiteLLM for multi-provider support
|
|
83
|
+
- Function calling support within completion blocks
|
|
84
|
+
|
|
85
|
+
### Filters System
|
|
86
|
+
|
|
87
|
+
**Core Filters** (`src/banks/filters/`):
|
|
88
|
+
- `image`: Convert file paths/URLs/bytes to base64-encoded image content blocks
|
|
89
|
+
- `audio`: Convert audio files to base64-encoded audio content blocks
|
|
90
|
+
- `video`: Convert video files to base64-encoded video content blocks
|
|
91
|
+
- `document`: Convert documents (PDF, TXT, HTML, CSS, XML, CSV, RTF, JS, JSON) to base64-encoded content blocks
|
|
92
|
+
- `cache_control`: Add Anthropic cache control metadata to content blocks
|
|
93
|
+
- `tool`: Convert Python callables to LLM function call schemas
|
|
94
|
+
- `lemmatize`: Text lemmatization using simplemma
|
|
95
|
+
|
|
96
|
+
**Filter Pattern**: Filters wrap content in `<content_block>` tags and are only useful within `{% chat %}` blocks.
|
|
97
|
+
|
|
98
|
+
### Registry System
|
|
99
|
+
|
|
100
|
+
**Storage Backends** (`src/banks/registries/`):
|
|
101
|
+
- `DirectoryTemplateRegistry`: File system-based prompt storage
|
|
102
|
+
- `FileTemplateRegistry`: Single file-based storage
|
|
103
|
+
- `RedisTemplateRegistry`: Redis-backed storage for distributed scenarios
|
|
104
|
+
- All registries implement the `PromptRegistry` protocol
|
|
105
|
+
|
|
106
|
+
### Caching System
|
|
107
|
+
|
|
108
|
+
**Render Cache** (`src/banks/cache.py`):
|
|
109
|
+
- `RenderCache`: Protocol interface for caching rendered prompts
|
|
110
|
+
- `DefaultCache`: In-memory cache using pickle-serialized context as key
|
|
111
|
+
- Prevents re-rendering identical template + context combinations
|
|
112
|
+
|
|
113
|
+
### Configuration
|
|
114
|
+
|
|
115
|
+
**Config System** (`src/banks/config.py`):
|
|
116
|
+
- Environment variable-based configuration with `BANKS_` prefix
|
|
117
|
+
- `BANKS_ASYNC_ENABLED`: Enable async template rendering (must be set before import)
|
|
118
|
+
- `BANKS_USER_DATA_PATH`: Custom user data directory
|
|
119
|
+
|
|
120
|
+
## Key Development Patterns
|
|
121
|
+
|
|
122
|
+
### Template Rendering Flow
|
|
123
|
+
1. Templates parsed by Jinja2 environment with Banks extensions
|
|
124
|
+
2. Chat blocks converted to JSON during rendering
|
|
125
|
+
3. `chat_messages()` parses JSON back to `ChatMessage` objects
|
|
126
|
+
4. Caching layer prevents re-rendering identical contexts
|
|
127
|
+
|
|
128
|
+
### Multimodal Content Handling
|
|
129
|
+
- Images/audio/video/documents converted to base64 during filter application
|
|
130
|
+
- Filters accept file paths, URLs, or raw bytes
|
|
131
|
+
- Content blocks maintain type safety and metadata
|
|
132
|
+
- Cache control integrated at content block level
|
|
133
|
+
|
|
134
|
+
### Function Calling Integration
|
|
135
|
+
- Python functions automatically converted to LLM schemas via introspection
|
|
136
|
+
- Docstring parsing for parameter descriptions
|
|
137
|
+
- Type annotations converted to JSON Schema
|
|
138
|
+
|
|
139
|
+
### Async Support Architecture
|
|
140
|
+
- Global environment state requires async decision at import time
|
|
141
|
+
- `BANKS_ASYNC_ENABLED` must be set before importing banks modules
|
|
142
|
+
- `AsyncPrompt` provides `await`-able rendering methods
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
### Test Markers
|
|
147
|
+
- `@pytest.mark.e2e`: End-to-end tests requiring external services
|
|
148
|
+
- `@pytest.mark.redis`: Tests requiring a running Redis instance
|
|
149
|
+
|
|
150
|
+
### Required Environment Variables for E2E Tests
|
|
151
|
+
- `OPENAI_API_KEY`: For OpenAI-based tests
|
|
152
|
+
- `ANTHROPIC_API_KEY`: For Anthropic-based tests
|
|
153
|
+
|
|
154
|
+
### Test Data
|
|
155
|
+
- Test fixtures in `tests/data/` (images, audio, video, PDFs)
|
|
156
|
+
- Template examples in `tests/templates/`
|
|
157
|
+
|
|
158
|
+
### Running Specific Tests
|
|
159
|
+
```bash
|
|
160
|
+
hatch run test tests/test_image.py # Single file
|
|
161
|
+
hatch run test tests/test_image.py::test_name # Single test
|
|
162
|
+
hatch run test -k "image" # Tests matching pattern
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Code Style
|
|
166
|
+
|
|
167
|
+
### Formatting
|
|
168
|
+
- Line length: 120 characters
|
|
169
|
+
- Use ruff for formatting and linting
|
|
170
|
+
- Imports sorted with `banks` as first-party
|
|
171
|
+
|
|
172
|
+
### Type Hints
|
|
173
|
+
- All public functions should have type annotations
|
|
174
|
+
- Use `from __future__ import annotations` for forward references
|
|
175
|
+
- MyPy strict mode enforced
|
|
176
|
+
|
|
177
|
+
### Conventions
|
|
178
|
+
- SPDX license headers in all source files
|
|
179
|
+
- Docstrings for public APIs
|
|
180
|
+
- Relative imports banned (use absolute `from banks.x import y`)
|
|
181
|
+
|
|
182
|
+
## Public API
|
|
183
|
+
|
|
184
|
+
The main exports from `banks` package:
|
|
185
|
+
```python
|
|
186
|
+
from banks import Prompt, AsyncPrompt, ChatMessage, config, env
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Dependencies
|
|
190
|
+
|
|
191
|
+
**Core (required)**:
|
|
192
|
+
- `jinja2`: Core templating engine
|
|
193
|
+
- `pydantic`: Type validation and serialization
|
|
194
|
+
- `griffe`: Code introspection utilities
|
|
195
|
+
- `platformdirs`: Cross-platform data directory handling
|
|
196
|
+
- `filetype`: File type detection for multimodal content
|
|
197
|
+
- `deprecated`: Deprecation decorators
|
|
198
|
+
|
|
199
|
+
**Optional**:
|
|
200
|
+
- `litellm`: Multi-provider LLM integration (`banks[all]`)
|
|
201
|
+
- `redis`: Redis registry backend (`banks[all]`)
|
|
202
|
+
- `simplemma`: Lemmatization filter (dev dependency)
|
|
203
|
+
|
|
204
|
+
## CI/CD
|
|
205
|
+
|
|
206
|
+
- **test.yml**: Runs tests on Python 3.10-3.14
|
|
207
|
+
- **docs.yml**: Builds and deploys documentation
|
|
208
|
+
- **release.yml**: Handles package releases
|
|
209
|
+
|
|
210
|
+
## PR Guidelines
|
|
211
|
+
|
|
212
|
+
Follow conventional commit prefixes for PR titles:
|
|
213
|
+
- `fix:` - Bug fixes
|
|
214
|
+
- `feat:` - New features
|
|
215
|
+
- `chore:` - Maintenance
|
|
216
|
+
- `docs:` - Documentation
|
|
217
|
+
- `refactor:` - Code refactoring
|
|
218
|
+
- `test:` - Test additions/changes
|
banks-2.4.0/CLAUDE.md
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: banks
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: A prompt programming language
|
|
5
5
|
Project-URL: Documentation, https://github.com/masci/banks#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/masci/banks/issues
|
|
@@ -15,11 +15,13 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
20
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
21
|
Requires-Python: >=3.9
|
|
21
22
|
Requires-Dist: deprecated
|
|
22
23
|
Requires-Dist: eval-type-backport; python_version < '3.10'
|
|
24
|
+
Requires-Dist: filetype>=1.2.0
|
|
23
25
|
Requires-Dist: griffe
|
|
24
26
|
Requires-Dist: jinja2
|
|
25
27
|
Requires-Dist: platformdirs
|
|
@@ -125,11 +127,11 @@ print(p.chat_messages({"persona": "helpful assistant"}))
|
|
|
125
127
|
# [
|
|
126
128
|
# ChatMessage(role='system', content=[
|
|
127
129
|
# ContentBlock(type=<ContentBlockType.text: 'text'>, cache_control=None, text='You are a helpful assistant.',
|
|
128
|
-
# image_url=None, input_audio=None)
|
|
130
|
+
# image_url=None, input_audio=None, input_video=None, input_document=None)
|
|
129
131
|
# ], tool_call_id=None, name=None),
|
|
130
132
|
# ChatMessage(role='user', content=[
|
|
131
133
|
# ContentBlock(type=<ContentBlockType.text: 'text'>, cache_control=None, text='Hello, how are you?',
|
|
132
|
-
# image_url=None, input_audio=None)
|
|
134
|
+
# image_url=None, input_audio=None, input_video=None, input_document=None)
|
|
133
135
|
# ], tool_call_id=None, name=None)
|
|
134
136
|
# ]
|
|
135
137
|
```
|
|
@@ -94,11 +94,11 @@ print(p.chat_messages({"persona": "helpful assistant"}))
|
|
|
94
94
|
# [
|
|
95
95
|
# ChatMessage(role='system', content=[
|
|
96
96
|
# ContentBlock(type=<ContentBlockType.text: 'text'>, cache_control=None, text='You are a helpful assistant.',
|
|
97
|
-
# image_url=None, input_audio=None)
|
|
97
|
+
# image_url=None, input_audio=None, input_video=None, input_document=None)
|
|
98
98
|
# ], tool_call_id=None, name=None),
|
|
99
99
|
# ChatMessage(role='user', content=[
|
|
100
100
|
# ContentBlock(type=<ContentBlockType.text: 'text'>, cache_control=None, text='Hello, how are you?',
|
|
101
|
-
# image_url=None, input_audio=None)
|
|
101
|
+
# image_url=None, input_audio=None, input_video=None, input_document=None)
|
|
102
102
|
# ], tool_call_id=None, name=None)
|
|
103
103
|
# ]
|
|
104
104
|
```
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
"Programming Language :: Python :: 3.11",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
22
23
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
24
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
25
|
]
|
|
@@ -29,6 +30,7 @@ dependencies = [
|
|
|
29
30
|
"deprecated",
|
|
30
31
|
"eval-type-backport;python_version<'3.10'",
|
|
31
32
|
"platformdirs",
|
|
33
|
+
"filetype>=1.2.0",
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
[project.optional-dependencies]
|
|
@@ -65,7 +67,7 @@ cov = ["test-cov", "cov-report"]
|
|
|
65
67
|
docs = "mkdocs {args:build}"
|
|
66
68
|
|
|
67
69
|
[[tool.hatch.envs.all.matrix]]
|
|
68
|
-
python = ["3.
|
|
70
|
+
python = ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
69
71
|
|
|
70
72
|
[tool.hatch.envs.lint]
|
|
71
73
|
detached = false # Normally the linting env can be detached, but mypy doesn't install all the stubs we need
|
|
@@ -77,6 +79,7 @@ lint = "pylint {args:src/banks}"
|
|
|
77
79
|
typing = "mypy --install-types --non-interactive {args:src/banks}"
|
|
78
80
|
all = ["check", "typing", "lint"]
|
|
79
81
|
fmt = "ruff format {args}"
|
|
82
|
+
fix = "ruff check --fix {args}"
|
|
80
83
|
|
|
81
84
|
[tool.hatch.build.targets.wheel]
|
|
82
85
|
only-include = ["src/banks", "src/templates"]
|
|
@@ -182,6 +185,7 @@ exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
|
|
|
182
185
|
module = ["litellm.*", "simplemma.*", "deprecated.*"]
|
|
183
186
|
ignore_missing_imports = true
|
|
184
187
|
|
|
188
|
+
|
|
185
189
|
[tool.pylint]
|
|
186
190
|
disable = [
|
|
187
191
|
"line-too-long",
|
|
@@ -199,7 +203,7 @@ max-args = 10
|
|
|
199
203
|
asyncio_default_fixture_loop_scope = "function"
|
|
200
204
|
markers = ["e2e"]
|
|
201
205
|
filterwarnings = [
|
|
202
|
-
#
|
|
206
|
+
# Silence litellm warning coming from their Pydantic config.
|
|
203
207
|
# This assumes our use of Pydantic is correct :)
|
|
204
208
|
"ignore:Support for class-based `config` is deprecated",
|
|
205
209
|
]
|
|
@@ -28,9 +28,12 @@ class _BanksConfig:
|
|
|
28
28
|
return original_value
|
|
29
29
|
|
|
30
30
|
# Convert string from env var to the actual type
|
|
31
|
-
|
|
31
|
+
annotations = getattr(type(self), "__annotations__", {})
|
|
32
|
+
t = annotations.get(name, type(original_value))
|
|
32
33
|
if t is bool:
|
|
33
34
|
return strtobool(read_value)
|
|
35
|
+
if t is Any:
|
|
36
|
+
return read_value
|
|
34
37
|
|
|
35
38
|
return t(read_value)
|
|
36
39
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from jinja2 import Environment, select_autoescape
|
|
5
5
|
|
|
6
6
|
from .config import config
|
|
7
|
-
from .filters import audio, cache_control, image, lemmatize, tool, xml
|
|
7
|
+
from .filters import audio, cache_control, document, image, lemmatize, tool, video, xml
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _add_extensions(_env):
|
|
@@ -38,6 +38,8 @@ env.filters["image"] = image
|
|
|
38
38
|
env.filters["lemmatize"] = lemmatize
|
|
39
39
|
env.filters["tool"] = tool
|
|
40
40
|
env.filters["audio"] = audio
|
|
41
|
+
env.filters["video"] = video
|
|
42
|
+
env.filters["document"] = document
|
|
41
43
|
env.filters["to_xml"] = xml
|
|
42
44
|
|
|
43
45
|
_add_extensions(env)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
import importlib
|
|
5
5
|
import json
|
|
6
|
-
from typing import cast
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, cast
|
|
7
7
|
|
|
8
8
|
from jinja2 import TemplateSyntaxError, nodes
|
|
9
9
|
from jinja2.ext import Extension
|
|
@@ -12,6 +12,8 @@ from pydantic import ValidationError
|
|
|
12
12
|
from banks.errors import InvalidPromptError, LLMError
|
|
13
13
|
from banks.types import ChatMessage, Tool
|
|
14
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from litellm.types.utils import ChatCompletionMessageToolCall
|
|
15
17
|
SUPPORTED_KWARGS = ("model",)
|
|
16
18
|
LITELLM_INSTALL_MSG = "litellm is not installed. Please install it with `pip install litellm`."
|
|
17
19
|
|
|
@@ -74,7 +76,19 @@ class CompletionExtension(Extension):
|
|
|
74
76
|
return nodes.CallBlock(self.call_method("_do_completion_async", args), [], [], body).set_lineno(lineno)
|
|
75
77
|
return nodes.CallBlock(self.call_method("_do_completion", args), [], [], body).set_lineno(lineno)
|
|
76
78
|
|
|
77
|
-
def _get_tool_callable(self, tools, tool_call):
|
|
79
|
+
def _get_tool_callable(self, tools: list[Tool], tool_call: "ChatCompletionMessageToolCall") -> Callable[..., Any]:
|
|
80
|
+
"""Get the callable function for a tool call.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
tools: List of available tools
|
|
84
|
+
tool_call: The tool call from the LLM response
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The callable function
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValueError: If the function is not found in available tools
|
|
91
|
+
"""
|
|
78
92
|
for tool in tools:
|
|
79
93
|
if tool.function.name == tool_call.function.name:
|
|
80
94
|
module_name, func_name = tool.import_path.rsplit(".", maxsplit=1)
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
from .audio import audio
|
|
5
5
|
from .cache_control import cache_control
|
|
6
|
+
from .document import document
|
|
6
7
|
from .image import image
|
|
7
8
|
from .lemmatize import lemmatize
|
|
8
9
|
from .tool import tool
|
|
10
|
+
from .video import video
|
|
9
11
|
from .xml import xml
|
|
10
12
|
|
|
11
|
-
__all__ = ("cache_control", "image", "lemmatize", "tool", "audio", "xml")
|
|
13
|
+
__all__ = ("cache_control", "image", "lemmatize", "tool", "audio", "video", "document", "xml")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Massimiliano Pippi <mpippi@gmail.com>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import cast
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
import filetype # type: ignore[import-untyped]
|
|
10
|
+
|
|
11
|
+
from banks.types import AudioFormat, ContentBlock, InputAudio, resolve_binary
|
|
12
|
+
|
|
13
|
+
BASE64_AUDIO_REGEX = re.compile(r"audio\/.*;base64,.*")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_url(string: str) -> bool:
|
|
17
|
+
"""Check if a string is a URL."""
|
|
18
|
+
result = urlparse(string)
|
|
19
|
+
if not result.scheme:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
if not result.netloc:
|
|
23
|
+
# The only valid format when netloc is empty is base64 data urls
|
|
24
|
+
return all([result.scheme == "data", BASE64_AUDIO_REGEX.match(result.path)])
|
|
25
|
+
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_audio_format_from_url(url: str) -> AudioFormat:
|
|
30
|
+
"""Extract audio format from URL.
|
|
31
|
+
|
|
32
|
+
Tries to determine format from URL path or defaults to mp3.
|
|
33
|
+
"""
|
|
34
|
+
parsed = urlparse(url)
|
|
35
|
+
path = parsed.path.lower()
|
|
36
|
+
for fmt in ("mp3", "wav", "m4a", "webm", "ogg", "flac"):
|
|
37
|
+
if path.endswith(f".{fmt}"):
|
|
38
|
+
return cast(AudioFormat, fmt)
|
|
39
|
+
# Default to mp3 if format cannot be determined
|
|
40
|
+
return "mp3"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_audio_format_from_bytes(data: bytes) -> AudioFormat:
|
|
44
|
+
"""Extract audio format from bytes data using filetype library."""
|
|
45
|
+
kind = filetype.guess(data)
|
|
46
|
+
if kind is not None:
|
|
47
|
+
fmt = kind.extension
|
|
48
|
+
if fmt in ("mp3", "wav", "m4a", "webm", "ogg", "flac"):
|
|
49
|
+
return cast(AudioFormat, fmt)
|
|
50
|
+
# Default to mp3 if format cannot be determined
|
|
51
|
+
return "mp3"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def audio(value: str | bytes) -> str:
|
|
55
|
+
"""Wrap the filtered value into a ContentBlock of type audio.
|
|
56
|
+
|
|
57
|
+
The resulting ChatMessage will have the field `content` populated with a list of ContentBlock objects.
|
|
58
|
+
|
|
59
|
+
Supports both file paths and URLs (including data URLs).
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
```jinja
|
|
63
|
+
{{ "path/to/audio/file.mp3" | audio }}
|
|
64
|
+
{{ "https://example.com/audio.mp3" | audio }}
|
|
65
|
+
```
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(value, bytes):
|
|
68
|
+
audio_format = _get_audio_format_from_bytes(resolve_binary(value, as_base64=False))
|
|
69
|
+
input_audio = InputAudio.from_bytes(value, audio_format=audio_format)
|
|
70
|
+
elif _is_url(value):
|
|
71
|
+
audio_format = _get_audio_format_from_url(value)
|
|
72
|
+
input_audio = InputAudio.from_url(value, audio_format)
|
|
73
|
+
else:
|
|
74
|
+
input_audio = InputAudio.from_path(Path(value))
|
|
75
|
+
block = ContentBlock.model_validate({"type": "audio", "input_audio": input_audio})
|
|
76
|
+
return f"<content_block>{block.model_dump_json()}</content_block>"
|