Project 3: docchat
In this project you will build chat, a command-line program that answers questions about the documents in a folder.
The language model can call four tools to inspect the files: ls, cat, grep, and calculate. Ask “does this project use regular expressions?” and the model can grep the code and answer from what it finds. chat uses retrieval-augmented generation, a method also used by many AI coding assistants.
Due: Wednesday, November 11 at 11:59pm, see the schedule.
Coding estimates are often too short:
Learning objectives
- understand how AI agents call and use tools
- create doctests and other project scaffolding from scratch
- maintain one project across several assignments:
- in a later lab, someone else will have to commit code to your project
- in the next project, you will extend this project with more features
- in your final project, you will use this project to write code for you
Instructions
First, your project must meet every specification in lab-more-project-setup. The lab provides the skeleton used here: chat.py, the repl loop, the GitHub Actions, and the packaging.
Note: You will write your own doctests from the specification below. Translate each requirement into concrete test cases.
The tools
Extend the Chat class with four tools the model can call.
calculateevaluates an arithmetic expression. Copy this one straight from the Groq tutorial on local tool calling; it is the worked example there.lsbehaves just like the shell’sls. It takes zero or one argument:- with no argument, list all the files in the current folder;
- with one argument, list all the files in that folder.
Hint: Use the
glob.globfunction to list the files.glob.globreturns files in an arbitrary order, so sort them asciibetically before returning them; otherwise your test cases will pass and fail at random.catopens a file and outputs its contents. It takes a single argument: the file to read.Hint: Catch any exception your code might raise. The common ones are
FileNotFoundError(the file isn’t there) andUnicodeDecodeError(the file is there but isn’t a text file). On Windows you may have to handle files encoded in both UTF-16 and UTF-8; on every other machine, UTF-8 alone should be fine.greptakes two parameters: a regex, and a path (which may contain globs). It should:- load every file that matches the glob;
- loop over every line in each file and test it against the regex;
- add each matching line to the output;
- produce no output if nothing matches.
Like
cat,grepmust not allow absolute paths or directory traversal attacks (see below).Hint:
re.searchtests whether a string matches a regex.
Calling the tools two ways
Every tool must be callable in both of the following ways.
Automatically. In the standard flow from the Groq tutorial, the model decides to call a tool while it answers.
Manually. Inside the repl, the user can type
/command param1 param2to run a tool directly. For example:chat> /ls .github workflows chat> what files are in the .github folder? There is only a `workflows` folder in the `.github` folder./ls .githubruns directly and puts its output into the model’s context. The later question can then be answered without another tool call.This “slash command” syntax avoids an API call and lets the user choose the exact tool. To make it work, modify the
replfunction so that it checks whether the first character of a line is/.Warning: Prove that the manual and automatic tool calls work with doctests and integration tests.
Keep the tools inside the folder
These tools can read files on your computer. Restrict them to the folder where chat was started. None of your tools may allow:
- reading absolute paths (paths that start with
/), or - directory traversal attacks, passing the filename
..anywhere in the path, which would let the model climb out of the project folder and read documents elsewhere.
Use a shared helper function, is_path_safe:
- It takes a single path as input and checks whether that path is absolute or contains
... - Every tool calls it first and only proceeds if it returns
True.
Coding conventions
Every function has a docstring. It contains a one-sentence English description of what the function does, and doctests that demonstrate it. (Class methods may put their doctests in the class docstring instead of having their own.)
Every class has a docstring with a two-to-three sentence English description of the class, and doctests that demonstrate it.
Every file has a docstring with a one-to-two sentence description of the file. No doctests are required at the file level.
chat.pyholds the main code for your program.Every tool lives in its own file inside a
toolssubfolder. For example, thelstool goes in a filetools/ls.py.You need over 90% code coverage from your doctests. Every tool must have 100% coverage; only the IO-performing functions in
chat.pyare allowed less than 100%.No “cheesy” doctests that don’t meaningfully test the function. For example:
def do_fancy_string_processing(input_str): ''' This function does a lot of hard, fancy string processing. >>> assert do_fancy_string_processing('this is a **super** __hard__ function to implement') '''This test hides the output of
do_fancy_string_processingwithassert. It neither documents the output nor detects changes in behavior.
Integration tests
- Your repo must include a folder named
test_projects. - Inside
test_projects, add submodules for all of your previous class projects. The three previous projects are your webpage, your markdown compiler, and your eBay scraper. A submodule is a git repo living inside another git repo; add them withgit submodule add <url>. Never clone a repo inside another repo.
Your chat program will use these projects as test inputs.
Repository organization
Hint: Describe what the program does in your README instead of presenting it as a school project.
- The repo must have no unnecessary files (for example
.DS_Storeor__pycache__). - The repo must have no
.envfile uploaded, and no hard-coded credentials anywhere else. - The repo must have a valid
requirements.txtthat lists every dependency, plus anything elsepipneeds to build the project. - The repo must have three GitHub Actions:
- doctests,
- integration tests, and
- flake8.
Note: Your lab had the doctests and integration-tests actions but not flake8. Copy the flake8 action from one of your previous assignments.
- The repo must have a
README.mdthat has:a good title (inside a
#heading);a short one-to-two sentence description of your program;
badges: one for each of your three GitHub Actions, one for PyPI, and one for code coverage;
an animated gif of your program running;
Note: The gif should show only your terminal session, not your whole VS Code window. See examples from terminalizer, terminal-demo, and vhs. If you need recording software, this dev.to post gives instructions and links.
a text-based usage example inside a code block, one for each git submodule. For example:
$ cd markdown_compiler $ chat chat> does this project use regular expressions? No. I grepped all of the python files for any uses of the `re` library and did not find any.or
$ cd ebay_scraper $ chat chat> tell me about this project The README says this project is designed to scrape product information off of ebay. chat> is this legal? Yes. It is generally legal to scrape webpages, but ebay offers an API that would be more efficient to use.Put these examples in their own section of the README, and give a one-sentence explanation of why each example is a good one.
Grading rubric
This project is worth 41 points. There are also up to 26 possible points of extra credit, so it is possible to score 67/41 on this assignment.
To earn the full 41 points, your project must satisfy every requirement in the Instructions above:
The 38 automated points come from instructor-owned pytest tests in Gradescope. Model calls are mocked and file tools run only in temporary fixtures, so grading is deterministic, offline, and does not consume your API quota. GitHub Actions are useful feedback, but their badges are not the grade.
This project uses the late schedule below instead of the usual doubling penalty. The standard two-day extension for collaboration still applies.
| Days late | Standard policy | This project |
|---|---|---|
| 1 | -1 | -1 |
| 2 | -2 | -1 |
| 3 | -4 | -2 |
| 4 | -8 | -2 |
| 5 | -16 | -4 |
| 6 | -32 | -4 |
| 7 | -64 | -8 |
| 8 | -128 | -8 |
| 9 | -256 | -16 |
Extra credit
Note: Many of these tasks cannot be covered by doctests, so completing them may lower your code coverage. Keep 100% coverage on your tool functions.
-
$ chat 'what files are in the .github folder?' The only file in this folder is the workflows subfolder $ chat 'what is this project about?' Looking at the README.md file, I see this project is an AI agent for chatting with documents. -
$ chat chat> what files are in the .github folder? The only file in this folder is the workflows subfolder chat> ^C $ chat --debug chat> what files are in the .github folder? [tool] /ls .github The only file in this folder is the workflows subfolderTo earn this, you must have both doctests and an integration test demonstrating the behavior works.
-
openai: use the latest GPT model;anthropic: use the latest Claude Opus model;google: use the latest Gemini model;groq(the default): use whichever Groq model you like best.
Note: You will need an openrouter.ai API key for this. All of your queries should cost less than a penny, so $10 of credit is more than enough.
-
Note: The
compactcommand has to create its own instance of theChatclass to do the summarizing. That second instance is technically called a subagent. -
- typing
/and pressing tab lists the supported tools; - typing
/land pressing tab completes to/ls; - typing
/ls .gand pressing tab completes to/ls .git; - typing
/ls .githand pressing tab completes to/ls .github.
For examples of how to do this, see this gist and the readline docs.
- typing
-
Note: If you complete this task, include a video in your README demonstrating the output. If you add that video, you do not also need the animated gif.
-
Note: If you complete this task, include a video in your README demonstrating the output.
Note: If you use trigger word detection instead of a keypress, you get an additional +2 points of extra credit.
Submission
Push your work to GitHub, then submit your repository and branch to the Project 3 programming assignment on Gradescope. Gradescope grades the exact commit you select; push and resubmit after each fix.
If you completed extra credit, add submission.toml at the repository root and set only the features you actually implemented:
[features]
cli_message = false
debug = false
providers = false
compact = false
tab_completion = false
images = false
text_to_speech = false
speech_to_text = false
trigger_word = false
mobile = false
# Supply these URLs only for claims that require video/deployment evidence.
trigger_word_evidence = ""
mobile_evidence = ""Unclaimed extra-credit tests are skipped and do not lower the grade.
In the Gradescope comments, also submit a one-to-two sentence explanation of what you believe your grade should be:
- if you completed any extra credit, say so;
- describe any portions of your assignment that do not work; this may earn you more lenient grading.