mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-01-25 02:07:56 +00:00
Compare commits
12 Commits
v0.9.2-rc3
...
fix/deepgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
380cd7f792 | ||
|
|
6889f0e4ab | ||
|
|
1efb198f72 | ||
|
|
4b5df855e1 | ||
|
|
24126ef1ec | ||
|
|
8e4995ec02 | ||
|
|
a005253a9f | ||
|
|
10efc5d608 | ||
|
|
1c48c40496 | ||
|
|
c79a6aaf8a | ||
|
|
da5f51e8e0 | ||
|
|
e7fd40e297 |
@@ -293,6 +293,8 @@ router.post('/',
|
||||
},
|
||||
cbProvisional: (prov) => {
|
||||
const callStatus = prov.body ? CallStatus.EarlyMedia : CallStatus.Ringing;
|
||||
// Update call-id for sbc outbound INVITE
|
||||
cs.callInfo.sbcCallid = prov.get('X-CID');
|
||||
if ([180, 183].includes(prov.status) && prov.body) connectStream(prov.body);
|
||||
restDial.emit('callStatus', prov.status, !!prov.body);
|
||||
cs.emit('callStatusChange', {callStatus, sipStatus: prov.status});
|
||||
|
||||
@@ -385,7 +385,7 @@ module.exports = function(srf, logger) {
|
||||
const {rootSpan, siprec, application:app} = req.locals;
|
||||
let span;
|
||||
try {
|
||||
if (app.tasks && !JAMBONES_MYSQL_REFRESH_TTL) {
|
||||
if (app.tasks && app.tasks?.length > 0 && !JAMBONES_MYSQL_REFRESH_TTL) {
|
||||
app.tasks = normalizeJambones(logger, app.tasks).map((tdata) => makeTask(logger, tdata));
|
||||
if (0 === app.tasks.length) throw new Error('no application provided');
|
||||
return next();
|
||||
|
||||
@@ -32,6 +32,7 @@ class CallInfo {
|
||||
this.sipStatus = 100;
|
||||
this.sipReason = 'Trying';
|
||||
this.callStatus = CallStatus.Trying;
|
||||
this.sbcCallid = req.get('X-CID');
|
||||
this.originatingSipIp = req.get('X-Forwarded-For');
|
||||
this.originatingSipTrunkName = req.get('X-Originating-Carrier');
|
||||
const {siprec} = req.locals;
|
||||
@@ -129,6 +130,7 @@ class CallInfo {
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
callId: this.callId,
|
||||
sbcCallid: this.sbcCallid,
|
||||
sipStatus: this.sipStatus,
|
||||
sipReason: this.sipReason,
|
||||
callStatus: this.callStatus,
|
||||
|
||||
@@ -1590,17 +1590,29 @@ Duration=${duration} `
|
||||
}
|
||||
|
||||
_lccToolOutput(tool_call_id, opts, callSid) {
|
||||
// this whole thing requires us to be in a Dial verb
|
||||
// only valid if we are in an LLM verb
|
||||
const task = this.currentTask;
|
||||
if (!task || !task.name.startsWith('Llm')) {
|
||||
return this.logger.info('CallSession:_lccToolOutput - invalid command since we are not in an llm');
|
||||
}
|
||||
|
||||
task.processToolOutput(tool_call_id, opts)
|
||||
task.processToolOutput(tool_call_id, opts, callSid)
|
||||
.catch((err) => this.logger.error(err, 'CallSession:_lccToolOutput'));
|
||||
}
|
||||
|
||||
|
||||
_lccLlmUpdate(opts, callSid) {
|
||||
// only valid if we are in an LLM verb
|
||||
const task = this.currentTask;
|
||||
if (!task || !task.name.startsWith('Llm')) {
|
||||
return this.logger.info('CallSession:_lccLlmUpdate - invalid command since we are not in an llm');
|
||||
}
|
||||
|
||||
task.processLlmUpdate(opts, callSid)
|
||||
.catch((err) => this.logger.error(err, 'CallSession:_lccLlmUpdate'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* perform call hangup by jambonz
|
||||
*/
|
||||
@@ -1660,6 +1672,12 @@ Duration=${duration} `
|
||||
else if (opts.boostAudioSignal) {
|
||||
return this._lccBoostAudioSignal(opts, callSid);
|
||||
}
|
||||
else if (opts.llm_tool_output) {
|
||||
return this._lccToolOutput(opts.tool_call_id, opts.llm_tool_output, callSid);
|
||||
}
|
||||
else if (opts.llm_update) {
|
||||
return this._lccLlmUpdate(opts.llm_update, callSid);
|
||||
}
|
||||
|
||||
// whisper may be the only thing we are asked to do, or it may that
|
||||
// we are doing a whisper after having muted, paused recording etc..
|
||||
@@ -1961,6 +1979,10 @@ Duration=${duration} `
|
||||
this._lccToolOutput(tool_call_id, data, call_sid);
|
||||
break;
|
||||
|
||||
case 'llm:update':
|
||||
this._lccLlmUpdate(data, call_sid);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.info(`CallSession:_onCommand - invalid command ${command}`);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ class InboundCallSession extends CallSession {
|
||||
|
||||
_jambonzHangup() {
|
||||
this.dlg?.destroy();
|
||||
// kill current task or wakeup the call session.
|
||||
this._callReleased();
|
||||
}
|
||||
|
||||
_hangup(terminatedBy = 'jambonz') {
|
||||
|
||||
@@ -340,15 +340,17 @@ class TaskDial extends Task {
|
||||
|
||||
const to = parseUri(req.getParsedHeader('Refer-To').uri);
|
||||
const by = parseUri(req.getParsedHeader('Referred-By').uri);
|
||||
const referredBy = req.get('Referred-By');
|
||||
const userAgent = req.get('User-Agent');
|
||||
this.logger.info({to}, 'refer to parsed');
|
||||
const json = await cs.requestor.request('verb:hook', this.referHook, {
|
||||
...(callInfo.toJSON()),
|
||||
refer_details: {
|
||||
sip_refer_to: req.get('Refer-To'),
|
||||
sip_referred_by: req.get('Referred-By'),
|
||||
sip_user_agent: req.get('User-Agent'),
|
||||
refer_to_user: to.scheme === 'tel' ? to.number : to.user,
|
||||
referred_by_user: by.scheme === 'tel' ? by.number : by.user,
|
||||
...(referredBy && {sip_referred_by: referredBy}),
|
||||
...(userAgent && {sip_user_agent: userAgent}),
|
||||
...(by && {referred_by_user: by.scheme === 'tel' ? by.number : by.user}),
|
||||
referring_call_sid,
|
||||
referred_call_sid
|
||||
}
|
||||
@@ -379,6 +381,7 @@ class TaskDial extends Task {
|
||||
res.send(202);
|
||||
this.logger.info('DialTask:handleRefer - sent 202 Accepted');
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'DialTask:handleRefer - error processing incoming REFER');
|
||||
res.send(err.statusCode || 501);
|
||||
}
|
||||
}
|
||||
@@ -484,7 +487,7 @@ class TaskDial extends Task {
|
||||
}
|
||||
|
||||
async _attemptCalls(cs) {
|
||||
const {req, srf} = cs;
|
||||
const {req, callInfo, direction, srf} = cs;
|
||||
const {getSBC} = srf.locals;
|
||||
const {lookupTeamsByAccount, lookupAccountBySid} = srf.locals.dbHelpers;
|
||||
const {lookupCarrier, lookupCarrierByPhoneNumber} = dbUtils(this.logger, cs.srf);
|
||||
@@ -496,8 +499,11 @@ class TaskDial extends Task {
|
||||
this.headers = {
|
||||
'X-Account-Sid': cs.accountSid,
|
||||
...(req && req.has('X-CID') && {'X-CID': req.get('X-CID')}),
|
||||
...(req && req.has('P-Asserted-Identity') && !JAMBONZ_DISABLE_DIAL_PAI_HEADER &&
|
||||
{'P-Asserted-Identity': req.get('P-Asserted-Identity')}),
|
||||
...(direction === 'outbound' && callInfo.sbcCallid && {'X-CID': callInfo.sbcCallid}),
|
||||
...(!JAMBONZ_DISABLE_DIAL_PAI_HEADER && req && {
|
||||
...(req.has('P-Asserted-Identity') && {'P-Asserted-Identity': req.get('P-Asserted-Identity')}),
|
||||
...(req.has('Privacy') && {'Privacy': req.get('Privacy')}),
|
||||
}),
|
||||
...(req && req.has('X-Voip-Carrier-Sid') && {'X-Voip-Carrier-Sid': req.get('X-Voip-Carrier-Sid')}),
|
||||
// Put headers at the end to make sure opt.headers override all default behavior.
|
||||
...this.headers
|
||||
@@ -612,6 +618,7 @@ class TaskDial extends Task {
|
||||
dialCallStatus: obj.callStatus,
|
||||
dialSipStatus: obj.sipStatus,
|
||||
dialCallSid: sd.callSid,
|
||||
dialSbcCallid: sd.callInfo.sbcCallid
|
||||
});
|
||||
}
|
||||
switch (obj.callStatus) {
|
||||
|
||||
@@ -924,7 +924,7 @@ class TaskGather extends SttTask {
|
||||
}
|
||||
}
|
||||
// If transcription received, reset timeout timer.
|
||||
if (this._timeoutTimer) {
|
||||
if (this._timeoutTimer && !emptyTranscript) {
|
||||
this._startTimer();
|
||||
}
|
||||
/* restart asr timer if we get a partial transcript (only if the asr timer is already running) */
|
||||
|
||||
@@ -79,7 +79,18 @@ class TaskLlm extends Task {
|
||||
this.llm.processToolOutput(this.ep, tool_call_id, data);
|
||||
}
|
||||
|
||||
|
||||
async processLlmUpdate(data, callSid) {
|
||||
if (this.ep.connected) {
|
||||
if (typeof this.llm.processLlmUpdate === 'function') {
|
||||
this.llm.processLlmUpdate(this.ep, data, callSid);
|
||||
}
|
||||
else {
|
||||
const {vendor, model} = this.llm;
|
||||
this.logger.info({data, callSid},
|
||||
`TaskLlm:_processLlmUpdate: LLM ${vendor}:${model} does not support llm:update`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskLlm;
|
||||
|
||||
@@ -2,6 +2,7 @@ const Task = require('../../task');
|
||||
const TaskName = 'Llm_OpenAI_s2s';
|
||||
const {LlmEvents_OpenAI} = require('../../../utils/constants');
|
||||
const ClientEvent = 'client.event';
|
||||
const SessionDelete = 'session.delete';
|
||||
|
||||
const openai_server_events = [
|
||||
'error',
|
||||
@@ -125,6 +126,13 @@ class TaskLlmOpenAI_S2S extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
async _api(ep, args) {
|
||||
const res = await ep.api('uuid_openai_s2s', `^^|${args.join('|')}`);
|
||||
if (!res.body?.startsWith('+OK')) {
|
||||
throw new Error({args}, `Error calling uuid_openai_s2s: ${res.body}`);
|
||||
}
|
||||
}
|
||||
|
||||
async exec(cs, {ep}) {
|
||||
await super.exec(cs);
|
||||
|
||||
@@ -140,26 +148,57 @@ class TaskLlmOpenAI_S2S extends Task {
|
||||
|
||||
async kill(cs) {
|
||||
super.kill(cs);
|
||||
|
||||
this._api(cs.ep, [cs.ep.uuid, SessionDelete])
|
||||
.catch((err) => this.logger.info({err}, 'TaskLlmOpenAI_S2S:kill - error deleting session'));
|
||||
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send function call output to the OpenAI server in the form of conversation.item.create
|
||||
* per https://platform.openai.com/docs/guides/realtime/function-calls
|
||||
*/
|
||||
async processToolOutput(ep, tool_call_id, data) {
|
||||
try {
|
||||
this.logger.debug({tool_call_id, data}, 'TaskLlmOpenAI_S2S:processToolOutput');
|
||||
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify(data)]);
|
||||
if (!data.type || data.type !== 'conversation.item.create') {
|
||||
this.logger.info({data},
|
||||
'TaskLlmOpenAI_S2S:processToolOutput - invalid tool output, must be conversation.item.create');
|
||||
}
|
||||
else {
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify(data)]);
|
||||
|
||||
// send immediate response.create per https://platform.openai.com/docs/guides/realtime/function-calls
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify({type: 'response.create'})]);
|
||||
// spec also recommends to send immediate response.create
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify({type: 'response.create'})]);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskLlmOpenAI_S2S:processToolOutput');
|
||||
}
|
||||
}
|
||||
|
||||
async _api(ep, args) {
|
||||
const res = await ep.api('uuid_openai_s2s', `^^|${args.join('|')}`);
|
||||
if (!res.body?.startsWith('+OK')) {
|
||||
throw new Error({args}, `Error calling uuid_openai_s2s: ${res.body}`);
|
||||
/**
|
||||
* Send a session.update to the OpenAI server
|
||||
* Note: creating and deleting conversation items also supported as well as interrupting the assistant
|
||||
*/
|
||||
async processLlmUpdate(ep, data, _callSid) {
|
||||
try {
|
||||
this.logger.debug({data, _callSid}, 'TaskLlmOpenAI_S2S:processLlmUpdate');
|
||||
|
||||
if (!data.type || ![
|
||||
'session.update',
|
||||
'conversation.item.create',
|
||||
'conversation.item.delete',
|
||||
'response.cancel'
|
||||
].includes(data.type)) {
|
||||
this.logger.info({data}, 'TaskLlmOpenAI_S2S:processLlmUpdate - invalid mid-call request');
|
||||
}
|
||||
else {
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify(data)]);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskLlmOpenAI_S2S:processLlmUpdate');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,32 +177,22 @@ class TaskSay extends TtsTask {
|
||||
account_sid,
|
||||
alert_type: AlertType.TTS_FAILURE,
|
||||
vendor,
|
||||
detail: evt.variable_tts_error
|
||||
detail: evt.variable_tts_error,
|
||||
target_sid
|
||||
}).catch((err) => this.logger.info({err}, 'Error generating alert for no tts'));
|
||||
}
|
||||
else {
|
||||
this.logger.debug({evt}, 'Say got playback-stop');
|
||||
if (evt.variable_tts_error) {
|
||||
writeAlerts({
|
||||
account_sid,
|
||||
alert_type: AlertType.TTS_FAILURE,
|
||||
vendor,
|
||||
detail: evt.variable_tts_error,
|
||||
target_sid
|
||||
}).catch((err) => this.logger.info({err}, 'Error generating alert for no tts'));
|
||||
}
|
||||
if (evt.variable_tts_cache_filename && !this.killed) {
|
||||
const text = parseTextFromSayString(this.text[segment]);
|
||||
addFileToCache(evt.variable_tts_cache_filename, {
|
||||
account_sid,
|
||||
vendor,
|
||||
language,
|
||||
voice,
|
||||
engine,
|
||||
text
|
||||
}).catch((err) => this.logger.info({err}, 'Error adding file to cache'));
|
||||
}
|
||||
if (evt.variable_tts_cache_filename && !this.killed) {
|
||||
const text = parseTextFromSayString(this.text[segment]);
|
||||
addFileToCache(evt.variable_tts_cache_filename, {
|
||||
account_sid,
|
||||
vendor,
|
||||
language,
|
||||
voice,
|
||||
engine,
|
||||
text
|
||||
}).catch((err) => this.logger.info({err}, 'Error adding file to cache'));
|
||||
}
|
||||
|
||||
if (this._playResolve) {
|
||||
evt.variable_tts_error ? this._playReject(new Error(evt.variable_tts_error)) : this._playResolve();
|
||||
}
|
||||
|
||||
@@ -213,6 +213,8 @@ class SingleDialer extends Emitter {
|
||||
},
|
||||
cbProvisional: (prov) => {
|
||||
const status = {sipStatus: prov.status, sipReason: prov.reason};
|
||||
// Update call-id for sbc outbound INVITE
|
||||
this.callInfo.sbcCallid = prov.get('X-CID');
|
||||
if ([180, 183].includes(prov.status) && prov.body) {
|
||||
if (status.callStatus !== CallStatus.EarlyMedia) {
|
||||
status.callStatus = CallStatus.EarlyMedia;
|
||||
|
||||
@@ -161,7 +161,12 @@ const selectDefaultDeepgramModel = (task, language) => {
|
||||
|
||||
const optimalGoogleModels = {
|
||||
'v1' : {
|
||||
'en-IN':['telephony', 'latest_long']
|
||||
'en-IN':['telephony', 'telephony'],
|
||||
'es-DO':['default', 'default'],
|
||||
'es-MX':['default', 'default'],
|
||||
'en-AU':['telephony', 'telephony'],
|
||||
'en-GB':['telephony', 'telephony'],
|
||||
'en-NZ':['telephony', 'telephony']
|
||||
},
|
||||
'v2' : {
|
||||
'en-IN':['telephony', 'long']
|
||||
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.1.18",
|
||||
"@jambonz/speech-utils": "^0.1.20",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.9",
|
||||
"@jambonz/verb-specifications": "^0.0.83",
|
||||
@@ -1539,10 +1539,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/speech-utils": {
|
||||
"version": "0.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.18.tgz",
|
||||
"integrity": "sha512-GlcPvUIKcyiiH4cfUPXyYZtP1HIIdFbrqYUmeTmeBaOuZUrJ0xW+TAp/pbysh54vgPnAfcS43Y3ciULx0S3IjQ==",
|
||||
"license": "MIT",
|
||||
"version": "0.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.20.tgz",
|
||||
"integrity": "sha512-3Ff9zLcFoVZhrI4jBKyjgWpv/fEMx1BpJP85daRwZNC2S0BFshULJ54fQxc8S63IsdgFf6g1cLD5VxqPaqCfbQ==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
@@ -10637,9 +10636,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/speech-utils": {
|
||||
"version": "0.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.18.tgz",
|
||||
"integrity": "sha512-GlcPvUIKcyiiH4cfUPXyYZtP1HIIdFbrqYUmeTmeBaOuZUrJ0xW+TAp/pbysh54vgPnAfcS43Y3ciULx0S3IjQ==",
|
||||
"version": "0.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.20.tgz",
|
||||
"integrity": "sha512-3Ff9zLcFoVZhrI4jBKyjgWpv/fEMx1BpJP85daRwZNC2S0BFshULJ54fQxc8S63IsdgFf6g1cLD5VxqPaqCfbQ==",
|
||||
"requires": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.1.18",
|
||||
"@jambonz/speech-utils": "^0.1.20",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.9",
|
||||
"@jambonz/verb-specifications": "^0.0.83",
|
||||
|
||||
Reference in New Issue
Block a user