chore(vocero): remove the legacy ApiPilot integration (#3)
Vocero now retrieves AquaPortal content through authenticated MCP. This removes the obsolete ApiPilot integration from the API and browser while preserving the separately deployed speech pipeline. Release notes: - Removed ApiPilot endpoints, configuration, credential overrides, and HTTP/realtime tool execution. - Removed browser ApiPilot catalog loading and prompt generation while retaining authenticated MCP discovery. - Updated regression coverage and operating documentation for the retired routes and settings. Reviewed-on: AquaPortal/s2s#3
This commit is contained in:
@@ -6,7 +6,6 @@ from typing import Any, cast
|
||||
|
||||
from vocero_api.core import config as settings
|
||||
from vocero_api.core import logging as app_logging
|
||||
from vocero_api.services import tool_context as services_tool_context
|
||||
|
||||
VOCERO_USER_CONTEXT_HINT = " Caller profile resolved from the page phoneNumber: "
|
||||
REPORTS_LIST_HINT = (
|
||||
@@ -334,8 +333,6 @@ _CONTEXT_DEDUPE_MARKERS: tuple = (
|
||||
("summary_full", SUMMARY_FULL_MARKER),
|
||||
("today_fixed", TODAY_BLOCK_MARKER),
|
||||
("reports_minimal", MINIMAL_REPORTS_MARKER),
|
||||
("apipilot_tools_list", services_tool_context.APIPILOT_PASS1_LIST_MARKER),
|
||||
("apipilot_result", services_tool_context.APIPILOT_PASS2_RESULT_MARKER),
|
||||
)
|
||||
|
||||
|
||||
@@ -971,25 +968,19 @@ def _patch_client_session_update_with_profile(
|
||||
summary_context_text: str | None = None,
|
||||
episode_context_text: str | None = None,
|
||||
profile_obj: Any = None,
|
||||
apipilot_context_text_pass1: str | None = None,
|
||||
) -> str:
|
||||
"""If raw_text is a client session.update JSON and S2S_INJECT_PROFILE is
|
||||
true, append episode_context_text + user_context_text + (optional) reports_context_text +
|
||||
(optional) summary_context_text + (optional) apipilot_context_text_pass1 to session.instructions.
|
||||
(optional) summary_context_text to session.instructions.
|
||||
Returns the modified JSON string, or the original string unchanged if:
|
||||
- it is not session.update JSON (the VAST majority of frames -> silent passthrough, NO LOG)
|
||||
- episode_context_text AND user_context_text AND reports_context_text AND summary_context_text AND apipilot_context_text_pass1 are all empty
|
||||
- S2S_INJECT_PROFILE is False (but if we have apipilot_context_text_pass1 -> PATCH even if S2S_INJECT_PROFILE=false, tools injection is independent.)
|
||||
- episode_context_text AND user_context_text AND reports_context_text AND summary_context_text are all empty
|
||||
- S2S_INJECT_PROFILE is False
|
||||
- parse fails
|
||||
|
||||
2026-09-11: NEW keyword profile_obj. Forwarded to
|
||||
_augment_instructions_with_profile so the prompt lock can emit the proper-name
|
||||
anti-anglicization rule for this caller (e.g. Alessandro never Alexander).
|
||||
|
||||
2026-09-25: NEW keyword apipilot_context_text_pass1. Injected IN ADDITION so WS
|
||||
Realtime LLM sees the tools list match description / marker rules (same as the
|
||||
/api/reasoning HTTP endpoint). Independent gate: even S2S_INJECT_PROFILE=false,
|
||||
if apipilot_context_text_pass1 has content -> PATCH instructions.
|
||||
"""
|
||||
# Step 1: only consider session.update frames. All other frame types
|
||||
# (response.create, conversation.item.*, audio_* etc.) pass through
|
||||
@@ -1005,10 +996,9 @@ def _patch_client_session_update_with_profile(
|
||||
# From here on: we know this is a real session.update (typically emitted
|
||||
# just once or twice per realtime session). We can log safely — 1 line,
|
||||
# not 20 repeated lines.
|
||||
has_apipilot_pass1 = bool((apipilot_context_text_pass1 or "").strip())
|
||||
if not settings.S2S_INJECT_PROFILE and not has_apipilot_pass1:
|
||||
if not settings.S2S_INJECT_PROFILE:
|
||||
app_logging.logger.info(
|
||||
"[INJECT-PROFILE] ⏭️ WS patch SKIPPED on session.update (S2S_INJECT_PROFILE=false and no apipilot list pass1). "
|
||||
"[INJECT-PROFILE] ⏭️ WS patch SKIPPED on session.update (S2S_INJECT_PROFILE=false). "
|
||||
"WS stays pure passthrough."
|
||||
)
|
||||
return raw_text
|
||||
@@ -1016,9 +1006,9 @@ def _patch_client_session_update_with_profile(
|
||||
has_profile = bool((user_context_text or "").strip())
|
||||
has_reports = bool((reports_context_text or "").strip())
|
||||
has_summary = bool((summary_context_text or "").strip())
|
||||
if not (has_episode or has_profile or has_reports or has_summary or has_apipilot_pass1):
|
||||
if not (has_episode or has_profile or has_reports or has_summary):
|
||||
app_logging.logger.info(
|
||||
"[INJECT-PROFILE] ⏭️ WS patch SKIPPED on session.update (no episode, no profile context, no reports list, no latest summary, no apipilot tools list pass1). "
|
||||
"[INJECT-PROFILE] ⏭️ WS patch SKIPPED on session.update (no episode, no profile context, no reports list, no latest summary). "
|
||||
"Probably phoneNumber/episode_id query param missing / fetch failed upstream / "
|
||||
"endpoints or S2S_LATEST_SUMMARY / S2S_REPORT_LIST returned empty result."
|
||||
)
|
||||
@@ -1038,12 +1028,6 @@ def _patch_client_session_update_with_profile(
|
||||
"[INJECT-REPORTS] profile missing, but REPORTS LIST block loaded. "
|
||||
"Will inject reports alone into session.update."
|
||||
)
|
||||
if has_apipilot_pass1 and not has_profile and not has_episode and not has_reports and not has_summary:
|
||||
app_logging.logger.info(
|
||||
"[INJECT-APIPILOT] profile/episode/reports/summary missing, but APIPILOT TOOLS list PASS-1 block loaded (%d chars). "
|
||||
"Will inject tools list alone into session.update.",
|
||||
len(apipilot_context_text_pass1) if isinstance(apipilot_context_text_pass1, str) else 0,
|
||||
)
|
||||
top_session = payload.get("session")
|
||||
if not isinstance(top_session, dict):
|
||||
return raw_text
|
||||
@@ -1064,22 +1048,10 @@ def _patch_client_session_update_with_profile(
|
||||
len(current_instructions),
|
||||
current_instructions,
|
||||
)
|
||||
# Append ApiPilot pass-1 tools and marker rules after profile/report/episode/
|
||||
# summary context, matching /api/reasoning. If a second client session.update
|
||||
# overwrites instructions without the tool-list marker, append the list again.
|
||||
already_has_apipilot_pass1 = (
|
||||
isinstance(current_instructions, str)
|
||||
and services_tool_context.APIPILOT_PASS1_LIST_MARKER in current_instructions
|
||||
)
|
||||
if has_apipilot_pass1 and not already_has_apipilot_pass1:
|
||||
augmented = (augmented + "\n\n" if augmented.strip() else "") + (
|
||||
apipilot_context_text_pass1 if isinstance(apipilot_context_text_pass1, str) else ""
|
||||
)
|
||||
# Do not skip a tool-list repair even if profile augmentation was unchanged.
|
||||
if augmented == current_instructions and not (has_apipilot_pass1 and not already_has_apipilot_pass1):
|
||||
if augmented == current_instructions:
|
||||
app_logging.logger.info(
|
||||
"[INJECT-PROFILE] ⏭️ WS patch SKIPPED on session.update (augmented == current; "
|
||||
"episode/profile already injected or context empty or apipilot same)"
|
||||
"episode/profile already injected or context empty)"
|
||||
)
|
||||
if settings.S2S_DEBUG_PROMPT and augmented.strip():
|
||||
app_logging.logger.info(
|
||||
@@ -1091,7 +1063,7 @@ def _patch_client_session_update_with_profile(
|
||||
top_session["instructions"] = augmented
|
||||
app_logging.logger.info(
|
||||
"[INJECT-PROFILE] ✅ WS session.update PATCHED -> upstream instructions. "
|
||||
"origLen=%d newLen=%d injectedLen=%d episode=%s profile=%s reports=%s summary=%s apipilotPass1=%s(len=%d, wasAlreadyPresent=%s)",
|
||||
"origLen=%d newLen=%d injectedLen=%d episode=%s profile=%s reports=%s summary=%s",
|
||||
len(current_instructions),
|
||||
len(augmented),
|
||||
(len(augmented) - len(current_instructions)),
|
||||
@@ -1099,9 +1071,6 @@ def _patch_client_session_update_with_profile(
|
||||
has_profile,
|
||||
has_reports,
|
||||
has_summary,
|
||||
has_apipilot_pass1,
|
||||
(len(apipilot_context_text_pass1) if isinstance(apipilot_context_text_pass1, str) else 0),
|
||||
("true" if already_has_apipilot_pass1 else "false"),
|
||||
)
|
||||
if settings.S2S_DEBUG_PROMPT:
|
||||
app_logging.logger.info(
|
||||
@@ -1121,25 +1090,6 @@ def _patch_client_session_update_with_profile(
|
||||
len(reports_context_text.strip()),
|
||||
reports_context_text.strip(),
|
||||
)
|
||||
if (
|
||||
settings.S2S_DEBUG_PROMPT
|
||||
and isinstance(apipilot_context_text_pass1, str)
|
||||
and apipilot_context_text_pass1.strip()
|
||||
):
|
||||
app_logging.logger.info(
|
||||
"[DEBUG-APIPILOT][WS session.update] FULL ApiPilot tools list PASS-1 block (len=%d):\n%s\n",
|
||||
len(apipilot_context_text_pass1.strip()),
|
||||
_strip_internal_markers(apipilot_context_text_pass1),
|
||||
)
|
||||
# Also log the FINAL upstream instructions when S2S_DEBUG_PROMPT is on and
|
||||
# this is a session.update (so we see the EXACT text reaching the model even
|
||||
# when S2S_INJECT_PROFILE is off and no patch happened.)
|
||||
if (not settings.S2S_INJECT_PROFILE) and settings.S2S_DEBUG_PROMPT and current_instructions.strip():
|
||||
app_logging.logger.info(
|
||||
"[DEBUG-PROMPT][WS session.update] FINAL upstream instructions (injectProfile=false, len=%d):\n%s\n",
|
||||
len(current_instructions),
|
||||
current_instructions,
|
||||
)
|
||||
try:
|
||||
return json.dumps(payload)
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user