Skip to content

Konfigurierbare Agentenformulare

Dieser Leitfaden erklärt, wie Sie Agenten-Konfigurationsformulare definieren, die es Administratoren ermöglichen, Agentenprofile ohne Codeänderungen zu erstellen und anzupassen.

Überblick

Das SDK verwendet das Formular-Dualitätsmuster, bei dem ein einziges Pydantic-Modell zwei Zwecken dient:

  1. Formularmodus: Felder enthalten FormkitElement-Instanzen, die das UI-Formular definieren
  2. Datenmodus: Felder enthalten primitive Werte, die die validierte Konfiguration speichern

Dieses Muster stellt sicher, dass das Formularschema und das Datenmodell nicht desynchronisiert werden können.

Grundlegendes Muster

Definieren einer konfigurierbaren AgentConfig

python
from typing import Annotated
from pydantic import Field

from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.form.constraints import Ge, Le
from swiss_ai_hub.core.form.elements import InputNumber, InputText


class MyAgentConfig(AgentConfig):
    # Field with duality: str for data mode, InputText for form mode
    model_name: Annotated[
        str | InputText,
        Field(description="The LLM model to use"),
    ] = "gpt-4"

    # Numeric field with constraints
    temperature: Annotated[
        float | InputNumber,
        Field(description="LLM temperature"),
        Ge(0.0),  # Minimum value
        Le(1.0),  # Maximum value
    ] = 0.7

    @classmethod
    def as_form(cls) -> "MyAgentConfig":
        """Create form-mode config with FormKit elements for UI rendering."""
        base = AgentConfig.as_form()
        return cls(
            # Inherit base fields from AgentConfig
            agent_id=base.agent_id,
            name=base.name,
            description=base.description,
            icon=base.icon,
            # Define form elements for custom fields
            model_name=InputText(
                label=LocaleString(en="Model", de="Modell"),
                help=LocaleString(en="Select the LLM model to use"),
            ),
            temperature=InputNumber(
                label=LocaleString(en="Temperature", de="Temperatur"),
                help=LocaleString(en="Controls response creativity (0=focused, 1=creative)"),
                min=0.0,
                max=1.0,
                step=0.1,
            ),
        )

Registrierung beim AgentRunner

python
from swiss_ai_hub.agent.runners.agent_runner import AgentRunner

from .MyAgent import MyAgent
from .MyAgentConfig import MyAgentConfig


async def main():
    runner = AgentRunner(
        agent_type=MyAgent,
        agent_config=MyAgentConfig.as_form(),  # Form mode!
    )
    await runner.run_forever()

Der Aufruf von as_form() erstellt eine Konfigurationsinstanz im Formularmodus. Wenn der Agent sich über Discovery registriert, wird das Formularschema extrahiert und gespeichert. Administratoren können dann Profile über die Admin-Benutzeroberfläche erstellen.

Verfügbare FormKit-Elemente

Texteingabe

python
from swiss_ai_hub.core.form.elements import InputText

system_prompt: Annotated[str | InputText, Field()] = "You are a helpful assistant."

# In as_form():
system_prompt=InputText(
    label=LocaleString(en="System Prompt"),
    help=LocaleString(en="Instructions for the agent"),
    required=True,
)

Numerische Eingabe

python
from swiss_ai_hub.core.form.elements import InputNumber
from swiss_ai_hub.core.form.constraints import Ge, Le

max_tokens: Annotated[int | InputNumber, Field(), Ge(1), Le(4096)] = 1024

# In as_form():
max_tokens=InputNumber(
    label=LocaleString(en="Max Tokens"),
    min=1,
    max=4096,
    step=1,
)

Boolescher Umschalter

python
from swiss_ai_hub.core.form.elements import ToggleSwitch

enable_citations: Annotated[bool | ToggleSwitch, Field()] = True

# In as_form():
enable_citations=ToggleSwitch(
    label=LocaleString(en="Enable Citations"),
    help=LocaleString(en="Include source citations in responses"),
)
python
from swiss_ai_hub.core.form.elements import Select

response_format: Annotated[str | Select, Field()] = "text"

# In as_form():
response_format=Select(
    label=LocaleString(en="Response Format"),
    options=[
        {"label": "Plain Text", "value": "text"},
        {"label": "Markdown", "value": "markdown"},
        {"label": "JSON", "value": "json"},
    ],
)

Mehrsprachige Eingabe

python
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.form.elements import LocaleInput

greeting: Annotated[LocaleString | LocaleInput, Field()]

# In as_form():
greeting=LocaleInput(
    label=LocaleString(en="Greeting Message"),
    help=LocaleString(en="Shown when conversation starts"),
)

Modell-Auswahl

python
from swiss_ai_hub.core.form.elements import ModelSelect

llm_model: Annotated[str | ModelSelect, Field()] = "gpt-4"

# In as_form():
llm_model=ModelSelect(
    label=LocaleString(en="Language Model"),
    help=LocaleString(en="Select from available models"),
)

Das ModelSelect-Element wird zur Laufzeit automatisch aus dem LiteLLM-Modellregister befüllt.

Formularsichere Constraints

Standard-Pydantic-Constraints (Field(ge=0, le=1)) funktionieren nicht mit dem Dualitätsmuster, da sie FormkitElement-Typen nicht validieren können. Verwenden Sie stattdessen die vom SDK bereitgestellten Constraints:

python
from swiss_ai_hub.core.form.constraints import Ge, Le, Gt, Lt, MinLen, MaxLen, Pattern

# Numeric constraints
temperature: Annotated[float | InputNumber, Ge(0.0), Le(1.0)] = 0.7
min_score: Annotated[float | InputNumber, Gt(0.0)] = 0.1  # Greater than (exclusive)

# String constraints
api_key: Annotated[str | InputText, MinLen(10), MaxLen(100)] = ""
agent_id: Annotated[str | InputText, Pattern(r"^[a-z0-9_-]+$")] = ""

Diese Constraints überspringen die Validierung, wenn das Feld ein FormkitElement enthält, wodurch die Pydantic-Validierung in beiden Modi funktioniert.

Nicht-konfigurierbare Felder

Einige Felder sollten nicht im Formular erscheinen (bereitstellungsspezifische Konfiguration). Lassen Sie die FormKit-Element-Alternative weg:

python
class MyAgentConfig(AgentConfig):
    # Configurable (appears in form) - has FormKit alternative
    model_name: Annotated[str | InputText, Field()] = "gpt-4"

    # Non-configurable (set at deployment) - no FormKit alternative
    channel_config: TeamsConfig

    @classmethod
    def as_form(cls, channel_config: TeamsConfig) -> "MyAgentConfig":
        base = AgentConfig.as_form()
        return cls(
            agent_id=base.agent_id,
            name=base.name,
            description=base.description,
            icon=base.icon,
            model_name=InputText(label=LocaleString(en="Model")),
            channel_config=channel_config,  # Actual value, not FormKit element
        )

Nicht-konfigurierbare Felder werden zur Laufzeit mit der vom Benutzer übermittelten Konfiguration zusammengeführt. Das Formular zeigt nur konfigurierbare Felder an.

Verschachtelte Formulare

Formulare können andere Formulare unter Verwendung des Group-Elements enthalten:

python
from swiss_ai_hub.core.form.form import Form
from swiss_ai_hub.core.form.elements import InputNumber, ModelSelect


class LLMConfig(Form):
    """Nested form for LLM settings."""

    model_name: Annotated[str | ModelSelect, Field()] = "gpt-4"
    temperature: Annotated[float | InputNumber, Ge(0.0), Le(1.0)] = 0.7
    max_tokens: Annotated[int | InputNumber, Ge(1), Le(4096)] = 1024

    @classmethod
    def as_form(cls) -> "LLMConfig":
        return cls(
            model_name=ModelSelect(label=LocaleString(en="Model")),
            temperature=InputNumber(label=LocaleString(en="Temperature"), min=0.0, max=1.0),
            max_tokens=InputNumber(label=LocaleString(en="Max Tokens"), min=1, max=4096),
        )


class MyAgentConfig(AgentConfig):
    llm: Annotated[LLMConfig, Field(title="LLM Settings")]

    @classmethod
    def as_form(cls) -> "MyAgentConfig":
        base = AgentConfig.as_form()
        return cls(
            agent_id=base.agent_id,
            name=base.name,
            description=base.description,
            icon=base.icon,
            llm=LLMConfig.as_form(),  # Nested form
        )

Verschachtelte Formulare werden in der Benutzeroberfläche automatisch als einklappbare Group-Elemente gerendert.

Repeater-Formulare (Arrays)

Für Listen von konfigurierbaren Elementen verwenden Sie eine Liste von Formularen:

python
class ExampleForm(Form):
    """Single example for few-shot learning."""

    input_text: Annotated[str | InputText, Field()] = ""
    expected_output: Annotated[str | InputText, Field()] = ""

    @classmethod
    def as_form(cls) -> "ExampleForm":
        return cls(
            input_text=InputText(label=LocaleString(en="Input")),
            expected_output=InputText(label=LocaleString(en="Expected Output")),
        )


class MyAgentConfig(AgentConfig):
    examples: Annotated[list[ExampleForm], Field(title="Few-Shot Examples")]

    @classmethod
    def as_form(cls) -> "MyAgentConfig":
        base = AgentConfig.as_form()
        return cls(
            agent_id=base.agent_id,
            name=base.name,
            description=base.description,
            icon=base.icon,
            examples=[ExampleForm.as_form()],  # List with one template
        )

Listen von Formularen werden als Repeater-Elemente gerendert, die es Benutzern ermöglichen, Elemente dynamisch hinzuzufügen/zu entfernen.

Zugriff auf Konfiguration in Schritten

Der Dispatcher injiziert die validierte Konfiguration über Typannotationen in Schrittmethoden:

python
from swiss_ai_hub.agent.workflow.decorators.step import step


