DotPrompt est une bibliothèque simple qui vous permet de créer des invites à l'aide d'une syntaxe basée sur la configuration, sans avoir besoin de les intégrer dans votre application. Il prend en charge la création de modèles d'invites via le langage de création de modèles Fluid, vous permettant de réutiliser la même invite et de transmettre différentes valeurs au moment de l'exécution.
Un fichier d'invite est simplement n'importe quel fichier se terminant par une extension .prompt
. Le fichier lui-même est un fichier de configuration YAML et l'extension permet à la bibliothèque d'identifier rapidement le fichier pour l'usage auquel il est destiné.
Il existe un problème connu avec les fichiers .prompt
provoquant un comportement inhabituel dans des outils tels que Rider et IntelliJ. Vous pouvez contourner ce problème en désactivant le plug-in Terminal ou en utilisant un autre éditeur pour modifier les fichiers.
Le contenu d'un fichier d'invite contient certaines propriétés d'identification de niveau supérieur, suivies par des informations de configuration et enfin les invites.
Un fichier d'invite complet ressemblerait à ceci.
nom : exemple de modèle : gpt-4oconfig : format de sortie : texte température : 0,9 maxTokens : 500 entrée : paramètres : sujet : style de chaîne ? : chaîne par défaut : sujet : invites de médias sociaux : système : | Vous êtes un assistant de recherche utile qui fournira des réponses descriptives sur un sujet donné et son impact sur la société. utilisateur : | Expliquez l'impact de {{ topic }} sur la façon dont nous interagissons avec la technologie en tant que société {% if style -%} Pouvez-vous répondre sous la forme d'un {{ style }} {% endif -%}quelques plans : - utilisateur : Qu'est-ce que Bluetoothresponse : Bluetooth est une norme de technologie sans fil à courte portée utilisée pour échanger des données entre des appareils fixes et mobiles sur de courtes distances et pour créer des réseaux personnels. - utilisateur : En quoi l'apprentissage automatique diffère-t-il de la programmation traditionnelle ?réponse : L'apprentissage automatique permet aux algorithmes d'apprendre à partir des données et de s'améliorer au fil du temps sans être explicitement programmés. - utilisateur : Pouvez-vous donner un exemple d'IA dans la vie quotidienne ?réponse : L'IA est utilisée dans des assistants virtuels comme Siri et Alexa, qui comprennent et répondent aux commandes vocales.
Le name
est facultatif dans la configuration, s'il n'est pas fourni alors le nom est tiré du nom du fichier moins l'extension. Ainsi, un fichier appelé gen-lookup-code.prompt
aurait le nom gen-lookup-code
. Cela ne joue pas de rôle dans la génération des invites elles-mêmes (bien que de futures mises à jour puissent le faire), mais vous permet d'identifier la source de l'invite lors de la journalisation et de sélectionner l'invite dans le gestionnaire d'invites.
Si vous utilisez cette propriété, lorsque le fichier est chargé, le nom est converti en minuscules et les espaces sont remplacés par des traits d'union. Ainsi, un nom de My cool Prompt
deviendrait my-cool-prompt
. Ceci est fait pour s'assurer que le nom est facilement accessible à partir du code.
Il s'agit d'un autre élément facultatif dans la configuration, mais il fournit des informations à l'utilisateur du fichier d'invite sur le modèle (ou le déploiement pour Azure Open AI) qu'il doit utiliser. Comme cela peut être nul s'il n'est pas spécifié, le consommateur doit s'assurer de vérifier avant utilisation. Par exemple:
var modèle = promptFile.Model ?? "mon défaut" ;
L'utilisation de cette option permet cependant à l'ingénieur d'être très explicite sur le modèle qu'il envisage d'utiliser pour fournir les meilleurs résultats.
La section config
contient des éléments de niveau supérieur que le client peut utiliser dans ses appels LLM pour définir les options de chaque appel. La propriété outputFormat
prend la valeur text
ou json
selon la manière dont le LLM est censé répondre à la demande. Si vous spécifiez json
, certains LLM nécessitent que l'invite du système ou de l'utilisateur indique que la sortie attendue est également JSON. Si la bibliothèque ne détecte pas le terme JSON
dans l'invite, elle ajoutera une petite instruction à l'invite système demandant que la réponse soit au format JSON.
La section input
contient des détails sur les paramètres fournis aux invites. Celles-ci ne sont pas obligatoires et vous pouvez créer des invites dans lesquelles aucune valeur n'est transmise. Mais si vous le faites, c'est ce dont vous avez besoin.
Sous input
se trouve la section parameters
qui contient une liste de paires clé-valeur où la clé est le nom du paramètre et la valeur est son type. Si vous ajoutez un point d'interrogation au nom du paramètre (par exemple style?
), alors il est considéré comme un paramètre facultatif et ne générera pas d'erreur si vous ne lui fournissez pas de valeur.
Les types pris en charge sont :
Type de paramètre | Type de réseau de points | Équivalent C# |
---|---|---|
chaîne | Système.String | chaîne |
bouffon | Système.Booléen | bouffon |
dateheure | Système.DateTimeOffset | Système.DateTimeOffset |
nombre | Système.Octet Système.SByte Système.UInt16 Système.Int16 Système.UInt32 Système.Int32 Système.UInt64 Système.Int64 Système.Single Système.Double Système.Décimal | octet soctet ushort court uint int oulong long flotter double décimal |
objet | Système.Object | objet |
Les 4 premiers sont utilisés comme prévu. Les objets transmis à l'invite verront leur méthode ToString
appelée pour être utilisée dans l'invite.
Le type datetime
peut être affiché avec sa représentation ToString
par défaut, ou vous pouvez utiliser les filtres de Fluid pour spécifier son format, modifier le fuseau horaire, etc.
Si vous fournissez une valeur pour un paramètre qui n'est pas conforme au type spécifié, une erreur sera générée.
En input
se trouve également la section default
. Cette section vous permet de spécifier des valeurs par défaut pour n'importe lequel des paramètres. Ainsi, si le paramètre n'est pas fourni dans votre application, la valeur par défaut sera utilisée à la place.
La section prompts
contient les modèles pour les invites système et utilisateur. Bien que l'invite utilisateur soit requise, vous n'avez pas besoin de spécifier une invite système.
Les invites du system
et user
sont des valeurs de chaîne et peuvent être définies de n'importe quelle manière prise en charge par YAML. L'exemple ci-dessus utilise une chaîne multiligne où les retours chariot sont conservés.
YAML prend en charge les valeurs de chaîne multiligne via Block Scalars. Avec ceux-ci, il prend en charge les chaînes littérales et pliées . Avec les chaînes littérales, les caractères de nouvelle ligne dans la chaîne d'entrée sont conservés et la chaîne reste exactement telle qu'elle est écrite. Une fois pliés, les nouveaux caractères de ligne sont réduits et remplacés par un caractère espace, vous permettant d'écrire de très longues chaînes sur plusieurs lignes. En utilisant plié, si vous utilisez deux caractères de nouvelle ligne, une nouvelle ligne est ajoutée à la chaîne.
# Exemple plié : > Les navires pendaient dans le ciel de la même manière que les briques ne le font pas# Produit :# Les navires pendaient dans le ciel de la même manière que les briques ne le faisaient pas
# Exemple littéral : | Les navires étaient suspendus dans le ciel de la même manière que les briques.# Produit :# Les navires étaient suspendus# dans le ciel, à peu près de la même# manière que les briques.
La syntaxe des invites utilise le langage de modèles Fluid, lui-même basé sur Liquid créé par Shopify. Ce langage de modèle nous permet de définir des invites utilisateur qui peuvent changer en fonction des valeurs transmises à l'analyseur de modèle.
Dans l'exemple ci-dessus, vous pouvez voir {{ topic }}
qui est un espace réservé pour la valeur transmise et qui sera remplacé directement dans le modèle. Il existe également la section {% if style -%} ... {% endif -%}
qui indique à l'analyseur de n'inclure cette section que si le paramètre style
a une valeur. Le -%}
à la fin du marqueur contient le symbole de trait d'union qui indique à l'analyseur qu'il doit réduire les lignes vides.
Il existe un excellent tutoriel sur la rédaction de modèles avec Fluid disponible en ligne.
Lorsque vous générez l'invite, elle ne remplace pas le modèle, vous donnant uniquement le résultat généré. Cela signifie que vous pouvez générer l'invite autant de fois que vous le souhaitez avec différentes valeurs d'entrée.
fewShots
est une section permettant à l'auteur de l'invite de fournir des techniques d'invite en quelques étapes à la solution. Lors de la création d'une invite, vous devez les inclure, ainsi que l'invite de votre système, puis l'invite de l'utilisateur, cela fournit des exemples sur la façon dont le LLM doit répondre à l'invite de l'utilisateur. Si vous utilisez OpenAI ou Azure OpenAI, vous pouvez utiliser les méthodes d'extension (voir plus loin) qui créeront tous les messages pour vous.
Les fichiers d'invite sont accessibles directement. Si vous ne disposez que de quelques fichiers ou si vous souhaitez les tester rapidement, c'est un moyen assez simple de le faire.
en utilisant DotPrompt;var promptFile = PromptFile.FromFile("path/to/prompt-file.prompt");var systemPrompt = promptFile.GetSystemPrompt(null);var userPrompt = promptFile.GetUserPrompt(new Dictionary<string, object>{{ " topic", "bluetooth" },{ "style", "vendeur de voitures d'occasion" }});
Si le fichier d'invite contenait l'exemple ci-dessus, il produirait ce qui suit.
System Prompt:
You are a helpful research assistant who will provide descriptive responses for a given topic and how it impacts society
User Prompt:
Explain the impact of bluetooth on how we engage with technology as a society
Can you answer in the style of a used car salesman
Cela pourrait entraîner une réponse du LLM qui ressemble à ceci (désolé)
Mesdames et messieurs, rassemblez-vous et laissez-moi vous parler du miracle de la technologie moderne qui a révolutionné la façon dont nous nous connectons avec nos gadgets : je parle de Bluetooth ! Le Bluetooth est le héros méconnu, la sauce secrète qui rend nos vies plus pratiques, plus connectées et certainement plus high-tech. Imaginez ceci : une communication transparente et sans fil entre vos appareils préférés. Fini les cordons emmêlés, plus de désordre. C'est comme avoir un pass VIP pour être au premier rang du futur !
...
Le gestionnaire d'invites est la méthode privilégiée pour gérer vos fichiers d'invites. Il vous permet de les charger depuis un emplacement, d'y accéder ensuite par nom, puis de les utiliser dans votre application.
La valeur par défaut du gestionnaire d'invites consiste à accéder aux fichiers du dossier prompts
local, bien que vous puissiez spécifier un chemin différent si vous le souhaitez.
// Charger à partir de l'emplacement par défaut du répertoire `invites`var promptManager = new PromptManager();var promptFile = promptManager.GetPromptFile("example");// Utiliser un dossier différentvar promptManager = new PromptManager("another-location"); var promptFile = promptManager.GetPromptFile("example");// Liste toutes les invites chargéesvar promptNames = promptManager.ListPromptFileNames();
Le gestionnaire d'invites implémente une interface IPromptManager
, et donc si vous souhaitez l'utiliser via un conteneur DI ou un modèle IoC, vous pouvez facilement fournir une version simulée à des fins de test.
Le gestionnaire d'invites peut également prendre une instance IPromptStore
qui vous permet de créer un magasin personnalisé qui peut ne pas être basé sur un fichier (voir Création d'un magasin d'invites personnalisé). Cela permet également de fournir une interface simulée afin que vous puissiez écrire des tests unitaires qui ne dépendent pas du mécanisme de stockage.
Utilisation du gestionnaire d'invites pour lire une invite, puis l'utiliser dans un appel vers un point de terminaison Azure OpenAI.
NB Cet exemple suppose qu'il existe un répertoire prompts
avec le fichier d'invite disponible.
en utilisant System.ClientModel;en utilisant Azure.AI.OpenAI;en utilisant DotPrompt;var openAiClient = new(new Uri("https://endpoint"), new ApiKeyCredential("abc123"));var promptManager = new PromptManager();var promptFile = promptManager.GetPromptFile("example");// Les méthodes d'invite système et d'invite utilisateur utilisent des dictionnaires contenant les valeurs nécessaires pour le// modèle. Si aucun n'est nécessaire, vous pouvez simplement transmettre null.var systemPrompt = promptFile.GetSystemPrompt(null);var userPrompt = promptFile.GetUserPrompt(new Dictionary<string, object>{{ "topic", "bluetooth" },{ "style" , "vendeur de voitures d'occasion" }});var client = openAiClient.GetChatClient(promptFile.Model ?? "modèle par défaut");var complétion = attendre client.CompleteChatAsync([new SystemChatMessage(systemPrompt),new UserChatMessage(userPrompt)],new ChatCompletionOptions(ResponseFormat = promptFile.OutputFormat == OutputFormat.Json ? ChatResponseFormat.JsonObject : ChatResponseFormat.Text ,Température = promptFile.Config.Temperature,MaxTokens = promptFile.Config.MaxTokens));
Ou, en utilisant les méthodes d'extension fournies par OpenAI.
en utilisant System.ClientModel;en utilisant Azure.AI.OpenAI;en utilisant DotPrompt;en utilisant DotPrompt.Extensions.OpenAi;var openAiClient = new(new Uri("https://endpoint"), new ApiKeyCredential("abc123"));var promptManager = new PromptManager();var promptFile = promptManager.GetPromptFile("exemple");var promptValues = new Dictionary<string, object>{{ "topic", "bluetooth" },{ "style", "vendeur de voitures d'occasion" }};var client = openAiClient.GetChatClient(promptFile.Model ?? "default-model");var complétion = wait client.CompleteChatAsync(promptFile.ToOpenAiChatMessages(promptValues),promptFile.ToOpenAiChatCompletionOptions());var réponse = achèvement.Value;Console.WriteLine(response.Content[0].Text);
Et maintenant, si nous devons modifier notre invite, nous pouvons simplement changer le fichier d'invite et laisser notre code tranquille (en supposant que les paramètres ne changent pas).
Ce qui précède montre comment utiliser DotPrompt pour lire les fichiers d'invite à partir du disque. Mais que se passe-t-il si vous souhaitez recevoir vos invites dans un endroit plus central, comme un service de stockage cloud ou une base de données ? Eh bien, le gestionnaire d'invites peut prendre une instance IPromptStore
comme argument. Dans tous les exemples ci-dessus, il utilise le FilePromptStore
qui est inclus, mais vous pouvez également créer le vôtre. Il suffit d'implémenter l'interface et le tour est joué.
Pour vous donner un exemple, voici une implémentation simple qui utilise un Azure Storage Table Store pour conserver les détails de l'invite.
/// <summary>/// Implémentation d'IPromptStore pour les tables de stockage Azure/// </summary>classe publique AzureTablePromptStore : IPromptStore{/// <summary>/// Charge les invites à partir du magasin de tables/// < /summary>public IEnumerable<PromptFile> Load(){var tableClient = GetTableClient();var promptEntities = tableClient.Query<PromptEntity>(e => e.PartitionKey == "DotPromptTest");var promptFiles = promptEntities.Select(pe => pe.ToPromptFile()).ToList();return promptFiles;}/// <summary >/// Obtient un client de table/// </summary>TableClient statique privé GetTableClient(){// Remplacez les éléments de configuration ici par votre ou passez à l'utilisation de // Entra based ignitionvar client = new TableServiceClient(new Uri($"https://{Configuration.StorageAccountName}.table.core.windows.net/"),new TableSharedKeyCredential(Configuration.StorageAccountName, Configuration. StorageAccountKey));var tableClient = client.GetTableClient("prompts");tableClient.CreateIfNotExists();return tableClient;}}/// <summary>/// Représente un enregistrement contenu dans la table de stockage/// </summary>classe publique PromptEntity : ITableEntity{/// <summary>/// Obtient, définit la clé de partition pour l'enregistrement/// </summary>chaîne publique PartitionKey { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit la clé de ligne pour l'enregistrement/// </summary>public string RowKey { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit l'horodatage de l'entrée/// </summary>public DateTimeOffset ? Horodatage { obtenir ; ensemble; }/// <summary>/// Obtient, définit la valeur ETag des enregistrements/// </summary>public ETag ETag { get; ensemble; }/// <summary>/// Obtient, définit le modèle à utiliser /// </summary>chaîne publique ? Modèle { obtenir ; ensemble; }/// <summary>/// Obtient, définit le format de sortie/// </summary>public string OutputFormat { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit le nombre maximum de jetons/// </summary>public int MaxTokens { get; ensemble; }/// <summary>/// Obtient, définit les informations de paramètre conservées sous forme de valeur de chaîne JSON/// </summary>public string Parameters { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit les valeurs par défaut qui sont conservées sous forme de valeur de chaîne JSON/// </summary>public string Default { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit le modèle d'invite système/// </summary>public string SystemPrompt { get; ensemble; } = string.Empty;/// <summary>/// Obtient, définit le modèle d'invite utilisateur/// </summary>public string UserPrompt { get; ensemble; } = string.Empty;/// <summary>/// Renvoie l'enregistrement d'entité d'invite dans une instance <see cref="PromptFile"/>/// </summary>/// <returns></returns>public PromptFile ToPromptFile(){var settings = new Dictionary<string, string>();var defaults = new Dictionary<string, object>();// S'il y a des valeurs de paramètres, convertissez-les en dictionnaireif (!string.IsNullOrEmpty(Parameters)){varentityParameters = (JsonObject)JsonNode.Parse(Parameters)!;foreach (var (prop, propType) inentityParameters){parameters.Add(prop, propType?.AsValue().ToString () ?? string.Empty);}}// S'il existe des valeurs par défaut, convertissez-les en dictionnaire. (!string.IsNullOrEmpty(Default)){varentityDefaults = (JsonObject)JsonNode.Parse(Default)!;foreach (var (prop, defaultValue) inentityDefaults){defaults.Add(prop, defaultValue?.AsValue().GetValue <object>() ?? string.Empty);}}// Générer le nouveau fichier d'invite promptFile = new PromptFile{Name = RowKey,Model = Model,Config = new PromptConfig{OutputFormat = Enum.Parse<OutputFormat>(OutputFormat, true),MaxTokens = MaxTokens,Input = new InputSchema{Parameters = paramètres,Default = valeurs par défaut}},Prompts = nouvelles invites{System = SystemPrompt,User = UserPrompt}};return promptFile;}}
Et puis pour l'utiliser, nous ferions ce qui suit
var promptManager = new PromptManager(new AzureTablePromptStore());var promptFile = promptManager.GetPromptFile("exemple");
Il y a encore du travail à faire ici et certains des éléments que nous examinons incluent
Options de configuration supplémentaires
Techniques d'invite supplémentaires
Ouvert aux commentaires. Y a-t-il quelque chose que vous aimeriez voir ? Faites-nous savoir