Join our Discord community — connect, share, and grow with us! Join Now

Workflows

Workflows ermöglichen es Nutzerinnen und Nutzern, eigene Automationen innerhalb ihrer Workspaces zu erstellen.
"Workflows" bestehen aus "Nodes", die man als serverlose Funktionen verstehen kann, die miteinander verbunden werden können.

Trigger

Manuell (Button klicken)

Dies ist die Standardoption. Sie bedeutet, dass der Workflow nur ausgelöst wird, wenn du in der Workflow-UI manuell auf "execute" klickst.

Cron (AWS cron)

Cron ermöglicht es dir, die Ausführung deines Workflows sehr präzise zu planen (minutengenau). Wir verwenden das AWS-Cron-Format, um ein zuverlässiges Scheduling-Erlebnis sicherzustellen.

Webhook (URL + Payload)

OrbitType erzeugt eine Webhook-URL, die als Einstiegspunkt dient. Wenn eine Drittanbieter-App oder dein Bot aus einem anderen Client eine POST-Anfrage mit einem JSON-Body an diesen Endpoint sendet, wird dieser JSON-Body als Input-JSON für die erste Node verwendet. Dieser Input wird außerdem als Beispiel in deinem Workspace gespeichert, um Tests zu vereinfachen. Du kannst ihn im Code-Editor der ersten Node mit "Load Input" laden.

Datenbank (Tabellen-Events und OrbitType-API-Trigger)

Da OrbitType eine integrierte Umgebung ist, kannst du auf Trigger aus Datenbanktabellen lauschen. Wir unterstützen Events für Erstellung, Updates und Löschungen. Die SQL-Anfrage, die den Workflow auslöst, muss über die OrbitType-API ausgeführt werden und einen definierten Return-Value enthalten. Für Tests wird der Input gespeichert, sodass er später wieder abgerufen werden kann.
Wenn eine Zeile aktualisiert wird, erhältst du als Input ein Array mit zwei Objekten: eines mit der alten Zeile und eines mit der neuen Zeile. Du kannst dann JavaScript innerhalb der Node verwenden, um präzise zu prüfen, ob die Änderungen den Flow auslösen sollen oder ob du frühzeitig zurückkehren möchtest.

Versionen/Wiederherstellen

Jeder Workflow ist versioniert. Bei jedem Speichern einer Node wird der komplette Workflow gespeichert. Über den Button "Versions" kannst du auf frühere Versionen zurückspringen und diese wiederherstellen. Es werden 50 Versionen als Backups aufbewahrt.

Node-Typen

Code-Nodes

Dies ist die Standard-Node. In dieser Node kannst du Code schreiben und Config-Werte setzen, die du innerhalb des Codes verwendest.

Dekorative Nodes

Das sind visuelle Elemente, die dir helfen, den Workflow-Plan zu dokumentieren und visuell zu strukturieren. Sie können außerdem nützlichen Kontext für Orbitype Intelligence liefern.

Node-Einstellungen

Entry-Node

Jeder Workflow hat genau eine Entry-Node. Das ist entweder per Konvention die erste Node auf dem Canvas oder die Node, die du in den Node-Einstellungen manuell als Entry festlegst.

Hard Limit

Nodes können bis zu 60 Minuten laufen. Da du Nodes aneinanderketten kannst, können dadurch sehr lang laufende Tasks entstehen. Um zu verhindern, dass Nodes in Situationen zu lange laufen, in denen sie klarerweise nur wenige Minuten dauern sollten, kannst du ein Hard Limit setzen, das die Laufzeit hart abbricht. Das ist kein sanfter Prozess, das heißt: Wenn die Node in ein Timeout läuft, erhältst du kein Output.
Das ist besonders hilfreich beim Experimentieren mit AI-Agents und Prompts, die noch nicht optimiert sind, weil diese sonst zu langen Läufen und verschwendeten Credits führen können, wenn kein Hard Limit gesetzt ist.

Benennung

Eine saubere Benennung von Nodes hilft dir, deinen Kolleginnen und Kollegen und Orbitpye Intelligence dabei, den Zweck jeder Node besser zu verstehen. Es ist wichtig, jede Node klar anhand ihrer Funktion zu benennen.

Node-Funktionsstruktur und Logging

Orbitype-Nodes müssen die folgende Syntax verwenden, wobei nur eine Funktion erlaubt ist. Innerhalb dieser Funktion kannst du normales Node.js verwenden. Bitte beachte, dass dies eine serverlose Umgebung ist, daher funktionieren einige OS-nahe Funktionen nicht.
In Orbitype-Funktionen musst du niemals etwas require oder importieren. Stattdessen kannst du die erlaubten Libraries und API-Produkte per Dependency Injection in die Funktion geben und dann nutzen.

async ({input}) => {
  return {output: input}
}


Wenn du Informationen loggen möchtest, empfiehlt es sich, ein debug-Array zu erstellen und Nachrichten hinein zu pushen. Am Ende kannst du es zusammen mit anderen Dingen zurückgeben. So kann die nächste Node Fehler analysieren oder du kannst sie weiterverarbeiten.

Node-Output und Next-Parameter

Output einer Node

Wenn du in der Funktion einfach etwas zurückgibst, wird das als Output gesetzt.
Wenn du mehr Kontrolle möchtest und außerdem Features wie next nutzen willst, musst du ein Objekt mit dem Key output zurückgeben. Dann legst du den Output dort ab und er wird als sauberer, nicht „boxed“ Input an die nächste Node weitergegeben.

async () => {
  const output = Math.random() 
  return {output, 
next: output > 0.5 ? "idOfTheNextNode" : "idOfTheOtherNextNode"}
}

Next-Attribut

Das next-Attribut erlaubt es dir, die nächste Node dynamisch zu bestimmen. Das funktioniert nur, wenn zwei oder mehr Nodes über den Output-Handle der Node verbunden sind.

async () => {
  const output = Math.random() 
  return {output, next: output > 0.5 ? "idOfTheNextNode" : "idOfTheOtherNextNode"}
}

Flow-Typen

Linear

Das ist ein einfacher Workflow, der von a -> b -> c läuft. Das ist der einfachste und häufigste Weg.

Branching

Wenn dein Workflow-Pfad an einem Punkt entscheiden muss, ob er mit Pfad a.a oder Pfad a.b weitergeht, kannst du den next-Parameter setzen, um das zu steuern.

Multi Input

Jeder Workflow hat nur eine Entry-Node, aber du kannst auch eine weitere Node hinzufügen und sie an die zweite Node anschließen, wenn das beim Testen eines Input-Payloads hilft. Später im Flow kann es auch passieren, dass du mehrere Inputs hast. Denk daran, dass das Payload je nach Input variieren kann, weil zwei oder mehr verschiedene Nodes es befüllen können. Wenn du in den vorherigen Nodes nicht mit demselben Format arbeitest, kann das in der Node mit mehreren Inputs zu Fehlern führen.

Looping

Das ist super, um Bedingungen innerhalb eines Workflows abzubilden, aber falsche Bedingungen können zu Endlosschleifen führen, die wiederum Tokens verbrennen. Nutze Looping nur in sehr kontrollierten Settings mit klaren Exit-Regeln.

Sub-Workflow-Architektur

Wenn ein Workflow aus klar trennbaren Sub-Workflows besteht, ergibt es meist Sinn, diese als eigene Workflows zu bauen und per Webhooks zu triggern. So bekommst du eine deutlich modularere Workspace-Struktur und kannst Sub-Teile isoliert nutzen und testen.

Orbitype-Environment Injections

Orbi-Objekt

Das ist ein Objekt, das du per Dependency Injection verwenden kannst. Es enthält viele Informationen über den aktuellen Workspace und ermöglicht dir programmatischen Zugriff darauf.

async ({ orbi, linkedin }) => {
  // start
  const id = orbi.project.credentials.find((x) => x.name === "linkedin").value
  const self = await linkedin.connect(id)
}

Zugangsdaten

Das ist ein direkterer Weg, um auf Zugangsdaten zuzugreifen. Er könnte jedoch zugunsten des Orbi-Objekts deprecated werden.

async ({ credentials }) => {
  // 
let apiKey = credentials.myApiKey
}

