Przejdź do nawigacji

Workflowi jako kod

Twórz, edytuj i wersjonuj workflowi agentów za pomocą SDK Python i JavaScript

Omówienie

Workflow jest przechowywany jako graf węzłów i krawędzi. Ręczne tworzenie takiego grafu oznacza, że musisz sam dbać o spójność identyfikatorów krawędzi, list edge_order i obiektów warunków. Konstruktor Workflow w SDK Python i JavaScript pozwala opisać ten sam graf za pomocą łańcuchowych wywołań metod. Następnie tworzy wartość workflow, którą akceptują już agents.create() i agents.update().

Użyj go, aby:

  • Tworzyć workflow w kodzie i sprawdzać je jak każdą inną zmianę w kodzie.
  • Wczytać workflow agenta, zmienić jeden węzeł i wysłać go z powrotem bez naruszania reszty grafu.
  • Przechowywać workflowi jako JSON w kontroli wersji i sprawdzać je w CI.

Konstruktor działa wyłącznie w SDK. Nie wywołuje nowych endpointów API, a wszystko, co utworzy, można otworzyć i edytować w edytorze wizualnym w dashboardzie.

Wymagania

  • Python: pakiet elevenlabs. Konstruktor importujesz z elevenlabs.agent_workflows.
  • JavaScript: pakiet @elevenlabs/elevenlabs-js. Konstruktor jest eksportowany z głównego modułu pakietu.

Tworzenie workflow

Obiekt Workflow reprezentuje węzeł początkowy. Każdy czasownik dodaje węzeł i go zwraca, więc możesz od niego połączyć kolejne kroki:

PythonJavaScriptDodaje
converse(id, prompt, **options)converse(id, prompt, options?)Węzeł podagenta. Agent prowadzi rozmowę z prompt dodanym do instrukcji.
tool(id, tool_ids, **options)tool(id, toolIds, options?)Węzeł narzędzia dyspozytorskiego, który uruchamia podane narzędzia.
transfer(id, destination, **options)transfer(id, destination, options?)Przekazanie do numeru telefonu, identyfikatora URI SIP lub innego agenta.
end(id="end")end(options?)Węzeł końcowy. Każde end() bez ID używa wspólnego węzła "end".

Metody routingu określają, która krawędź prowadzi do następnego węzła:

PythonJavaScriptTworzy
when(condition)when(condition)Krawędź warunkową. Ciąg znaków to warunek LLM, a Expr to wyrażenie.
on_success() / on_failure()onSuccess() / onFailure()Krawędź z węzła narzędzia, wybieraną po powodzeniu lub niepowodzeniu wywołania narzędzia.
on_result(success=..., failure=...)onResult({ success, failure })Obie krawędzie wyniku narzędzia naraz.
go_to(node)goTo(node)Krawędź do istniejącego już węzła, dla połączeń i pętli.
up()up()Bez krawędzi. Zwraca węzeł, z którego połączono bieżący, by kontynuować łańcuch od niego. Zobacz Zapisz workflow jako jeden łańcuch.

Czasowniki wywołane bezpośrednio na workflow tworzą gałęzie z węzła początkowego przez bezwarunkową krawędź. Ten przykład kieruje dzwoniących do wyszukiwania zamówienia, ponawia identyfikację, jeśli wyszukiwanie się nie uda, oraz przekazuje klientów VIP lub zdenerwowanych:

from elevenlabs import ElevenLabs
from elevenlabs.agent_workflows import Destination, Expr, Workflow
elevenlabs = ElevenLabs()
wf = Workflow()
triage = wf.converse("triage", "Greet the caller and find out why they're calling")
identify = triage.when("Caller wants to check an order").converse(
"identify", "Ask for the order number and the email on the account"
)
lookup = identify.when("Caller gave an order number").tool("lookup_order", ["tool_get_order"])
lookup.on_result(
success=lambda ok: ok.converse("resolve", "Explain the order status")
.when("Caller is satisfied")
.end(),
failure=lambda failed: failed.go_to(identify),
)
triage.when(Expr.variable("tier").eq("gold")).transfer("vip", Destination.agent("agent_vip"))
triage.when("Caller wants a human").transfer("human", Destination.phone("+15551234567"))
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

build() sprawdza cały graf i zwraca model żądania. Dodatkowo:

  • Nadaje węzłowi początkowemu wymagany przez API identyfikator start_node.
  • Generuje identyfikatory krawędzi w formacie source__target.
  • Uzupełnia edge_order każdego węzła w kolejności dodawania krawędzi.
  • Pozycjonuje każdy węzeł bez pozycji, aby graf był ułożony od lewej do prawej w edytorze wizualnym.

Zapisz workflow jako jeden łańcuch

up() zwraca węzeł, z którego połączono bieżący, więc możesz opisać cały workflow jednym wyrażeniem zamiast przechowywać każdy węzeł w zmiennej. W pojedynczym łańcuchu użyj go_to() / goTo() z ID węzła, ponieważ nie masz zmiennej do przekazania:

wf = Workflow()
(
wf.converse("triage", "Find out why the customer is calling")
.when("Caller gave an order number")
.tool("lookup_order", ["tool_get_order"])
.on_result(
success=lambda ok: ok.converse("resolve", "Explain the order status").when("Customer is satisfied").end(),
failure=lambda failed: failed.go_to("triage"),
)
.up()
.when(Expr.variable("tier").eq("gold"))
.transfer("vip", Destination.agent("agent_vip_desk"))
)
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

Dla uchwytu węzła z wf.node(id) metoda up() zwraca węzeł, z którego prowadzi pierwsza krawędź do tego węzła. Dla węzła początkowego zwraca węzeł początkowy.

Opcje węzłów

Opcjonalne ustawienia przekazujesz jako argumenty nazwane w Pythonie i w końcowym obiekcie opcji w JavaScript. Ich nazwy odpowiadają polom węzła w API:

WęzełPythonJavaScript
converselabel, tool_ids, knowledge_base, conversation_config, entry_behaviorlabel, toolIds, knowledgeBase, conversationConfig, entryBehavior
toolschema_overridesschemaOverrides
transfertransfer_type, custom_sip_headers, post_dial_digits, delay_ms, transfer_message, …transferType, customSipHeaders, postDialDigits, delayMs, transferMessage, …
endidid

Każdy czasownik przyjmuje też position, jeśli chcesz samodzielnie ustawić pozycję węzła. Workflow(prevent_subagent_loops=True) w Pythonie lub new Workflow({ preventSubagentLoops: true }) w JavaScript zapobiega ponownemu wejściu rozmowy do odwiedzonego już podagenta.

triage = wf.converse(
"triage",
"Greet the caller and find out why they're calling",
label="Triage",
tool_ids=["tool_crm_search"],
entry_behavior="generate_immediately",
)
triage.when("Caller is a VIP").transfer(
"vip_desk",
Destination.agent("agent_vip_concierge"),
transfer_message="Connecting you to our VIP team",
delay_ms=500,
)
identify.when("Caller refuses to share details").end(id="abandoned")

Cele przekazywania

PythonJavaScriptPrzekazuje do
Destination.phone(number)Destination.phone(number)Numeru telefonu
Destination.phone_variable(name)Destination.phoneVariable(name)Numeru telefonu w zmiennej dynamicznej
Destination.sip(uri)Destination.sip(uri)Identyfikatora URI SIP
Destination.sip_variable(name)Destination.sipVariable(name)Identyfikatora URI SIP w zmiennej dynamicznej
Destination.agent(agent_id)Destination.agent(agentId)Innego agenta

Warunki

when() przyjmuje zwykły ciąg znaków, Condition lub Expr:

  • Ciąg znaków to warunek LLM: LLM decyduje, czy jest spełniony.
  • Condition.llm(text, label=...) działa tak samo, z etykietą wyświetlaną na krawędzi w edytorze wizualnym. Użyj Condition.expr(expr, label=...), aby oznaczyć wyrażenie etykietą.
  • Expr jest obliczany na podstawie zmiennych dynamicznych, bez wywoływania LLM.

