Es inherentemente difícil producir salidas de LLM en una estructura consistente. Typegpt simplifica que este proceso es tan fácil como definir una clase en Python.
Impulsar nuestros propios proyectos, como Spexia
pip install typegpt
Defina su esquema de salida rápido y deseado como una subclase en 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 está utilizando OpenAI como su proveedor de LLM, simplemente reemplace el nombre de la clase de cliente Operai con la subclase TypeOpenAI
(para async use AsyncTypeOpenAI
, o para usar Azure TypeAzureOpenAI
/ AsyncTypeAzureOpenAI
) para que sea seguro. Todavía puede usarlo como lo hubiera hecho antes, pero ahora también puede llamar a la función generate_output
para completar el chat como esta para generar el objeto de salida:
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 )
Y obtienes una buena salida como esta:
Output ( num_sentences = 1 , adjectives = [ 'young' , 'exceptional' ], nouns = [ 'athlete' , 'skill' , 'agility' , 'field' ], verbs = [ 'demonstrated' ])
Su tipo de salida puede contener cadena, entero, flotación, booleano o listas de estos. También es posible marcar elementos como opcionales. Los valores predeterminados también se pueden proporcionar.
class Output ( BaseLLMResponse ):
title : str = "My Recipe"
description : str | None
num_ingredients : int
ingredients : list [ int ]
estimated_time : float
is_oven_required : bool
Aquí, el analizador analizará description
si el LLM lo devuelve, pero no lo requerirá. No es None
de forma predeterminada. Lo mismo se espera para title
, ya que tiene un valor predeterminado.
También puede definir más restricciones o darle al LLM más información para algunos elementos:
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
Por defecto, la biblioteca siempre espera solo una respuesta de línea por elemento. Puede anular esto configurando multiline=True
en 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 )
Puede anidar los tipos de respuesta. Tenga en cuenta que debe usar BaseLLMArrayElement
para clases que desea anidar dentro de una lista. Para agregar instrucciones dentro de un elemento de BaseLLMArrayElement
, debe usar LLMArrayElementOutput
en lugar 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
Es posible que tenga un aviso que utilice una cantidad impredecible de tokens debido a dependencias potencialmente grandes. Para asegurarse de que su solicitud siempre se ajuste dentro del límite de token de LLM, puede implementar la función reduce_if_possible
dentro de su clase rápida:
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
Dentro de la función reduce_if_possible
, debe reducir el tamaño de su mensaje en pequeños pasos y devolver True
si se reduce con éxito. La función se llama repetidamente hasta que el mensaje se ajusta. Al llamar a la función OpenAI generate_output
, esto garantiza automáticamente que el mensaje sea adecuado para los modelos dados. Además, puede especificar un límite de token de entrada personalizado con el mismo efecto para ahorrar costos: client.chat.completions.generate_output(..., max_input_tokens=2000)
.
En algunos casos, GPT aún podría devolver una salida que no sigue el esquema correctamente. Cuando esto ocurre, el cliente de OpenAI lanza una LLMParseException
. Para volver a intentar automáticamente cuando la salida no cumple con su esquema, puede configurar retry_on_parse_error
en la cantidad de reintentos que desea permitir:
out = client . chat . completions . generate_output ( "gpt-3.5-turbo" , prompt = prompt , ..., retry_on_parse_error = 3 )
Ahora, la biblioteca intentará llamar a GPT tres veces antes de lanzar un error. Sin embargo, asegúrese de usar esto solo cuando la temperatura no es cero.
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , ...)
Debido al sistema de tipo limitado de Python, el tipo de salida es de tipo BaseLLMResponse
en lugar del ejemplo de subclase explícito ExamplePrompt.Output
. Para lograr la seguridad de tipo completo en su código, simplemente agregue el parámetro output_type=ExamplePrompt.Output
:
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , output_type = ExamplePrompt . Output , ...)
Este parámetro no es simplemente un tipo decorador. También se puede usar para sobrescribir el tipo de salida real que GPT intenta predecir.
Dé ejemplos al modelo para explicar las tareas que son difíciles de explicar:
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 :
...
Asegúrese de usar el AzureChatModel
como modelo al generar la salida, que consiste en el implementment_ID y el modelo base correspondiente (esto se utiliza para reducir automáticamente las indicaciones si es necesario).
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 )
Cualquier LLM que tenga una noción de indicaciones y indicaciones del usuario puede usar esta biblioteca. Genere el sistema y los mensajes de usuario (incluido el indicador del esquema) como este:
messages = prompt . generate_messages (
token_limit = max_prompt_length , token_counter = lambda messages : num_tokens_from_messages ( messages )
)
Donde max_prompt_length
es el número máximo de tokens, se permite usar el indicador, y num_tokens_from_messages
debe ser una función que cuenta el uso de token predicho para una lista dada de mensajes. Regrese 0
aquí si no desea reducir automáticamente el tamaño de un mensaje.
Use los mensajes generados para llamar a su LLM. Analice la cadena de finalización que recibe nuevamente en la clase de salida deseada como esta:
out = ExamplePrompt . Output . parse_response ( completion )
Esta biblioteca genera automáticamente un esquema compatible con LLM a partir de su clase de salida definida y agrega instrucciones al final de la solicitud del sistema para cumplir con este esquema. Por ejemplo, para el siguiente mensaje de resumen:
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 ]
Se generará el siguiente mensaje del sistema:
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>
...
"""
Observe cómo los "ratones" plurales se convierten automáticamente al "mouse" singular para evitar confundir el modelo de idioma.