Skip to content

Commit 0737d23

Browse files
committed
Add [THREAD_REPLY] support: DraftResponseAgent results posted as thread replies to customer inquiry messages
1 parent 9db9668 commit 0737d23

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

src/bro/agents/email_agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ def _handle_customer_inquiry(self, state: EmailAgentState) -> EmailAgentState:
386386
"customer": customer_display,
387387
"question": extracted["question"],
388388
"order_details": order_details,
389+
"message_id": email.get("message_ids", [""])[0] if email.get("message_ids") else "",
389390
}
390391

391392
state["customer_inquiries"].append(inquiry_data)
@@ -432,6 +433,7 @@ def _generate_report(self, state: EmailAgentState) -> EmailAgentState:
432433
report.append(f"Customer: {inquiry['customer']}")
433434
report.append(f"Question: {inquiry['question']}")
434435
report.append(f"Order Details: {inquiry['order_details']}")
436+
report.append(f"Message ID: {inquiry['message_id']}")
435437

436438
final_report = "\n".join(report)
437439
state["messages"].append(AIMessage(content=final_report))

src/bro/conversation.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,13 @@ def _on_task_completed_cb(self, message: str, scheduled: bool = False) -> None:
380380
_logger.info("Scheduled task completion: posting to main channel without threading")
381381

382382
try:
383+
last_message_ts = None # Track the last posted message for thread replies
383384
for msg in messages:
385+
# Check if this is a thread reply
386+
is_thread_reply = msg.startswith("[THREAD_REPLY]")
387+
if is_thread_reply:
388+
msg = msg.replace("[THREAD_REPLY]\n", "").strip()
389+
384390
input_data = textwrap.dedent(
385391
f"""\
386392
via:
@@ -398,12 +404,33 @@ def _on_task_completed_cb(self, message: str, scheduled: bool = False) -> None:
398404
}
399405
]
400406

401-
_logger.info(f"Requesting conversation response...")
402-
conversation_response = self._request_inference(self._context)
403-
output = conversation_response["output"]
404-
if not output:
405-
_logger.warning("No output from conversation model; response: %s", conversation_response)
406-
self._process_response_output(output)
407+
# If this is a thread reply, temporarily set thread_ts to last message
408+
if is_thread_reply and last_message_ts:
409+
with self._thread_ts_lock:
410+
saved_for_reply = self._current_thread_ts
411+
self._current_thread_ts = last_message_ts
412+
413+
_logger.info(f"Posting as thread reply to {last_message_ts}")
414+
conversation_response = self._request_inference(self._context)
415+
output = conversation_response["output"]
416+
if not output:
417+
_logger.warning("No output from conversation model; response: %s", conversation_response)
418+
self._process_response_output(output)
419+
420+
# Restore thread_ts
421+
with self._thread_ts_lock:
422+
self._current_thread_ts = saved_for_reply
423+
else:
424+
_logger.info(f"Requesting conversation response...")
425+
conversation_response = self._request_inference(self._context)
426+
output = conversation_response["output"]
427+
if not output:
428+
_logger.warning("No output from conversation model; response: %s", conversation_response)
429+
430+
# Get the message ts from the response to use for next thread reply
431+
# This is a simplification - ideally we'd extract it from the actual Slack post
432+
self._process_response_output(output)
433+
last_message_ts = self._current_thread_ts # Store for potential thread reply
407434
finally:
408435
# Restore the original thread_ts
409436
with self._thread_ts_lock:

src/bro/reasoner/openai_generic.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,34 +1013,53 @@ def _process(self, item: dict[str, Any]) -> tuple[
10131013
_logger.info("Running EmailAgent...")
10141014
result = self._email_agent.run()
10151015

1016-
# Auto-trigger DraftResponseAgent for customer inquiries
1016+
# Process each inquiry with DraftResponseAgent and append as thread replies
10171017
if self._draft_response_agent and result:
10181018
# Extract full inquiry context from the result
10191019
import re
10201020

10211021
# Split by ===SPLIT_MESSAGE=== to get individual inquiries
10221022
inquiries = result.split("===SPLIT_MESSAGE===")
1023+
summary = inquiries[0] if inquiries else ""
1024+
processed_parts = [summary]
1025+
10231026
for inquiry in inquiries[1:]: # Skip the summary part
10241027
inquiry = inquiry.strip()
10251028
if inquiry.startswith("Customer:"):
10261029
try:
1027-
# Extract customer, question, and order details
1030+
# Extract customer, question, order details, and message_id
10281031
customer_match = re.search(r"^Customer:\s*(.+)$", inquiry, re.MULTILINE)
10291032
question_match = re.search(r"^Question:\s*(.+)$", inquiry, re.MULTILINE)
10301033
order_match = re.search(r"^Order Details:\s*(.+)$", inquiry, re.MULTILINE)
1034+
message_id_match = re.search(r"^Message ID:\s*(.+)$", inquiry, re.MULTILINE)
10311035

10321036
if customer_match:
10331037
customer = customer_match.group(1)
10341038
question = question_match.group(1) if question_match else ""
10351039
order_details = order_match.group(1) if order_match else ""
1040+
message_id = message_id_match.group(1) if message_id_match else ""
10361041

1037-
# Pass full context to DraftResponseAgent
1038-
inquiry_context = f"Customer: {customer}\nQuestion: {question}\nOrder: {order_details}"
1042+
# Run DraftResponseAgent
1043+
inquiry_context = f"Customer: {customer}\nQuestion: {question}\nOrder: {order_details}\nMessage ID: {message_id}"
10391044
_logger.info(f"Running DraftResponseAgent for: {customer}")
10401045
draft_result = self._draft_response_agent.run(inquiry_context)
10411046
_logger.info(f"DraftResponseAgent result: {draft_result}")
1047+
1048+
# Add inquiry message
1049+
processed_parts.append(f"===SPLIT_MESSAGE===\n{inquiry}")
1050+
# Add draft as a thread reply (marked with special delimiter)
1051+
processed_parts.append(
1052+
f"===SPLIT_MESSAGE===\n[THREAD_REPLY]\n{draft_result}"
1053+
)
10421054
except Exception as e:
10431055
_logger.error(f"DraftResponseAgent failed: {e}")
1056+
# Keep original inquiry even if draft fails
1057+
processed_parts.append(f"===SPLIT_MESSAGE===\n{inquiry}")
1058+
else:
1059+
processed_parts.append(f"===SPLIT_MESSAGE===\n{inquiry}")
1060+
1061+
# Rebuild result with drafts as thread replies
1062+
result = "\n".join(processed_parts)
10441063
else:
10451064
result = "EmailAgent not available. Google Workspace client is required."
10461065

0 commit comments

Comments
 (0)