Il s’agit d’un notebook interactif. Vous pouvez l’exécuter localement ou utiliser les liens suivants :
Importer des traces depuis des systèmes tiers
Ce notebook vous montre comment importer des traces historiques de conversation à partir d’un fichier CSV dans W&B Weave afin de les analyser, de comparer le comportement des modèles et d’exécuter des évaluations sur des données générées en dehors d’une application instrumentée avec Weave.
Il arrive parfois que vous ne puissiez pas instrumenter votre code Python ou JavaScript avec l’intégration Weave afin d’obtenir des traces en temps réel de votre application GenAI. Souvent, ces traces sont ensuite disponibles au format CSV ou JSON.
Ce notebook utilise l’API Python de bas niveau de Weave pour extraire des données d’un fichier CSV et les importer dans Weave afin que vous puissiez les analyser et les évaluer.
Le jeu de données d’exemple utilisé dans ce cookbook a la structure suivante :
conversation_id,turn_index,start_time,user_input,ground_truth,answer_text
1234,1,2024-09-04 13:05:39,This is the beginning, ['This was the beginning'], That was the beginning
1235,1,2024-09-04 13:02:11,This is another trace,, That was another trace
1235,2,2024-09-04 13:04:19,This is the next turn,, That was the next turn
1236,1,2024-09-04 13:02:10,This is a 3 turn conversation,, Woah thats a lot of turns
1236,2,2024-09-04 13:02:30,This is the second turn, ['That was definitely the second turn'], You are correct
1236,3,2024-09-04 13:02:53,This is the end,, Well good riddance!
Pour comprendre les choix d’import dans ce notebook, rappelez-vous que les traces Weave ont des relations parent-enfant continues de type 1:N. Cela signifie qu’un parent peut avoir plusieurs enfants, mais qu’il peut lui-même être l’enfant d’un autre parent.
Ce notebook utilise conversation_id comme identifiant du parent, et turn_index comme identifiant de l’enfant afin de disposer d’une journalisation complète des conversations.
Vous devez modifier les variables dans les sections suivantes pour qu’elles correspondent à votre propre jeu de données, à vos chemins de fichiers et à votre projet W&B.
Installez et importez tous les packages nécessaires.
Définissez WANDB_API_KEY dans votre environnement afin de pouvoir vous connecter avec wandb.login() (fournissez-la à Colab comme secret).
Définissez le nom du fichier que vous téléversez dans Colab dans name_of_file, et le projet W&B dans lequel vous souhaitez enregistrer dans name_of_wandb_project.
name_of_wandb_project peut aussi être au format [TEAM_NAME]/[PROJECT_NAME] pour indiquer l’équipe dans laquelle enregistrer les traces.
Ensuite, récupérez un client Weave en appelant weave.init().
%pip install wandb weave pandas datetime --quiet
python
import os
import pandas as pd
import wandb
from google.colab import userdata
import weave
## Écrire le fichier d'exemples sur le disque
with open("/content/import_cookbook_data.csv", "w") as f:
f.write(
"conversation_id,turn_index,start_time,user_input,ground_truth,answer_text\n"
)
f.write(
'1234,1,2024-09-04 13:05:39,This is the beginning, ["This was the beginning"], That was the beginning\n'
)
f.write(
"1235,1,2024-09-04 13:02:11,This is another trace,, That was another trace\n"
)
f.write(
"1235,2,2024-09-04 13:04:19,This is the next turn,, That was the next turn\n"
)
f.write(
"1236,1,2024-09-04 13:02:10,This is a 3 turn conversation,, Woah thats a lot of turns\n"
)
f.write(
'1236,2,2024-09-04 13:02:30,This is the second turn, ["That was definitely the second turn"], You are correct\n'
)
f.write("1236,3,2024-09-04 13:02:53,This is the end,, Well good riddance!\n")
os.environ["WANDB_API_KEY"] = userdata.get("WANDB_API_KEY")
name_of_file = "/content/import_cookbook_data.csv"
name_of_wandb_project = "import-weave-traces-cookbook"
wandb.login()
python
weave_client = weave.init(name_of_wandb_project)
Une fois l’environnement prêt, vous pouvez charger et structurer les données CSV afin qu’elles correspondent à la structure parent-enfant attendue par Weave.
Chargez les données dans un DataFrame pandas, puis triez-les par conversation_id et turn_index pour garantir que les éléments parents et enfants sont correctement ordonnés.
Vous obtenez ainsi un DataFrame pandas à deux colonnes, avec les tours de conversation regroupés dans un tableau sous conversation_data.
## Charger et structurer les données
df = pd.read_csv(name_of_file)
sorted_df = df.sort_values(["conversation_id", "turn_index"])
# Fonction pour créer un tableau de dictionnaires pour chaque conversation
def create_conversation_dict_array(group):
return group.drop("conversation_id", axis=1).to_dict("records")
# Regrouper le dataframe par conversation_id et appliquer l'agrégation
result_df = (
sorted_df.groupby("conversation_id")
.apply(create_conversation_dict_array)
.reset_index()
)
result_df.columns = ["conversation_id", "conversation_data"]
# Afficher le résultat de notre agrégation
result_df.head()
Enregistrer les traces dans Weave
Une fois les données structurées en conversations et en tours de conversation, l’étape suivante consiste à importer ces enregistrements dans Weave sous forme d’appels parents et enfants.
Parcourez le DataFrame pandas :
- Créez un appel parent pour chaque
conversation_id.
- Parcourez le tableau des tours de conversation pour créer des appels enfants triés selon leur
turn_index.
Concepts importants de l’API Python bas niveau :
- Un appel Weave est équivalent à une trace Weave. Cet appel peut avoir un parent ou des enfants associés.
- Un appel Weave peut aussi avoir d’autres éléments associés, comme le feedback et les métadonnées. Cet exemple associe uniquement les
inputs et l’output, mais vous pouvez ajouter ces autres éléments lors de l’importation si les données les fournissent.
- Un appel Weave passe par les états
created et finished, car il est conçu pour être suivi en temps réel. Comme il s’agit ici d’une importation a posteriori, vous créez et terminez l’appel une fois que les objets sont définis et liés entre eux.
- La valeur
op d’un appel indique comment Weave catégorise les appels de même type. Dans cet exemple, tous les appels parents sont de type Conversation et tous les appels enfants sont de type Turn. Vous pouvez modifier cela comme bon vous semble.
- Un appel peut avoir des
inputs et un output. Les inputs sont définis lors de la création, et l’output est défini lorsque l’appel est terminé.
# Enregistrer les traces vers Weave
# Itérer sur nos conversations agrégées
for _, row in result_df.iterrows():
# Définir le parent de notre conversation,
# nous créons maintenant un "call" avec le weave_client défini précédemment
parent_call = weave_client.create_call(
# La valeur Op enregistre ceci comme un op Weave, ce qui nous permettra de récupérer ces éléments en groupe facilement à l'avenir
op="Conversation",
# Nous définissons les entrées de notre conversation de haut niveau comme l'ensemble des tours de conversation qu'elle contient
inputs={
"conversation_data": row["conversation_data"][:-1]
if len(row["conversation_data"]) > 1
else row["conversation_data"]
},
# Notre parent Conversation n'a pas de parent supplémentaire
parent=None,
# Le nom sous lequel cette conversation spécifique apparaîtra dans l'interface utilisateur
display_name=f"conversation-{row['conversation_id']}",
)
# Nous définissons la sortie du parent comme la dernière trace de la conversation
parent_output = row["conversation_data"][len(row["conversation_data"]) - 1]
# Nous itérons maintenant sur tous les tours de conversation du parent
# et les enregistrons comme enfants de la conversation
for item in row["conversation_data"]:
item_id = f"{row['conversation_id']}-{item['turn_index']}"
# Nous créons ici un nouvel appel pour le rattacher à la conversation
call = weave_client.create_call(
# Nous qualifions une trace de conversation unique de "Turn"
op="Turn",
# Nous fournissons toutes les entrées du tour de conversation, y compris le 'ground_truth' RAG
inputs={
"turn_index": item["turn_index"],
"start_time": item["start_time"],
"user_input": item["user_input"],
"ground_truth": item["ground_truth"],
},
# Nous définissons ceci comme un enfant du parent que nous avons défini
parent=parent_call,
# Nous lui attribuons un nom pour l'identifier dans Weave
display_name=item_id,
)
# Nous définissons la sortie de l'appel comme la réponse
output = {
"answer_text": item["answer_text"],
}
# Ces traces ayant déjà eu lieu, nous clôturons l'appel du tour de conversation
weave_client.finish_call(call=call, output=output)
# Maintenant que nous avons enregistré tous ses enfants, nous clôturons également l'appel parent
weave_client.finish_call(call=parent_call, output=parent_output)
Résultat : traces enregistrées dans Weave
À ce stade, vos données CSV ont été importées dans Weave. Vous pouvez désormais parcourir les conversations et leurs tours de conversation dans l’interface Weave, regroupés sous les opérations Conversation et Turn que vous avez définies.
Traces :
Opérations :
Facultatif : exportez vos traces pour lancer des évaluations
Une fois vos traces dans Weave et après avoir compris à quoi ressemblent les conversations, vous pouvez les exporter vers un autre processus afin d’exécuter des évaluations Weave.
Pour ce faire, récupérez toutes les conversations à partir de W&B via l’API de requête, puis créez un jeu de données à partir de celles-ci.
## Cette cellule ne s'exécute pas par défaut, commentez la ligne ci-dessous pour exécuter ce script
%%script false --no-raise-error
## Récupérer toutes les traces de conversation pour l'évaluation et préparer le dataset
# Nous créons un filtre de requête qui récupère tous nos objets Conversation
# La référence indiquée ci-dessous est spécifique à votre projet ; vous pouvez l'obtenir en
# accédant aux opérations de votre projet dans l'interface utilisateur, en cliquant sur l'objet "Conversations",
# puis sur l'onglet "Use" dans le panneau latéral.
weave_ref_for_conversation_op = "weave://wandb-smle/import-weave-traces-cookbook/op/Conversation:tzUhDyzVm5bqQsuqh5RT4axEXSosyLIYZn9zbRyenaw"
filter = weave.trace_server.trace_server_interface.CallsFilter(
op_names=[weave_ref_for_conversation_op],
)
# Nous exécutons la requête
conversation_traces = weave_client.get_calls(filter=filter)
rows = []
# Nous parcourons nos traces de conversation et construisons les lignes du dataset à partir de celles-ci
for single_conv in conversation_traces:
# Dans cet exemple, nous ne nous intéressons qu'aux conversations ayant utilisé notre pipeline
# RAG ; nous filtrons donc ce type de conversations
is_rag = False
for single_trace in single_conv.inputs['conversation_data']:
if single_trace['ground_truth'] is not None:
is_rag = True
break
if single_conv.output['ground_truth'] is not None:
is_rag = True
# Une fois qu'une conversation est identifiée comme ayant utilisé RAG, nous l'ajoutons à notre dataset
if is_rag:
inputs = []
ground_truths = []
answers = []
# Nous parcourons chaque tour de conversation
for turn in single_conv.inputs['conversation_data']:
inputs.append(turn.get('user_input', ''))
ground_truths.append(turn.get('ground_truth', ''))
answers.append(turn.get('answer_text', ''))
## Gérer le cas des conversations à tour unique
if len(single_conv.inputs) != 1 or single_conv.inputs['conversation_data'][0].get('turn_index') != single_conv.output.get('turn_index'):
inputs.append(single_conv.output.get('user_input', ''))
ground_truths.append(single_conv.output.get('ground_truth', ''))
answers.append(single_conv.output.get('answer_text', ''))
data = {
'question': inputs,
'contexts': ground_truths,
'answer': answers
}
rows.append(data)
# Une fois les lignes du dataset créées, nous créons l'objet Dataset et
# le publions vers Weave pour pouvoir le récupérer ultérieurement
dset = weave.Dataset(name = "conv_traces_for_eval", rows=rows)
weave.publish(dset)
Le jeu de données exporté est désormais republié vers Weave et est prêt à être utilisé comme entrée pour une évaluation.
Pour en savoir plus sur les évaluations, consultez le Démarrage rapide pour apprendre à utiliser le jeu de données que vous venez de créer afin d’évaluer votre application RAG.