You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js ami-io发起呼叫Context异常,求全局设置lead_id方案

问题:呼叫未接通时无法设置lead_id及Context异常问题

我在Node.js环境使用ami-io库发起呼叫,指定Context用于将接通的呼叫重定向至对应IVR(共有两个不同用途的IVR),同时需要把lead_id设置到通道中处理后续结果。但遇到Context相关问题:即使发送自定义Context,呼叫仍使用from-trunk-sip-{callerid} Context,导致lead_id无法设置。目前自定义Context仅在呼叫接通时生效,我希望无论呼叫是否接通都能完成lead_id的设置。


现有配置

Asterisk Context配置

[type1-ivr-forward]
exten => _X.,1,NoOp(Calls for IVR)
   same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)})
   same => n,Set(CHANNEL(accountcode)=${LEAD_ID})
   same => n,NoOp(Lead ID: ${LEAD_ID})
   same => n,Answer()
   same => n,Goto(ivr-1,s,1)
   
[type2-ivr-forward]
exten => _X.,1,NoOp(Calls for IVR)
   same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)})
   same => n,Set(CHANNEL(accountcode)=${LEAD_ID})
   same => n,NoOp(Lead ID: ${LEAD_ID})
   same => n,Answer()
   same => n,Set(CALLERID(num)=${customer_phone})
   same => n,Goto(ivr-2,s,1)

Node.js 发起呼叫代码

async function call(lead_data) {
    var action = new amiio.Action.Originate();
    action.Channel = `SIP/${lead_data.caller_id}/${lead_data.phone_number}`;
    action.Context = `${lead_data.type}-ivr-forward`; // type 1 or type 2
    action.Exten = '1003';
    action.Priority = 1;
    action.Async = true;
    action.WaitEvent = true;
    action.CallerId = `${lead_data.caller_id}`;
    action.Timeout = 30000;
    action.variables = { lead_id: lead_data.lead_id, customer_phone: lead_data.phone_number };
    client.send(action, async function (err, data) {
        if (err) {
            // success actions
        } else {
            // error actions
        }
    });
}

解决方案

1. 调整Originate参数,确保Context在呼叫发起阶段生效

问题核心是:呼叫未接通时,Asterisk会自动将呼叫转至from-trunk-sip-{callerid}这类默认Context,跳过你指定的自定义Context。需修改Originate参数逻辑:

  • 修正Channel格式,确保指向正确的SIP中继(而非callerid)
  • 将Exten设置为被呼叫号码,匹配自定义Context的_X.规则
  • 保证变量在呼叫发起时就传入通道

修改后的Node.js代码:

async function call(lead_data) {
    var action = new amiio.Action.Originate();
    // 修正Channel格式,指向正确的SIP中继
    action.Channel = `SIP/${lead_data.trunk_name}/${lead_data.phone_number}`;
    action.Context = `${lead_data.type}-ivr-forward`;
    // Exten设为被呼叫号码,匹配Context里的_X.规则
    action.Exten = lead_data.phone_number;
    action.Priority = 1;
    action.Async = true;
    action.WaitEvent = true;
    action.CallerId = `${lead_data.caller_id}`;
    action.Timeout = 30000;
    action.variables = { 
        lead_id: lead_data.lead_id, 
        customer_phone: lead_data.phone_number 
    };
    client.send(action, async function (err, data) {
        if (err) {
            // 处理成功逻辑
        } else {
            // 处理错误逻辑,可通过AMI事件补设lead_id
        }
    });
}

2. 修改自定义Context,覆盖未接通场景

当前Context的变量设置逻辑在Answer()之后,未接通时不会执行。需将变量设置提前,并添加未接通分支处理:

[type1-ivr-forward]
exten => _X.,1,NoOp(Calls for IVR - Lead ID: ${lead_id})
   same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)})
   same => n,Set(CHANNEL(accountcode)=${LEAD_ID})
   same => n,Set(CALL_TIMEOUT=30)
   // 先拨号,等待应答
   same => n,Dial(SIP/${EXTEN},${CALL_TIMEOUT})
   // 处理未接通状态:无人接听、占线
   same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer,1)
   same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy,1)
   // 接通后跳转IVR
   same => n,Answer()
   same => n,Goto(ivr-1,s,1)

exten => noanswer,1,NoOp(Call no answer - Lead ID: ${LEAD_ID})
   same => n,Hangup()

exten => busy,1,NoOp(Call busy - Lead ID: ${LEAD_ID})
   same => n,Hangup()

[type2-ivr-forward]
exten => _X.,1,NoOp(Calls for IVR - Lead ID: ${lead_id})
   same => n,Set(LEAD_ID=${IF($[${LEN(${lead_id})} > 0]?${lead_id}:UNKNOWN)})
   same => n,Set(CHANNEL(accountcode)=${LEAD_ID})
   same => n,Set(CALL_TIMEOUT=30)
   same => n,Dial(SIP/${EXTEN},${CALL_TIMEOUT})
   same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer,1)
   same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy,1)
   same => n,Answer()
   same => n,Set(CALLERID(num)=${customer_phone})
   same => n,Goto(ivr-2,s,1)

exten => noanswer,1,NoOp(Call no answer - Lead ID: ${LEAD_ID})
   same => n,Hangup()

exten => busy,1,NoOp(Call busy - Lead ID: ${LEAD_ID})
   same => n,Hangup()

3. 通过AMI事件监听,补全变量设置

如果以上设置仍有遗漏,可监听Newchannel事件,在通道创建时直接设置变量:

client.on('event', function(event) {
    if (event.name === 'Newchannel') {
        const channelId = event.Channel;
        // 根据CallerID关联对应的lead数据(需自行实现findLeadByCallerId逻辑)
        const lead = findLeadByCallerId(event.CallerIDNum);
        if (lead) {
            // 设置lead_id变量
            const setVarAction = new amiio.Action.Setvar();
            setVarAction.Channel = channelId;
            setVarAction.Variable = 'lead_id';
            setVarAction.Value = lead.lead_id;
            client.send(setVarAction);
            
            // 设置accountcode
            const setAccountCodeAction = new amiio.Action.Setvar();
            setAccountCodeAction.Channel = channelId;
            setAccountCodeAction.Variable = 'CHANNEL(accountcode)';
            setAccountCodeAction.Value = lead.lead_id;
            client.send(setAccountCodeAction);
        }
    }
});

4. 检查Asterisk配置,避免Context被覆盖

  • 检查SIP中继配置,移除强制设置的context=from-trunk-sip-${callerid}参数,或替换为你的自定义Context
  • 检查extensions.conf中的默认规则,确保没有优先级更高的规则拦截呼叫

内容的提问来源于stack exchange,提问作者Sezer Can Kaynar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 01:05:18