Project: a help-centre assistant with citations and permissions
Lesson 0 promised an assistant that answers from the help centre, cites it, respects permissions and refuses when nothing fits. Here it is.
The assistant
import os
from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.postprocessor import SimilarityPostprocessor
from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from extractive_llm import ExtractiveLLM
Settings.embed_model = HuggingFaceEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
Settings.llm = ExtractiveLLM()Local embeddings (lesson 2) and the stand-in answering model (lesson 10), set once as defaults.
def with_audience(path):
audience = "staff" if "/staff/" in path else "customer"
return {"file_name": os.path.basename(path), "audience": audience}
documents = SimpleDirectoryReader(".", recursive=True, required_exts=[".md"], file_metadata=with_audience).load_data()
for document in documents:
document.excluded_embed_metadata_keys = ["audience"]
document.excluded_llm_metadata_keys = ["audience"]
index = VectorStoreIndex.from_documents(documents)The help centre and the staff folder, each file marked with its audience, which is kept out of the embeddings and the prompt (lessons 4, 6 and 12).
ALLOWED = {"customer": ["customer"], "staff": ["customer", "staff"]}
def ask(question, role):
filters = MetadataFilters(
filters=[ExactMatchFilter(key="audience", value=a) for a in ALLOWED[role]], condition="or"
)
engine = index.as_query_engine(
similarity_top_k=2,
filters=filters,
node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.3)],
)
response = engine.query(question)
if not response.source_nodes:
return "I can't find that in our help centre. A person from the team will reply."
sources = sorted({n.metadata["file_name"] for n in response.source_nodes})
return f"{response} (sources: {', '.join(sources)})"ALLOWED maps a role to the audiences it may read: staff see everything, customers only customer documents. The filter lists each allowed audience and joins them with condition="or". The cutoff (lesson 11) turns an empty context into an honest handover to a person, and the sources become a citation (lesson 10).
for question, role in [
("How long until my refund money reaches my card?", "customer"),
("Who approves a refund over 200?", "customer"),
("Who approves a refund over 200?", "staff"),
("What are your opening hours?", "customer"),
]:
print(f"[{role}] {question}")
print(" ", ask(question, role))Run it
python assistant.pyRead the four answers against the promise. The refund question is answered from refunds.md and cited. The customer's filter never offered the staff document, so for them the model found no sentence answering the approval question and said so; the staff member got the approval rule. The opening hours question has nothing above the cutoff, so nobody is given an invented answer.
Where each piece came from
Things to add
- Use the hybrid retriever from lesson 14 and the reranker from lesson 15 inside
ask, and rerun the hit rate from lesson 16 on it. - Persist the index (lesson 17) and refresh it when files change (lesson 18).
- Replace
ExtractiveLLMwith a real model through LiteLLM or an OpenAI integration, and compare the answers.
What this course left out
| Topic | What it is for |
|---|---|
| Vector databases | Chroma, Qdrant, pgvector and others for large or shared indexes, each through an integration package. |
| Response modes | compact, refine and tree_summarize: how many model calls build one answer from many chunks. |
| Metadata extraction | Using a model to add titles, summaries or questions to chunks before embedding. |
| Chat engines | Multi-turn conversations over documents, rewriting each follow-up into a standalone question. |
| Agents and workflows | LlamaIndex's own agent and event-driven workflow layer; the agent SDK courses cover the same ideas. |
| LlamaParse and LlamaCloud | Hosted parsing and managed indexes from the LlamaIndex company. |
| Answer evaluation | Faithfulness and relevancy of answers; the Ragas and DeepEval courses teach these. |
This is what real progress feels like.