alvis 1.0.0rc1__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.
- alvis-1.0.0rc1/.dockerignore +13 -0
- alvis-1.0.0rc1/.github/workflows/ci.yml +99 -0
- alvis-1.0.0rc1/.github/workflows/publish.yml +79 -0
- alvis-1.0.0rc1/.gitignore +22 -0
- alvis-1.0.0rc1/CHANGELOG.md +40 -0
- alvis-1.0.0rc1/CONSTITUTION.md +110 -0
- alvis-1.0.0rc1/CONTRIBUTING.md +244 -0
- alvis-1.0.0rc1/Dockerfile +39 -0
- alvis-1.0.0rc1/LICENSE +201 -0
- alvis-1.0.0rc1/PKG-INFO +539 -0
- alvis-1.0.0rc1/README.md +488 -0
- alvis-1.0.0rc1/ROADMAP.md +118 -0
- alvis-1.0.0rc1/docker/mock-confluence/Dockerfile +9 -0
- alvis-1.0.0rc1/docker/mock-confluence/main.py +109 -0
- alvis-1.0.0rc1/docker/observability/docker-compose.yml +57 -0
- alvis-1.0.0rc1/docker/observability/grafana/dashboards/alvis.json +148 -0
- alvis-1.0.0rc1/docker/observability/grafana/provisioning/dashboards/dashboards.yml +10 -0
- alvis-1.0.0rc1/docker/observability/grafana/provisioning/datasources/datasource.yml +7 -0
- alvis-1.0.0rc1/docker/observability/pipeline.yaml +26 -0
- alvis-1.0.0rc1/docker/observability/prometheus.yml +9 -0
- alvis-1.0.0rc1/docker-compose.yml +67 -0
- alvis-1.0.0rc1/docs/README.md +23 -0
- alvis-1.0.0rc1/docs/dsl.md +84 -0
- alvis-1.0.0rc1/docs/faq.md +99 -0
- alvis-1.0.0rc1/docs/getting-started.md +69 -0
- alvis-1.0.0rc1/docs/observability.md +132 -0
- alvis-1.0.0rc1/docs/plugins.md +64 -0
- alvis-1.0.0rc1/docs/release-1.0.md +130 -0
- alvis-1.0.0rc1/docs/schema.md +60 -0
- alvis-1.0.0rc1/docs/status.md +141 -0
- alvis-1.0.0rc1/docs/versioning.md +57 -0
- alvis-1.0.0rc1/examples/confluence-qdrant.yaml +27 -0
- alvis-1.0.0rc1/examples/docs/alvis-guide.md +27 -0
- alvis-1.0.0rc1/examples/docs/infra.md +19 -0
- alvis-1.0.0rc1/examples/documents/intro.md +11 -0
- alvis-1.0.0rc1/examples/hello-pipeline.yaml +21 -0
- alvis-1.0.0rc1/examples/kb-plugin/catalog.yaml +10 -0
- alvis-1.0.0rc1/examples/kb-plugin/data/incremental-ingestion.txt +3 -0
- alvis-1.0.0rc1/examples/kb-plugin/data/scalable-vector-search.txt +3 -0
- alvis-1.0.0rc1/examples/kb-plugin/kb_plugin/__init__.py +79 -0
- alvis-1.0.0rc1/examples/kb-plugin/pyproject.toml +14 -0
- alvis-1.0.0rc1/examples/pgvector.yaml +26 -0
- alvis-1.0.0rc1/examples/pipeline.yaml +27 -0
- alvis-1.0.0rc1/examples/python_dsl.py +35 -0
- alvis-1.0.0rc1/examples/s3-qdrant.yaml +18 -0
- alvis-1.0.0rc1/examples/s3-seed/docs/bucket-notes.md +8 -0
- alvis-1.0.0rc1/examples/s3-seed/manuals/quickstart.txt +8 -0
- alvis-1.0.0rc1/examples/s3-seed/videos/demo.mp4 +0 -0
- alvis-1.0.0rc1/examples/url-qdrant.yaml +29 -0
- alvis-1.0.0rc1/pyproject.toml +95 -0
- alvis-1.0.0rc1/scripts/check_golden_coverage.py +58 -0
- alvis-1.0.0rc1/scripts/check_release.py +30 -0
- alvis-1.0.0rc1/src/alvis/__init__.py +39 -0
- alvis-1.0.0rc1/src/alvis/__main__.py +6 -0
- alvis-1.0.0rc1/src/alvis/answer.py +160 -0
- alvis-1.0.0rc1/src/alvis/chunk/__init__.py +7 -0
- alvis-1.0.0rc1/src/alvis/chunk/auto.py +109 -0
- alvis-1.0.0rc1/src/alvis/chunk/sections.py +50 -0
- alvis-1.0.0rc1/src/alvis/chunk/size.py +68 -0
- alvis-1.0.0rc1/src/alvis/cli.py +866 -0
- alvis-1.0.0rc1/src/alvis/config/__init__.py +22 -0
- alvis-1.0.0rc1/src/alvis/config/loader.py +47 -0
- alvis-1.0.0rc1/src/alvis/config/models.py +175 -0
- alvis-1.0.0rc1/src/alvis/core/__init__.py +5 -0
- alvis-1.0.0rc1/src/alvis/core/ids.py +11 -0
- alvis-1.0.0rc1/src/alvis/core/models.py +96 -0
- alvis-1.0.0rc1/src/alvis/deprecated.py +72 -0
- alvis-1.0.0rc1/src/alvis/docstore.py +247 -0
- alvis-1.0.0rc1/src/alvis/dsl.py +325 -0
- alvis-1.0.0rc1/src/alvis/embed/__init__.py +23 -0
- alvis-1.0.0rc1/src/alvis/embed/base.py +36 -0
- alvis-1.0.0rc1/src/alvis/embed/cache.py +173 -0
- alvis-1.0.0rc1/src/alvis/embed/hash.py +52 -0
- alvis-1.0.0rc1/src/alvis/embed/openai.py +73 -0
- alvis-1.0.0rc1/src/alvis/env.py +78 -0
- alvis-1.0.0rc1/src/alvis/errors.py +7 -0
- alvis-1.0.0rc1/src/alvis/extract/__init__.py +18 -0
- alvis-1.0.0rc1/src/alvis/extract/base.py +85 -0
- alvis-1.0.0rc1/src/alvis/extract/csv.py +66 -0
- alvis-1.0.0rc1/src/alvis/extract/documents.py +164 -0
- alvis-1.0.0rc1/src/alvis/extract/json.py +51 -0
- alvis-1.0.0rc1/src/alvis/extract/markdown.py +62 -0
- alvis-1.0.0rc1/src/alvis/factories.py +154 -0
- alvis-1.0.0rc1/src/alvis/index/__init__.py +8 -0
- alvis-1.0.0rc1/src/alvis/index/base.py +52 -0
- alvis-1.0.0rc1/src/alvis/index/memory.py +99 -0
- alvis-1.0.0rc1/src/alvis/index/pgvector.py +196 -0
- alvis-1.0.0rc1/src/alvis/index/qdrant.py +210 -0
- alvis-1.0.0rc1/src/alvis/observability.py +446 -0
- alvis-1.0.0rc1/src/alvis/pipeline/__init__.py +5 -0
- alvis-1.0.0rc1/src/alvis/pipeline/engine.py +323 -0
- alvis-1.0.0rc1/src/alvis/pipeline/runner.py +285 -0
- alvis-1.0.0rc1/src/alvis/pipeline/stages.py +272 -0
- alvis-1.0.0rc1/src/alvis/plugin.py +277 -0
- alvis-1.0.0rc1/src/alvis/registry.py +76 -0
- alvis-1.0.0rc1/src/alvis/sources/__init__.py +20 -0
- alvis-1.0.0rc1/src/alvis/sources/base.py +42 -0
- alvis-1.0.0rc1/src/alvis/sources/confluence.py +100 -0
- alvis-1.0.0rc1/src/alvis/sources/content_types.py +87 -0
- alvis-1.0.0rc1/src/alvis/sources/fs.py +96 -0
- alvis-1.0.0rc1/src/alvis/sources/github.py +134 -0
- alvis-1.0.0rc1/src/alvis/sources/gitlab.py +157 -0
- alvis-1.0.0rc1/src/alvis/sources/http.py +193 -0
- alvis-1.0.0rc1/src/alvis/sources/s3.py +264 -0
- alvis-1.0.0rc1/src/alvis/sources/url.py +273 -0
- alvis-1.0.0rc1/src/alvis/testing.py +157 -0
- alvis-1.0.0rc1/src/alvis/transport.py +13 -0
- alvis-1.0.0rc1/tests/fixtures/corpus/data.csv +3 -0
- alvis-1.0.0rc1/tests/fixtures/corpus/guide.md +12 -0
- alvis-1.0.0rc1/tests/fixtures/corpus/notes.txt +3 -0
- alvis-1.0.0rc1/tests/golden/confluence.golden.json +26 -0
- alvis-1.0.0rc1/tests/golden/fs.golden.json +32 -0
- alvis-1.0.0rc1/tests/golden/github.golden.json +14 -0
- alvis-1.0.0rc1/tests/golden/gitlab.golden.json +13 -0
- alvis-1.0.0rc1/tests/golden/manifest.json +10 -0
- alvis-1.0.0rc1/tests/golden/s3.golden.json +13 -0
- alvis-1.0.0rc1/tests/golden/static_url.golden.json +24 -0
- alvis-1.0.0rc1/tests/test_answer.py +116 -0
- alvis-1.0.0rc1/tests/test_chunk.py +33 -0
- alvis-1.0.0rc1/tests/test_chunk_sections.py +56 -0
- alvis-1.0.0rc1/tests/test_chunk_size.py +50 -0
- alvis-1.0.0rc1/tests/test_cli.py +513 -0
- alvis-1.0.0rc1/tests/test_config.py +178 -0
- alvis-1.0.0rc1/tests/test_deprecated.py +46 -0
- alvis-1.0.0rc1/tests/test_documents.py +153 -0
- alvis-1.0.0rc1/tests/test_dsl.py +107 -0
- alvis-1.0.0rc1/tests/test_embed_cache.py +137 -0
- alvis-1.0.0rc1/tests/test_embed_index.py +55 -0
- alvis-1.0.0rc1/tests/test_embed_openai.py +111 -0
- alvis-1.0.0rc1/tests/test_env.py +103 -0
- alvis-1.0.0rc1/tests/test_extract.py +52 -0
- alvis-1.0.0rc1/tests/test_extract_csv.py +63 -0
- alvis-1.0.0rc1/tests/test_extract_json.py +85 -0
- alvis-1.0.0rc1/tests/test_golden_sources.py +209 -0
- alvis-1.0.0rc1/tests/test_http.py +120 -0
- alvis-1.0.0rc1/tests/test_idempotency.py +84 -0
- alvis-1.0.0rc1/tests/test_incremental.py +282 -0
- alvis-1.0.0rc1/tests/test_listing_sources.py +162 -0
- alvis-1.0.0rc1/tests/test_memory_safe.py +114 -0
- alvis-1.0.0rc1/tests/test_observability.py +259 -0
- alvis-1.0.0rc1/tests/test_pgvector.py +127 -0
- alvis-1.0.0rc1/tests/test_pipeline.py +160 -0
- alvis-1.0.0rc1/tests/test_plugin_sdk.py +255 -0
- alvis-1.0.0rc1/tests/test_qdrant.py +223 -0
- alvis-1.0.0rc1/tests/test_qdrant_integration.py +49 -0
- alvis-1.0.0rc1/tests/test_query.py +262 -0
- alvis-1.0.0rc1/tests/test_remote_sources.py +297 -0
- alvis-1.0.0rc1/tests/test_runner.py +85 -0
- alvis-1.0.0rc1/tests/test_stages.py +72 -0
- alvis-1.0.0rc1/tests/test_url_source.py +189 -0
- alvis-1.0.0rc1/tests/test_version.py +5 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install
|
|
23
|
+
run: pip install -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Lint (ruff)
|
|
26
|
+
run: ruff check src tests
|
|
27
|
+
|
|
28
|
+
- name: Typecheck (mypy)
|
|
29
|
+
run: mypy src/alvis
|
|
30
|
+
|
|
31
|
+
- name: Tests (pytest)
|
|
32
|
+
run: pytest --cov=alvis --cov-report=term-missing --cov-fail-under=90
|
|
33
|
+
|
|
34
|
+
golden:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
|
|
43
|
+
- name: Install
|
|
44
|
+
run: pip install -e ".[dev]"
|
|
45
|
+
|
|
46
|
+
- name: Golden tests (read-only snapshots)
|
|
47
|
+
run: pytest -m golden
|
|
48
|
+
|
|
49
|
+
- name: Golden coverage (every connector ships fixture + snapshot)
|
|
50
|
+
run: python scripts/check_golden_coverage.py
|
|
51
|
+
|
|
52
|
+
audit:
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
|
|
57
|
+
- uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: "3.12"
|
|
60
|
+
|
|
61
|
+
- name: Install (runtime deps + extras)
|
|
62
|
+
run: pip install -e ".[documents,pgindex,observability]"
|
|
63
|
+
|
|
64
|
+
- name: Audit dependencies (pip-audit)
|
|
65
|
+
run: pip install pip-audit && pip-audit
|
|
66
|
+
|
|
67
|
+
integration:
|
|
68
|
+
name: Qdrant integration
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
services:
|
|
71
|
+
qdrant:
|
|
72
|
+
image: qdrant/qdrant:v1.13.6
|
|
73
|
+
ports:
|
|
74
|
+
- 6333:6333
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
|
|
78
|
+
- uses: actions/setup-python@v5
|
|
79
|
+
with:
|
|
80
|
+
python-version: "3.12"
|
|
81
|
+
|
|
82
|
+
- name: Install
|
|
83
|
+
run: pip install -e ".[dev]"
|
|
84
|
+
|
|
85
|
+
- name: Wait for Qdrant
|
|
86
|
+
run: |
|
|
87
|
+
for attempt in {1..30}; do
|
|
88
|
+
if curl --fail --silent http://localhost:6333/readyz >/dev/null; then
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
sleep 2
|
|
92
|
+
done
|
|
93
|
+
docker logs "${{ job.services.qdrant.id }}"
|
|
94
|
+
exit 1
|
|
95
|
+
|
|
96
|
+
- name: Filesystem -> Qdrant -> query -> incremental -> reconcile
|
|
97
|
+
env:
|
|
98
|
+
ALVIS_QDRANT_URL: http://localhost:6333
|
|
99
|
+
run: pytest -m integration
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
package:
|
|
13
|
+
name: Build and publish to PyPI
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment:
|
|
16
|
+
name: pypi
|
|
17
|
+
url: https://pypi.org/project/alvis/
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
- name: Validate release tag
|
|
26
|
+
env:
|
|
27
|
+
GITHUB_REF_NAME: ${{ github.ref_name }}
|
|
28
|
+
run: python scripts/check_release.py
|
|
29
|
+
|
|
30
|
+
- name: Install build tools
|
|
31
|
+
run: python -m pip install --upgrade build twine
|
|
32
|
+
|
|
33
|
+
- name: Build distributions
|
|
34
|
+
run: python -m build
|
|
35
|
+
|
|
36
|
+
- name: Validate distributions
|
|
37
|
+
run: python -m twine check dist/*
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
|
+
|
|
42
|
+
docker:
|
|
43
|
+
name: Build and publish Docker image
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
needs: package
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- name: Set up Docker Buildx
|
|
50
|
+
uses: docker/setup-buildx-action@v3
|
|
51
|
+
|
|
52
|
+
- name: Set up QEMU (multi-arch builds)
|
|
53
|
+
uses: docker/setup-qemu-action@v3
|
|
54
|
+
|
|
55
|
+
- name: Extract metadata (tags, labels)
|
|
56
|
+
id: meta
|
|
57
|
+
uses: docker/metadata-action@v5
|
|
58
|
+
with:
|
|
59
|
+
images: bzdvdn/alvis
|
|
60
|
+
tags: |
|
|
61
|
+
type=ref,event=tag
|
|
62
|
+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
|
63
|
+
|
|
64
|
+
- name: Log in to Docker Hub
|
|
65
|
+
uses: docker/login-action@v3
|
|
66
|
+
with:
|
|
67
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
68
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
69
|
+
|
|
70
|
+
- name: Build and push multi-platform image
|
|
71
|
+
uses: docker/build-push-action@v6
|
|
72
|
+
with:
|
|
73
|
+
context: .
|
|
74
|
+
push: true
|
|
75
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
76
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
77
|
+
cache-from: type=gha
|
|
78
|
+
cache-to: type=gha,mode=max
|
|
79
|
+
platforms: linux/amd64,linux/arm64
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Local (never committed) — RU constitution copy
|
|
2
|
+
CONSTITUTION.ru.md
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
# incremental ingestion state
|
|
19
|
+
.alvis/
|
|
20
|
+
|
|
21
|
+
# port pilot (workspace, not repo content)
|
|
22
|
+
pilot/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Alvis are documented here.
|
|
4
|
+
|
|
5
|
+
## [1.0.0rc1] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
First release candidate for the stable Alvis contract.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Versioned YAML pipeline schema (`schema_version: 1`) and Canonical Content Tree v1.
|
|
12
|
+
- Typed Python DSL with parity to the YAML contract.
|
|
13
|
+
- Filesystem, Confluence, GitHub, GitLab, S3, and static URL sources.
|
|
14
|
+
- Memory, Qdrant, and optional pgvector indexes.
|
|
15
|
+
- Incremental ingestion, cheap remote listing fingerprints, reconcile, and watch mode.
|
|
16
|
+
- OpenAI-compatible embeddings with batching, retries, and optional caching.
|
|
17
|
+
- Retrieval APIs (`alvis.query`, `alvis.answer`) and CLI commands.
|
|
18
|
+
- Plugin SDK with discovery, validation, and a documented deprecation policy.
|
|
19
|
+
- Structured logging, metrics, optional Prometheus/OpenTelemetry support, and status reporting.
|
|
20
|
+
- Versioned Qdrant payload fields with reserved `__` system namespace and `__document_id`.
|
|
21
|
+
- Multi-platform Docker image build configuration for `bzdvdn/alvis`.
|
|
22
|
+
|
|
23
|
+
### Breaking Changes
|
|
24
|
+
|
|
25
|
+
- Product, import package, CLI, and distribution were renamed from `winnow` to `alvis`.
|
|
26
|
+
- The default incremental state directory is now `.alvis/`.
|
|
27
|
+
- The default CLI command is now `alvis`.
|
|
28
|
+
|
|
29
|
+
### Known Limitations
|
|
30
|
+
|
|
31
|
+
- Qdrant point identity and reconcile currently use source URI; document-id-ledger
|
|
32
|
+
ownership is planned for a later release.
|
|
33
|
+
- Metadata filters, hybrid search, reranking, multi-turn history, and LLM-based
|
|
34
|
+
evaluation remain v2.x scope.
|
|
35
|
+
- The `default` embedder is deterministic and intended for development/testing;
|
|
36
|
+
production deployments should configure an OpenAI-compatible embedder.
|
|
37
|
+
|
|
38
|
+
## [0.6.0]
|
|
39
|
+
|
|
40
|
+
See the repository history for pre-Alvis development releases.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Alvis — Knowledge Ingestion Engine
|
|
2
|
+
|
|
3
|
+
**Project Constitution**
|
|
4
|
+
|
|
5
|
+
**Version:** 1.2.0
|
|
6
|
+
**Status:** Architecture baseline
|
|
7
|
+
**Language:** Python 3.10+
|
|
8
|
+
**Primary backends:** Qdrant, PostgreSQL/pgvector
|
|
9
|
+
**Primary sources:** GitLab, Confluence, S3
|
|
10
|
+
**Primary formats:** Markdown, HTML, PDF, DOCX, XLSX, CSV, source code
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# 1. Purpose
|
|
15
|
+
|
|
16
|
+
Alvis is an internal framework for building corporate knowledge bases from heterogeneous sources.
|
|
17
|
+
|
|
18
|
+
The core pipeline:
|
|
19
|
+
|
|
20
|
+
Source
|
|
21
|
+
↓
|
|
22
|
+
Artifact
|
|
23
|
+
↓
|
|
24
|
+
Extraction
|
|
25
|
+
↓
|
|
26
|
+
Canonical Content Tree
|
|
27
|
+
↓
|
|
28
|
+
Transformation
|
|
29
|
+
↓
|
|
30
|
+
Chunking
|
|
31
|
+
↓
|
|
32
|
+
Embedding
|
|
33
|
+
↓
|
|
34
|
+
Index
|
|
35
|
+
|
|
36
|
+
The framework must allow users to assemble an ingestion pipeline declaratively,
|
|
37
|
+
without writing Python code — a YAML descriptor must be sufficient for any
|
|
38
|
+
pipeline (no-code by default). The typed Python DSL (`alvis.dsl`) is a
|
|
39
|
+
first-class alternative that produces the exact same config contract, and is
|
|
40
|
+
verified to run identically to its YAML twin.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
pipeline:
|
|
46
|
+
source:
|
|
47
|
+
type: confluence
|
|
48
|
+
|
|
49
|
+
extract:
|
|
50
|
+
strategy: auto
|
|
51
|
+
|
|
52
|
+
chunk:
|
|
53
|
+
strategy: auto
|
|
54
|
+
config:
|
|
55
|
+
max_tokens: 500
|
|
56
|
+
overlap: 50
|
|
57
|
+
|
|
58
|
+
embed:
|
|
59
|
+
type: default
|
|
60
|
+
|
|
61
|
+
index:
|
|
62
|
+
type: qdrant
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
# 2. Scope
|
|
66
|
+
|
|
67
|
+
## In scope
|
|
68
|
+
- Pluggable source adapters (GitLab, Confluence, S3) and format parsers (Markdown, HTML, PDF, DOCX, XLSX, CSV, source code).
|
|
69
|
+
- Configurable extraction, chunking, and transformation strategies exposed via YAML.
|
|
70
|
+
- Canonical Content Tree as the intermediate representation between extraction and indexing.
|
|
71
|
+
- Embedding and indexing into a pluggable vector store.
|
|
72
|
+
- Idempotent, deduplicated ingestion with retry and error-tolerance semantics.
|
|
73
|
+
|
|
74
|
+
## Out of scope
|
|
75
|
+
- Building end-user applications (chatbots, search UIs) on top of the indexed data.
|
|
76
|
+
- Managing source-of-truth systems (GitLab, Confluence, S3) themselves.
|
|
77
|
+
- Embedding model training.
|
|
78
|
+
|
|
79
|
+
# 3. Open Source Strategy
|
|
80
|
+
|
|
81
|
+
Alvis is distributed and developed as an open-source project.
|
|
82
|
+
|
|
83
|
+
## Principles
|
|
84
|
+
- **Framework, not app.** The core must stay source/format-agnostic; all integration lives behind pluggable adapters.
|
|
85
|
+
- **Declarative by default.** Users assemble pipelines via YAML; the typed Python DSL is a first-class alternative with the same contract. Python remains the language for plugin SDK extensions.
|
|
86
|
+
- **First impression wins.** Time-to-hello-world is a hard quality gate: `pip install alvis` + one YAML must produce a running index.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
- **Apache 2.0.** Permissive for corporate adopters, allows proprietary integration without forcing contribution back.
|
|
90
|
+
- CLA required for external contributors to keep the licensing path clean.
|
|
91
|
+
- A separate managed/hosted offering (future, optional) is a distinct distribution, never a fork of the core.
|
|
92
|
+
|
|
93
|
+
## Repository & governance
|
|
94
|
+
- Single main repo (`alvis/alvis`) for core + CLI.
|
|
95
|
+
- Adapters/bundles may live in the same repo under `plugins/` until they prove stability, then graduate to the mono-repo or their own repos as the community demands.
|
|
96
|
+
- `CONTRIBUTING.md` contract: "how to write a connector" is the cover-page of the contributor guide, not an appendix.
|
|
97
|
+
- Discussions/issues on GitHub; RFCs for anything that changes the YAML contract or Canonical Content Tree.
|
|
98
|
+
|
|
99
|
+
## Contributor model
|
|
100
|
+
- Core team: pipeline core, Canonical Content Tree, embedding/index abstraction.
|
|
101
|
+
- Community: connectors, format parsers, chunking strategies.
|
|
102
|
+
- Review rule: any plugin lands only with a hello-world example + test fixture + CI golden test.
|
|
103
|
+
|
|
104
|
+
# 4. Roadmap
|
|
105
|
+
|
|
106
|
+
- v0.1 — CLI (`alvis init/run/validate`), YAML schema validation, hello-world on Confluence → Qdrant
|
|
107
|
+
- v0.2 — docs site, contributor guide, first external connector PRs
|
|
108
|
+
- v1.0 — core pipeline stable contract (Canonical Content Tree v1), pgvector + GitLab/S3 sources
|
|
109
|
+
- v1.1 — plugin SDK for third-party source/strategy extensions
|
|
110
|
+
- v2.0 — stable plugin API, community-managed connector registry
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Contributing to Alvis
|
|
2
|
+
|
|
3
|
+
Thanks for contributing. Alvis is a no-code knowledge ingestion engine, and
|
|
4
|
+
most of its value is in breadth: the more sources, formats, and stores it
|
|
5
|
+
speaks, the more useful it is. This guide is the cover page for **how to write
|
|
6
|
+
a connector** — the single most common contribution.
|
|
7
|
+
|
|
8
|
+
Read [CONSTITUTION.md](CONSTITUTION.md) first: the roadmap and cross-cutting
|
|
9
|
+
rules live there and in [ROADMAP.md](ROADMAP.md).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Development setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m venv .venv
|
|
17
|
+
.venv/bin/pip install -e ".[dev]"
|
|
18
|
+
.venv/bin/ruff check src tests
|
|
19
|
+
.venv/bin/mypy src/alvis
|
|
20
|
+
.venv/bin/python -m pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- Python ≥ 3.10, ruff (lint), mypy `--strict`, pytest (asyncio auto mode).
|
|
24
|
+
- A feature ships only with its documentation (cross-cutting rule) and its
|
|
25
|
+
tests — see "Connector fixture + golden test" below, which is enforced in CI.
|
|
26
|
+
- YAML contract and Canonical Content Tree changes are RFC-first: open a
|
|
27
|
+
proposal before touching `config/models.py` or `core/models.py`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## How to write a connector
|
|
32
|
+
|
|
33
|
+
A connector turns a source of documents into Alvis `Artifact`s so the rest of
|
|
34
|
+
the pipeline (extract → chunk → embed → index) can process them. Alvis never
|
|
35
|
+
reads the raw bytes of a source itself — it always goes through a connector's
|
|
36
|
+
`fetch()`.
|
|
37
|
+
|
|
38
|
+
### 1. The contract
|
|
39
|
+
|
|
40
|
+
`src/alvis/sources/base.py` defines the interface:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
class Source(Protocol):
|
|
44
|
+
async def fetch(self) -> list[Artifact]: ...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`Artifact` (`alvis.core.models`) carries exactly:
|
|
48
|
+
|
|
49
|
+
| field | meaning |
|
|
50
|
+
| -------------- | ------------------------------------------------------------------ |
|
|
51
|
+
| `step_id` | stable, unique id for this document (page id, blob sha, ...) |
|
|
52
|
+
| `uri` | canonical URL/path the document lives at (used for retrieval links) |
|
|
53
|
+
| `content_type` | MIME type Alvis's extract stage understands (see step 3) |
|
|
54
|
+
| `data` | the raw bytes to be ingested |
|
|
55
|
+
| `metadata` | extra fields surfaced to users; keep it small and serialisable |
|
|
56
|
+
|
|
57
|
+
`fetch()` either returns artifacts or raises `SourceError`
|
|
58
|
+
(`alvis.sources.base`) with an optional `status_code`.
|
|
59
|
+
|
|
60
|
+
**Optional: cheap listing (recommended).** If your source can fingerprint a
|
|
61
|
+
document without downloading its body — an S3 ETag, a GitLab/GitHub blob sha, a
|
|
62
|
+
Confluence page version — also implement `ListingSource`
|
|
63
|
+
(`alvis.sources.base`):
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
class ListingSource(Source, Protocol):
|
|
67
|
+
async def list_documents(self) -> list[DocumentMeta]: ...
|
|
68
|
+
async def fetch(self, *, uris: set[str] | None = None) -> list[Artifact]: ...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `list_documents()` returns a `DocumentMeta` (`uri`, `step_id`, `content_type`,
|
|
72
|
+
`fingerprint`) per document, doing **only** the cheap scan call(s).
|
|
73
|
+
- `fetch(uris=...)` must download **only** the requested URIs — `uris=None`
|
|
74
|
+
means "everything". The engine's `--incremental` mode lists first, skips
|
|
75
|
+
documents whose stored fingerprint already matches, and calls
|
|
76
|
+
`fetch(uris=...)` with just the wanted URIs, so unchanged remote objects are
|
|
77
|
+
never fetched. A connector that skips this interface falls back to
|
|
78
|
+
content-hash fingerprinting (still skips processing, not downloads).
|
|
79
|
+
- `gitlab.py` / `github.py` / `s3.py` / `confluence.py` are complete
|
|
80
|
+
`ListingSource` examples.
|
|
81
|
+
|
|
82
|
+
### 2. Layout and naming
|
|
83
|
+
|
|
84
|
+
Look at how the built-ins are organised — each adapter is one small module:
|
|
85
|
+
|
|
86
|
+
- `src/alvis/sources/confluence.py` — a REST API adapter, the best template
|
|
87
|
+
for an HTTP connector.
|
|
88
|
+
- `src/alvis/sources/fs.py` — the local-filesystem adapter.
|
|
89
|
+
- `src/alvis/sources/github.py`, `gitlab.py`, `s3.py` — REST adapters that
|
|
90
|
+
accept a `transport=` kwarg for testing.
|
|
91
|
+
|
|
92
|
+
For an HTTP connector that talks to a REST API, use `HttpClient`
|
|
93
|
+
(`alvis.sources.http`): it gives you retries/backoff on 429/5xx/network
|
|
94
|
+
errors, `Retry-After` honoring, token/basic auth from env vars
|
|
95
|
+
(`api_token_env`, `username`), optional `verify=False` for self-signed TLS, and
|
|
96
|
+
a `max_bytes` cap so oversized responses are aborted mid-read instead of
|
|
97
|
+
buffered. Confluence is a 70-line example of a full adapter.
|
|
98
|
+
|
|
99
|
+
Reach for `transport=` kwargs / `header_hook=` when the service needs special
|
|
100
|
+
signing (see `s3.py` for a SigV4 example).
|
|
101
|
+
|
|
102
|
+
### 3. Wire it in — the five touch points
|
|
103
|
+
|
|
104
|
+
1. **Module** — `src/alvis/sources/<name>.py` implementing `fetch()`.
|
|
105
|
+
2. **Export** — add it to `__all__` in `src/alvis/sources/__init__.py`.
|
|
106
|
+
3. **Registry** — `src/alvis/registry.py`: add the type string to
|
|
107
|
+
`KNOWN_SOURCES` so `alvis validate` recognises it.
|
|
108
|
+
4. **Factory** — `src/alvis/factories.py`:
|
|
109
|
+
- `build_source(...)` — map `config.type == "<name>"` to your class.
|
|
110
|
+
- `source_identity(config)` — a stable `"<name>:<key>@<host>"` string; the
|
|
111
|
+
index uses it to namespace points so `reconcile` prunes only your source's
|
|
112
|
+
stale documents. Pick discriminator keys that uniquely identify a data
|
|
113
|
+
scope (e.g. GitLab: `project@host`).
|
|
114
|
+
5. **DSL** — `src/alvis/dsl.py`: add a `dsl.<name>(...)` builder mirroring the
|
|
115
|
+
YAML config keys, so the Python DSL and the YAML schema stay in parity.
|
|
116
|
+
|
|
117
|
+
Config validation lives in `src/alvis/config/models.py` (`SourceConfig` is
|
|
118
|
+
permissive `dict` today; type-specific keys are validated in the adapter).
|
|
119
|
+
Document the YAML keys in the README source table.
|
|
120
|
+
|
|
121
|
+
### 4. Content types and formats
|
|
122
|
+
|
|
123
|
+
`alvis.sources.content_types` maps extensions to MIME types. Your connector
|
|
124
|
+
should produce content types the `auto` extractor understands — Markdown
|
|
125
|
+
(`text/markdown`), HTML (`text/html`), plain text / code (`text/plain`), or
|
|
126
|
+
PDF/DOCX/XLSX for the `alvis[documents]` extra. If a remote format needs an
|
|
127
|
+
extension (e.g. GitLab raw blobs), derive it from the object name and pass it
|
|
128
|
+
through `content_type()`.
|
|
129
|
+
|
|
130
|
+
### 5. Every connector ships two things — enforced in CI
|
|
131
|
+
|
|
132
|
+
No connector lands without:
|
|
133
|
+
|
|
134
|
+
1. **A fixture** — a `tests/fixtures/` corpus of realistic sample data the
|
|
135
|
+
adapter ingests (a couple of small files is plenty). Fixtures must not live
|
|
136
|
+
under a directory Alvis itself would ingest in tests.
|
|
137
|
+
2. **A golden test** — a test marked `@pytest.mark.golden` that runs the
|
|
138
|
+
connector against canned service responses and diffs the `Artifact` output
|
|
139
|
+
against a committed snapshot.
|
|
140
|
+
|
|
141
|
+
`src/alvis/testing.py` provides the harness (used by every built-in in
|
|
142
|
+
`tests/test_golden_sources.py`):
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from alvis.testing import MockServer, artifacts_snapshot, assert_golden
|
|
146
|
+
|
|
147
|
+
@pytest.mark.golden
|
|
148
|
+
async def test_golden_my_source() -> None:
|
|
149
|
+
server = MockServer()
|
|
150
|
+
server.on("GET", "/api/v1/items", json_payload={"items": [{"id": "a", "body": "# Hi"}]})
|
|
151
|
+
|
|
152
|
+
source = MySource(url="http://example.com", transport=server.transport)
|
|
153
|
+
artifacts = await source.fetch()
|
|
154
|
+
|
|
155
|
+
assert_golden("my_source", artifacts_snapshot(artifacts))
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `MockServer` is an `httpx.MockTransport` under the hood — no network. Point
|
|
159
|
+
your `HttpClient` at `server.transport` (via a `transport=` kwarg, or
|
|
160
|
+
`source.client.transport = server.transport` when the constructor builds its
|
|
161
|
+
own client, as Confluence does). **Every request must have a route** —
|
|
162
|
+
unimapped routes raise, so the fixture doubles as a contract check on which
|
|
163
|
+
endpoints your connector calls. `server.request_log()` lets you assert on
|
|
164
|
+
the calls themselves.
|
|
165
|
+
- `artifacts_snapshot(...)` renders the `fetch()` result as a stable,
|
|
166
|
+
serialisable list so the snapshot is diff-able in review. Pass `root=` to
|
|
167
|
+
scrub machine-specific absolute paths (see the `fs` golden test).
|
|
168
|
+
- Snapshots live in `tests/golden/<name>.golden.json`. Run
|
|
169
|
+
`ALVIS_ACCEPT=1 pytest -m golden` to (re)write them, review the diff, and
|
|
170
|
+
commit. CI runs `pytest -m golden` **read-only** (no `ALVIS_ACCEPT`), so
|
|
171
|
+
drift and missing snapshots fail loudly.
|
|
172
|
+
|
|
173
|
+
**Register the golden test in `tests/golden/manifest.json`** under the
|
|
174
|
+
connector's type string pointing at the test file. The CI script
|
|
175
|
+
`scripts/check_golden_coverage.py` fails if a connector has a manifest entry
|
|
176
|
+
but no test/snapshot — keep the three in a single commit.
|
|
177
|
+
|
|
178
|
+
### 6. Beyond the connector
|
|
179
|
+
|
|
180
|
+
If you add a new parser, chunker, embedder, or indexer, follow the same shape:
|
|
181
|
+
module → export → `KNOWN_*` in `registry.py` → `build_*` in `factories.py` →
|
|
182
|
+
`dsl.*` builder → docs. Indexers that hold state (e.g. a new vector store)
|
|
183
|
+
must implement idempotent upsert + a `reconcile` pass for the per-source prune
|
|
184
|
+
to work.
|
|
185
|
+
|
|
186
|
+
## Write a plugin, not a fork (v1.1)
|
|
187
|
+
|
|
188
|
+
You do **not** need to modify this repository to ship an adapter. A plugin is
|
|
189
|
+
an ordinary installed Python package that registers itself under the
|
|
190
|
+
`alvis.plugins` entry-point group and declares a `alvis.plugin.Plugin`:
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
# pyproject.toml
|
|
194
|
+
[project.entry-points."alvis.plugins"]
|
|
195
|
+
kb-catalog = "kb_plugin:plugin"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
# kb_plugin/__init__.py
|
|
200
|
+
from alvis.plugin import Plugin
|
|
201
|
+
|
|
202
|
+
def _catalog(*, config, max_bytes=None): # factory contract (see alvis/plugin.py)
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
plugin = Plugin(
|
|
206
|
+
name="kb-catalog",
|
|
207
|
+
version="0.1.0",
|
|
208
|
+
sources={"catalog": _catalog}, # also: extractors, chunkers, embedders, indexers
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
After `pip install .`, the adapter is accepted by `alvis validate`, listed by
|
|
213
|
+
`alvis plugins`, and resolved by the stage factories — zero core changes.
|
|
214
|
+
Plugins are validated their own adapter (mirroring built-ins), so plugin types
|
|
215
|
+
may read any `config:` keys. The reference implementation is
|
|
216
|
+
`examples/kb-plugin` (a `catalog` source with cheap-listing incremental
|
|
217
|
+
support); copy it as your template and keep the tutorial contract: hello-world
|
|
218
|
+
example + test + docs. External plugins are tracked in [docs/plugins.md]
|
|
219
|
+
(docs/plugins.md).
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Contribution process
|
|
224
|
+
|
|
225
|
+
1. Fork, create a branch (`add-magnolia-connector`).
|
|
226
|
+
2. Implement the connector + fixture + golden test + docs + README table row
|
|
227
|
+
as one commit — the golden manifest, snapshot, and test never split.
|
|
228
|
+
3. Run the full gate: `ruff check src tests`, `mypy src/alvis`,
|
|
229
|
+
`pytest` (which includes `-m golden` read-only), and
|
|
230
|
+
`python scripts/check_golden_coverage.py`.
|
|
231
|
+
4. Open the pull request. CI runs lint, typecheck, the full suite on
|
|
232
|
+
Python 3.10/3.11/3.12, and a dedicated `golden` job that re-runs snapshots
|
|
233
|
+
read-only and enforces coverage.
|
|
234
|
+
|
|
235
|
+
## Cross-cutting rules (from ROADMAP)
|
|
236
|
+
|
|
237
|
+
- Every plugin lands only with: hello-world example + test fixture + CI golden
|
|
238
|
+
test.
|
|
239
|
+
- Any change to the YAML contract or Canonical Content Tree is an RFC-first
|
|
240
|
+
change.
|
|
241
|
+
- No feature ships without the chapter it documents.
|
|
242
|
+
- Breaking a stable-surface name (see [docs/versioning.md](docs/versioning.md))
|
|
243
|
+
needs a semver `MAJOR` (or a `MINOR` before `1.0.0`), a `deprecated()`
|
|
244
|
+
warning cycle, and a changelog line — never a silent break.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
|
|
3
|
+
# Alvis as a container. The product surface is YAML, so the image is a
|
|
4
|
+
# fixed `alvis` CLI with every optional extra preinstalled — users configure
|
|
5
|
+
# it purely by mounting pipeline files and a state volume, never by rebuilding:
|
|
6
|
+
#
|
|
7
|
+
# docker build -t alvis:dev .
|
|
8
|
+
# docker run --rm \
|
|
9
|
+
# -v "$PWD/pipeline.yaml:/workspace/pipeline.yaml:ro" \
|
|
10
|
+
# -v "$PWD/.alvis:/workspace/.alvis" \
|
|
11
|
+
# alvis:dev run --incremental pipeline.yaml
|
|
12
|
+
#
|
|
13
|
+
# State defaults to `.alvis/state.json` under the workdir (`/workspace`),
|
|
14
|
+
# which is exactly the mounted volume above.
|
|
15
|
+
|
|
16
|
+
FROM python:3.12-slim AS builder
|
|
17
|
+
|
|
18
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
19
|
+
COPY src ./src
|
|
20
|
+
|
|
21
|
+
RUN pip install --no-cache-dir .[documents,pgindex,observability]
|
|
22
|
+
|
|
23
|
+
FROM python:3.12-slim
|
|
24
|
+
|
|
25
|
+
COPY --from=builder /usr/local /usr/local
|
|
26
|
+
|
|
27
|
+
ENV PYTHONUNBUFFERED=1 \
|
|
28
|
+
PYTHONDONTWRITEBYTECODE=1
|
|
29
|
+
|
|
30
|
+
RUN adduser --disabled-password --gecos "" --home /workspace alvis \
|
|
31
|
+
&& chown -R alvis:alvis /workspace
|
|
32
|
+
|
|
33
|
+
USER alvis
|
|
34
|
+
WORKDIR /workspace
|
|
35
|
+
|
|
36
|
+
VOLUME ["/workspace"]
|
|
37
|
+
|
|
38
|
+
ENTRYPOINT ["alvis"]
|
|
39
|
+
CMD ["--help"]
|