Stage 6: learn_call_flow rebuilt on the learner, docs truth sweep
The call-flow learner finally gets fed: exploration mode records its IVR discoveries on the call (ActiveCall.exploration_steps) instead of throwing them away, persistence stores them in the call record's metadata, and the rebuilt learn_call_flow MCP tool turns a completed exploration call into a stored flow via CallFlowLearner — correct constructor (llm_client from get_llm, heuristic labels when the LLM is unavailable), build for a new number, merge/refine when a flow already exists. save_learned_flow/update_flow_from_model keep the CallFlow↔row mapping in call_persistence. Test gaps closed: tests/test_learner.py (discoveries→linked steps, exploration persistence, learn-then-refine through the in-memory MCP client, no-data and unknown-call answers) and tests/test_websocket.py (4401 without token, trunk-status-then-replay on connect, per-call stream filtering). Docs aligned to code: README (15 tools incl. learn_call_flow, HTTP not SSE, Python 3.12+, PostgreSQL+Alembic — no SQLite fallback, media pipeline marked stub-mode until pjsua2 installed, Alembic and honest /health checked off); docs/mcp-server.md rewritten against the actual tool surface (hangup not end_call, real params, 3 real resources, /mcp/ streamable HTTP + bearer auth); architecture/development/ configuration drift fixed. pyproject: pruned never-imported deps (websockets, librosa, soundfile, python-multipart). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,6 +264,70 @@ def create_mcp_server(
|
||||
except Exception as e:
|
||||
return f"Error creating call flow: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def learn_call_flow(call_id: str, company_name: str = "") -> str:
|
||||
"""
|
||||
Build (or refine) a reusable IVR call flow from a completed
|
||||
hold-slayer exploration call.
|
||||
|
||||
Exploration calls record every IVR prompt heard and DTMF sent;
|
||||
this turns those discoveries into a stored call flow so the next
|
||||
call to that number navigates directly instead of exploring.
|
||||
If a flow already exists for the number, the discoveries refine
|
||||
it (timeouts averaged, usage counters updated).
|
||||
|
||||
Args:
|
||||
call_id: A completed call that ran in exploration mode
|
||||
company_name: Optional company name for labeling a new flow
|
||||
"""
|
||||
from db.database import session_scope
|
||||
from services import call_persistence as store
|
||||
from services.call_flow_learner import CallFlowLearner
|
||||
from services.llm_client import get_llm
|
||||
|
||||
try:
|
||||
async with session_scope() as session:
|
||||
record = await store.get_record(session, call_id)
|
||||
if not record:
|
||||
return f"No record found for call {call_id}."
|
||||
|
||||
steps = (record.metadata_ or {}).get("exploration_steps") or []
|
||||
if not steps:
|
||||
return (
|
||||
f"Call {call_id} has no exploration data to learn from. "
|
||||
"Only hold-slayer calls without a stored flow record "
|
||||
"IVR discoveries."
|
||||
)
|
||||
|
||||
learner = CallFlowLearner(llm_client=get_llm())
|
||||
existing = await store.get_flow_by_number(
|
||||
session, record.remote_number
|
||||
)
|
||||
if existing:
|
||||
flow = await learner.merge_discoveries(
|
||||
store.flow_to_model(existing), steps, intent=record.intent
|
||||
)
|
||||
await store.update_flow_from_model(session, existing, flow)
|
||||
return (
|
||||
f"Refined existing flow '{existing.name}' for "
|
||||
f"{record.remote_number} from {len(steps)} discoveries "
|
||||
f"({len(flow.steps)} steps, used {flow.times_used}x)."
|
||||
)
|
||||
|
||||
flow = await learner.build_flow(
|
||||
phone_number=record.remote_number,
|
||||
discovered_steps=steps,
|
||||
intent=record.intent,
|
||||
company_name=company_name or None,
|
||||
)
|
||||
await store.save_learned_flow(session, flow)
|
||||
return (
|
||||
f"Learned new flow '{flow.name}' with {len(flow.steps)} "
|
||||
f"steps from {len(steps)} discoveries (ID: {flow.id})."
|
||||
)
|
||||
except Exception as e:
|
||||
return f"Error learning call flow: {e}"
|
||||
|
||||
@mcp.tool()
|
||||
async def send_dtmf(call_id: str, digits: str) -> str:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user