So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

In dem vorherigen Tutorial haben wir die App-Entwicklung mit dem CrewAI-Framework unter Verwendung von Streamlit durchlaufen. Die erstellte App war jedoch recht basic und demonstrierte lediglich eine CrewAI-Workflow-Visualisierung, die durch den initialen Prompt eines Benutzers ausgelöst wurde. Es fehlte die Interaktion zwischen Menschen und der Agenten-Gruppe, nachdem der Workflow begonnen hatte. In einer typischen Multi-Agenten-Anwendung ist die menschliche Eingabe entscheidend, um sicherzustellen, dass jeder von KI-Agenten generierte Schritt überprüft und genehmigt werden kann. Daher werden wir heute eine neue Version der CrewAI-App erstellen, die es Agenten ermöglicht, im visualisierten Chat-Fenster eine menschliche Eingabe anzufordern, und es Menschen erlaubt, über das Chat-Eingabefeld auf der Website Feedback zu geben.

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

Insbesondere habe ich mich in diesem neuen UI-Design dafür entschieden, von Streamlit als UI-Builder Abstand zu nehmen. Ich werde die Gründe für diese Entscheidung später erläutern. Stattdessen werde ich Panel nutzen, ein Framework, das ich zuvor für AutoGen-Projekte verwendet habe, um das gesamte CrewAI-Visualisierungsdesign neu zu gestalten und die Möglichkeiten der Interaktion für Menschen zu erweitern.

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

CrewAI in Panel

Warum nicht Streamlit verwenden

Anfangs versuchte ich, die ursprüngliche CrewAI + Streamlit-Entwicklung zu erweitern, um die Interaktion mit Menschen zu integrieren. Es gab jedoch eine grundlegende Einschränkung innerhalb des Streamlit-Frameworks, die diesen Ansatz undurchführbar machte, aufgrund des prinzipiellen Designs von Streamlit.

Wenn Sie sich den Chatbot-Demo-Code für Streamlit ansehen, finden Sie immer wieder Codefragmente wie dieses am Anfang jedes Programms:

for msg in st.session_state.messages:
    st.chat_message(msg["role"]).write(msg["content"])

Diese Schleife ist dafür verantwortlich, alle historischen Chat-Nachrichten rekursiv anzuzeigen. Als webbasiertes Python-Framework arbeitet Streamlit nach dem „Refresh“-Konzept. Das bedeutet, dass jede Interaktion mit Streamlit-Widgets, wie Nachrichteneingabe oder Menüauswahl, einen internen Refresh auslöst, der die Anwendung ab der ersten Zeile neu ausführt. Folglich müssen alle Zwischendaten in einem Cache-Objekt session_state gespeichert werden, um ihre Existenz nach dem Reset zu gewährleisten.

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

Dieser Ansatz funktioniert gut für einfache LLM-gestützte Chatbot-Anwendungen. Wir können die Benutzereingabe der Chat-Historie hinzufügen und das Sprachmodell zur Inferenz aufrufen, unabhängig von App-Resets. Er wird jedoch unpraktisch für komplexe LLM-gestützte Anwendungen wie strukturierte Multi-Agenten-Apps von CrewAI.

Die kollaborative Natur des internen Prozesses von Denken-Entscheiden-Handeln innerhalb eines Multi-Agenten-Workflows passt nicht gut zum Store-Restore-Paradigma, insbesondere wenn die Interaktion mit Menschen involviert ist. In CrewAI hält ein Agent, wenn er eine menschliche Eingabe anfordert, den gesamten Workflow an und verwendet die Standard-Eingabefunktion input(), um auf die Benutzereingabe im Terminal zu warten. Wenn wir die Standardeingabe durch das Chat-Eingabe-Widget von Streamlit in unserem Design ersetzen würden, würde jede Benutzereingabe einen Workflow-Neustart auslösen, wodurch die generierten Gedanken und Aktionen verloren gingen.

Daher benötigen wir ein geeigneteres Framework, das in der Lage ist, die Laufzeitsitzung sowohl der App als auch der Weboberfläche über mehrere Runden von Ein- und Ausgaben hinweg aufrechtzuerhalten.

Lassen Sie uns nun unseren Fokus auf Panel richten und untersuchen, wie es das UI-Design mit nahtloser Mensch-Agenten-Interaktion innerhalb unserer CrewAI-Anwendung erreicht.

CrewAI + Panel

Wie Panel funktioniert

Bevor wir in den Code des gesamten CrewAI-Prozesses einsteigen, werfen wir einen kurzen Blick auf das Panel-Framework.

Als Web-Entwicklungs-Framework bietet Panel eine Reihe leistungsstarker, aber einfach zu verwendender Widgets, mit denen Entwickler von Daten-Apps schnell ihre Ideen, Experimente oder finalen Projekte visualisieren können, ohne HTML-Kenntnisse zu benötigen. Um den explosiven Anforderungen von LLM-Apps gerecht zu werden, haben sie ihre Widget-Bibliothek um Chatbot-Vorlagen namens ChatInterface erweitert, die in diesem CrewAI-Demo-Projekt verwendet werden.

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

Im Gegensatz zu Streamlit fungiert Panel als „Server“ im Hintergrund, ohne die App aktiv zurückzusetzen, so dass wir die Funktionslogik sequenziell im Hauptteil oder in Callbacks wie bei einem normalen Python-Programm platzieren können.

Um einen einfachen Panel-Server zu starten, importieren Sie zunächst das Paket:

import panel as pn

Wählen Sie einen Stil für das Gesamterscheinungsbild der Benutzeroberfläche.

pn.extension(design="material")

Starten Sie den Server mit ChatInterface-Widgets.

chat_interface = pn.chat.ChatInterface(callback=callback)
chat_interface.send("Senden Sie eine Nachricht!", user="System", respond=False)
chat_interface.servable()

Die Hauptfunktion unserer CrewAI wird im callback implementiert, der später eingeführt wird. Um Nachrichten im Chat-Bereich hinzuzufügen, verwenden Sie einfach die Methode .send(). Nachdem das Programm mit dem Befehl im Terminal ausgeführt wurde:

panel serve app.py

finden Sie die folgende Ausgabe auf Ihrem Terminal, die anzeigt, dass die App erfolgreich läuft.

2024–04–21 19:12:46,169 Starting Bokeh server version 3.4.0 (running on Tornado 6.4) 2024–04–21 19:12:46,171 User authentication hooks NOT provided (default user enabled) 2024–04–21 19:12:46,176 Bokeh app running at: http://localhost:5006/crewai_panel 2024–04–21 19:12:46,176 Starting Bokeh server with process id: 5308

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

CrewAI UI-Entwicklung

Jetzt, da wir wissen, wie man eine Panel-App erstellt, sehen wir uns an, wie wir den CrewAI-Workflow darin integrieren.

Unser Demo-Workflow wird ein Copywriting-Studio implementieren, das dem Benutzer ermöglicht, das Thema für eine Schreibaufgabe einzugeben. Anschließend wird die Benutzeroberfläche zeigen, wie der Orchestrator-Agent den Writer-Agenten und den Reviewer-Agenten nacheinander anweist, und es wird eine menschliche Eingabe geben, um eine Bestätigung zu erhalten, bevor das Endergebnis generiert wird.

So erstellen Sie eine interaktive Benutzeroberfläche für CrewAI-Anwendungen

Copywriting-Studio mit Mensch-Interaktion

Um diesen Workflow zu implementieren, folgen wir einfach den Richtlinien des CrewAI-Frameworks, um einen Agenten, eine Aufgabe, ein Crew und einen Prozess zu erstellen.

Importieren der Abhängigkeiten.

from crewai import Crew, Process, Agent, Task
from langchain_openai import ChatOpenAI

Definition eines Sprachmodells.

llm = ChatOpenAI(model="gpt-3.5-turbo-0125")

Erstellung von zwei Agenten für das Schreiben und Überprüfen der generierten Artikel. Stellen Sie in Ihren eigenen Projekten sicher, dass Sie eine verfeinerte rolebackstory und goal haben, um den Agenten als Systembeschreibung anzugeben.

writer = Agent(
    role='Blog Post Writer',
    backstory='''You are a blog post writer who is capable of writing a travel blog.
                      You generate one iteration of an article once at a time.
                      You never provide review comments.
                      You are open to reviewer's comments and willing to iterate its article based on these comments.
                      ''',
    goal="Write and iterate a decent blog post.",
    llm=llm,
    callbacks=[MyCustomHandler("Writer")],
)
reviewer = Agent(
    role='Blog Post Reviewer',
    backstory='''You are a professional article reviewer and very helpful for improving articles.
                 You review articles and give change recommendations to make the article more aligned with user requests.
                 You will give review comments upon reading entire article, so you will not generate anything when the article is not completely delivered. 
                  You never generate blogs by itself.''',
    goal="list builtins about what need to be improved of a specific blog post. Do not give comments on a summary or abstract of an article",
    llm=llm,
    callbacks=[MyCustomHandler("Reviewer")],
)

Um die Ausgabe von Agenten umzuleiten, definieren wir hier einen benutzerdefinierten Callback-Handler MyCustomHandler(role_name), um unsere bevorzugte Ausgabe auf die Panel-Oberfläche zu drucken.

from langchain_core.callbacks import BaseCallbackHandler
from typing import TYPE_CHECKING, Any, Dict, Optional
avators = {"Writer":"https://cdn-icons-png.flaticon.com/512/320/320336.png",
            "Reviewer":"https://cdn-icons-png.freepik.com/512/9408/9408201.png"}
class MyCustomHandler(BaseCallbackHandler):
    def __init__(self, agent_name: str) -> None:
        self.agent_name = agent_name
    def on_chain_start(
        self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
    ) -> None:
        """Print out that we are entering a chain."""
        chat_interface.send(inputs['input'], user="assistant", respond=False)
    def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
        """Print out that we finished a chain."""
        chat_interface.send(outputs['output'], user=self.agent_name, avatar=avators[self.agent_name], respond=False)

Dieser Callback-Handler-Objekt ist von einer Klasse abgeleitet, die von LangChains Klasse BaseCallbackHandler stammt und die wichtigsten Stadien während des AgentAction-Prozesses verfolgt. In unserem Fall zeigen wir die Anweisungsnachricht vom Orchestrator durch das Ereignis on_chain_start und die Antwortnachricht vom jeweiligen Agenten durch das Ereignis on_chain_end in der Generierungssequenz an.

Fahren wir fort mit der Definition einer Funktion StartCrew mit einer Benutzeranweisung prompt, um die Chat-Gruppe innerhalb dieser Agenten und des Orchestrators zusammenzufassen.

def StartCrew(prompt):
    task1 = Task(
      description=f"""Write a blog post of {prompt}. """,
      agent=writer,
      expected_output="an article under 100 words."
    )
    task2 = Task(
      description=("list review comments for improvement from the entire content of blog post to make it more viral on social media."
                    "Make sure to check with a human if your comment is good before finalizing your answer."
                ),
      agent=reviewer,
      expected_output="Builtin points about where need to be improved.",
      human_input=True,
    )
    # Establishing the crew with a hierarchical process 
    project_crew = Crew(
        tasks=[task1, task2],  # Tasks to be delegated and executed under the manager's supervision
        agents=[writer, reviewer],
        manager_llm=llm,
        process=Process.hierarchical  # Specifies the hierarchical management approach
    )
    result = project_crew.kickoff()
    chat_interface.send("## Endergebnis\n"+result, user="assistant", respond=False)

Hier definieren wir zwei Aufgaben in Bezug auf die beiden Agenten und konsolidieren sie in der project_crew sowie den Prozess als hierarchical.

Bitte beachten Sie insbesondere, dass Sie human_input=True in einer Aufgabe setzen müssen, in die Sie die Interaktion mit Menschen einbeziehen möchten, und in der Aufgaben-description erwähnen, dass mit Menschen eine Überprüfungsbestätigung erfolgen soll.

Wir werden nicht direkt die Funktion StartCrew() aufrufen. Sie ist für den Aufruf im Callback von Panel definiert. Falls Sie sich erinnern, haben wir einen Callback bei der Initialisierung der Panel-ChatInterface zu Beginn registriert, der durch jede Nachrichteneingabe ausgelöst wird.

chat_interface = pn.chat.ChatInterface(callback=callback)

Hier ist die Definition des callback.

import threading 
import time
user_input = None
initiate_chat_task_created = False
def initiate_chat(message):
    global initiate_chat_task_created
    # Indicate that the task has been created
    initiate_chat_task_created = True
    StartCrew(message)
def callback(contents: str, user: str, instance: pn.chat.ChatInterface):
    global initiate_chat_task_created
    global user_input
    if not initiate_chat_task_created:
        thread = threading.Thread(target=initiate_chat, args=(contents,))
        thread.start()
    else:
        user_input = contents

In dieser einfachen Definition verwenden wir den Threading-Mechanismus, um StartCrew() (tatsächlich .kickoff()) in einem Thread auszuführen, damit die Callback-Funktion nicht durch die CrewAI-Verarbeitung blockiert wird. Threading ist der Schlüssel zur Ermöglichung der Interaktion mit Menschen, denn wenn „Warten auf Benutzereingabe“ den Prozess blockiert, kann der callback()-Handler noch ausgelöst werden, um die Benutzereingabe zu behandeln.

Benutzereingabe

Da CrewAI keine Schnittstelle für die Übergabe benutzerdefinierter Methoden der Benutzereingabe bereitstellt, müssen wir einen „Monkey Patch“ durchführen, um temporär die interne Funktion _ask_human_input der Klasse CrewAgentExecutor zu überschreiben.

from crewai.agents import CrewAgentExecutor
import time
def custom_ask_human_input(self, final_answer: dict) -> str:
      global user_input
      prompt = self._i18n.slice("getting_input").format(final_answer=final_answer)
      chat_interface.send(prompt, user="assistant", respond=False)
      while user_input == None:
          time.sleep(1)  
      human_comments = user_input
      user_input = None
      return human_comments
CrewAgentExecutor._ask_human_input = custom_ask_human_input

In dieser benutzerdefinierten Funktion zeigen wir zuerst die vom Orchestrator generierte Aufforderungsnachricht an, um den Benutzer zur Eingabe aufzufordern. Dann gibt der Prozess die Ausführung in einen sleep für user_input aus dem Panel-callback()-Handler frei. Durch Überschreiben der ursprünglichen, auf der Standard-input()-basierten CrewAgentExecutor._ask_human_input wird jedes Mal, wenn der Workflow zur Mensch-Interaktion übergeht, diese in der Benutzeroberfläche angezeigt.

Das war’s mit dem Code. Jetzt können Sie alles zusammenführen und Ihre eigene CrewAI-Anwendung mit Ihren verfeinerten Agenten und Aufgaben auf dieser anständigen Web-Oberfläche ausführen. Viel Spaß beim Coden!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert