Il est intrinsèquement difficile de produire des sorties de LLMS dans une structure cohérente. TypeGPT simplifie ce processus aussi facile que de définir une classe dans Python.
Aliver nos propres projets, comme Spexia
pip install typegpt
Définissez votre schéma de sortie invite et souhaité comme une sous-classe dans Python:
from typegpt import BaseLLMResponse , PromptTemplate
class ExamplePrompt ( PromptTemplate ):
def __init__ ( self , sentence : str ):
self . sentence = sentence
def system_prompt ( self ) -> str :
return "Given a sentence, extract sentence parts."
def user_prompt ( self ) -> str :
return self . sentence
class Output ( BaseLLMResponse ):
num_sentences : int
adjectives : list [ str ]
nouns : list [ str ]
verbs : list [ str ]
Si vous utilisez OpenAI comme fournisseur LLM, remplacez simplement le nom de classe du client OpenAI par la sous-classe TypeOpenAI
(pour Async Utilisez AsyncTypeOpenAI
, ou pour Azure utilisez TypeAzureOpenAI
/ AsyncTypeAzureOpenAI
) pour le rendre sûr. Vous pouvez toujours l'utiliser comme vous le feriez auparavant, mais vous pouvez désormais également appeler la fonction generate_output
pour les compléments de chat comme celui-ci pour générer l'objet de sortie:
from typegpt . openai import TypeOpenAI
prompt = ExamplePrompt ( "The young athlete demonstrated exceptional skill and agility on the field." )
client = TypeOpenAI ( api_key = "<your api key>" ) # subclass of `OpenAI`
output = client . chat . completions . generate_output ( model = "gpt-3.5-turbo" , prompt = prompt , max_output_tokens = 1000 )
Et vous obtenez une belle sortie comme ceci:
Output ( num_sentences = 1 , adjectives = [ 'young' , 'exceptional' ], nouns = [ 'athlete' , 'skill' , 'agility' , 'field' ], verbs = [ 'demonstrated' ])
Votre type de sortie peut contenir une chaîne, un entier, un flotteur, un booléen ou des listes. Il est également possible de marquer les éléments comme facultatifs. Les valeurs par défaut peuvent également être fournies.
class Output ( BaseLLMResponse ):
title : str = "My Recipe"
description : str | None
num_ingredients : int
ingredients : list [ int ]
estimated_time : float
is_oven_required : bool
Ici, l'analyseur analyse description
si le LLM le renvoie, mais n'en aura pas besoin. Ce None
par défaut. Il en va de même pour title
, car il a une valeur par défaut.
Vous pouvez également définir plus de restrictions ou donner au LLM plus d'informations pour certains éléments:
class Output ( BaseLLMResponse ):
title : str = LLMOutput ( instruction = "The title for the recipe." )
description : str | None = LLMOutput ( instruction = "An optional description for the recipe." )
num_ingredients : int
ingredients : list [ int ] = LLMArrayOutput ( expected_count = ( 1 , 5 ), instruction = lambda pos : f"The id of the { pos . ordinal } ingredient" ) # between 1 and 5 ingredients expected (and required at parse time)
estimated_time : float = LLMOutput ( instruction = "The estimated time to cook" )
is_oven_required : bool
Par défaut, la bibliothèque n'attend toujours qu'une seule réponse de ligne par élément. Vous pouvez remplacer cela en définissant multiline=True
dans LLMOutput
:
class Output ( BaseLLMResponse ):
description : str = LLMOutput ( instruction = "A description for the recipe." , multiline = True )
items : list [ str ] = LLMArrayOutput ( expected_count = 5 , instruction = lambda pos : f"The { pos . ordinal } item in the list" , multiline = True )
Vous pouvez nicher les types de réponse. Notez que vous devez utiliser BaseLLMArrayElement
pour les classes que vous souhaitez nicher dans une liste. Pour ajouter des instructions à l'intérieur d'un élément de BaseLLMArrayElement
, vous devez utiliser LLMArrayElementOutput
au lieu de LLMOutput
.
class Output ( BaseLLMResponse ):
class Item ( BaseLLMArrayElement ):
class Description ( BaseLLMResponse ):
short : str | None
long : str
title : str
description : Description
price : float = LLMArrayElementOutput ( instruction = lambda pos : f"The price of the { pos . ordinal } item" )
items : list [ Item ]
count : int
Vous pourriez avoir une invite qui utilise un nombre imprévisible de jetons en raison de dépendances potentiellement importantes. Pour vous assurer que votre invite s'inscrit toujours dans la limite de jeton de LLM, vous pouvez implémenter la fonction reduce_if_possible
dans votre classe rapide:
class SummaryPrompt ( PromptTemplate ):
def __init__ ( self , article : str ):
self . article = article
def system_prompt ( self ) -> str :
return "Summarize the given news article"
def user_prompt ( self ) -> str :
return f"ARTICLE: { self . article } "
def reduce_if_possible ( self ) -> bool :
if len ( self . article ) > 100 :
# remove last 100 characters at a time
self . article = self . article [: - 100 ]
return True
return False
class Output ( BaseLLMResponse ):
summary : str
À l'intérieur de la fonction reduce_if_possible
, vous devez réduire la taille de votre invite en petites étapes et revenir True
si vous êtes réduit avec succès. La fonction est appelée à plusieurs reprises jusqu'à ce que l'invite s'adapte. Lorsque vous appelez la fonction Openai generate_output
, cela garantit automatiquement que l'invite convient aux modèles donnés. De plus, vous pouvez spécifier une limite de jeton d'entrée personnalisée avec le même effet pour réduire les coûts: client.chat.completions.generate_output(..., max_input_tokens=2000)
.
Dans certains cas, GPT pourrait toujours renvoyer une sortie qui ne suit pas correctement le schéma. Lorsque cela se produit, le client OpenAI lance une LLMParseException
. Pour réessayer automatiquement lorsque la sortie ne répond pas à votre schéma, vous pouvez définir retry_on_parse_error
au nombre de tentatives que vous souhaitez autoriser:
out = client . chat . completions . generate_output ( "gpt-3.5-turbo" , prompt = prompt , ..., retry_on_parse_error = 3 )
Maintenant, la bibliothèque tentera d'appeler GPT trois fois avant de lancer une erreur. Cependant, assurez-vous de l'utiliser uniquement lorsque la température n'est pas nulle.
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , ...)
En raison du système de type limité de Python, le type de sortie est de type BaseLLMResponse
au lieu de l'exemple de sous-classe explicite ExamplePrompt.Output
. Pour obtenir une sécurité complète dans votre code, ajoutez simplement le paramètre output_type=ExamplePrompt.Output
:
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , output_type = ExamplePrompt . Output , ...)
Ce paramètre n'est pas simplement un décorateur de type. Il peut également être utilisé pour écraser le type de sortie réel que GPT tente de prédire.
Donnez des exemples au modèle pour expliquer les tâches difficiles à expliquer:
class ExamplePrompt ( PromptTemplate ):
class Output ( BaseLLMResponse ):
class Ingredient ( BaseLLMResponse ):
name : str
quantity : int
ingredients : list [ Ingredient ]
def system_prompt ( self ) -> str :
return "Given a recipe, extract the ingredients."
def few_shot_examples ( self ) -> list [ FewShotExample [ Output ]]:
return [
FewShotExample (
input = "Let's take two apples, three bananas, and four oranges." ,
output = self . Output ( ingredients = [
self . Output . Ingredient ( name = "apple" , quantity = 2 ),
self . Output . Ingredient ( name = "banana" , quantity = 3 ),
self . Output . Ingredient ( name = "orange" , quantity = 4 ),
])
),
FewShotExample (
input = "My recipe requires five eggs and two cups of flour." ,
output = self . Output ( ingredients = [
self . Output . Ingredient ( name = "egg" , quantity = 5 ),
self . Output . Ingredient ( name = "flour cups" , quantity = 2 ),
])
)
]
def user_prompt ( self ) -> str :
...
Assurez-vous d'utiliser AzureChatModel
comme modèle lors de la génération de la sortie, qui se compose de Deployment_ID et du modèle de base correspondant (ceci est utilisé pour réduire automatiquement les invites si nécessaire).
from typegpt . openai import AzureChatModel , TypeAzureOpenAI
client = TypeAzureOpenAI (
azure_endpoint = "<your azure endpoint>" ,
api_key = "<your api key>" ,
api_version = "2023-05-15" ,
)
out = client . chat . completions . generate_output ( model = AzureChatModel ( deployment_id = "gpt-35-turbo" , base_model = "gpt-3.5-turbo" ), prompt = prompt , max_output_tokens = 1000 )
Tout LLM qui a une notion de système et d'invites utilisateur peut utiliser cette bibliothèque. Générez le système et les messages utilisateur (y compris l'invite de schéma) comme ceci:
messages = prompt . generate_messages (
token_limit = max_prompt_length , token_counter = lambda messages : num_tokens_from_messages ( messages )
)
Lorsque max_prompt_length
est le nombre maximal de jetons, l'invite est autorisée à utiliser, et num_tokens_from_messages
doit être une fonction qui compte l'utilisation de jetons prévue pour une liste donnée de messages. Renvoyez 0
ici si vous ne souhaitez pas réduire automatiquement la taille d'une invite.
Utilisez les messages générés pour appeler votre LLM. Analyser la chaîne d'achèvement que vous recevez dans la classe de sortie souhaitée comme celle-ci:
out = ExamplePrompt . Output . parse_response ( completion )
Cette bibliothèque génère automatiquement un schéma compatible LLM à partir de votre classe de sortie définie et ajoute des instructions à la fin de l'invite du système pour adhérer à ce schéma. Par exemple, pour l'invite abstraite suivante:
class DemoPrompt ( PromptTemplate ):
def system_prompt ( self ) -> str :
return "This is a system prompt"
def user_prompt ( self ) -> str :
return "This is a user prompt"
class Output ( BaseLLMResponse ):
title : str
description : str = LLMOutput ( "Custom instruction" )
mice : list [ str ]
L'invite système suivante sera générée:
This is a system prompt
Always return the answer in the following format:
"""
TITLE: <Put the title here>
DESCRIPTION: <Custom instruction>
MOUSE 1: <Put the first mouse here>
MOUSE 2: <Put the second mouse here>
...
"""
Remarquez comment le «souris» pluriel est automatiquement converti en «souris» singulière pour éviter de confondre le modèle de langue.