Skip to content

feat(ai-assistants-samples): add new template #550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions ai-assistants-samples/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# description: SID of your Twilio Account or Subaccount
# format: sid
# link: https://www.twilio.com/console
# required: true
ACCOUNT_SID=

# description: Auth Token of your Twilio Account or Subaacount
# format: text
# link: https://www.twilio.com/console
# required: true
AUTH_TOKEN=

# description: The ID of your AI Assistant (starts with ai_asst_)
# format: text
# required: true
# configurable: false
ASSISTANT_SID=

# description: API key for Google Maps service
# format: text
# required: true
# configurable: false
GOOGLE_MAPS_API_KEY=

# description: API key for OpenAI used in Internet Search tool
# format: secret
# required: false
OPENAI_API_KEY=

# description: API key for Exa used in Internet Search tool
# format: text
# required: true
# configurable: false
EXA_API_KEY=

# description: Flex Workflow SID used for Flex Handoffs
# format: text
# required: true
# configurable: false
FLEX_WORKFLOW_SID=

# description: Flex Workspace SID used for Flex Handoffs
# format: text
# required: true
# configurable: false
FLEX_WORKSPACE_SID=
2 changes: 2 additions & 0 deletions ai-assistants-samples/.owners
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dkundel
eshenfield
8 changes: 8 additions & 0 deletions ai-assistants-samples/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## [Unreleased]

## [1.0.0]
### Added
- Initial release.

53 changes: 53 additions & 0 deletions ai-assistants-samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ai-assistants-samples

Functions to connect AI Assistants to various Channels and Tools

If you want to use this template standalone please visit [github.com/twilio-labs/ai-assistants-samples](https://github.com/twilio-labs/ai-assistants-samples).

## Pre-requisites

### Environment variables

This project requires some environment variables to be set. A file named `.env` is used to store the values for those environment variables. To keep your tokens and secrets secure, make sure to not commit the `.env` file in git. When setting up the project with `twilio serverless:init ...` the Twilio CLI will create a `.gitignore` file that excludes `.env` from the version history.

In your `.env` file, set the following values:

| Variable | Description | Required |
| :------- | :---------- | :------- |



## Create a new project with the template

1. Install the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart#install-twilio-cli)
2. Install the [serverless toolkit](https://www.twilio.com/docs/labs/serverless-toolkit/getting-started)

```shell
twilio plugins:install @twilio-labs/plugin-serverless
```

3. Initiate a new project

```
twilio serverless:init example --template=ai-assistants-samples && cd example
```

4. Start the server with the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):

```
twilio serverless:start
```

5. Open the web page at https://localhost:3000/index.html and enter your phone number to test

ℹ️ Check the developer console and terminal for any errors, make sure you've set your environment variables.

## Deploying

Deploy your functions and assets with either of the following commands. Note: you must run these commands from inside your project folder. [More details in the docs.](https://www.twilio.com/docs/labs/serverless-toolkit)

With the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart):

```
twilio serverless:deploy
```
88 changes: 88 additions & 0 deletions ai-assistants-samples/assets/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Get started with your Twilio Functions!</title>

<link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico">
<link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css">
<script src="https://twilio-labs.github.io/function-templates/static/v1/ce-helpers.js" defer></script>
<script>
window.addEventListener('DOMContentLoaded', (_event) => {
inputPrependBaseURL();
});
</script>
</head>
<body>
<div class="page-top">
<header>
<div id="twilio-logo">
<a href="https://www.twilio.com/" target="_blank" rel="noopener">
<svg class="logo" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 60 60">
<title>Twilio Logo</title><path class="cls-1" d="M30,15A15,15,0,1,0,45,30,15,15,0,0,0,30,15Zm0,26A11,11,0,1,1,41,30,11,11,0,0,1,30,41Zm6.8-14.7a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,26.3Zm0,7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,36.8,33.7Zm-7.4,0a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,33.7Zm0-7.4a3.1,3.1,0,1,1-3.1-3.1A3.12,3.12,0,0,1,29.4,26.3Z"/></svg>
</a>
</div>
<nav>
<span>Your Twilio application</span>
<aside>
<svg class="icon" role="img" aria-hidden="true" width="100%" height="100%" viewBox="0 0 20 20" aria-labelledby="NewIcon-1577"><path fill="currentColor" fill-rule="evenodd" d="M6.991 7.507c.003-.679 1.021-.675 1.019.004-.012 2.956 1.388 4.41 4.492 4.48.673.016.66 1.021-.013 1.019-2.898-.011-4.327 1.446-4.48 4.506-.033.658-1.01.639-1.018-.02-.03-3.027-1.382-4.49-4.481-4.486-.675 0-.682-1.009-.008-1.019 3.02-.042 4.478-1.452 4.49-4.484zm.505 2.757l-.115.242c-.459.9-1.166 1.558-2.115 1.976l.176.08c.973.465 1.664 1.211 2.083 2.22l.02.05.088-.192c.464-.973 1.173-1.685 2.123-2.124l.039-.018-.118-.05c-.963-.435-1.667-1.117-2.113-2.034l-.068-.15zm10.357-8.12c.174.17.194.434.058.625l-.058.068-1.954 1.905 1.954 1.908a.482.482 0 010 .694.512.512 0 01-.641.056l-.07-.056-1.954-1.908-1.954 1.908a.511.511 0 01-.71 0 .482.482 0 01-.058-.626l.058-.068 1.954-1.908-1.954-1.905a.482.482 0 010-.693.512.512 0 01.64-.057l.07.057 1.954 1.905 1.954-1.905a.511.511 0 01.71 0z"></path></svg>
Live
</aside>
</nav>
</header>
</div>
<main>
<div class="content">
<h1>
<img src="https://twilio-labs.github.io/function-templates/static/v1/success.svg" />
<div>
<p>Welcome!</p>
<p>Your live application with Twilio is ready to use!</p>
</div>
</h1>
<section>
<h2>Get started with your application</h2>
<p>
Follow these steps to try out your new app:
</p>
<p>
This app will return the
<a href="https://www.twilio.com/docs/sms/twiml" target="_blank" rel="noopener">TwiML</a>
required to respond "Hello World" to incoming SMS messages.
</p>
<ol class="steps">
<li>Text any message to your Twilio phone number</li>
<li>You should receive a response saying "Hello World"</li>
</ol>
</section>
<section>
<!-- APP_INFO_V2 -->
</section>
<section>
<h2>Troubleshooting</h2>
<ul>
<li>
Check the
<a href="https://www.twilio.com/console/phone-numbers/incoming"
target="_blank"
rel="noopener">
phone number configuration
</a>
and make sure the Twilio phone number you want for your app has a SMS webhook
configured to point at the following URL
<form>
<label for="twilio-webhook">Webhook URL</label>
<input type="text" id="twilio-webhook" class="function-root" readonly=true value="/hello-messaging">
</form>
</li>
</ul>
</section>
</div>
</main>
<footer>
<span class="statement">We can't wait to see what you build.</span>
</footer>
</body>
</html>
142 changes: 142 additions & 0 deletions ai-assistants-samples/assets/utils.private.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* eslint-disable no-nested-ternary, sonarjs/no-nested-template-literals */

const { sign, decode } = require('jsonwebtoken');

/**
* @param {import('@twilio-labs/serverless-runtime-types/types').Context} context
* @param {string} assistantSid
* @param {*} body
*/
async function sendMessageToAssistant(context, assistantSid, body) {
const environmentPrefix = context.TWILIO_REGION?.startsWith('stage')
? '.stage'
: context.TWILIO_REGION?.startsWith('dev')
? '.dev'
: '';
const url = `https://assistants${environmentPrefix}.twilio.com/v1/${assistantSid}/Messages`;

// Attention! There's explicitly no "await" since we want to do a "fire & forget"
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(body),
headers: {
Authorization: `Basic ${Buffer.from(
`${context.ACCOUNT_SID}:${context.AUTH_TOKEN}`,
'utf-8'
).toString('base64')}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
if (response.ok) {
console.log('Sent message to AI Assistant');
} else {
throw new Error(
`Failed to send request to AI Assistants. ${await response.text()}`
);
}
}

/**
* @param {import('@twilio-labs/serverless-runtime-types/types').Context} context
* @param {string} chatServiceSid
* @param {string} conversationSid
*/
async function readConversationAttributes(
context,
chatServiceSid,
conversationSid
) {
try {
const client = context.getTwilioClient();
const data = await client.conversations.v1
.services(chatServiceSid)
.conversations(conversationSid)
.fetch();
return JSON.parse(data.attributes);
} catch (err) {
console.error(err);
return {};
}
}

/**
* @param {import('@twilio-labs/serverless-runtime-types/types').Context} context
* @param {*} event
*/
async function getAssistantSid(context, event) {
if (event.EventType === 'onMessageAdded') {
try {
const { ConversationSid, ChatServiceSid } = event;
const parsed = await readConversationAttributes(
context,
ChatServiceSid,
ConversationSid
);
if (typeof parsed.assistantSid === 'string' && parsed.assistantSid) {
return parsed.assistantSid;
}
} catch (err) {
console.log('Invalid attribute structure', err);
}
}
const assistantSid =
event.AssistantId ||
context.ASSISTANT_ID ||
event.AssistantSid ||
context.ASSISTANT_SID;

if (!assistantSid) {
throw new Error('Missing Assistant ID configuration');
}

return assistantSid;
}

/**
* @param {import('@twilio-labs/serverless-runtime-types/types').Context} context
* @param {*} event
*/
async function signRequest(context, event) {
const assistantSid = await getAssistantSid(context, event);
const authToken = context.AUTH_TOKEN;
if (!authToken) {
throw new Error('No auth token found');
}
return sign({ assistantSid }, authToken, { expiresIn: '5m' });
}

/**
* @param {import('@twilio-labs/serverless-runtime-types/types').Context} context
* @param {*} event
*/
function verifyRequest(context, event) {
const token = event._token;
if (!token) {
throw new Error('Missing token');
}

const authToken = context.AUTH_TOKEN;
if (!authToken) {
throw new Error('No auth token found');
}

try {
const decoded = decode(token, authToken, { json: true });
if (decoded.assistantSid) {
return true;
}
} catch (err) {
console.error('Failed to verify token', err);
return false;
}
return false;
}

module.exports = {
getAssistantSid,
signRequest,
verifyRequest,
sendMessageToAssistant,
readConversationAttributes,
};
Loading
Loading