Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PDKScribe 🏭

A domain-expert assistant for semiconductor fab/process and physical-verification engineers: answers questions over process specs, design-rule manuals, and EDA documentation, and drafts ICV/PXL-style rule-check pseudocode. Built on public PDK/EDA data so it's fully shareable, and designed to be re-pointed at private client documentation for real fab/EDA-vendor work.

⚠️ LLM-generated answers can make mistakes. PDKScribe grounds every answer in retrieved sources and runs a citation guard that flags weakly grounded answers needs_human_review, but this is a heuristic, not a guarantee β€” always verify citations before acting on a design-rule value or a generated rule snippet, especially anything safety- or mask-critical.

Architecture

 query
   β”‚
   β–Ό
 router.py            LLM classifies into one of three routes
   β”‚                  (structured output + keyword-heuristic fallback)
   β–Ό
 retriever.py          hybrid retrieval: dense (Qdrant) + BM25, fused by
   β”‚                   reciprocal rank (LlamaIndex QueryFusionRetriever)
   β–Ό
 generators/            conceptual_qa.py   β†’ grounded prose explanation
   <route>               rule_lookup.py     β†’ grounded numeric rule value
                          rule_snippet.py    β†’ validated Pydantic spec, then
                                               deterministically rendered
                                               ICV/PXL pseudocode
   β”‚
   β–Ό
 guardrails.py          citation guard: cross-checks cited source_ids
   β”‚                    against what was actually retrieved β†’
   β”‚                    needs_human_review flag + reason
   β–Ό
 memory.py              per-session conversation history (file-backed),
                        replayed into the next turn's prompt
  • Router (router.py): classifies a query into conceptual_QA, rule_lookup, or snippet_generation via a structured LLM call (llm/structured.py β€” JSON validated against a Pydantic schema, one retry on failure); falls back to a keyword heuristic if the LLM call fails outright, so the very first pipeline step never hard-fails.
  • Retrieval (retriever.py + ingest/pdk_loader.py): a small, real, permissively-licensed corpus (see Datasets below) is chunked and indexed into an embedded on-disk Qdrant collection (dense leg) plus a BM25 index over the same nodes (sparse leg); QueryFusionRetriever combines both with reciprocal-rank fusion. No LLM call is needed for retrieval itself.
  • Generation: conceptual_qa.py / rule_lookup.py are prompted to cite every claim inline as [source_id]; rule_snippet.py only asks the LLM for the semantic fields of a rule (schemas.RuleSnippetSpec) and deterministically renders the actual ICV/PXL syntax in code, so the emitted pseudocode is always well-formed regardless of the model's grasp of runset syntax. Pseudocode is explanatory only β€” never executed against a real EDA tool.
  • Citation guard (guardrails.py): cross-references cited source_ids against what was actually retrieved. A fabricated citation, low citation coverage, or a safety-critical route (rule_lookup / snippet_generation) producing zero citations all set needs_human_review=True with a reason.
  • Memory (memory.py): one JSON file per session under pdkscribe_sessions/, replayed as a "prior conversation" block so follow-ups ("and for metal2?") resolve correctly.
  • Serving: api.py (FastAPI: POST /chat, GET /health) + app.py (Streamlit chat UI, calls the API over PDKSCRIBE_API_BASE_URL).
  • Cloud: infra/azure/main.bicep provisions an Azure Container App (running api.py) + an Azure Blob Storage container for the corpus, with a managed identity granted Storage Blob Data Contributor so cloud/blob_store.py needs no stored secret. LLM backend in cloud mode is claude or azure_foundry (an Azure AI Foundry model deployment β€” Foundry endpoints are OpenAI-compatible, so azure_foundry reuses the same client plumbing as the local backends).

LLM backends

Backend Where Notes
vllm (default) Local Any OpenAI-compatible vLLM server you run yourself
ollama Local Any Ollama server you run yourself (ollama serve)
llamacpp Local Any OpenAI-compatible llama.cpp server you run yourself
claude Cloud Anthropic API, requires PDKSCRIBE_ANTHROPIC_API_KEY
azure_foundry Cloud Azure AI Foundry model deployment, requires a base URL + API key

All five expose the same LLMClient.generate(system_prompt, user_prompt) interface (llm/client.py); structured outputs (router classification, rule snippets) are layered on top by prompting for JSON and validating against a Pydantic schema (llm/structured.py), so they're interchangeable regardless of which one has native JSON/tool-calling support.

Datasets (public, permissively licensed)

ingest/pdk_loader.py fetches and caches these on first run (offline-safe afterward):

Source License Content
SkyWater SKY130 PDK docs Apache-2.0 GDS layer reference + design-rule tables (metal1/2, via, licon, li, poly, nwell, diff/tap)
ASAP7 predictive PDK BSD-3-Clause 7nm FinFET process overview
arXiv abstracts Open access A few open-source-silicon / open-EDA papers, fetched live via the arXiv API

unstructured partitions each cached file (CSV/Markdown) into elements, which are re-joined into LlamaIndex Documents carrying source_id / title / source_url metadata β€” the citation trail every retrieved chunk and generated answer traces back to. If a source can't be fetched (no network on first run), it's skipped with a warning rather than failing the whole corpus build.

To point PDKScribe at private client documentation instead, add sources to ingest/pdk_loader.py (or drop files directly into PDKSCRIBE_CORPUS_DIR) following the same CorpusSource pattern.


Quick Start β€” Local

1. Install PDKScribe

cd PDKScribe
python3.12 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # defaults work for local vLLM mode

unstructured[md] needs the system package libmagic1 for reliable file-type detection: sudo apt-get install -y libmagic1 (it still works without it, just noisier).

This project was developed and tested against Python 3.12 β€” the retrieval stack (llama-index-vector-stores-qdrant, qdrant-client, sentence-transformers) lagged behind Python 3.14 wheel availability at the time of writing. Use python3.12 -m venv venv even if a newer interpreter is also installed.

2. Start the LLM backend

start_pdkscribe.sh assumes the backend is already running β€” it only checks reachability and warns if it isn't. Start one first:

vllm serve Qwen/Qwen2.5-14B-Instruct-AWQ --port 8000   # vLLM (default backend)
# or: ollama serve                                      (Ollama)
# or: your own llama.cpp server on port 8080             (llama.cpp)
# or nothing, if using --backend claude / --backend azure_foundry (just set the API key in .env)

3. Start PDKScribe

./scripts/start_pdkscribe.sh
# First run: downloads the corpus (~30s).

Open http://localhost:8501 and ask something like "What's the minimum metal1 spacing in SKY130?" or "Write a DRC check for poly-to-diff spacing." Stop everything with ./scripts/stop_pdkscribe.sh (your LLM server is left running β€” stop it separately if you no longer need it).

Other local backends

./scripts/start_pdkscribe.sh --backend ollama      # after starting `ollama serve`
./scripts/start_pdkscribe.sh --backend llamacpp    # after starting your llama.cpp server

Pre-fetch the corpus without touching the LLM

python -m pdkscribe.ingest.pdk_loader

Cloud Deployment β€” Azure Container Apps + Blob Storage

cd infra/azure
az deployment group create \
  --resource-group <rg> \
  --template-file main.bicep \
  --parameters containerImage=<registry>/pdkscribe:latest \
               llmBackend=claude anthropicApiKey=<key>

This provisions a Container App running api.py, a Storage Account + Blob container for the corpus, and a user-assigned managed identity granted Storage Blob Data Contributor on that container (no storage secret needed inside the app β€” cloud/blob_store.py authenticates via DefaultAzureCredential). Set PDKSCRIBE_RUN_MODE=cloud and call pdkscribe.cloud.blob_store.upload_corpus() / download_corpus() to sync the corpus to/from Blob Storage.

To use an Azure AI Foundry model deployment instead of Claude, deploy the model via the Foundry portal/CLI first (not a Bicep resource this template manages), then pass llmBackend=azure_foundry, azureFoundryBaseUrl, and azureFoundryApiKey.

This template is written to be deploy-ready but has not been applied against a real Azure subscription as part of this repository.


Testing

source venv/bin/activate
pytest tests/ -v

Unit tests use a stub LLM client (tests/fixtures/stub_llm.py) and a fake retriever β€” no network, GPU, or vector index build required. This covers the router (heuristic + structured-output + fallback), the citation guard, the rule-snippet renderer, conversation memory, and the full agent pipeline end-to-end with mocked LLM/retrieval. tests/test_ingest.py exercises the corpus fetch/cache logic with requests.get monkeypatched, so it passes identically with or without internet access.

Tutorial

tutorial/ has a set of Jupyter notebooks that walk through the codebase module by module β€” corpus ingestion, hybrid retrieval, schemas & LLM backends, the router, generators & citation guard, the agent pipeline & memory, and the API/UI/cloud layer. See tutorial/README.md to get started.

Project layout

pdkscribe/
  config.py             Settings (pydantic-settings): run mode, LLM backend, Qdrant, citation guard
  schemas.py              Pydantic models: RouteClassification, RuleSnippet(Spec), Citation, AgentAnswer
  llm/client.py             LLMClient ABC + VLLMClient/OllamaClient/LlamaCppClient/ClaudeClient/AzureFoundryClient
  llm/structured.py          generate_structured(): JSON-mode prompt + Pydantic validation + 1 retry
  router.py                 classify_query(): structured LLM output, keyword-heuristic fallback
  retriever.py                hybrid BM25 + dense retrieval (LlamaIndex + Qdrant + sentence-transformers)
  ingest/pdk_loader.py         fetches/caches/parses the public corpus (unstructured)
  generators/                   conceptual_qa.py, rule_lookup.py, rule_snippet.py
  guardrails.py                  citation guard + needs_human_review
  memory.py                       file-backed per-session conversation history
  agent.py                         orchestrates router β†’ retrieve β†’ generate β†’ guard β†’ memory
  api.py                            FastAPI: POST /chat, GET /health
  app.py                             Streamlit chat UI
  cloud/blob_store.py                Azure Blob corpus sync (cloud mode)
infra/azure/main.bicep       Container App + Blob Storage + managed identity
scripts/                     start_pdkscribe.sh, stop_pdkscribe.sh
tests/                       pytest suite + fixtures (stub LLM, no network/GPU needed)
tutorial/                    Jupyter notebooks walking through the codebase module by module

(data/corpus/, data/qdrant_storage/, and pdkscribe_sessions/ are gitignored runtime data, created on first run.)

License

Apache License 2.0 β€” see LICENSE.

About

Citation-grounded RAG assistant for semiconductor PDK/EDA engineers. Answers design-rule questions and drafts DRC/LVS snippet pseudocode, grounded in public PDK docs with a built-in citation guard.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages