Swiss Lawyer
БесплатноНе проверенA production-minded Agentic RAG backend for informational guidance about Swiss immigration and administrative procedures, using only official Swiss government s
Описание
A production-minded Agentic RAG backend for informational guidance about Swiss immigration and administrative procedures, using only official Swiss government sources with evidence metadata.
README
Swiss Lawyer MCP is a production-minded Agentic RAG backend for informational guidance about Swiss immigration and administrative procedures. The system is designed to use official Swiss government sources only, preserve evidence metadata, and later expose grounded procedure support through an MCP tool.
This repository currently implements Phase 1: PDF ingestion, Phase 2: hybrid retrieval, Phase 3: reranking, Phase 4.2: schema-driven clarification, Phase 5: grounded answer generation, Phase 6: planner/workflow engine, Phase 7: SQLite memory, Phase 8: FastAPI orchestration, Phase 9: official source synchronization, and Phase 10 Part 1: evaluation module architecture. It does not yet implement MCP integration, OAuth, a frontend, cloud deployment, GitHub Actions scheduling, final evaluation metrics, regression thresholds, or RAGAS evaluation.
Safety Scope
This project is not a legal adviser. It provides informational guidance only and must ground future answers in retrieved official-source evidence.
Folder Structure
Swiss Lawyer MCP/
├── .env.example
├── .gitignore
├── README.md
├── alembic.ini
├── docker-compose.yml
├── migrations/
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
│ ├── 0001_phase_7_memory.py
│ └── 0002_phase_9_synchronizer.py
├── pytest.ini
├── requirements.txt
├── backend/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── app.py
│ │ ├── dependencies.py
│ │ ├── error_handlers.py
│ │ ├── routes/
│ │ │ ├── __init__.py
│ │ │ ├── admin_synchronization.py
│ │ │ ├── health.py
│ │ │ └── procedures.py
│ │ └── schemas.py
│ ├── clarification/
│ │ ├── __init__.py
│ │ ├── clarification_engine.py
│ │ ├── intent_classifier.py
│ │ ├── procedure_schemas.py
│ │ └── test_clarification.py
│ ├── generation/
│ │ ├── __init__.py
│ │ ├── answer_generator.py
│ │ ├── confidence.py
│ │ ├── prompts.py
│ │ ├── source_attribution.py
│ │ └── test_generation.py
│ ├── ingestion/
│ │ ├── __init__.py
│ │ ├── chunking.py
│ │ ├── discovery.py
│ │ ├── embeddings.py
│ │ ├── extraction.py
│ │ ├── index.py
│ │ └── vector_store.py
│ ├── location/
│ │ ├── __init__.py
│ │ └── canton_resolver.py
│ ├── memory/
│ │ ├── __init__.py
│ │ ├── database.py
│ │ ├── memory_service.py
│ │ ├── models.py
│ │ ├── repositories/
│ │ │ ├── __init__.py
│ │ │ ├── converters.py
│ │ │ ├── interaction_repository.py
│ │ │ ├── procedure_repository.py
│ │ │ ├── profile_repository.py
│ │ │ └── user_repository.py
│ │ └── test_memory.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── chunk.py
│ │ ├── clarification.py
│ │ ├── document.py
│ │ ├── generation.py
│ │ ├── memory.py
│ │ ├── planner.py
│ │ ├── reranking.py
│ │ ├── retrieval.py
│ │ └── user_profile.py
│ ├── orchestration/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ └── procedure_orchestrator.py
│ ├── planners/
│ │ ├── __init__.py
│ │ ├── prompts.py
│ │ ├── test_planner.py
│ │ └── workflow_planner.py
│ ├── prompts/
│ │ ├── grounded_answer_system_prompt.txt
│ │ └── workflow_planner_system_prompt.txt
│ ├── reranking/
│ │ ├── __init__.py
│ │ ├── reranker.py
│ │ ├── reranking_service.py
│ │ └── test_reranker.py
│ ├── retrieval/
│ │ ├── __init__.py
│ │ ├── bm25.py
│ │ ├── hybrid.py
│ │ ├── test_retrieval.py
│ │ └── vector.py
│ ├── synchronizer/
│ │ ├── __init__.py
│ │ ├── cli.py
│ │ ├── discovery.py
│ │ ├── document_processor.py
│ │ ├── hashing.py
│ │ ├── html_extraction.py
│ │ ├── http_client.py
│ │ ├── identifiers.py
│ │ ├── models.py
│ │ ├── regions.py
│ │ ├── repository.py
│ │ ├── source_registry.py
│ │ └── synchronizer_service.py
│ └── utils/
│ ├── __init__.py
│ └── config.py
├── data/
│ ├── chromadb/
│ │ └── .gitkeep
│ ├── pdfs/
│ │ ├── be/
│ │ ├── federal/
│ │ ├── ge/
│ │ ├── metadata/
│ │ │ └── sources.yaml
│ │ ├── vd/
│ │ └── zh/
│ └── sqlite/
│ └── .gitkeep
├── docs/
├── evaluation/
│ ├── __init__.py
│ ├── config.py
│ ├── models.py
│ ├── runner.py
│ ├── adapters/
│ │ ├── __init__.py
│ │ ├── clarification_adapter.py
│ │ ├── common.py
│ │ ├── end_to_end_adapter.py
│ │ ├── generation_adapter.py
│ │ ├── planner_adapter.py
│ │ ├── reranking_adapter.py
│ │ └── retrieval_adapter.py
│ ├── artifacts/
│ ├── baselines/
│ ├── datasets/
│ ├── metrics/
│ └── reports/
├── notebooks/
└── tests/
├── evaluation/
│ └── test_evaluation_module.py
├── test_answer_generator.py
├── test_bm25_retrieval.py
├── test_clarification_engine.py
├── test_chunking.py
├── test_discovery.py
├── test_embeddings.py
├── test_extraction.py
├── test_generation_models.py
├── test_generation_prompts.py
├── test_hybrid_retrieval.py
├── test_index.py
├── test_intent_classifier.py
├── test_memory_service.py
├── test_phase8_api_orchestration.py
├── test_phase9_synchronizer.py
├── test_planner_models.py
├── test_reranker.py
├── test_reranking_models.py
├── test_reranking_service.py
├── test_retrieval_models.py
├── test_source_attribution.py
├── test_user_profile.py
├── test_vector_retrieval.py
├── test_vector_store.py
└── test_workflow_planner.py
Phase 1 ingestion through Phase 10 Part 1 evaluation architecture are implemented right now. Generated folders such as __pycache__/, .pytest_cache/, .venv/, generated ChromaDB files, generated evaluation artifacts, synchronized normalized webpage files, temporary downloads, and the generated SQLite database are intentionally omitted from this tree.
The data/pdfs/ directory contains regional subfolders such as federal, zh, ge, vd, and be. The ingestion pipeline uses each PDF's parent folder as its region metadata.
Setup
Use Python 3.12.
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
Export OPENAI_API_KEY in your shell before running ingestion. The .env.example file documents the expected variables for local setup.
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
OPENAI_API_KEY |
empty | Required for OpenAI embeddings |
OPENAI_EMBEDDING_MODEL |
text-embedding-3-small |
Embedding model |
PDF_ROOT |
data/pdfs |
Root folder scanned recursively for PDFs |
CHROMA_PATH |
data/chromadb |
Persistent ChromaDB storage path |
CHROMA_COLLECTION |
swiss_procedures |
ChromaDB collection name |
CHUNK_SIZE_WORDS |
600 |
Chunk size in words |
CHUNK_OVERLAP_WORDS |
100 |
Word overlap between adjacent chunks |
RETRIEVAL_TOP_K |
10 |
Default number of candidates returned by each retrieval method |
RERANK_MODEL |
cross-encoder/ms-marco-MiniLM-L-6-v2 |
Local Sentence Transformers CrossEncoder model |
RERANK_TOP_K |
5 |
Number of reranked chunks selected from merged candidates |
OPENAI_GENERATION_MODEL |
gpt-4o-mini |
OpenAI GPT model used for grounded answer generation |
OPENAI_PLANNER_MODEL |
gpt-4o-mini |
OpenAI GPT model used for workflow planning |
API_HOST |
127.0.0.1 |
Local FastAPI host |
API_PORT |
8000 |
Local FastAPI port |
SQLITE_DATABASE_URL |
sqlite:///data/sqlite/memory.db |
SQLAlchemy database URL for user memory |
REQUEST_TIMEOUT_SECONDS |
60 |
Request timeout budget for API clients and future callers |
LOG_LEVEL |
INFO |
API logging level |
ENABLE_SYNC_ADMIN_ENDPOINTS |
false |
Enables development-only synchronization admin endpoints |
SYNC_SOURCE_REGISTRY_PATH |
data/pdfs/metadata/sources.yaml |
Curated source registry path |
SYNC_PDF_PATH |
data/pdfs |
Local storage root for synchronized PDFs |
SYNC_DOCUMENT_PATH |
data/documents |
Local storage root for normalized webpage documents |
SYNC_TEMP_DOWNLOAD_PATH |
data/tmp/synchronizer |
Temporary download location |
SYNC_HTTP_TIMEOUT_SECONDS |
30 |
HTTP timeout for synchronization requests |
SYNC_MAX_DOCUMENT_BYTES |
20000000 |
Maximum downloaded document size |
SYNC_RETRY_COUNT |
2 |
Retry count for temporary HTTP failures |
SYNC_RETRY_BACKOFF_SECONDS |
0.25 |
Exponential retry backoff base |
SYNC_USER_AGENT |
Swiss Lawyer MCP Synchronizer/0.9 |
Synchronizer HTTP user agent |
SYNC_RETAIN_UNAVAILABLE_SOURCES |
true |
Retain last valid indexed version when a source is unavailable |
SYNC_CANDIDATE_DISCOVERY_ENABLED |
true |
Enable candidate discovery commands |
SYNC_WEBPAGE_MIN_CONTENT_CHARS |
100 |
Minimum extracted webpage content target |
Storage Roles
Swiss Lawyer MCP uses two separate storage systems:
- ChromaDB stores official Swiss knowledge and document chunks.
- SQLite stores user-specific memory: profile facts, saved procedures, progress, and concise summaries.
Keeping these stores separate prevents user memory from mixing with the legal/procedural knowledge base.
Run Ingestion
export OPENAI_API_KEY="your-api-key"
python -m backend.ingestion.index
The command scans data/pdfs/, extracts text page by page with PyMuPDF, chunks extracted text, generates OpenAI embeddings, and writes the chunks into the persistent ChromaDB collection swiss_procedures.
Hybrid Retrieval
Phase 2 retrieves candidate chunks from the existing ChromaDB collection using two methods:
- Vector search embeds the user question with
text-embedding-3-smalland queries ChromaDB for semantically similar chunks. - BM25 keyword search loads all stored ChromaDB chunks, tokenizes their text, builds a
rank-bm25index, and scores chunks against the user's query terms.
The HybridRetriever runs both methods, merges the results, removes duplicate chunk IDs, and preserves retrieval source information. It does not rerank and does not generate answers.
Reranking
Hybrid retrieval is good at recall: vector search can find semantically similar chunks, while BM25 can catch exact keyword matches. It can still return candidates that are only loosely related to the question. Reranking improves precision by scoring each merged candidate directly against the question.
Phase 3 uses a local Sentence Transformers CrossEncoder:
cross-encoder/ms-marco-MiniLM-L-6-v2
For each candidate, the reranker scores the pair:
(question, chunk text)
The reranker sorts candidates by CrossEncoder relevance score and keeps the top results. It does not call external APIs and does not generate answers.
The current architecture is:
Question
↓
Hybrid Retrieval
↓
Merged Candidates
↓
Reranker
↓
Top Relevant Chunks
Clarification
Clarification is schema-driven. Before retrieval and answer generation, the system classifies the user's intent and checks the known user profile against deterministic procedure schemas.
Current clarification architecture:
Question
↓
Intent Classification
↓
Clarification Engine
↓
Clarification Questions
↓
(Next phase: Answer Generation)
Each supported procedure has one centralized schema in backend/clarification/procedure_schemas.py. A schema defines:
- intent name
- required fields
- optional fields
- field descriptions
- default clarification question for each required field
- intent keywords
The clarification engine asks all and only missing required fields for the detected procedure. This matters in legal and administrative workflows because nationality, canton, permit status, purpose of stay, and dates can materially change eligibility, documents, competent authority, steps, timelines, and applicable federal or cantonal rules.
Asking fewer but more relevant questions improves the user experience and avoids unnecessary LLM calls. It is also safer and easier to test: required information is determined by versioned schemas, not by a model improvising case-by-case.
To add a new procedure, add one entry to PROCEDURE_SCHEMAS with its required fields, optional fields, descriptions, questions, and keywords. The classifier and clarification engine will use it automatically.
Test Clarification
python -m backend.clarification.test_clarification \
"Can I move to Switzerland as a Brazilian citizen?" \
--profile-json '{"nationality":"Brazil"}'
Grounded Answer Generation
Phase 5 adds final answer generation after clarification, hybrid retrieval, and reranking. The generator receives the user question, detected intent, completed user profile, and top reranked chunks. It does not perform retrieval.
Current end-to-end architecture:
Question
↓
Clarification
↓
Hybrid Retrieval
↓
Reranker
↓
Grounded Answer Generation
Grounded generation uses a reusable system prompt stored in backend/prompts/grounded_answer_system_prompt.txt. The prompt instructs the GPT model to answer only from supplied official context, never invent rules, documents, deadlines, authorities, or procedures, and explicitly say when retrieved context is insufficient.
The generated answer is structured as:
- short direct answer
- explanation
- procedure steps
- important notes
- cited official sources
- deterministic confidence label
- insufficient-context flag
If no reranked context is available, the generator does not call OpenAI. It returns:
The retrieved official documentation does not contain enough information to answer this question completely.
Source Attribution
Every generated answer includes source attribution from the reranked chunks. Citations include source filename, page number when available, and region. This is essential for legal/procedural workflows because users need to see where guidance came from, and later evaluation can verify whether the answer was actually grounded in official evidence.
Confidence Estimation
Confidence is not GPT self-reported confidence. It is estimated deterministically from retrieval quality signals:
- number of reranked chunks
- number of unique sources
- average rerank score
This keeps confidence tied to evidence quality rather than model tone.
Test Generation
export OPENAI_API_KEY="your-api-key"
python -m backend.generation.test_generation \
"Can a Brazilian citizen work in Switzerland?" \
--profile-json '{"nationality":"Brazil","employment_status":"Swiss job offer","purpose_of_stay":"work","intended_canton":"Zurich"}'
Planner & Workflow Engine
Phase 6 converts a grounded answer into an actionable procedure workflow. The planner receives the user question, detected intent, completed profile, generated answer, citations, and reranked evidence. It does not perform retrieval and does not invent missing procedural details.
Current architecture:
Question
↓
Clarification
↓
Hybrid Retrieval
↓
Reranker
↓
Grounded Answer Generation
↓
Planner & Workflow Engine
Answer generation explains what the retrieved evidence supports. Planning turns that grounded explanation into a workflow with:
- title and summary
- step-by-step plan
- required documents
- estimated timelines
- potential blockers
- next recommended action
- workflow status
- source references
- missing information
The planner prompt is stored in backend/prompts/workflow_planner_system_prompt.txt. It instructs the model to use only the grounded answer and cited evidence. Unsupported deadlines, documents, offices, fees, authorities, or eligibility rules must be written as:
Not specified in retrieved sources.
Workflow status values are:
ready_to_start: enough information exists to begin.needs_more_information: important information is missing.blocked: the grounded answer indicates the user may not currently qualify or cannot proceed.in_progress: reserved for future memory support.completed: reserved for future memory support.
These statuses prepare the project for SQLite memory in Phase 7 because a future persistent workflow record can track whether a user has started, completed, or is blocked on specific procedure steps.
Test Planner
export OPENAI_API_KEY="your-api-key"
python -m backend.planners.test_planner
SQLite Memory
Phase 7 adds persistent, structured user memory at:
data/sqlite/memory.db
The memory layer stores only procedure-relevant structured information. It does not store API keys, passwords, identity documents, uploaded document contents, or full conversation transcripts by default. It stores concise summaries and JSON-compatible profile facts that can be inspected and deleted.
Database Schema
The initial Alembic migration creates:
users: user records with optionalexternal_user_keyuser_profile_facts: flexible confirmed or unconfirmed profile facts keyed byuser_id + field_nameprocedures: savedProcedurePlanJSON, workflow status, current step, and progress timestampsprocedure_interactions: concise interaction summaries and optional structured payloads
Profile facts are flexible so new clarification fields can be added without a database migration.
Migrations
Run migrations before using the real memory database:
.venv/bin/alembic upgrade head
Tests run migrations against temporary SQLite databases and do not modify data/sqlite/memory.db.
Memory CLI Demo
python -m backend.memory.test_memory
The demo creates or retrieves a demo user, saves confirmed profile facts, reconstructs a Phase 4 UserProfile, saves a Phase 6 ProcedurePlan, records an interaction summary, updates procedure status, builds a MemoryContext, and deletes a separate demo user.
Procedure Resumption
MemoryService.build_memory_context(...) loads:
- known user profile facts as a Phase 4
UserProfile - an active procedure when a
procedure_idis supplied - active procedures filtered deterministically by intent/question and status
- recent interaction summaries
- saved
ProcedurePlan, latest status, and current step
Future FastAPI and MCP layers can load this context before clarification, so users are not repeatedly asked for already confirmed information.
Updated architecture:
Official PDFs
↓
Ingestion
↓
ChromaDB
User interaction
↓
Clarification
↓
Retrieval and reranking
↓
Grounded answer
↓
Planner
↓
SQLite memory
↓
Future procedure continuation
FastAPI Orchestration
Phase 8 exposes the workflow through FastAPI and connects the existing domain services without duplicating their logic.
Run locally:
uvicorn backend.api.app:app --reload
The API layer is intentionally thin:
backend/api/app.pycreates the FastAPI application.backend/api/dependencies.pywires reusable services and cached expensive components.backend/api/routes/health.pyexposes dependency health checks.backend/api/routes/procedures.pyexposes procedure query, read, update, list, and memory deletion endpoints.backend/orchestration/procedure_orchestrator.pycoordinates clarification, retrieval, reranking, generation, planning, and memory.backend/location/canton_resolver.pydeterministically resolves known Swiss cities to cantons.
Current Phase 8 architecture:
ChatGPT in a later phase
↓
MCP in a later phase
↓
FastAPI
↓
ProcedureOrchestrator
├── SQLite memory
├── Clarification engine
├── Hybrid retrieval
├── Reranker
├── Grounded generation
└── Workflow planner
Complete request lifecycle:
Request
↓
Resolve or create user
↓
Load SQLite memory
↓
Persist explicitly confirmed profile updates
↓
Resolve intended city to canton when possible
↓
Intent detection
↓
Clarification engine
↓
If required information is missing: return questions and stop
↓
Hybrid retrieval
↓
Reranker
↓
Grounded answer generation
↓
Planner and workflow engine
↓
Save or update procedure
↓
Record concise interaction summary
↓
Return answer, plan, sources, and procedure state
Endpoints
GET /health: checks application, SQLite, ChromaDB, and OpenAI configuration availability.POST /v1/procedures/query: runs the clarification-first procedure workflow.GET /v1/users/{user_id}/procedures: lists saved procedures, with optional status, intent, active-only, limit, and offset filters.GET /v1/procedures/{procedure_id}?user_id=...: reads one saved procedure with recent interaction summaries.PATCH /v1/procedures/{procedure_id}: updates status, current step, confirmed profile facts, and progress notes.DELETE /v1/users/{user_id}/memory: deletes user-specific SQLite memory only. It does not delete ChromaDB official knowledge.
Query Example
curl -X POST http://localhost:8000/v1/procedures/query \
-H "Content-Type: application/json" \
-d '{
"external_user_key": "demo-user",
"question": "Can I move to Switzerland as a Brazilian citizen?",
"profile_updates": {
"nationality": "Brazil"
},
"confirmed_profile_fields": [
"nationality"
]
}'
When clarification is still required, the API stops before retrieval, reranking, generation, and planning:
{
"state": "clarification_required",
"needs_clarification": true,
"missing_fields": [
"intended_canton",
"purpose_of_stay",
"employment_status"
],
"clarification_questions": [
{
"field": "intended_canton",
"question": "Which Swiss canton or city are you planning to move to?"
}
]
}
Only profile fields listed in confirmed_profile_fields are stored as confirmed memory. Unconfirmed profile_updates can be used for the current request, but they are not persisted as confirmed user facts.
City-to-Canton Resolution
CantonResolver maps known Swiss cities deterministically, for example:
- Zurich or Zürich →
ZH - Geneva or Genève →
GE - Lausanne →
VD - Bern or Berne →
BE - Basel →
BS - Lugano →
TI
If a city is unknown or ambiguous, the API returns a clarification question instead of guessing a canton.
Procedure Continuation
When procedure_id is supplied, the API verifies that the procedure belongs to the resolved user before reading or updating it. This ownership check exists even though authentication is not implemented yet. Saved procedures keep their validated Phase 6 ProcedurePlan, workflow status, current step, and recent concise interaction summaries.
Every query response includes the disclaimer:
This information is based on retrieved official Swiss sources and is provided for informational purposes only. It does not constitute legal advice.
Official Source Synchronizer
Phase 9 keeps the official knowledge base current through a controlled synchronizer. Automatic refresh does not mean unrestricted crawling.
- Approved sources are refreshed automatically.
- New sources are only discovered into a review queue.
- Discovered candidates must be approved before indexing.
- Only official Swiss government domains from the centralized allowlist are accepted.
Current Phase 9 architecture:
Official approved sources
↓
Synchronizer
├── Conditional HTTP checks
├── Change detection
├── PDF and webpage processors
├── Candidate discovery
└── Synchronization audit records
↓
Incremental ingestion
↓
ChromaDB
↓
FastAPI orchestration
↓
Grounded answers
Supported Regions
The synchronizer supports federal plus all 26 Swiss cantons:
ag ai ar be bl bs fr ge gl gr ju lu ne nw ow sg sh so sz tg ti ur vd vs zg zh
Region names and approved government domains live centrally in backend/synchronizer/regions.py. Domain rules are not scattered across the codebase.
Source Registry
Curated static sources live in:
data/pdfs/metadata/sources.yaml
Schema:
version: 1
sources:
- id: sem_family_reunification
enabled: true
region: federal
authority: State Secretariat for Migration
procedure_types:
- family_reunification
source_type: pdf
url: https://www.sem.admin.ch/...
language: en
local_filename: family_reunification.pdf
discovery_enabled: false
title: Optional title
expected_content_type: application/pdf
css_content_selector: main
css_link_selector: a
include_url_patterns: []
exclude_url_patterns: []
notes: Optional notes
priority: 10
expected_update_frequency: weekly
replacement_group: family_reunification_federal
metadata: {}
Remote pdf, webpage, and landing_page entries must use HTTPS and pass the region/domain allowlist. Existing manually collected PDFs are preserved as disabled local_only seed entries with local:// URLs and TODO notes instead of fabricated official URLs.
Approved Domain Policy
The synchronizer rejects:
- non-HTTPS remote sources
- redirects that leave the approved allowlist
- blogs, law firms, news sites, social media, private mirrors, shorteners, and arbitrary storage
- path traversal or unsafe local filenames
Final URLs are validated after redirects.
Database Schema
Phase 9 adds these SQLite tables through Alembic migration 0002_phase_9_synchronizer:
synchronized_sources: source status, canonical URL, local path, ETag, Last-Modified, SHA-256, document id, failures, timestamps.synchronization_runs: run status, scope, checked/updated/unchanged/failed counts, discovered candidate count.synchronization_events: per-source audit events such ascheck_started,unchanged,validated,indexed,updated,failed,unavailable, and candidate review events.source_candidates: discovered official-looking links awaiting approval, with candidate statuspending,approved,rejected,duplicate, orinvalid.
Run migrations:
.venv/bin/alembic upgrade head
Change Detection
Change detection uses this order:
HTTP 304
↓
ETag comparison
↓
Last-Modified comparison
↓
SHA-256 comparison
SHA-256 is the final source of truth. A changed Last-Modified header alone is not enough to force reindexing when the content hash is unchanged.
PDF Synchronization
For approved PDFs:
conditional HTTP request
↓
temporary download
↓
content-type, PDF signature, and PyMuPDF validation
↓
SHA-256
↓
Phase 1 extraction and chunking
↓
embeddings
↓
ChromaDB replace_document(document_id, chunks)
↓
move validated PDF into data/pdfs/<region>/
↓
record sync metadata and events
The old local file and old ChromaDB chunks are retained if validation, embedding, or replacement fails.
Webpage Synchronization
For approved webpages, the synchronizer extracts primary visible content, removes boilerplate such as navigation, headers, footers, cookie text, scripts, and styles, preserves headings, paragraphs, lists, tables, and visible link labels where feasible, then saves a normalized JSON document under:
data/documents/<region>/<source_id>.json
Every synchronized chunk includes provenance metadata:
document_id
source_id
source
official_url
region
authority
language
procedure_types
page or section
content_sha256
synchronized_at
source_type
Incremental Reindexing
ChromaChunkStore.replace_document(...) replaces only chunks for a changed document_id.
New chunks are embedded before old chunks are deleted. If insertion fails, the old chunks remain active. The synchronizer does not rebuild the entire ChromaDB collection for one updated document.
Candidate Discovery
Discovery runs only from approved landing pages or sources with discovery_enabled: true.
It:
- extracts links
- resolves relative URLs
- canonicalizes URLs
- rejects links outside approved domains
- applies include/exclude patterns
- ignores obvious assets
- infers procedure types from deterministic keywords
- deduplicates candidates
- stores candidates in
source_candidates
Candidates are never indexed automatically.
CLI
python -m backend.synchronizer.cli validate
python -m backend.synchronizer.cli sync --all
python -m backend.synchronizer.cli sync --region zh
python -m backend.synchronizer.cli sync --source sem_family_reunification
python -m backend.synchronizer.cli discover --all
python -m backend.synchronizer.cli discover --region vd
python -m backend.synchronizer.cli status
python -m backend.synchronizer.cli candidates list
python -m backend.synchronizer.cli candidates approve <candidate_id>
python -m backend.synchronizer.cli candidates reject <candidate_id> --note "Irrelevant report"
python -m backend.synchronizer.cli cleanup --dry-run
Example synchronization report:
{
"run_id": "8e3f...",
"requested_scope": "all",
"status": "completed",
"checked_count": 2,
"unchanged_count": 1,
"updated_count": 1,
"failed_count": 0,
"discovered_candidate_count": 0,
"events": ["zh_driving: updated", "sem_family: unchanged"]
}
Admin Endpoints
Development/admin synchronization endpoints are available only when:
ENABLE_SYNC_ADMIN_ENDPOINTS=true
Exposed endpoints:
POST /v1/admin/synchronization/runGET /v1/admin/synchronization/statusGET /v1/admin/synchronization/runsGET /v1/admin/synchronization/candidatesPOST /v1/admin/synchronization/candidates/{candidate_id}/approvePOST /v1/admin/synchronization/candidates/{candidate_id}/reject
These must not be publicly exposed without authentication. The CLI remains the preferred local control interface.
Scheduling Preparation
Phase 9 does not embed a scheduler inside FastAPI. External schedulers can call deterministic commands:
python -m backend.synchronizer.cli sync --all
python -m backend.synchronizer.cli discover --all
Suggested cadence:
- approved-source refresh: weekly
- candidate discovery: monthly
Future scheduling can be handled by cron, GitHub Actions, Azure DevOps, or another external scheduler.
Recovery Behavior
When a source returns 404 or 410, it is marked unavailable, a failure/event is recorded, and the last successfully indexed version is retained. The synchronizer does not delete local files or ChromaDB chunks because a government website may be temporarily restructured.
Evaluation Module
Phase 10 Part 1 adds a reusable evaluation architecture. It does not add final metric calculations, regression thresholds, before/after reports, or production RAG behavior changes yet.
The evaluation module is separate from production code under evaluation/. It can inspect stages independently:
- intent classification
- clarification-question generation
- vector retrieval
- BM25 retrieval
- hybrid retrieval
- reranking
- grounded-answer generation
- source citation
- procedure-plan generation
- insufficient-context and abstention behavior
- complete end-to-end execution
Offline vs Live
Offline mode is the default. It uses stored or mocked outputs and must not call OpenAI or live retrieval/model services unless those dependencies are explicitly injected for a test.
Live mode must be selected explicitly:
EvaluationConfig(execution_mode="live")
Live mode is where configured OpenAI models and the local retrieval system can be evaluated later. Phase 10 Part 1 only creates the architecture and safety boundaries.
Adapters
Evaluation adapters are thin wrappers around existing production services:
ClarificationEvaluationAdapter: runs Phase 4 intent classification and clarification.RetrievalEvaluationAdapter: runs vector, BM25, and hybrid retrieval independently.RerankingEvaluationAdapter: runs the Phase 3 reranker over supplied candidates.GenerationEvaluationAdapter: runs Phase 5 generation in live mode or returns precomputed offline answers.PlannerEvaluationAdapter: runs Phase 6 planning in live mode or returns precomputed offline plans.EndToEndEvaluationAdapter: runs the Phase 8 orchestrator through an isolated dependency supplied to evaluation.
The adapters normalize production outputs into EvaluationCaseResult without changing production behavior.
Evaluation Artifacts
Each run writes raw artifacts to:
evaluation/artifacts/<run_id>/
├── run_metadata.json
├── config.json
├── case_results.jsonl
├── warnings.json
├── errors.json
└── intermediate_outputs/
Generated artifacts are ignored by Git by default. The committed .gitkeep only preserves the directory.
Data Isolation
Evaluation runs must not use real user conversations or private data. They must not modify production SQLite or ChromaDB data. Where persistence is needed, tests and future live evaluation should use temporary SQLite/ChromaDB paths or explicitly injected isolated services.
Run metadata records reproducibility details such as:
- run id and run name
- dataset name and version
- execution mode
- timestamp
- Git commit when available
- Python and dependency versions
- evaluation configuration
- retrieval limits and random seed
This prepares the project for Phase 10 Part 2 datasets and later metric/reporting phases.
Test Retrieval
export OPENAI_API_KEY="your-api-key"
python -m backend.retrieval.test_retrieval "Can a Brazilian citizen work in Switzerland?"
The command prints vector results, BM25 results, and the merged candidate list with metadata and scores.
Test Reranking
export OPENAI_API_KEY="your-api-key"
python -m backend.reranking.test_reranker "Can a Brazilian citizen work in Switzerland?"
The command prints merged retrieval candidates followed by reranked results with source file, region, retrieval source, retrieval score, and rerank score.
Run Tests
.venv/bin/pytest
Установка Swiss Lawyer
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/ericp/Swiss-Lawyer-MCPFAQ
Swiss Lawyer MCP бесплатный?
Да, Swiss Lawyer MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Swiss Lawyer?
Нет, Swiss Lawyer работает без API-ключей и переменных окружения.
Swiss Lawyer — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Swiss Lawyer в Claude Desktop, Claude Code или Cursor?
Открой Swiss Lawyer на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Fetch
Web content fetching and conversion for efficient LLM usage.
AWS KB Retrieval
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
автор: modelcontextprotocolSpring AI MCP Server
Provides auto-configuration for setting up an MCP server in Spring Boot applications.
llm-analysis-assistant
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
автор: xuzexin-hzCompare Swiss Lawyer with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории ai
