Back to Tutorials
AdvancedTutorial 14

Prompt Engineering: Techniques and Best Practices

NeuronDB Team
2/24/2025
28 min read

Prompt Engineering Overview

Prompt engineering designs effective prompts for language models. It structures instructions clearly. It provides examples when needed. It guides model behavior. It improves output quality.

Good prompts produce better results. They specify tasks clearly. They provide context. They set expectations. They guide generation.

Prompt Engineering
Figure: Prompt Engineering

The diagram shows prompt structure. Instructions specify task. Context provides background. Examples demonstrate format. Output format guides structure.

Prompt Design Principles

Prompt design follows key principles. Be specific about tasks. Provide relevant context. Use clear instructions. Specify output format. Include examples when helpful.

Specificity reduces ambiguity. Context improves understanding. Clear instructions guide behavior. Output format ensures consistency. Examples demonstrate expectations.

# Prompt Design
def create_prompt(task, context="", examples="", output_format=""):
prompt = f"""Task: {task}
{context}
{examples}
Output Format: {output_format}
Please complete the task following the instructions above."""
return prompt
# Good prompt
good_prompt = create_prompt(
task="Classify the sentiment of the following text as positive, negative, or neutral",
context="Sentiment analysis determines emotional tone",
examples="Text: 'I love this product' -> Sentiment: positive",
output_format="Return only the sentiment label"
)
# Bad prompt
bad_prompt = "What is the sentiment?"
print("Good prompt: " + str(good_prompt))
print("Bad prompt: " + str(bad_prompt))

Good prompts improve results. They reduce errors. They ensure consistency.

Prompt Design
Figure: Prompt Design

The diagram shows prompt components. Each component contributes to quality.

Few-shot Learning

Few-shot learning provides examples in prompts. It demonstrates desired behavior. It enables learning from examples. It improves performance without fine-tuning.

Few-shot prompts include task description, examples, and query. Examples show input-output pairs. Model learns pattern from examples. It applies pattern to query.

# Few-shot Learning
def few_shot_prompt(examples, query):
prompt = "Task: Translate English to French\n\n"
for example in examples:
prompt += f"English: {example['input']}\n"
prompt += f"French: {example['output']}\n\n"
prompt += f"English: {query}\n"
prompt += "French:"
return prompt
# Example
examples = [
{'input': 'Hello', 'output': 'Bonjour'},
{'input': 'Goodbye', 'output': 'Au revoir'}
]
query = "Thank you"
prompt = few_shot_prompt(examples, query)
print("Few-shot prompt: " + str(prompt))

Few-shot learning improves performance. It requires no training. It adapts quickly.

Few-Shot Learning
Figure: Few-Shot Learning

The diagram shows few-shot prompt structure. Examples demonstrate pattern. Model learns from examples. Query follows same pattern.

Zero-Shot vs Few-Shot Learning

Zero-shot learning uses no examples. It relies on pre-training knowledge. It works for standard tasks. Few-shot learning provides examples. It demonstrates desired behavior. It improves performance.

Zero-shot is faster and simpler. It requires no examples. It works for common tasks. Few-shot is more accurate. It adapts to specific formats. It works for custom tasks.

Zero-Shot vs Few-Shot
Figure: Zero-Shot vs Few-Shot

The diagram compares zero-shot and few-shot approaches. Zero-shot uses only task description. Few-shot includes examples. Each approach suits different scenarios.

Chain-of-Thought Prompting

Chain-of-thought prompting guides step-by-step reasoning. It breaks complex problems into steps. It shows reasoning process. It improves accuracy on reasoning tasks.

Chain-of-thought includes problem, reasoning steps, and answer. Steps show logical progression. Answer follows from reasoning. Process improves accuracy.

# Chain-of-Thought Prompting
def chain_of_thought_prompt(problem):
prompt = f"""Problem: {problem}
Let's solve this step by step:
Step 1: [First reasoning step]
Step 2: [Second reasoning step]
Step 3: [Final conclusion]
Answer: [Final answer]"""
return prompt
# Example
problem = "If a train travels 60 mph for 2 hours, how far does it go?"
prompt = chain_of_thought_prompt(problem)
print("Chain-of-thought prompt: " + str(prompt))

Chain-of-thought improves reasoning. It shows logical steps. It increases accuracy.

Chain-of-Thought
Figure: Chain-of-Thought

The diagram shows reasoning steps. Each step builds on previous. Final answer follows logically.

Detailed Chain-of-Thought Techniques

Standard chain-of-thought provides step-by-step reasoning. It breaks complex problems into steps. It shows intermediate calculations. It improves accuracy on math and reasoning tasks.

Self-consistency generates multiple reasoning paths. It samples different chains. It takes majority vote on answers. It improves accuracy further. It requires more computation.

Tree of thoughts explores multiple reasoning branches. It generates candidate thoughts. It evaluates each branch. It selects best path. It works for complex problems.

# Detailed Chain-of-Thought Implementation
from transformers import pipeline
import re
class ChainOfThought:
def __init__(self, model_name='gpt2'):
self.generator = pipeline('text-generation', model=model_name)
def standard_cot(self, problem):
prompt = f"""Problem: {problem}
Let's solve this step by step:
Step 1:"""
response = self.generator(prompt, max_length=200, num_return_sequences=1)
return response[0]['generated_text']
def self_consistency_cot(self, problem, num_samples=5):
"""Generate multiple reasoning paths and take majority vote"""
all_answers = []
for _ in range(num_samples):
prompt = f"""Problem: {problem}
Let's solve this step by step:
Step 1:"""
response = self.generator(prompt, max_length=200, num_return_sequences=1,
do_sample=True, temperature=0.7)
generated = response[0]['generated_text']
# Extract answer (simplified)
answer = self.extract_answer(generated)
all_answers.append(answer)
# Majority vote
from collections import Counter
most_common = Counter(all_answers).most_common(1)[0][0]
return most_common, all_answers
def extract_answer(self, text):
# Simple extraction (would need more sophisticated parsing)
numbers = re.findall(r'\d+\.?\d*', text)
if numbers:
return numbers[-1]
return "unknown"
def tree_of_thoughts(self, problem, num_branches=3):
"""Explore multiple reasoning branches"""
initial_prompt = f"""Problem: {problem}
Possible approaches:
1."""
# Generate initial branches
branches = []
for i in range(num_branches):
branch_prompt = initial_prompt + f" Approach {i+1}:"
response = self.generator(branch_prompt, max_length=150, num_return_sequences=1)
branches.append(response[0]['generated_text'])
# Evaluate each branch (simplified)
branch_scores = []
for branch in branches:
# In practice, would use a scoring model
score = len(branch) # Placeholder
branch_scores.append(score)
# Select best branch
best_idx = np.argmax(branch_scores)
return branches[best_idx]
# Example
cot = ChainOfThought()
problem = "If a train travels 60 mph for 2 hours, how far does it go?"
result = cot.standard_cot(problem)
print("Chain-of-thought result: " + str(result))

Advanced Prompting Techniques

Role prompting assigns roles to models. It guides behavior through persona. Example: "You are an expert data scientist." It improves task-specific performance.

Few-shot chain-of-thought provides reasoning examples. It shows model how to reason. It improves reasoning quality. It combines few-shot and chain-of-thought benefits.

# Advanced Prompting Techniques
class AdvancedPrompting:
def role_prompting(self, role, task, input_data):
prompt = f"""You are {role}.
Task: {task}
Input: {input_data}
Response:"""
return prompt
def few_shot_cot(self, examples, query):
prompt = "Solve these problems step by step.\n\n"
for example in examples:
prompt += f"Problem: {example['problem']}\n"
prompt += f"Solution: {example['solution']}\n\n"
prompt += f"Problem: {query}\n"
prompt += "Solution:"
return prompt
def iterative_refinement(self, initial_prompt, refinement_steps=3):
"""Refine prompt through iterations"""
current_prompt = initial_prompt
for step in range(refinement_steps):
# Generate response
response = self.generate(current_prompt)
# Analyze and refine
refined_prompt = self.refine_prompt(current_prompt, response)
current_prompt = refined_prompt
return current_prompt
def generate(self, prompt):
# Placeholder for generation
return "Generated response"
def refine_prompt(self, prompt, response):
# Add feedback to prompt
refined = prompt + "\n\nPrevious attempt: " + response + "\n\nImproved:"
return refined
# Example
prompter = AdvancedPrompting()
# Role prompting
role_prompt = prompter.role_prompting(
role="an expert machine learning engineer",
task="Explain gradient descent",
input_data=""
)
print("Role prompt: " + str(role_prompt))
# Few-shot chain-of-thought
examples = [
{
'problem': '2 + 3 = ?',
'solution': 'Step 1: Add 2 and 3. Step 2: 2 + 3 = 5. Answer: 5'
},
{
'problem': '5 * 4 = ?',
'solution': 'Step 1: Multiply 5 by 4. Step 2: 5 * 4 = 20. Answer: 20'
}
]
fs_cot_prompt = prompter.few_shot_cot(examples, '6 * 7 = ?')
print("Few-shot CoT prompt: " + str(fs_cot_prompt))

Prompt Optimization

Prompt optimization improves prompt effectiveness. It tests variations. It measures performance. It selects best prompts. It iterates for improvement.

Optimization methods include A/B testing, grid search, and automated optimization. A/B testing compares variants. Grid search tests combinations. Automated optimization uses algorithms.

# Prompt Optimization
def optimize_prompt(base_prompt, variations, test_data):
best_prompt = base_prompt
best_score = evaluate_prompt(base_prompt, test_data)
for variation in variations:
score = evaluate_prompt(variation, test_data)
if score > best_score:
best_score = score
best_prompt = variation
return best_prompt, best_score
# Example
base = "Classify sentiment"
variations = [
"Classify the sentiment as positive, negative, or neutral",
"Determine the emotional tone: positive, negative, or neutral",
"What is the sentiment? Options: positive, negative, neutral"
]
best, score = optimize_prompt(base, variations, test_data)
print("Best prompt: " + str(best))
print("Score: " + str(score))

Optimization improves prompt quality. It finds effective formulations. It maximizes performance.

Template Systems

Template systems create reusable prompt structures. They parameterize prompts. They enable consistent formatting. They simplify prompt creation.

Templates include placeholders for variables. They structure information. They ensure consistency. They enable automation.

# Prompt Templates
class PromptTemplate:
def __init__(self, template):
self.template = template
def format(self, **kwargs):
return self.template.format(**kwargs)
# Example
template = PromptTemplate("""Task: {task}
Context: {context}
Examples: {examples}
Query: {query}
Output:""")
prompt = template.format(
task="Sentiment analysis",
context="Classify text sentiment",
examples="Positive: 'I love it', Negative: 'I hate it'",
query="This is great"
)
print("Template prompt: " + str(prompt))

Templates enable consistency. They simplify creation. They support automation.

Summary

Prompt engineering designs effective prompts. Design principles guide creation. Few-shot learning provides examples. Chain-of-thought guides reasoning. Optimization improves effectiveness. Templates enable reusability. Good prompts improve model performance.

References

Related Tutorials