Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Website knowledge chatbot grounded in 7 PlacidWay pages: cited answers, code-checked prices, honest 'I don't know'. FastAPI + BM25 + Gemini.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
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/