Repository navigation
Add "Collect caller input" guide - #682
Conversation
|
Consider a rename to "Call logic" to better reflect content |
…llect input guide
Devon-White
left a comment
There was a problem hiding this comment.
I checked the guide against the SWML engine, the Relay and REST collect handlers, Rails webhook delivery, and the Dashboard voice log. Then I ran every SDK call in the samples against signalwire-sdk 3.4.1 and 3.7.5 and @signalwire/sdk 2.0.5 and 3.7.1. A customer following the SWML and Relay paths gets a working menu. The REST path and several explanations need fixes first.
Must change before merge
- The REST collect never starts its no-input clock.
start_input_timersdefaults tofalse, so silence isn't reported asno_inputthe way the page says. Send the collect after playback finishes, withstart_input_timers: true. Suggestions are on all three REST samples. - The TypeScript REST sample drops
status_urlon the current SDK. 3.7.1 silently omits unknown option keys and failstsc --strict, so it has to go throughextras. This is tied to the unpinning in #908, which this page should follow (install lines, plus the "3.4.1" code comment). no_matchis described as "speech that matched nothing". It means the recognizer returned no text. An off-menu reply comes back as recognized speech and is caught by your own comparison. The samples already handle it right; the prose, the matrix row, and the compare table need correcting.- "Including partial ones" is wrong.
play_and_collectdoesn't acceptpartial_results, so no partials arrive. - The SWML
status_urlalso receivescalling.call.playevents. Readers need to filter onevent_type. - Call Flow Builder exposes the text as
%{prompt_value}, per its Gather Input reference. The compare table says it isn't exposed. - The Dashboard's Collect entry shows neither the outcome nor the text. Line 773 says it shows the outcome, which also contradicts lines 613-614.
Authoring rules
- Per-language method names and SDK internals in prose (lines 281-286 and 1092-1097). For the retry, building it from sections instead of
labellets both builders emit it. - The first-run verification re-teaches SWML delivery and its troubleshooting. Prepare does too, with the ngrok and basic-auth items. Link Make and receive calls instead.
Nits (not blocking)
- Link the webhook body to the REST reference's collect status callback page (
/docs/apis/rest/webhooks/collect-status-callback, fromcollectStatusCallbackinspecs/signalwire-rest/webhooks/calling/main.tsp), next to thepromptreference link at line 688 and the Calling API link at line 492. - The Gather Input reference already has a bounded retry ("Input with infinite loop prevention": Set Variables plus Conditions). Adding a
#### Ask again when the caller says nothing via Call Flow Builderwould keep the examples parallel with the other two tasks. errorresult type, the webhook diagram's ordering, and therelay-clientlink (all inline).
What I verified as correct
- Prompt input selection: any of
speech_hints,speech_language,speech_timeout, orspeech_end_timeoutswitchespromptto speech only, and addingmax_digitsenables both (create_speech_from_object/create_digits_from_objectinswml.c). - Prompt variables:
prompt_valuespeech normalization (swml_normalize_speech_result_text), theprompt_resultvalues, and the 5-secondinitial_timeoutdefault. - Branching:
switchresolves a bare variable name with an exact, case-sensitive key and falls back todefault.transferto a section name doesn't return.gotowhen/maxand a bare-stringlabelwork. - Spoken text: the JavaScript ternary inside
say:works, because play strings go throughresolve_swml_var_string. - Webhook payload: the shape matches what the engine builds (
event_type,event_channel,timestamp,project_id,space_id,paramswithcontrol_id,call_id,node_id,result, and noresult.paramsonno_input/no_match). Rails posts it minusprivate. - Relay result fields:
speech→textandconfidence;digit→digitsandterminator. - REST commands:
calling.collectacceptsstatus_url(relay_apis.cwhitelist), and REST has noplay_and_collect(it's absent from the spec, and the injectedasyncfails its whitelist). - Python SDK:
play_and_collect(media, collect)resolves only on collect events, not play events.play(on_completed=…),call.on,client.run(), and the RESTcollect(extras=…)merge behave as the samples assume. - Builder output: I rendered the Python and TypeScript builder chains, and they produce the same documents as the JSON tabs.
- Dashboard labels: Collect, Play, Script Warning, and Error match the timeline titles.
- Links: every internal link resolves to a page
slug:, and every anchor matches a heading or stepid. - Removed page: the redirect
/docs/swml/guides/ivr→/docs/platform/voice/collect-inputis in place. Nothing else links the deleted recipe, the SWML recipes nav is folder-driven, and thellms.txtbullet copies the frontmatter description. CI passes and the branch merges cleanly withmain. - Changelog: leaving out a changelog entry is right.
Couldn't verify
- The Call Flow Builder node and connector labels in the live UI. They match the Gather Input reference page.
- Click-to-Test and the Addresses & Phone Numbers tab wording.
- Whether the speech engine returns empty text, and so
no_match, for low-confidence audio.play_and_collectsends a 0.60 confidence threshold by default. - How the speech engine applies
start_input_timers. Only the digit path is visible in source. - That
event_channelisswml:<uuid>for SWML calls.
Devon-White
left a comment
There was a problem hiding this comment.
I re-checked 3918334 against current mod_infrastructure master (c80bf95), current prime-rails master, and the latest SDKs (signalwire-sdk 3.7.5, @signalwire/sdk 3.7.1). #908 is still open.
Resolved
- "including partial ones" is gone.
- The Call Flow Builder row now uses
%{prompt_value}. - The Dashboard sentence now says the call log shows neither the outcome nor the text.
New text checks out. SWML can hand off to your server with transfer or execute to a URL. On c80bf95, swml_fetch POSTs get_swml_script_state (call, vars, envs), and prompt stores prompt_value in vars, so the bullet is accurate. The replacement matrix row (only Relay delivers the reply to your process without a webhook) is also correct.
Still open before merge
- The REST collect still doesn't set
start_input_timers, in the prose, the Python sample, the TypeScript sample, or the cURL sample. no_matchis still described as "matched nothing": line 570, the matrix row at line 48, and the compare table.- The SWML
status_urlparagraph still leaves out thecalling.call.playevents. - Language-specific prose remains at line 287 (
cond) and lines 1094-1096 (label). - The first run still re-teaches SWML delivery troubleshooting.
Tied to #908: the install pins and the TypeScript REST status_url → extras change. Whichever PR merges second carries them.
Nits, not blocking: the error result type, the webhook diagram's ordering, and the relay-client link.
|
@Devon-White thanks for the thorough pass, both rounds. c850c50 addresses everything, including the nits:
Three places I went a different way:
|
Summary
/docs/platform/voice/collect-input). It covers asking a question and branching on the answer via SWML, WebSocket (Relay) together with the RESTcalling.collectcommand, and Call Flow Builder; handling silence and unmatched speech; tracking what the flow heard in the Dashboard call log, astatus_urlwebhook, and Relay events (two themed lifecycle diagrams with mermaid twins); and examples for keypad-plus-speech input and a bounded retry on each surface./docs/swml/guides/ivrredirects to the new guide, the same treatment Devon/forward calls guide #793 gave the SWML forwarding recipe.llms.txt: the existing "Collect caller input" bullet points at the guide. One-sentence backlink at the end of Speak text on a call.Notes for reviewers
extras, following Install the latest Server SDK in guides instead of pinned versions #908 ahead of its merge. Both REST samples sendstatus_urlandstart_input_timersthroughextras, because neither the Python (3.7.5) nor the TypeScript (3.7.1) RESTcollect()takes them as arguments. On the old 2.0.5 pin the TypeScript sample would sendextrasas a literal key, so the unpinning and this change go together.start_input_timers: true. The other order, collect first andcalling.collect.start_input_timerson the play'sfinishedevent, is mentioned in prose only.labelon the latest SDK (the generated verb methods take keyword arguments only, andadd_verbrejects a bare string), so the retry example keeps TypeScript, YAML, and JSON tabs and notes the gap in a code comment.fern/assets/images/dashboard/phone-numbers/assign-resource-voice.pngis unreferenced now that the recipe is gone; left in place.Verification
yarn fern-md-check: all 2830 MDX files valid.yarn fern-check: passes apart from the redirects check, which needsFERN_TOKENlocally.collect()signatures and the builder'slabelhandling checked against thesignalwire-sdk3.7.4 and@signalwire/sdk3.7.1 sources.no_matchonly when the recognizer returns no text,start_input_timersdefault, play events on the prompt'sstatus_url) and the Dashboard's call log timeline.Preview: