Skip to content

Welcome to Pydantic Prompter

Pydantic Prompter is a lightweight tool designed for effortlessly constructing prompts and obtaining Pydantic objects as outputs.

Seamlessly call LLMs like functions in Python with Pydantic Prompter. It handles prompt creation and output parsing to custom models for providers like Cohere, Bedrock, and OpenAI. Get OpenAi function calling API capabilities for any LLM. Structured text generation with less code.

The design of the library's API draws inspiration by DeclarAI. Other alternatives Outlines and Jsonformer

Why should you use Pydantic Prompter

💻 Seamless LLM Integration: Pydantic Prompter supported multiple LLM providers, including Cohere, Bedrock, and OpenAI, right out of the box. This meant we could easily switch between providers without modifying our code, ensuring flexibility and portability.

📦 Structured Outputs: By leveraging Pydantic models, Pydantic Prompter automatically parsed the LLM's output into structured Python objects. Manual parsing became a thing of the past, and we enjoyed consistently formatted data that was a breeze to work with.

✍️ Easy Prompt Engineering: Crafting effective prompts is an art, and Pydantic Prompter made us all masters. By defining prompts using Python classes and string interpolation, we created readable, maintainable, and reusable prompts.

🔧 Reusable Components: Pydantic Prompter encouraged a modular approach, allowing us to define reusable prompt components such as instructions, examples, and constraints. This promoted code reuse and made maintaining our code effortless.

🐛 Logging and Debugging: Built-in logging and debugging features meant we could quickly identify and resolve any issues, ensuring a smooth and efficient development process, free of bugs and errors.

Install

OpenAI

pip install 'pydantic-prompter[openai]'

Bedrock

pip install 'pydantic-prompter[bedrock]'

Cohere

pip install 'pydantic-prompter[cohere]'

Usage

Basic usage

To utilize Pydantic Prompter with Jinja2 templates, follow the example below:

from pydantic_prompter import Prompter
from pydantic import BaseModel, Field
from typing import List
import os

os.environ["OPENAI_API_KEY"] = "sk-...."


class RecommendedEntry(BaseModel):
    id: str
    name: str
    reason: str = Field(description="Why this entry fits the query", default="")


class RecommendationResults(BaseModel):
    title: str
    entries: List[RecommendedEntry]


@Prompter(llm="openai", jinja=True, model_name="gpt-3.5-turbo-16k")
def rank_recommendation(entries, query) -> RecommendationResults:
    """
    - system: You are a movie ranking expert
    - user: |
        Which of the following JSON entries fit best to the query.
        order by best fit descending
        Base your answer ONLY on the given JSON entries

    - user: >
        The JSON entries:
        {{ entries }}

    - user: "query: {{ query }}"

    """


my_entries = (
    '[{"text": "Description: Four everyday suburban guys come together as a ....'
)
print(rank_recommendation(entries=my_entries, query="Romantic comedy"))

# >>> title='Romantic Comedy' entries=[RecommendedEntry(id='2312973', \
#       name='The Ugly Truth', reason='Romantic comedy genre')]


# ==Debug you query==
print(rank_recommendation.build_string(entries=my_entries, query="Romantic comedy"))

# >>> system: You are a movie ranking expert
#     user: Which of the following JSON entries fit best to the query.
#     order by best fit descending
#     Base your answer ONLY on the given JSON entries
#     user: The JSON entries: [{"text": "Description: Four everyday suburban guys come together as a ....
#     user: query: Romantic comedy

Simple string formatting

For injecting conversation history through straightforward string formatting, refer to this example:

from pydantic import BaseModel

from pydantic_prompter import Prompter


class QueryGPTResponse(BaseModel):
    google_like_search_term: str


@Prompter(llm="openai", model_name="gpt-3.5-turbo")
def search_query(history) -> QueryGPTResponse:
    """
    {history}

    - user: |
        Generate a Google-like search query text
        encompassing all previous chat questions and answers
    """


history = """
- user: Hi
- assistant: what genre do you want to watch?
- user: Comedy
- assistant: do you want a movie or series?
- user: Movie
"""
res = search_query.build_string(history=history)
print(res)

# >>> assistant: what genre do you want to watch?
#     user: Comedy
#     assistant: do you want a movie or series?
#     user: Movie
#     user: Generate a Google-like search query text
#     encompassing all previous chat questions and answers

Jinja2 advance usage

For more advanced usage involving Jinja2 loops to inject conversation history, consider the following code snippet:

from pydantic import BaseModel

from pydantic_prompter import Prompter


class QueryGPTResponse(BaseModel):
    google_like_search_term: str


@Prompter(llm="openai", jinja=True, model_name="gpt-3.5-turbo")
def search_query(history) -> QueryGPTResponse:
    """
    {%- for line in history %}
     {{ line }}
    {% endfor %}

    - user: |
        Generate a Google-like search query text
        encompassing all previous chat questions and answers
    """


history = [
    "- user: Hi"
    "- assistant: what genre do you want to watch?",
    "- user: Comedy",
    "- assistant: do you want a movie or series?",
    "- user: Movie",
]
res = search_query.build_string(history=history)
print(res)

# >>> assistant: what genre do you want to watch?
#     user: Comedy
#     assistant: do you want a movie or series?
#     user: Movie
#     user: Generate a Google-like search query text
#     encompassing all previous chat questions and answers

Simple typings

Use int, float, bool or str

from pydantic_prompter import Prompter
import os

os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
os.environ["AWS_ACCESS_KEY_ID"] = "..."
os.environ["AWS_SECRET_ACCESS_KEY"] = "..."
os.environ["AWS_SESSION_TOKEN"] = "..."


@Prompter(llm="cohere", model_name="command")
def me_and_mu_children(name) -> int:
    """
    - user: hi, my name is {name} and my children are called, aa, bb, cc, dd, ee
    - user: |
        how many children do I have?
    """


print(me_and_mu_children(name="Zud"))
# >>> 5

Best practices

When using Pydantic Prompter, it is recommended to explicitly specify the parameter name you wish to retrieve, as demonstrated in the example below, where title is explicitly mentioned:

class RecommendationTitleResponse(BaseModel):
    title: str = Field(description="4 to 6 words title")


@Prompter(llm="openai", jinja=True, model_name="gpt-3.5-turbo-16k")
def recommendation_title(json_entries) -> RecommendationTitleResponse:
    """
    - user: >
        Based on the JSON entries, suggest a minimum 4 words and maximum 6 words title

    - user: >
        The JSON entries:
        {{ json_entries }}

    """
Avoid the following practice where the parameter name is not explicitly stated:

class BaseResponse(BaseModel):
    text: str = Field(description="4 to 6 words text")


@Prompter(llm="openai", jinja=True, model_name="gpt-3.5-turbo-16k")
def recommendation_title(json_entries) -> BaseResponse:
    """
    ...
    """
Adhering to these best practices will ensure clarity and precision when using Pydantic Prompter.

Debugging and logging

You can view info and/or debugging logging using the following snippet:

import logging

logging.basicConfig(level=logging.INFO)
logging.getLogger("pydantic_prompter").setLevel(logging.DEBUG)
Resulting
DEBUG:pydantic_prompter:Using OpenAI provider with model gpt-3.5-turbo
DEBUG:pydantic_prompter:Using PydanticParser
DEBUG:pydantic_prompter:Calling with prompt: 
 {'role': 'user', 'content': 'say hi'}
DEBUG:pydantic_prompter:Response from llm: 
 {
  "response": "Hi there!"
 }