Prompt Engineering mit DSPy

Joshua Noble

Data Scientist

DSPy ist ein Open-Source-Python-Framework für die Erstellung von LLM-Anwendungen (Large Language Model) und die Feinabstimmung ihrer Leistung durch Code anstelle von einmaligen Techniken zur Prompt-Optimierung. Ein DSPy-Programm bietet eine modulare Möglichkeit zur Konfiguration und Feinabstimmung von LLM-Anwendungen, indem es Prompts optimiert, um genaue Ausgaben zu erhalten. Der Hauptvorteil von DSPy ist, dass es Ihnen ermöglicht, Prompt Engineering und das Tracking durch Python-Code durchzuführen, anstatt die Leistung selbst verfolgen zu müssen.

Die Stärke von DSPy besteht darin, dass es generative KI verwendet, um natürliche Sprache zu generieren und dann die Ergebnisse zu testen, um die effektivsten Prompts zu erstellen. Auf diese Weise können Sie ein sich selbst verbesserndes KI-System aufbauen. Es unterstützt eine Vielzahl von Schnittstellen zu Retrieval-Modellen und Sprachmodellen. Sie können Modelle lokal über Systeme wie ollama oder huggingface oder über eine API ausführen, wenn Sie ChatGPT oder GPT-4 von OpenAI verwenden. DSPy unterstützt eine Vielzahl von Anwendungsfällen wie Chain of Thought (CoT), Retrieval-Augmented Generation (RAG) sowie Zusammenfassungen. 

In diesem Tutorial werden Sie durch den Workflow geleitet, um eine RAG-Anwendung zur Beantwortung von Fragen mit DSPy auf IBM watsonx zu erstellen. Sie verwenden Llama 3 als Sprachmodell und ColBERT als Abrufmodell. Sie können DSPy zur Feinabstimmung der Prompts und zur Strukturierung verschiedener Ansätze für die Beantwortung von Fragen nutzen, um zu sehen, wie Sie selbst bei hochkomplexen Fragen bessere Antworten erhalten können.

Richten Sie Ihre Umgebung ein

Sie können zwar aus mehreren Tools wählen, aber dieses Tutorial führt Sie durch die Einrichtung eines IBM Kontos für die Verwendung eines Jupyter Notebook.

Melden Sie sich bei watsonx.ai mit Ihrem IBM Cloud-Konto an.

Erstellen Sie ein watsonx.ai-Projekt.

Sie können Ihre Projekt-ID in Ihrem Projekt abrufen.

Klicken Sie dann auf die Registerkarte „Verwalten“ und kopieren Sie die Projekt-ID aus dem Abschnitt „Details“ der Seite „Allgemein“. Sie benötigen diese ID für dieses Tutorial.

Erstellen Sie als Nächstes ein Jupyter Notebook in der Umgebung Ihrer Wahl. Sie kopieren den Code aus diesem Tutorial in das neue Notebook. Alternativ können Sie dieses Notebook auf Ihr lokales System herunterladen und als Asset in Ihr watsonx.ai-Projekt hochladen.

Richten Sie eine Watson Machine Learning (WML) Serviceinstanz und einen API-Schlüssel ein

Erstellen Sie eine Instanz des watsonx.ai Runtime Service (wählen Sie Ihre entsprechende Region und den Lite-Plan aus, der eine kostenlose Instanz darstellt).

Generieren Sie einen API-Schlüssel in watsonx.ai Runtime.

Verknüpfen Sie den watsonx.ai Runtime Service mit dem Projekt, das Sie in watsonx.ai erstellt haben.

Installieren Sie die DSPy-Bibliothek und richten Sie Ihre Anmeldeinformationen ein

Um DSPy zu verwenden, führen Sie eine einfache pip-Installation durch. Sie installieren dotenv auch, um Ihre Umgebungsvariablen zu verwalten:

!pip install dspy-ai python-dotenv;

Als Nächstes importieren Sie die Bibliotheken, die für den Rest dieses Tutorials benötigt werden:

import dspy
from dspy import LM
from dspy.datasets import HotPotQA
from dspy.teleprompt import BootstrapFewShot
import json
import os

from dotenv import load_dotenv
load_dotenv(os.getcwd()+’/.env’, override=True)

