Python: Expose forwardedProps to agents and tools via session metadata#5264
Python: Expose forwardedProps to agents and tools via session metadata#5264moonbox3 wants to merge 5 commits intomicrosoft:mainfrom
Conversation
…osoft#5239) Include forwarded_props from AG-UI request input_data in session.metadata (agent runner) and function_invocation_kwargs (workflow runner) so that agents, tools, and workflow executors can access request-level metadata such as invocation source flags from CopilotKit. - Add forwarded_props to base_metadata in _agent_run.py when present - Add 'forwarded_props' to AG_UI_INTERNAL_METADATA_KEYS to filter it from LLM-bound client metadata - Extract forwarded_props in _workflow_run.py and pass via function_invocation_kwargs to workflow.run() - Accept both snake_case and camelCase keys (forwarded_props/forwardedProps) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…esolution (microsoft#5239) The previous fix passed stream=True via **kwargs dict, which prevented pyright from resolving the Workflow.run() overload to the streaming variant. Pass stream=True as an explicit keyword argument so pyright can correctly infer the ResponseStream return type. Also remove unused pytest import in test file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 94%
✓ Correctness
The change is a clean refactoring that separates the always-present
stream=Truekeyword from the conditionally-presentfunction_invocation_kwargs. Before, both lived inrun_kwargs; nowstream=Trueis passed as an explicit keyword argument andfwd_kwargsonly carries forwarded-prop data when present. The behavior is identical in all cases. The test file change removes an unusedimport pytest, which is correct since no pytest decorators or fixtures are used in that module.
✓ Security Reliability
This is a clean, low-risk refactor that separates the always-present
stream=Truekeyword argument from the conditionally-presentfunction_invocation_kwargs. The rename fromrun_kwargstofwd_kwargsimproves clarity by scoping the variable name to its actual content (forwarded props only). The test file change removes an unusedpytestimport. The behavior is functionally identical before and after —stream=Trueis always passed, andfunction_invocation_kwargsis only passed whenforwarded_propsis present. No security or reliability concerns found.
✓ Test Coverage
The code change refactors how kwargs are built for workflow.run(), separating 'stream=True' from the forwarded_props kwargs dict. The behavioral change is purely structural — both old and new code pass the same arguments to workflow.run(). Test coverage forwarded_props forwarding is thorough (present, absent, camelCase variants) via CapturingWorkflow tests in test_workflow_run.py. The test file change just removes an unused pytest import. The one minor gap: no existing test asserts that stream=True is passed to workflow.run(), though this is pre-existing and not introduced by this PR — the CapturingWorkflow already captures it but tests don't assert on it.
✓ Design Approach
The diff makes two unrelated but both correct cleanups: (1) it refactors
run_kwargsintofwd_kwargsso thatstream=True— which is unconditional — is always passed explicitly rather than being hidden inside a dict that is only populated whenforwarded_propsis present, and (2) it removes an unusedpytestimport from the test file. Both changes are straightforward and correct. No design-approach issues were found.
Suggestions
- Consider adding an assertion like
assert workflow.captured_kwargs['stream'] is Truein the existing CapturingWorkflow tests (e.g., test_workflow_run_passes_forwarded_props_as_function_invocation_kwargs) to verify stream=True is always passed. This is a pre-existing gap, but would guard against accidental removal of the explicit kwarg.
Automated review by moonbox3's agents
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR exposes AG-UI forwardedProps/forwarded_props to downstream agent/workflow execution by extracting it from request input and threading it into session/workflow invocation metadata, with tests validating the plumbing.
Changes:
- Extract
forwarded_props(snake_case + camelCase) and inject into agent session metadata, while filtering it from LLM-bound metadata viaAG_UI_INTERNAL_METADATA_KEYS. - Pass
forwarded_propsintoworkflow.run()viafunction_invocation_kwargs. - Add/extend tests to cover forwarded-props propagation paths.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py | Adds forwarded_props to internal metadata keys and injects it into session.metadata. |
| python/packages/ag-ui/agent_framework_ag_ui/_workflow_run.py | Extracts forwarded_props and forwards it to workflow.run() via function_invocation_kwargs. |
| python/packages/ag-ui/tests/ag_ui/test_workflow_run.py | Adds workflow-stream tests asserting function_invocation_kwargs behavior. |
| python/packages/ag-ui/tests/ag_ui/test_forwarded_props_in_metadata.py | Adds tests around internal key filtering and safe metadata serialization. |
- Use key-presence checks instead of truthiness for forwarded_props so
empty dict {} is forwarded correctly
- Gate function_invocation_kwargs on workflow.run() signature inspection
to avoid TypeError for workflows without **kwargs
- Change _build_safe_metadata to drop (with warning) keys whose
serialized values exceed 512 chars instead of truncating into invalid
JSON
- Rewrite metadata tests to exercise _build_safe_metadata directly with
JSON-decodability and truncation assertions
- Add workflow tests for empty dict forwarded_props, stream=True
assertion, and signature-gated kwarg dropping
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t#5239) Guard against accidental removal of the explicit stream=True kwarg in all forwarded_props CapturingWorkflow test cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rops to agents and tools via session metadata
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 95% | Result: All clear
Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach
Automated review by moonbox3's agents
Python Test Coverage Report •
Python Unit Test Overview
|
|||||||||||||||||||||||||||||||||||
Motivation and Context
forwardedPropsfrom the AG-UI request was parsed but silently dropped—agents, tools, and workflows had no way to access request-level metadata (e.g., invocation-source flags from CopilotKit), forcing users to write custom FastAPI endpoints instead of using the built-inadd_agent_framework_fastapi_endpoint().Fixes #5239
Description
In
_agent_run.py,forwarded_propsis now extracted from the input data (accepting both snake_case and camelCase keys) and injected intosession.metadata, and the key is added toAG_UI_INTERNAL_METADATA_KEYSso it is filtered from LM-bound metadata. In_workflow_run.py, the same extraction is performed and the props are passed through to the workflow viafunction_invocation_kwargs. A newstate_update()helper and_state.pymodule were also added to let tools push deterministic AG-UI state updates from actual tool output, with correspondingStateSnapshotEventcoalescing in_run_common.py. Tests cover both the forwarded-props metadata plumbing and the new tool-driven state update path.Contribution Checklist