Twórz wyrażenia za pomocą Expr.variable(name) i Expr.literal(value). Porównuj je z eq, neq, gt, gte, lt i lte, a działania arytmetyczne wykonuj przez add, sub, mul i div. Łącz je przez all_of / allOf, any_of / anyOf i if_else / ifElse. Zwykłe wartości są automatycznie opakowywane w Expr.literal(). Expr.llm(description) prosi LLM o wyodrębnienie wartości z rozmowy; przekaż type i enum, aby otrzymać ciąg znaków, liczbę lub jedną z ustalonych wartości zamiast wartości logicznej.

from elevenlabs.agent_workflows import Condition, Expr
is_vip = Expr.any_of(
Expr.variable("tier").eq("gold"),
Expr.all_of(Expr.variable("orders").gte(10), Expr.variable("churn_risk").eq(True)),
)
triage.when(Condition.expr(is_vip, label="VIP")).transfer("vip_desk", Destination.agent("agent_vip"))
big_refund = Expr.variable("unit_price").mul(Expr.variable("quantity")).gt(500)
resolve.when(big_refund).transfer("finance", Destination.phone("+15551234567"))
score = Expr.llm("How satisfied the caller sounds, from 1 to 5", type="integer")
resolve.when(score.lte(2)).converse("recovery", "Apologize and offer a discount code")

Pętle i krawędzie wsteczne

API pozwala na jedną krawędź między dowolnymi dwoma węzłami, w każdym kierunku. Aby rozmowa mogła wrócić tą samą drogą, nadaj krawędzi warunek wsteczny za pomocą back:

faq = triage.when("Caller has a general question", back="Question answered").converse(
"faq", "Answer using the FAQ knowledge base"
)

go_to() / goTo() obsługuje typowy przypadek automatycznie. Gdy docelowy węzeł ma już krawędź prowadzącą tutaj, nowa krawędź staje się warunkiem wstecznym tamtej krawędzi. W pierwszym przykładzie failed.go_to(identify) jest wysyłane jako warunek wsteczny krawędzi identify → lookup_order. Każda inna druga krawędź między tymi samymi dwoma węzłami oraz każda krawędź z węzła do niego samego nie przejdzie walidacji.

Edycja istniejącego agenta

Workflow.from_agent() / Workflow.fromAgent() wczytuje workflow agenta zwróconego przez agents.get(). Identyfikatory węzłów i krawędzi, pozycje w edytorze oraz kolejność krawędzi zostają zachowane, więc wysłanie go z powrotem zmienia tylko to, co zmienisz.

PythonJavaScriptDziałanie
wf.node(id)wf.node(id)Zwraca uchwyt istniejącego węzła, aby go edytować lub z niego łączyć.
node.update(**fields)node.update(fields)Zmienia prompt, narzędzia, cel lub opcje węzła.
wf.insert_between(source, target, fn)wf.insertBetween(source, target, fn)Wstawia nowe węzły w istniejącą krawędź, zachowując jej warunek.
wf.remove(id)wf.remove(id)Usuwa węzeł oraz wszystkie krawędzie do niego i od niego.

insert_between() / insertBetween() przekazuje twojej funkcji gałąź zawierającą warunek zastępowanej krawędzi. Funkcja zwraca ostatni dodany węzeł, który zostaje następnie połączony z pierwotnym celem. Metoda zwraca ten węzeł albo None / undefined, jeśli między tymi dwoma węzłami nie ma krawędzi.

agent = elevenlabs.conversational_ai.agents.get("agent_id")
wf = Workflow.from_agent(agent)
wf.node("resolve").update(prompt="Explain the status, then offer a discount code")
verify = wf.insert_between(
"identify",
"lookup_order",
lambda start: start.converse("verify", "Verify the caller's identity with a one-time code"),
)
if verify is not None:
verify.when("Caller can't verify").end(id="unverified")
wf.remove("vip")
elevenlabs.conversational_ai.agents.update("agent_id", workflow=wf.build())

Przechowywanie workflowi w kontroli wersji