Zum Festlegen Ihrer Anmeldedaten benötigen Sie den WATSONX_APIKEY und die PROJECT_ID, die Sie in Schritt 1 generiert haben. Sie können sie entweder in einer .env-Datei in Ihrem Verzeichnis speichern oder den Platzhaltertext ersetzen. Sie legen auch die URL fest, die als API-Endgerät dient.

os.environ[‘WX_URL’] = “https://us-south.ml.cloud.ibm.com”
os.environ[‘WX_APIKEY’] = os.getenv(“WATSONX_APIKEY”, “”)

WATSONX_APIKEY= os.getenv(“WATSONX_APIKEY”, “”)
PROJECT_ID = os.getenv(“PROJECT_ID”,””)

Verwenden von watsonx mit DSPy

Jetzt konfigurieren Sie DSPy so, dass es mit watsonx-Modellen mit der DSPy LM-Klasse funktioniert. Mit dieser Klasse können Sie die watsonx-APIs aufrufen, um sowohl neue Prompts als auch Antworten auf diese Prompts zu generieren, die Sie testen können. Darunter verwendet DSPy eine weitere Bibliothek namens LiteLLM, um auf die watsonx-Dienste zuzugreifen. LiteLLM bietet einen einfachen Wrapper zum Aufrufen einer großen Vielzahl von LLM-APIs unter Verwendung des OpenAI-Formats, einschließlich Hugging Face, Azure und watsonx.

Bevor Sie auf Ihr watsonx-Konto zugreifen können, müssen Sie einen Token vom watsonx-Service mit Ihrem API-Schlüssel speichern, den Sie im ersten Schritt generiert haben. Rufen Sie die Betriebssystembibliothek auf, um auf „https://iam.cloud.ibm.com/identity/token“ zuzugreifen, Ihren Token abzurufen und ihn für die spätere Verwendung zu speichern.

token = os.popen(‘curl -k -X POST \
    --header “Content-Type: application/x-www-form-urlencoded” \
    --header “Accept: application/json” \
    --data-urlencode “grant_type=urn:ibm:params:oauth:grant-type:apikey” \
    --data-urlencode “apikey=’ + WATSONX_APIKEY + ‘” \
    “https://iam.cloud.ibm.com/identity/token”’).read()

Jetzt können Sie eine LanguageModel-Instanz erstellen, die watsonx verwendet. Verwenden Sie den Token, den Sie zuvor abgerufen haben, als API-Schlüssel und wir verwenden das Modell „llama-3-8b-instruct“ von Meta als Sprachmodell. Sie übergeben den Pfad zu diesem Modell an DSPy, damit es als Sprachmodell verwendet werden kann, zusammen mit der Temperatur, die das Sprachmodell verwenden soll. Weitere Informationen zur Konfiguration von LiteLLM für die Verwendung von watsonx finden Sie in den GitHub-Dokumenten. In diesem Fall gibt Ihnen 0,7 eine gewisse Kreativität ohne übermäßige Halluzination.

lm = dspy.LM(‘watsonx/meta-llama/llama-3-8b-instruct’, api_key=WATSONX_APIKEY, api_base=”https://us-south.ml.cloud.ibm.com”)

dspy.configure(lm=lm, trace=[], temperature=0.7, experimental=True)

Ein Abrufmodell hinzufügen

Jetzt laden Sie das Retrieval-Modell für das R Ihres RAG. Verwenden Sie ColBERTv2, um die Auszüge aus dem Wikipedia-Datensatz 2017 zu laden. ColBERT ist ein schnelles und genaues Abrufmodell, das eine skalierbare BERT-basierte Suche in großen Textsammlungen in Dutzenden von Millisekunden ermöglicht. ColBERT ist einfach eine der vielen Optionen, die zum Abrufen von Informationen aus einer Vektordatenbank verwendet werden können. Es ist vergleichbar mit anderen Vektordatenbanken wie Qdrant, Milvus, Pinecone, Chroma oder Weaviate.

Vektordatenbanken verfügen über einen bestimmten Satz von Informationen, auf die das Sprachmodell schnell zugreifen kann. In diesem Fall verwenden Sie eine Reihe von Zusammenfassungen aus Wikipedia 2017, um eine Vielzahl von Fakten für Ihr Sprachmodell bereitzustellen, die bei der Generierung verwendet werden können. Diese Kombination aus ColBERT und dem Wiki 17 Datensatz ist auch besonders nützlich, weil eine Version davon vom DSPy-Team kostenlos gehostet wird, sodass sie von jedem genutzt werden kann. Es bietet Zugriff auf eine Vielzahl von Informationen, ohne dass Sie Daten aufnehmen oder Ihr eigenes Vektordatenbanksystem einrichten müssen. Ein Nachteil dieses Datensatzes ist, dass er nichts über Ereignisse nach 2017 enthält, aber für die Zwecke der Demonstration ist er sehr nützlich.

Wenn Sie Ihre eigene Version von ColBERT mit Ihren eigenen Daten oder einem aktualisierten Datensatz ausführen möchten, sind diese Tutorials sehr hilfreich.

Laden Sie anschließend den HotPotQA-Datensatz und teilen Sie ihn in Trainings- und Testsätze auf, mit denen Sie Ihre Abfragekette testen können. HotpotQA ist ein Datensatz zur Beantwortung von Fragen, der natürliche Multi-Hop-Fragen enthält und die unterstützenden Fakten streng überwacht, um besser erklärbare Systeme zur Beantwortung von Fragen zu ermöglichen. 

colbertv2_wiki17_abstracts = dspy.ColBERTv2(url=’http://20.102.90.50:2017/wiki17_abstracts’)
dspy.configure(rm=colbertv2_wiki17_abstracts)

Testen der grundlegenden Qualitätssicherung

Nun erstellen Sie eine Signatur, die für Ihr erstes Beispiel verwendet wird. Eine Signatur ist eine Klasse, die Eingabe- und Ausgabetypen eines Moduls definiert und so die Kompatibilität zwischen verschiedenen Modulen in einem DSPy-Programm sicherstellt. Eine Signatur kombiniert mehrere Aufgaben wie das Aufnehmen einer Frage und die Ausgabe einer Antwort sowie die Argumentation des Modells. Die Signatur, die Sie hier verwenden, nimmt nur eine Frage auf und liefert eine Antwort:

class BasicQA(dspy.Signature):
    “””Answer questions with short factoid answers.”””

    question = dspy.InputField()
    answer = dspy.OutputField(desc=”often between 1 and 5 words”)

Sie verfügen jetzt über einen Prädiktor, den Sie einfach durch Aufrufen der Predict-Methode von DSPy testen können. Diese Methode verwendet die „newBasicQA“-Klasse, die Sie zuvor definiert haben, und verwendet diese Klasse, wenn Sie eine Frage an DSPy übergeben.

# Define the predictor.
generate_answer = dspy.Predict(BasicQA)

Jetzt erstellen Sie eine Frage, die mehrere Informationen erfordert, um sie richtig zu beantworten, und testen sie mit einer Architektur, die nur ein Sprachmodell verwendet. Sie verwenden die gerade erstellte Funktion generate_answer, um die Frage zu beantworten.

# Call the predictor on a particular input.
test_question = “What country was the winner of the Nobel Prize in Literature in 2006 from and what was their name?”

pred = generate_answer(question=test_question)

if pred == None:
    print(“ no answer “)
else:
    # Print the input and the prediction.
    print(f”Answer: Turkey, Orhan Pamuk”)
    print(f”Predicted Answer: {pred.answer}”)

Der Code gibt Folgendes zurück (Ihre Antwort kann anders lauten):

Answer: Turkey, Orhan Pamuk
Predicted Answer: The winner was France and the author was Orhan Pamuk.

Orhan Pamuk wurde 2006 mit dem Literaturnobelpreis ausgezeichnet, aber er stammt nicht aus Frankreich und die Formulierung der Antwort stimmt nicht. Sie erweitern nun das Modell durch Retrieval-Augmented Generation und lassen DSPy bessere Prompts entwickeln, um die Leistung zu verbessern.

Retrieval-Augmented Generation (RAG)

Retrieval-Augmented Generation (RAG) ist eine Architektur, die die Ausgabe eines großen Sprachmodells durch Referenzen aus einer maßgeblichen Wissensdatenbank optimiert. Dadurch werden die Trainingsdaten mit verifizierten Quellen ergänzt, bevor das Sprachmodell eine Antwort generiert. LLMs werden auf großen Korpussen trainiert und verwenden Milliarden von Parametern, um Ausgaben zu erzeugen, aber sie können möglicherweise nicht auf aktuelle oder genaue Informationen aus ihrem Trainingskorpus zugreifen. RAG erweitert die bereits leistungsstarken Fähigkeiten von LLMs auf einen bestimmten Bereich, ohne dass das Modell neu trainiert werden muss. Es ist eine leistungsfähige und potenziell kostenwirksame Möglichkeit, die Ausgaben von LLMs so zu verbessern, dass sie in verschiedenen Kontexten relevant, genau und nützlich bleiben.

In DSPy verwenden Sie eine RAG-Architektur, indem Sie einen Kontextschritt in der Signatur hinzufügen. In diesem Schritt wird der Kontext aus dem Abrufmodell erfasst und in der Prompt des Sprachmodells eingefügt, um hoffentlich eine bessere Antwort zu geben.

class GenerateAnswer(dspy.Signature):
    “””Answer questions with short factoid answers.”””

    context = dspy.InputField(desc=”may contain relevant facts”)
    question = dspy.InputField()
    answer = dspy.OutputField(desc=”often between 1 and 5 words”)

Diese newGenerateAnswer-Signatur kann mit Ihrem RAG-Modell verwendet werden. Sie übergeben die GenerateAnswer an das Modul „ChainOfThought“, sodass der abgerufene Kontext sowie Frage und Antwort einen ChainOfThought-Ansatz verwenden.

Sie aktualisieren auch die Vorwärtsmethode, um Kontextpassagen aus dem RAG zu generieren, und verwenden diese kontextbezogenen Passagen dann, um Antworten zu generieren. DSPy ruft diese Vorwärtsmethode jedes Mal auf, wenn es eine neue Antwort auf eine Frage generiert. Dabei sammelt es beide Kontexte aus dem ColBERT Wiki 17-Abstraktionsdatensatz und übergibt diesen Kontext dann an das Sprachmodell, in diesem Fall Llama 3.1. Wenn jede Antwort generiert wird, vergleicht DSPy die Ausgabe mit der gewünschten Ausgabe, um sicherzustellen, dass die Prompts dem Modell helfen, die richtigen Antworten zu generieren.

class RAG(dspy.Module):
    def __init__(self, num_passages=3):
        super().__init__()

        self.retrieve = dspy.Retrieve(k=num_passages)
        self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
   
    def forward(self, question):
        context = self.retrieve(question).passages
        prediction = self.generate_answer(context=context, question=question)
        return dspy.Prediction(context=context, answer=prediction.answer)

Um DSPy dabei zu unterstützen, die besten Prompts für uns zu entwickeln, benötigen Sie einen Datensatz, mit dem es Prompts testen und dann bewerten kann.

Um DSPy-Testfragen zu stellen, laden Sie den HotPotQA-Datensatz. HotpotQA ist ein Datensatz zur Beantwortung von Fragen mit natürlichen Multi-Hop-Fragen, die mehrere Abrufe und Inferenzen erfordern, um die richtige Antwort zu finden. Es ist ein großartiges Tool, um zu testen, wie gut Modelle unterstützende Fakten generieren, um besser erklärbare Fragen-Antwort-Systeme zu trainieren und zu testen. 

Eine Frage aus dem Datensatz lautet zum Beispiel: „Wen hat Präsident Franklin Roosevelt dazu ernannt, für die Übermittlung der Stimmen des Wahlgremiums für den Kongress verantwortlich zu sein?“ Sie können sehen, dass für diese Frage mehrere Informationen erforderlich sind, um sie richtig zu beantworten.

The answer is: “Robert Digges Wimberly Connor”.

Der unterstützende Kontext stammt von Wikipedia-Seiten über Robert Digges Wimberly Connor und über die National Archives and Records Administration.

HotPotQA wird von einem Team von NLP-Forschern an der Carnegie Mellon University, der Stanford University und der Universite de Montreal erfasst und veröffentlicht. Weitere Informationen zu HotPotQA finden Sie auf der GitHub-Website.

Nachdem Sie den Datensatz geladen haben, teilen Sie ihn in Trainings- und Testsätze auf. Auf diese Weise können Sie die Abrufkette testen und DSPy dabei helfen, die besten Prompts für das Sprachmodell zu finden.

# Load the dataset.
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)

# Tell DSPy that the ‘question’ field is the input. Any other fields are labels and/or metadata.
trainset = [x.with_inputs(‘question’) for x in dataset.train]
devset = [x.with_inputs(‘question’) for x in dataset.dev]

Als Nächstes führen Sie ein Bootstrapping weiterer Beispiele durch, um DSPy mehr Möglichkeiten zu geben, Prompts zu generieren und diese auszuwerten. Callingcompile nutzt die gesamte Architektur, die Sie konfiguriert haben, sowie den HotPotQA-Datensatz, um Prompts zu generieren, zu testen und die beste Leistung aus Ihrem Sprachmodell herauszuholen.

  from dspy.teleprompt import BootstrapFewShot

# Validation logic: check that the predicted answer is correct.
# Also check that the retrieved context does actually contain that answer.
def validate_context_and_answer(example, pred, trace=None):
    answer_EM = dspy.evaluate.answer_exact_match(example, pred)
    answer_PM = dspy.evaluate.answer_passage_match(example, pred)
    return answer_EM and answer_PM

# Set up a basic DSPy optimizer, which will compile your RAG program.
bfs_optimizer = BootstrapFewShot(metric=validate_context_and_answer)

# Compile!
compiled_rag = bfs_optimizer.compile(RAG(), trainset=trainset)

Nachdem DSPy nun das Prompt Engineering für Sie durchgeführt hat, können Sie es mit der benutzerdefinierten Frage zum November 2006 testen, die Sie zuvor verwendet haben. Da das Retrieval-Modell Wikipedia-Auszüge aus dem Jahr 2017 verwendet, funktioniert es am besten mit Wissen, das in diesem Korpus vorhanden sein könnte:

# Get the prediction. This contains `pred.context` and `pred.answer`.
pred = compiled_rag(test_question)

# Print the contexts and the answer.
print(f”Question: {test_question}”)
print(f”Predicted Answer: {pred.answer}”)

Jetzt erhalten Sie die richtige Antwort zurück. 

    Frage: Aus welchem Land ging der Gewinner des Literaturnobelpreises 2006 hervor und wie lautete sein Name?
    Prognostizierte Antwort: Türkei, Orhan Pamuk

Orhan Pamuk kommt aus der Türkei, daher ist diese Antwort richtig. Die kompilierte Version von DSPy hat die Antwort nicht nur richtig verstanden, sondern sie auch richtig formuliert und mit einer kurzen und klaren Aussage beantwortet. Sehen wir uns den Kontext für diese prognostizierte Antwort an, um zu sehen, wie das Modell zur richtigen Antwort gekommen ist:

pred.context

Dies liefert Folgendes:

    [„Orhan Pamuk | Ferit Orhan Pamuk (allgemein bekannt als Orhan Pamuk; * 7. Juni 1952) ist ein türkischer Romanautor, Drehbuchautor, Akademiker und Gewinner des Literaturnobelpreises 2006. Als einer der bekanntesten Romanautoren der Türkei wurden mit seinem Werk über dreizehn Millionen Bücher in 63 Sprachen verkauft, was ihn zum meistverkauften Autor des Landes macht.“,
     „2006 Palanca Awards | Die Gewinner der Carlos Palanca Memorial Awards for Literature im Jahr 2006 (Rang, Titel des Gewinnerbeitrags, Name des Autors).“,
     „Miguel Donoso Pareja | Miguel Donoso Pareja (13. Juli 1931 – 16. März 2015) war ein ecuadorianischer Schauspieler und Gewinner des Premio Eugenio Espejo-Preises 2006 (Nationalpreis von Ecuador für Literatur, verliehen vom Präsidenten von Ecuador).“]

Die Antwort liegt im ersten Teil des zurückgegebenen Kontexts. Sie können sehen, wie DSPy optimale Prompts entwickelt hat, indem Sie sich die Historie des Sprachmodells mit der Methode inspect_history() des Sprachmodells ansehen.

lm.inspect_history()

Diese Historie ist sehr lang, da sie alle Beispiele aus dem Kompilierungsprozess enthält, bei dem DSPy seine generierten Prompts getestet hat. Der letzte Teil der Historie zeigt, wie das Modell zur richtigen Antwort im richtigen Format gelangt ist:

    [[ ## Kontext ## ]]
[1] «Orhan Pamuk | Ferit Orhan Pamuk (allgemein einfach als Orhan Pamuk bekannt; geboren am 7. Juni 1952) ist ein türkischer Romanautor, Drehbuchautor, Akademiker und Gewinner des Literaturnobelpreises 2006. Als einer der bekanntesten Romanautoren der Türkei wurden mit seinem Werk über dreizehn Millionen Bücher in 63 Sprachen verkauft, was ihn zum meistverkauften Autor des Landes macht.
    [2] «Palanca Awards 2006 | Die Gewinner der Carlos Palanca Memorial Awards for Literature im Jahr 2006 (Rang, Titel des Gewinnerbeitrags, Name des Autors).»
    [3] «Miguel Donoso Pareja | Miguel Donoso Pareja (13. Juli 1931 – 16. März 2015) war ein ecuadorianischer Schauspieler und Gewinner des Premio Eugenio Espejo-Preises 2006 (Nationalpreis von Ecuador für Literatur, verliehen vom Präsidenten von Ecuador).»
    
    [[ ## Frage ## ]] Aus welchem Land kam der Gewinner des Literaturnobelpreises 2006 und wie lautet sein Name?
    
    Antworten Sie mit den entsprechenden Ausgabefeldern, beginnend mit dem Feld „[[ ## Reasoning ## ]]“, dann mit „[[ ## Answer ## ]]“ und enden mit der Markierung für „[[ ## completed # # ]]“.
    
    
    [31mResponse:[0m [32m[[ ## Argumentation ## ]] Der Text erwähnt den Literaturnobelpreis 2006 und besagt, dass Orhan Pamuk, ein türkischer Romanautor, der Gewinner war.
    
    [[ ## Antwort ## ]] Türkei, Orhan Pamuk [[ ## abgeschlossen ## ]][0m
    

Sie können sehen, dass DSPy das Modell verwendet hat, um den Prompt zu generieren:

Antworten Sie mit den entsprechenden Ausgabefeldern, beginnend mit dem Feld [[ ## reasoning ## ]] , dann [[ ## answer ## ]] und endet dann mit dem Marker für [[ ## completed ## ]] .

Dies führt zur richtigen Antwort und Einordnung.

Zusammenfassung

In diesem Tutorial haben Sie DSPy verwendet, um die Feinabstimmung eines RAG-Agenten mithilfe der watsonx-Plattform zu unterstützen. Ihr RAG-Agent bestand aus einem Sprachmodell, Llama 3 und einem Retrieval-Modell, ColBERT. Anschließend haben Sie mit DSPy ein Prompt-Engineering für eine Aufgabe zur Fragenbeantwortung durchgeführt, indem Sie Ihr Modell kompiliert und einen optimierten Prompt generiert haben.

Sie können mehr über DSPy in ihrem GitHub-Repository erfahren, wo Tutorials, Demos und ihre Dokumente gehostet werden.

Weiterführende Lösungen
IBM watsonx.ai

Trainieren, validieren, optimieren und implementieren Sie generative KI, Foundation Models und maschinelle Lernfunktionen mit IBM watsonx.ai, einem Studio der nächsten Generation für AI Builder in Unternehmen. Erstellen Sie KI-Anwendungen mit einem Bruchteil der Zeit und Daten.

watsonx.ai erkunden
Lösungen im Bereich künstlicher Intelligenz

Setzen Sie KI in Ihrem Unternehmen ein
– mit branchenführendem Fachwissen im Bereich KI und dem umfassenden Lösungsportfolio von IBM an Ihrer Seite.

Erkunden Sie KI-Lösungen
Beratung und Services zu künstlicher Intelligenz (KI)

Die KI-Services von IBM Consulting unterstützen Sie dabei, die Art und Weise, wie Unternehmen mit KI arbeiten, neu zu denken.

KI-Services entdecken
Machen Sie den nächsten Schritt

Mithilfe von KI liefert IBM Concert wichtige Erkenntnisse über Ihre Abläufe und gibt anwendungsspezifische Empfehlungen zur Verbesserung. Entdecken Sie, wie Concert Ihr Unternehmen voranbringen kann.

Entdecken Sie Concert Erkunden Sie Lösungen zur Geschäftsprozessautomatisierung