По своей сути трудно произвести выходы из LLM в постоянной структуре. TypeGpt упрощает этот процесс таким же простым, как определение класса в Python.
Обеспечение наших собственных проектов, таких как Spexia
pip install typegpt
Определите свою подсказку и желаемую схему вывода в качестве подкласса в 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 ]
Если вы используете OpenAI в качестве поставщика LLM, просто замените имя класса клиента OpenAI на подкласс TypeOpenAI
(для асинхронического использования AsyncTypeOpenAI
или для использования Azure TypeAzureOpenAI
/ AsyncTypeAzureOpenAI
), чтобы сделать его безопасным. Вы все еще можете использовать его, как это было бы раньше, но теперь вы можете также вызвать функцию generate_output
для такого завершения чата, чтобы сгенерировать выходной объект:
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 )
И вы получаете хороший выход, как это:
Output ( num_sentences = 1 , adjectives = [ 'young' , 'exceptional' ], nouns = [ 'athlete' , 'skill' , 'agility' , 'field' ], verbs = [ 'demonstrated' ])
Ваш выходной тип может содержать строки, целое число, плавание, логическое или их списки. Также возможно пометить элементы как необязательные. Значения по умолчанию также могут быть предоставлены.
class Output ( BaseLLMResponse ):
title : str = "My Recipe"
description : str | None
num_ingredients : int
ingredients : list [ int ]
estimated_time : float
is_oven_required : bool
Здесь анализатор будет анализировать description
, если LLM вернет его, но не потребует этого. Это None
по умолчанию. То же самое относится и к title
, поскольку он имеет значение по умолчанию.
Вы также можете определить больше ограничений или предоставить LLM больше информации для некоторых элементов:
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
По умолчанию библиотека всегда ожидает только одного ответа линии на элемент. Вы можете переопределить это, установив multiline=True
в 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 )
Вы можете гнездовать типы ответов. Обратите внимание, что вам нужно использовать BaseLLMArrayElement
для классов, которые вы хотите гнездиться в списке. Чтобы добавить инструкции в элемент BaseLLMArrayElement
, вы должны использовать LLMArrayElementOutput
вместо 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
У вас может быть подсказка, которая использует непредсказуемо большое количество жетонов из -за потенциально больших зависимостей. Чтобы убедиться, что ваша подсказка всегда вписывается в предел токена LLM, вы можете реализовать функцию reduce_if_possible
В вашем классе запросов:
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
Внутри функции reduce_if_possible
вы должны уменьшить размер вашей подсказки в небольших шагах и вернуть True
, если успешно уменьшен. Функция вызывается неоднократно, пока подсказка не подходит. При вызове функции openai generate_output
это автоматически гарантирует, что подсказка подходит для данных моделей. Кроме того, вы можете указать пользовательский предел входного токена с тем же эффектом для сохранения затрат: client.chat.completions.generate_output(..., max_input_tokens=2000)
.
В некоторых случаях GPT может по -прежнему вернуть вывод, который не соответствует схеме правильно. Когда это происходит, клиент Openai бросает LLMParseException
. Чтобы автоматически повторно повторно, когда вывод не соответствует вашей схеме, вы можете установить retry_on_parse_error
к количеству поисков, которые вы хотите разрешить:
out = client . chat . completions . generate_output ( "gpt-3.5-turbo" , prompt = prompt , ..., retry_on_parse_error = 3 )
Теперь библиотека попытается позвонить в GPT три раза, прежде чем бросить ошибку. Однако убедитесь, что вы используете это только тогда, когда температура не равна нулю.
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , ...)
Из -за системы с ограниченным типом Python выходной тип имеет тип BaseLLMResponse
вместо явного ExamplePrompt.Output
. Чтобы достичь безопасности полного типа в вашем коде, просто добавьте параметр output_type=ExamplePrompt.Output
:
prompt = ExamplePrompt (...)
output = client . chat . completions . generate_output ( model = "gpt-4" , prompt = prompt , output_type = ExamplePrompt . Output , ...)
Этот параметр не просто декоратор типа. Он также может быть использован для перезаписи фактического выходного типа, который GPT пытается предсказать.
Приведите примеры модели, чтобы объяснить задачи, которые трудно объяснить:
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 :
...
Обязательно используйте AzureChatModel
в качестве модели при генерации вывода, который состоит из Deployment_id и соответствующей базовой модели (это используется для автоматического снижения подсказок, если это необходимо).
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 )
Любой LLM, который имеет понятие системы и пользовательских подсказок, может использовать эту библиотеку. Сгенерировать систему и сообщения пользователей (включая схему подсказки) как это:
messages = prompt . generate_messages (
token_limit = max_prompt_length , token_counter = lambda messages : num_tokens_from_messages ( messages )
)
где max_prompt_length
- это максимальное количество токенов, которое разрешено использовать подсказка, и num_tokens_from_messages
должна быть функцией, которая подсчитывает прогнозируемое использование токена для данного списка сообщений. Верните 0
здесь, если вы не хотите автоматически уменьшать размер подсказки.
Используйте сгенерированные сообщения, чтобы вызвать ваш LLM. Проанализируйте строку завершения, которую вы получаете обратно в желаемый класс вывода, как это:
out = ExamplePrompt . Output . parse_response ( completion )
Эта библиотека автоматически генерирует LLM-совместимую схему из вашего определенного вывода и добавляет инструкции в конце подсказки системы, чтобы придерживаться этой схемы. Например, для следующей абстрактной подсказки:
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 ]
Будет сгенерирована следующая система системы:
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>
...
"""
Обратите внимание, как множественное число «мыши» автоматически преобразуется в единственную «мышь», чтобы избежать путаницы языковой модели.