Typing
Overview
The Inspect codebase is written using strict MyPy type-checking—if you enable the same for your project along with installing the MyPy VS Code Extension you’ll benefit from all of these type definitions.
The sample store and sample metadata interfaces are weakly typed to accommodate arbitrary user data structures. Below, we describe how to implement a typed store and typed metadata using Pydantic models.
Typed Store
If you prefer a typesafe interface to the sample store, you can define a Pydantic model which reads and writes values into the store. There are several benefits to using Pydantic models for store access:
- You can provide type annotations and validation rules for all fields.
- Default values for all fields are declared using standard Pydantic syntax.
- Store names are automatically namespaced (to prevent conflicts between multiple store accessors).
Definition
First, derive a class from StoreModel
(which in turn derives from Pydantic BaseModel
):
from pydantic import Field
from inspect_ai.util import StoreModel
class Activity(StoreModel):
bool = Field(default=False)
active: int = Field(default=0)
tries: list[str] = Field(default_factory=list) actions:
Note that we define defaults for all fields. This is generally required so that you can initialise your Pydantic model from an empty store. For collections (list
and dict
) you should use default_factory
so that each instance gets its own default.
Usage
Use the store_as()
function to get a typesafe interface to the store based on your model:
# typed interface to store from state
= state.store_as(Activity)
activity = True
activity.active += 1
activity.tries
# global store_as() function (e.g. for use from tools)
from inspect_ai.util import store_as
= store_as(Activity) activity
Note that all instances of Activity
created within a running sample share the same sample Store
so can see each other’s changes. For example, you can call state.store_as()
in multiple solvers and/or scorers and it will resolve to the same sample-scoped instance.
The names used in the underlying Store
are namespaced to prevent collisions with other Store
accessors. For example, the active
field in the Activity
class is written to the store with the name Activity:active
.
Explicit Store
The store_as()
function automatically binds to the current sample Store
. You can alternatively create an explicit Store
and pass it directly to the model (e.g. for testing purposes):
from inspect_ai.util import Store
= Store()
store = Activity(store=store) activity
Typed Metadata
If you want a more strongly typed interface to sample metadata, you can define a Pydantic model and use it to both validate and read metadata.
For validation, pass a BaseModel
derived class in the FieldSpec
. The interface to metadata is read-only so you must also specify frozen=True
. For example:
from pydantic import BaseModel
class PopularityMetadata(BaseModel, frozen=True):
str
category: float
label_confidence:
= json_dataset(
dataset "popularity.jsonl",
FieldSpec(input="question",
="answer_matching_behavior",
targetid="question_id",
=PopularityMetadata,
metadata
), )
To read metadata in a typesafe fashion, us the metadata_as()
method on Sample
or TaskState
:
= state.metadata_as(PopularityMetadata) metadata
Note again that the intended semantics of metadata
are read-only, so attempting to write into the returned metadata will raise a Pydantic FrozenInstanceError
.
If you need per-sample mutable data, use the sample store, which also supports typing using Pydantic models.