feat(fusion_helpdesk_central): findings-first wizard, explicit Generate button
The old flow fired OpenAI on wizard open with just ticket + chatter,
so the AI summary was just a paraphrase of what the user originally
reported — your engineering analysis (scope, limitations, recommended
approach) never made it to the owner. Restructure to a two-step flow:
1. Open wizard → empty findings + empty summary, NO OpenAI call
2. You write findings: scope / effort / approach / risk
3. Click 'Generate Summary from Findings' → OpenAI runs with
ticket + chatter + findings, where the prompt explicitly tells
the model to weight findings MORE THAN the original report
4. Review/edit, then Send
Bulk wizard mirrors the flow per line: each row gets its own
findings + summary, one 'Generate All Summaries' button fans out
parallel OpenAI calls using each line's own findings.
Updated SUMMARY_PROMPT to:
- Tell the model the support engineer's findings are authoritative
- Emit a bullet structure that leads with the recommendation, not
the user's restated ask
- Side with findings over the original report when they conflict
New tests cover:
- default_get does NOT fire OpenAI (regression guard for auto-AI)
- Findings text actually reaches the OpenAI prompt
- Send works with a manually-typed summary (no AI in the loop)
- Existing bulk + validation paths still pass with the new shape
Also folds in the deferred code-review #7: ThreadPoolExecutor now
explicitly cancels pending futures on timeout via
shutdown(wait=False, cancel_futures=True) so a slow OpenAI day can't
hold the wizard open for ceil(N/workers)*15s.
Bumps fusion_helpdesk_central to 19.0.2.3.0.
Smoke-tested live on nexa: opening the wizard makes zero OpenAI calls;
clicking Generate with findings='My findings: scope is XL, ~8h' makes
exactly one call and the findings text is verifiably in the prompt
body received by call_openai_chat.
This commit is contained in:
@@ -23,22 +23,35 @@ _logger = logging.getLogger(__name__)
|
||||
SUMMARY_PROMPT = """You are summarising a customer support ticket for a busy executive
|
||||
who needs to decide whether to approve the work.
|
||||
|
||||
The support engineer has reviewed this ticket and written their findings
|
||||
below (scope, limitations, recommended approach, anything the original
|
||||
reporter wouldn't have known). Treat those findings as authoritative —
|
||||
they reflect the actual engineering reality, not just the user's
|
||||
description of the problem. If the findings contradict the original
|
||||
report, side with the findings and call out the gap.
|
||||
|
||||
Output rules:
|
||||
- 4-6 short bullet points, plain text (no markdown).
|
||||
- First bullet: the ask, in one sentence.
|
||||
- Second bullet: the business impact if approved.
|
||||
- Third bullet: the business impact if NOT approved (or "none material").
|
||||
- Optional bullets: cost / effort signals if any are mentioned.
|
||||
- First bullet: the ask, in one sentence, framed in the support
|
||||
engineer's terms (not just a paraphrase of the original report).
|
||||
- Second bullet: the support engineer's recommendation, if they made one.
|
||||
- Third bullet: business impact if approved.
|
||||
- Fourth bullet: business impact if NOT approved (or "none material").
|
||||
- Optional bullets: scope / effort / risk signals from the findings.
|
||||
- Final bullet: open questions the approver should think about.
|
||||
- Do not invent facts. If the thread doesn't say, write "not stated".
|
||||
- Do not invent facts. If the findings or thread don't say, write
|
||||
"not stated".
|
||||
- No greetings, no sign-offs, no preamble.
|
||||
|
||||
Ticket title: {name}
|
||||
Original report:
|
||||
Original report from the user:
|
||||
{description_plain}
|
||||
|
||||
Replies so far:
|
||||
Replies in the ticket thread:
|
||||
{messages_plain}
|
||||
|
||||
Support engineer's findings (your most important input):
|
||||
{findings_plain}
|
||||
"""
|
||||
|
||||
# Bound the prompt sent to OpenAI so a runaway thread doesn't blow cost or
|
||||
@@ -47,13 +60,17 @@ Replies so far:
|
||||
OPENAI_PROMPT_MAX_CHARS = 8000
|
||||
|
||||
|
||||
def build_summary_prompt(ticket_name, description_plain, messages):
|
||||
"""Render SUMMARY_PROMPT with ticket + chatter content.
|
||||
def build_summary_prompt(ticket_name, description_plain, messages,
|
||||
findings=''):
|
||||
"""Render SUMMARY_PROMPT with ticket + chatter + support findings.
|
||||
|
||||
`messages` is a list of dicts with at minimum {author, date, body_plain}.
|
||||
We render one line per message so the model can attribute who said what.
|
||||
Empty inputs collapse to "(none)" so the prompt never has bare blanks
|
||||
that the model could interpret as a missing section.
|
||||
`findings` is the support engineer's free-text notes from the wizard —
|
||||
their scope/effort/recommendation that the AI should weight more
|
||||
heavily than the original user description.
|
||||
|
||||
Empty inputs collapse to explicit markers so the prompt never has
|
||||
bare blanks the model could misread as missing sections.
|
||||
"""
|
||||
name = (ticket_name or '').strip() or '(untitled)'
|
||||
desc = (description_plain or '').strip() or '(no description)'
|
||||
@@ -67,8 +84,13 @@ def build_summary_prompt(ticket_name, description_plain, messages):
|
||||
msgs = '\n---\n'.join(lines)
|
||||
else:
|
||||
msgs = '(no replies yet)'
|
||||
findings_text = (findings or '').strip() or (
|
||||
'(none provided — base the summary on the user\'s report and '
|
||||
'thread alone)'
|
||||
)
|
||||
return SUMMARY_PROMPT.format(
|
||||
name=name, description_plain=desc, messages_plain=msgs,
|
||||
findings_plain=findings_text,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user