我在 Twilio 控制台中使用两个无服务器功能,它们相互调用以进行由 openai 驱动的语音聊天。第一个是/transcribe,由来电触发。 我在 /transcribe 中成功进行了 MySQL 调用,但似乎无法让脚本等待它完成。 /transcribe 继续运行并调用 /respond,后者处理对 openai 的调用,然后回调 /transcribe 以继续对话。 未及时接收 MySQL 数据以在脚本中设置重要值。这就是挑战。
我想了解更多的是,Twilio 希望在此结构中看到什么来暂停脚本足够长的时间以完成 MySQL 调用并使用 /transcribe 函数中的值? 等待并承诺?还有其他具有相同效果的方法吗? 很高兴去学习并回来报告。
以下是 /transcribe 功能的大部分内容:
{
context.callbackWaitsForEmptyEventLoop = false;
const twiml = new Twilio.twiml.VoiceResponse();
if (!event.request.cookies.convo) { //if no conversation is present
getDbRow((error, users) => {
//here’s where I want to assign values needed by the rest of the script
}); // end of db call
// Greet the user with a message
twiml.say({
voice: 'Polly.Joanna-Neural',
},
“Howdy”
);
} // end of “if no conversation “
// Listen to the user's speech and pass the input to the /respond Function
twiml.gather({
speechTimeout: 'auto', // Automatically determine the end of user speech
speechModel: 'experimental_conversations', // Use the conversation-based speech recognition model
input: 'speech', // Specify speech as the input type
action: '/respond', // Send the collected input to /respond
});
// Create a Twilio Response object
const response = new Twilio.Response();
// Set the response content type to XML (TwiML)
response.appendHeader('Content-Type', 'application/xml');
// Set the response body to the generated TwiML
response.setBody(twiml.toString())
response.setCookie('first_system_prompt', encodeURIComponent(JSON.stringify(openai_system_prompt)));
// If no conversation cookie is present, set an empty conversation cookie
if (!event.request.cookies.convo) {
response.setCookie('convo', '', ['Path=/']);
}
// Return the response to Twilio
return callback(null, response);
}; // end of exports.handler for /transcribe
//then my database function which takes about 0.2 seconds to resolve
function getDbRow(callback) {
const connection = mysql.createConnection({
host: 'testHere',
user: 'testHere',
password: ''testHere'',
database: ''testHere''
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return callback(err, null);
}
console.log('Connected to the database.');
const query = 'SELECT first_name, system_prompt, summary_last_conversation FROM test WHERE id=1';
connection.query(query, (error, results) => {
if (error) {
console.error('Error executing query:', error);
connection.end();
return callback(error, null);
}
connection.end((err) => {
if (err) {
console.error('Error closing the connection:', err);
}
console.log('Connection closed.');
});
callback(null, results);
});
});
}
/respond有一个异步exports.handler,一旦使用openai完成,就会调用/transcribe,如此重复。 这是 /respond 函数的粗略结构:
// Import the OpenAI module using CommonJS syntax
const OpenAI = require("openai");
// since console.log doesn't seem to work well, let's make the openai system prompt very
// different to debug if the cookie comes over
let initial_system_prompt = "You are a polite professor, teaching facts about history.";
// Define the main function for handling requests
exports.handler = async function (context, event, callback) {
try {
// Set up the OpenAI API client with the API key from your environment variables
const openai = new OpenAI.OpenAIApi(new OpenAI.Configuration({
apiKey: context.OPENAI_API_KEY,
}));
// Set up the Twilio VoiceResponse object to generate the TwiML
const twiml = new Twilio.twiml.VoiceResponse();
// Initiate the Twilio Response object to handle updating the cookie with the chat history
const response = new Twilio.Response();
// Parse the cookie value if it exists
const cookieValue = event.request.cookies.convo;
const cookieData = cookieValue ? JSON.parse(decodeURIComponent(cookieValue)) : null;
// Now I attempt to use the cookie values I sent over
const cookieValuePrompt = event.request.cookies.first_system_prompt;
if (cookieValuePrompt) {
try {
initial_system_prompt = JSON.parse(decodeURIComponent(cookieValuePrompt));
} catch (error) {
console.error('Error parsing first_system_prompt cookie:', error);
// If there's an error parsing, we'll keep the default initial_system_prompt
}
}
// Get the user's voice input from the event
let voiceInput = event.SpeechResult;
// Create a conversation object to store the dialog and the user's input to the conversation history
const conversation = cookieData?.conversation || [];
conversation.push({ role: 'user', content: voiceInput });
// Get the AI's response based on the conversation history
const aiResponse = await generateAIResponse(conversation, openai);
// Add the AI's response to the conversation history
conversation.push({ role: 'assistant', content: aiResponse });
// Limit the conversation history to the last 20 messages
while (conversation.length > 20) {
conversation.shift();
}
// Generate some <Say> TwiML using the cleaned up AI response
twiml.say({
voice: "Polly.Joanna-Neural",
},
aiResponse
);
// Redirect to the Function where the <Gather> is capturing the caller's speech
twiml.redirect({
method: "POST",
},
`/transcribe`
);
// Since we're using the response object to handle cookies we can't just pass the TwiML straight back to the callback, we need to set the appropriate header and return the TwiML in the body of the response
response.appendHeader("Content-Type", "application/xml");
response.setBody(twiml.toString());
// Update the conversation history cookie with the response from the OpenAI API
const newCookieValue = encodeURIComponent(JSON.stringify({
conversation
}));
response.setCookie('convo', newCookieValue, ['Path=/']);
// Return the response to the handler
return callback(null, response);
} catch (error) {
console.error("Error in handler:", error);
return callback(error);
}
};
// Function to generate the AI response based on the conversation history
async function generateAIResponse(conversation, openai) {
const messages = formatConversation(conversation);
return await createChatCompletion(messages, openai);
}
// Function to create a chat completion using the OpenAI API
async function createChatCompletion(messages, openai) {
try {
// Define system messages to model the AI
const systemMessages = [{
role: "system",
content: initial_system_prompt
}
];
messages = systemMessages.concat(messages);
const chatCompletion = await openai.createChatCompletion({
model: 'gpt-4',
messages: messages,
temperature: 0.8,
max_tokens: 100,
top_p: 0.9,
n: 1,
});
if (chatCompletion.data.choices[0].message.content &&
chatCompletion.data.choices[0].message.content.toLowerCase().includes("toasted shrimp")) {
// do stuff
}
return chatCompletion.data.choices[0].message.content;
} catch (error) {
console.error("Error during OpenAI API request:", error);
throw error;
}
}
// Function to format the conversation history
function formatConversation(conversation) {
return conversation.map(message => ({
role: message.role,
content: message.content
}));
}
有帮助的人建议将exports.handler 设置为await 函数()。 Twilio 不喜欢这样,而且它会超时。在同步函数中使用await 并不好。 其他人建议使用回调来表示它已完成。 那不起作用。有一些工作可以使 MySQL 调用成为一个承诺,但这也失败了(可能是因为我没有得到正确的语法)。
在此环境中进行 MySQL 调用是否有最佳实践,可以在脚本运行之前填充字段?
您将需要返回一个新的promise,其中resolve和reject作为将包含的匿名函数定义的参数。现在返回承诺的函数的代码主体。
在MySQL错误中只需调用reject(error)即可。 或者调用resolve(results)来解决promise。 因为你的函数返回一个承诺对象。
您现在可以链接 method.then() 并传入 then 函数定义和 wrige 游览语句下一步要做什么。 这样你就可以等待承诺解决,然后继续下一步。