Rupak (Bob) Roy - II
9 min readAug 8, 2024

So what are Program-Aided Large Language (PAL) Models in LLM?

Full Guide to Program-Aided Language(PAL) Models Vs Agents

Sora: Meghalaya

Hi everyone, I hope you had a wonderful day. We have already seen in my previous articles on how to craft an LLM pipeline in my previous article.

Today we will understand another interesting topic of LLM PAL — Program-Aided Large Language Models.

So what is Program-Aided Large Language Models?

PAL, or Program-Aided Language model, refers to a method that combines language models (LLMs) with traditional programming capabilities to solve complex problems..

In PAL:

  1. Problem Decomposition: The LLM breaks down a complex problem into smaller, manageable steps, similar to how it might decompose a task into subtasks in a logical sequence.
  2. Program Execution: Instead of solving the problem purely through language generation, PAL transforms these steps into a program (often in a programming language like Python). The program is then executed by an interpreter or a computational environment to solve the problem.
  3. Integration with LLMs: This method allows the LLM to leverage the capabilities of traditional programming, such as performing precise calculations, data manipulation, or executing algorithms, while still benefiting from the LLM’s ability to understand and generate human-like language.

For instance, if an LLM is asked to solve a complex math problem, instead of attempting to directly calculate the solution through text generation, the model might break the problem into steps, generate a Python script that solves the problem, and then execute the script to obtain the answer.

Difference between PAL & AGENTS

1. Tool Usage:

  • Agents: Agents are designed to use a variety of external tools to solve problems. These tools can include mathematical libraries, search functions, Human-In-The-Loop processes, other LLMs, and more. The agent selects and applies the appropriate tool(s) to reach a final conclusion.
  • PAL: PAL focuses on breaking down a problem into steps that are then translated into a program or script. The program is executed by an interpreter, and the results are returned as the solution.

2. Problem Decomposition:

  • Agents: Agents decompose problems into smaller tasks, where each task might be handled by different tools or processes. The agent then integrates the results from these tasks to provide a final solution.
  • PAL: In PAL, the problem is decomposed into logical steps within a program. These steps are executed sequentially as code, often in a programming language like Python, to solve the problem.

3. Execution:

  • Agents: The execution involves leveraging external tools dynamically based on the problem requirements. Agents may call upon various resources as needed, which makes them versatile in handling diverse types of tasks.
  • PAL: Execution in PAL is more linear and structured, as it involves running a predefined program that has been generated based on the problem decomposition. The process is deterministic, following the program’s logic.

4. Complexity Handling:

  • Agents: Agents can handle complex, multi-step tasks by using different tools for each part of the process. They are designed to navigate a wide array of scenarios by dynamically choosing the best approach.
  • PAL: PAL handles complexity by converting the problem into a series of executable steps. The complexity is managed through the logical flow of the program, where each step builds on the previous one.

5. Flexibility:

  • Agents: Agents are flexible in the sense that they can adapt to different kinds of problems by leveraging a variety of tools and resources. Their ability to interact with multiple tools allows them to be applied in diverse contexts.
  • PAL: PAL is more rigid in structure, focusing on problems that can be effectively solved by a sequence of programmatic steps. While it excels in tasks where logic and computation are key, it may not be as adaptable to tasks that require diverse, real-time tool interactions.

In summary, Agents are versatile systems that integrate various tools to solve problems dynamically, while PAL is a method that decomposes problems into programmatic steps, which are then executed to arrive at a solution.

Now let’s have hands-on with PAL

Likewise, for our example in the article, we will be using Hugging face model API calls which provide better token limits than the OpenAi Api.

First login to the Hugging face and generate the API key(Access Token)

hugging face access token
huggingface access token
#Load the libraries and the model using HuggingFaceEndPoint()

from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint

##################################################
#Model API call
repo_id = "mistralai/Mistral-7B-Instruct-v0.2"
llm = HuggingFaceEndpoint(
repo_id=repo_id,
#max_length=1288,
temperature=0.5,
huggingfacehub_api_token= "hf_youkey")

Install the required libraries and initialize the PAL

######### Initialize PAL ################################

#from langchain.chains import PALChain #broken link
#langchain-core-0.2.28
#pip install langchain_experimental

from langchain_experimental.pal_chain import PALChain
pal_chain = PALChain.from_math_prompt(llm, verbose=True,
return_intermediate_steps=True,
allow_dangerous_code=True)

Now let’s query.

question = "The cafeteria had 23 apples. \
If they used 20 for lunch and bought 6 more,\
how many apples do they have?"

results = pal_chain.invoke({'question':question})

Here we have our results stored as a dictionary.

results = pal_chain.invoke({‘question’:question})
results = pal_chain.invoke({‘question’:question})

We have our intermediate steps converting the query into a series of executable steps..

Let’s try out some more complex examples.

What we will do here, is add up some memory with K=1 refers we will store only the last conversation. “return_messages” indicates return in String format, and “memory_key” is to give a name to the conversation

#adding a memory*******
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(memory_key="chat_history",
k=2,return_messages = True)

from langchain_experimental.pal_chain import PALChain
pal_chain = PALChain.from_math_prompt(llm, verbose=True,
return_intermediate_steps=True,
allow_dangerous_code=True,
memory = memory,
output_key="intermediate_steps")

we have also observed additional parameters like “output_key” indicating the output of the chain which is the “intermediate_steps” that we we saw earlier in the example.

#define the prompt
from langchain.prompts import PromptTemplate
prompt = PromptTemplate.from_template(
"""Answer the following questions as best you can.
You have access to the following memory for follow-up:

{chat_history}

Begin!

Question: {question}

Thought: Initial thoughts or calculations again
"""
)

Now this is optional we can also use variables to define the input variables

#optional - directly inject the variable defination
variables = {
"question": question,
"chat_history": "Previous conversation history here.",
"intermediate_steps": "Initial thoughts or calculations."
}
formatted_prompt = prompt.format(**variables)
print(formatted_prompt)

When we print the formatted prompt template. we can observe that we have added definitions/more details in {chat_history}, {Questions} as well other variables

Time to query the chain


##############################
pal_chain1 = pal_chain | prompt

#Math Reasoning
question = "Emily purchases three times as many green apples as red apples.\
The green apples are 25% more expensive than the red apples. \
She spent $180 on red apples that cost $30 each. How much did she spend in total on apples?"

follow_up="After buying the apples, Emily decided to buy bananas. \
She spent $120 on bananas, which cost $2 each. How many bananas did Emily purchase, \
and what was her total expenditure on both apples and bananas?"

results = pal_chain1.invoke({'question':question,
'chat_history': memory
})
results1 = pal_chain1.invoke({'question':follow_up,
'chat_history': memory
})
results1 = pal_chain1.invoke({‘question’:follow_up,
‘chat_history’: memory })

We can clearly see that results1 has the follow-up question and the chain was able to pass the chat history.

Let’s try another one.


question = "In March, the price of a gadget was $9,453,278.75. In April, \
the price increased by 25%. In May, the price decreased by 40%. \
What was the price of the gadget after the discount in May?"
follow_up = "After the price change in May, the store decided to apply an \
additional 15% discount on the gadget during a special sale. \
What is the final price of the gadget after this additional discount?"

results = pal_chain1.invoke({'question':question,
'chat_history': memory})
results1 = pal_chain1.invoke({'question':follow_up,
'chat_history': memory})
results1 = pal_chain1.invoke({‘question’:follow_up,
‘chat_history’: memory})

List of examples we can try


#Logical Reasoning:
question ="Create a program that checks if a given list of integers can be rearranged to form a palindrome."
follow_up ="What would the program output if the list contains duplicate numbers?"

#Data Manipulation:
question ="Write a Python script to merge two dictionaries, ensuring that if they share keys, the values are summed."
follow_up ="How can the program be adjusted to concatenate string values instead of summing numbers?"

#String Processing:
question ="Create a function that removes all non-alphanumeric characters from a string and returns the cleaned string."
follow_up ="How would you modify the function to also convert the string to lowercase?"

#File Handling:
question ="Write a program that reads a text file, counts the frequency of each word, and writes the results to a new file."
follow_up ="How could the program be adjusted to ignore case sensitivity and punctuation in the word count?"

#Sorting Algorithms:
question ="Implement a Python function to perform a quicksort on a list of integers."
follow_up ="How would you modify the function to sort a list of strings alphabetically?"

#Database Interaction:
question ="Write a SQL query to retrieve all records from a table where the 'status' column is 'active'."
follow_up ="How would you modify the query to also count the number of 'active' records?"

#Error Handling:
question ="Create a Python program that reads user input and converts it to an integer, handling any potential exceptions."
follow_up ="How could the program be improved to provide more detailed error messages?"

#API Interaction:
question ="Write a Python script that sends a GET request to a public API and prints the response."
follow_up ="How would you modify the script to handle different response statuses and return appropriate messages?"

#Optimization Problem:
question ="Develop a Python function to solve the knapsack problem using dynamic programming."
follow_up ="How could you modify the function to account for fractional weights?"

Sometimes it might throw an error, in that case, we need to do some prompt engineering to handle such scenarios.

#Sorting Algorithms:
question ="Implement a Python function to perform a quicksort on a list of integers."
follow_up ="How would you modify the function to sort a list of strings alphabetically?"

results = pal_chain1.invoke({'question':question,
'chat_history': memory})
results1 = pal_chain1.invoke({'question':follow_up,
'chat_history': memory})

Half the way it might generate the output then it fails. Do some Prompt Engineering, which should be able to handle such scenarios.

There are tons of functions available in the PALChain Class. One of them is “from_colored_object_prompt”

pal_chain = PALChain.from_colored_object_prompt(llm, verbose=True,
return_intermediate_steps=True,
allow_dangerous_code=True,
memory = memory,
output_key="intermediate_steps")

question = "On the table, there are three green notebooks, two red notebooks, one red pen,\
and three orange keychains. If I take all the keychains off the table, how many red items remain on it?"

result = pal_chain({"question": question})

result['intermediate_steps']

#Cost Analysis

we can also perform cost analysis for openai using the callbacks

from langchain.callbacks import get_openai_callback

#Cost analysis
with get_openai_callback() as cb:
result = pal_chain({"question":question})
print(f"Total Tokens:{cb.total_tokens}")
print(f"Prompt Tokens:{cb.prompt_tokens}")
print(f"Completion Tokens:{cb.completion_tokens}")
print(f"Total Cost (USD):{cb.total_cost}")

As of now, it will show 0.00 because we are using the huggingface :)

It is already a long article, so we will continue exploring Program-Aided Large Language Models (PAL) in the next article.

Once again, thanks again for your time. i hope you enjoyed this. I tried my best to gather details across and simply as much as possible i could.

In the next article, we will explore ways to train and create our own Program-Aided Large Language Models (PAL) model.

Until then feel free to reach out. Thanks for your time, if you enjoyed this short article there are tons of topics in advanced analytics, data science, and machine learning available in my medium repo. https://medium.com/@bobrupakroy

Some of my alternative internet presences are Facebook, Instagram, Udemy, Blogger, Issuu, Slideshare, Scribd, and more.

Also available on Quora @ https://www.quora.com/profile/Rupak-Bob-Roy

Let me know if you need anything. Talk Soon.

Check out the links i hope it helps.

https://www.udemy.com/user/rupak-roy-2/
udemy: https://www.udemy.com/user/rupak-roy-2/
Boosted Hybrid Model TS-model Forecasting with Residuals
Rupak (Bob) Roy - II
Rupak (Bob) Roy - II

Written by Rupak (Bob) Roy - II

Things i write about frequently on Medium: Data Science, Machine Learning, Deep Learning, NLP and many other random topics of interest. ~ Let’s stay connected!

No responses yet