Javis OS
Javis OS is an Agentic AI that assists entrepreneurs in business tasks, helping them grow their work.
AI translation
Answers visitor questions using only content from selected PlacidWay pages, with a source link for every answer.
BusinessPython · JavaScriptGitHub @Jaffry12OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Website knowledge chatbot grounded in 7 PlacidWay pages: cited answers, code-checked prices, honest 'I don't know'. FastAPI + BM25 + Gemini.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗A chatbot that answers visitor questions only from seven PlacidWay pages (ITC Immunity Therapy Center in Tijuana: clinic profile, three treatment packages, videos, price list, plus the alternative-medicine price comparison). Every answer links the page it came from, prices are checked in code before they reach the user, and when the pages don't say something the bot says so.
python -m venv .venv && .venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements-dev.txt
copy .env.example .env # then set LLM_API_KEY (free: aistudio.google.com/apikey)
uvicorn app.main:app --reload # open http://127.0.0.1:8000
The knowledge snapshot (data/knowledge.json) is committed, so the bot works without crawling first.
| Command | What it does |
|---|---|
python -m scripts.ingest | Re-crawl the 7 pages, rebuild the snapshot, print which pages changed |
python -m scripts.run_test_questions | Ask the brief's test questions and write docs/test-results.md (--url to test a deployment) |
python -m pytest | 84 tests: robots.txt rules, extraction, chunking, a 25-question retrieval recall check, price/claim/name checks, model fallback, answer pipeline, API |
flowchart LR
subgraph Ingestion["Ingestion (CLI, admin endpoint or weekly CI)"]
F[Polite fetcher<br/>robots.txt + Crawl-delay,<br/>clear User-Agent] --> E[Extractor<br/>page content only, noise removed]
E --> C[Chunker<br/>heading-aware, ~180 words]
C --> S[(knowledge.json<br/>+ readable data/pages/*.md)]
end
subgraph Question["Each question"]
Q[User message + history] --> R[Router model<br/>language, intent,<br/>standalone English question]
R --> B[BM25 search<br/>top 8 passages]
S --> B
B --> A[Answer model<br/>JSON answer + cited source ids]
A --> G{Checks in code<br/>prices, claims,<br/>doctor names, citations}
G -- pass --> U[Answer + source links]
G -- fail --> A2[One corrected retry] --> G2{Checks} -- fail --> H[Honest fallback<br/>'I don't know' + quote form]
G2 -- pass --> U
end
app/ingest/)app/sources.py). The fetcher identifies itself
(PlacidWayAssessmentBot/1.0 ... contact: ...), retries server errors, and keeps a page's previous
copy if it can't be downloaded during a refresh.app/ingest/robots.py, RFC 9309). PlacidWay's file starts with
Allow: / and lists Disallow rules after it; Python's built-in urllib.robotparser applies the
first matching line and would allow everything. The standard says the longest matching rule wins,
so that's what this parser does. The file also sets Crawl-delay: 10: the brief asks for about one
request per second and robots.txt asks for ten seconds, so the crawler waits ten (the stricter of the
two). A full refresh therefore takes about 80 seconds.Clinic / Surgeon: Stem Health Guadalajara | Standard: $1000 | Procedure: Ozone Therapy), so a row still says what its price is for when read on
its own (without that, "$1,000 at Stem Health" was once quoted as a stem cell price). UI-only columns
such as "Chat with Center" are removed.price-social-block-ctm, so "drop anything with social in the
class" would delete the headline price. Class filters match whole class names.=$priceRange?>"). Blocks
with unrendered template code are skipped and counted.app/ingest/chunk.py)Chunks follow the page's headings and never mix two top-level sections. Small sub-sections (FAQ entries, video cards) are packed together up to ~180 words, and a table split across chunks repeats its header row. 121 chunks in total.
app/knowledge.py)BM25 keyword search, written out in about 25 lines with Lucene's IDF.
tests/test_retrieval_eval.py asks 25 realistic questions with the raw question text
(no rewriting, i.e. the worst case) and checks the answering fact is in the top 8: 25/25.app/chat.py, app/prompts.py, app/guard.py)question, medical_advice, booking,
quote, smalltalk, out_of_scope), standalone question, search keywords. Bookings, small talk and
off-topic messages skip retrieval.answered / partial / not_found / declined), cited source ids, whether the user's own
figure is supported, a medical flag and a short disclaimer.18,995, 18.995, 18 995 and Arabic-Indic digits are normalised).$50 does exist on these pages, as a consultation fee, so "it appears
somewhere" is not enough.not_found answers say so, suggest a free quote, and are logged
(unanswered.jsonl plus the server log) so PlacidWay can see content gaps.| Choice | Why |
|---|---|
| Python, FastAPI | Small, typed API; the same code runs locally and as a Vercel function |
| BeautifulSoup + lxml | Precise, testable control over a known HTML template |
| BM25 (own implementation) | See Retrieval above; zero infrastructure |
| Gemini 3.5 Flash-Lite (answers) and 3.1 Flash-Lite (router) | Free tier, multilingual, fast. Free quotas are per model (Flash-Lite about 500 requests/day; Flash only 20/day), so the two steps use different models, with automatic fallback to other Flash-Lite models when one is busy or out of quota |
| OpenAI-compatible client | Swap to Groq, OpenAI, Claude (via a compatible gateway) or a local model by changing three env vars |
| Vanilla HTML/CSS/JS front end | No build step; works on phones; right-to-left text handled with dir="auto" |
python -m scripts.ingest re-crawls, compares content hashes and reports changed / unchanged / failed.POST /api/admin/refresh with header X-Admin-Token re-crawls and hot-swaps the
knowledge base without a restart (enabled when ADMIN_TOKEN is set). GET /api/admin/unanswered
lists questions the bot couldn't answer..github/workflows/refresh.yml runs weekly: re-crawl, run the tests, commit the new
snapshot if anything changed.| Method | Path | Purpose |
|---|---|---|
POST | /api/chat | {message, history[], disclaimer_shown} → answer, status, sources, disclaimer, offer_quote, debug (retrieved passages, checks, timing) |
POST | /api/lead | Quote request {name, email, question, consent} (validated, logged, optional webhook) |
GET | /api/sources | The pages the bot knows and when they were fetched |
GET | /api/health | Status, model, chunk count, snapshot date |
POST | /api/admin/refresh | Re-crawl and hot-swap (token) |
GET | /api/admin/unanswered | Content-gap log (token) |
Input is validated (max 800 characters, history capped), and each client is limited to 20 messages
per minute to protect the free API key. No secrets live in the code: everything comes from env vars
(.env.example).
| Brief item | Where |
|---|---|
| Content ingestion from the shared URLs only | app/sources.py, app/ingest/fetch.py, app/ingest/robots.py (robots.txt incl. Crawl-delay, User-Agent) |
| Knowledge base that finds the right content | app/ingest/chunk.py, app/knowledge.py |
| Grounded answers citing source URLs | Answer prompt + citation check; links under every answer |
| Honest "I don't know" + free-quote suggestion | not_found status, fallback answer, quote button |
| Prices with currency and context | Prompt rule 3 + code price check against cited sources |
| Simple chat UI | public/ |
| Refresh | CLI, admin endpoint, weekly GitHub Action |
| Live deployment | Vercel (link above) |
| Nice to have: user's language | Router detects language; answers in it (Spanish and Arabic in the test results) |
| Nice to have: follow-ups | Router rewrites follow-ups using history ("What about in Turkey?") |
| Nice to have: medical disclaimer, not on every answer | Shown once per conversation when the topic is medical |
| Nice to have: lead capture | "Request a free quote" form → /api/lead |
| Nice to have: logging unanswered questions | unanswered.jsonl + server log + admin endpoint |
app/
sources.py the 7 allowed URLs
ingest/ fetch.py · robots.py · extract.py · chunk.py · pipeline.py
knowledge.py BM25 index, search, hot-swappable store
prompts.py router and answer prompts
chat.py route -> retrieve -> answer -> verify
guard.py money, user-claim and doctor-name checks
llm.py OpenAI-compatible client with per-model fallback
events.py unanswered-question and lead logs
main.py FastAPI app
api/index.py Vercel entry point
public/ chat page
data/knowledge.json snapshot; data/pages/*.md readable copies
scripts/ ingest.py, run_test_questions.py
tests/ pytest suite
vercel deploy --prod from the repo root. Set LLM_API_KEY (and optionally ADMIN_TOKEN,
LEAD_WEBHOOK_URL) in the project's environment variables. Static files come from public/;
/api/* is rewritten to the Python function in api/index.py, whose time limit is raised to 300 s
so an admin refresh can finish while honouring the 10-second crawl delay.
data/pages/