class MyAgent(Agent):
    @step()
    async def process_message(
        self,
        event: UserMessageEvent,
        agent_config: MyAgentConfig,  # Injected by dispatcher
    ) -> ResponseEvent:
        # Access configuration values
        model = agent_config.model_name
        temperature = agent_config.temperature

        # Use nested config
        max_tokens = agent_config.llm.max_tokens

        # ...

Die Konfiguration wird über RPC abgerufen, wenn der Agent ein StartEvent erhält, gegen das Pydantic-Modell validiert und für die Dauer des Laufs zwischengespeichert.

Bedingte Feldsichtbarkeit

Felder basierend auf den Werten anderer Felder anzeigen oder verbergen:

python
use_custom_prompt: Annotated[bool | ToggleSwitch, Field()] = False
custom_prompt: Annotated[str | InputText, Field()] = ""

# In as_form():
use_custom_prompt=ToggleSwitch(label=LocaleString(en="Use Custom Prompt")),
custom_prompt=InputText(
    label=LocaleString(en="Custom Prompt"),
    condition_if="$get(use_custom_prompt).value === true",  # FormKit condition
)

Der Parameter condition_if verwendet die Ausdruckssyntax von FormKit. Das Feld wird nur angezeigt, wenn die Bedingung als wahr ausgewertet wird.

Vollständiges Beispiel

python
from typing import Annotated

from pydantic import Field

from swiss_ai_hub.agent.agents.agent import Agent
from swiss_ai_hub.agent.runners.agent_runner import AgentRunner
from swiss_ai_hub.agent.workflow.decorators.step import step
from swiss_ai_hub.core.agents.agent_config import AgentConfig
from swiss_ai_hub.core.i18n.locale_string import LocaleString
from swiss_ai_hub.core.events.control.stop_event import StopEvent
from swiss_ai_hub.core.events.control.user_message_event import UserMessageEvent
from swiss_ai_hub.core.form.constraints import Ge, Le
from swiss_ai_hub.core.form.elements import InputNumber, InputText, ModelSelect, ToggleSwitch


class QAAgentConfig(AgentConfig):
    """Configuration for a question-answering agent."""

    model_name: Annotated[str | ModelSelect, Field(description="LLM model")] = "gpt-4"
    temperature: Annotated[float | InputNumber, Field(), Ge(0.0), Le(1.0)] = 0.3
    system_prompt: Annotated[str | InputText, Field()] = "You are a helpful assistant."
    enable_citations: Annotated[bool | ToggleSwitch, Field()] = True

    @classmethod
    def as_form(cls) -> "QAAgentConfig":
        base = AgentConfig.as_form()
        return cls(
            agent_id=base.agent_id,
            name=base.name,
            description=base.description,
            icon=base.icon,
            model_name=ModelSelect(
                label=LocaleString(en="Model", de="Modell"),
                help=LocaleString(en="Language model for generating responses"),
            ),
            temperature=InputNumber(
                label=LocaleString(en="Temperature", de="Temperatur"),
                help=LocaleString(en="Lower = more focused, Higher = more creative"),
                min=0.0,
                max=1.0,
                step=0.1,
            ),
            system_prompt=InputText(
                label=LocaleString(en="System Prompt", de="System-Prompt"),
                help=LocaleString(en="Instructions for the assistant"),
            ),
            enable_citations=ToggleSwitch(
                label=LocaleString(en="Enable Citations", de="Zitate aktivieren"),
                help=LocaleString(en="Include source references in answers"),
            ),
        )


class QAAgent(Agent):
    @step()
    async def answer_question(
        self,
        event: UserMessageEvent,
        agent_config: QAAgentConfig,
    ) -> StopEvent:
        # Use configuration
        model = agent_config.model_name
        temp = agent_config.temperature
        citations = agent_config.enable_citations

        # ... generate answer using config ...

        return StopEvent()


async def main():
    runner = AgentRunner(
        agent_type=QAAgent,
        agent_config=QAAgentConfig.as_form(),
    )
    await runner.run_forever()

Best Practices

Feld-Benennung

  • Verwenden Sie beschreibende Feldnamen, die für Administratoren verständlich sind
  • Stellen Sie help-Text bereit, der erklärt, was jede Einstellung bewirkt
  • Fügen Sie sinnvolle Standardwerte hinzu

Validierung

  • Verwenden Sie immer Constraints (Ge, Le, Pattern) für numerische Felder und String-Felder
  • Validieren Sie frühzeitig, um zu verhindern, dass ungültige Konfigurationen gespeichert werden

Lokalisierung

  • Verwenden Sie LocaleString für alle benutzerspezifischen Texte (Labels, Hilfe, Beschreibungen)
  • Unterstützen Sie mindestens Deutsch und Englisch (de, en)

Testen

  • Testen Sie sowohl den Formularmodus (as_form()) als auch den Datenmodus (mit tatsächlichen Werten)
  • Überprüfen Sie die Generierung des Formularschemas mit config.to_formkit_form()
  • Testen Sie die Konfigurationsinjektion in Schrittmethoden

Gebaut mit ❤️ in der Schweiz 🇨🇭