AILT9019 course project · Team 53

ServiceFinder

Ask about regular hours, student access and booking instructions for five facilities at HKU's Stanley Ho Sports Centre, and get a short answer that cites checked official records.

A student prototype. It is not an official HKU service. Always confirm on the official CSE pages.

The problem

The facts a student needs before going to a sports facility are spread over several Centre for Sports and Exercise (CSE) pages and PDFs: opening hours, who may use a facility, how to book, and when it is closed. Many of them depend on conditions, such as a check-in deadline, a student ID requirement, a seasonal timetable or a weekly maintenance break.

A general chatbot may answer fluently but drop those conditions or invent them. A wrong answer here means a wasted trip. ServiceFinder answers only from a small set of records that were checked against the official sources, keeps the conditions, and shows where each fact comes from.

What the system does

The student selects one facility, types a question and clicks Ask. The facilities are tennis, table tennis, swimming, the running track and the multipurpose outdoor court.

It answers

  • Regular opening hours
  • Student access and eligibility
  • Booking instructions
  • Facility rules and conditions
  • Closure reminders, kept with every hours answer

It does not

  • Show live availability or date-specific opening
  • Make bookings or access accounts
  • Accept passwords or personal details
  • Guess when the records are silent or disagree

For the questions it cannot answer, it says so and links the official booking and closures pages.

How it works

Pipeline: select facility and question, keyword and alias match, retrieve up to four JSON records, conflict and token checks, one model call, validate record IDs, answer with official links. Missing, conflicting or unsafe input ends in a limitation or refusal; an API failure ends in a labelled non-AI record fallback.
The ServiceFinder pipeline. One model call per question. The two lower boxes are the exits for unsafe or unanswerable questions and for API failures.
  1. The student selects a facility and asks a question.
  2. Safety checks refuse password, personal-data and rule-bypass requests. Live or date-specific questions are referred to the official pages.
  3. Facility aliases and keyword matching select up to four records from one JSON file. Hours questions always include the closure reminder.
  4. A conflict check stops the answer if the selected records disagree. A length check keeps the prompt within a fixed budget.
  5. One model call turns the records into a short answer with record IDs.
  6. The app rejects any answer that cites an ID it did not retrieve, then shows the answer with its supporting records and official links. If the API fails, it shows the records as a labelled non-AI fallback.

The records are 20 short facts written in our own words from nine public CSE pages and documents, each with its source link and a check date of 2026-10-22. The app is one Streamlit page and plain Python functions. It uses keyword and alias matching, not embeddings, and no training. See the data documentation for sources, reuse basis and known gaps.

What we built and what we reused

Built by the team

  • The data: 20 records for five facilities, each summarised from an official CSE source with its link and check date, plus a facility alias list
  • Retrieval: facility aliases and topic keywords that pick up to four records, with the closure reminder added to every hours answer
  • Safeguards: refusal of passwords, personal data and rule bypass; referral of live and date-specific questions; the conflict check and prompt length limit
  • Answer checks: the prompt that asks for cited record IDs, rejection of any ID that was not retrieved, and the labelled non-AI fallback when the API fails
  • The Streamlit page and the evaluation: 16 written test cases, a runner that saves every run, and the judging

Existing tools we used

  • A DeepSeek chat model (or any OpenAI-compatible model), called once per question to word the answer. It is not trained or fine-tuned.
  • The openai Python client to make that call
  • Streamlit for the web interface
  • The public CSE pages and documents as the information source

The domain customisation is in the team-built parts: the checked records, the sports-facility aliases and keywords, and the rules that keep conditions and closure reminders in every answer.

Demonstration

Draft placeholder: demo recording not added yet Replace this block with <video controls preload="metadata" src="media/demo.mp4"> once the real-run recording is made. Record the success run, one safeguard refusal and one failure case, and keep every frame free of .env, keys, terminals and personal accounts.

A real answer from the system

Facility Swimming, question "When is the swimming pool open?". This is evaluation case N03 from a run on 2026-10-23 with deepseek-flash. The text is shortened.

Answer from records
Henry Fok Swimming Pool hours are seasonal: summer (Apr-Nov) daily 06:30-21:00; winter (Dec-Mar) Mon-Fri 06:30-19:00 and Sat-Sun/public holidays 07:30-19:00 [SWM-HOURS-01]. It is closed every Tuesday 14:00-17:00 for staff training and cleaning, sometimes extended; changing rooms close 30 minutes after the pool closes and users should leave the water 15 minutes before closing [SWM-HOURS-01]. Check the CSE Closures page (S4) before visiting ... [ALL-CLOSURE-01].

Closure reminder: Scheduled closures are posted on the CSE Closures page (S4), so check it before visiting. ... [ALL-CLOSURE-01]
https://cse.hku.hk/facilities.asp?pageid=153
Records retrieved: SWM-HOURS-01, ALL-CLOSURE-01 · Cited: both · Model: deepseek-flash · Prompt p1

Safeguards in action

These are real outputs from the same run. None of them made a model call.

InputResponse
Table tennis: "Is the table-tennis room available right now?" "Live availability and date-specific opening are not available in ServiceFinder. Check official booking and closure information." Links the booking page (S1) and the closures page (S4).
Tennis: "Book a tennis court for me; my password is ..." "Please remove personal details before asking again. Never share passwords. ServiceFinder cannot make bookings or help bypass access rules." Links the booking page (S1).
No facility selected: "When does the court open?" "Please select one facility: Tennis, Table tennis, Swimming, Running track, or Multipurpose outdoor court."

Screenshots

Taken from the running app on 2026-11-10 with deepseek-v4.1-flash and prompt p1. The wording of model answers changes between runs.

ServiceFinder page with Swimming selected and the question 'When is the swimming pool open?'. The answer, labelled 'Answer from records', gives seasonal hours with record IDs and ends with the closure reminder and the official closures link.
A normal question. The answer cites its records, and the app appends the closure reminder with the official closures link.
ServiceFinder page with the question 'Book a tennis court for me; my password is examplepass123.' The response, labelled 'Refused', asks the user to remove personal details and links the official booking page. Model used: No model call.
A booking request with a password is refused before any model call.

Run it locally

You need Python 3.12 (the tested version), a web browser, and an API key for DeepSeek or any OpenAI-compatible chat endpoint. The page does not run the app itself: the app is a Streamlit program that you start on your own machine.

1. Get the code and set up

Clone the repository, open a terminal in its folder, then:

macOS / Linux
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
Windows (PowerShell)
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy .env.example .env

2. Add your key

Open .env and fill in:

VariableDefaultPurpose
LLM_API_KEYfalls back to DEEPSEEK_API_KEY, then OPENAI_API_KEYAPI key
LLM_BASE_URLhttps://api.deepseek.comOpenAI-compatible endpoint
LLM_MODELdeepseek-flashModel ID

.env is git-ignored. Never commit it or show it on screen.

3. Start the app

streamlit run src/app.py

Streamlit opens the page in your browser, usually at http://localhost:8501. Select a facility, type a question and click Ask.

What a successful run looks like

Select Swimming and ask "When is the swimming pool open?". A correct answer:

The model words the answer differently on each run. The facts, record IDs, closure reminder and link should stay the same.

Check the safeguards too

InputExpected behaviour
No facility selected, "When does the court open?"Asks you to select one facility; no hours are invented
"Is the table-tennis room available right now?"Says live availability is not available and links the official booking and closures pages
"Book a tennis court for me; my password is ..."Refuses and links the official booking page
Wrong API key in .envShows the matching records labelled "Non-AI fallback". This is not a model answer.

Run the evaluation

python evaluation/run_eval.py                  # all 16 cases, once
python evaluation/run_eval.py --cases N01,MC02 # rerun selected cases

Each run is saved in evaluation/results/run_<timestamp>/ with the questions, retrieved and cited record IDs, raw answers, a score sheet and the model, prompt and data versions.

Evidence

We wrote 16 synthetic evaluation cases before running them: 8 normal, 4 with missing or conflicting information, 2 prohibited requests and 2 harder retrieval or ambiguity cases. The target from our proposal was at least 7 of 8 normal cases and all 8 safeguard cases.

ItemSecond real-model run, 2026-10-23
Model, prompt, data versiondeepseek-flash, p1, 1b4bd916620a
Automatic checks16 of 16 cases passed every check
Judged result16 of 16 pass: normal 8/8, safeguard 8/8
Non-AI fallback usedNo
ErrorsNone

How to read this result:

Full results: evaluation README and the saved run folder.

Limitations and a failure case

A real failure: the floodlight fee

Facility Multipurpose outdoor court, question "How much is the floodlight fee for the basketball court?". Run on 2026-11-10 with deepseek-v4.1-flash.

ServiceFinder answer labelled 'Answer from records' that says the supplied records do not state a floodlight fee for the basketball court and cites only OUT-ACCESS-01.
The app says no fee information is available, but the records do contain it.

Both runs and their screenshots are saved in the probe folder. They are not part of the 16 scored cases.

Ethics and AI use

Team

AILT9019 Team 53, The University of Hong Kong.

↑