Skip to content

chore: thread format= through act()/instruct() return type overloads so structured output is cast-free #1274

Description

@planetf1

Context: Same script as companion bug #1273. After hitting type errors on result.value, I checked the type stubs and found ComputedModelOutputThunk[str]S is bound to Instruction's output type, not to the format= argument. The type system gave no way to express the correct type, so I suppressed its complaint with cast(MyModel, result.value). Pyright was satisfied.

That cast is exactly what hid the silent failure in #1273. The type system didn't guide me toward the wrong pattern by accident — it had no way to express the right one. A developer with no prior Mellea experience has no signal that cast is dangerous here; from the type stubs it looks correct.

The gap: session.act(action, format=MyModel) returns ComputedModelOutputThunk[S] where S is the Component's type variable — for Instruction, that's str. The format= argument is invisible to the type system.

Proposed fix: BaseModelSubclass already exists in core/backend.py as a TypeVar(bound=BaseModel, default=BaseModel). Add overloads using it to thread format= into the return type:

@overload
def act(
    self, action: Component[Any], *, format: type[BaseModelSubclass],
    return_sampling_results: Literal[False] = False, ...
) -> ComputedModelOutputThunk[BaseModelSubclass]: ...

@overload
def act(
    self, action: Component[S], *, format: None = None,
    return_sampling_results: Literal[False] = False, ...
) -> ComputedModelOutputThunk[S]: ...

Scope:

  • act and aact in session.py — return ComputedModelOutputThunk
  • instruct and ainstruct in session.py — same shape
  • The equivalent methods in functional.py — note these return tuple[ComputedModelOutputThunk[BaseModelSubclass], Context] in the format= overload, not just the thunk
  • chat and query have different return shapes and should be scoped separately

Test plan: add a reveal_type assertion in a type-stub test or run pyright --verifytypes to confirm the overloads resolve correctly.

Compatibility: type-only change, no runtime behaviour changes. Existing cast(MyModel, result.value) calls will correctly be flagged as unnecessary by strict Pyright — that's the intended outcome, guiding callers to the fix.

Related: #1177 (type annotations broadly). Companion to bug #1273.

Metadata

Metadata

Assignees

Labels

area/stdlibCore abstractions: Context, MOT, SamplingStrategy, formatters, serializationchoreHousekeeping: renames, comment fixes, dependency bumps, repo hygienep2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions