Georgia State University — J. Mack Robinson College of Business PATH — Pathways for AI Training & Hiring CIS 4394 Agentic AI  ·  Fall 2026  ·  Dr. Xinyu Fu
03 · Hands-on lab

Give your local model an open book.

The model you installed in Week 4 has never seen this course. Today you embed the course website on your own laptop, retrieve the right passage for a question, and watch the same model go from a confident wrong answer to a cited right one. No API key, no vector database to install, nothing leaves your machine.

The one sentence to keep: RAG does not make the model smarter. It gives the model a better desk. Same brain; the right page is now open in front of it.
📦 Download the add-on: CIS4394_Week7_RAG_Addon.zip ↓: one script (7_rag_local.py), the corpus builder, and corpus/: the text of this course website, 38 pages. Unzip it into the same week5_ollama_starter folder you used two weeks ago, so the files sit next to 1_agent_raw.py. Nothing new to pip install.
Step 0 · one more download

Pull an embedding model

Your chat model turns text into more text. Retrieval needs a model that turns text into numbers: a vector whose direction encodes meaning (page 02). That is a different kind of model, and Ollama will refuse to use a chat model for it. Pull one (274 MB; it takes about a minute on campus Wi-Fi):

ollama pull nomic-embed-text

Slow connection? ollama pull all-minilm is 46 MB and works for this lab (retrieval is a little worse). If you use it, put EMBED_MODEL=all-minilm in front of every command below, the same way MODEL= works in the Week 5 README.

Step 1 · get to the folder

Same folder as Week 5

Open a terminal and move into your week5_ollama_starter folder, the one that contains 1_agent_raw.py. If it is on your Desktop:

Mac: Terminal
cd Desktop/week5_ollama_starter ls # you should see 7_rag_local.py and corpus/
Windows: PowerShell
cd Desktop\week5_ollama_starter dir # you should see 7_rag_local.py and corpus\

Forgot how to find a folder? Week 5, page 03, Step 1 walks through it. The prompt shows the folder you are standing in, if it does not say week5_ollama_starter, you are not there yet.

Step 2 · closed book

Ask the model something it cannot know

Before you give it anything, ask your model a question about this course. It has never seen the course. Watch what it does with that.

Mac
python3 7_rag_local.py ask --no-rag "When is Quiz 3 and what does it cover?"
Windows
python 7_rag_local.py ask --no-rag "When is Quiz 3 and what does it cover?"

What qwen2.5:0.5b said on the instructor's laptop:

CLOSED BOOK (qwen2.5:0.5b): Quiz 3 is scheduled for Tuesday, March 19th. It covers the material from lectures 11, 12, 13, and 14.

A date, a weekday, four lecture numbers; none of it exists. It did not say "I don't know." Write down what your model said; you will need it for the group task.

Step 3 · build the index

Cut the course into chunks, turn each chunk into a vector

First look at the cutting without embedding anything. This is the Lakeside exercise from page 04, done by code:

Mac
python3 7_rag_local.py index --dry
Windows
python 7_rag_local.py index --dry
38 files -> 505 chunks (avg 571 chars, max 1485) example chunk: [week3_index #1] This week: 📝 Quiz 1 (Weeks 1–3 foundations, 5 multiple choice, closed-book) …

Each chunk keeps its metadata, which page it came from and where on the page. That label is what lets the answer carry a citation later. Now embed for real (about 20 seconds; a progress counter runs):

Mac
python3 7_rag_local.py index
Windows
python 7_rag_local.py index
wrote course_index.json (5221 KB, 768 dims, 18.2s)
That JSON file is your vector database. 505 entries, each with its text, its page, and a list of 768 numbers. Searching it means comparing the question's 768 numbers against every entry's 768 numbers and keeping the closest: about 30 lines of plain Python in the script, no library. Real vector databases (Chroma, pgvector, Pinecone) do exactly this, plus an index structure so it stays fast at a hundred million entries instead of 505. The idea does not change. Open the file in a text editor if you want to see it.
Step 4 · retrieval only

Look at what gets retrieved before anyone generates anything

Most RAG failures are retrieval failures, and you can only see them if you look at this step on its own. search embeds your question, finds the three closest chunks, and stops: no model, no answer.

Mac
python3 7_rag_local.py search "When is Quiz 3 and what does it cover?"
Windows
python 7_rag_local.py search "When is Quiz 3 and what does it cover?"
[1] score=0.694 source=week2_evaluate #4 📝 Quiz 1 next week — in class, closed-book, 5 multiple-choice. Scope: ALL assigned readings from Weeks 1–3 … [2] score=0.680 source=week7_exercises #26 Next week: Evaluation & Reliability — … Quiz 3 is next week — in class, closed-book, 5 multiple-choice. Scope: Weeks 6–8 readings … [3] score=0.674 source=index #12

Read the top hit. It is about Quiz 1. The right chunk is second, by 0.014. An embedding encodes meaning, and "quiz next week, closed-book, five multiple-choice" means almost the same thing whether the number is 1 or 3. Numbers are weak signals in this geometry. Keep that in mind for Step 6.

Step 5 · open book

Same question, same model, three passages on the desk

ask runs the retrieval you just saw, pastes the three chunks into the prompt as numbered passages, and tells the model to answer only from them and cite the number.

Mac
python3 7_rag_local.py ask "When is Quiz 3 and what does it cover?"
Windows
python 7_rag_local.py ask "When is Quiz 3 and what does it cover?"

With a 3B reader (llama3.2, 2 GB), the instructor's laptop gets this every time:

OPEN BOOK (llama3.2): Quiz 3 is next week, in-class, closed-book, and covers the weeks 6-8 readings (MCP/A2A LinkedIn courses, the RAG course, HF unit, and the Agents Companion whitepaper). [2]

Nothing about the model changed between Step 2 and now. The desk changed. And it cited [2], the chunk that was ranked second, because the model, not the ranking, decided which passage answered the question.

If your model is qwen2.5:0.5b, expect a rougher ride. Run it five times and count:
python3 7_rag_local.py ask --runs=5 "When is Quiz 3 and what does it cover?"
On the instructor's laptop the 0.5B model got the scope right in 3 of 5 runs and said "Week 3" in the other two. The desk is the same desk. The 0.5B model is a weaker reader: it finds the passage and still garbles it. Counting runs like this is next week's entire topic (pass^k). If you want the reliable version, ollama pull llama3.2 (2 GB) and put MODEL=llama3.2 in front of the command (Windows PowerShell: $env:MODEL="llama3.2"; python 7_rag_local.py ask …).
Step 6 · not in the book

Ask something the corpus cannot answer

Mac
python3 7_rag_local.py ask "Who teaches CIS 4394 in Spring 2027 and in which room?"
Windows
python 7_rag_local.py ask "Who teaches CIS 4394 in Spring 2027 and in which room?"
OPEN BOOK (llama3.2): I don't have any information about the specific instructor or room for CIS 4394 in Spring 2027. The course schedule or faculty information is not provided in the passages. OPEN BOOK (qwen2.5:0.5b), two of five runs: Saeed Rand-Hendriksen teaches CIS 4394 in Spring 2027. The passage number is 5. Stanley Williams teaches CIS 4394 in Spring 2027.

Look at the 0.5B answers. Rand-Hendriksen is a real name. It is the author of next week's LinkedIn Learning course, sitting in one of the retrieved passages. The model took a real name from the desk and stapled it to your question. RAG does not end hallucination; it changes its raw material. The only reason the bigger model said "not provided" is that the prompt told it that answer was allowed: the same abstention lesson as the cannot tell option in Week 5. If you do not write the exit into the instructions, the model has no exit.

Step 7 · break it on purpose

Two knobs you own, and what each one breaks

Retrieval has settings, and the settings are yours, not the model's. Change one at a time. (Windows PowerShell: set the variable first, e.g. $env:TOP_K="1", then run the command without the prefix.)

Knob 1 · how many passages
TOP_K=1 python3 7_rag_local.py ask "When is Quiz 3 and what does it cover?"
RETRIEVED (top 1): [1] week2_evaluate #4 ← the Quiz 1 chunk There is no mention of Quiz 3 in the passage. [1]

The right chunk was ranked second, so with one slot it never reached the desk. The model did the honest thing and still failed you. Retrieval quality is destiny: the generator cannot recover a passage it was never shown.

Knob 2 · how big the chunks are
CHUNK_CHARS=3000 INDEX_FILE=big.json python3 7_rag_local.py index CHUNK_CHARS=3000 INDEX_FILE=big.json python3 7_rag_local.py ask "When is Quiz 3 and what does it cover?"
RETRIEVED (top 3): week5_summary #3 · index #1 · week3_exercises #2 Quiz 3 covers Weeks 5-6 readings, which includes the topic of Tool Use & Function Calling. [1]

Chunks four times bigger carry four times more unrelated text, so every vector points in a vaguer direction and the Quiz 3 chunk falls out of the top three. The model then answered from the wrong page: wrong, confident, and with a citation. A citation proves the model read the passage. It does not prove the passage was the right one.

Concept check

Three questions before the group task

1 · Between Step 2 and Step 5, what changed: list everything that did and everything that did not.

Did not change: the model, its weights, its training data, its reasoning ability. Did change: the prompt; it now contains three passages and an instruction to answer only from them and cite. That is the whole of RAG: a prompt-construction step placed before generation. The model learned nothing; it was handed the right page. This is why RAG is the cheap way to give a model access to private or recent documents, and why it is useless when the problem is the model's reasoning rather than its knowledge.

2 · In Step 7, Knob 1 produced an honest "no mention of Quiz 3." Where is the bug: in the model, the retriever, or the prompt?

The retriever. The embedding ranked the Quiz 1 chunk above the Quiz 3 chunk because, as vectors, they mean nearly the same thing; with TOP_K=1 only the wrong one was shown. The model behaved correctly on what it was given and the prompt did its job. Fixes live at the retrieval layer: a larger k, chunk metadata the query can filter on ("week 7"), a keyword match combined with the vector match (hybrid search), or a re-ranking step. Blaming the model here would send you to a bigger model that would fail the same way.

3 · Knob 2 produced a wrong answer with a citation. In an HR or legal deployment, why is that outcome worse than Step 2's hallucination?

Step 2's answer looks like a guess and a reader treats it as one. Knob 2's answer arrives with the costume of evidence (a passage number that really exists), so the reader's skepticism switches off. The citation is accurate about what the model read and silent about whether it should have read that. In practice this is the failure the Lakeside case on page 04 is built around: a confidently wrong policy answer with a source attached. It is why production systems show the retrieved passage next to the answer, keep metadata like version and date, and measure retrieval precision separately from answer quality.
👥 Group task · Pairs · ~20 minutes

Pick three questions about this course whose answers you can verify on the website: one easy (the answer sits in one place), one hard (the answer is spread over two pages or two weeks), and one trap (sounds like a course question but is not answered anywhere). For each, run ask --no-rag, search, and ask, and fill one row:

questionclosed book saidright chunk in top 3?open book saidverdict: right · wrong · abstained
easy
hard
trap

Produce: the table, plus one sentence each on (1) which failure you saw (retrieval, reading, or abstention) and how you know, and (2) the single setting change you would try first for the hard question. Bring your trap question; we will run the best ones on the projector.

A strong pair uses the search output as evidence, not the final answer: "the right chunk was ranked 4th, so this is a retrieval failure; raising k to 5 is the first thing to try" or "the right chunk was ranked 1st and the 0.5B model still answered 'Week 3', so this is a reading failure: a bigger reader, not a bigger k." For the trap question, a strong pair checks whether the model abstained or stitched (a real name from the passages attached to a false claim) and notes that only the abstention instruction in the prompt made the honest answer possible. Weak pairs report only what the final answer said.
🎯 Take it to your final project

Your corpus is your project's documents

Replace corpus/ with the text your agent needs (a product catalog, a policy manual, past tickets) and 7_rag_local.py is a working retrieval tool for your capstone with no API bill. Then make retrieval a tool the agent calls when it decides it needs to (the agentic RAG pattern on page 02): the retrieve() function in this script, registered the way calculator was in Week 5's 1_agent_raw.py. Three decisions are yours and will be graded: how you chunk, how many passages you pass, and what the model is told to do when the passages do not contain the answer.

Troubleshooting

If something goes wrong

You seeWhat it meansFix
This server does not support embeddingsYou are embedding with a chat model. Ollama will not do that.ollama pull nomic-embed-text, and do not set EMBED_MODEL to your chat model.
index was built with X, but EMBED_MODEL=YYou changed embedding models after building the index. Vectors from two models are not comparable.Run index again.
No such file or directory: corpusYou are not in the week5_ollama_starter folder, or the add-on was unzipped somewhere else.Read the prompt; cd to the folder that contains both 1_agent_raw.py and corpus/.
No module named ollamaPython cannot see the package.pip install -r requirements.txt (Week 5, Step 1).
The answer is just [2] or one wordA very small model latched onto the format instruction.Run with --runs=5 and count; or use MODEL=llama3.2.
Embedding is slowOlder laptop, or another model is loaded in memory.Use all-minilm; close other Ollama sessions.
← Previous02 · RAG