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:
2026-10-08 11:23:41 -04:00
parent 0311d3f1ff
commit dfed03ac7c
39 changed files with 198 additions and 3314 deletions
+10 -60
View File
@@ -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: