Aug 12 - Fix AI support bugs

This commit is contained in:
2026-08-12 09:27:15 -04:00
parent 59338f678d
commit 3209a0c717
3 changed files with 169 additions and 23 deletions
+86 -23
View File
@@ -101,9 +101,20 @@ to third parties. They download the app, complete the enrollment form, and they
ready to go.
Such a person is enrolled with an inspecting role of their own (rather than the \
read-only customer portal role), scoped to that customer's own contracts and facilities. \
Enrollment is the JQC Enrollment Form, reachable from the About Us page ("Enroll More \
People"), where you list each person, their role, and what they should be able to do. \
Each person then receives an email invitation to set up their own username and password.
See ENROLLING MORE PEOPLE below for the form.
=== ENROLLING MORE PEOPLE (adding users) ===
To add colleagues to JQC, use the JQC Enrollment Form:
https://jqc.ltservicesinc.com/enrollment
It is also linked from the About Us page in the portal ("Enroll More People"). No login \
is needed to fill it in, so it can be forwarded to anyone who needs to be set up.
On the form you list each person (name, job title, email and the role they need), tick \
what each of them should be able to do, and say who should get the mobile app. After \
submitting, the requester receives a confirmation email with a reference number, and \
each person listed then receives their own email invitation to choose a username and \
password.
When a customer asks how to add a user, get the app, or give someone inspecting access, \
give them this URL. It is the correct link — do not alter it or invent another one.
=== GETTING HUMAN HELP ===
If the customer needs something this chat can't resolve — an access/login problem, a \
@@ -113,11 +124,16 @@ opens a request that the provider's admin team answers by email and in "My Reque
=== STYLE & RULES ===
- Be concise, warm, and practical. Prefer short paragraphs or numbered steps.
- Ground answers in the features above. If you are not sure or the app may differ, say \
so honestly rather than guessing — and suggest "Submit to Support".
- Ground answers in everything above, INCLUDING the "ADDITIONAL KNOWLEDGE" section when \
one is present — that section is curated by the JQC team and is authoritative. If it \
answers the question, use it. If you are not sure or the app may differ, say so \
honestly rather than guessing — and suggest "Submit to Support".
- When the knowledge above contains a link (URL), email address or exact wording, quote \
it EXACTLY as written. Repeating something given to you here is not inventing — do it \
freely. Never alter a URL, shorten it, or replace it with a description.
- NEVER invent specific staff names, contract prices, cleaning schedules, phone numbers, \
facility data, or scores. You do not have access to the customer's live data — guide \
them to where to find it in the portal instead.
facility data, or scores that are NOT given above. You do not have access to the \
customer's live data — guide them to where to find it in the portal instead.
- Do not claim to perform actions yourself; explain where in the portal the customer does it.\
"""
@@ -166,30 +182,55 @@ def _redact_pii(text):
return redacted
#: The curated knowledge is spliced in immediately BEFORE this heading, not
#: appended to the end of the prompt. The rules under it say "ground answers in
#: everything above", so knowledge appended after them was, by the prompt's own
#: instruction, out of scope — which is exactly why admin KB entries appeared to
#: be ignored. Keep this marker in sync with the heading in _SYSTEM_PROMPT.
_STYLE_MARKER = '=== STYLE & RULES ==='
def _system_prompt_with_kb():
"""Return the base system prompt plus all ACTIVE admin knowledge entries
(phase38), so staff can curate the chatbot's knowledge without code changes.
Best-effort — a KB failure never breaks the chat."""
prompt = _SYSTEM_PROMPT
"""Return the base system prompt with all ACTIVE admin knowledge entries
(phase38) spliced in, so staff can curate the chatbot's knowledge without
code changes. Best-effort — a KB failure never breaks the chat."""
try:
entries = (SupportKnowledge.query
.filter_by(active=True)
.order_by(SupportKnowledge.sort_order.asc(), SupportKnowledge.id.asc())
.all())
if entries:
parts = ["\n\n=== ADDITIONAL KNOWLEDGE (curated by the JQC team; "
"treat as authoritative and prefer it over general guesses) ==="]
total = 0
for e in entries:
block = f"\n\nTopic: {e.title}\n{e.content.strip()}"
if total + len(block) > _KB_MAX_CHARS:
break
parts.append(block)
total += len(block)
prompt += ''.join(parts)
if not entries:
logger.info('SUPPORT | KB | no active entries — base prompt only')
return _SYSTEM_PROMPT
parts = ['=== ADDITIONAL KNOWLEDGE (curated by the JQC team; authoritative — '
'prefer it over general guesses, and quote any link in it exactly) ===']
total = 0
used = 0
for e in entries:
block = f"\n\nTopic: {e.title}\n{e.content.strip()}"
if total + len(block) > _KB_MAX_CHARS:
logger.warning('SUPPORT | KB | %d of %d entries dropped — %d char cap '
'reached', len(entries) - used, len(entries), _KB_MAX_CHARS)
break
parts.append(block)
total += len(block)
used += 1
kb_block = ''.join(parts)
idx = _SYSTEM_PROMPT.find(_STYLE_MARKER)
if idx == -1: # marker renamed — fall back to append
logger.warning('SUPPORT | KB | style marker not found; appending at end')
prompt = f'{_SYSTEM_PROMPT}\n\n{kb_block}'
else:
prompt = (f'{_SYSTEM_PROMPT[:idx]}{kb_block}\n\n{_SYSTEM_PROMPT[idx:]}')
logger.info('SUPPORT | KB | %d/%d entries injected (%d chars), prompt=%d chars',
used, len(entries), total, len(prompt))
return prompt
except Exception as exc:
logger.warning('SUPPORT | knowledge-base load failed: %s', exc)
return prompt
return _SYSTEM_PROMPT
# ── Customer chat page ────────────────────────────────────────────────────────
@@ -571,6 +612,28 @@ def admin_knowledge():
entries=entries, groq_ready=groq_ready)
@bp.route('/admin/knowledge/preview')
@login_required
@supervisor_required
def admin_knowledge_preview():
"""Show the exact system prompt the chatbot receives, knowledge included.
Added after admin entries appeared to be ignored: without this there is no
way to tell "my entry never reached the prompt" from "the model saw it and
chose not to use it". Read-only, builds nothing of its own — it calls the
same _system_prompt_with_kb() the chat endpoint calls.
"""
prompt = _system_prompt_with_kb()
active_count = SupportKnowledge.query.filter_by(active=True).count()
total_count = SupportKnowledge.query.count()
return render_template('support/admin_knowledge_preview.html',
prompt=prompt,
active_count=active_count,
total_count=total_count,
kb_included='=== ADDITIONAL KNOWLEDGE' in prompt,
kb_cap=_KB_MAX_CHARS)
@bp.route('/admin/knowledge/new', methods=['GET', 'POST'])
@login_required
@supervisor_required