Split paid research acknowledgement from answer

This commit is contained in:
twentyOne2x 2026-07-01 21:35:29 +02:00
parent 1872e57e26
commit a9a90f0faf
3 changed files with 84 additions and 4 deletions

View file

@ -65,6 +65,7 @@ from http_chat_proxy import (
should_attach_structured_market_context, should_attach_structured_market_context,
smart_research_payment_fields_for_message, smart_research_payment_fields_for_message,
smart_research_command_names, smart_research_command_names,
split_smart_research_auto_resume_reply,
) )
# ─── Config ───────────────────────────────────────────────────────────── # ─── Config ─────────────────────────────────────────────────────────────
@ -859,11 +860,21 @@ async def _poll_smart_research_auto_resume(
if classification == "pending": if classification == "pending":
continue continue
if classification == "ready" and proxy_reply: if classification == "ready" and proxy_reply:
await _reply_text_native(msg, proxy_reply, do_quote=True) ack_reply, answer_reply = split_smart_research_auto_resume_reply(
proxy_body,
proxy_reply,
)
if ack_reply:
await _reply_text_native(msg, ack_reply, do_quote=True)
if answer_reply:
await _reply_text_native(msg, answer_reply, do_quote=not ack_reply)
visible_reply = "\n\n".join(
part for part in (ack_reply, answer_reply) if part
) or proxy_reply
if msg.from_user: if msg.from_user:
entry = { entry = {
"user": research_goal[:500], "user": research_goal[:500],
"bot": proxy_reply[:500], "bot": visible_reply[:500],
"username": username or "anonymous", "username": username or "anonymous",
} }
user_key = (msg.chat_id, msg.from_user.id) user_key = (msg.chat_id, msg.from_user.id)
@ -879,9 +890,9 @@ async def _poll_smart_research_auto_resume(
user_response_times[msg.from_user.id].append(time.time()) user_response_times[msg.from_user.id].append(time.time())
_record_transcript( _record_transcript(
msg, msg,
proxy_reply, visible_reply,
is_bot=True, is_bot=True,
rio_response=proxy_reply, rio_response=visible_reply,
internal={ internal={
"agent": AGENT_NAME.lower(), "agent": AGENT_NAME.lower(),
"http_research_proxy_auto_resume": True, "http_research_proxy_auto_resume": True,

View file

@ -7,7 +7,15 @@ from html import escape
from typing import Any from typing import Any
DEFAULT_SMART_RESEARCH_COMMAND_PREFIXES = ("/smart_research", "/paid_research") DEFAULT_SMART_RESEARCH_COMMAND_PREFIXES = ("/smart_research", "/paid_research")
SMART_RESEARCH_PAYMENT_ACK_TEXT = "Payment received. Research is starting now."
_TELEGRAM_COMMAND_NAME_RE = re.compile(r"^[A-Za-z0-9_]+$") _TELEGRAM_COMMAND_NAME_RE = re.compile(r"^[A-Za-z0-9_]+$")
_SMART_RESEARCH_PAYMENT_ACK_PREFIX_RE = re.compile(
r"^\s*"
r"Payment received[.!]\s*"
r"(?:Research is starting now[.!]\s*)?"
r"(?:I will use the included source-tool budget and continue with the answer here[.!]\s*)?",
re.IGNORECASE,
)
_AUTO_SMART_RESEARCH_RE = re.compile( _AUTO_SMART_RESEARCH_RE = re.compile(
r"\b(" r"\b("
r"research|source|sources|citation|citations|evidence|" r"research|source|sources|citation|citations|evidence|"
@ -319,6 +327,37 @@ def classify_smart_research_auto_resume_response(
return "ignore" return "ignore"
def split_smart_research_auto_resume_reply(
payload: dict[str, Any] | None,
reply: str | None,
) -> tuple[str | None, str | None]:
"""Split a paid auto-resume reply into receipt ack and answer text.
The HTTP route may return one combined reply starting with "Payment
received...". Telegram should show that as two messages: a quick payment
receipt first, then the research result or clean tool-failure message.
"""
if not isinstance(reply, str) or not reply.strip():
return None, None
text = reply.strip()
if not isinstance(payload, dict):
return None, text
auto_resume = payload.get("autoResume")
paid_work_order = payload.get("paidWorkOrder")
paid_resume = (
isinstance(auto_resume, dict)
and auto_resume.get("activated") is True
or isinstance(paid_work_order, dict)
and paid_work_order.get("status") == "settled"
)
if not paid_resume:
return None, text
answer = _SMART_RESEARCH_PAYMENT_ACK_PREFIX_RE.sub("", text, count=1).strip()
return SMART_RESEARCH_PAYMENT_ACK_TEXT, answer or None
def build_smart_research_proxy_payload( def build_smart_research_proxy_payload(
*, *,
research_goal: str, research_goal: str,

View file

@ -23,6 +23,7 @@ from http_chat_proxy import ( # noqa: E402
extract_chat_proxy_reply, extract_chat_proxy_reply,
extract_smart_research_goal, extract_smart_research_goal,
should_attach_structured_market_context, should_attach_structured_market_context,
split_smart_research_auto_resume_reply,
smart_research_payment_fields_for_message, smart_research_payment_fields_for_message,
smart_research_command_names, smart_research_command_names,
) )
@ -227,6 +228,35 @@ def test_classify_smart_research_auto_resume_response(http_status, payload, expe
assert classify_smart_research_auto_resume_response(http_status, payload) == expected assert classify_smart_research_auto_resume_response(http_status, payload) == expected
def test_split_smart_research_auto_resume_reply_separates_payment_ack_from_answer():
payload = {
"autoResume": {"activated": True},
"paidWorkOrder": {"status": "settled"},
}
reply = (
"Payment received. Research is starting now. "
"I will use the included source-tool budget and continue with the answer here.\n\n"
"Based on the Twitter/X results I found, Ranger is being discussed as wound down."
)
ack, answer = split_smart_research_auto_resume_reply(payload, reply)
assert ack == "Payment received. Research is starting now."
assert answer == (
"Based on the Twitter/X results I found, Ranger is being discussed as wound down."
)
def test_split_smart_research_auto_resume_reply_keeps_non_paid_ready_reply_intact():
ack, answer = split_smart_research_auto_resume_reply(
{"status": "complete"},
"Here is the answer.",
)
assert ack is None
assert answer == "Here is the answer."
@pytest.mark.parametrize( @pytest.mark.parametrize(
("message", "expected"), ("message", "expected"),
[ [