
O PromptSub o ajudará a criar instruções parametrizadas para modelos de idiomas, quando o conjunto de parâmetros pode alterar para cada amostra. Persegue dois objetivos:
Facilite para os desenvolvedores criar fluxos de trabalho modelo e agnóstico de parâmetros. Isso significa que, em algum momento do seu aplicativo, você pode combinar um modelo rápido com alguns parâmetros, os quais você não sabe nada. E se o resultado não estiver vazio - você está pronto para ir.
Crie uma sintaxe simples e legível por humanos para escrever modelos parametrizados, de modo que mesmo as pessoas não técnicas possam fazê-lo.
A sintaxe arrumada para modelos de redação é uma parte importante do Prompsub, e você pode ler tudo sobre isso na próxima seção.
Mas primeiro vamos percorrer a API, para que você possa entender melhor o objetivo deste projeto. A instalação é casual:
% pip install promptsub
from promptsub import Prompt
template = "Say hello [to {name}]"
prompt = Prompt ( template )Quando criamos uma instância do prompt, a sequência de entrada é analisada e verificada quanto a erros de sintaxe - se houver algum, recebemos uma exceção informativa:
invalid_template = "Say hello [to {name}"
Prompt ( invalid_template ) File "<string>", line 1
Say hello [to {name}
^
promptsub.errors.PromptSyntaxError: Template not closed
Após a criação, o objeto imediato pode ser facilmente usado com qualquer parâmetros válidos:
parameters_batch = (
{ "name" : "John" , "age" : 26 },
{ "name" : "" },
{},
}
for params in parameters_batch :
message = prompt . substitute ( params )
print ( message )
# "Say hello to John"
# "Say hello"
# "Say hello" # The following parameters are accepted:
InputParams: TypeAlias = dict[str, str | int | float]
Vamos escrever um modelo para um aplicativo de sugestão de filme. Prevemos que nem todos os nossos usuários tenham preenchido seus títulos favoritos ; Nesses casos, é melhor perguntar a eles sobre suas preferências. Além disso, um usuário pode solicitar recomendações em um gênero específico ou sem restrições. Finalmente, não é incomum que o nome de um usuário esteja faltando.
template = """
Recommend a [{movie_genre}|movie] to [{user_name}|the user],
who is a fan of {favourite_title}
|
Ask [{user_name}|the user] about their favourite [{movie_genre}|film]
"""
prompt = Prompt ( template )Ok, não ter erros de sintaxe é um bom lugar para começar. A próxima coisa que devemos fazer é "brincar" com este modelo para ver como ele se comporta com diferentes conjuntos de parâmetros. Nosso prompt tem um atributo conviniente para mostrar todas as suas variáveis:
prompt . variables
# [
# (required={'favourite_title'}, optional={'user_name', 'movie_genre'}),
# (required=set(), optional={'movie_genre', 'user_name'})
# ]O modelo possui duas opções de nível superior divididas por um separador: "Recomendar ..." e "Ask ...". Se o primeiro falhar devido à ausência de variáveis necessárias nos parâmetros, o segundo será usado.
Agora, podemos criar valores arbitrários para as variáveis e verificar os resultados. Observe que um valor de string vazio é equivalente a uma chave ausente .
# A similar method will probably be added to the API
from itertools import product
def test_prompt ( prompt : Prompt , test_params : dict ):
value_combinations = product ( * test_params . values ())
for values in value_combinations :
params = dict ( zip ( test_params . keys (), values ))
print ( prompt . substitute ( params )) test_params = {
"movie_genre" : [ "romantic comedy" , "" ],
"favourite_title" : [ "Rio Bravo (1959)" , "" ],
"user_name" : [ "Quentin" , "" ],
}
test_prompt ( prompt , test_params )
# Recommend a romantic comedy to Quentin, who is a fan of Rio Bravo (1959)
# Recommend a romantic comedy to the user, who is a fan of Rio Bravo (1959)
# Ask Quentin about their favourite romantic comedy
# Ask the user about their favourite romantic comedy
# Recommend a movie to Quentin, who is a fan of Rio Bravo (1959)
# Recommend a movie to the user, who is a fan of Rio Bravo (1959)
# Ask Quentin about their favourite film
# Ask the user about their favourite film Aqui está um aplicativo simples que recebe um modelo rápido e alguns parâmetros. Em seguida, ele os combina para obter uma mensagem de entrada para um modelo de idioma, consultas que modelam e retornam sua resposta - enquanto não sabem nada sobre o modelo ou os parâmetros.
Há exatamente três coisas que podem dar errado aqui:
Em nosso exemplo, relatamos diligentemente todos os erros ao cliente.
from functools import partial
from fastapi import FastAPI , HTTPException
from fastapi . responses import JSONResponse
from promptsub import Prompt
from promptsub . errors import ParametersTypeError , PromptSyntaxError
from pydantic import BaseModel
app = FastAPI ()
http_400 = partial ( HTTPException , status_code = 400 )
class Request ( BaseModel ):
template : str
params : dict
def get_response_from_language_model ( message : str ) -> str :
...
@ app . exception_handler ( PromptSyntaxError )
def syntax_exception_handler ( request , exc ):
raise http_400 ( detail = f"Error in template: { exc } " )
@ app . exception_handler ( ParametersTypeError )
def params_exception_handler ( request , exc ):
raise http_400 ( detail = f"Error in params: { exc } " )
@ app . post ( "/ask_language_model" )
def generate_response ( request : Request ):
prompt = Prompt ( request . template )
message = prompt . substitute ( request . params )
if message == "" :
raise http_400 ( detail = "Required variable not provided" )
response = get_response_from_language_model ( message )
return JSONResponse ( content = { "response" : response })Em um projeto real, seu modelo e params podem se originar de diferentes locais, por exemplo, bancos de dados, mas isso realmente não importa. Se você não confia em seus clientes, poderá armazenar seus avisos no aplicativo assim:
import redis
import settings
template_storage = redis . from_url ( settings . redis_dsn , decode_responses = True )
class Request ( BaseModel ):
params : dict
@ app . post ( "/ask_language_model/{template_id}" )
def generate_response ( template_id : str , request : Request ):
template = template_storage . get ( template_id )
if template is None :
raise HTTPException ( status_code = 404 , detail = "Template not found" )
prompt = Prompt ( template )
...Além disso, você pode ser tentado a instanciar seus objetos rápidos com antecedência e armazená -los na memória do programa. Essa abordagem, no entanto, afetará negativamente sua adaptabilidade , a menos que você implemente um cache sofisticado que permita a troca a quente. Lembre -se de que o custo de analisar uma string de modelo é insignificante em comparação com uma chamada para qualquer modelo de idioma.
O PromptSub usa apenas 4 caracteres especiais em sua sintaxe básica: []{} . Graças à sua lógica, você já pode escrever instruções elegantes e poderosas apenas com isso.
Se você deseja ainda mais controle sobre as substituições, a sintaxe avançada adiciona 3 caracteres ao jogo, o que pode substituir vários blocos condicionais do seu código de back -end: = ~ | .
Esta é uma variável : {name} . Se fornecermos um valor para isso, ele será substituído. Se não o fizermos, obteremos uma string vazia:
{name}
| params | resultado |
|---|---|
| {"Nome": "John"} | John |
| {"Age": "26"} | |
| {} |
Obviamente, o ponto principal é inserir variáveis em algum texto "fixo". Esse texto é chamado de modelo . Aqui está simples:
Say hello to
| params | resultado |
|---|---|
| {"Nome": "John"} | Diga olá para |
Não é muito útil? Talvez, mas nos ensina que um modelo pode ter zero ou mais variáveis.
Agora, se adicionarmos uma variável ao nosso modelo, as coisas começam a ficar interessantes:
Say hello to {name}
| params | resultado |
|---|---|
| {"Nome": "John"} | Diga olá para John |
| {"Age": "26"} | |
| {} |
Espera -se ver "diga olá para", não é? Mas, sem dúvida, isso não seria um bom prompt. Se supormos que uma variável pode estar ausente em alguns parâmetros, devemos fazê -lo explicitamente . Então, vamos tornar nossa variável opcional:
Say hello to [{name}]
| params | resultado |
|---|---|
| {} | Diga olá para |
EWW, dois tipos de colchetes juntos? Certo, não parece chique. A boa notícia é que você raramente o usará assim.
Um modelo é um texto com alguns caracteres especiais que permitem interagir com os parâmetros. O modelo pode conter zero ou mais variáveis e, se algum deles estiver ausente dos parâmetros, o resultado será uma string vazia. E essas variáveis opcionais? Bem, eles não existem. Para renderizar um modelo, todas as suas variáveis devem estar em parâmetros. Mas aqui está a parte importante:
Os modelos podem ser aninhados dentro de outros modelos usando os colchetes.
Vamos melhorar nosso exemplo anterior:
Say hello [to {name}]
| params | resultado |
|---|---|
| {"Nome": "John"} | Diga olá para John |
| {} | Diga olá |
O que acontece aqui é que o modelo interno "para {nome}" se torna "para john" ou uma string vazia.
Esta regra simples nos permite criar alguns avisos versáteis:
Write a [{length}] summary about {subject} [in {language}] [from the perspective of {author}]
| params | resultado |
|---|---|
| {"sujeito": "Entropy"} | Escreva um resumo sobre entropia |
| {"sujeito": "The Moon Landing", "Autor": "Aliens"} | Escreva um resumo sobre o pouso da lua da perspectiva dos alienígenas |
| {"comprimento": "detalhado", "idioma": "alemão"} |
O que alcançamos aqui é a seguinte hierarquia de modelos:
Write a summary about {subject}
| - {length}
| - in {language}
| - from the perspective of {author}
A variável necessária é "sujeito", pois sem ela a coisa toda se tornaria uma corda vazia. As variáveis "comprimento", "idioma" e "autor" são necessárias para cada um de seus respectivos modelos, mas como esses modelos estão aninhados, podemos lidar com eles se tornando cordas vazias. Você pode inspecionar as variáveis opcionais e necessárias acessando o atributo ".Variables" de um prompt, como mostrado aqui.
Para resumir, This is a template e [This is a template] também, e eles são idênticos. Os suportes quadrados são necessários para marcar os limites dos modelos aninhados, mas no nível "superior" eles são opcionais.
Às vezes, queremos mudar significativamente nosso prompt sob certas condições. Para permitir isso, um modelo pode ter opções alternativas . Isso é conseguido dividindo seu texto com um personagem especial separador - a barra vertical "|" por padrão.
Durante a substituição, as opções são avaliadas sequencialmente da esquerda para a direita e o primeiro resultado não vazio é retornado:
Examine this picture and [assess the probability of it being taken in {suggested_location} | try to guess where it was taken]
| params | resultado |
|---|---|
| {"sugerido_location": "Japan"} | Examine esta imagem e avalie a probabilidade de ser tomada no Japão |
| {} | Examine esta foto e tente adivinhar para onde foi levada |
Não devemos esquecer que as frases de "nível superior" ainda são modelos, então isso também é válido:
Say hello to {name} | Ask the speaker's name
| params | resultado |
|---|---|
| {"Nome": "John"} | Diga olá para John |
| {"Age": "26"} | Pergunte o nome do orador |
Há situações em que a presença de uma variável é importante, mas seu valor não é. Digamos que queremos dar uma saudação informal aos nossos usuários conhecidos sem mencionar o nome deles.
if "name" in params :
template = "Hey, mate! What's up?"
else :
template = "Hello, sir, how can I help?"Sim, isso funcionará, mas agora seu aplicativo precisa saber sobre algum modelo que: a) pode ter sido escrito por outra pessoa; b) pode exigir mudanças amanhã. Bem, o Promptsub o abordou:
Hey, {~name} mate! What's up? | Hello, sir, how can I help?
| params | resultado |
|---|---|
| {"Nome": "John"} | Ei, cara! E aí? |
| {} | Olá, senhor, como posso ajudar? |
Se você simplesmente iniciar sua variável com um tilde "~", ela se comportará quase como uma normal, o que significa que ainda fará com que seu modelo seja um sring vazio se não houver valor para isso. Mas se um valor for fornecido, ele será ignorado e nossa variável silenciada será "bem -sucedida" substituída por nada.
Como você pode imaginar, não há diferença em onde colocar variáveis suaves em seu modelo. De fato, como pode haver vários deles, às vezes tornará um modelo mais legível se os colocarmos no começo. Suponha que percebemos que nossa saudação informal é demais para pessoas de quem só conhecemos o nome deles. Decidimos que um verdadeiro amigo é alguém que confia em nós com seus segredos. Tudo bem:
{~name} {~bank_account} Hey there, King Midas! | Hello, sir, how can I help?
Outro exemplo:
Shall I book you a dinner place? [ {~address} | Where did you stay? ]
| params | resultado |
|---|---|
| {"endereço": "1600 Pennsylvania Avenue NW, Washington"} | Devo reservar um jantar para você? |
| {} | Devo reservar um jantar para você? Onde você ficou? |
Os condicionais baseados na presença de uma variável nos parâmetros são simples e poderosos. No entanto, eles são limitados. Considere este prompt:
Remind the user to not forget the umbrella
Seria uma coisa boba de escrever se não houver chuva na previsão, certo? Vamos tentar otimizar esse modelo com condicionais. Nossa primeira ideia pode ser:
{~is_rainy} Remind the user to not forget the umbrella
| params | resultado |
|---|---|
| {"is_rainy": "true"} | Lembre o usuário para não esquecer o guarda -chuva |
| {"is_rainy": "false"} | Lembre o usuário para não esquecer o guarda -chuva |
Caramba! Parece que agora nossa condição não deve apenas depender da presença da variável, mas também de seu valor . Felizmente, isso é possível:
{~is_rainy=true} Remind the user to not forget the umbrella
| params | resultado |
|---|---|
| {"is_rainy": "true"} | Lembre o usuário para não esquecer o guarda -chuva |
| {"is_rainy": "false"} |
Se adicionarmos um sinal igual a uma variável, ele se dividirá efetivamente em uma chave (o texto antes dela) e um valor (o texto depois). Essa variável será substituída apenas no caso de presença de um par de valor-chave idêntico nos parâmetros. Isso funciona para as variáveis silenciadas (exemplo acima) e regulares:
Remind the user to not forget the umbrella because it's {weather=rainy}
| params | resultado |
|---|---|
| {"Weather": "Rainy"} | Lembre o usuário para não esquecer o guarda -chuva porque está chuvoso |
| {"Weather": "Sunny"} |
{~length=short} Be as consice as possible | Make the story {length=long}, I will tip
| params | resultado |
|---|---|
| {"Length": "Rainy"} | Seja o mais considente possível |
| {"Length": "Long"} | Faça a história longa, vou dar gorjeta |
Existem poucos, mas eles são importantes:
| regra | requisitos |
|---|---|
| Nomes (chaves) de variáveis | Cadeiras não vazias de cartas ASCII, dígitos e sublinhados |
| Valores de variáveis usadas para comparação (escrito em modelos) | Strings não vazios de qualquer personagem, exceto os especiais básicos de sintaxe: []{} |
| Valores de variáveis fornecidas em parâmetros | Cordas não vazias de qualquer personagem; Inteiros ou flutuadores |
| Caráter mudo variável | Pode ser apenas o primeiro caractere dentro de uma variável |
Além disso, você ficará feliz em saber que o número de variáveis, as opções de modelo e a profundidade do ninho de modelo são limitadas apenas pela sua consciência.
Say hello to {name}
| params | resultado |
|---|---|
| {"name": "null"} | Diga olá para NULL |
| {"Nome": "algumas palavras ruins"} | Diga olá para algumas palavras ruins |
| {"nome": ""} |
Se o seu modelo tiver várias opções, o sem variáveis sempre será válido. Portanto, você deve colocá -lo em último. Incorreto: Ask the user's name | Greet {name}
| params | resultado |
|---|---|
| {"Nome": "John"} | Pergunte o nome do usuário |
Os espaços de branco e as novas linhas podem tornar seus modelos brutos mais legíveis. Mas os modelos de idiomas principalmente não se importam com eles. Além disso, ao testar seus avisos com parâmetros diferentes, você provavelmente não deseja ver os espaços extras.
É por isso que, por padrão, o Prompsub pós -processamento será o resultado da substituição de parâmetros para remover os caracteres de espaço em branco, à direita ou repetidos, incluindo ' t', ' n', ' r', ' f', ' v' etc.
No entanto, pode haver exceções a isso, para que você possa desligá -lo se quiser:
template = "Continue the conversation: {text}"
prompt = Prompt ( template )
text = """
- Also, you know what they call a Quarter Pounder with Cheese in Paris?
- They don't call it a Quarter Pounder with Cheese?
"""
params = { "text" : text }
prompt . substitute ( params )
# Continue the conversation: - Also... - They...
prompt . substitute ( params , postprocess_whitespace_reduction = False )
# Continue the conversation:
# - Also...
# - They...Certifique -se de que todos os seus modelos sejam testados com diversos conjuntos de parâmetros antes de serem usados na produção.
Também conhecido como limitações atuais. Listados em uma ordem arbitrária, essas são apenas as idéias para trabalhos futuros sem um roteiro concreto. As implementações sugeridas parecem ser compatíveis com a sintaxe atual. No entanto, é provável que alterem o formato de saída do método "Prompt.Variables".
As mudanças de quebra não são descartadas no futuro; portanto, preste atenção ao versão.
Não é difícil imaginar uma situação em que uma variável possa servir como uma alternativa ao outro. Digamos que queremos abordar um usuário por seu apelido ou pelo seu primeiro nome , e se Nether estiver louco, peça -lhe uma intrução. No momento, seria assim:
Hello, {user_nickname}! | Hello, {user_firstname}! | Hey! What's your name?
Esta repetição simples parece tolerável. Mas se tivéssemos um modelo mais longo ou mais variáveis "intercambiáveis", acabaríamos com uma corda ampla, que seria difícil de ler e propensar a erros.
Uma solução possível seria adicionar o suporte para várias opções para variáveis, assim como nos modelos: "{user_nickname | user_firstname}". Seria necessário considerar a compatibilidade com símbolos de mudo e comparação. Além disso, nesse caso, os espaços em branco provavelmente devem ser permitidos em determinadas posições dentro das variáveis para uma melhor legibilidade.
Observe como no exemplo anterior tivemos as variáveis {user_nickname} e {user_firstname} . No mundo real, é mais provável que esses não sejam os pares de valor-chave separados, mas os atributos de um user de objeto. O que significa que, em algum momento do seu aplicativo, você precisa fazer algo assim:
params = {
"user_firstname" : request . params . user . firstname ,
"user_nickname" : request . params . user . nickname ,
}
result = prompt . substitute ( params )Considerando que uma abordagem muito melhor seria:
result = prompt.substitute(request.params.model_dump())
Para que isso funcione, precisamos do Prompsub para acessar os atributos dos objetos fornecidos. A sintaxe mais simples seria a notação de pontos: "{user.firstname}". Alguma validação precisaria ocorrer.
Voltando ao prompt de recomendação do filme, pode -se sugerir que, para melhorar a precisão, devemos incluir uma lista dos títulos altamente avaliados do usuário, em vez de apenas referenciar um único. Embora possível, a solução atual seria mais um hack que (você adivinhou) faz com que o aplicativo interfira nos parâmetros:
user_favourite_titles = [
'The Good, the Bad and the Ugly (1966)' ,
'Rio Bravo (1959)' ,
'Blow Out (1981)' ,
'Taxi Driver (1976)' ,
]
template = "Recommend a movie to the user, who is a fan of {favourite_titles}"
params = { "favourite_titles" : ", " . join ( user_favourite_titles )}Suportar iterables como valores de parâmetros exigiria uma consideração cuidadosa em relação à sua validação, comparação e formatação.
Indiscutivelmente, a introdução de operadores adicionais para comparação de valor (como <,>,! = Ou "in") faria mais bom para funcionalidade do que danos à simplicidade porque:
A implementação levaria uma boa quantidade de trabalho no módulo "variável". Deve ser considerado na necessidade.