Skip to content

Commit 5df2a02

Browse files
committed
Add Together AI / serverless LLM API integration example
1 parent 1c1c23f commit 5df2a02

File tree

6 files changed

+203
-2
lines changed

6 files changed

+203
-2
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ STEAM_KEY=D1240DEF4D41D416FD291D0075B6ED3F
7171
STRIPE_SKEY=sk_test_BQokikJOvBiI2HlWgH4olfQ2
7272
STRIPE_PKEY=pk_test_6pRNASCoBOKtIshFeQd4XMUh
7373

74+
TOGETHERAI_API_KEY=sample-api-key
75+
TOGETHERAI_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
76+
7477
TRAKT_ID=trakt-client-id
7578
TRAKT_SECRET=trackt-client-secret
7679

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ I also tried to make it as **generic** and **reusable** as possible to cover mos
8585
- Contact Form (powered by SMTP via Sendgrid, Mailgun, AWS SES, etc.)
8686
- File upload
8787
- **API Examples**
88-
- Facebook, Foursquare, Tumblr (OAuth 1.0a example), Github, Steam, Quickbooks, Paypal, Stripe, Twilio (text messaging), Lob (USPS Mail), HERE Maps, Google Maps, Google Drive, Google Sheets, Alpha Vantage (stocks and finance info) with ChartJS, Last.fm, New York Times, Web Scraping, Trakt.tv (movies/TV)
88+
89+
- **AI:** OpenAI Moderation, Together AI foundational model LLMs (aka Deepseek, Llama, Mistral, etc.)
90+
- **Backoffice:** Lob (USPS Mail), Paypal, Quickbooks, Stripe, Twilio (text messaging)
91+
- **Data, Media & Entertainment:** Alpha Vantage (stocks and finance info) with ChartJS, Github, Foursquare, Last.fm, New York Times, Trakt.tv (movies/TV), Twitch, Tumblr (OAuth 1.0a example), Web Scraping
92+
- **Maps and Location:** Google Maps, HERE Maps
93+
- **Productivity:** Google Drive, Google Sheets
94+
8995
- Flash notifications
9096
- reCAPTCHA and rate limit protection
9197
- CSRF protection
@@ -407,6 +413,16 @@ The OpenAI moderation API for checking harmful inputs is free to use as long as
407413

408414
<hr>
409415

416+
<img src="https://i.imgur.com/dOCkJxT.png" height="50">
417+
418+
- Visit <a href="https://www.together.ai" target="_blank">Together AI</a>
419+
- Sign in or create a Together AI account.
420+
- Click on **Create API Key** to generate a new key. You will also be able to access your API key under your account settings in the API Keys tab.
421+
- Copy and paste the generated API key into your `.env` file as `TOGETHERAI_API_KEY` or set it as an environment variable.
422+
- Go to Together AI's <a href="https://api.together.ai/models" target="_blank"> Models</a> page and pick a model based on your usecase and budget and specify it the `TOGETHERAI_MODEL` in your `.env` file or as an environment variable (e.g. `togethercomputer/llama-3-70b-chat`).
423+
424+
<hr>
425+
410426
<img src="https://i.imgur.com/QMjwCk6.png" height="50">
411427

412428
- Sign in at <a href="https://developer.x.com/" target="_blank">https://developer.x.com/</a>

