Skip to content

bug: parsed_repr has runtime/type mismatch for Instruction when format= is set #1313

Description

@planetf1

Problem

When `format=` is passed to `act()` or `instruct()`, the `format=` overloads (PR #1284) narrow the return type to `ComputedModelOutputThunk[MyModel]`. That narrowing is reflected on `parsed_repr: S | None` — so `result.parsed_repr` is statically typed `MyModel | None`. However, at runtime it is always a plain `str`.

The root cause is `Instruction._parse` (mellea/stdlib/components/instruction.py:235), which ignores `_format` and always returns the raw string value:

```python
def _parse(self, computed: ModelOutputThunk) -> str:
return computed.value if computed.value is not None else ""
```

So this type-checks but raises `AttributeError` at runtime:

```python
result = session.act(Instruction("Classify this"), format=Sentiment)
result.parsed_repr.label # typed Sentiment | None — AttributeErrors at runtime
```

Workaround

Use `.parsed` instead of `.parsed_repr`. `.parsed` was added in PR #1284 specifically to provide a runtime-correct path — it calls `model_validate_json` directly and returns the Pydantic instance:

```python
result.parsed.label # typed Sentiment | None — correct at runtime
```

Why this was not fixed in PR #1284

The proper fix requires `Instruction` to become generic over its output type (`Instruction[S]`) so that `_parse` can be typed `-> S` where `S` is either `str` (no `format=`) or `BaseModelSubclass` (with `format=`). That is a non-trivial change to the component hierarchy:

  • `Instruction` is currently `Component[str]`; making it `Component[S]` requires propagating `S` through all construction sites
  • `format=` is passed at the `act()` call site, after `Instruction` is constructed — so the type parameter cannot be inferred from the action alone
  • There are 22 `_parse` implementations across the component hierarchy; a principled fix should address the pattern consistently, not just patch `Instruction`

This scope belongs in its own PR rather than extending #1284 further.

Acceptance criteria

  • `result.parsed_repr` delivers a `MyModel` instance (not a `str`) at runtime when `format=MyModel` was passed to `act()`
  • The static type `S | None` matches the runtime value — no hidden `AttributeError` risk
  • `.parsed` and `parsed_repr` are consistent for the `Instruction` path (they may remain separate entry points but should agree on the value)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    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