Zum Inhalt springen
CADCAMFUN
Doku

Dokumentation

Erstellen Sie einen Streaming-Assistent-Client

Öffnen Sie eine Runde, stellen Sie die tatsächlichen Ereignisse dar, beantworten Sie ausstehende Anfragen und stellen Sie die Konversation nach einem Verbindungsabbruch wieder her.

Automatisch aus dem Englischen übersetzt. Englisches Original ansehen

An den laufenden Sidecar anschließen

Ein Streaming-Assistent-Client verbindet sich mit demselben lokalen Sidecar wie der Desktop. Rufen Sie die zugewiesene Loopback-URL und das aktuelle Anwendungs-Bearer-Token von der Verbindung ab, mit der Sie die Integration durchführen. Ein vom gestrigen Start gespeicherter Port ist keine dauerhafte Adresse: Der Sidecar fordert bei jedem Start einen kurzlebigen Port an. Ein Kontositzungstoken, ein Companion-Pairing-Token und ein Scoped Action-Bridge-Token haben unterschiedliche Zwecke und können den Anwendungsträger auf dieser Chat-Route nicht ersetzen.

Bevor Sie einen Zug absenden, lesen Sie GET /api/ai/status, /api/ai/providers, und optional /api/ai/models?configuredOnly=true. Ein konfigurierter Berechtigungsnachweis bedeutet, dass ein Schlüssel vorhanden ist; Es beweist nicht, dass der Anbieter die Anfrage akzeptiert oder dass ein Endpunkt das ausgewählte Modell unterstützt. Der Standardhost ist eingebettetes Nikcli. Der Direktmodus kann eine Modellantwort streamen, stellt jedoch nicht die persistente Tool-Schleife oder den speziellen Scheduler des eingebetteten Hosts bereit.

Dieser Leitfaden verwendet CADCAMFUN_URL und CADCAMFUN_TOKEN als Client-Shell-Variablen. Versorgen Sie sie über Ihre bestehende Verbindungsverwaltung. Siehe die AI-API-Referenz für die genauen Schemata und Sidecar für das Startverhalten. In den Beispielen werden weder Anmeldeinformationen installiert noch ein Modell in Ihrem Namen ausgewählt.

Wählen Sie Projekt-, Zeichnungs- und Sitzungsidentität

Der Kunde kann a sessionId in der ersten Runde und verwende es in späteren Runden wieder. Der Dienst ordnet diese Client-ID der tatsächlichen Hostsitzung zu und überprüft die Bindung vor jeder Eingabeaufforderung. Behalten Sie die Ereignis-/Sitzungsidentität als Konversationsstatus bei, anstatt jedes Mal, wenn die Benutzeroberfläche erneut eine Verbindung herstellt, eine andere Kennung zu erstellen.

Ein echter projectId gibt dem Zug Zugriff auf die zulässigen Ressourcen dieses Projekts. Eine optionale drawingId muss zu diesem Projekt gehören; Es bindet die Werkzeuge des Turns an diese Zeichnung. Eine Zeichnung kann nicht ohne ihr Projekt benannt werden. Eine Sitzung, die bereits einem anderen Konto oder Projekt gehört, wird abgelehnt. Hierbei handelt es sich um Zulassungsprüfungen, bevor das Modell Projekttools ausführen kann, und nicht um Hinweise in einer Systemeingabeaufforderung.

Für ein allgemeines erklärendes Gespräch lassen Sie Projekt und Zeichnung weg. Die Anwendung verwendet ihren Standardkontext. Übertragen Sie bei Geometriearbeiten die tatsächlich ausgewählten Projekt-/Zeichnungs-IDs über Chat, Statuswiederherstellung, Warteschlange, Abbruch und Kontrollanfragen. Bei einem Kontowechsel sollte die aktive Sitzung des vorherigen Kontos nicht stillschweigend übernommen werden.

Senden Sie eine Streaming-Runde

