AS.

F1 RAG Chatbot

A Formula 1 chatbot that answers from race data going back to 1950, and radios the web when the data runs dry.

  • Ran on EC2, retired
  • RAG · GPT-4.1-mini · Pinecone

View the repo Skip to the lap

The F1 Race Engineer chat: the Pit Wall Radio welcome with six suggestion chips
Pit Wall Radio welcome on a phone

Screens recreated from the app’s template. Speed, gear, lap and the ticker are decoration, not data.

Ask it anything.

  1. Pick a chip, or type.

    Six suggestions to start. Enter transmits, Shift+Enter adds a line. The first visit sets a session cookie, so the engineer remembers the conversation.

    Pit Wall Radio welcome screen: six suggestion chips and the Driver Comms input
  2. Radio’s open.

    The welcome clears, the question goes to /get, and the engineer’s typing indicator runs until the answer lands.

    The question "Tyre strategy explained" sent, with the engineer typing
  3. The engineer answers.

    A real answer, and a wrong one: it puts Senna in Formula 1 in 1983, a year before his debut. It is why a test set is first on the list below.

    A real exchange: a question about Ayrton Senna and the race engineer’s answer
  4. Or the radio drops.

    If the request fails, the page says so in the engineer’s voice instead of spinning forever.

    A failed request: the engineer replies "Radio interference, no response received"

Built like a pit wall.

Its own type, five colours and a HUD. Under 500 pixels the telemetry hides and the panel goes full width.

Pit Wall Radio welcome on a phone
The Senna exchange on a phone

Pit wall radio

Bebas Neue · headers and the HUD

The engineer answers in plain sentences, the way a radio message reads back.

Rajdhani · messages

BOX BOX... ASK YOUR QUESTION

JetBrains Mono · labels, chips, the input

  • Racing redthe engineer, sends, alerts
  • Off-whitetext
  • Steellabels and time stamps
  • Goldthe driver’s messages
  • Greenlive, and good news

Speed, gear, lap and the timing ticker are there for mood. None of it is live data.

In the garage.

Retrieval first, the web when the data runs dry, one small model to write it up. Then the part that fought back: getting it onto a server.

One question, one lap.

A drawn circuit, not a real one. Sector colours follow F1 timing: green for a good sector, purple for the fastest, yellow for a slower one. The pit lane costs time because it is one more network call.
  1. Sector 1 · RetrieveThe question is embedded by all-MiniLM-L6-v2, the model that embedded the race data, and Pinecone returns the three closest chunks by cosine similarity.
  2. Sector 2 · Enough context?More than 100 characters across the three chunks: stay out. Less: box, box. SerpAPI searches the web and the results ride along with the question.
  3. Sector 3 · GenerateGPT-4.1-mini answers from the chunks, with everything said earlier in this session as history.
  4. Flag · AnswerPlain text back to the page, into the race engineer’s bubble.

The parts.

store_index.py, outside the app

Entry

  • store_index.pya script, run by hand

Work

  • Splitter9 CSVs, 1950 to 2025, cut into 500-character chunks

Models

  • MiniLM-L6-v2embeds every chunk as 384 numbers

Data

  • Pineconeserverless index, cosine, us-east-1

Nine files of race data, 1950 to 2025, cut into chunks and embedded once. After that the server never writes to the index.

The index lives in Pinecone, not in the image: the server only reads.

The pit stop.

21 GB to about 5 GB

  1. CPU-only PyTorch.The server has no GPU, so torch 2.2 comes from PyTorch’s CPU index instead of the default wheels, which bring CUDA with them.
  2. A slim base.python:3.11-slim-bookworm, plus the one system library CPU torch needs, libgomp1.
  3. No pip cache.--no-cache-dir, so the image doesn’t carry every wheel it installed.

Race control.

Every push to main goes to the host. Three jobs, one of them not pulling its weight yet.

  1. IntegrationLint and tests are placeholders: the job only prints. Real tests are on the list below.
  2. DeliveryBuildx builds the image and pushes f1-chatbot:latest to Docker Hub.
  3. DeploymentA self-hosted runner on the EC2 host stops the old container, clears images older than a day, pulls, and starts the new one with the keys as environment variables.
GitHub Actions graph for cicd.yaml: Continuous Integration, Continuous Delivery and Continuous Deployment, all passed
A real run, the push that added session memory. Integration finishes in seconds because it doesn’t test anything yet.

What broke.

  1. 7 Mar 2026 · Docker

    Works on my Mac.

    The first image built and then died on start: the Dockerfile ran flask_api/app.py, and the folder is Flask_api. macOS doesn’t care about capital letters; the Linux inside the container does. One letter fixed it, and the lesson stuck: run the container before trusting it.

    The commit, 55a4564
  2. 7 Mar 2026 · Retrieval

    The fallback that never fired.

    The first version searched the web only when retrieval came back with 3 characters or fewer. Pinecone always returns three chunks of up to 500 characters, so the search never ran and thin answers went out as if they were grounded. The bar moved to 100 characters. It is still a length check, which is why it is on the list below.

    The commit, bb3e459
  3. During a deploy · EC2

    Out of disk, mid-deploy.

    The EC2 host ran out of disk in the middle of a deploy. I attached a new EBS volume to the running server and finished it there. The deploy job clears images older than a day before every pull, and the image itself went on the diet above.

    Fixed on the server, so there is no commit to link.

What I’d do next.

  • A small set of F1 questions with known answers, run on every change. The Senna answer above would fail it: his debut was 1984.
  • Real tests in Integration, then let them gate the deploy.
  • Judge retrieval by similarity score, not by how many characters came back.
  • Keep sessions in Redis. Today they live in a Python dict: lost on every restart, never trimmed.
  • Bake the embedding model into the image. Today every new container downloads it on start.

Tech specs.

App
Python 3.11, Flask 3.1, LangChain 0.3, one HTML template
Model
GPT-4.1-mini, with this session’s history
Retrieval
Pinecone serverless (cosine, us-east-1), all-MiniLM-L6-v2 embeddings (384), top 3, SerpAPI fallback
Data
9 CSVs of race data, seasons 1950 to 2025, about 16,800 rows, 500-character chunks with 20 overlap
Image
python:3.11-slim-bookworm, CPU-only torch 2.2, from 21 GB to about 5 GB
Delivery
GitHub Actions, Docker Buildx, Docker Hub, a self-hosted runner on the host
Hosting
One EC2 t3.medium running Docker. Retired.