Pāriet uz saturu
Sursa Developers

Jautājumu un atbilžu robota izveide

Šajā ceļvedī tiek izveidots pamats tērzēšanas robotam, kas atbild uz jautājumiem no jūsu zināšanu bāzes: tērzēšanas rīkā, jūsu palīdzības centrā vai jūsu produktā. Asistents atrod vajadzīgās zināšanas un atbild, atsaucoties uz izmantotajām kartītēm; jūsu kods nosūta jautājumu un parāda rezultātu.

Jums vajadzīga atslēga ar tvērumiem search un ask. Atbildē tiek izmantotas tikai tās kartītes, kuras atslēgai ir atļauts lasīt, tāpēc to, par ko robots var runāt, nosaka atslēgas loma, datu profils un kolekcijas. Klientiem paredzētam robotam piešķiriet atslēgu, kas redz tikai publiskās kolekcijas.

Trīs veidi, kā saņemt atbildi

OperācijaAtgriežKam izmantot
POST /v1/searchAtbilstošos izvilkumus, sākot ar labākajiem. Bez uzrakstītas atbildes.Meklēšanas laukam, sadaļai „saistītie raksti“, savai rezultātu kārtošanai.
POST /v1/askPilnu asistenta atbildi ar avotiem vienā HTTP atbildē.Robotiem, kas publicē vienu ziņojumu katrai atbildei; servera puses darbiem.
POST /v1/chatAtbildi kā notikumu straumi, kamēr tā tiek rakstīta.Tērzēšanas saskarnēm, kur lietotājam jāredz, kā atbilde parādās.
curl -X POST https://api.sursa.com/v1/search \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "how do I return a damaged item", "collection": "support"}'

Katrs trāpījums ir kartītes izvilkums: kartītes doc_id un doc_name, sadaļas heading, izvilkuma text, lappuses vai tabulas rindas, kurās tas atrasts, kā arī kartītes uzticamība un pārbaudes statuss.

collection izvēlas kolekciju, kurā meklēt; bez tā tiek izmantota kolekcija ar nosaukumu default. tags un card_ids sašaurina meklēšanu līdz kartītēm ar šīm birkām vai līdz konkrētām kartītēm.

Jautājums un pilna atbilde

curl -X POST https://api.sursa.com/v1/ask \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "How do I return a damaged item?", "collection": "support"}'

Atbildes teksts laukā answer ir Markdown formātā. Tā atsauces, piemēram, [1] vai [1, 3], atbilst sources ierakstu laukam ref; sasaistiet katru no tām ar kartīti (doc_id), lai lasītājs varētu pārliecināties, uz ko atbilde balstīta.

Skaitlis tekstā, ko nevarēja apstiprināt pēc dokumentiem, var parādīties kā atzīme, piemēram, ⟦n:a1⟧. verification.items apraksta katru atzīmi: tās marker, reason un original tekstu. Ja jūsu kods atzīmes neapstrādā, aizstājiet katru no tām ar tās original tekstu.

Kad atbildes nav

HTTP atbilde vienmēr ir 200. Ja asistents nevar atbildēt, empty norāda iemeslu, bet answer satur īsu ziņojumu, ko varat parādīt:

emptyNozīme
no_hitsZināšanu bāzē nekas neatbilst jautājumam.
restrictedTēma ir kartītēs, kuras atslēgai nav atļauts lasīt.
guardrailJautājums ir ārpus tā, uz ko organizācija atļauj asistentam atbildēt.
no_data_for_periodJautājums attiecas uz periodu, par kuru nav dokumentu.

no_hits ir noderīgs signāls: tas parāda, par ko jūsu lietotāji jautā, bet zināšanu bāze vēl neaptver.

Režīmi „Fast“ un „Precise“

Nododiet "quality": "precise", lai uz sarežģītu jautājumu saņemtu rūpīgāku atbildi, kas prasa vairāk laika. Noklusējums "fast" der vairumam jautājumu.

Atbildes straumēšana

Tērzēšanas saskarnē rādiet atbildi, kamēr tā tiek rakstīta. POST /v1/chat ar "stream": true atbild ar text/event-stream: viens notikums katram ziņojumam, katrs ar rindu id: (tā numurs straumē, sākot no 1) un rindu data: ar JSON. JSON lauks type norāda, kas tas par notikumu.

