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
- Deploy a Teams bot using
@AGENT_APP.activity(ActivityTypes.message, auth_handlers=["GRAPH"]) to a real Teams environment (not the emulator/playground).
- Send a message that triggers the SSO sign-in flow — Teams responds with an invoke activity where
"text": null.
- 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)
-
-
-
- Microsoft Teams channel (real deployment)
Summary
In
microsoft-agents-activity(all versions including1.0.0),Activity.textandActivity.speakare declared as:In Pydantic v2, this makes them non-optional
strfields withNoneas a default. When Teams sends an invoke activity during SSO (token exchange), the payload contains"text": null. Pydantic markstext = Noneas explicitly set (not unset). When_SignInStateserialises the continuation activity toMemoryStorageusingexclude_unset=True, thenullis included. On the next turn, reloading state callsmodel_validate({"continuation_activity": {"text": null, ...}}), which fails:Steps to Reproduce
@AGENT_APP.activity(ActivityTypes.message, auth_handlers=["GRAPH"])to a real Teams environment (not the emulator/playground)."text": null.Root Cause
activity.pyline:Pydantic v2 distinguishes between a field that was never set vs. a field explicitly set to
None. When JSON{"text": null}is parsed,textis marked as set, soexclude_unset=Truedoes not exclude it during serialisation, persistingnullto storage. On deserialisation, the strictstrtype annotation rejects the storednull.Proposed Fix
Environment
microsoft-agents-activity==0.9.1(bug also present in1.0.0— confirmed onmainbranch)pydantic>=2.8.0