resumos
Os modelos de linguagem grande (LLMs) despertaram um grande interesse em todo o mundo, possibilitando muitos aplicativos de IA que antes eram ilusórios. Os LLMs são controlados por solicitações textuais altamente expressivas e retornam respostas textuais. No entanto, esse texto não estruturado de entrada e saída torna os aplicativos baseados em LLM vulneráveis. Isso alimentou o surgimento de estruturas de sugestões que visam regular a interação do LLM com o mundo externo. No entanto, as estruturas de dicas existentes têm uma alta curva de aprendizado ou privam os desenvolvedores do controle sobre dicas precisas. Para resolver esse dilema, este documento apresenta a linguagem declarativa de dicas (PDL). A PDL é uma linguagem declarativa simples direcionada a dados, baseada em YAML, que coloca as dicas em seu núcleo. A PDL funciona bem com várias plataformas de LLM e LLMs, oferece suporte à criação de aplicativos interativos que invocam LLMs e ferramentas e é fácil de implementar, por exemplo, chatbots, RAGs ou agentes, entre casos de uso comuns, como chatbots, RAGs ou proxies. Esperamos que a PDL torne a programação imediata mais fácil, mais robusta e mais agradável.
1. introdução
Os modelos de linguagem ampla (LLMs) progrediram muito, demonstrando a capacidade de executar uma variedade de tarefas úteis. Como os LLMs são controlados por meio de dicas de linguagem natural, a engenharia de dicas tornou-se um método ad hoc para aumentar a precisão (White et al.2023). Aprendizagem por meio de padrões de dicas, como a aprendizagem contextual (Brown et al.2020), várias cadeias de chamadas LLM (Chase et al.2022), geração aprimorada (RAG) (Lewis et al.2020), uso de ferramentas (Schick et al.2023), o modelo de linguagem auxiliar processual (PAL) (Gao et al.2023) e agentes (Yao et al.2023) pode desbloquear mais recursos. No entanto, apesar de seu poder, o LLM ainda é frágil: às vezes, ele tem alucinações ou até mesmo não adere à sintaxe e aos tipos esperados.
A estrutura de dicas (Liu et al.2023) permite que os desenvolvedores usem o LLM e os padrões de dicas relacionados com mais facilidade e, ao mesmo tempo, reduzam sua vulnerabilidade. Alguns frameworks, como o LangChain (Chase et al.2022) e AutoGen (Wu et al.2023), fornecendo recursos específicos para padrões populares, como RAGs ou proxies. No entanto, esses recursos podem privar os usuários do controle sobre os prompts básicos e forçá-los a aprender muitos recursos complexos da estrutura. Por outro lado, estruturas de prompting de baixo nível, como o Guidance (Microsoft.2023) e LMQL (Beurer-Kellner et al.2023) que oferecem mais controle por meio de sintaxe e tipos. No entanto, elas exigem que os usuários programem em linguagens imperativas, como Python ou TypeScript. Estruturas no outro extremo do espectro, como o DSPy (Khattab et al.2023) e Vieira (Li et al.2024), evitando completamente os prompts escritos à mão ao gerar prompts automaticamente. Infelizmente, isso tira ainda mais o controle do desenvolvedor. A questão, portanto, é como tornar a programação LLM mais robusta e manter o desenvolvedor no comando, sem perder a simplicidade.
Para resolver esse problema, nos baseamos em ideias de design de linguagem de programação testadas pelo tempo. O princípio da ortogonalidade defende o uso de um conjunto pequeno e simples de funções que são combinadas para obter uma funcionalidade poderosa (van Wijngaarden et al.1977). Nesse contexto, ortogonalidade significa evitar casos especiais o máximo possível. Para estruturas de dicas, a ortogonalidade é uma forma de evitar recursos específicos. Em seguida, se a linguagem puder passar pela verificação de tipos e funções (Hugging Face, o2023) forçada estruturalmente, os desenvolvedores terão menos dificuldades com a vulnerabilidade. Permanece uma tensão intratável: por um lado, queremos que os desenvolvedores possam controlar dicas precisas e, por outro lado, precisamos de uma linguagem declarativa simples. Por esse motivo, optamos por uma linguagem orientada a dados que deliberadamente obscurece a linha entre procedimentos (por exemplo, para cadeias e ferramentas) e dados (para dicas). Essa inspiração veio da antiga noção de código como dados (McCarthy.1960), e o trabalho seminal sobre programação sem camadas (Cooper et al.2006).
Este artigo apresenta a Prompt Declaration Language (PDL), uma linguagem orientada a dados ortogonal e tipada. Diferentemente de outras linguagens de prompt incorporadas em linguagens imperativas, a PDL é baseada em YAML (Ben-Kiki et al.2004O YAML é um formato de serialização de dados que pode ser lido por humanos (facilitando uma sintaxe simples para cadeias de caracteres não estruturadas) e estruturado (compatível com JSON). As variáveis no PDL também contêm valores JSON e, opcionalmente, usam o esquema JSON (Pezoa et al.2016Atualmente, a PDL é implementada por um interpretador que executa a verificação dinâmica de tipos. Uma vantagem de representar programas como dados é a facilidade de conversão de programas (Mernik et al.2005), por exemplo, para otimização. A renderização de programas em um formato de representação de dados pode até mesmo facilitar que os programas PDL gerem programas PDL por meio de um modelo de linguagem grande, semelhante ao PAL (Gao et al.2023).
Os programas PDL consistem em blocos (objetos YAML), cada um dos quais adiciona dados ao contexto do prompt. Essa mentalidade é ideal para uso com tecnologias de prompting, como chatbots ou agentes: a execução do programa constrói implicitamente o diálogo ou a trajetória sem pipelining explícito. O PDL oferece suporte a LLMs nativos, bem como a LLMs hospedados por vários fornecedores, incluindo, entre outros, o modelo Granite de código aberto no IBM Watsonx1 e Replicate2 (Abdelaziz et al.2024Equipe Granite, IBM.2024A PDL fornece loops e estruturas de controle condicional, bem como introdução de funções e arquivos para modularidade.2008) para modelar não apenas dicas, mas programas inteiros.
Este artigo fornece uma visão geral da PDL por meio de um exemplo introdutório (Seção 2), seguido de uma descrição detalhada da linguagem (Seção 3). Ele descreve as ferramentas para executar e editar programas PDL (Seção 4), e fornece estudos de caso para demonstrar outros aplicativos da PDL (Seção 5). Por fim, o documento discute trabalhos relacionados (Seção 6), e emSeção 7O PDL é de código aberto e pode ser encontrado no diretório https://github.com/IBM/prompt-declaration-language Como obtê-la. De modo geral, a PDL é uma nova linguagem de programação simples, mas poderosa, para prompt LLM.
2. visão geral
Esta seção oferece uma visão geral da funcionalidade do PDL por meio de um exemplo de chatbot. Um programa PDL executa uma série de pedaço (de terra)Os dados gerados por cada bloco são contribuídos para o contexto em segundo plano. Há vários tipos de blocos que podem gerar dados de diferentes maneiras: chamadas de modelo, leitura de dados de stdin ou arquivos, criação direta de vários dados JSON e execução de código. Além disso, há uma variedade de blocos de controle (if-then-else, for e repeat) que permitem que os usuários do PDL expressem pipelines de dados avançados e aplicativos de IA.
mapa 1(a) mostra o código PDL de um chatbot simples. O bloco read: nas linhas 1-4 imprime uma mensagem solicitando que o usuário insira uma consulta e a lê de stdin. Fig. 1(b) É mostrado um rastro da execução do mesmo programa. Por exemplo, um usuário pode perguntar "O que é uma salada de idiomas?" . Para evitar a repetição, a cláusula "attribute: [context]" coloca a resposta do usuário no contexto de fundo, mas não o resultado (o que é impresso no stdout).
O bloco repeat:until: nas linhas 5-16 contém um bloco text: aninhado, que, por sua vez, contém uma sequência de dois blocos aninhados. Os blocos text: convertem os resultados de seus blocos aninhados em cadeias de caracteres e os concatenam. O bloco model: nas linhas 7-9 chama um modelo de linguagem grande (LLM) que usa o contexto acumulado atual como uma dica. Na primeira iteração do loop, o contexto consiste em apenas duas linhas: "What is your query?" (Qual é a sua consulta?) e "What's a language salad?" (O que é uma salada de linguagem?). O parâmetro do modelo 'stop: [\n\n]' faz com que o LLM pare de gerar tokens após duas quebras de linha consecutivas terem sido geradas. O interpretador LLM imprime a saída do LLM em verde; Fig. 1(b) mostra que, neste exemplo, o LLM gerou "A language salad is [...]". O bloco read: nas linhas 10-15 imprime a mensagem usando a sintaxe de string de várias linhas do YAML (começando com uma linha vertical (|)). Esse exemplo mostra como a PDL coloca o prompt em primeiro lugar, facilitando a leitura e dando ao desenvolvedor um controle preciso. O rastreamento do interpretador à direita mostra que o usuário digitou "Say it as a poem!", que é definido como a variável question à esquerda na linha 10 e anexado ao contexto na linha 12. A cláusula until: na linha 16 especifica que a expressão Jinja2 '${question == "quit"}'
A PDL usa a sintaxe '${...}' para incorporar modelos Jinja2 em vez de '{{...}}' em vez de '{{...}}', porque a última é incompatível com os caracteres especiais do YAML (chaves).
Na segunda iteração do loop, o contexto contém os efeitos da primeira iteração do loop. Assim, a segunda execução do bloco model: vê o resultado da primeira execução e pode parafraseá-lo como um poema, "Em um mundo onde muitas línguas [...]", como mostrado aqui 1(b). Eventualmente, durante a execução do segundo bloco read: deste exemplo, o usuário digita "quit", fazendo com que o loop seja encerrado. Agora que já vimos alguns blocos PDL comuns (read:, repeat:, text: e model:) em ação, podemos passar para o segundo bloco read:. 3 seção que descreve os blocos restantes e os recursos da linguagem.
(a) Código
- leia.
contribuição: [contexto]
mensagem: |
Qual é a sua consulta?
- repeat: | [context] message: | [context] message: | What is your query?
texto.
- modelo: watsonx/ibm/granite-13b-chat-v2
parâmetros: stop: ["\n\n"]]
stop: ["\n\n"]]
- def: question
ler: ["\n\n"] def: question
contribuir: [contexto]
message: |
Digite uma consulta ou diga "quit" para sair.
until: ${question == "quit"}
(b) Rastreamento de intérpretes
O que é sua consulta?
O que é uma salada de idiomas?
Salada linguística é um termo usado para descrever a mistura de diferentes idiomas e dialetos em um único diálogo ou texto. Ela pode ser vista como [...]
Digite uma consulta ou diga "quit" para sair.
Expressão na forma de um poema!
Em um mundo de muitos idiomas.
A salada de idiomas nasce e cresce em alegria.
As palavras se entrelaçam e fluem em harmonia.
Uma linguagem colorida, um florescimento vibrante.
Digite uma consulta ou diga "quit" para sair.
Sair
Figura 1: Chatbot simples em PDL
3. idioma
Figura 2: Referência rápida da PDL
O PDL é uma linguagem incorporada ao YAML, o que torna cada programa PDL um documento YAML válido que está em conformidade com a arquitetura PDL. Figura 2 é uma referência rápida à PDL e é explicada nesta seção usando as regras de sintaxe. Um programa é um bloco ou uma lista de blocos, em que um bloco pode ser uma expressão ou um bloco estruturado, conforme mostrado nas regras de sintaxe a seguir:
pdl ::= bloco | [bloco, . . . ,block]
bloco ::= expressão | bloco_estruturado
Todas as regras de sintaxe nesta seção usam a sintaxe de estilo de fluxo do YAML (por exemplo, [block, ...,block]). ...,block]). O mesmo código PDL também pode ser renderizado com a sintaxe de estilo de bloco do YAML, por exemplo:
- bloco
... - bloco
Cada bloco contém um corpo de bloco com uma palavra-chave que indica o tipo de bloco (por exemplo, modelo ou leitura). Há 15 tipos de corpos de bloco (os campos opcionais são anotados com pontos de interrogação):
block_body ::=
model:expression,input:?pdl,parameters:?
expression
| read:file,message:?
string,message:?
bool
| text:pdl
| lastOf:pdl
| array:pdl
| object:pdl
| data:json
| include:file
| function:args,return:pdl
| call:𝑓,args:args
| if:expressão,then:pdl,else:?pdl
| for:args,repeat:𝑝𝑑𝑙,join:?
join
| repeat:pdl,num_iterations:n,join:?
join
| repeat:pdl,until:expression,join:?
join
| code:pdl,lang:string
Já vimos os blocos model: e read: na seção anterior. Os blocos model: invocam o modelo de linguagem grande. Os prompts são extraídos do contexto atual, a menos que o campo opcional input: seja especificado. O campo opcional parameters: é usado para configurar o comportamento de inferência do modelo. read: lê a entrada de um arquivo ou da entrada padrão se nenhum nome de arquivo for especificado. O campo opcional message: é usado para exibir uma mensagem para o usuário; o campo opcional multiline: determina se deve parar nas quebras de linha.
Os cinco tipos de blocos para a criação de dados incluem: text:, lastOf:, array:, object: e data:. Figura 2 Elas são mostradas em um exemplo simples. A lista de blocos sem a palavra-chave é mostrada como lastOf:. A diferença entre o bloco object: e o bloco data: é que o interpretador PDL ignora a palavra-chave PDL no bloco data: e a trata como um campo JSON normal.
Para fins de modularidade, a PDL oferece suporte a include: blocos e funções. include: blocos abrem programas PDL em um caminho relativo especificado e adicionam sua saída ao local onde ela aparece. A sintaxe dos argumentos da função é a seguinte:
args::={x:exp ression,... ,x:expres sion}
Cada x:expressi mapeia nomes de parâmetros para especificações de tipo (em function: definition) ou valores (em function call:). return: keyword fornece o corpo da função, que pode conter blocos aninhados; a figura 2 Um exemplo simples de expressão Jinja2 é mostrado. A palavra-chave opcional pdl_context: redefine o contexto durante uma chamada, por exemplo, para o contexto vazio [].
Há três tipos de blocos de controle: if:, for: e várias formas repeat:. Eles podem conter blocos aninhados ou expressões simples; se contiverem uma lista de blocos, a lista se comportará como lastOf: por padrão. Se você não quiser o comportamento lastOf:, uma abordagem comum é encapsular o corpo do loop em um bloco text: ou mesclar os resultados das iterações do loop usando a palavra-chave join::
join::=as:? (text∣array∣lastOf),with:? string
Os 15 blocos acima podem ser usados em combinação com zero ou mais palavras-chave opcionais que se aplicam a qualquer bloco:
estruturado _block::=
{ block_body.
description:?
string, def:?
def:?
x, defs:?
defs:?
x, defs:?
def:?x, defs:?defs, role:?
defs:?
defs:?
contribuir, parser:?parser, parser:?parser, parser
parser:?
spec:?
type }
description: é um comentário especial. def: atribui o resultado de um bloco a uma variável; figura 1 Já existe um exemplo na linha 10 da PDL. Por outro lado, defs: cria várias definições de variáveis, cada uma com seu próprio nome, x, e atribui valores por meio de um procedimento PDL aninhado:
defs::={x:pdl ,...,x:pdl}
role: atribui uma função específica, como "usuário", "assistente" ou "sistema", aos dados gerados pelo bloco. As chamadas PDL para o modelo de chat seguem a prática comum das APIs de chat modernas, passando pares de sequências {content:str, role:str}, em vez de texto simples, como prompts. Em seguida, a API do modelo aplica o modelo de bate-papo específico do modelo e organiza a sequência inserindo as tags de controle apropriadas, o que dá ao programa PDL alguma independência do modelo. Se o bloco não especificar explicitamente a função:, o padrão do bloco do modelo é "assistente" e o padrão dos outros blocos é "usuário". As funções dos blocos incorporados são consistentes com as dos blocos externos. Em pesquisas futuras, também planejamos implementar mecanismos de segurança baseados em permissão usando funções.
A palavra-chave contribute: pode ser usada para especificar um subconjunto (possivelmente vazio) para dois destinos: 'result' ou 'context'. Por padrão, cada módulo contribui para seu próprio resultado e para o contexto de fundo usado para chamadas subsequentes do Modelo de Linguagem Grande (LLM). Figura 1 A linha 2 mostra um exemplo de restrição das contribuições do módulo apenas ao contexto de fundo para simplificar a saída.
A palavra-chave parser: permite que um módulo que normalmente gera apenas cadeias de caracteres simples (por exemplo, uma chamada LLM) gere dados estruturados. Os analisadores compatíveis incluem json, yaml, regex e jsonl. spec: A palavra-chave especifica o tipo. Os tipos para PDL são um subconjunto do JSON Schema (Pezoa et al. 2016), Fig. 2 Uma breve demonstração da sintaxe abreviada comum é mostrada em. Por exemplo, o tipo '{questions: [str], answers: [str]}' é um objeto que contém dois campos para perguntas e respostas, ambos contendo matrizes de cadeias de caracteres. O primeiro campo 5 A seção mostrará como o parser: e o spec: trabalham juntos. Trabalhos futuros também utilizarão essas palavras-chave para decodificação de restrições (Scholak et al. 2021).
Um bloco atômico é uma expressão:
expressão ::= bool | número | string | ${𝑗𝑖𝑛𝑗𝑎_𝑒𝑥𝑝𝑟𝑒 𝑠𝑠𝑖𝑜𝑛} | string_expression
As expressões podem ser valores básicos, expressões Jinja2 (Ronacher. 2008O Jinja2 é uma maneira conveniente de especificar modelos para dicas, em que partes da dica são codificadas e outras partes são preenchidas por expressões. No entanto, o PDL amplia ainda mais o uso do Jinja2, permitindo que os desenvolvedores criem modelos não apenas para dicas individuais, mas para cadeias de chamadas de modelos inteiras e outros módulos. Embora recomendemos que os leitores consultem a documentação do Jinja2 para obter uma lista completa das possíveis expressões, a Figura 2 O PDL usa apenas expressões Jinja2, excluindo declarações Jinja2, como {% se ... %}
talvez {% para ... %}
porque elas já se sobrepõem às funções if: e for: da PDL.
Por último, mas não menos importante, a PDL tem um módulo code: que permite a execução de código em uma determinada linguagem de programação (até o momento em que este texto foi escrito, apenas Python era compatível). A próxima seção descreve as ferramentas do PDL, incluindo o interpretador, que fornece um recurso de sandboxing para reduzir o risco de execução de código arbitrário. Para obter mais informações, consulte o link para o tutorial no repositório GitHub do PDL.
4. ferramentas
A PDL fornece ferramentas para facilitar a escrita, a execução e a compreensão dos programas PDL.
Em primeiro lugar, o PDL intérprete é um mecanismo de execução com uma interface de linha de comando, como seria de se esperar de uma linguagem de script. O interpretador é compatível com o modo de fluxo contínuo, em que a saída do LLM é progressivamente visível à medida que é gerada, proporcionando, assim, uma experiência de bate-papo mais interativa. O interpretador também suporta sandboxing, o que permite que ele seja iniciado em um contêiner e é recomendado ao executar ações ou códigos gerados pelo LLM.
PDL Suporte a IDE O VSCode foi aprimorado para facilitar a escrita de código PDL por meio de realce de sintaxe, preenchimento automático, dicas de ferramentas para palavras-chave PDL e verificação de erros. Esses recursos são orientados em parte pelo metamodo PDL, o esquema JSON que define uma PDL válida.
%%pdl
unidade de magia Aprimorados com o Jupyter Notebooks, os desenvolvedores podem escrever unidades de código diretamente no PDL. Dessa forma, a plataforma de notebook hospedada pode ser usada como um playground simples para a exploração interativa de prompts. Considerando várias unidades de código PDL no mesmo notebook, as unidades posteriores podem usar variáveis definidas em unidades anteriores. Além disso, o contexto de fundo da unidade posterior é transferido da unidade anterior; quando isso não for desejado, os desenvolvedores podem usar a função %%pdl --reset-context
para substituir esse comportamento.
PDL Visualizador de documentos em tempo real Um traço específico da execução do programa PDL é exibido na forma de caixas coloridas aninhadas, semelhante ao gráfico típico encontrado em uma dissertação ou postagem de blog sobre dicas de LLM. O usuário pode então selecionar uma das caixas para exibir o código PDL correspondente, semelhante a uma célula de planilha que exibe dados, mas o usuário pode selecioná-las para examinar a fórmula que gerou esses dados. Essa visualização em tempo real permite que o usuário compreenda rapidamente os dados específicos e, em seguida, passe a compreender o código que gerou esses dados.
Por fim, a PDL tem um SDK(Isso é útil para estender aplicativos Python maiores para usar procedimentos baseados em dicas, como agentes. Conforme descrito na Seção 3 Conforme discutido na Seção , os arquivos PDL podem conter Python em blocos de código. Ao desenvolver aplicativos maiores usando o PDL, achamos útil manter esse código em poucas linhas, definindo a função em um arquivo Python separado e chamando-a a partir do PDL. Uma boa prática é passar dados entre a PDL e o Python na forma de objetos JSON. Opcionalmente, você pode usar a função espec.
e TypedDict ou Pydantic no lado do Python para verificação de tipo, conforme mostrado na próxima seção. 3 Exibido.
(a) Códigos PDL
1texto.
2-lang: python
3 código: |
4 import rag_mbpp
5 PDL_SESSION.mbpp = rag_mbpp.initialise()
6 result = ""
7- defs.
8 test_query: >-
9 Escreva uma função Python que remova a primeira e a última ocorrência de um determinado caractere de uma string.
12 recuperado.
13 lang: python
14 spec: [{query: str, answer: str}]
15 code: |
16 import rag_mbpp
17 result = rag_mbpp.retrieve(
18 PDL_SESSION.mbpp, "${test_query}", 5
19 )
20 text: >
21 Dado o texto após "Q:", gere uma função Python após "A:".
24 Aqui estão alguns exemplos, termine o último:
25- for.
26 few_shot_sample: ${retrieved}
27 repeat: |
28 Q: ${few_shot_sample.query}
29 A: '''${few_shot_sample.answer}''''
30- |-
31 Q: ${test_query}
32 A.
33- model: watsonx/ibm/granite-3-8b-instruct
34 parameters.
35 stop: ["Q:", "A:"]
(b) Código Python
1de typing import TypedDict
2importar conjuntos de dados
3from sklearn.feature_extraction.text \
4 import TfidfVectorizer
5
6def initialize().
7 train_in = datasets.load_dataset(
8 "mbpp", "sanitised", split="train"
9 )
10 corpus = [row["prompt"] for row in train_in]
11 tfidf = TfidfVectorizer().fit(corpus)
12 def embed(text).
13 sparse_result = tfidf.transform(
14 raw_documents=[text]
15 )
16 return sparse_result.toarray().flatten()
17 train_em = train_in.map(
18 lambda row: {"em": embed(row["prompt"])}
19 )
20 vec_db = train_em.add_faiss_index("em")
21 return vec_db, embed
22
23 QA = TypedDict("QA", {"query":str, "answer":str})
24def retrieve(mbpp, query, n: int) -> list[QA].
25 vec_db, embed = mbpp
26 key = embed(query)
27 nearest = vec_db.get_nearest_examples(
28 "em", key, n
29 )
30 queries = nearest.examples["prompt"]
31 answers = nearest.examples["code"]
32 return [
33 {"query": q, "answer": a}
34 for q, a in zip(queries, answers)
35 ]
Figura 3. RAG Exemplo em PDL
5. estudos de caso
Estamos no primeiro 2 Um exemplo simples de chatbot PDL foi visto na seção. Esta seção mostrará alguns casos de uso um pouco mais complexos para PDL: RAGs, proxies e geração de PDLs a partir de PDLs.
5.1 Recuperar a geração de aprimoramento
Geração aprimorada por recuperação, ou RAGque funciona primeiro recuperando o contexto relevante e depois adicionando-o aos prompts do modelo para gerar respostas (Lewis et al. 2020). Fig. 3(a) Mostra um programa PDL usando o RAG para recuperar um pequeno número de exemplos para uma tarefa de geração de código. Código: As linhas 2 a 6 usam Python para inicializar um banco de dados vetorial de divisões de treinamento para o conjunto de dados MBPP de "programas Python em sua maioria básicos" (Austin et al., 2008). 2021). Ele usa o gráfico 3função Python definida em (b), e uma variável especial PDL_SESSION que possibilita passar o estado para blocos de código posteriores. Figura 3As linhas 8-11 de (a) inicializam a variável test_query com a solicitação de linguagem natural que gerou o código Python. As linhas 12-19 inicializam a variável retrieved com os cinco exemplos mais semelhantes dos dados de treinamento.
As linhas 20-24 adicionam instruções ao contexto, as linhas 25-29 adicionam alguns exemplos ao contexto e as linhas 30-32 adicionam consultas de teste ao contexto. O loop for: na linha 25 é uma forma comum de gerar dados usando PDL, neste caso para aprendizado de contexto. Por fim, as linhas 33-35 chamam um modelo do Granite 3 (Equipe do Granite, IBM 2024), usando o contexto cumulativo que faz com que ele gere funções Python que testam solicitações de consulta. Embora esse seja um exemplo simples, também usamos o PDL com o Codellm-Devkit (Krishna et al. 2024) é usado em conjunto com uma ferramenta que analisa estaticamente o código-fonte de várias linguagens de programação para recuperar outro contexto relevante ao solicitar ao LLM tarefas de codificação.
(a) Código
1texto.
2- leia: react_few_shot_samples.txt
3- |
4- leia: react_few_shot_samples.txt
5 Quando nasceu o descobridor do Rio Hudson?
6- repetir.
7 text.
8 - def: thought
9 modelo: watsonx/ibm/granite-34b-code-instruct
10 parâmetros.
11 stop: ["Act:"]
12 include_stop_sequence: true
13 - def: action
14 model: watsonx/ibm/granite-34b-code-instruct
15 parâmetros.
16 stop: ["\n"]
17 parser: json
18 spec: {name: str, arguments: {topic: str}}
19 - def: observation
20 if: ${ action.name == "Search" }
21 then.
22 text.
23 - "Obs: "
24 - lang: python
25 code: |
26 import wikipedia
27 query = "${ action.arguments.topic }"
28 result = wikipedia.summary(query)
29 until: ${ action.name ! = "Search" }
(b) Rastreamento de intérpretes
Qual é a faixa de elevação da região leste da orogênese do Colorado?
Tho: Preciso pesquisar a orogênese do Colorado para descobrir o alcance da região leste.
Ato: {"name": "Search", "arguments": {"topic ": "Colorado orogeny"}}
Obs: A Campanha de Construção da Montanha do Colorado é um evento [...]
[...]
Quando nasceu o descobridor do Rio Hudson?
Tho: Preciso pesquisar o descobridor do Rio Hudson para descobrir quando ele nasceu.
Ato: {"name": "Search", "arguments": {"topic ": "Discoverer of the Hudson River"}}
Obs: O Rio Hudson é um rio de 315 milhas de comprimento [...]...
Tho: O descobridor do Rio Hudson foi Henry Hudson Preciso pesquisar Henry Hudson para descobrir quando ele nasceu.
Ato: {"name": "Search", "arguments": {"topic ": "Henry Hudson"}}
Obs: Henry Hudson (c. 1565 - desapareceu [...])
Tho: Henry Hudson nasceu em 1565. act: {"name": "Finish", "arguments": { "topic": "1565"}}
Figura 4. ReAct agir em nome de alguém em uma posição de responsabilidade
5.2 Agente ReAct
Baseado em modelagem de linguagem grande agir em nome de alguém em uma posição de responsabilidade Permite a seleção e a configuração de modelos de idiomas grandes movimentoem matriz no qual essas ações são executadas, e a saída das ações é enviada de volta ao modelo de linguagem grande como o prestar atenção. Existem diferentes modelos para esses agentes, como o ReAct (Yao et al. 2023) e ReWOO (Xu et al. 2023). As ações são baseadas em um grande modelo de linguagem Chamada de ferramenta (Schick et al. 2023), enquanto os agentes encadeiam várias chamadas de ferramentas em uma sequência dinâmica e ampla de bootstrap de modelos de linguagem. O objetivo é tornar os aplicativos baseados em IA menos prescritivos e mais orientados a objetivos. Além disso, os agentes podem usar observações como feedback para se recuperar quando as ações dão errado.
mapa 4 No centro do ReAct está um loop pensar-agir-observar, que é representado no código como definições de variáveis para pensar (linha 8), agir (linha 13) e observar (linha 19). Pensar é a linguagem natural da geração de modelos, por exemplo, na Figura 4"Preciso pesquisar o descobridor do Rio Hudson para saber quando ele nasceu" no rastro do interpretador de (b). A ação é o JSON gerado pelo modelo para corresponder às ferramentas do modelo Granite usando os dados de treinamento (Abdelaziz et al. 2024). As linhas 17 e 18 no lado esquerdo garantem que a saída do modelo de linguagem grande seja analisada como JSON e esteja em conformidade com o esquema {name, arguments}, enquanto o rastreamento do interpretador no lado direito mostra que o modelo de fato gera esses objetos. Isso permite usar o Jinja2 para acessar os campos do objeto, como ${ action.arguments.topic } na linha 27. As observações são geradas pelo ambiente, neste caso, o código Python que chama a Wikipédia. Conforme mostrado na linha 27, o 4 Conforme discutido na Seção , para casos que envolvam a execução de código gerado (parcialmente) a partir de um modelo de linguagem grande, recomendamos o uso dos recursos de sandboxing do PDL.
mapa 4O rastreamento do interpretador em (b) mostra que essa execução tem duas iterações do loop do agente. Embora esse seja um exemplo simples, também implementamos um agente de edição de código usando a PDL, que foi usada na tabela de classificação do SWE-bench Lite como parte do commit4 (Jimenez et al. 2024). O envio resolve a primeira instância de 23,7% usando apenas o modelo de código aberto, que é maior do que qualquer resultado anterior usando o modelo de código aberto e comparável aos resultados do modelo de fronteira.
5.3 Geração de PDLs a partir de PDLs usando o Big Language Model
As seções anteriores mostraram como os desenvolvedores humanos podem usar a PDL para codificar diferentes padrões de dicas. Essa geração de meta-PDL é útil quando grandes modelos de linguagem precisam criar planos de solução de problemas, por exemplo, como parte de um fluxo de trabalho de agente. Tradicionalmente, esses planos são apenas texto, JSON ou código Python. Com o PDL, esses planos podem ser uma combinação de modelos totalmente executáveis e chamadas de código. Esta seção explora o uso da meta-geração da PDL no conjunto de dados GSMHard5 .
O GSMHard é uma versão mais difícil do GSM8k que contém problemas de matemática do ano letivo que exigem aritmética simples ou raciocínio simbólico. O GSMHard contém uma entrada, o enunciado do problema de matemática, e uma saída, o código Python que resolve o problema. Implementamos o PAL (Gao et al. 2023), mas em vez de gerar código Python, o modelo de linguagem grande é solicitado a gerar PDL. O raciocínio textual em cadeia é representado como blocos de texto PDL, enquanto a aritmética é executada usando blocos de código PDL.
mapa 6 É mostrado um programa PDL que gera código PDL e o executa no mesmo programa. A variável demos contém um pequeno número de exemplos criados para ensinar o modelo a gerar código PDL. Na linha 32, um bloco de chamada de modelo usa esses exemplos e um problema com variáveis livres como entrada. O resultado é um programa PDL para resolver o problema. A linha 38 extrai o programa PDL e o executa em Python. O programa é aplicado ao conjunto de dados GSMHard, em que o problema preenche o problema de entrada.
Esse experimento constatou que o 10% para o conjunto de dados GSMHard estava de fato errado porque a verdade básica era inconsistente com as perguntas feitas. Fig. 6 São mostrados exemplos de tais inconsistências. O uso do PDL ajudou a descobrir isso, pois o código PDL gerado era legível por humanos, de modo que pudemos verificar facilmente os pontos de dados que não correspondiam à verdade básica e descobrimos que a verdade básica estava errada em alguns casos. Usamos um modelo de linguagem grande para cobrir todo o conjunto de dados e escolhemos sistematicamente os exemplos que pareciam ser inconsistentes. Em seguida, examinamos manualmente os resultados para remover falsos positivos e identificamos pontos de dados do 10% com esse problema.
1 Definição.
2 Exemplo.
3 Dados.
4 Texto.
5 Texto.
6 ...
6 ...
8 Problema: Roger tem 5 bolas de tênis.
9 Ele comprou mais 2 latas de bolas de tênis.
10 Cada lata tem 3 bolas de tênis.
11 Quantas bolas de tênis ele tem agora?
12 Resposta: Roger tem 5 bolas de tênis.
13 Resposta.
14 ```
15 Texto.
16 - "Roger começou com \n."
17 - Definição: tennis_balls
18 Dados: 5
19 - "\n bolas de tênis. \n"
20 - "2 latas, cada uma com 3 bolas de tênis, total \n"
21 - Linguagem: python
22 Definição: bought_balls
23 Código: result = 2 * 3
24 - "\n bolas de tênis. \n"
25 - "O resultado é: \n"
26 - Linguagem: python
27 - Definição: RESULT
28 Código: result = ${ bolas_de_tênis } + ${ bolas_compradas }
29 ``
30 Raw: true
31 Texto: watson
32 - model: watsonx/meta-llama/llama-3-70b-instruct
33 Definição: PDL
34 Entrada: true
35 Text.
36 - ${ demos.text }
37 - "Question: ${ question }"
38 - Idioma: python
39 Código: |
40 from pdl.pdl import exec_str
41 s = """${ PDL }""""
42 pdl = s.split("````")[1]
43 result = exec_str(pdl)
44 Definição: RESULT
Figura 5.
James decidiu correr 1793815 sprints por semana.
Cada sprint tem 60 metros.
Quantos metros ele corre no total a cada semana?
def solution():
sprints_per_day = 1793815
dias_por_semana = 3
metros_por_sprint = 60
total_sprints = sprints_per_day * days_per_week
total_meters = total_sprints * metros_por_sprint
resultado = total_metros
total_meters = total_sprints * metros_por_sprint resultado
Figura 6. pontos de dados do problema de amostra GSMHard
6. trabalhos relacionados
Um estudo recente coloca Quadros de tacos Definido como uma camada que gerencia, simplifica e facilita a interação entre o LLM e os usuários, ferramentas ou outros modelos (Liu et al.2023). O estudo destaca que uma das principais deficiências da estrutura de dicas é a curva de aprendizado acentuada.
Provavelmente, a estrutura de solicitação mais popular atualmente é a LangChain (Chase et al.2022A principal motivação do MiniChain é justamente evitar essa complexidade (Rush.2023), que oferece menos recursos mais simples que podem ser combinados para aplicativos avançados. No entanto, tanto o LangChain quanto o MiniChain são estruturas baseadas em Python, o que os torna mais não declarativos, pois os desenvolvedores precisam escrever código imperativo. O PDL tem uma motivação semelhante à do MiniChain, mas vai além ao usar YAML em vez de Python como base.
Assim como em outras estruturas de solicitação, o objetivo do PDL é tornar o LLM mais robusto.Guidance (Microsoft.2023) é uma estrutura baseada em Python que oferece um design mais estruturado, mas é mais primitiva que a LangChain. Da mesma forma, o LMQL (Beurer-Kellner et al.2023A PDL se inspira na LMQL em seu entrelaçamento de dicas e programação, mas, diferentemente da LMQL, ela depende menos do código imperativo do Python. Crouse et al. usam máquinas de estado finito para especificar formalmente o fluxo interno de vários loops inteligentes (Crouse et al.2024); embora esse seja um esforço fascinante, ele não introduz uma linguagem de sinalização sofisticada.
Uma das vantagens das linguagens específicas de domínio é que elas podem implementar transformações de programas, por exemplo, para otimização (Mernik et al.2005A estrutura de dicas do DSPy (Khattab et al.2023) tem o lema "Programming, not hints" (Programação, não dicas): ele gera dicas automaticamente, de modo que os desenvolvedores não precisam escrevê-las manualmente. Da mesma forma, Vieira (Li et al.2024) estende o Prolog para usar o LLM como uma relação probabilística e para gerar prompts automaticamente. O DSPy e o Vieira são estruturas muito avançadas, mas, ao contrário do PDL, ambos enfraquecem o controle do desenvolvedor sobre prompts específicos.2021) é uma linguagem que permite que os usuários ajustem de forma incremental o equilíbrio entre automação e controle no pipeline de IA, mas não se concentra em dicas de LLM.DSPy, Vieira e Lale otimizam o desempenho preditivo, enquanto a outra otimização visa o desempenho computacional.SGLang (Zheng et al.2023) consegue isso fazendo melhor uso do cache de prefixo, resultando em mais acessos ao cache KV (Kwon et al.2023). O trabalho futuro explorará se a natureza declarativa da PDL pode permitir otimizações de desempenho computacional semelhantes.
Recentemente, surgiram várias estruturas de solicitação baseadas em Large Language Modelling (LLM), com foco em agentes LLM.AutoGen (Wu et al.2023) é uma estrutura multiagente na qual todo o conteúdo consiste em agentes e diálogos. Outras estruturas multiagentes incluem CrewAI (Moura.2023) e GPTSwarm (Zhuge et al.2024). Essas estruturas priorizam o suporte a proxies em detrimento de outros casos de uso baseados em LLM. O PDL, embora também ofereça suporte a proxies, adota uma postura mais equilibrada, tratando os proxies como uma das várias técnicas de cueing.
7 Conclusão
A PDL é uma linguagem declarativa orientada a dados: os programas consistem em blocos YAML, cada um dos quais é um dado literal ou generativo. O modelo de pensamento consiste em executar um bloco anexando seus dados a um contexto de fundo, que é usado como uma pista para chamadas subsequentes ao modelo de linguagem maior. Esta tese apresenta a linguagem com programas de amostra e um tour guiado pela sintaxe e pelas ferramentas. A natureza declarativa da linguagem também facilita a implementação de otimizações automáticas para velocidade, precisão e segurança, que serão implementadas de forma incremental em trabalhos futuros:https://github.com/IBM/prompt-declaration-language.