5

Enforce and Validate LLM Output with Pydantic

 10 months ago
source link: https://xebia.com/blog/enforce-and-validate-llm-output-with-pydantic/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Share

Introduction

Large Language Models (LLMs) excel in generating text but often struggle to produce structured output. By leveraging Pydantic's type validation and prompt engineering, we can enforce and validate the output generated by LLMs.

All code examples in this blog post are written in Python. The LLM used is OpenAI's gpt-3.5-turbo.

Query the LLM

To query the LLM, we use the following function:

import openai

def query(prompt: str) -> str:
    """Query the LLM with the given prompt."""
    completion = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {
                "role": "user",
                "content": prompt,
            }
        ],
        temperature=0.0,
    )
    return completion.choices[0].message.content
Python

Query the model

We can query the model with a simple question:

response = query("What is the largest planet in our solar system?")
print(response)
'The largest planet in our solar system is Jupiter.'
Python

Enforcing JSON output with a prompt

In our prompt, we can ask the LLM to respond in a certain format:

prompt = """
I will ask you questions and you will respond. Your response should be in the following format:
```json
{
    "thought": "How you think about the question",
    "answer": "The answer to the question"
}
```
"""
Python

Then, we query the model:

question = "What is the largest planet in our solar system?"
response = query(prompt + question)
print(response)
'{
    "thought": "This is a factual question that can be answered with scientific knowledge.",
    "answer": "The largest planet in our solar system is Jupiter."
}'
Python

This is great, because we can easily parse the structured output:

import json

parsed_response = json.loads(response)
print(parsed_response["answer"])
'The largest planet in our solar system is Jupiter.'
Python

Validating the output

from pydantic import BaseModel


class ThoughtAnswerResponse(BaseModel):
    thought: str
    answer: str


raw_response = query(prompt)

# Note: When you are using pydantic<2.0, use parse_raw instead of model_validate_json
validated_response = ThoughtAnswerResponse.model_validate_json(raw_response)

print(validated_response)
thought='This is a factual question that can be answered with scientific knowledge.' answer='The largest planet in our solar system is Jupiter.'

print(type(validated_response))
<class 'ThoughtAnswerResponse'>
Python

Using the Pydantic model in the prompt

At this moment, we describe our response format in two places:

  • a JSON description in our prompt
  • a corresponding Pydantic model

When we want to update the response format, we need to change both the prompt and the Pydantic model. This can cause inconsistencies.

We can solve this by exporting the Pydantic model to a JSON schema and adding the schema to the prompt. This will make the response and the Pydantic model consistent.

response_schema_dict = ThoughtAnswerResponse.model_json_schema()
response_schema_json = json.dumps(response_schema_dict, indent=2)

prompt = f"""
I will ask you questions, and you will respond.
Your response should be in the following format:
```json
{response_schema_json}
```
"""
Python

The prompt will now look like this:

I will ask you questions, and you will respond. Your response should be in the following format:
```json
{
    "properties": {
        "thought": { "title": "Thought", "type": "string" },
        "answer": { "title": "Answer", "type": "string" }
    },
    "required": ["thought", "answer"],
    "title": "ThoughtAnswerResponse",
    "type": "object"
}

The response will look like this:

{
  "thought": "The largest planet in our solar system is Jupiter.",
  "answer": "Jupiter"
}

Now, whenever you change the Pydantic model, the corresponding schema will be put in the prompt. Note that the schema has become more complex than it was before. One benefit is that it allows us to be more specific in what responses we require.

Error handling

The LLM may still produce results that are not consistent with our model. We can add some code to catch this:

from pydantic import ValidationError

try:
    validated_response = ThoughtAnswerResponse.model_validate_json(raw_response)
except ValidationError as e:
    print("Unable to validate LLM response.")
    # Add your own error handling here
    raise e
Python

Enforce specific values using a Literal

Sometimes, you want to enforce the use of specific values for a given field. We add the field "difficulty" to our response object. The LLM should use it to provide information about the difficulty of the question. In a regular prompt, we would do the following:

prompt = """Your response should be in the following format:
```json
{
  "thought": "How you think about the question",
  "answer": "The answer to the question",
  "difficulty": "How difficult the question was. One of easy, medium or hard"
}
```
"""
Python

Of course, the model could potentially still use other values. To validate it, we would need to write custom code.

With Pydantic, it is a lot easier. We create a new type called Difficulty using a Literal. A Literal allows us to specify the use of a select list of values. We add a Difficulty type hint to the difficulty field in our Pydantic model:

from typing import Literal

from pydantic import BaseModel


# We create a new type
Difficulty = Literal["easy", "medium", "hard"]


class ThoughtAnswerResponse(BaseModel):
    thought: str
    answer: str
    difficulty: Difficulty
Python

The LLM responds may respond with a value we do not allow:

{
  "thought": "The largest planet in our solar system is Jupiter.",
  "answer": "Jupiter",
  "difficulty": "Unknown"
}

When we parse this result, Pydantic will validate the values for the difficulty field. Unknown does not match one of the values specified in the Literal type we have defined. So we get the following error:

validated_response = ThoughtAnswerResponse.model_validate_json(response)

ValidationError: 1 validation error for ThoughtAnswerResponse
difficulty
    Input should be 'easy', 'medium' or 'hard' [type=literal_error, input_value='Unknown', input_type=str]
Python

Conclusion

By using Pydantic and prompt engineering, you can enforce and validate the output of LLMs. This provides you with greater control of the LLM output and allow you to build more robust AI systems.


Photo by Andrew Ridley on Unsplash


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK