Download test_knowledge_base.py from BeiingHuman/bh_support_doc_chatbot: direct link, hf CLI and curl.
- Browser
- Download file 5.79 kB
-
https://huggingface.co/spaces/BeiingHuman/bh_support_doc_chatbot/resolve/main/test_knowledge_base.py
- Command line
-
hf download hf://spaces/BeiingHuman/bh_support_doc_chatbot/test_knowledge_base.py
-
curl -L -o test_knowledge_base.py https://huggingface.co/spaces/BeiingHuman/bh_support_doc_chatbot/resolve/main/test_knowledge_base.py
5.79 kB
| """ | |
| Probe tests for the chatbot's knowledge base. | |
| Runs real questions through app.respond() — the same code path the Space serves — | |
| so the system prompt, the knowledge base and the answer are exactly what a user | |
| would get. send_email_if_needed is stubbed out, so no unanswered-question mail is | |
| sent to the support alias; instead, a probe FAILS when the real code path *would* | |
| have sent one. That is the signal worth testing: every question the bot cannot | |
| answer becomes an email to rishi@, support@ and ubaid@. | |
| A probe WARNs when the bot answers but never mentions a term a correct answer has | |
| to contain — that catches an answer about the wrong feature. | |
| pip install -r requirements.txt | |
| HF_TOKEN=... python test_knowledge_base.py | |
| Exit code is non-zero if anything failed or warned. | |
| """ | |
| import os | |
| import sys | |
| REPO = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, REPO) | |
| if not os.environ.get("HF_TOKEN"): | |
| env = os.path.join(REPO, ".env") | |
| if os.path.exists(env): | |
| with open(env) as handle: | |
| for line in handle: | |
| if line.startswith("HF_TOKEN"): | |
| os.environ["HF_TOKEN"] = line.split("=", 1)[1].strip().strip('"').strip("'") | |
| if not os.environ.get("HF_TOKEN"): | |
| sys.exit("HF_TOKEN not set (environment or .env)") | |
| import app # noqa: E402 — imported after HF_TOKEN so the client picks it up | |
| # Never mail the support alias from a test run — but keep the REAL decision | |
| # logic. send_email_if_needed runs after every answer and decides internally | |
| # whether to send, so stubbing that function would record every question rather | |
| # than every alert. Stub the SES client underneath it instead: what lands here | |
| # is exactly what production would have mailed. | |
| would_have_emailed = [] | |
| class _RecordingSES: | |
| def send_email(self, **kwargs): | |
| would_have_emailed.append(kwargs) | |
| app.ses = _RecordingSES() | |
| # (question, a substring a correct answer must contain) | |
| PROBES = [ | |
| # --- file types (KB 5.7) --- | |
| ("Can I email an iPhone photo of an invoice?", "yes"), | |
| ("Why was my HEIC photo rejected when I dragged it into the web app?", "email"), | |
| ("Can I send an invoice as an Excel file?", "pdf"), | |
| ("What file types can Beiing Human read?", "png"), | |
| # --- pay control (KB 6.2, 6.5) --- | |
| ("What is the Pay Control column?", "erp"), | |
| ("Why can't I see the Pay Control column?", "foundation"), | |
| ("How do I set the pay control on several invoices at once?", "assign"), | |
| ("Can I bulk assign pay control on Vista?", "foundation"), | |
| ("A pay control name is missing from the list. Why?", "sync"), | |
| # --- annotation (KB 7.8) --- | |
| ("How do I put a TAX EXEMPT stamp on an invoice?", "annotate"), | |
| ("What stamps can I use on a document?", "void"), | |
| ("Can I write a job number on the invoice image?", "note"), | |
| ("How do I remove a stamp I already saved?", "again"), | |
| # --- mobile --- | |
| ("Is there a mobile app?", "android"), | |
| ("Where do I download the Beiing Human app?", "play"), | |
| ("Why can't I log into the mobile app?", "accountant"), | |
| ("Can our AP admin use the phone app?", "owner"), | |
| ("How do I upload a receipt from my phone?", "upload file"), | |
| ("I don't see the Create PO button on my phone, why?", "sign"), | |
| ("Why is there no approve button on the To Review list in the app?", "details"), | |
| ("The app says fix 3 issues before approving. What do I do?", "validation"), | |
| ("Can I reject an invoice on mobile without leaving a comment?", "comment"), | |
| ("Does the mobile app send push notifications?", "not"), | |
| ("How do I get out of the Update Required screen?", "store"), | |
| ("Can I edit an exported document on my phone?", "read-only"), | |
| ("How do I answer a question assigned to me in the app?", "reply"), | |
| ("Can I search for a specific invoice in the mobile app?", "web"), | |
| ("Can I match an invoice to a PO on my phone?", "web"), | |
| ("Why did my app log me out?", "session"), | |
| # --- web app regressions --- | |
| ("What does the INYA report show?", "approv"), | |
| ("How do I use the pull back feature?", "pull back"), | |
| ("What is a Sub Admin and what can they do?", "admin"), | |
| ("How do I attach a delivery ticket to an invoice?", "match"), | |
| ("What's the difference between Approved and Verified statuses?", "api"), | |
| ("How do I trigger the document splitter?", "split"), | |
| ("Why is a vendor missing from the COI tracker?", "active job"), | |
| ("Can I bulk delete documents?", "does not"), | |
| ] | |
| def ask(question): | |
| """Drive the Space's own respond() generator and return the final answer.""" | |
| answer = "" | |
| for chunk in app.respond(question, [], "", 2000, 0.3, 0.95, None): | |
| answer = chunk | |
| return answer | |
| def main(): | |
| print(f"Knowledge base: {len(app.doc_context):,} chars\n") | |
| if len(app.doc_context) < 2000: | |
| sys.exit("Knowledge base looks empty or failed to load") | |
| problems = 0 | |
| for question, expect in PROBES: | |
| before = len(would_have_emailed) | |
| try: | |
| answer = ask(question) | |
| except Exception as e: | |
| print(f"ERROR {question}\n {e}") | |
| problems += 1 | |
| continue | |
| emailed = len(would_have_emailed) > before | |
| if emailed: | |
| verdict, note = "FAIL", "unanswered -> would have emailed the support alias" | |
| elif expect not in answer.lower(): | |
| verdict, note = "WARN", f"answered but never mentions '{expect}'" | |
| else: | |
| verdict, note = "PASS", "" | |
| if verdict != "PASS": | |
| problems += 1 | |
| print(f"{verdict} {question}") | |
| if note: | |
| print(f" {note}") | |
| print(f"\n{len(PROBES) - problems}/{len(PROBES)} clean") | |
| return 1 if problems else 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |