É inerentemente difícil produzir saídas a partir de LLMs em uma estrutura consistente. O TypeGPT simplifica esse processo para ser tão fácil quanto definir uma classe em Python.
Alimentando nossos próprios projetos, como a Spexia
pip install typegpt
Defina seu esquema de saída rápido e desejado como uma subclasse em 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 ]
Se você estiver usando o OpenAI como seu provedor LLM, basta substituir o nome da classe do cliente OpenAI pelo Subclasse TypeOpenAI
(para usar async use AsyncTypeOpenAI
ou para usar o Azure TypeAzureOpenAI
/ AsyncTypeAzureOpenAI
) para torná -lo seguro. Você ainda pode usá -lo como teria antes, mas agora também pode chamar a função generate_output
para conclusões de bate -papo como esta para gerar o objeto de saída:
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 )
E você obtém uma boa saída como esta:
Output ( num_sentences = 1 , adjectives = [ 'young' , 'exceptional' ], nouns = [ 'athlete' , 'skill' , 'agility' , 'field' ], verbs = [ 'demonstrated' ])
Seu tipo de saída pode conter string, número inteiro, flutuador, booleano ou listas destes. Também é possível marcar elementos como opcionais. Os valores padrão também podem ser fornecidos.
class Output ( BaseLLMResponse ):
title : str = "My Recipe"
description : str | None
num_ingredients : int
ingredients : list [ int ]
estimated_time : float
is_oven_required : bool
Aqui, o analisador analisará description
se o LLM o retornar, mas não exigirá. Não é None
por padrão. O mesmo vale para title
, pois possui um valor padrão.
Você também pode definir mais restrições ou fornecer ao LLM mais informações para alguns 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 padrão, a biblioteca sempre espera apenas uma resposta de linha por elemento. Você pode substituir isso definindo multiline=True
em 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 )
Você pode aninhar tipos de resposta. Observe que você precisa usar BaseLLMArrayElement
para as aulas que deseja aninhar dentro de uma lista. Para adicionar instruções dentro de um elemento de BaseLLMArrayElement
, você deve usar LLMArrayElementOutput
em vez do 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
Você pode ter um aviso que usa um número imprevisivelmente grande de tokens devido a dependências potencialmente grandes. Para garantir que seu prompt sempre se encaixe no limite de token do LLM, você pode implementar a função reduce_if_possible
dentro da sua classe prompt:
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 da função reduce_if_possible
, você deve reduzir o tamanho do seu prompt em pequenas etapas e retornar True
se for reduzido com sucesso. A função é chamada repetidamente até que o prompt se encaixe. Ao chamar a função OpenAI generate_output
, isso garante automaticamente que o prompt seja adequado para os modelos fornecidos. Além disso, você pode especificar um limite de token de entrada personalizado com o mesmo efeito para economizar custos: client.chat.completions.generate_output(..., max_input_tokens=2000)
.
Em alguns casos, o GPT ainda pode retornar uma saída que não segue o esquema corretamente. Quando isso ocorre, o cliente OpenAI lança uma LLMParseException
. Para tentar novamente automaticamente quando a saída não atender ao seu esquema, você pode definir retry_on_parse_error
para o número de tentativas que deseja permitir:
out = client . chat . completions . generate_output ( "gpt-3.5-turbo" , prompt = prompt , ..., retry_on_parse_error = 3 )
Agora, a biblioteca tentará ligar para o GPT três vezes antes de lançar um erro. No entanto, verifique se você usa isso apenas quando a temperatura não for zero.
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , ...)
Devido ao sistema de tipo limitado do Python, o tipo de saída é do tipo BaseLLMResponse
em vez da subclasse explícita ExamplePrompt.Output
. Para alcançar a segurança do tipo completo em seu código, basta adicionar o 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 não é apenas um decorador de tipo. Também pode ser usado para substituir o tipo de saída real que o GPT tenta prever.
Dê exemplos ao modelo para explicar tarefas difíceis 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 :
...
Certifique -se de usar o AzureChatModel
como modelo ao gerar a saída, que consiste no MOPMAPLIMENT_ID e no modelo básico correspondente (isso é usado para reduzir automaticamente os prompts, se necessário).
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 )
Qualquer LLM que tenha uma noção de sistema e avisos de usuário pode usar esta biblioteca. Gere o sistema e as mensagens do usuário (incluindo o prompt de esquema) como este:
messages = prompt . generate_messages (
token_limit = max_prompt_length , token_counter = lambda messages : num_tokens_from_messages ( messages )
)
Onde max_prompt_length
é o número máximo de tokens que o prompt pode usar e num_tokens_from_messages
precisa ser uma função que conta o uso de token previsto para uma determinada lista de mensagens. Retorne 0
aqui se você não deseja reduzir automaticamente o tamanho de um prompt.
Use as mensagens geradas para ligar para o seu LLM. Analise a sequência de conclusão que você recebe de volta à classe de saída desejada assim:
out = ExamplePrompt . Output . parse_response ( completion )
Essa biblioteca gera automaticamente um esquema compatível com LLM da sua classe de saída definida e adiciona instruções ao final do prompt do sistema para aderir a esse esquema. Por exemplo, para o seguinte prompt de abstrato:
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 ]
O prompt do sistema a seguir será gerado:
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 como o "rato" plural é convertido automaticamente ao "mouse" singular para evitar confundir o modelo de idioma.