Files
567-labs--instructor/docs/concepts/prompting.md
T
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

320 lines
8.4 KiB
Markdown

---
title: Prompt Engineering Best Practices
description: Learn prompt engineering tips for using Pydantic and Instructor effectively.
---
# General Tips for Prompt Engineering
When using Instructor and Pydantic, make your models self-descriptive, modular, and flexible while keeping data integrity.
- Modularity: Design self-contained components for reuse
- Self-description: Use Pydantic's `Field` for clear field descriptions
- Optionality: Use Python's `Optional` type for nullable fields and set defaults
- Standardization: Use enumerations for fields with fixed values; include a fallback option
- Dynamic data: Use key-value pairs for arbitrary properties and limit list lengths
- Entity relationships: Define explicit identifiers and relationship fields
- Contextual logic: Optionally add a "chain of thought" field in reusable components for extra context
## Modular Chain of Thought {#chain-of-thought}
Use chain of thought to improve data quality. You can add it to specific components rather than making it global.
```python hl_lines="4 5"
from pydantic import BaseModel, Field
class Role(BaseModel):
chain_of_thought: str = Field(
..., description="Think step by step to determine the correct title"
)
title: str
class UserDetail(BaseModel):
age: int
name: str
role: Role
```
## Utilize Optional Attributes
Use Python's Optional type and set a default value to prevent undesired defaults like empty strings.
```python hl_lines="6"
from typing import Optional
from pydantic import BaseModel, Field
class UserDetail(BaseModel):
age: int
name: str
role: Optional[str] = Field(default=None)
```
## Handling Errors Within Function Calls
Create a wrapper class to hold either the result of an operation or an error message. This lets you stay within a function call even if an error occurs, improving error handling without breaking the code flow.
```python
from pydantic import BaseModel, Field
from typing import Optional
class UserDetail(BaseModel):
age: int
name: str
role: Optional[str] = Field(default=None)
class MaybeUser(BaseModel):
result: Optional[UserDetail] = Field(default=None)
error: bool = Field(default=False)
message: Optional[str]
def __bool__(self):
return self.result is not None
```
With the `MaybeUser` class, you can either receive a `UserDetail` object in result or get an error message in message.
### Simplification with the Maybe Pattern
Simplify this using Instructor to create the `Maybe` pattern dynamically from any `BaseModel`.
```python
import instructor
from pydantic import BaseModel
class UserDetail(BaseModel):
age: int
name: str
MaybeUser = instructor.Maybe(UserDetail)
```
This lets you quickly create a Maybe type for any class.
## Tips for Enumerations
Use Enums for standardized fields to prevent data misalignment. Always include an "Other" option as a fallback so the model can signal uncertainty.
```python hl_lines="7 12"
from enum import Enum, auto
from pydantic import BaseModel, Field
class Role(Enum):
PRINCIPAL = auto()
TEACHER = auto()
STUDENT = auto()
OTHER = auto()
class UserDetail(BaseModel):
age: int
name: str
role: Role = Field(
description="Correctly assign one of the predefined roles to the user."
)
```
## Literals {#literals}
If you're having a hard time with `Enum` an alternative is to use `Literal`
```python hl_lines="4"
from typing import Literal
from pydantic import BaseModel
class UserDetail(BaseModel):
age: int
name: str
role: Literal["PRINCIPAL", "TEACHER", "STUDENT", "OTHER"]
```
If you'd like to improve performance more you can reiterate the requirements in the field descriptions or in the docstrings.
## Reiterate Long Instructions
For complex attributes, repeat the instructions in the field's description.
```python hl_lines="5 11"
from pydantic import BaseModel, Field
class Role(BaseModel):
"""
Extract the role based on the following rules ...
"""
instructions: str = Field(
...,
description="Restate the instructions and rules to correctly determine the title.",
)
title: str
class UserDetail(BaseModel):
age: int
name: str
role: Role
```
## Handle Arbitrary Properties
When you need to extract undefined attributes, use a list of key-value pairs.
```python hl_lines="10"
from typing import List
from pydantic import BaseModel, Field
class Property(BaseModel):
key: str
value: str
class UserDetail(BaseModel):
age: int
name: str
properties: List[Property] = Field(
..., description="Extract any other properties that might be relevant."
)
```
## Limiting the Length of Lists
When dealing with lists of attributes, especially arbitrary properties, manage the length. Use prompting and enumeration to limit the list length and keep a manageable set of properties.
```python hl_lines="2 9"
from typing import List
from pydantic import BaseModel, Field
class Property(BaseModel):
index: str = Field(..., description="Monotonically increasing ID")
key: str
value: str
class UserDetail(BaseModel):
age: int
name: str
properties: List[Property] = Field(
...,
description="Numbered list of arbitrary extracted properties, should be less than 6",
)
```
### Using Tuples for Simple Types
For simple types, tuples can be a more compact alternative to custom classes, especially when the properties don't require additional descriptions.
```python hl_lines="4"
from typing import List, Tuple
from pydantic import BaseModel, Field
class UserDetail(BaseModel):
age: int
name: str
properties: List[Tuple[int, str]] = Field(
...,
description="Numbered list of arbitrary extracted properties, should be less than 6",
)
```
## Advanced Arbitrary Properties
For multiple users, use consistent key names when extracting properties.
```python
from typing import List
from pydantic import BaseModel
class UserDetail(BaseModel):
id: int
age: int
name: str
class UserDetails(BaseModel):
"""
Extract information for multiple users.
Use consistent key names for properties across users.
"""
users: List[UserDetail]
```
This refined guide should offer a cleaner and more organized approach to structure engineering in Python.
## Defining Relationships Between Entities
When relationships exist between entities, define them explicitly in the model. The following example shows how to define relationships between users by adding an id and a friends field:
```python hl_lines="2 5 8"
from typing import List
from pydantic import BaseModel, Field
class UserDetail(BaseModel):
id: int = Field(..., description="Unique identifier for each user.")
age: int
name: str
friends: List[int] = Field(
...,
description="Correct and complete list of friend IDs, representing relationships between users.",
)
class UserRelationships(BaseModel):
users: List[UserDetail] = Field(
...,
description="Collection of users, correctly capturing the relationships among them.",
)
```
## Reusing Components with Different Contexts
You can reuse the same component for different contexts within a model. In this example, the TimeRange component is used for both work_time and leisure_time.
```python hl_lines="9 10"
from pydantic import BaseModel, Field
class TimeRange(BaseModel):
start_time: int = Field(..., description="The start time in hours.")
end_time: int = Field(..., description="The end time in hours.")
class UserDetail(BaseModel):
id: int = Field(..., description="Unique identifier for each user.")
age: int
name: str
work_time: TimeRange = Field(
..., description="Time range during which the user is working."
)
leisure_time: TimeRange = Field(
..., description="Time range reserved for leisure activities."
)
```
Sometimes, a component like TimeRange may need context or additional logic to work well. Adding a "chain of thought" field within the component can help understand or optimize the time range allocations.
```python hl_lines="2"
from pydantic import BaseModel, Field
class TimeRange(BaseModel):
chain_of_thought: str = Field(
..., description="Step by step reasoning to get the correct time range"
)
start_time: int = Field(..., description="The start time in hours.")
end_time: int = Field(..., description="The end time in hours.")
```