to_json_string() / toJSONString() zapisuje workflow w formacie przesyłanym przez API: klucze snake_case, posortowane na każdym poziomie, z wcięciem 2 spacji. To ten sam format, którego używają REST API i CLI, a oba SDK zapisują dla tego samego workflow pliki identyczne bajt po bajcie, więc różnice pozostają niewielkie. Workflow.from_json() / Workflow.fromJSON() odczytuje go z powrotem i przyjmuje też pełną konfigurację agenta z kluczem workflow, na przykład plik z katalogu agent_configs/ CLI.

from pathlib import Path
from elevenlabs.agent_workflows import Workflow
Path("workflows/support.json").write_text(wf.to_json_string())
stored = Workflow.from_json(Path("workflows/support.json").read_text())
from_cli = Workflow.from_json(Path("agent_configs/support.json").read_text())

Typowa konfiguracja:

1

Utwórz plik

Wczytaj agenta za pomocą from_agent() / fromAgent() i zapisz go albo pobierz przez elevenlabs agents pull.

2

Edytuj

Zmień JSON bezpośrednio albo wczytaj go przez from_json() / fromJSON() i edytuj konstruktorem w skrypcie.

3

Waliduj w CI

Uruchom Workflow.from_json(file).build() / Workflow.fromJSON(file).build() dla każdego pull requesta i dodaj jako komentarz elevenlabs agents workflow diagram <file> --format mermaid-md, aby osoby sprawdzające mogły zobaczyć graf.

4

Wdrażaj po scaleniu

Wyślij utworzony workflow przez agents.update().

Wizualizacja workflow

CLI rysuje plik workflow jako schemat blokowy, dzięki czemu możesz sprawdzić, co utworzył kod, bez otwierania dashboardu. Przyjmuje sam plik workflow zapisany przez to_json_string() / toJSONString() lub konfigurację agenta z kluczem workflow:

elevenlabs agents workflow diagram workflows/support.json

Domyślnie rysuje w terminalu pola i strzałki, a każdy warunek krawędzi numeruje na krawędzi i opisuje w legendzie poniżej. Użyj --format mermaid, aby zamiast tego wyświetlić kod źródłowy Mermaid, lub --format mermaid-md, aby umieścić go w ogrodzonym bloku kodu renderowanym przez GitHub, na przykład w komentarzu do pull requesta:

elevenlabs agents workflow diagram workflows/support.json --format mermaid-md

Polecenie odczytuje tylko pliki lokalne, więc nie potrzebuje klucza API i może działać w CI.

Walidacja

Wywołania łańcuchowe same w sobie nigdy nie kończą się błędem. build(), to_json() / toJSON() i to_json_string() / toJSONString() sprawdzają cały graf i zgłaszają lub wyrzucają pojedynczy WorkflowValidationError, którego lista issues zawiera każdy problem wraz z jego code, message oraz dotyczącym go node_id / nodeId.

from elevenlabs.agent_workflows import WorkflowValidationError
try:
workflow = wf.build(on_warning=lambda issue: print("warning:", issue.message))
except WorkflowValidationError as err:
for issue in err.issues:
print(issue.code, issue.message)

Błędy obejmują:

  • Dwie krawędzie między tą samą parą węzłów lub krawędź z węzła do niego samego.
  • Krawędź do węzła początkowego lub z węzła końcowego.
  • Krawędź z węzła narzędzia, która nie jest krawędzią sukcesu ani porażki, albo krawędź sukcesu lub porażki z węzła innego typu.
  • Dwa węzły z tym samym ID lub krawędź do nieistniejącego węzła.

Węzły, do których nie można dotrzeć z węzła początkowego, są zgłaszane jako ostrzeżenia przez on_warning / onWarning i nie zatrzymują tworzenia.

Typy węzłów nowsze niż twoje SDK

Workflow wczytany z API lub z pliku może zawierać typy węzłów, których twoja wersja SDK jeszcze nie zna. Przechodzą bez zmian przez from_json() / fromJSON() i to_json_string() / toJSONString(), a build() zgłasza każdy z nich jako ostrzeżenie. Typowany model żądania nie może ich obsłużyć, więc zaktualizuj SDK albo wyślij format przesyłany przez sieć bezpośrednio:

elevenlabs.conversational_ai.agents.update(
"agent_id",
request_options={"additional_body_parameters": {"workflow": wf.to_json()}},
)