POST /api/ai/chat/stream gibt einen SSE-Körper zurück. Es erfordert eine nicht leere Nachricht, obwohl das Transportschema mehrere Felder optional macht, damit Handler lesbare Validierungsnachrichten zurückgeben können. Die folgende allgemeine Frage deaktiviert die Injektion gespeicherten Kontexts und eignet sich daher zur Überprüfung des Transports unabhängig von der Projektgeometrie.

Eine erste gestreamte Anfrage
curl --no-buffer --fail-with-body \
  -H "Authorization: Bearer $CADCAMFUN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"message":"Explain the difference between G0 and G1.","sessionId":"inspection-demo","includeContext":false,"mode":"gcode"}' \
  "$CADCAMFUN_URL/api/ai/chat/stream"

Hinzufügen projectId und drawingId um an einer bestimmten Zeichnung zu arbeiten. mode akzeptiert Allgemein, CAD, Cam, Gcode oder Werkzeugweg. A model der Selektor kann Anbieter, Modell, Stufe und Variante enthalten. Die Stufen sind schnell, ausgewogen oder am besten. Entdecken Sie Anbieter-/Modell-IDs aus dem laufenden Katalog, anstatt eine Annahme darüber festzuschreiben, welcher Anbieter verfügbar ist.

permissionMode akzeptiert „Genehmigen“, „Fragen“ oder „Automatisch“. Zerstörerische Werkzeuggruppen behalten in jedem Modus die Zustimmung der Menschen. Der agent feld wählt eine konfigurierte Assistentenrolle aus; lesen GET /api/ai/agents anstatt Rollen-IDs zu erraten.

Komplette SSE-Datenrahmen lesen

Die Route verwendet den SSE-Datenmodus von Effect: Jede SSE-Datennutzlast ist ein JSON-Ereignisdatensatz mit einem type diskriminator. Ein Netzwerkblock ist keine Ereignisgrenze. JSON, Zeilenenden und UTF-8-Zeichen können zwischen Lesevorgängen aufgeteilt werden. Sammeln Sie dekodierten Text und analysieren Sie nur vollständige Frames und verbinden Sie mehrere Datenzeilen, sofern vorhanden.

Ein minimaler JSON-Datenrahmen-Reader
async function* events(response: Response): AsyncGenerator<Record<string, unknown>> {
  if (!response.ok) throw new Error(await response.text())
  if (!response.body) throw new Error("The response has no stream")
  const reader = response.body.getReader()
  const decoder = new TextDecoder()
  let pending = ""
  try {
    while (true) {
      const next = await reader.read()
      pending += decoder.decode(next.value, { stream: !next.done })
      let boundary: RegExpExecArray | null
      while ((boundary = /\r?\n\r?\n/.exec(pending)) !== null) {
        const frame = pending.slice(0, boundary.index)
        pending = pending.slice(boundary.index + boundary[0].length)
        const data = frame.split(/\r?\n/)
          .filter((line) => line.startsWith("data:"))
          .map((line) => line.slice(5).replace(/^ /, "")).join("\n")
        if (!data) continue
        const event: unknown = JSON.parse(data)
        if (!event || typeof event !== "object" || !("type" in event)) {
          throw new Error("Invalid assistant event")
        }
        yield event as Record<string, unknown>
      }
      if (next.done) break
    }
  } finally {
    await reader.cancel().catch(() => {})
    reader.releaseLock()
  }
}

Dieser Reader demonstriert das Einrahmen; Ein Produktionsclient sollte außerdem jedes Ereignis anhand des gemeinsamen Vertrags validieren oder den typisierten generierten Streaming-Client verwenden. SSE-Kommentar-/Kontrollfelder werden absichtlich ignoriert, da der Anwendungsstatus dieses Endpunkts in JSON-Datensätzen gespeichert ist. Bei der Freigabe eines Client-Streams handelt es sich um eine Transportbereinigung und nicht um eine explizite Aufforderung, serverseitige Arbeit abzubrechen.

Rendern Sie Fortschritte, ohne Inhalte zu duplizieren

Starten Sie den Abruf mit demselben Träger und JSON-Text wie oben gezeigt und iterieren Sie dann den Reader. Anhängen delta.text zum sichtbaren Antworttext und zur Route reasoning.text zu seiner eigenen Ansicht. A done das Ereignis enthält den endgültigen Antworttext. Ersetzen oder gleichen Sie die gesammelte Antwort mit diesem Endwert ab, anstatt die gesamte Antwort erneut anzuhängen.

VeranstaltungenZustand, der beibehalten werden soll
turn, session_status, stepBeschäftigt/Wiederholungsstatus und aktueller Schritt; Auf einen Schrittabschluss kann in derselben Runde ein weiterer Schritt folgen.
tool_call, tool_progress, tool_resultEin Datensatz nach CallId mit Name, Eingabe, Argumentfortschritt, Ausgabe und Erfolg; Der Agent identifiziert einen Spezialisten, wenn er anwesend ist.
tool_diagnosticArgument-/Ausführungs-/Teil-/Überprüfungsergebnisse, Schweregrad, Wiederherstellungstext und optional mutationApplied.
todo, goalErsetzen Sie den aktuellen Plan-/Ziel-Snapshot, anstatt wiederholte Kopien anzuhängen.
retry, compaction, compactedErklären Sie die weitere Arbeit und bewahren Sie dabei den aktuellen Text und die Sitzungsidentität des Gesprächs.
heartbeatKein sichtbarer Inhalt; Der Server sendet etwa alle fünf Sekunden eine Nachricht, während der Quellstream aktiv bleibt.

Korrelieren Sie Werkzeugaufrufe und Ergebnisse nach callId, nicht ihre Array-Position. Ein ausgefallenes Tool kann nützliche Diagnosen zurückgeben, während der Assistent fortfährt. Ein Anbieterfehler nach einem erfolgreichen Bearbeitungstool führt nicht dazu, dass diese Geometrieänderung rückgängig gemacht wird.

Beantworten Sie Berechtigungen und Fragen

A permission_request enthält Sitzungs-ID, Anforderungs-ID, Berechtigung und Muster. Präsentieren Sie der Person die Anfrage und senden Sie die ausgewählte Entscheidung an POST /api/ai/permission/reply. Die entsprechende Sitzungssteuerungsaktion ist reply_permission. Der Server überprüft, ob die Anfrage noch in der Konversation oder einer zugelassenen untergeordneten Sitzung aussteht.

Berechtigungsantworttext
{
  "sessionId": "inspection-demo",
  "requestId": "request-id-from-the-event",
  "decision": "once"
}

Die Entscheidung ist einmal, immer oder ablehnen. Es handelt sich um eine Serverberechtigungsentscheidung, die sich von einem Aktionsargument wie „Bestätigen“ unterscheidet. Ein ausstehendes question_request enthält ein geordnetes Fragenarray. Antworten Sie durch /api/ai/sessions/control mit Aktion answer_question, die Anfrage-ID und ein Antwortarray pro Frage; lehne es mit ab reject_question.

Beantworten Sie eine Single-Choice-Frage
{
  "sessionId": "inspection-demo",
  "action": "answer_question",
  "requestId": "question-id-from-the-event",
  "answers": [["The selected option label"]]
}

Jede Antwort darf nicht leer sein. Eine Single-Choice-Frage lehnt Mehrfachantworten ab und custom=false lässt nur aufgelistete Bezeichnungen zu. Behalten Sie die genaue Beschriftung und Reihenfolge der Fragen bei. Wenn der Server angibt, dass die Anfrage nicht mehr aussteht, aktualisieren Sie die Steuerelemente, anstatt die alte Antwort wiederholt zu veröffentlichen.

Arbeiten Sie in die Warteschlange, steuern Sie sie und stoppen Sie sie explizit

Verwenden POST /api/ai/chat/queue für eine Nachricht, die während des Zuges gesendet wurde. Es benötigt Sitzungs-ID, Nachricht, optionale Projekt-ID und Lieferung. queue ist die Standardeinstellung und wartet auf den nächsten sicheren Schritt; steer bittet den Gastgeber um eine zeitnahe Einbindung. A user_message das Ereignis signalisiert, dass die Nachricht in die Konversation gelangt ist.

Verwenden POST /api/ai/chat/abort mit der Konversationsidentität, wenn die Person auf Stopp drückt. Dies stoppt die Runde und schließt sich den zugehörigen Arbeitern an. Implementieren Sie Stop nicht allein durch Abbrechen des Abrufs: Der Verlust des HTTP-Streams ist nicht derselbe Vorgang. Eine Sitzung kann dann eine weitere Runde akzeptieren, ohne eine neue Projektidentität zu erstellen.

POST /api/ai/sessions/remove entfernt die Host-Sitzung, nachdem sie abgebrochen und ihren untergeordneten Elementen beigetreten ist. Es gibt eine entfernte Flagge zurück. Das Entfernen einer Konversation ist kein Rollback von Projektbearbeitungen, die durch ihre Tools vorgenommen wurden; Überprüfen oder wiederherstellen Sie diese bei Bedarf über die Projekt-/Verlaufs-APIs.

Wiederherstellung nach Verlust des Streams

Wenn der Reader ohne ein letztes Ereignis endet, behandeln Sie die Fertigstellung als unbekannt. Anruf POST /api/ai/sessions/state mit derselben Sitzungs-ID und Projekt-ID. Die Antwort enthält „Beschäftigt“ und Text, mit optionalen wiederhergestellten Tool-/Todo-/Diagnoseereignissen, Verwendung und Aufgaben. Rekonstruieren Sie die Benutzeroberfläche anhand dieses Snapshots. Durch die Wiedergabe wiederhergestellter Tool-Ereignisse werden diese nicht erneut ausgeführt.

Dann fordern Sie an /api/ai/sessions/control mit Aktionsstatus zur Überprüfung ausstehender Berechtigungen/Fragen, Arbeitsaufgaben, Ziel und Kontextrichtlinie. Wer viel zu tun hat und auf eine unbeantwortete Frage wartet, braucht eine Antwort und keine zweite Kopie der ursprünglichen Aufforderung. Eine als inaktiv gemeldete Sitzung mit angesammeltem Text kann als wiederhergestellte Ausgabe angezeigt werden, obwohl die ursprüngliche TCP-Verbindung verschwunden ist.

Unterscheiden Sie zwischen Netzwerkfehler, einem typisierten Anbieter-/Konfigurationsfehlerereignis, explizitem Abbruch und Sitzungsentfernung. Wiederholen Sie die Modellanforderung erst, nachdem Sie überprüft haben, was die erste Runde tatsächlich bewirkt hat. Lesen Sie insbesondere die aktuelle Zeichnung nach einer teilweisen Mutationsdiagnose oder einem unterbrochenen Bearbeitungsvorgang.

Innerhalb des Servers wird eine unterbrochene Modellverbindung bis zu fünf Mal mit einem Backoff von 2 Sekunden wieder aufgenommen, der sich auf 30 Sekunden verdoppelt. Ein Client, der sich erneut mit dem Stream verbindet, sollte weiterhin den Sitzungsstatus lesen, da diese Fortsetzungen Arbeiten beenden können, die der Client nicht im Stream gesehen hat.

Kontext, Anhänge und Komprimierung

includeContext: false deaktiviert die Erfassung des gespeicherten Kontexts; Für diesen Zug kann weiterhin expliziter Kontexttext bereitgestellt werden. Die normale Montage ordnet Daueranforderungen, aktuelle Beobachtungen, UI-Snapshots, Speicher und unterstützende Referenzen, dedupliziert sie und wendet ein Gesamtbudget von 24,000-Zeichen an. Ausgelassene/abgeschnittene Blöcke werden gemeldet; Dieses Budget ist nicht das Token-Fenster des Anbieters.

Zu den Anhängen gehören Name, optionales MIME, Daten und optionale Text-/Base64-Kodierung. Es gibt höchstens fünf pro Anfrage. Bilder sind auf jeweils 5 MiB, PDFs auf 10 MiB und Text auf 2,000,000 Zeichen beschränkt; Kurztext mit bis zu 6,000 Zeichen erscheint auch inline. Für Tools aufbewahrter Text ist projektbezogen im Speicher, daher unterscheidet sich das erneute Verbinden vom Neustart des Dienstes. Nicht unterstützte Dateien werden mit Begründung abgelehnt.

Der Steuerungsaktionskontext fordert eine detaillierte Token-Aufschlüsselung an. Eine normale Statusabfrage vermeidet die Arbeit des Tokenizers. Action Compact bittet den Moderator, ältere Gesprächsstoffe zusammenzufassen. Die Speicherkonsolidierung ist ein separater Hostvorgang. Behalten Sie die ursprünglichen Zeichnungs- und Fertigungsanforderungen über ihre eigenen APIs bei, anstatt sich auf eine komprimierte Konversation zu verlassen, um die exakte Geometrie zu reproduzieren.

Trennen Sie Transportfehler von Domänenergebnissen

HTTP 401 bedeutet, dass die Anfrage die Trägerautorisierung nicht bestanden hat. Ein 400 vor dem Streaming bedeutet üblicherweise eine leere Nachricht, eine nicht zugängliche Sitzungsidentität, eine ungültige Projekt-/Zeichnungskombination oder einen fehlerhaften Text. Sobald ein Stream eingerichtet ist, kann der Handler ein Fehlerereignis mit der Klassifizierung „config“, „provider“, „aborted“ oder „intern“ ausgeben. Behandeln Sie dieses Ereignis auch dann, wenn der Antwortstatus 200 war.

Ein Tool-Ergebnis mit ok=false ist ein Tool-Ergebnis, nicht unbedingt ein fehlgeschlagener Stream. Zeigen Sie die Ausgabe und Diagnose an, damit der Assistent oder die Person sich erholen kann. Eine Worker-Warteschlange kann auch voll sein: Der Scheduler lässt vier aktive und sechzehn wartende Aufgaben zu. Ein unterbrochener Leser kann innerhalb begrenzter Versuche wiederhergestellt werden; Bearbeitungsarbeiten werden als unsicher behandelt und nicht automatisch wiederholt.

Lesen Sie die ausführbaren Verträge ein packages/api/src/groups/ai.ts und ai-events.ts; aufnahme- und Leitungsverhalten in apps/sidecar/src/http/ai.ts; sitzungszuordnung in packages/ai/src/service.ts; fragenvalidierung in nikcli/session-controls.ts; herzschlag-Timing in heartbeat.ts. Diese Quellen definieren, welches Ergebnis der Klient unabhängig von der Prosa des Assistenten präsentieren muss.

Ereignisreferenz

Jede SSE data: frame ist ein JSON-Objekt mit einem type und a sessionId. Nur komplette Frames analysieren; Ein Frame kann über mehrere Netzwerkblöcke verteilt ankommen.

typSchlüsselfelderWie man damit umgeht
turnphase: start, recovering, continuing, finish; fakultativ reasonFortschrittsstatus anzeigen. recovering bedeutet, dass die Verbindung zum Modell wieder aufgenommen wird.
session_statusstatus: busy, idle, retryAktivieren oder deaktivieren Sie die Schaltfläche „Senden“ und die Warteschlange.
user_messagedie gespeicherte BenutzernachrichtOptimistische Benutzeroberfläche in Einklang bringen.
deltatextAn die sichtbare Antwort anhängen.
reasoningbegründungstextOptional; in einem zusammenklappbaren Bereich anzeigen.
step, todo, goalplanen Sie den FortschrittRendern Sie die Aufgabenliste oder den Plan.
tool_callcallId, werkzeug, Argumente, optional agentZeigen Sie, welches Tool für welchen Spezialisten läuft.
tool_progresscallId, fortschrittAktualisieren Sie ein Tool mit langer Laufzeit.
tool_resultcallId, ergebnisZeigen Sie das Ergebnis; Passen Sie es an callId.
tool_diagnosticdetails inkl mutationAppliedEntscheiden Sie, ob sich das Modell geändert hat. Lesen Sie es, bevor Sie eine Bearbeitung erneut versuchen.
permission_request / permission_resolved / permission_blockedrequestId; antwort once, always, rejectFragen Sie dann den Benutzer POST /api/ai/permission/reply.
question_request / question_resolvedfragedatenPräsentieren Sie die Frage und senden Sie die Antwort.
context_snapshot, compaction, compactedkontextzustandInformieren Sie den Benutzer darüber, dass der ältere Verlauf zusammengefasst wurde.
retryattempt, messageZeigen Sie eine vorübergehende Benachrichtigung an. Senden Sie die Aufforderung nicht erneut.
donetext, fakultativ usage, fakultativ recoveredEndgültiger Text der Runde. Ersetzen Sie die angesammelten Deltas damit.
errorcode: provider, config, aborted, internal; messageBeenden Sie die Runde in der Benutzeroberfläche mit der Nachricht; config bedeutet normalerweise, dass Anmeldeinformationen fehlen.
heartbeatkeinerKeep-Alive etwa alle 5 Sekunden; ignorieren.

Ein kompletter Streaming-Client

stream-turn.ts
const base = process.env.CADCAMFUN_URL!       // e.g. http://127.0.0.1:53211
const token = process.env.CADCAMFUN_TOKEN!

const response = await fetch(`${base}/api/ai/chat/stream`, {
  method: "POST",
  headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
  body: JSON.stringify({ projectId: "p1", message: "Summarise this project", mode: "general" }),
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
let buffer = "", answer = ""
for (;;) {
  const { value, done } = await reader.read()
  if (done) break
  buffer += value
  let end
  while ((end = buffer.indexOf("\n\n")) >= 0) {        // one SSE frame
    const frame = buffer.slice(0, end); buffer = buffer.slice(end + 2)
    const data = frame.split("\n").filter((l) => l.startsWith("data:"))
                      .map((l) => l.slice(5).trimStart()).join("\n")
    if (!data) continue
    const event = JSON.parse(data)
    switch (event.type) {
      case "delta": answer += event.text; break
      case "permission_request": /* ask, then POST /api/ai/permission/reply */ break
      case "done": answer = event.text; console.log(answer); break
      case "error": throw new Error(`${event.code}: ${event.message}`)
    }
  }
}

Wenn die Verbindung unterbrochen wird, senden Sie die Nachricht nicht erneut. Lesen Sie die Sitzung mit POST /api/ai/sessions/state, da der Server möglicherweise die Modellverbindung wieder aufgenommen und die Runde ohne Sie beendet hat. Verwenden /api/ai/chat/abort eine Kurve absichtlich stoppen und /api/ai/chat/queue um eine Nachricht hinzuzufügen, während die Sitzung beschäftigt ist.

Wie ein Stream ohne Anbieter aussieht

Auch wenn kein KI-Anbieter konfiguriert ist, wird der Stream trotzdem geöffnet und antwortet mit einem eingegebenen Fehler, gefolgt von einem Heartbeat. Von einem frischen Sidecar aufgenommen:

POST /api/ai/chat/stream
data: {"type":"error","sessionId":"","code":"config","message":"No AI provider is configured. Add an API key in the AI settings."}

data: {"type":"heartbeat"}
  • Der HTTP-Status ist immer noch 200 und der Inhaltstyp text/event-stream: konfigurationsprobleme sind Domänenergebnisse, die als Ereignisse übermittelt werden.
  • sessionId ist leer, da keine Sitzung erstellt wurde.
  • heartbeat frames halten die Verbindung aufrecht; Ignorieren Sie sie beim Rendern und zählen Sie sie nicht als Fortschritt.
  • Jeder Frame endet mit einer Leerzeile. Analysieren Sie, indem Sie Leerzeilen aufteilen und diese verbinden data: zeilen eines Frames, wie es der SDK-Iterator tut.

Testen Sie Ihren Client ohne Modell

Verweisen Sie einen Client auf einen Sidecar ohne Anmeldeinformationen: Er übt den vollständigen SSE-Pfad und den Fehlerzweig kostenlos aus.

In 3D

Konversationsspeicher Zum Orbit ziehen · zum Zoomen scrollen · zum Schwenken mit der rechten Maustaste ziehen
Konversations-, Nachrichten- und zugehörige Tabellen mit ihren Fremdschlüsseln aus den Migrationen.