app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ app.get('/api/quickbooks', passportConfig.isAuthenticated, passportConfig.isAuth
232232
app.get('/api/trakt', apiController.getTrakt);
233233
app.get('/api/openai-moderation', apiController.getOpenAIModeration);
234234
app.post('/api/openai-moderation', apiController.postOpenAIModeration);
235+
app.get('/api/togetherai-classifier', apiController.getTogetherAIClassifier);
236+
app.post('/api/togetherai-classifier', apiController.postTogetherAIClassifier);
235237

236238
/**
237239
* OAuth authentication routes. (Sign in)

controllers/api.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,3 +1488,120 @@ exports.postOpenAIModeration = async (req, res) => {
14881488
input: inputText,
14891489
});
14901490
};
1491+
1492+
/**
1493+
* GET /api/togetherai-classifier
1494+
* Together AI / LLM API Example.
1495+
*/
1496+
exports.getTogetherAIClassifier = (req, res) => {
1497+
res.render('api/togetherai-classifier', {
1498+
title: 'Together.ai/LLM Department Classifier',
1499+
result: null,
1500+
error: null,
1501+
input: '',
1502+
});
1503+
};
1504+
1505+
/**
1506+
* POST /api/togetherai-classifier
1507+
* Together AI API Example.
1508+
* - Classifies customer service inquiries into departments.
1509+
* - Uses Together AI API with a foundational LLM model to classify the input text.
1510+
* - The systemPrompt is the instructions from the developer to the model for processing
1511+
* the user input.
1512+
*/
1513+
exports.postTogetherAIClassifier = async (req, res) => {
1514+
const togetherAiKey = process.env.TOGETHERAI_API_KEY;
1515+
const togetherAiModel = process.env.TOGETHERAI_MODEL;
1516+
const inputText = (req.body.inputText || '').slice(0, 300);
1517+
let result = null;
1518+
let error = null;
1519+
1520+
if (!togetherAiKey) {
1521+
error = 'TogetherAI API key is not set in environment variables.';
1522+
} else if (!togetherAiModel) {
1523+
error = 'TogetherAI model is not set in environment variables.';
1524+
} else if (!inputText.trim()) {
1525+
error = 'Please enter a message to classify.';
1526+
} else {
1527+
try {
1528+
const systemPrompt = `You are a customer service classifier for an e-commerce platform. Your role is to identify the primary issue described by the customer and return the result in JSON format. Carefully analyze the customer's message and select one of the following departments as the classification result:
1529+
1530+
Order Tracking and Status
1531+
Returns and Refunds
1532+
Payments and Billing Issues
1533+
Account Management
1534+
Product Inquiries
1535+
Technical Support
1536+
Shipping and Delivery Issues
1537+
Promotions and Discounts
1538+
Marketplace Seller Support
1539+
Feedback and Complaints
1540+
1541+
Provide the output in this JSON structure:
1542+
1543+
{
1544+
"department": "<selected_department>"
1545+
}
1546+
Replace <selected_department> with the name of the most relevant department from the list above. If the inquiry spans multiple categories, choose the department that is most likely to address the customer's issue promptly and effectively.`;
1547+
1548+
const response = await fetch('https://api.together.xyz/v1/chat/completions', {
1549+
method: 'POST',
1550+
headers: {
1551+
'Content-Type': 'application/json',
1552+
Authorization: `Bearer ${togetherAiKey}`,
1553+
},
1554+
body: JSON.stringify({
1555+
model: togetherAiModel,
1556+
messages: [
1557+
{ role: 'system', content: systemPrompt },
1558+
{ role: 'user', content: inputText },
1559+
],
1560+
temperature: 0,
1561+
max_tokens: 64,
1562+
}),
1563+
});
1564+
1565+
if (!response.ok) {
1566+
const errData = await response.json().catch(() => ({}));
1567+
error = errData.error && errData.error.message ? errData.error.message : `API Error: ${response.status}`;
1568+
} else {
1569+
const data = await response.json();
1570+
const content = data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content;
1571+
let department = null;
1572+
if (content) {
1573+
try {
1574+
// Try to extract JSON from the response
1575+
const jsonStringMatch = content.match(/{.*}/s);
1576+
if (jsonStringMatch) {
1577+
const parsed = JSON.parse(jsonStringMatch[0].replace(/'/g, '"'));
1578+
department = parsed.department;
1579+
}
1580+
} catch (err) {
1581+
console.log('Failed to parse JSON from TogetherAI API response:', err);
1582+
// fallback: try to extract department manually
1583+
const match = content.match(/"department"\s*:\s*"([^"]+)"/);
1584+
if (match) {
1585+
[, department] = match;
1586+
}
1587+
}
1588+
}
1589+
result = {
1590+
department: department || 'Unknown',
1591+
raw: content,
1592+
systemPrompt, // Send the sysetemPrompt to the front-end for this demo, not actual production applications.
1593+
};
1594+
}
1595+
} catch (err) {
1596+
console.log('TogetherAI Classifier API Error:', err);
1597+
error = 'Failed to call TogetherAI API.';
1598+
}
1599+
}
1600+
1601+
res.render('api/togetherai-classifier', {
1602+
title: 'TogetherAI Department Classifier',
1603+
result,
1604+
error,
1605+
input: inputText,
1606+
});
1607+
};

views/api/index.pug

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ block content
133133
| trakt.tv
134134
.col-md-4
135135
a(href='/api/openai-moderation', style='color: #000')
136-
.card.mb-3(style='background-color: #f5f5f5')
136+
.card.text-white.mb-3(style='background-color: rgb(36, 36, 36)')
137137
.card-body
138138
img(src='https://i.imgur.com/E0QdCiI.png', height=40, style='padding: 0px 10px 0px 0px')
139139
| OpenAI Input Moderation
140+
.col-md-4
141+
a(href='/api/togetherai-classifier', style='color: #000')
142+
.card.mb-3(style='background-color: rgb(128, 181, 255)')
143+
.card-body
144+
img(src='https://i.imgur.com/dOCkJxT.png', height=40, style='padding: 0px 10px 0px 0px')
145+
| Together AI - one-shot LLM
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
extends ../layout
2+
3+
block content
4+
.pb-2.mt-2.mb-4.border-bottom
5+
h2
6+
i.fas.fa-network-wired.fa-sm.iconpadding(style='color: #6f42c1')
7+
| Together AI - one shot LLM API call example
8+
9+
.btn-group.mb-4.d-flex(role='group')
10+
a.btn.btn-primary.w-100(href='https://api.together.ai', target='_blank')
11+
i.fas.fa-table-columns.fa-sm.iconpadding
12+
| Together AI Dashboard
13+
a.btn.btn-primary.w-100(href='https://docs.together.ai/docs/inference', target='_blank')
14+
i.fas.fa-info.fa-sm.iconpadding
15+
| Together AI Docs
16+
a.btn.btn-primary.w-100(href='https://docs.together.ai/reference/chat-completions', target='_blank')
17+
i.fas.fa-book.fa-sm.iconpadding
18+
| API Reference
19+
20+
p.text-muted
21+
| This example demonstrates the use of Together.AI's LLM API to build an AI Agent that classifies customer messages for department routing. TogetherAI provides serverless access to various foundational models, including Llama, Deepseek, Mistral, and more. The hackathon starter live demo leverages the Llama-3.3-70B-Instruct-Turbo-Free model. TogetherAI's REST API is fully compatible with OpenAI's API as well as locally hosted Ollama REST API, offering the flexibility to run a locally hosted LLM instead of relying on remote API calls. Furthermore, the output is requested in JSON format, enabling us to extend the code as an AI Agent to take additional actions for the user, such as querying a tracking number from a database based on the classification.
22+
23+
.row
24+
.col-md-8.col-lg-6
25+
form(method='POST', action='/api/togetherai-classifier')
26+
input(type='hidden', name='_csrf', value=_csrf)
27+
.mb-3
28+
label(for='inputText') Customer message
29+
textarea#inputText.form-control(name='inputText', maxlength='300', rows='4', required)= input
30+
.form-text.text-muted Maximum 300 characters.
31+
button.btn.btn-primary(type='submit') Classify
32+
33+
if error
34+
.alert.alert-danger.mt-3= error
35+
36+
if result
37+
.mt-4
38+
h5.text-secondary.mb-3 Classification (Routing) Results
39+
.d-flex.align-items-center.mb-2
40+
i.fas.fa-tag.me-2(style='color: #6f42c1')
41+
if result.department && result.department !== 'Unknown'
42+
span.fw-bold.text-primary.fs-4 Department:
43+
span.ms-2.fs-4= result.department
44+
else
45+
span.text-warning Could not determine department.
46+
if result.raw
47+
details
48+
summary Show raw model output
49+
pre.mt-2= result.raw
50+
if result.systemPrompt
51+
details
52+
summary Show system prompt
53+
pre.mt-2(style='white-space: pre-wrap; word-break: break-word')= result.systemPrompt
54+
if result.userPrompt
55+
details
56+
summary Show user prompt
57+
pre.mt-2= result.userPrompt

0 commit comments

Comments
 (0)