Self-Refine: Enhancing LLM Outputs Through Iterative Self-Improvement¶
This recipe demonstrates how to implement the Self-Refine technique using Large Language Models (LLMs) with Mirascope. Self-Refine is a prompt engineering method that enhances an LLM's output by iteratively generating feedback and improving its responses.
Background
Self-refine is a prompt engineering technique where a model gives feedback about its answer and uses the feedback to generate a new answer. This self refinement can take place multiple times to generate the final answer. Self-refine is helpful for reasoning, coding, and generation tasks.
Basic Self-Refine Implementation¶
Let's start with a basic implementation of Self-Refine using Mirascope:
from mirascope.core import openai, prompt_template
from mirascope.core.openai import OpenAICallResponse
@openai.call(model="gpt-4o-mini")
def call(query: str) -> str:
return query
@openai.call(model="gpt-4o-mini")
@prompt_template(
"""
Here is a query and a response to the query. Give feedback about the answer,
noting what was correct and incorrect.
Query:
{query}
Response:
{response}
"""
)
def evaluate_response(query: str, response: OpenAICallResponse): ...
@openai.call(model="gpt-4o-mini")
@prompt_template(
"""
For this query:
{query}
The following response was given:
{response}
Here is some feedback about the response:
{feedback}
Consider the feedback to generate a new response to the query.
"""
)
def generate_new_response(
query: str, response: OpenAICallResponse
) -> openai.OpenAIDynamicConfig:
feedback = evaluate_response(query, response)
return {"computed_fields": {"feedback": feedback}}
def self_refine(query: str, depth: int) -> str:
response = call(query)
for _ in range(depth):
response = generate_new_response(query, response)
return response.content
query = """Olivia has $23. She bought five bagels for $3 each.
How much money does she have left?"""
print(self_refine(query, 1))
To determine how much money Olivia has left after her purchase, let's break it down step by step: 1. **Starting Amount**: Olivia has $23 initially. 2. **Cost of Bagels**: She bought 5 bagels at $3 each. The total spent on bagels is calculated as: \[ 5 \times 3 = 15 \text{ dollars} \] 3. **Amount Left**: Now, we subtract the total amount spent on the bagels from Olivia's starting amount: \[ 23 - 15 = 8 \text{ dollars} \] Therefore, after buying the bagels, Olivia has **$8 remaining**.
Enhanced Self-Refine with Response Model¶
Now, let's improve our implementation by adding a response model to structure the output:
from pydantic import BaseModel, Field
class MathSolution(BaseModel):
steps: list[str] = Field(..., description="The steps taken to solve the problem")
final_answer: float = Field(..., description="The final numerical answer")
@openai.call(model="gpt-4o-mini", response_model=MathSolution)
@prompt_template(
"""
For this query:
{query}
The following response was given:
{response}
Here is some feedback about the response:
{feedback}
Consider the feedback to generate a new response to the query.
Provide the solution steps and the final numerical answer.
"""
)
def enhanced_generate_new_response(
query: str, response: OpenAICallResponse
) -> openai.OpenAIDynamicConfig:
feedback = evaluate_response(query, response)
return {"computed_fields": {"feedback": feedback}}
def enhanced_self_refine(query: str, depth: int) -> MathSolution:
response = call(query)
for _ in range(depth):
solution = enhanced_generate_new_response(query, response)
response = f"Steps: {solution.steps}\nFinal Answer: {solution.final_answer}"
return solution
# Example usage
result = enhanced_self_refine(query, 1)
print(result)
steps=['Olivia has $23.', 'She bought five bagels for $3 each.', 'Calculate the total cost for the bagels: 5 bagels * $3/bagel = $15.', 'Subtract the total cost of the bagels from the amount of money she had: $23 - $15 = $8.'] final_answer=8.0
This enhanced version introduces a MathSolution response model to structure the output, providing a clearer separation between solution steps and the final answer.
Benefits and Considerations¶
The Self-Refine implementation offers several advantages:
- Improved accuracy through iterative refinement of responses.
- Enhanced reasoning capabilities, especially for complex problems.
- Potential for generating more detailed and step-by-step solutions.
When implementing this technique, consider:
- Balancing the number of refinement iterations with computational cost and time constraints.
- Tailoring the feedback prompts to focus on specific aspects of improvement relevant to your use case.
- Experimenting with different model parameters (e.g., temperature) for initial responses vs. refinement steps.
Additional Real-World Applications
- Essay Writing: Use Self-Refine to iteratively improve essay drafts, focusing on structure, argument coherence, and style.
- Code Generation: Apply the technique to generate, evaluate, and refine code snippets or entire functions.
- Data Analysis Reports: Enhance the quality and depth of data analysis reports through iterative self-improvement.
- Product Descriptions: Refine product descriptions to be more engaging, accurate, and tailored to target audiences.
- Legal Document Drafting: Improve the precision and comprehensiveness of legal documents through self-refinement.
When adapting this recipe to your specific use-case, consider:
- Customizing the feedback prompts to focus on domain-specific criteria.
- Implementing different types of response models for various tasks (e.g., text generation, problem-solving).
- Combining Self-Refine with other techniques like Chain of Thought for more complex reasoning tasks.
- Developing a mechanism to halt refinement when improvements become marginal.
By leveraging Mirascope's call decorator, response models, and dynamic configuration, you can easily implement and customize the Self-Refine technique to enhance your LLM's output quality across a wide range of applications.