Agent

orbitype workflow functions haben Zugriff auf ein injiziertes agent-Objekt. Es ist ein vorkonfigurierter Wrapper um langchain mit zwei Methoden: addTool() registriert ein Tool, run() führt den Agent aus und gibt die finale Message zurück

Beispiel:

const func = async ({agent, zod}) => {
  const { z } = zod
  
  agent.addTool(
    (id) => {
      const url = `https://jsonplaceholder.typicode.com/todos/${id}`
      return fetch(url).then((x) => x.json())
    },
    {
      name: "fetch_todo",
      description: "Fetches a todo by ID from JSONPlaceholder",
      schema: z.string()
    },
  )
  
  agent.addTool(
    (text) => text.trim().split(/\s+/).length,
    {
      name: "count_words",
      description: "Counts the number of words in a given string",
      schema: z.string(),
      returnDirect: true,
    },
  )
  
  const data = await agent.run("number of words in todo with id 3")
  return data
}

Browser

orbitype workflow functions haben Zugriff auf ein injiziertes browser-Objekt. Dessen run-Methode führt Puppeteer-Code remote auf einem anderen Server aus. Die Funktion, die als erster Parameter übergeben wird, wird stringifiziert und via HTTP gesendet. Der zweite Parameter wird als JSON serialisiert und erlaubt es, Variablen von außen zu injizieren. Am Ende wird das Resultat zurückgegeben.

Beispiel:

async ({browser}) => {
  const url = "https://vuejs.org/guide/introduction.html"
  const clicks = 3
  const html = await browser.run(
    // puppeteer code
    async ({page, data}) => {
      await page.goto(data.url)
      const button = page.locator(".demo > button")
      for (let i = 0; i < data.clicks; i++)
        await button.click()
      return button.map((x) => x.textContent).wait()
    },
    // injected variables
    {url, clicks}
  )
  return html
}

KI

Nutze KI-Features ganz einfach in deinen Nodes:

async ({ai}) => {
  const text = await ai.chat("This is a test")
  // -> "this is an answer..."
  const json = await ai.chat("JSON for red rgb color", true)
  // -> {r: 255, g: 0, b: 0}
  return {text , json}
}

async ({ai}) => {
  return await ai.image("a red apple")
  // -> { base64: "iVBORw..." }
}

async ({ai}) => {
  return await ai.embedding("lorem ipsum")
  // -> [-0.0014445713,-0.021729672, ... ]
}

OCR

orbitype workflow functions haben Zugriff auf ein injiziertes ocr-Objekt. fileToText() gibt den reinen Text einer Datei zurück. fileToTable() gibt tabellarische Daten zurück, die aus der Datei extrahiert wurden. fileToForm() gibt Felder aus einem PDF-Formular zurück

Beispiele:

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/sample.pdf"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "dummmy.pdf", {type: "application/pdf"})
  const text = ocr.fileToText(file)
  return text
}

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/table.png"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "table.png", {type: "image/png"})
  const table = ocr.fileToTable(file)
  return table
}

async ({ocr}) => {
  const url = "https://cdn.orbitype.com/sXbDwXihqsp0/pdfs/table.png"
  const resp = await fetch(url)
  const blob = await resp.blob()
  const file = new File([blob], "table.png", {type: "image/png"})
  const table = ocr.fileToForm(file)
  return table
}

// tabular data is returned as a 2d array, e.g.
[
  ["name", "hex", "temperature"],
  ["red", "#FF0000", "warm"],
  ["blue", "#0000FF", "cold"],
  ["yellow", "#FFFF00", "warm"],
  ["cyan", "#00FFFF", "cold"]
]

LinkedIn (erweitert)

async ({ orbi, linkedin }) => {
  // start
  const id = orbi.project.credentials.find((x) => x.name === "linkedin").value
  const self = await linkedin.connect(id)
  
  // get other profiles
  const other = await linkedin.getProfile("julianvorraro")
  
  // chatting
  await linkedin.startChat("intro message", [other.provider_id])
  // await linkedin.resumeChat(chat_id)
  await linkedin.sendMessage("second meesage")
  const messages = await linkedin.getMessages()
  console.log(linkedin.chat_id, messages.items.map((x) => x.text))
  
  // posting and commenting
  await linkedin.createPost("test post")
  const posts = await linkedin.getPosts(self.provider_id)
  await linkedin.sendComment(posts.items[0].id, "test comment")
}

Bibliotheken

orbitype workflow functions haben Zugriff auf einige injizierte npm-Bibliotheken.

  • cheerio

  • imapflow

  • lodash

  • nodemailer

  • pdf-lib (verfügbar als PDFLib)

  • pdfkit (verfügbar als PDFDocument)

  • unpdf

  • swissqrbill

  • zod

zusätzliches manuelles Importieren/Requiren weiterer Libraries wird NICHT unterstützt

Beispiele:

const func = async ({lodash}) => {
  await new Promise((res) => setTimeout(res, 100))
  const output = lodash.random(0,9)
  return {output}
}

const func = async ({config, input, nodemailer}) => { 
  const transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,
    auth: {
      user: config.user,
      pass: config.pass,
    },
  })
  const mail = {
    from: config.user,
    to: input.email,
    subject: config.subject,
    text: config.text,
  }
  await transporter.sendMail(mail)
  return mail
}

const func = async ({ImapFlow}) => {
  const client = new ImapFlow({
    host: 'smtp.ethereal.email',
    port: 993,
    secure: true,
    auth: {
      user: "nathanial.oreilly@ethereal.email",
      pass: "BnK2wAJtg4P1Efu5zp",
    }
  })
  await client.connect()
  await client.mailboxOpen('INBOX')
  const message = await client.fetchOne('*', { source: true })
  await client.logout()
  return message.source.toString()
}

const func = async ({unpdf}) => {
  const url = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
  const res = await fetch(url)
  const buffer = await res.arrayBuffer()
  const pdf = await unpdf.getDocumentProxy(new Uint8Array(buffer))
  const { text } = await unpdf.extractText(pdf, { mergePages: true })
  return text
}

const func = async ({PDFLib}) => {
  const pdfDoc = await PDFLib.PDFDocument.create()
  const page = pdfDoc.addPage()
  page.drawText('lorem ipsum')
  const base64 = await pdfDoc.saveAsBase64({ dataUri: true })
  return base64
}

const func = async ({SwissQRBill, PDFDocument}) => {
  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({ size: 'A4' });
    const chunks = [];
    doc.on('data', (chunk) => chunks.push(chunk));
    doc.on('end', () => resolve(Buffer.concat(chunks).toString('base64')));
    doc.on('error', reject);

    doc.fontSize(20).text('Invoice #12345', 50, 50);
    doc.fontSize(12).text('Lorem ipsum...', 50, 100);

    const data = {
      amount: 1994.75,
      creditor: {
        account: "CH44 3199 9123 0008 8901 2",
        address: "Musterstrasse",
        buildingNumber: 7,
        city: "Musterstadt",
        country: "CH",
        name: "SwissQRBill",
        zip: 1234
      },
      currency: "CHF",
      debtor: {
        address: "Musterstrasse",
        buildingNumber: 1,
        city: "Musterstadt",
        country: "CH",
        name: "Peter Muster",
        zip: 1234
      },
      reference: "21 00000 00003 13947 14300 09017"
    };

    const qrBill = new SwissQRBill(data);
    qrBill.attachTo(doc);
    doc.end();
  });
}

Learn more

Cookbook - Sektionen

Moderne Webseiten bestehen oft aus vielen verschiedenen Sektionen.

Zum Beispiel Hero-Banner, Testimonials, FAQs oder einfache Textblöcke.

Entwickler setzen diese als wiederverwendbare Komponenten mit Props um.

Aber wie ermöglichen wir es Nicht-Technikern, sie in Orbitype zu nutzen?

Konto kündigen

Wir möchten sicherstellen, dass du Orbitype problemlos nutzen kannst. Falls du Fragen hast oder auf Schwierigkeiten stößt, zögere nicht, uns zu kontaktieren. Wir sind für dich da – schreib uns einfach eine Nachricht an support@orbitype.com, und wir helfen dir gerne weiter.