W&B Weave prend en charge le tracing des fonctions générateur synchrones et asynchrones, y compris dans des schémas profondément imbriqués. Cette page vous montre comment décorer des fonctions générateur avec @weave.op afin que Weave capture leurs entrées, les sorties qu’elles génèrent et la hiérarchie complète des appels imbriqués. Utilisez cette approche lorsque vous avez des chemins de code en streaming ou évalués paresseusement dont vous souhaitez inspecter les sorties dans l’interface Weave aux côtés de vos autres traces.
Comme les générateurs produisent leurs valeurs de façon paresseuse, Weave ne journalise les sorties que lorsque vous consommez entièrement le générateur (par exemple, lorsque vous le convertissez en liste). Pour vous assurer que Weave capture les sorties dans la trace,
consommez entièrement le générateur (par exemple, avec list()).
from typing import Generator
import weave
weave.init("my-project")
# Cette fonction utilise un générateur synchrone.
# Weave tracera l'Appel et son entrée (`x`),
# mais les valeurs de sortie ne sont capturées qu'une fois le générateur consommé (par exemple, avec `list()`).
@weave.op
def basic_gen(x: int) -> Generator[int, None, None]:
yield from range(x)
# Une fonction synchrone normale utilisée dans le pipeline du générateur.
# Ses appels sont également tracés séparément par Weave.
@weave.op
def inner(x: int) -> int:
return x + 1
# Un générateur synchrone qui appelle une autre fonction tracée (`inner`).
# Chaque valeur produite provient d'un Appel tracé distinct à `inner`.
@weave.op
def nested_generator(x: int) -> Generator[int, None, None]:
for i in range(x):
yield inner(i)
# Un générateur qui compose le générateur ci-dessus.
# Le tracing produit ici une arborescence d'Appels hiérarchique :
# - `deeply_nested_generator` (parent)
# - `nested_generator` (enfant)
# - `inner` (petit-enfant)
@weave.op
def deeply_nested_generator(x: int) -> Generator[int, None, None]:
for i in range(x):
for j in nested_generator(i):
yield j
# Le générateur doit être *consommé* pour que Weave capture les sorties.
# Cela vaut pour les générateurs synchrones comme asynchrones.
res = deeply_nested_generator(4)
list(res) # Déclenche le tracing de tous les appels imbriqués et des valeurs produites
Cette fonctionnalité n'est pas encore disponible dans le SDK TypeScript.
La capture d’écran suivante montre la page Traces avec une trace sélectionnée du code précédent. Le panneau central affiche l’arborescence de trace de la trace sélectionnée. Cette arborescence montre les Ops deeply_nested_generator, nested_generator et inner dans la hiérarchie des traces.
Consommer des générateurs
La section suivante explique pourquoi vous devez consommer les générateurs pour que Weave enregistre leurs sorties, et quels modes de consommation fonctionnent.
Weave ne capture les sorties d’un générateur qu’après sa consommation complète. Consommez le générateur en itérant dessus (par exemple avec list(), une boucle for ou next() jusqu’à épuisement). Il en va de même pour les générateurs asynchrones lorsque vous utilisez async for ou tout autre mode de consommation équivalent.
Pour en savoir plus sur la décoration de fonctions et de méthodes avec @weave.op, voir Créer des appels.
Accumuler les valeurs produites par yield dans une seule trace
Si vous souhaitez que Weave enregistre un résultat combiné (comme une chaîne concaténée ou une liste) au lieu de la séquence brute des valeurs produites, utilisez un accumulateur.
Vous pouvez utiliser le paramètre accumulator de weave.op pour personnaliser la manière dont les valeurs produites par des fonctions génératrices sont combinées, par exemple pour concaténer des jetons de texte diffusés en continu en une seule chaîne. L’accumulateur est une fonction à deux arguments que Weave appelle une fois par valeur produite, afin de construire un résultat de manière incrémentielle.
Le paramètre accumulator n’est pas disponible en TypeScript.
L’exemple suivant montre un accumulateur personnalisé qui ajoute chaque valeur produite à une liste, afin que Weave enregistre cette liste comme sortie de l’appel après que vous avez entièrement consommé le générateur.
from typing import Generator
import weave
weave.init("your-team-name/your-project-name")
# Weave appelle cette fonction après chaque yield. acc vaut None lors du premier appel.
# La dernière valeur retournée devient la sortie de l'Op tracé.
def list_accumulator(acc, value):
if acc is None:
acc = []
acc.append(value)
return acc
# Définir le paramètre accumulator
@weave.op(accumulator=list_accumulator)
def basic_gen_with_accumulator(x: int) -> Generator[int, None, None]:
yield from range(x)
# Itérer jusqu'à la fin pour que chaque yield s'exécute et que l'accumulateur produise la sortie tracée finale.
result = list(basic_gen_with_accumulator(3))
print(result)