Skip to content

Activity.text declared as str = None causes Pydantic v2 validation error when deserialising _SignInState.continuation_activity from MemoryStorage #414

@jkshan

Description

@jkshan

Summary

In microsoft-agents-activity (all versions including 1.0.0), Activity.text and Activity.speak are declared as:

text: str = None
speak: str = None

In Pydantic v2, this makes them non-optional str fields with None as a default. When Teams sends an invoke activity during SSO (token exchange), the payload contains "text": null. Pydantic marks text = None as explicitly set (not unset). When _SignInState serialises the continuation activity to MemoryStorage using exclude_unset=True, the null is included. On the next turn, reloading state calls model_validate({"continuation_activity": {"text": null, ...}}), which fails:

1 validation error for _SignInState
continuation_activity.text
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
  For further information visit https://errors.pydantic.dev/2.13/v/string_type

Steps to Reproduce

  1. Deploy a Teams bot using @AGENT_APP.activity(ActivityTypes.message, auth_handlers=["GRAPH"]) to a real Teams environment (not the emulator/playground).
  2. Send a message that triggers the SSO sign-in flow — Teams responds with an invoke activity where "text": null.
  3. On the next turn (continuation), the bot crashes with the validation error above.

Note: This is NOT reproducible locally with the emulator/playground because it does not exercise the real Teams SSO invoke path. It only manifests in a real Teams deployment.

Root Cause

activity.py line:

text: str = None   # non-optional str — Pydantic v2 rejects None on validation

Pydantic v2 distinguishes between a field that was never set vs. a field explicitly set to None. When JSON {"text": null} is parsed, text is marked as set, so exclude_unset=True does not exclude it during serialisation, persisting null to storage. On deserialisation, the strict str type annotation rejects the stored null.

Proposed Fix

# activity.py
text: Optional[str] = None
speak: Optional[str] = None

Environment

  • microsoft-agents-activity==0.9.1 (bug also present in 1.0.0 — confirmed on main branch)
    • pydantic>=2.8.0
    • Python 3.11
    • Microsoft Teams channel (real deployment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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