Conditional Fields¶
With use_search=True and search_per_field=True, each model field is filled by its own agent call. In that mode, a field can depend on the value of another:
- Conditional execution: the field is only requested from the LLM when the condition holds; otherwise it stays
Noneand spends no search. - Nested fields can be read in the condition with dot notation.
- Execution order is derived from the condition: the field that is read runs before the field that depends on it.
Basic Setup¶
Use the condition key in the field's json_schema_extra:
condition(dict): the dependency, and therefore the execution order, is derived from the field incondition['field']. The dict holdsfield, the field name as a string, and one operator:equals,not_equals,in,not_inorexists.inandnot_intake a list, tuple, set or frozenset of the accepted values.condition(callable): receives the fields already filled and returns a bool. Build thejson_schema_extrawithfield_condition(condition, depends_on=...), which also takes the fields the function reads, so they run first: a field name as a string, or a list or tuple of names. The helper returns{'condition': condition, 'depends_on': [...]}, the same dict you would write by hand; written by hand, the type checker rejects theField, because Pydantic typesjson_schema_extraas JSON and a function is not JSON.
Any other form of condition or depends_on raises ValueError before processing.
Since a skipped field stays None, declare it as optional (str | None = None).
Example 1: Individual vs Company¶
from pydantic import BaseModel, Field
from dataframeit import dataframeit
class PessoaInfo(BaseModel):
tipo: str = Field(
description="Person type: 'pf' for an individual or 'pj' for a company"
)
cpf: str | None = Field(
default=None,
description="Individual's CPF",
json_schema_extra={'condition': {'field': 'tipo', 'equals': 'pf'}},
)
cnpj: str | None = Field(
default=None,
description="Company's CNPJ",
json_schema_extra={'condition': {'field': 'tipo', 'equals': 'pj'}},
)
razao_social: str | None = Field(
default=None,
description="Company's legal name",
json_schema_extra={'condition': {'field': 'tipo', 'equals': 'pj'}},
)
textos = [
"João Silva, CPF 123.456.789-00",
"Empresa XYZ LTDA, CNPJ 12.345.678/0001-90",
]
result = dataframeit(
textos,
PessoaInfo,
"Extract the person's or company's information: {texto}",
use_search=True,
search_per_field=True, # condition requires per-field search
)
# Row 1: tipo='pf', cpf='123.456.789-00', cnpj=None, razao_social=None
# Row 2: tipo='pj', cpf=None, cnpj='12.345.678/0001-90', razao_social='Empresa XYZ LTDA'
Example 2: Chained Fields¶
class EnderecoInfo(BaseModel):
pais: str = Field(description="Country")
estado: str | None = Field(
default=None,
description="State (Brazil only)",
json_schema_extra={'condition': {'field': 'pais', 'equals': 'Brasil'}},
)
cep: str | None = Field(
default=None,
description="CEP (Brazil only)",
json_schema_extra={'condition': {'field': 'estado', 'exists': True}},
)
zip_code: str | None = Field(
default=None,
description="Postal code (other countries only)",
json_schema_extra={'condition': {'field': 'pais', 'not_equals': 'Brasil'}},
)
The execution order is pais → estado → cep and pais → zip_code.
Example 3: Callable Condition with Several Dependencies¶
When the condition combines several fields, use a function and declare the fields it reads in depends_on:
from dataframeit import field_condition
class PedidoInfo(BaseModel):
tipo_cliente: str = Field(description="Customer type: 'novo' or 'vip'")
valor_pedido: float = Field(description="Order total")
desconto: float | None = Field(
default=None,
description="Discount applied",
json_schema_extra=field_condition(
lambda dados: (
dados.get('tipo_cliente') == 'vip'
and (dados.get('valor_pedido') or 0) > 1000
),
depends_on=['tipo_cliente', 'valor_pedido'],
),
)
To add other per-field keys, unpack the dict: json_schema_extra={**field_condition(...), 'search_depth': 'advanced'}.
Condition Operators¶
# Equality
{'field': 'tipo', 'equals': 'pf'}
# Inequality
{'field': 'tipo', 'not_equals': 'pj'}
# In the list
{'field': 'status', 'in': ['ativo', 'pendente']}
# Not in the list
{'field': 'status', 'not_in': ['inativo', 'cancelado']}
# Field filled (not None)
{'field': 'campo_opcional', 'exists': True}
Without depends_on, the order of a callable is not guaranteed.
Nested Fields in Conditions¶
A condition can read a field of a nested model with dot notation. The derived dependency is the root field:
class Endereco(BaseModel):
cidade: str = Field(description="City")
uf: str = Field(description="State")
class Entrega(BaseModel):
endereco: Endereco = Field(description="Delivery address")
taxa_entrega: float | None = Field(
default=None,
description="Delivery fee",
json_schema_extra={
'condition': {'field': 'endereco.cidade', 'in': ['São Paulo', 'Rio de Janeiro']}
},
)
The condition belongs to a top-level field. condition or depends_on on a field of a nested model, or of a list item, raises ValueError before processing.
Circular Dependencies¶
A cycle is detected before the first row:
class ModeloInvalido(BaseModel):
a: str | None = Field(default=None, json_schema_extra={'condition': {'field': 'b', 'equals': 'x'}})
b: str | None = Field(default=None, json_schema_extra={'condition': {'field': 'a', 'equals': 'x'}})
# ValueError: Dependências circulares detectadas: a -> b -> a
Logging and Debugging¶
The execution order, the dependencies and each condition's evaluation are logged at DEBUG level, and the skipped fields at INFO, on the dataframeit.conditional and dataframeit.agent loggers:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('dataframeit').setLevel(logging.DEBUG)
Combining with Other Settings¶
A condition works alongside the other per-field search keys:
class InfoCompleta(BaseModel):
tipo: str
detalhes_pf: str | None = Field(
default=None,
json_schema_extra={
'condition': {'field': 'tipo', 'equals': 'pf'},
'search_depth': 'advanced',
'max_results': 10,
'max_search_calls': 3,
'prompt_append': 'Include detailed history.',
},
)
Limitations¶
- Conditions only work with
use_search=Trueandsearch_per_field=True; otherwise,conditionordepends_onin the model raisesValueError. - With
search_groups, a grouped field's condition is evaluated before the group call, and a field whose condition is false is left out of it; if the condition depends on another field of the same group, it is evaluated with the group's answer, and a field whose condition is false staysNone. - A condition can only read fields that exist in the model.
- Circular dependencies are not allowed, including between a group and fields outside it.
- Each condition is evaluated once: before the field's call, or after the group's answer when it depends on another field of the same group.
- Fields with conditions are processed in sequence, in dependency order; a skipped field spends no search.