chunky-monkey is an automated, containerized pipeline for scraping knowledge base articles, converting them to Markdown, and uploading them to an OpenAI Vector Store for use with an AI Assistant. The service is designed to run as a scheduled daily job (e.g., on DigitalOcean App Platform), efficiently detecting and uploading only new or updated articles.
- Scraper (
src/scraper.py): Fetches articles from a knowledge base API (e.g., Zendesk), cleans and converts them to Markdown, and saves them in thearticles/directory. - Delta Detection (
src/main.py): Computes SHA256 hashes for each Markdown file and compares them to a stored record (article_hashes.json) to detect new or updated articles. - Uploader (
src/uploader.py): Uploads Markdown files to OpenAI, creates a Vector Store, batch-attaches files, and updates the Assistant to use the new Vector Store. - Orchestrator (
src/main.py): Runs the scraper, delta detection, and uploader in sequence as a single job. - Containerization (
Dockerfile): The entire workflow is packaged in a Docker image for reproducible, portable deployment.
- Automated scraping and conversion of knowledge base articles to Markdown.
- Delta detection: Only new or changed articles are uploaded, minimizing API usage and cost.
- OpenAI integration: Files are uploaded to a Vector Store and attached to an Assistant for retrieval-augmented AI.
- Daily scheduling: Designed to run as a daily job on DigitalOcean App Platform or any container scheduler.
- Robust logging and error reporting for easy monitoring and debugging.
git clone https://github.com/your-org/chunky-monkey.git
cd chunky-monkeyCopy .env.sample to .env and fill in your secrets:
cp .env.sample .env
Required variables:
OPENAI_API_KEY— Your OpenAI API key.ASSISTANT_ID— The Assistant ID from the OpenAI platform.KNOWLEDGE_BASE_API_URL— The API endpoint for your knowledge base.KNOWLEDGE_BASE_PAGE_SIZE— (e.g.,10)- Any other variables required by your scraper.
Important:
- Do not use quotes or comments in
.envvalues. - Example:
OPENAI_API_KEY=sk-... ASSISTANT_ID=asst_... KNOWLEDGE_BASE_API_URL=https://support.optisigns.com/api/v2/help_center/en-us/articles.json KNOWLEDGE_BASE_PAGE_SIZE=10
make docker-build
make docker-runYou can run this service in a single command using Docker.
Just replace the environment variable values as needed:
docker run \
-e OPENAI_API_KEY=your-openai-api-key \
-e ASSISTANT_ID=your-assistant-id \
-e KNOWLEDGE_BASE_API_URL=https://support.optisigns.com/api/v2/help_center/en-us/articles.json \
-e KNOWLEDGE_BASE_PAGE_SIZE=1 \
-e LOG_LEVEL=INFO \
-e SCRAPE_PERIOD="* * * * *" \
your-dockerhub-username/your-image-name:latestReplace:
your-openai-api-keywith your actual OpenAI API key.your-assistant-idwith your OpenAI Assistant ID.- Adjust
KNOWLEDGE_BASE_API_URL,KNOWLEDGE_BASE_PAGE_SIZE,LOG_LEVEL, andSCRAPE_PERIODas needed for your deployment. your-dockerhub-username/your-image-name:latestwith your actual Docker image name and tag.
docker run \
-e OPENAI_API_KEY=sk-... \
-e ASSISTANT_ID=asst_... \
-e KNOWLEDGE_BASE_API_URL=https://support.optisigns.com/api/v2/help_center/en-us/articles.json \
-e KNOWLEDGE_BASE_PAGE_SIZE=1 \
-e LOG_LEVEL=INFO \
-e SCRAPE_PERIOD="0 * * * *" \
johndoe/chunky-monkey:latest| Variable | Description |
|---|---|
OPENAI_API_KEY |
Your OpenAI API key (required for uploading to Vector Store) |
ASSISTANT_ID |
The ID of your OpenAI Assistant |
KNOWLEDGE_BASE_API_URL |
Knowledge base API endpoint (e.g., Zendesk articles endpoint) |
KNOWLEDGE_BASE_PAGE_SIZE |
Number of articles per page to fetch from the knowledge base |
LOG_LEVEL |
Log level (e.g., INFO, DEBUG) |
SCRAPE_PERIOD |
Cron schedule for running the scraper (e.g., 0 0 * * * for daily) |
-
Important: If you are deploying to a cloud platform (like DigitalOcean), you must build your Docker image for the correct architecture (linux/amd64). On Apple Silicon (M1/M2) or ARM machines, use:
make docker-build-linux
or
docker build --platform linux/amd64 -t chunky-monkey .This ensures your image will run on cloud servers (which are amd64/x86_64).
-
All logs and errors will be printed to your terminal.
-
To reset the Docker image and containers:
make docker-reset
- Push your Docker image to a registry (Docker Hub or DigitalOcean Container Registry).
- Create a new App in DigitalOcean App Platform:
- Choose "Worker" or "Job" type.
- Set the image to your pushed Docker image.
- Set the command to
python src/main.py(default). - Add your
.envvariables in the App Platform UI. - Set the schedule to run once per day.
- Monitor logs in the DigitalOcean dashboard for each run.
- The service uses SHA256 hashes to detect new or updated articles.
- Hashes are stored in
article_hashes.json. - On each run:
- The scraper fetches and saves articles as Markdown.
- The orchestrator computes hashes for all
.mdfiles. - Only files with new or changed hashes are uploaded.
- The hash record is updated after a successful upload.
Note: Chunking is currently not applied in this service for three reasons:
- To keep the implementation simple and robust.
- To ensure that article URLs remain relatable and visible in every file.
- Because the files being uploaded are not large enough to require chunking at this stage.
If chunking becomes necessary in the future (for larger files or more advanced retrieval), this strategy can be revisited.
- Docker fails to run with Error 125:
- Check your
.envfile for spaces or quotes around variable names/values. - Example:
KNOWLEDGE_BASE_API_URL=https://...(no quotes, no spaces).
- Check your
- ValueError for int() conversion:
- Remove comments or extra text from environment variable values.
- Example:
KNOWLEDGE_BASE_PAGE_SIZE=10
- No connection adapters were found:
- Remove quotes from URL values in
.env.
- Remove quotes from URL values in
- OpenAI API errors:
- Check that your API key and Assistant ID are correct and valid for your organization/project.
- Review the error message printed in the logs for details.
- No new or updated articles detected:
- Ensure your scraper is fetching fresh content and saving
.mdfiles in thearticles/directory.
- Ensure your scraper is fetching fresh content and saving
chunky-monkey/
├── assets/ # Demo screenshots and visual assets
├── articles/ # Markdown files (output)
├── logs/ # Log files
├── src/ # All Python modules
│ ├── scraper.py
│ ├── markdown_converter.py
│ ├── uploader.py
│ ├── utils.py
│ ├── main.py
│ └── __init__.py
├── Dockerfile
├── requirements.txt
├── .env.sample
├── README.md
Below is a screenshot showing the Assistant correctly answering a sample question with citations:
Sample Q&A:
User: How do I add a YouTube video?
OptiBot:
To add a YouTube video in OptiSigns:
- Go to the Files/Assets page.
- Click Add Asset and select YouTube App from the app list.
- Enter the YouTube video URL.
- Set optional configurations like video title and duration.
- Click Save to add the YouTube video as an asset.
You can then use this asset in your playlists or assign it to your screens.
Article URL: https://www.optisigns.com/support/how-to-display-youtube-video-on-your-digital-signage
All screenshots and demo images are stored in the assets/ folder.
Below is a screenshot showing logs from the DigitalOcean Runtime logs interface:
Basic unit and integration tests are included to demonstrate how testing can be implemented for this project. These tests cover core utilities (such as hash computation and delta detection), as well as basic mocking for the scraper and uploader modules. The current tests are not exhaustive, but serve as a foundation for further test development and show that the codebase is testable and can be extended with more comprehensive tests as needed.
Run all tests with:
make test
make install— Set up Python virtual environment and install dependencies.make run— Run the pipeline locally (venv).make test-utils— Test the utils module.make test-scraper— Test the scraper module.make test-upload— Run the uploader script.make clean— Remove venv, cache, and generated files.make reset— Clean and reinstall everything.make reset-openai— Remove all uploaded files and vector stores from OpenAI.make reset-all— Full local and OpenAI cleanup.make docker-build— Build the Docker image.make docker-run— Run the app in Docker (requires.env).make docker-reset— Remove Docker image and stopped containers.
- Modular, extensible codebase.
- No hard-coded secrets; all config via
.env. - Designed for easy deployment and scheduling.
- Logs counts of added, updated, and skipped articles.
- Robust error handling and logging for easy debugging.
MIT License