id: 1
data: {"type":"meta","sources":3,"timings":{…},"answer_id":"…"}

id: 2
data: {"type":"sources","sources":[{"ref":1,"doc_id":"8c1d…","doc_name":"Returns","text":"…"}]}

id: 3
data: {"type":"token","text":"Send a photo of the item "}

id: 4
data: {"type":"token","text":"within 7 days [1]."}

id: 5
data: {"type":"done","answer":"Send a photo of the item within 7 days [1].","question_id":9812,"sources":[…]}
typeKas jādara
metaAtbilde ir sākusies. Saglabājiet answer_id, ja ierakstāt atbildes žurnālā.
sourcesKartītes, uz kurām atbilde balstīta. Var pienākt vairāk nekā vienu reizi; noteicošā ir pēdējā.
tokenAtbildes teksta daļa. Pievienojiet to jau parādītajam.
doneAtbilde ir pabeigta. Tajā ir tie paši lauki, kas /v1/ask atbildē.
errorAtbilde apstājās. Straume beidzas bez done. Sk. tālāk.
followupsPēc done: ieteiktie nākamie jautājumi, ko varat piedāvāt kā pogas.
jebkas citsIzlaidiet to. Jauni notikumu tipi var parādīties jebkurā laikā.

Rinda, kas sākas ar : (piemēram, : ping, kas tiek sūtīts ik pēc 15 sekundēm, kamēr atbilde tiek sagatavota), ir komentārs. Izlaidiet to.

messages satur līdzšinējo sarunu, sākot ar vecāko repliku un beidzot ar lietotāja jautājumu. Nosūtiet dažas pēdējās replikas, lai papildjautājumi, piemēram, „un pasūtījumiem no ārzemēm?“, tiktu saprasti.

// Streams an answer and calls onText with each piece of text. Node.js 20+ or a browser.
export async function streamAnswer({ messages, collection, requestId, lastEventId, onText }) {
  const response = await fetch("https://api.sursa.com/v1/chat", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SURSA_API_KEY}`,
      "Content-Type": "application/json",
      Accept: "text/event-stream",
      ...(lastEventId ? { "Last-Event-ID": String(lastEventId) } : {}),
    },
    body: JSON.stringify({ messages, collection, stream: true, request_id: requestId }),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);

  const decoder = new TextDecoder();
  let buffer = "";
  let event = { id: 0, data: "" };
  let done = null;
  for await (const piece of response.body) {
    buffer += decoder.decode(piece, { stream: true });
    let newline;
    while ((newline = buffer.indexOf("\n")) >= 0) {
      const line = buffer.slice(0, newline).replace(/\r$/, "");
      buffer = buffer.slice(newline + 1);
      if (line.startsWith("id:")) event.id = Number(line.slice(3).trim());
      else if (line.startsWith("data:")) event.data += line.slice(5).trim();
      else if (line === "" && event.data) {
        const message = JSON.parse(event.data);
        if (message.type === "token") onText(message.text, event.id);
        else if (message.type === "done") done = message;
        else if (message.type === "error") throw Object.assign(new Error(message.message), { event: message, id: event.id });
        event = { id: 0, data: "" };
      }
    }
  }
  if (!done) throw new Error("the stream ended before the answer was complete"); // resume it
  return done;
}

Bez "stream": true operācija POST /v1/chat atgriež pilnu atbildi JSON formātā, tāpat kā /v1/ask.

Pārtrauktas straumes turpināšana

Savienojumi pārtrūkst: mainās mobilais tīkls, starpniekserverim iestājas noildze, jūsu serveris tiek restartēts. Piešķiriet katram jautājumam request_id, un atbilde pārdzīvos pārtraukumu:

  • Ar request_id atbilde tiek sagatavota neatkarīgi no savienojuma. Atvienošanās to neaptur: atbilde tiek pabeigta un saglabāta.
  • Lai turpinātu, vēlreiz nosūtiet to pašu pieprasījumu ar to pašu request_id un pēdējā saņemtā notikuma numuru galvenē Last-Event-ID.
  • Izmantojiet vienu request_id (piemēram, UUID) katram jautājumam un to pašu – katrā šī jautājuma atkārtojumā. Jauns jautājums saņem jaunu request_id.

Ko saņem atkārtotais pieprasījums, ir atkarīgs no tā, kādā stāvoklī ir atbilde:

SituācijaAtkārtotais pieprasījums saņem
Atbilde vēl tiek rakstīta vai tikko pabeigtaNotikumus pēc Last-Event-ID ar tiem pašiem numuriem: straume vienkārši turpinās.
Atbilde pabeigta pirms dažām minūtēmSaglabāto atbildi kā token ar visu tekstu un done, numurētus no 1. Jautājums netiek uzdots vēlreiz.
Straumi nevar turpināt no šī numuraAtbildi no sākuma, numurētu no 1.

Jūsu kods visus trīs gadījumus apstrādā ar vienu noteikumu: ja pirmā saņemtā notikuma numurs nav lielāks par Last-Event-ID, atbilde sākas no jauna; notīriet parādīto un veidojiet to vēlreiz. Pretējā gadījumā pievienojiet jauno tekstu.

import { randomUUID } from "node:crypto";

async function answerWithResume(messages, collection, show) {
  const requestId = randomUUID();
  let text = "";
  let lastId = 0;
  for (let attempt = 1; ; attempt++) {
    let first = true;
    try {
      return await streamAnswer({
        messages, collection, requestId, lastEventId: lastId,
        onText(piece, id) {
          if (first && lastId && id <= lastId) text = ""; // the answer starts over
          first = false;
          text += piece;
          lastId = id;
          show(text);
        },
      });
    } catch (error) {
      const retry = error.event ? error.event.retry === true : true; // a network error, or a server restart
      if (!retry || attempt === 5) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Sursa API var darboties vairākos serveros. Straume turpinās no Last-Event-ID tikai tajā serverī, kas raksta atbildi; ja jūsu atkārtotais pieprasījums nonāk citā serverī, tas saņem saglabāto atbildi, tiklīdz tā ir pabeigta, vai arī atbilde tiek uzrakstīta vēlreiz no sākuma. Iepriekš minētais noteikums aptver abus gadījumus, un jautājums nekad netiek ieskaitīts divreiz.

Bez request_id savienojuma aizvēršana atceļ atbildi.

Kļūdas straumē

Kļūdas pirms straumes sākuma ir parastas HTTP kļūdas: 401, 403, 422 vai 429 ar Retry-After, ja organizācija ir izlietojusi savu jautājumu limitu vai tai vienlaikus ir pārāk daudz sagatavojamu atbilžu.

Kad straume ir sākusies, tās HTTP statuss vienmēr ir 200, un problēma tiek paziņota kā notikums error:

{"type": "error", "code": "server_restart", "retry": true, "message": "…", "answer_id": "…"}
  • retry: true nozīmē, ka atbilde apstājās servera dēļ, piemēram, atjaunināšanas laikā, un varat nosūtīt to pašu pieprasījumu vēlreiz nemainītu.
  • Citi kodi, piemēram, timeout, ir uzskaitīti API rokasgrāmatā. Nezināmu kodu apstrādājiet tāpat kā jebkuru citu straumes kļūdu: informējiet lietotāju un ļaujiet jautāt vēlreiz.

Atbilžu vērtēšana

Katrai atbildei ir question_id. Nosūtiet lietotāja vērtējumu uz POST /v1/questions/{question_id}/feedback: 1 – noderīga, -1 – nav noderīga, ar neobligātu reason un note. Vērtējumi palīdz organizācijai saprast, kuras kartītes jāuzlabo.

curl -X POST https://api.sursa.com/v1/questions/9812/feedback \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": -1, "reason": "outdated", "note": "The return window is now 14 days."}'

Kontrolsaraksts robotam darba vidē

  • Atslēga tikai ar search un ask, ierobežota ar kolekcijām, kuras drīkst redzēt robota auditorija.
  • request_id katram jautājumam un iepriekš aprakstītais turpināšanas noteikums.
  • Straumes lasīšanas noildze, kas ir garāka par 15 sekundēm, lai komentāri : ping uzturētu savienojumu.
  • Atbildes attēlotas kā Markdown, atsauces sasaistītas ar to avotiem.
  • Skaidrs ziņojums, ja iestatīts empty, un iespēja sazināties ar cilvēku.
  • 429 apstrāde, nogaidot Retry-After.
  • Vērtējumi tiek nosūtīti atpakaļ, lai zināšanu bāze laika gaitā uzlabotos.