Customer support assistant
Answer customer questions from your help centre, with a link to the source.
A help centre your customers can ask questions of instead of searching. One workspace, every published article in it, answers that link back to the article they came from.
Architecture
Your frontend never touches this API. It calls your own endpoint, which holds the key and forwards the question — that is what keeps a credential capable of deleting workspaces off the public internet.
- Browser → your
/support/askroute (your session, your rate limits). - Your route → this API, with the API key and the customer's id.
- Answer and sources back the same way.
Setup
Create one workspace and upload every published article into it. A public help centre has nothing to separate, so a single shared workspace is the right shape.
The code
Two functions: one to seed the workspace, one to answer. The sources are deduplicated per article, because three retrieved passages from one page should render as one link.
const BASE = "https://api.daneshyar.info/api/v1";
const KEY = process.env.DANESHYAR_API_KEY;
const WORKSPACE = "8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f";
/**
* One shared workspace holding every help-centre article, because in a public
* help centre there is nothing to separate — every reader may see every doc.
*/
export async function seedHelpCentre(articles) {
for (const article of articles) {
const body = new FormData();
body.append("file", new Blob([article.markdown]), `${article.slug}.pdf`);
body.append("title", article.title);
await fetch(`${BASE}/workspaces/${WORKSPACE}/resources/`, {
method: "POST",
headers: { "X-API-Key": KEY },
body,
});
}
}
/** Called from your own /support/ask route, which holds the key. */
export async function ask({ customerId, question, sessionId }) {
const res = await fetch(`${BASE}/workspaces/${WORKSPACE}/chat/`, {
method: "POST",
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({
// Your customer's id, so one person's thread is never another's.
external_user_id: customerId,
session_id: sessionId,
message: question,
}),
});
const { data } = await res.json();
const reply = data.assistant_message;
return {
sessionId: data.session.id,
answer: reply.content,
// Deduplicate per article: three passages from one page is one link.
sources: [
...new Map(
reply.citations.map((c) => [c.resource_id, c.resource_title]),
),
].map(([id, title]) => ({ id, title })),
};
}What you get
Customers get an answer with the articles it came from, and you get a session per customer that survives page reloads. Wire the source list to your own article URLs and a reader can verify every claim in one click.