mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
dbfacbfc4aa676aca1c0a866e78e4da08ea2d2de
323 Commits
-
Python: Filter MCP tool kwargs to declared params via allowlist (#6399)
* Filter MCP tool kwargs to declared params via allowlist Previously MCPTool combined framework runtime kwargs (from FunctionInvocationContext.kwargs) with the LLM-supplied arguments and stripped only a hardcoded denylist of known framework keys before forwarding to the MCP server. Any new framework-injected kwarg leaked to the server unless the denylist was updated. Switch to an allowlist built from each tool's declared parameters (inputSchema.properties). Only declared params are forwarded; everything else is stripped. Add an `additional_tool_argument_names` constructor argument so users can opt extra names back in, globally (Sequence[str]) and/or per remote tool name (Mapping with reserved "*" global key). The existing denylist is kept as a safety net for framework-named params a server declares in its schema; explicitly opted-in extras always win. The reserved _meta handling is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address MCP allowlist review comments and fix reload arg loss - Fix pyright reportUnknownArgumentType in _load_tools (cast schema properties). - Register declared param names before the existing-tool skip guard so that tool-list reloads preserve the allowlist for already-loaded tools (previously unchanged tools silently dropped all declared args after a background reload). - Handle bare-string values in an additional_tool_argument_names mapping instead of iterating their characters. - Clarify the framework denylist comment: explicit extras override the denylist. - Make the extras-override-denylist test unambiguous (opt in a denylisted name). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-06-09 07:37:11 +00:00 -
Python: Fix per-service-call history persistence with server-storing clients (#6310)
* Fix per-service-call history persistence with server-storing clients When an Agent set require_per_service_call_history_persistence=True together with a HistoryProvider, and the chat client stored history server-side by default (e.g. OpenAIChatClient, STORES_BY_DEFAULT=True), the external history provider was silently never persisted. Unify persistence on the per-service-call middleware: when the flag is set and a HistoryProvider exists, the middleware is always installed and owns persistence. service_stores_history now only selects middleware behavior: - service does not store: load providers and drive the function loop with a local sentinel conversation id, or - service stores: skip loading (the service owns history) and persist each service call while the real conversation id flows through. Also rationalize chat-options handling in _prepare_run_context: - _merge_options now skips None overrides and strips remaining None values, so an unset `store` is never forwarded and the service decides its own default. - Resolve `store` and `conversation_id` once from a single combined view (effective_options) instead of probing both default and runtime dicts; the auto-injection and per-service-call resolution now agree on conversation_id. Fixes #5798 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Correct as_agent() docstring: persistence is per service call, not once per run Address PR review: when the client stores history server-side, the per-service-call middleware still persists after each model call; only provider loading is skipped. The previous "persist once per run()" wording contradicted the implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: docs, missing-conversation-id warning, and tests - Clarify that require_per_service_call_history_persistence is a no-op when no HistoryProvider is present (docstrings in _agents.py and _clients.py). - Warn on every service call when the client stores history server-side but returns no conversation_id, so the (uncommon) loss of cross-turn resumability cannot fail silently. - Add tests: storing client + existing conversation_id does not raise and the id propagates; two runs on the same session keep persisting with a stable service_session_id and no provider loading; storing-without-conversation-id warns per call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-06-09 05:47:57 +00:00 -
Python: feat(python): Add MCP client OTel spans per GenAI semantic conventions (#6349)
* feat(python): Add MCP client OTel spans per GenAI semantic conventions Implement MCP client spans per the OTel GenAI Semantic Conventions for MCP (https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/#client). Operations instrumented: - initialize: CLIENT span capturing MCP session setup - tools/list: CLIENT span for tool listing (per-page) - prompts/list: CLIENT span for prompt listing (per-page) - tools/call: CLIENT span (nested under execute_tool when called via FunctionTool) - prompts/get: CLIENT span Span attributes follow the MCP semantic conventions: - Required: mcp.method.name - Conditional: error.type, gen_ai.tool.name, gen_ai.prompt.name - Recommended: gen_ai.operation.name, mcp.protocol.version, mcp.session.id, network.transport, server.address, server.port Transport-specific attributes per subclass: - MCPStdioTool: network.transport=pipe - MCPStreamableHTTPTool: network.transport=tcp, network.protocol.name=http - MCPWebsocketTool: network.transport=tcp, network.protocol.name=websocket All span creation gated behind OBSERVABILITY_SETTINGS.ENABLED. Closes #3624 Closes #4697 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: simplify MCP spans — remove enrichment logic and protocol version caching - Always create nested CLIENT spans for tools/call instead of enriching the parent execute_tool span - Remove _ACTIVE_TOOL_EXECUTION_SPAN contextvar (no longer needed) - Remove enrich_span_with_mcp_attributes() helper - Remove _otel_error_type preservation in FunctionTool.invoke() - Remove _mcp_protocol_version instance variable; protocol version is only set on the initialize span where it is available Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refine copilot solution * fix: enable automatic exception recording on MCP spans Remove record_exception=False and set_status_on_exception=False from create_mcp_client_span. Let OTel handle exception recording and status setting automatically. The manual set_mcp_span_error calls for tools/call still correctly set error.type (which OTel's automatic handling doesn't touch), so tool_error is preserved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reduce number of lines * Add comment to sample * test: address PR review comments on MCP observability tests - Fix initialize test to call mocked session.initialize() and read protocolVersion from the result instead of hardcoding it - Add tools/call McpError error-path test - Add prompts/get McpError error-path test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix export error --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tao Chen ·
2026-06-05 19:23:01 +00:00 -
Python: Refactor workflow as agent pending request handling (#6259)
* WIP: Refactor Workflow as agent pending request handling * WIP: debugging empty message bug * Working: Workflow as agent with function approval * Address Copilot comments * Fix mypy * Address comments and fix pipeline * Request info non function approval now becomes function call * Revert uv.lock * Fix mypy * Bump min version of azure-ai-project * Remove RequestInfoFunctionArgs * fix tests * Fix failing tests * Fix sample
Tao Chen ·
2026-06-05 17:23:19 +00:00 -
Python: MCP long-running task support in Python (#6319)
* MCP long-running task support in Python * Fix pyupgrade and AGENTS.md reconnect description - pyupgrade: drop forward-reference string annotations in _mcp.py (Python 3.10+ resolves them natively now that MCPTaskOptions is defined before use). - AGENTS.md: align reconnect description with current behavior. Phase 1 (initial tools/call) does NOT retry on connection loss; raises 'connection lost; task state unknown' instead, so a server that accepted the request but lost the response cannot start the operation twice. Phase 2 (tasks/get / tasks/result) still reconnects once against the same task_id. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix bandit nosec marker for CI pipeline * Address PR feedbacks * Clarifiied comments and addressed more PR feedbacks. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Peter Ibekwe ·
2026-06-05 00:04:55 +00:00 -
Python: Fix compaction message-id collisions and tool-loop summary persistence (#6299)
* Fix compaction message-id collisions and tool-loop summary persistence Fixes two bugs in the compaction strategies: - #5237: incremental group annotation assigned message ids by position within the re-annotated slice, so moving the re-annotation start back to a previous group start restarted ids at 0 and produced collisions (e.g. a user message reusing an assistant message's id), merging groups and causing tool-result compaction to wrongly exclude messages. group_messages/_ensure_message_ids now take an id_offset and guard against existing-id collisions; annotate_message_groups threads the slice start index through as the offset. - #4991: the function-invocation loop copied the message list each iteration, so summaries inserted by compaction landed in a throwaway copy and were lost across tool-loop iterations (only the persistent excluded flags survived). _prepare_messages_for_model_call now compacts the list in place when messages is a list, so inserted summaries persist. Adds regression tests (incremental id uniqueness, existing-id collision avoidance, idempotency, and tool-loop summary persistence including streaming and conversation-id modes). Also adds a summarization.py sample demonstrating SummarizationStrategy directly with a real client, and reworks advanced.py with tool-call groups and a real summarizer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard incremental message-id assignment against prefix-id collisions Addresses PR review on #5237: _ensure_message_ids only guarded against collisions within the re-annotated slice. A preexisting (e.g. user-supplied) id in the preserved prefix could still be reassigned in the suffix when the id was numerically out of position, merging groups across the re-annotation boundary again. group_messages/_ensure_message_ids now accept reserved_ids, and annotate_message_groups passes the preserved prefix's ids so auto-assigned suffix ids never collide across the full list. Adds a regression test reproducing the out-of-position prefix-id collision. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-06-04 08:37:59 +00:00 -
Python: run sync tools off the event loop (#5773)
* fix: run sync tools off event loop * chore: silence harness tool marker type check
Yufeng He ·
2026-06-04 04:42:08 +00:00 -
Python: Add MCP-based skills discovery (McpSkillsSource) (#6169)
* Add MCP-based skills discovery (McpSkill, McpSkillsSource, McpSkillResource) Implement Agent Skills discovery over MCP following the SEP-2640 convention: - McpSkillsSource: reads skill://index.json to discover skills served by an MCP server - McpSkill: lazily fetches SKILL.md content via resources/read on demand - McpSkillResource: wraps MCP resource results (text and binary) - Path traversal protection in get_resource for defense in depth - Samples for Foundry Toolbox and standalone MCP skills server - Comprehensive unit tests (514 lines) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review comments: rename to MCP* convention, fix error handling and samples - Rename McpSkill/McpSkillResource/McpSkillsSource to MCPSkill/MCPSkillResource/MCPSkillsSource - Add data-URI prefix stripping for blob resource decoding - Let non-McpError exceptions propagate from get_resource() - Fix contradictory test comment - Use interactive input() in mcp_based_skill sample - Remove misleading sample output block Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore debug logging for McpError in get_resource() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use AzureCliCredential in Foundry toolbox skills sample for consistency Replace DefaultAzureCredential with AzureCliCredential to match the credential convention used in all other samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use MCPStreamableHTTPTool in MCP skills sample Replace raw mcp library imports (ClientSession, streamable_http_client) with the framework's MCPStreamableHTTPTool to keep MCP server connections consistent regardless of whether skills are enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Branch on McpError.error.code so only not-found errors return empty Previously _try_read_index() and get_resource() swallowed every McpError as 'no skills available', making auth failures, server crashes, and connection drops indistinguishable from a server that simply has no skills. Now only two codes are treated as not-found: - -32002 (MCP-spec Resource not found) - -32601 (METHOD_NOT_FOUND — server lacks resources/read) All other McpError codes and non-McpError exceptions propagate with a warning log, surfacing real failures visibly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add tests for non-McpError and non-not-found error propagation in MCP skills Cover the re-raise branch in MCPSkill.get_resource for plain ConnectionError/TimeoutError, the generic McpError (code 0) propagation on get_resource, and TimeoutError propagation in _try_read_index. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert "Use MCPStreamableHTTPTool in MCP skills sample" This reverts commit
f31ed0ded9. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Introduce MCP_SKILLS experimental feature for MCP skill classes Add a separate MCP_SKILLS feature ID to ExperimentalFeature enum and use it for MCPSkillResource, MCPSkill, and MCPSkillsSource, since their promotion timeline is partly outside of our control. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>semenshi-m ·
2026-06-03 18:09:50 +00:00 -
Python: progressive tool exposure via FunctionInvocationContext (#6233)
* Python: progressive tool exposure via FunctionInvocationContext Add first-class progressive tool exposure to the Python core function-calling loop. Tools can now add or remove real FunctionTool schemas at runtime via the injected FunctionInvocationContext, taking effect on the next iteration of the loop. - FunctionInvocationContext gains a live `tools` list plus experimental `add_tools()` / `remove_tools()` helpers (feature: PROGRESSIVE_TOOLS). - The function-calling loop establishes a run-local, normalized tools list and threads it into the context at both invocation paths so mutations propagate. - Add a sample (dynamic_tool_exposure.py) and a tools samples README, including a note that CodeAct providers (Monty/Hyperlight) use their own provider-level tool management instead. Supersedes #3877. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Validate non-negative input in dynamic_tool_exposure sample tools Address review feedback: factorial and fibonacci now return an error message for negative n instead of producing incorrect results. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make add_tools atomic and surface swallowed function errors Address review feedback on progressive tool exposure: - add_tools now validates the full batch against a throwaway copy before committing, so a duplicate-name clash partway through a sequence leaves the live tool list unchanged (all-or-nothing). - _auto_invoke_function now logs a warning (with traceback) when a tool raises, so contract errors such as a duplicate-name ValueError from add_tools are debuggable without enabling include_detailed_errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid retaining tracebacks when logging swallowed function errors Logging with exc_info=exc fed the exception traceback to the logging machinery, whose frame references created reference cycles collected lazily by the cyclic GC. On Windows that could drop a hyperlight WasmSandbox on a non-owning thread ("unsendable, dropped on another thread"), crashing the xdist worker. Log a pre-formatted message with the exception repr instead, so no traceback object is retained. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * added missing decorator --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-06-03 09:01:07 +00:00 -
Python: Fix OTLP HTTP base-endpoint losing /v1/{signal} auto-append (#5913)
* Python: Fix OTLP HTTP base-endpoint losing /v1/{signal} auto-append Per the OTel spec, OTEL_EXPORTER_OTLP_ENDPOINT is a *base* URL for HTTP — the SDK auto-appends /v1/traces, /v1/metrics, /v1/logs when it reads the env var directly. Signal-specific endpoint env vars are *full* URLs used verbatim. _get_exporters_from_env read the base endpoint and forwarded it as the constructor ``endpoint=`` argument, which the SDK always treats as a full signal URL. As a result, with OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 and HTTP protocol, the exporter sent to http://localhost:4318 instead of http://localhost:4318/v1/traces (and likewise for metrics/logs). Replicate the spec's auto-append here when falling back to the base endpoint under HTTP. gRPC behavior is unchanged. * Python: Fix mypy type errors in OTLP endpoint assignment Pre-declare traces_endpoint, metrics_endpoint, logs_endpoint as str | None before the if/else block. Mypy inferred str from the if-branch f-string assignments and then rejected the str | None expressions in the else-branch as incompatible.Dineshsuriya D ·
2026-06-02 09:59:50 +00:00 -
Python: feat(evals): Foundry Adaptive Evals integration (rubric-generation) (#6101)
* Python: feat(evals): RubricScore type + EvalScoreResult.dimensions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(foundry-evals): RubricDimension + GeneratedEvaluatorRef + accept in evaluators= Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(evals): parse rubric_scores from output items + assertion helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(evals): BaseAgent.as_eval_source / Workflow.as_eval_source Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(foundry-evals): EvalGenerationSource + generate_rubric helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(foundry-evals): YAML config loader + sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: fix(evals): address PR review feedback Addresses 4 Copilot review comments on PR #6101: 1. assert_dimension_score_at_least: drop the (not evaluator or found_any) guard so require_applicable=True correctly raises when the named evaluator produces no entries for the dimension. Adds TestRubricAssertions covering the regression. 2. GeneratedEvaluatorRef docstring: reword to describe actual behaviour (pinning recommended, not required) so it matches the dataclass default and FoundryEvals warning path. 3. _poll_generation_job: switch from asyncio.get_event_loop() to get_running_loop() and bound the per-iteration sleep by remaining time, matching _poll_eval_run. 4. generate_rubric: type category as Literal['quality','safety'] and validate at the entry point with a ValueError; drop the silent 'invalid -> quality' rewrite in _generation_job_to_ref. Adds a regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: feat(foundry-evals): hosted-agent-aware rubric generation * Auto-detect hosted Foundry agents in agent_as_eval_source: when the agent's chat_client exposes a string agent_name (the convention used by RawFoundryAgentChatClient for PromptAgents/HostedAgents), emit a type='agent' EvalGenerationSource so the service fetches instructions and tools from the agent registry instead of relying on the local wrapper (which holds neither for hosted agents). * Add hosted_agent_version kwarg and a new agent_version field on EvalGenerationSource so PromptAgent runs can pin to a specific hosted version for reproducible rubric generation. * Add force_prompt_source escape hatch to bypass auto-detection and always emit a rendered prompt dossier - useful when the local wrapper carries overrides the service-side agent doesnt see. * Fix _to_sdk_source for dataset sources: SDK ctor takes name=/version=, not dataset_name=/dataset_version=. The mismatch would raise TypeError against the real azure-ai-projects 2.3.0a* SDK; only unmocked integration paths were affected. Tests cover: auto-detection happy path, versionless hosted agent, explicit hosted_agent_version forwarding, force_prompt_source override, non-string chat_client attrs (MagicMock test doubles) not mis-detected, agent_version forwarded through _to_sdk_source, and the corrected dataset SDK kwarg names. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(foundry-evals): accept canonical dimension_scores key per docs The published Foundry rubric-evaluator output (Microsoft Learn 'Rubric evaluators' reference) places per-dimension breakdowns under properties.dimension_scores, not properties.rubric_scores. The parser now tries dimension_scores first and falls back to rubric_scores for preview-build compatibility, and tolerates non-list payloads (e.g. MagicMock auto-attrs) by trying the next candidate when parsing yields zero entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(foundry-evals): add manual create_rubric_evaluator Adds FoundryEvals.create_rubric_evaluator as the agent-framework surface over project_client.beta.evaluators.create_version. This is the manual counterpart to generate_rubric: callers supply RubricDimension instances (authored locally, ported from another framework, or hand-tuned) and we POST a RubricBasedEvaluatorDefinition. The service auto-attaches the non-editable residual dimension (general_quality for quality, general_policy_compliance for safety). Per the Microsoft Learn 'Rubric evaluators' reference, the auto-generation path (create_generation_job) is primarily a portal/UI feature; external SDK clients with rich local agent context are better served by manual create_version. This keeps generate_rubric for users who want to round-trip through a Foundry-registered agent. Validation up front: weight must be in [1,10], ids unique, descriptions non-empty, pass_threshold in [0,1]. The returned GeneratedEvaluatorRef is identical in shape to one obtained from generate_rubric, so downstream evaluators= lists work unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * samples(foundry-evals): manual rubric sample + namespace re-exports Adds evaluate_with_manual_rubric_sample.py demonstrating the end-to-end dev scenario for FoundryEvals.create_rubric_evaluator: hand-author a list of RubricDimension, register via create_rubric_evaluator, then use the pinned GeneratedEvaluatorRef alongside built-in evaluators in an agent regression run. Also re-exports RubricDimension, GeneratedEvaluatorRef, build_sources, and load_evals_config from agent_framework.foundry (both the lazy runtime shim and the type stub) so the rubric samples can import everything from a single namespace; the auto-generate sample was previously broken because the shim was missing build_sources / load_evals_config. Updates the foundry-evals README with a chooser entry for the two rubric paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(foundry-evals): remove rubric creation flows; keep consumption only Reframes agent-framework as a pure consumer of Foundry rubric evaluators: scoring against rubrics that already exist (authored in the Foundry portal or via the dedicated SDK / REST surface) instead of creating them from the SDK. Removed creation surface area: - FoundryEvals.generate_rubric (auto-generate path) and create_rubric_evaluator (manual path), plus all _GenerationSdkTypes / _ManualRubricSdkTypes / _to_sdk_dimensions / _coalesce_generation_sources / _to_sdk_source / _poll_generation_job / _generation_job_to_ref / _evaluator_version_to_ref / _get_beta_evaluators / _import_*_sdk_types helpers. - EvalGenerationSource (the input source discriminator), RubricDimension (the input dimension type), agent_as_eval_source / workflow_as_eval_source / _detect_hosted_foundry_agent helpers, and the YAML-config loader (_evals_config.py with RubricGenerationSpec / RubricSourceSpec / parse_evals_config / load_evals_config / build_sources). - BaseAgent.as_eval_source / Workflow.as_eval_source plus the _render_agent_dossier / _render_workflow_dossier helpers in core. These existed only to feed the now-removed generation pipeline. - Samples evaluate_with_generated_rubric_sample.py, evaluate_with_manual_rubric_sample.py, and evaluators.yaml. Replaced with a short README section showing how to reference an existing rubric evaluator via GeneratedEvaluatorRef. Kept (consumption surface): - GeneratedEvaluatorRef, slimmed to (name, version, display_name). Still accepted alongside built-in evaluator strings in FoundryEvals(evaluators=[...]). Versionless refs still warn. - RubricScore on EvalScoreResult.dimensions plus EvalResults.assert_dimension_score_at_least for per-dimension CI gates. - _parse_dimension_entries / _extract_rubric_scores output parsing (both canonical dimension_scores and the legacy rubric_scores key). Tests: 160/160 foundry unit tests and 71/71 core local-eval tests pass; pyright is clean across changed files. The pre-existing tests/core/test_telemetry.py::test_detect_hosted_fallback_import_error failure is unrelated and reproduces on the prior commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * samples(foundry-evals): add evaluate_with_rubric_sample Adds a runnable end-to-end sample showing how to consume a pre-existing rubric evaluator created in Foundry: reference it with GeneratedEvaluatorRef(name, version), mix it with built-in evaluators in FoundryEvals, and gate CI with assert_dimension_score_at_least on a specific dimension. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(foundry-evals): satisfy mypy on _fetch_output_items mypy infers OutputItemListResponse.sample as dict[str, object] | None while pyright correctly infers the typed Sample model. Cast to Any so both type checkers accept the attribute access pattern, rename the local to avoid shadowing the inner-loop sample binding, and drop the now-stale pyright suppressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(foundry-evals): drop unpublished rubric-evaluators learn.microsoft.com link The Adaptive Evals authoring docs are not yet published on Microsoft Learn, so the link 404s. Keep the descriptive text without the broken hyperlink; we can re-add it once the docs ship. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(foundry-evals): hoist repeated local imports to module top Per code review feedback (eavanvalkenburg): the test file repeated 'from agent_framework_foundry._foundry_evals import ...' inside 22 test bodies and 'from agent_framework_foundry import GeneratedEvaluatorRef' inside 8 more. Move all of them to the existing top-level imports; the symbols are the same across tests and the local imports were redundant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ben Thomas ·
2026-06-01 23:01:56 +00:00 -
Python: Fix core observability unsafe serialization of function-call arguments containing dataclass/framework objects (#6026)
* fix: safely serialize function-call arguments in core observability Apply make_json_safe() to content.arguments in _to_otel_part() before building the otel message dict, so that dataclass/framework payloads (e.g. workflow request_info events) do not cause a TypeError when _capture_messages() calls json.dumps(). Lift make_json_safe() into agent_framework._serialization (no new external deps — dataclasses/datetime only) so the core observability path can use it without a dependency on the ag-ui adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(core): safely serialize workflow request_info payloads in observability (#5733) - Add make_json_safe() helper to recursively convert non-serializable objects - Use make_json_safe() in _to_otel_part() for function_call arguments - Fix CustomPayload test class to use @dataclass (resolves B903 lint error) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(serialization): guard callability and normalize dict keys in make_json_safe (#5733) - Use callable(getattr(obj, method, None)) instead of hasattr() so that non-callable attributes named model_dump/to_dict/dict do not raise TypeError at runtime. - Wrap each call in try/except TypeError to handle callables with mandatory arguments gracefully. - Convert dict keys to str() so that non-string keys (e.g. datetime, int) cannot cause json.dumps to raise TypeError. - Add regression tests for both scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address observability serialization review feedback --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evan Mattson ·
2026-06-01 21:41:52 +00:00 -
Python: refresh dev dependencies and validate runtime bounds (#6238)
Updates third-party dev dependencies across the Python workspace and validates that all runtime dependency bounds still hold at both ends. Dev dependency bumps (root, lab, declarative, durabletask): - uv 0.11.6 -> 0.11.17, ruff 0.15.8 -> 0.15.15, pytest-asyncio 1.3.0 -> 1.4.0, mcp 1.27.0 -> 1.27.2, azure-monitor-opentelemetry 1.8.7 -> 1.8.8, poethepoet 0.42.1 -> 0.46.0, prek 0.3.9 -> 0.4.3, types-python-dateutil and types-PyYaml stub bumps. - Transitive Dependabot items swept via lock: idna 3.11 -> 3.17, pip 26.0.1 -> 26.1.2. Deliberately excluded: - opentelemetry-sdk stays 1.40.0: azure-monitor-opentelemetry (incl. 1.8.8) hard-pins opentelemetry-sdk==1.40. - mypy stays 1.20.0 and pyright stays 1.1.408: the 2.1.0 / 1.1.409 bumps introduce new diagnostics that fail type checking and need dedicated PRs. - rich kept as a range: agentlightning (lab[lightning]) forces rich==13.9.4. Code/formatting changes driven by the ruff upgrade: - devui lifespan now uses try/finally so shutdown cleanup always runs (ruff RUF075). - Removed unused TYPE_CHECKING imports in core and foundry flagged by ruff 0.15.15. - Reapplied ruff 0.15.15 formatting to the files it changed. Validation: validate-dependency-bounds-test "*" passes (31/31 lower + 31/31 upper); typing 62/62; lint 31/31; devui tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-06-01 17:53:56 +00:00 -
Python: Add background agent support to harness agent (#6155)
* Add background agent support to harness agent * Address PR comments
westey ·
2026-06-01 17:20:39 +00:00 -
Python: coalesce code interpreter history chunks (#5801)
* fix: coalesce code interpreter history chunks * fix: narrow content item list types * fix: remove redundant content list casts
Yufeng He ·
2026-06-01 13:26:20 +00:00 -
Python: consolidate MCP reliability fixes (#6145)
* Python: consolidate MCP reliability fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix MCP cleanup and metadata typing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Satisfy MCP metadata mypy typing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Pyright metadata mapping type Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-29 07:21:14 +00:00 -
Python: Adding AgentFileStore and FileAccessProvider to support file access operations. (#6099)
* Adding AgentFileStore and FileAccessProvider to support file ased operations for agents. * Address PR review feedback on FileAccessProvider - Probe symlinks on the unresolved candidate path so in-root symlinks cannot silently pass and out-of-root symlinks surface the correct error message. - Validate matching_lines elements in FileSearchResult.from_dict and raise a clean ValueError for non-mapping entries. - Cap search regex pattern length (256 chars) via a new _compile_search_regex helper to mitigate ReDoS, and surface the cap in the file_access_search_files tool description. - Skip non-UTF-8 files during filesystem search instead of aborting the entire directory walk. - Replace the module-scope trailing string in the data-processing sample with comments to avoid Ruff B018. - Remove the checked-in working/region_totals.md sample artifact so the save flow works from a clean checkout. - Expand the Windows stdout reconfiguration comment in task_runner.py for clarity. - Add tests for invalid/oversize regex, non-UTF-8 file search, and in-root symlink rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mypy redundant-cast in FileSearchResult.from_dict Use cast(list[object], ...) instead of cast(list[Any], ...) so the cast represents a real type change (lists are invariant) and is no longer flagged by mypy as redundant, while still satisfying pyright's reportUnknownVariableType. Matches the existing pattern in _memory.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tighten path normalization and directory resolution in FileAccess - _normalize_relative_path now strips surrounding whitespace up front so leading/trailing spaces never leak into file segments, and rejects trailing path separators for file paths so 'foo/' is no longer silently coerced to 'foo'. - FileSystemAgentFileStore._resolve_safe_directory_path normalizes with is_directory=True and maps an empty normalized result to the root. This matches InMemoryAgentFileStore so whitespace-only directory inputs resolve to the root instead of raising. - Added tests for whitespace stripping, trailing-separator rejection, and whitespace-only directory listing on the filesystem store. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden FileAccess search and atomic save in store API - Add wall-clock timeout (10s) around regex scans so a pathological pattern (e.g. `(a+)+`) below the length cap cannot stall the event loop. - Offload the InMemoryAgentFileStore regex scan to a worker thread, matching the filesystem store. - Fail closed when `Path.is_symlink` raises during the safe-path probe so a permission error cannot silently bypass the symlink/reparse-point rejection. - Add `overwrite: bool = True` to `AgentFileStore.write_file`; the in-memory store performs the check under the existing lock and the filesystem store uses `open(mode='x')` so concurrent callers cannot race past `overwrite=False`. - `file_access_save_file` now relies on the atomic store call instead of a separate `file_exists` round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python 3.10 timeout handling and add directory arg to list/search tools - Catch asyncio.TimeoutError in _run_search_with_timeout. In Python 3.10 asyncio.wait_for raises asyncio.exceptions.TimeoutError, which is distinct from the builtin TimeoutError (the two were unified in 3.11). Catching the asyncio alias works on every supported version. - Add an optional directory parameter to file_access_list_files and file_access_search_files so agents can enumerate / scope searches to nested folders, not just the store root. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address FileAccess review feedback: case, errors, signal, TOCTOU - InMemoryAgentFileStore now stores (display_name, content) so list_files and search_files return the original-case names callers wrote, matching the behaviour of FileSystemAgentFileStore on case-preserving filesystems and removing the silent in-memory vs. on-disk contract divergence. - FileSystemAgentFileStore.read_file raises ValueError instead of letting UnicodeDecodeError bubble for binary / non-UTF-8 input, restoring symmetry with search_files (which still skips) and giving the tool layer a recoverable type to translate. - Tool wrappers now catch ValueError and OSError around every operation and surface them as readable strings, so 'you used ..' and 'the file already exists' are both reported to the model the same way instead of the former crashing out as an unhandled exception. - _search_files_sync logs per skipped non-UTF-8 file at WARNING and an aggregate INFO summary so operators can distinguish 'no matches' from 'half the corpus was unreadable'. - FileSystemAgentFileStore softens its docstrings to acknowledge the inherent probe-then-open TOCTOU window. On POSIX both read and write now pass O_NOFOLLOW so the kernel refuses if the leaf segment becomes a symlink between the probe and the open. Windows has no equivalent flag; the limitation is documented. - Tests cover: case preservation on list/search, ValueError on non-UTF-8 read at the store and tool layer, tool-layer string responses for path-traversal and oversized-regex inputs, search-skip log output, symlink rejection on delete/search/list, and symlinked intermediate directory rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address FileAccess nit comments: docstrings, enumerate, opt-in delete approval - Expand FileSearchMatch/FileSearchResult.to_dict docstrings to explain why the override is needed (__slots__ defeats the mixin's __dict__ iteration) and why exclude/exclude_none are accepted-but-ignored (mixin signature compatibility for callers like to_json). - Use enumerate(lines, start=1) in _search_file_content so the +1 below is no longer needed; rename loop variable to line_number for clarity. - Add opt-in require_delete_approval: bool = False on FileAccessProvider. When True, file_access_delete_file is registered with approval_mode 'always_require' so the host must approve every delete. Default False preserves current behaviour and matches the .NET reference, but deployments that want a safer-by-default posture can enable it. - Add tests covering both delete approval modes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * FileAccess: require delete approval by default Flip the default for FileAccessProvider(require_delete_approval=...) from False to True so destructive deletes are gated by host approval out of the box. Callers that want the previous autonomous behaviour (which matches the .NET reference) can pass require_delete_approval=False. Tests updated accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fixing linkinspector by installing Chrome for puppeteer first. --------- Co-authored-by: Ben Thomas <25218250+alliscode@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ben Thomas ·
2026-05-28 20:09:50 +00:00 -
Tao Chen ·
2026-05-28 20:03:46 +00:00 -
Python: [Breaking] Refactor Skill API to async resource and script lookup (#6135)
Port of .NET commit
08541ee5a9. Replace property-based Skill.content/resources/scripts with async by-name lookup methods: - content property -> async get_content() -> str - resources property -> async get_resource(name) -> SkillResource | None - scripts property -> async get_script(name) -> SkillScript | None SkillsProvider now always includes all three tools (load_skill, read_skill_resource, run_skill_script) and both instruction blocks regardless of whether any skills have resources or scripts. ClassSkill retains resources/scripts properties as overridable hooks for subclass reflection-based discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>semenshi-m ·
2026-05-28 15:54:20 +00:00 -
Python: Align c# and python TodoProvider tool names (#6107)
* Align c# and python TodoProvider tool names * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Address PR review: remove __slots__ and add typed schemas for tool params - Remove __slots__ from TodoItem, TodoInput, and TodoCompleteInput classes (not needed for low-instance-count objects and hinders dev scenarios) - Add _TodoAddItemSchema and _TodoCompleteItemSchema TypedDicts to provide proper JSON schema for todos_add and todos_complete tool parameters - Use typing_extensions for Python 3.10 compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
westey ·
2026-05-28 08:40:13 +00:00 -
Python: Add a HarnessAgent with available features and sample (#6041)
* Add a HarnessAgent with available features and sample * Fix formatting * Address PR comments and fix mypy error * Add web search support to HarnessAgent * Fix build warning * Apply suggestions from code review Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> * Address PR comments * Address PR comments * Address further PR comments. * Fix markdown broken link --------- Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
westey ·
2026-05-27 14:54:00 +01:00 -
Python: Add a BackgroundAgentsProvider for python (#6069)
* Add a BackgroundAgentsProvider for python * Address PR comments and fix linting warnings * Address PR comment
westey ·
2026-05-27 09:12:01 +00:00 -
Python: Align ModeProvider tool names and instructions (#6071)
* Align ModeProvider tool names and instructions * Address PR comments
westey ·
2026-05-26 14:37:34 +00:00 -
Python: fix(core): point @experimental warnings at user code, not stdlib internals (#5996)
* fix(core): point @experimental warnings at user code, not stdlib internals Previously the wrappers installed by @experimental called warnings.warn with a fixed stacklevel=3. ABCMeta inserts an extra abc.__new__ frame when an experimental ABC is subclassed, so the warning landed inside abc.py (or <frozen abc>:106 on modern CPython) instead of the user's class Sub(...) line. Resolve the user frame by walking inspect.currentframe(), skipping frames whose module name is abc/functools/typing/contextlib (or submodules), then emit via warnings.warn_explicit so the recorded filename/lineno point at user code. Falls back to warnings.warn with stacklevel=2 if no user frame is found. Module-name matching is used because frozen stdlib modules report '<frozen abc>' as their filename. Also install a one-line warnings.formatwarning specifically for FeatureStageWarning so 'file:line: ExperimentalWarning: [ID] Name ...' prints without the secondary source-snippet line. Other categories delegate to the stdlib default formatter unchanged. Added a regression test that subclasses an @experimental ABC inside warnings.catch_warnings and asserts the recorded filename equals the test file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(core): address review feedback on @experimental warning fix - Make _install_feature_stage_formatter idempotent: tag the installed formatter with a marker attribute and short-circuit re-installation, so re-imports/reloads don't wrap the formatter on top of itself. Also expose the previous formatter via __wrapped__ for restoration. - Avoid leaking frame references in _resolve_user_frame: capture data into plain locals inside try and del frame/candidate in finally, per CPython's guidance on inspect.currentframe usage. - Drop redundant _WARNED_FEATURES.clear() in the new ABC subclass test (the autouse fixture already handles it). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * changed query for foundry web search test --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-22 12:07:10 +00:00 -
Python: Prevent duplicate system instructions in Python telemetry (#5981)
* Initial plan * Fix duplicated system instructions in Python telemetry * Clarify telemetry message filtering * test: cover separate and in-history system messages * Clarify observability message logging split * Simplify observability logging serialization * Harden observability regression test * Reuse observability span message serialization * Clarify observability logging loops * Polish observability message serialization * Tighten observability zip checks * Refactor observability message capture loop * Fix telemetry logging for separate system instructions * Refine observability OTEL message typing * Restore prepended-instruction logging path in _capture_messages * Revert logging change in _capture_messages; keep chat-history-only logging --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Copilot ·
2026-05-21 19:59:06 +00:00 -
[BREAKING] Python: Enable instrumentation by default (#5865)
* Enable instrumentation by default * Update samples * Optimization when span is not recording * Address Copilot comments * Revert uv.lock * Add warning * Formatting * Fix mypy * Add disable_instrumentation() with sticky user-intent semantics Add a public disable_instrumentation() entry point so users can explicitly opt out of Agent Framework telemetry, with a sticky-disable flag that makes the user's intent "leading" — no framework code path (foundry's configure_azure_monitor, configure_otel_providers, enable_instrumentation, enable_sensitive_telemetry, or direct OBSERVABILITY_SETTINGS.enable_* writes) can re-enable instrumentation until the user explicitly clears the disable with enable_instrumentation(force=True) / enable_sensitive_telemetry(force=True). Also addresses the two remaining unresolved review threads on the PR: 1. test_observability_settings_defaults_instrumentation_true pins the new "ENABLE_INSTRUMENTATION defaults to True when env unset" behavior. 2. test_enable_instrumentation_reads_env_sensitive_data restores coverage for the post-import load_dotenv() fallback path. Implementation: - ObservabilitySettings.enable_instrumentation / enable_sensitive_data become properties backed by _enable_*. While _user_disabled is True, the getters return False and the setters drop True writes (defense in depth so third- party writes can't subvert the disable). - Public is_user_disabled read-only property lets integrations (e.g. foundry's configure_azure_monitor) cheaply check the disable state without poking at privates. - enable_instrumentation() and enable_sensitive_telemetry() short-circuit with an info log when disabled; gain a force=True kwarg that clears the disable. - configure_otel_providers() still creates providers / exporters / views so a later force-enable can use them, but logs an info message when called while disabled. - Foundry's FoundryChatClient.configure_azure_monitor and FoundryAgent.configure_azure_monitor early-return when the user has disabled, so Azure Monitor's global providers aren't installed unnecessarily. Tests: 11 new tests covering default-on, env re-read at call time, sticky behavior against each re-enable surface (enable_instrumentation, enable_sensitive_telemetry, configure_otel_providers, direct attribute writes), force=True override, re-arming the disable, and the __all__ export. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: document disable_instrumentation() and force=True paths Add a "Disabling instrumentation" section to the observability sample README that walks through: - The distinction between the ENABLE_INSTRUMENTATION env var (initial, non-sticky) and disable_instrumentation() (process-wide, sticky). - Why the sticky semantics matter: framework integrations like FoundryChatClient.configure_azure_monitor() can call enable_instrumentation() as part of their setup, and the user's opt-out needs to win. - All five surfaces guarded by the sticky disable (property reads, public enable functions, configure_otel_providers, direct attribute writes, is_user_disabled-aware integrations). - The force=True escape hatch on both enable_instrumentation() and enable_sensitive_telemetry(). - How third-party integrations should consult OBSERVABILITY_SETTINGS.is_user_disabled. - The limits of the disable (does not tear down existing providers / in-flight spans / third-party instrumentation, does not persist across processes). Cross-links the new section from the ENABLE_INSTRUMENTATION row in the env vars table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: soften disable_instrumentation() overclaim about telemetry guarantees Replace 'no telemetry will be emitted no matter what' (which is too strong, since callers can still pass force=True or mutate private attributes) with language framing the disable as a user-intent contract that library and framework code is expected to honor: the framework actively short-circuits the public enable paths, force=True and private-attribute writes are acknowledged as out-of-contract escape hatches that integrations should not use on the user's behalf. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: correct observability Dependencies section - opentelemetry-sdk is no longer a hard dependency; it is lazily imported by create_resource(), create_metric_views(), and configure_otel_providers() with a clear ImportError when missing. Day-to-day instrumentation works with opentelemetry-api alone provided some other component configures the global OpenTelemetry providers (Azure Monitor, an APM agent, application bootstrap, etc.). - opentelemetry-semantic-conventions-ai is no longer used anywhere in the source; remove it from the listed dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: replace stale observability migration guide with current PR's only relevant migration The old guide documented the move away from setup_observability(otlp_endpoint=...) which was an earlier-release API change unrelated to this PR and stale enough that it's more confusing than helpful at this point. Replace it with a short note on the single migration this PR introduces: callers of enable_instrumentation(enable_sensitive_data=True) should switch to enable_sensitive_telemetry(). Cross-link to the Disabling instrumentation section for the rare 'force on without enabling sensitive data' use case where enable_instrumentation() still applies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tao Chen ·
2026-05-20 11:52:08 +00:00 -
Python: Skip MCP prompt loading when unsupported (#5370)
* Python: Skip MCP prompt loading when unsupported * Fix MCP pagination pyright checks * Simplify MCP support flag checks
Baidar ·
2026-05-20 11:50:26 +00:00 -
Python: Record actual served model from Azure OpenAI (#5910)
* Record actual served model as response model for Azure OpenAI * Formatting * Fix tests * Fix pipeline error * Comments * Address review: surface served model via ChatResponse.model Apply blocking review feedback from PR #5910: - Use ChatResponse.model / ChatResponseUpdate.model as the source of truth for the Azure x-ms-served-model header value, instead of stashing it in additional_properties and overriding it again in observability. Observability already reads response.model; the chat client now overwrites it post-parse when the served-model header is present. Empirically the Azure Responses API returns the deployment alias in body.model and the actual snapshot (e.g. gpt-5-nano-2025-08-07) in this header. - Move the AZURE_OPENAI_SERVED_MODEL_HEADER constant out of observability.py and into RawOpenAIChatClient (as the SERVED_MODEL_HEADER ClassVar). The header is Azure-OpenAI-Responses-API-specific so observability does not need to know about it. - Revert the streaming text_format path to client.responses.stream(...) and drop the _pydantic_model_to_text_format_param helper. That helper imported from openai.lib._parsing._responses (a private SDK path) and the swap to responses.create(stream=True) dropped client-side output_parsed for structured-output streaming. The streaming-with-text_format path is the only one that does not surface the served-model header - documented inline. - Wrap the raw streaming responses in async with so the underlying socket closes deterministically (continuation_token retrieve + create paths). - Fix the empty-string / whitespace-only header at the source by stripping in _extract_served_model and returning None when nothing remains. - Revert unrelated formatting-only churn in _skills.py and test_mcp.py. - Update unit tests to assert against chat_response.model / update.model and add an aggregated streaming assertion plus a pin that the streaming-with-text_format path does not get the header. Verified end-to-end against Azure OpenAI Responses API: deployment alias gpt-5-nano now reports gpt-5-nano-2025-08-07 as ChatResponse.model in both the non-streaming and streaming paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: preserve streaming structured output finalization Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639 Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com> * refactor: name streaming response finalizer Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639 Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com> * fix: capture streaming response format after prepare Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639 Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com> * refactor: clarify streaming response format capture Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639 Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com> * test: use public API for streaming structured output Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/f62076ef-558d-49e8-8fe2-f38d527c9639 Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com> * Inline the served-model header override at its two call sites The `_apply_served_model_header` helper was a 1-line wrapper around `_extract_served_model`. Inlining the `if served_model is not None: ...` matches the pattern already used in the streaming paths and folds the explanatory docstring onto `_extract_served_model` (which is now the single place that knows about the header). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Tao Chen ·
2026-05-19 06:38:53 +00:00 -
Python: Improve the handling of intermediate outputs for workflows and orchestrations (#5623)
* Improve the handling of intermediate outputs for workflows and orchestrations * Address PR review feedback on intermediate output forwarding - Switch workflow.as_agent() forwarding to an explicit allowlist of {output, intermediate, data, request_info} so orchestration-internal events (group_chat, handoff_sent, magentic_orchestrator) stay inside the workflow instead of leaking into agent responses via str(data) coercion. - Stop raising on intermediate AgentResponseUpdate in non-streaming run(); surface the partial as a Message with text_reasoning content. The defensive raise still applies to terminal output events, where Update payloads would corrupt message ordering. - Extend the DevUI workflow-event mapper so intermediate yields wrapping plain strings, Messages, and list[Message] render as visible output items instead of generic completed-trace events. - Add orchestration coverage for GroupChat, Handoff, and Magentic builders (default vs intermediate_outputs=True; structural where end-to-end is heavy). * Lift output-designation policy into a value type Replace the ``Workflow._output_executors`` list and the ``RunnerContext.should_label_as_intermediate`` Protocol method with a single immutable ``OutputDesignation`` value type owned by ``Workflow``. Thread the designation as a parameter through the existing call chain (Runner -> EdgeRunner -> Executor -> WorkflowContext) so ``yield_output`` consults the threaded snapshot directly rather than calling back into the runner context. Removes the ``InProcRunnerContext._workflow`` back-reference and the ``WorkflowBuilder.build()`` assignment that wired it up. Adds the public predicate ``Workflow.is_terminal_executor(executor_id)`` for external observers; ``OutputDesignation`` itself stays package-internal. Key decisions - ``OutputDesignation.designated`` is ``frozenset[str] | None`` -- ``None`` preserves legacy "every yield is type='output'" behavior, any frozenset (including empty) opts into strict mode. The ``DeprecationWarning`` for legacy mode at build time is unchanged. - ``output_designation`` is an optional parameter on ``Runner``, ``EdgeRunner.send_message``, ``EdgeRunner._execute_on_target``, ``Executor.execute``, ``Executor._create_context_for_handler``, and ``WorkflowContext.__init__``. Each defaults to legacy ``OutputDesignation()`` so direct callers (Azure Functions ``CapturingRunnerContext``, ``test_runner`` recording fixtures) keep working without ceremony. - The workflow-level filter in ``_run_core`` reads ``self._output_designation`` live, preserving today's semantics where mutating the designation after build still affects subsequent runs (used by two existing tests). - ``Workflow.to_dict()`` continues to emit ``"output_executors": list[str] | None`` (sorted from the frozenset). Checkpoint format unchanged. Files changed - _workflow.py: add ``OutputDesignation`` dataclass; replace ``_output_executors`` with ``_output_designation``; add ``is_terminal_executor``; delete ``_should_yield_output_event``. - _runner_context.py: drop ``should_label_as_intermediate`` Protocol method and ``InProcRunnerContext`` impl; drop ``_workflow`` back-reference. - _workflow_builder.py: remove ``context._workflow = workflow`` assignment. - _runner.py, _edge_runner.py, _executor.py, _workflow_context.py: thread ``output_designation`` parameter through the call chain. - tests/workflow/test_output_designation.py (new): three-state coverage of the value type plus the public predicate delegation. - tests/workflow/test_workflow_builder.py, test_validation.py, test_workflow.py, test_runner.py and orchestrations/tests/test_orchestration_intermediate_vs_terminal.py: switch probes from ``_output_executors`` set checks to ``get_output_executors`` / ``is_terminal_executor``; update two post-build mutation tests to set ``_output_designation`` instead. Verification - core/tests/workflow/, orchestrations/tests/, azurefunctions/tests/: 1119 passed, 42 skipped, 2 xfailed. - ``uv run poe lint``: clean. - ``uv run poe typing``: only the pre-existing ``_AGENT_FORWARDED_EVENT_TYPES`` pyright warning from394bcd607remains. Notes for next iteration - The builder's own ``_output_executors`` attribute (``list[Executor | SupportsAgentRun]``) is intentionally untouched; the issue scoped the rename to the workflow attribute. - Adjacent review candidates (twin ``WorkflowAgent`` translators, ``_AGENT_FORWARDED_EVENT_TYPES`` kind classifier, ``_event_origin_context`` ContextVar removal, ``WorkflowEvent`` ADT split, legacy-mode removal) remain out of scope. * Add explicit workflow output designation Key decisions - Extend the internal OutputDesignation value type from terminal-only membership to output/intermediate/hidden classification. Legacy mode remains outputs=None, so workflows built without output_executors or intermediate_executors still label every yield_output as type='output'. - WorkflowBuilder now accepts intermediate_executors. Providing either designation enters explicit mode; output executors emit output, intermediate executors emit intermediate, and unlisted yield_output payloads are hidden from caller-facing events while remaining in executor_completed data. - Empty explicit designation, duplicate entries, overlaps, unknown executors, and designated executors without workflow output annotations fail build validation. Existing orchestration builders pass intermediate-capable participants through intermediate_executors to preserve current intermediate_outputs behavior until participant-oriented designation lands. Files changed - packages/core/agent_framework/_workflows/_workflow.py, _workflow_builder.py, _workflow_context.py, _validation.py, _events.py - packages/core/tests/workflow/test_output_designation.py, test_output_executors_contract.py, test_strict_mode_event_labeling.py, test_validation.py, test_workflow.py, test_workflow_agent_intermediate.py - packages/orchestrations/agent_framework_orchestrations/_sequential.py, _concurrent.py, _group_chat.py, _magentic.py - packages/core/AGENTS.md Verification - uv run pytest packages/core/tests/workflow packages/orchestrations/tests packages/devui/tests/devui/test_mapper.py -q - uv run pytest packages/azurefunctions/tests -q - uv run poe lint - uv run poe typing fails only on pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error. Notes for next iteration - issues/03-core-workflow-explicit-designation.md was moved to issues/done but issues/ remains untracked and intentionally excluded from this commit. - Slice 4 should tighten workflow.as_agent() mapping for hidden emissions and streaming-only update payloads; Slice 5 should replace orchestration intermediate_outputs with participant-oriented designation. * Tighten workflow-as-agent output mapping Key decisions - Treat AgentResponseUpdate as a streaming-only payload across the workflow.as_agent() adapter, so non-streaming agent runs now reject both terminal output and intermediate workflow events carrying updates. - Keep streaming classification behavior explicit: terminal update payloads remain normal text content, while intermediate update payloads are rewritten to text_reasoning content. - Add explicit-mode coverage proving hidden yield_output emissions do not appear in non-streaming AgentResponse messages or streaming AgentResponseUpdate chunks. Files changed - packages/core/agent_framework/_workflows/_agent.py - packages/core/tests/workflow/test_workflow_agent_intermediate.py Verification - uv run pytest packages/core/tests/workflow/test_workflow_agent_intermediate.py -q - uv run pytest packages/core/tests/workflow/test_workflow_agent.py packages/core/tests/workflow/test_workflow_agent_intermediate.py -q - uv run pytest packages/core/tests/workflow packages/orchestrations/tests packages/devui/tests/devui/test_mapper.py -q - uv run poe lint - uv run poe typing fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error. Blockers or notes for next iteration - issues/04-workflow-as-agent-output-mapping.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit. - Slice 5 should replace orchestration intermediate_outputs with participant-oriented designation. * Add orchestration participant output designation Key decisions - Replace orchestration intermediate_outputs with participant-oriented output_participants and intermediate_participants across Sequential, Concurrent, GroupChat, Magentic, and Handoff builders. - Keep synthetic final executors terminal by default for Concurrent, GroupChat, and Magentic; keep Sequential's final participant terminal by default; keep Handoff participants terminal by default. - Centralize participant designation validation for empty explicit designation, duplicates, overlaps, and unknown participants, then map validated participants to workflow output/intermediate executors. Files changed - packages/orchestrations/agent_framework_orchestrations/_participant_designation.py - packages/orchestrations/agent_framework_orchestrations/_sequential.py - packages/orchestrations/agent_framework_orchestrations/_concurrent.py - packages/orchestrations/agent_framework_orchestrations/_group_chat.py - packages/orchestrations/agent_framework_orchestrations/_magentic.py - packages/orchestrations/agent_framework_orchestrations/_handoff.py - packages/orchestrations/tests/test_orchestration_intermediate_vs_terminal.py - packages/orchestrations/tests/test_magentic.py Blockers or notes for next iteration - issues/05-orchestration-participant-designation.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit. - Slice 7 should migrate samples and docs away from intermediate_outputs to the new participant designation API. - uv run poe typing still fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error. * Migrate samples to explicit output designation Key decisions - Replace sample usage of the removed orchestration intermediate_outputs boolean with participant-oriented intermediate_participants designation. - Update raw workflow guidance to show output_executors together with intermediate_executors, and document that unlisted yields are hidden in explicit designation mode. - Keep orchestration final outputs terminal while streaming designated participant responses as intermediate progress, including workflow.as_agent() samples where intermediates map to text_reasoning content. - Refresh workflow and orchestration README guidance plus the changelog reference so public docs no longer point users at intermediate_outputs. Files changed - CHANGELOG.md - packages/orchestrations/README.md - samples/README.md - samples/03-workflows/README.md - samples/03-workflows/control-flow/intermediate_vs_terminal_outputs.py - samples/03-workflows/orchestrations/README.md - samples/03-workflows/orchestrations/group_chat_agent_manager.py - samples/03-workflows/orchestrations/group_chat_philosophical_debate.py - samples/03-workflows/orchestrations/group_chat_simple_selector.py - samples/03-workflows/orchestrations/magentic.py - samples/03-workflows/orchestrations/magentic_human_plan_review.py - samples/03-workflows/orchestrations/sequential_chain_only_agent_responses.py - samples/03-workflows/agents/group_chat_workflow_as_agent.py - samples/03-workflows/agents/magentic_workflow_as_agent.py - samples/03-workflows/agents/sequential_workflow_as_agent.py - samples/semantic-kernel-migration/orchestrations/group_chat.py - samples/semantic-kernel-migration/orchestrations/magentic.py Blockers or notes for next iteration - issues/07-samples-and-docs-explicit-output-designation.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit. - issues/06-devui-intermediate-event-rendering.md remains present and appears already satisfied by existing DevUI mapper/tests from the prior implementation slice. - PRD-explicit-workflow-output-designation.md remains untracked and intentionally excluded from this commit. * Render DevUI intermediate workflow outputs Key decisions - Preserve workflow output designation metadata on visible DevUI output messages and text deltas so intermediate/data emissions remain distinguishable from terminal output. - Render intermediate workflow message items in the execution timeline using executor metadata, while excluding them from the final workflow result aggregation. - Keep terminal output message rendering unchanged and retain legacy data events on the intermediate compatibility path. Files changed - packages/devui/agent_framework_devui/_mapper.py - packages/devui/frontend/src/components/features/workflow/execution-timeline.tsx - packages/devui/frontend/src/components/features/workflow/workflow-view.tsx - packages/devui/frontend/src/types/openai.ts - packages/devui/tests/devui/test_mapper.py Blockers or notes for next iteration - issues/06-devui-intermediate-event-rendering.md was moved to issues/done/ but issues/ remains untracked and intentionally excluded from this commit. - PRD-explicit-workflow-output-designation.md remains untracked and intentionally excluded from this commit. - uv run poe typing still fails only on the pre-existing packages/core/agent_framework/_workflows/_agent.py _AGENT_FORWARDED_EVENT_TYPES private-use pyright error. * Fix mypy * Clarify orchestration participant output config * Rename participant output kwargs for clarity output_participants -> final_output_from, intermediate_participants -> intermediate_output_from. The old names read like categories of participant; the new names make it clear the kwarg designates which participants' outputs surface as final vs. intermediate events. * Rename core workflow output kwargs with deprecation shim Adds final_output_from / intermediate_output_from as canonical kwargs on Workflow and WorkflowBuilder. Old output_executors / intermediate_executors kwargs continue to work but emit DeprecationWarning via a shared coalesce helper that also rejects supplying both. Wire-format keys in to_dict() stay as output_executors / intermediate_executors so checkpoint compatibility is preserved. Internal call sites in orchestrations and samples updated to the new names so users following sample code learn the canonical vocabulary; legacy callers still work with a one-shot warning. * Suppress pyright reportPrivateUsage on cross-module sentinel import * Update docstrings * Propagate sub-workflow intermediate outputs, fix handoff/sequential intermediate-only designation, and shore up tests, sample, and docstrings around the intermediate output contract. * Add canonical workflow output_from selection Key decisions:\n- Make output_from the canonical workflow-output allow-list and keep output_executors/final_output_from as deprecated compatibility aliases.\n- Treat empty output_from/intermediate_output_from lists as explicit selections and keep validation responsible for empty, duplicate, overlap, and unknown selections.\n- Remove the branch-only public intermediate_executors WorkflowBuilder kwarg while preserving legacy wire keys in to_dict().\n\nFiles changed:\n- packages/core/agent_framework/_workflows/_workflow.py\n- packages/core/agent_framework/_workflows/_workflow_builder.py\n- packages/core/agent_framework/_workflows/_workflow_context.py\n- packages/core/agent_framework/_workflows/_agent.py\n- packages/core/agent_framework/_workflows/_agent_executor.py\n- packages/core/tests/workflow/* output-selection coverage updates\n- packages/core/AGENTS.md\n- issues/done/001-canonical-list-based-output-selection.md\n\nBlockers/notes:\n- Orchestration builders still pass final_output_from internally; follow-up issue 004 should migrate them to output_from.\n- Legacy omitted-selection behavior and explicit all/all_other literals are left for issues 002 and 003. * Add explicit all workflow output selection Key decisions: - Treat output_from='all' as an explicit workflow-output selection sentinel and expand it at build time to executors with declared workflow output types. - Keep omitted output selections in legacy all-output mode with a deprecation warning that names output_from and intermediate_output_from and points to output_from='all'. - Reject intermediate_output_from='all' at construction because the all-output literal is output-only for this issue. Files changed: - packages/core/agent_framework/_workflows/_workflow_builder.py - packages/core/tests/workflow/test_output_executors_contract.py - issues/done/002-explicit-all-output-and-legacy-migration.md Blockers/notes: - all_other intermediate-output selection remains for issue 003. - Workflow-as-agent/orchestration parity remains for issue 004. * Add all-other intermediate output selection Key decisions: - Treat intermediate_output_from='all_other' as an explicit intermediate-output selection sentinel and expand it at build time after the workflow graph is complete. - Expand all_other to output-capable executors not selected by output_from; omitted or empty output_from selects no workflow outputs, while output_from='all' leaves an empty intermediate selection. - Keep output_from='all_other' invalid so all_other remains intermediate-output-only and runtime classification still receives concrete executor-id sets. Files changed: - packages/core/agent_framework/_workflows/_workflow_builder.py - packages/core/tests/workflow/test_output_executors_contract.py - issues/done/003-all-other-intermediate-output-selection.md Blockers/notes: - Workflow-as-agent and orchestration parity remains for issue 004. - Full documentation updates remain for issue 005. * Add orchestration output selection parity Key decisions: - Expose output_from on sequential, concurrent, group chat, handoff, and magentic builders while keeping final_output_from as a deprecated compatibility alias. - Resolve orchestration participant selections through the same explicit rules as workflows: output_from='all', intermediate_output_from='all_other', hidden unselected participant payloads, and overlap/duplicate/unknown/invalid-literal validation. - Continue preserving documented orchestration defaults by always designating each pattern's terminal internal executor where applicable. Files changed: - packages/orchestrations/agent_framework_orchestrations/_participant_output_config.py - packages/orchestrations/agent_framework_orchestrations/_sequential.py - packages/orchestrations/agent_framework_orchestrations/_concurrent.py - packages/orchestrations/agent_framework_orchestrations/_group_chat.py - packages/orchestrations/agent_framework_orchestrations/_handoff.py - packages/orchestrations/agent_framework_orchestrations/_magentic.py - packages/orchestrations/agent_framework_orchestrations/_orchestration_request_info.py - packages/orchestrations/tests/test_orchestration_intermediate_vs_terminal.py - issues/done/004-workflow-as-agent-and-orchestration-parity.md Blockers/notes: - Full documentation and sample migration wording remains for issue 005. - Existing tests that intentionally use final_output_from now emit the new deprecation warning. * Document workflow output selection contract Key decisions: - Use Workflow Output and Intermediate Output as the developer-facing terms for selected caller-facing emissions. - Document output_from and intermediate_output_from as the canonical API, with output_from as an allow-list and unselected payloads hidden unless explicitly selected as intermediate. - Add scenario and invalid-selection tables for workflow and orchestration docs, including legacy omission warnings, output_from='all', intermediate_output_from='all_other', list selections, invalid literals, overlap, duplicates, unknown selections, and empty explicit selections. - Migrate samples away from final_output_from and output_executors except where compatibility aliases are explicitly documented. Files changed: - packages/core/AGENTS.md - packages/orchestrations/README.md - packages/orchestrations/agent_framework_orchestrations/_handoff.py - packages/orchestrations/agent_framework_orchestrations/_sequential.py - samples/03-workflows/README.md - samples/03-workflows/control-flow/intermediate_vs_terminal_outputs.py - samples/03-workflows/human-in-the-loop/agents_with_approval_requests.py - samples/03-workflows/orchestrations/README.md - samples/04-hosting/foundry-hosted-agents/responses/05_workflows/main.py - scripts/sample_validation/create_dynamic_workflow_executor.py - issues/done/005-document-output-selection-contract.md Blockers/notes: - Direct full Ruff on scripts/sample_validation/create_dynamic_workflow_executor.py still reports pre-existing docstring/print/line-length issues outside this docs migration; syntax-focused checks for changed files pass. - No remaining AFK issue files are present under issues/. * Latest updates * Typing fixes * CleanupEvan Mattson ·
2026-05-19 00:15:25 +00:00 -
Python: Parse YAML block scalars in SKILL.md frontmatter (#5863)
The frontmatter parser previously matched only single-line `key: value` pairs, so block scalar indicators (`|` literal, `>` folded, with chomping `-`/`+`) were silently truncated to the indicator character. Multi-line descriptions like `description: >\n ...` lost their content. Add `_parse_yaml_scalar_value()` which detects block scalar indicators, collects indented continuation lines, strips the common leading indentation, joins per scalar style (newlines for `|`, spaces for `>`), and applies chomping per the YAML 1.2 spec. Update `_extract_frontmatter()` to use the helper for unquoted values. Adds 15 unit tests covering literal/folded styles, all chomping variants, indentation handling, content containing colons, non-description fields, tab indentation, blank-line preservation, and a regression test for plain values. Fixes #5713. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SergeyMenshykh ·
2026-05-15 09:47:00 +00:00 -
Python: forward MCP tool call metadata (#5815)
* Python: forward MCP tool call metadata * fix: preserve MCP tool meta after prompt reload
Yufeng He ·
2026-05-14 21:50:39 +00:00 -
Python: Support list[str] arguments for file-based skill scripts (#5850)
Port of .NET PR #5475. Broadens the args type from dict[str, Any] | None to dict[str, Any] | list[str] | None across the skill script API surface, enabling CLI-style argv forwarding to subprocess scripts. Changes: - SkillScript.run(), InlineSkillScript.run(), FileSkillScript.run(): widen args type; InlineSkillScript rejects list with TypeError - FileSkillScript.parameters_schema: returns array-of-strings schema - FileSkill.content: appends <scripts> block with parameters_schema - SkillScriptRunner protocol: widen args type - SkillsProvider._run_skill_script: widen args type - run_skill_script tool schema: accept object, array, or null - subprocess_script_runner sample: accept list[str], reject dict - class_based_skill sample: fix missing SkillFrontmatter wrapper - Standardize 'folder' to 'directory' in docstrings (#5712) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SergeyMenshykh ·
2026-05-14 17:58:10 +00:00 -
[BREAKING] Python: Align file skill folder discovery with agentskills.io spec (#5807)
* Align Python FileSkillsSource with agentskills.io spec Update FileSkillsSource to scan spec-defined subdirectories instead of recursive rglob for resource and script discovery: - Resources: scan 'references/' and 'assets/' (was: entire skill tree) - Scripts: scan 'scripts/' (was: entire skill tree) - Add resource_directories and script_directories parameters for customization, with '.' root indicator for skill root files - Add directory validation: reject '..' traversal, absolute paths, empty names; normalize separators and deduplicate directories - Non-recursive scanning within each configured directory (top-level only) - Containment check validates files against target directory, not just skill root, for stronger path-traversal defense - Case-insensitive directory deduplication via os.path.normcase() - Cross-platform absolute path rejection in directory validation - Sort discovery results for stable ordering - Update SkillsProvider.from_paths() to pass new parameters through - Update all tests for new subdirectory-scoped discovery behavior Resolves #5711. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: tighten path validation and add containment guard - Narrow Windows absolute path check to proper drive-root pattern (re.match r'^[A-Za-z]:[/\\]') to avoid rejecting valid POSIX names - Add _is_path_within_directory guard before _has_symlink_in_path in both discovery methods to prevent ValueError on escaped paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Log warning on OSError during directory listing in skill discovery Address review comment: _discover_resource_files and _discover_script_files previously swallowed OSError silently when iterdir() failed. Now log a warning so permission errors and transient FS failures are visible instead of making resource/script directories silently disappear. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SergeyMenshykh ·
2026-05-14 10:28:22 +00:00 -
Python: Strip server-issued response item IDs under storage (#3295) (#5690)
Fixes microsoft/agent-framework#3295. When the OpenAI Responses chat client sends a request that carries previous_response_id / conversation_id / conversation, the server already has the prior turn's response items and rejects duplicates with "Duplicate item found with id fc_xxx". The chat client was re-sending them inline whenever the input messages still carried the items in additional_properties (workflow replay, history providers, etc.), which broke any tool-using agent with persistent history. Decisions: - Single chokepoint: _prepare_message_for_openai. When the resulting request uses service-side storage, drop function_call, reasoning, approval-request/response, and local-shell-call items from the wire input. Keep function_result with its call_id; the server pairs it to the prior function_call via that key. - function_result is preserved unconditionally except for the local-shell variant, which carries its own server-issued item id. - No public API change. Wire format change is subtractive and only on requests that would otherwise 400. - Re-pointed the strict-xfail in test_full_conversation.py from #4047 to #3295. Kept xfail because the test asserts executor-level session-id clearing, which is the defense-in-depth half tracked by 3295-03; this slice closes the wire-level half. Files: - python/packages/openai/agent_framework_openai/_chat_client.py: strip rule applied alongside the existing reasoning-item branch. - python/packages/openai/tests/openai/test_openai_chat_client.py: four new tests pin the contract (function_call, approval, local-shell-call stripped under storage; everything kept without storage). Updated pre-existing tests that exercised the storage-on path to either pass request_uses_service_side_storage=False explicitly or assert the new strip behavior. - python/packages/foundry/tests/foundry/test_foundry_chat_client.py: same explicit storage-off opt-in for the inherited test. - python/packages/core/tests/workflow/test_full_conversation.py: re-pointed xfail reason to #3295 and the executor-level follow-up. Notes for next iteration: - 3295-01 (HITL wire-format validation against live OpenAI/Foundry) was not run; it requires the user's API credentials. The PRD design is locked but the empirical confirmation is still pending. If script 3 fails on either provider, this slice may need to be revisited. - 3295-03 (clear service_session_id in AgentExecutor on full-history replay) remains open. After it lands the xfail in test_full_conversation.py can be removed. - pytest was not run in this iteration because uv-based pytest commands required interactive approval. Validation rests on careful reading; next iteration should run the openai + core test suites.
Evan Mattson ·
2026-05-13 22:09:04 +00:00 -
[Python] [Breaking] Extract skill spec metadata into SkillFrontmatter (#5775)
* Fix Skill docstring consistency and spelling - Add ClassSkill to Skill class docstring concrete implementations list - Normalize 'defence' to 'defense' for American English consistency - Remove extra blank line in InlineSkill docstring example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix E501 line-too-long lint error in test_skills.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix stale test section header to reflect SkillFrontmatter API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix metadata children overriding top-level frontmatter fields Scope YAML_KV_RE to column-0 keys only so indented children under metadata: are not mistakenly parsed as top-level fields. Add regression test and spec fields to sample SKILL.md files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SergeyMenshykh ·
2026-05-13 20:35:52 +00:00 -
Python: fix: prevent MCP message_handler deadlock on notification reload (#4866)
* fix(python): prevent MCP message_handler deadlock on notification reload When an MCP server sends a notifications/tools/list_changed or notifications/prompts/list_changed notification, the message_handler previously awaited load_tools()/load_prompts() directly. Since the handler runs on the MCP SDK's single-threaded receive loop, this caused a deadlock: load_tools() sends a list_tools request and waits for its response, but the receive loop cannot deliver that response while blocked in the handler. This manifested as a timeout in call_tool(), which then surfaced as "Error: Function failed." to the model instead of the real tool output. The MATLAB MCP server reliably triggers this because it sends a tools/list_changed notification during tool execution. Fix: schedule reloads as background asyncio.Tasks via a new _schedule_reload() helper, freeing the receive loop immediately. Fixes #4828 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review feedback: fix exc_info, coalesce reloads, shutdown cleanup, tests - Fix exc_info=exc -> exc_info=True in _schedule_reload and message_handler - Tighten _schedule_reload param type from Any to Coroutine[Any, Any, None] - Coalesce reloads: cancel-and-replace per reload kind to prevent unbounded growth - Cancel pending reload tasks in _close_on_owner before tearing down session - Re-raise CancelledError in _safe_reload to respect task cancellation - Replace flaky asyncio.sleep(0) with asyncio.wait_for/gather in tests - Add caplog assertions to verify reload failure is actually logged - Assert _pending_reload_tasks cleanup on error path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address review comments on MCP reload handling - Fix exc_info=True -> exc_info=message in message_handler error logging, since the handler is not called from an except block - Await cancelled reload tasks in _close_on_owner before tearing down the session to avoid 'Task was destroyed but pending' warnings - Add cancel-and-replace test verifying duplicate notifications cancel the first reload task and only keep one in flight Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove Task.cancelling() call for Python 3.10 compat Task.cancelling() was added in Python 3.11. Replace with awaiting the task and checking cancelled() instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add debug log when cancelling superseded reload task Log at DEBUG level when a new notification cancels an in-flight reload task, improving observability of the cancel-and-replace behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Giles Odigwe ·
2026-05-13 20:09:59 +00:00 -
Python: Add ClassSkill for class-based skill definitions (#5678)
* Python: Add ClassSkill for class-based skill definitions Add ClassSkill abstract base class with decorator-based resource and script discovery, porting .NET's AgentClassSkill (PRs #5027 and #5183) to Python. - Add ClassSkill(Skill, ABC) with instructions abstract property, cached content/resources/scripts properties - Add @ClassSkill.resource and @ClassSkill.script static method decorators for auto-discovery of methods and properties - Extract _build_skill_content() and _create_resource_element() shared helpers from InlineSkill for reuse - Add _discover_marked_members() for scanning class hierarchies - Add _make_method_name() for Python-to-skill name conversion - Add class_based_skill sample (UnitConverterSkill) - Update mixed_skills sample with TemperatureConverterSkill - Add 58 new tests covering ClassSkill, decorator discovery, property resources, inheritance, kwargs forwarding, and duplicate detection - Export ClassSkill from agent_framework public API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: replace try/except/continue with assignment to satisfy bandit B112 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address PR review feedback - Walk cls.__mro__ in _discover_marked_members for inherited property resources - Use inspect.getattr_static for MRO-aware is_property check - Return defensive copies from resources/scripts properties - Raise TypeError on wrong decorator stacking order (@resource above @property) - Log warning instead of silently swallowing descriptor errors during discovery - Validate explicit name= at decoration time via _validate_member_name - Add tests for all of the above Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix temperature converter skill: make resource necessary for script Refactor TemperatureConverterSkill so the agent must read the formulas resource (factor/offset) before calling the script, aligning with the volume-converter pattern. - Resource: numeric factor/offset table instead of symbolic formulas - Script: generic linear transform (value * factor + offset) - Instructions: updated to reflect new workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SergeyMenshykh ·
2026-05-07 19:39:12 +00:00 -
Python: Fix
MCPStreamableHTTPToolleakingasyncio.CancelledErrorwhen MCP server is unreachable (#5687)* fix: wrap asyncio.CancelledError in ToolException in _connect_on_owner (#5667) asyncio.CancelledError is a BaseException (not Exception) in Python 3.8+. When an MCP server is unreachable, the MCP library's internal anyio task group raises CancelledError, which escaped all three 'except Exception' handlers in _connect_on_owner(). This propagated through _run_lifecycle_owner -> _run_on_lifecycle_owner -> connect -> __aenter__, bypassing user except Exception blocks entirely. Fix: change the three except-Exception clauses in _connect_on_owner to 'except (Exception, asyncio.CancelledError)' so spurious CancelledErrors from the MCP transport layer are caught and wrapped in ToolException, consistent with the method's documented contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(mcp): propagate genuine task CancelledError in connect() (#5667) On Python >= 3.11, check task.cancelling() > 0 before wrapping CancelledError as ToolException in the three except blocks inside _connect_on_owner(). When the current task is being cancelled by its caller, the CancelledError now propagates after cleanup, consistent with the existing pattern at _mcp.py:560-564 and _runner.py:115-120. On Python < 3.11 task.cancelling() is unavailable, so MCP-internal CancelledErrors still cannot be reliably distinguished from caller-driven cancellation; they continue to be wrapped as ToolException with a comment documenting the trade-off. Tests: - Add cleanup assertion to transport-creation CancelledError test - Add MCPStdioTool variants exercising the 'command' message branches for both transport-creation and initialize CancelledError paths - Add Python 3.11+-gated tests verifying genuine task cancellation propagates (and still cleans up) for transport and initialize stages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(mcp): log CancelledError with exc_info before wrapping in ToolException (#5667) CancelledError inherits from BaseException (not Exception) on Python >= 3.8, so the 'inner_exception=ex if isinstance(ex, Exception) else None' guard always yields None for CancelledError. This means ToolException.__init__ calls logger.log(level, message, exc_info=None), dropping the traceback. Add an explicit logger.debug(error_msg, exc_info=ex) before each raise ToolException(...) in the three CancelledError handlers so the full traceback is preserved in debug logs when MCP-internal cancellation is wrapped rather than propagated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback for #5667: Python: [Bug]: Error Handling Issue regarding Python MCPStreamableHTTPTool Class * refactor(_mcp): extract cancellation helper, fix session error msg and exc_info - Extract _should_propagate_cancelled_error() helper to eliminate duplicated genuine-cancellation detection logic across the three connect() except blocks - Fix session-creation ToolException message to include exception details (e.g. 'Failed to create MCP session: <ex>') matching the transport and initialize failure paths - Change exc_info=ex to exc_info=True in all three logger.debug() calls for idiomatic logging - Add tests for _should_propagate_cancelled_error helper - Add regression test asserting session error message includes exception text - Add test verifying logger.debug is called with exc_info=True Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: factor out _close_and_check_cancelled helper in _connect_on_owner Addresses review comment on PR #5687: 1. Add _close_and_check_cancelled() helper method that combines _safe_close_exit_stack() + _should_propagate_cancelled_error() into a single await-able call. This eliminates the duplicated close-then-check pattern that appeared identically in all three connect phases (transport, session, initialize), reducing future drift risk. 2. Comments 2 and 3 (missing {ex} in session error message and non-idiomatic exc_info=ex) were already addressed in the current code: all error messages include {ex} and all logger.debug calls use exc_info=True. 3. Add test_connect_genuine_cancellation_during_session_creation_propagates to cover the previously untested genuine-cancellation path in the session-creation phase (transport and initialize phases already had tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback for #5667: review comment fixes --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evan Mattson ·
2026-05-07 17:58:30 +00:00 -
Python: Add
base_urlparameter toAnthropicClientandRawAnthropicClient(#5685)* feat(anthropic): add base_url parameter to AnthropicClient and RawAnthropicClient Add base_url support to AnthropicSettings TypedDict, RawAnthropicClient, and AnthropicClient so users can point the client at Foundry or other Anthropic-compatible endpoints without having to construct AsyncAnthropic manually. - Add base_url field to AnthropicSettings (resolved from ANTHROPIC_BASE_URL env var) - Add base_url parameter to RawAnthropicClient.__init__ and pass it to AsyncAnthropic - Add base_url parameter to AnthropicClient.__init__ and forward to super - Add unit tests for base_url on both client classes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Add `base_url` parameter to `AnthropicClient` and `RawAnthropicClient` Fixes #5683 * test: add ANTHROPIC_BASE_URL env fallback tests for issue #5683 Add unit tests verifying that both AnthropicClient and RawAnthropicClient pick up base_url from the ANTHROPIC_BASE_URL environment variable via load_settings when base_url is not passed explicitly as a constructor arg. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(anthropic): explicit base_url kwarg beats ANTHROPIC_BASE_URL env var (#5683) Add regression tests asserting that when both ANTHROPIC_BASE_URL is set in the environment *and* an explicit base_url kwarg is passed to AnthropicClient / RawAnthropicClient, the explicit kwarg wins. This closes the priority-ordering contract (explicit arg > env var) that the existing tests left implicit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evan Mattson ·
2026-05-07 17:57:09 +00:00 -
Python: Core: notify agent of external AgentModeProvider mode changes (#5650)
When the operating mode is changed externally (e.g. via a slash-command handler calling set_agent_mode), the agent's chat history still shows the prior set_mode tool call near the end. Updating only the system instructions is insufficient — models tend to anchor on the recent tool call and ignore the new mode. Mirror the .NET AgentModeProvider behavior: when set_agent_mode detects an actual mode change, record the previous mode in provider state. On the next before_run, the provider pops that flag and injects a user-role notification message announcing the switch, so the most recent context unambiguously reflects the current mode. The agent-driven set_mode tool path bypasses this so it does not trigger a redundant notification on its own change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-07 02:58:38 +00:00 -
Python: [Breaking] Restructure agent skills to use multi-source architecture (#5584)
* migrate skills to multi source architecture * Fix ruff lint errors in skills module (ASYNC240, SIM108, E501) - Use anyio.Path for async file I/O in _FileSkillResource.read() - Use noqa: ASYNC240 for pure string os.path calls in async context - Restore pre-commit if/else pattern in InlineSkillScript.run() - Break long lines to fit 120-char limit in _skills.py and test_skills.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: collapse multi-line lambdas to single lines to fix pyright errors The pyright ignore comments only suppress errors on the same line, so multi-line lambdas left arguments on continuation lines uncovered. Collapse both lambdas to single lines matching the existing load_skill lambda pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: replace untyped lambdas with typed inner functions to fix pyright errors Python lambdas cannot have type annotations, so pyright reports reportUnknownLambdaType and reportUnknownArgumentType errors that cannot be suppressed with inline ignore comments. Replace the lambdas for read_skill_resource and run_skill_script with typed inner async functions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback on docs and prompt template - Update with_prompt_template() docstring to document the {resource_instructions} placeholder requirement - Remove stray backslashes after {resource_instructions} and {runner_instructions} in DEFAULT_SKILLS_INSTRUCTION_PROMPT - Update subprocess_script_runner docstring to reflect FileSkillScript.full_path usage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: replace dict[str, Skill] with Sequence[Skill] in SkillsProvider Replace internal dict-based skills storage with Sequence[Skill] to eliminate silent duplicate overwrites and simplify the code. Add _find_skill helper for case-insensitive linear lookup. Also fix pyright errors in tests by adding isinstance assertions before accessing .function on SkillResource/SkillScript base types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: add read-time resource path validation in _FileSkillsSource Move security validation (path-traversal and symlink guards) for file-based skill resources into _FileSkillsSource, restoring the read-time checks that existed in main via _read_file_skill_resource. - Add _get_validated_resource_path static method on _FileSkillsSource that validates containment, existence, and symlink safety - _FileSkillsSource.get_skills() validates resource paths at discovery time via _get_validated_resource_path before passing to _FileSkillResource - Move _normalize_resource_path, _is_path_within_directory, and _has_symlink_in_path from module-level into _FileSkillsSource as static methods (only used there) - _FileSkillResource remains a simple path-to-content reader - Add tests for _get_validated_resource_path security checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: reject str/Path in SkillsProvider constructor to prevent str-as-Sequence ambiguity Since str is a Sequence, passing a path string to the source parameter would silently be treated as a sequence of characters instead of a file source. Add an explicit TypeError with a helpful message pointing callers to SkillsProvider.from_paths(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR #5584 review feedback - Remove .NET reference from _FileSkillResource docstring - Fix inconsistent resource name example (references/FAQ.md -> references/FAQ) - Simplify SkillsProvider usage in code_defined_skill sample (pass single skill directly) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove skillsproviderbuilder * Update python/packages/core/agent_framework/_skills.py Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> * fix: remove dead code and fix sync function call in InlineSkillResource.read() - Change await self.function() to self.function() for sync functions without **kwargs; async results are handled by inspect.isawaitable() - Remove unreachable raise ValueError since __init__ already validates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove full_path unnecessary property * replace anyio with asyncio.to_thread for file I/O in _FileSkillResource Replace anyio.Path usage with asyncio.to_thread + pathlib.Path since anyio is not a direct dependency of core (transitive via mcp). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * simplify awaitable check to return directly Use 'return await result' instead of assigning then returning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address PR review feedback for skills refactoring - Replace anyio with asyncio.to_thread + pathlib.Path for file I/O - Simplify awaitable check to return directly - Remove unnecessary function None guard in InlineSkillResource.read() - Add assert for type narrowing on self.function Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address PR review feedback for skills refactoring - Replace anyio with asyncio.to_thread + pathlib.Path for file I/O - Simplify awaitable checks to return directly - Remove unnecessary function None guard in InlineSkillResource.read() - Use typing.cast instead of assert for type narrowing - Add caching behavior note to SkillsProvider docstring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: move name/description from abstract properties to Skill.__init__ Replace abstract properties for name and description on the Skill ABC with a base __init__ that validates and stores them as regular attributes. This simplifies custom Skill subclasses (only content remains abstract) and centralizes validation in the base class, consistent with SkillResource and SkillScript base classes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
SergeyMenshykh ·
2026-05-06 09:45:06 +00:00 -
Python: information-flow control prompt injection defense (#5331)
* Python: Information-flow control based prompt injection defense (#5024) * fides integration * documentation * documentation * documentation * human-approval on policy violation * numenous hyena 'works' * IFC based implementation * minor edits in documentation * rebasing the branch and running the email example * Add security tests for IFC middleware * Fix Role.TOOL NameError in approval handling * tiered labelling scheme * 3 tier labelling scheme in middleware * Adapt security middleware to list[Content] tool results * Refactor SecureAgentConfig as context provider and address Copilot review comments * Update FIDES docs to reflect context provider pattern and update code for ContextProvider rename * Fix security examples: use OpenAIChatClient instead of non-existent AzureOpenAIChatClient * Address PR review: consolidate security modules, remove ContentLineage, update docs * remove unrelated files * remove comment from _tools.py and rename decision file * Fix CI failures: Bandit B110, broken md links, hosted approval passthrough * apply template to decision doc 0024 * minor fixes to decision doc 0024 --------- Co-authored-by: Aashish <t-akolluri@microsoft.com> * Python: follow up FIDES security flow (#5330) * Python: follow up FIDES security flow Refine the secure approval path, mark the security classes with the FIDES experimental feature label, and clean up the related docs/tests. Also fix workspace-level validation regressions uncovered while running the full Python check suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: remove FIDES GitHub MCP sample Drop the GitHub MCP security sample from the FIDES follow-up branch while keeping the remaining security docs and samples intact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: fix paths and update FIDES implementation (#5352) * Python: updated import naming and comment from review (#5421) * updated import naming and comment from review * Add approval replay None call-id test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Address PR 5331 comments and track sesssion while calling Agent in email_security_example (#5446) * Address PR review: fix paths and update FIDES implementation * Address PR comments and add session tracking in email example in samples * Fix session creation and resolve merge conflict in docstring example * Resolve merge conflict in docstring example * Python: add test for empty-message pruning in approval result replacement (#5617) Adds test coverage for the second-pass logic in `_replace_approval_contents_with_results` that removes messages whose `contents` list becomes empty after first-pass content removal. Addresses review comment on PR #5331: https://github.com/microsoft/agent-framework/pull/5331#discussion_r3129039445 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: shrutitople <shruti.tople@gmail.com> Co-authored-by: Aashish <t-akolluri@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-05 18:08:08 +00:00 -
Python: Core: add experimental session-mode harness context provider (#5611)
* Python: Core: add experimental session-mode harness context provider Introduces the _harness namespace and the first context provider: SessionModeContextProvider, with get_session_mode / set_session_mode helpers and a DEFAULT_MODE_SOURCE_ID constant. Behind @experimental(ExperimentalFeature.HARNESS). Also folds in a small _sessions.py cleanup (try/except ImportError -> contextlib.suppress) touched while developing the harness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: align session-mode harness with .NET AgentModeProvider Mirror the default mode descriptions and instruction template used by the .NET AgentModeProvider so the cross-language harness UX is consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: address review feedback on session-mode harness - json.dumps tool outputs to stay valid for arbitrary mode names - normalize configured mode keys (lower+strip) so custom-cased configs work - raise TypeError instead of silently replacing non-dict session state - mark get_session_mode/set_session_mode as @experimental(HARNESS) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: rename SessionModeContextProvider to AgentModeProvider Match the .NET AgentModeProvider class name for cross-language consistency. Helpers renamed accordingly: get_session_mode -> get_agent_mode, set_session_mode -> set_agent_mode. The default source_id is now "agent_mode". Construction pattern stays Pythonic (kwargs, not an options object). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: address AgentModeProvider review feedback - default_mode now defaults to None and falls back to the first configured mode, decoupling the kwarg from the built-in 'plan'/'execute' set. - get_agent_mode catches ValueError when a previously persisted mode is no longer in available_modes and resets to the default mode (matching the non-string recovery branch). Added regression coverage for both behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-05 10:09:19 +00:00 -
Python: Fix hyperlight WasmSandbox cross-thread Drop and harden hosted-agent sample (#5603)
* update hyperlight to beta and move samples, add hosted agent sample * Python: Fix hyperlight WasmSandbox cross-thread Drop and harden sample Root cause: when a worker-side closure raised, the exception's __traceback__ retained frame locals that included the partially constructed PyO3 sandbox. Future.result() re-raised that exception on the caller thread, and when the caller's exception was eventually GC'd the frame locals were released off-thread, dec_ref'ing the unsendable sandbox from the wrong thread and tripping the PyO3 panic '_native_wasm::WasmSandbox is unsendable, but is being dropped on another thread'. Fix: * Add _SandboxWorker._run_on_worker which catches every exception on the worker, drops __traceback__ there, deletes the original exception, and re-raises a fresh instance on the caller thread. initialize and execute route through it; dispose keeps its bare-submit semantics. * Add an opt-in diagnostic module _drop_diagnostic (no-op unless HYPERLIGHT_TRACE_DROPS=1) that installs a sys.unraisablehook and dumps owner-thread + per-thread stacks on any future cross-thread unsendable Drop. Useful for triaging similar PyO3 regressions. * Tests: cross-thread invocation, traceback-leak isolation, _SandboxEntry attribute-shape check, and a stale-reference stress test driven through asyncio.to_thread. Sample (samples/04-hosting/foundry-hosted-agents/responses/06_hyperlight_codeact): * Dockerfile installs agent-framework-* from in-tree source with python/ as build context so unreleased fixes can be validated end-to-end. * call_server.py pins the Responses API version. * main.py enables include_detailed_errors=True so future tool failures surface the actual exception text instead of a bare 'Error: Function failed.' string. * README.md documents the in-tree-package build and the Hyperlight hypervisor requirement (/dev/kvm on Linux, MSHV on Windows). Hosted environments without hypervisor passthrough surface 'No Hypervisor was found for Sandbox'; this is a hosting constraint, not a hyperlight bug. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: remove _drop_diagnostic from hyperlight package The diagnostic module was useful while bisecting the cross-thread Drop bug, but it is no longer needed now that _SandboxWorker._run_on_worker prevents the panic at the source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: address PR review feedback on hyperlight - Use lazy agent_framework.hyperlight import in sample main.py. - Env-driven endpoint (FOUNDRY_AGENT_ENDPOINT) in call_server.py; remove personal URLs. - Align agent.yaml model deployment with manifest (gpt-4.1-mini). - Tighten Dockerfile requirements guard; drop dangling deploy.ps1 reference. - Preserve exception args when sanitizing tracebacks in _run_on_worker. - Add public _SandboxWorker.is_alive(); update test to avoid private attr. - Add namespace coverage tests for agent_framework.hyperlight lazy loader. - Add prominent note: Foundry hosted-agent runtime does not yet support Hyperlight (no hypervisor exposed); container works locally with /dev/kvm. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: bump hyperlight-sandbox dependencies to 0.4.x Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: renumber hyperlight codeact sample to 08 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Coerce worker exception args to strings for cross-thread safety Stringify exc.args on the worker thread before propagating, so any PyO3 unsendable object captured in args (e.g. via a caller-supplied callback or underlying SDK) cannot be Dropped on the calling thread. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * moved sample --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-05 10:06:16 +00:00 -
Python: Core: add experimental todo-list harness context provider (#5612)
* Python: Core: add experimental todo-list harness context provider Adds TodoListContextProvider with pluggable TodoStore backends: TodoSessionStore (in-session) and TodoFileStore (JSONL on disk). Public types: TodoItem, TodoInput. Behind @experimental(ExperimentalFeature.HARNESS). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: align todo harness instructions with .NET TodoProvider Reformat DEFAULT_TODO_INSTRUCTIONS to mirror the .NET TodoProvider DefaultInstructions wording and structure, and bring the class docstring closer to the .NET XML <remarks> block. Keeps Python tool names in snake_case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: address review feedback on todo harness - mark TodoStore as @experimental(HARNESS) for surface consistency - TodoSessionStore.load_state now raises ValueError on malformed items - TodoFileStore now namespaces persisted state by source_id - TodoFileStore now safely encodes session_id/owner and verifies path containment (matches FileHistoryProvider pattern) - per-(session, source_id) asyncio.Lock around read-modify-write to avoid races Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: rename TodoListContextProvider to TodoProvider Match the .NET TodoProvider class name for cross-language consistency. Other public types (TodoStore, TodoSessionStore, TodoFileStore, TodoItem, TodoInput) are unchanged. Construction stays Pythonic (kwargs, not an options object). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: address TodoProvider review feedback - TodoStore.load_state/save_state are now async; TodoFileStore performs disk I/O via asyncio.to_thread so the event loop is no longer blocked while the per-session mutation lock is held. - TodoSessionStore now raises ValueError for malformed top-level state (non-dict / non-list 'items' / non-int 'next_id') to match the TodoFileStore contract instead of silently re-defaulting. - Both stores now clamp next_id to max(item.id) + 1 after load to make ID collisions impossible after recovery or reconfiguration. - TodoFileStore writes atomically by writing a sibling temp file and os.replace-ing it so a crash mid-write cannot truncate the state file. - TodoFileStore.load_state no longer creates parent directories for sessions that never write; mkdir is deferred to save_state. - TodoProvider mutation locks now live in a weakref.WeakKeyDictionary keyed by AgentSession, so locks for GC'd sessions are evicted instead of leaking in long-running services. Tests cover each change including a TodoFileStore-backed end-to-end provider flow, atomic-write recovery, and lock GC eviction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-05 08:39:41 +00:00 -
Python: Core: add experimental memory harness context provider (#5613)
* Python: Core: add experimental memory harness context provider Adds MemoryContextProvider with topic-indexed long-term memory and chat-driven compaction. Pluggable MemoryStore backends include MemoryFileStore. Public types: MemoryIndexEntry, MemoryTopicRecord. Behind @experimental(ExperimentalFeature.HARNESS). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Core: address review feedback on memory harness - mark MemoryStore as @experimental(HARNESS) for surface consistency - safely encode owner id and verify path containment (matches FileHistoryProvider pattern) - namespace MemoryFileStore on-disk layout by source_id to avoid cross-provider collisions - before_run computes index_entries once and only rewrites MEMORY.md when content changes - asyncio locks around topic/state read-modify-write to avoid concurrent-write races Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR feedback: harden memory store IO + consolidation behavior - Atomic writes via os.replace + temp sibling for topic, state, and index files so crashes/disk-full failures cannot leave a truncated half-written file. - Stop creating directories on read paths: list_topics/read_state/search_transcripts and get_messages return empty when nothing has been written. mkdir is deferred to the actual save path (write_topic/write_state/save_messages). - Escape lines that look like markdown headings on render and unescape them on parse, so a memory or summary containing '## Summary'/'## Memories' cannot tamper with the topic file structure. - Narrow extraction/consolidation chat-client failure handling to ChatClientException, asyncio.TimeoutError, and OSError. Programmer errors (AttributeError, TypeError, ...) now propagate so misconfigured clients fail loudly. - Log a payload-prefix preview for every silent shape branch in _extract_memories and _consolidate_topic so unparsable extractor output is debuggable instead of invisible. - Restructure _run_consolidation: read maintenance state and topic snapshot under the state lock, run the LLM consolidation loop without holding the state lock, and only advance last_consolidated_at/sessions_since_consolidation if at least one topic succeeded. Transient consolidation failures now leave the maintenance window in place so the next after_run retries instead of silently sliding forward. - Add regression tests for: markdown-marker round-trip, atomic-write recovery on os.replace failure, no-mkdir on pure read paths, transient consolidation failure preserves state, and propagation of programmer errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eduard van Valkenburg ·
2026-05-04 21:19:50 +00:00 -
Python: Support OpenAI and Gemini
allowed_toolstool choice (#5322)* Support OpenAI allowed_tools in ToolMode (#5309) Add allowed_tools field to ToolMode TypedDict, enabling users to restrict which tools the model may call via the OpenAI allowed_tools tool_choice type. This preserves prompt caching by keeping all tools in the tools list while limiting which ones the model can invoke. - Add allowed_tools: list[str] to ToolMode TypedDict - Add validation in validate_tool_mode() (only valid when mode == "auto") - Convert to OpenAI API format in _prepare_options() - Add tests for validation and API payload generation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Support OpenAI `allowed_tools` tool choice in Python SDK Fixes #5309 * Fix #5309: Validate allowed_tools shape and add Chat Completions client support - validate_tool_mode now checks allowed_tools is a non-string sequence of strings and normalizes to list[str], raising ContentError for invalid types - Add missing allowed_tools branch in _chat_completion_client._prepare_options so allowed_tools is emitted as the OpenAI allowed_tools wire format instead of being silently dropped - Add tests for invalid allowed_tools types (string, int, mixed), empty list, tuple normalization, and Chat Completions client payload generation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: support allowed_tools with mode 'required' in addition to 'auto' OpenAI's allowed_tools tool_choice type supports both mode 'auto' and 'required'. Update validation, client conversion, and tests to allow both modes instead of restricting to 'auto' only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use Gemini VALIDATED mode for allowed_tools, warn in unsupported providers - Use FunctionCallingConfigMode.VALIDATED instead of ANY when allowed_tools is set with auto mode in Gemini, preserving optional tool-call semantics. - Handle allowed_tools in required mode with required_function_name precedence. - Fix allowed_names guard to use identity check (is not None) so empty lists are preserved. - Bump google-genai minimum to >=1.32.0 (VALIDATED added in that version). - Add warnings in Anthropic and Bedrock when allowed_tools is set but not supported. - Add Gemini unit tests for allowed_tools with auto, required, empty list, and required_function_name precedence scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: Chat Completions API does not support allowed_tools, add integration tests - Chat Completions API (_chat_completion_client.py) now warns and falls back to plain mode when allowed_tools is set, since the /chat/completions endpoint does not support the allowed_tools type. - Add allowed_tools integration test param to both OpenAIChatClient (Responses API) and OpenAIChatCompletionClient parametrized option tests. - Update Chat Completions unit tests to reflect the warn-and-fallback behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: remove unused walrus operator variable in chat completion client Remove assigned-but-never-used variable 'allowed' flagged by ruff F841. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Giles Odigwe ·
2026-04-29 17:43:47 +00:00 -
Python: Fix spans not correctly nested when using streaming (#5552)
* Fix spans not correctly nested when using streaming * fix pre commit * Address comments
Tao Chen ·
2026-04-29 08:21:28 +00:00 -
Python: Feature/hosted dwf (#5531)
* Fix declarative Workflow.as_agent() by accepting list[Message] in start executor The declarative start executor (JoinExecutor) only advertised dict and str in its input_types, so WorkflowAgent.__init__ rejected it with 'Workflow's start executor cannot handle list[Message]'. Add list[Message] to the JoinExecutor handler annotation and add a matching branch in DeclarativeActionExecutor._ensure_state_initialized that extracts the last user-message text and falls through to the string-input initialization path, so =System.LastMessageText works end-to-end via as_agent(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Populate Conversation.messages from list[Message] trigger When Workflow.as_agent() is invoked with a list[Message], the start executor now populates Conversation.messages / Conversation.history / System.conversations.{id}.messages with prior turns only (excluding the latest user message), and surfaces the latest user message via Inputs.input and System.LastMessage*. This matches InvokeAzureAgent's contract that the messages binding holds prior turns and the executor itself appends the new user input before invoking, avoiding double-append of the trailing user turn while preserving full history (incl. assistant/system/tool roles and multi-modal content) for downstream actions. * Coerce Enum values when serializing PowerFx symbols MessageRole and other str-subclass Enums passed isinstance(v, str) and were forwarded to pythonnet unchanged. pythonnet then raised 'MessageRole value cannot be converted to System.String' for every PowerFx primitive when ConditionGroup/Expr eval walked the symbol table containing Conversation.messages. Reduce Enum members to their underlying value before the primitive check so eval sees plain strings/ints. * Foundry hosting: pass full conversation history to workflow agents _handle_inner_workflow only forwarded the latest user turn to WorkflowAgent.run, even though _handle_inner_agent already prepends history fetched from Foundry storage to the messages it sends a regular agent. Declarative workflows reset Conversation.messages on every run (state.initialize), so checkpoint replay alone does not give them prior turns - the host has to pass them in, the same way it does for non-workflow agents. Mirror that contract: fetch context.get_history() and pass [*history, *input_messages] to the workflow agent. * feat(workflows): support combined message + checkpoint_id for multi-turn continuation Allow Workflow.run(message=..., checkpoint_id=...) so callers can restore prior workflow state from a checkpoint AND deliver a new message to the start executor in a single call. The existing reset_context logic already preserves shared state when checkpoint_id is set, so this gives us 'fresh start executor invocation with prior state intact' - exactly what hosted multi-turn declarative workflows need. - _workflow.py: drop the message+checkpoint_id mutual exclusion and update _execute_with_message_or_checkpoint to do both (restore then execute) when both are provided. - _agent.py: in _run_core's checkpoint branch, also forward input_messages so WorkflowAgent.run(messages, checkpoint_id=...) works end-to-end. Falls back to the legacy 'restore only' behavior when messages are absent. - _declarative_base.py: detect continuation in _ensure_state_initialized by checking whether DECLARATIVE_STATE_KEY already exists in shared state; if so, refresh inputs/LastMessage* and append non-user trigger messages instead of calling state.initialize() (which would wipe Conversation/Local/System). - foundry_hosting/_responses.py: collapse the host's two-call pattern (restore-only, then fresh run) into a single combined call now that the underlying APIs support it. - tests: drop the assertion that combined message+checkpoint_id raises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Pivot: preserve workflow state across run() calls Replace the prior 'combined message + checkpoint_id in one run()' approach with a cleaner default: Workflow.run no longer wipes shared state or runner- context messages between calls. Iteration counting and per-run kwargs still reset on a fresh-message run; checkpoint and responses runs are continuations that preserve everything. This lets a WorkflowAgent be invoked repeatedly on the same instance and maintain multi-turn context (e.g. accumulated Conversation.messages) without asking developers to opt in. Hosted-agent multi-turn pattern becomes two explicit calls: restore-from-checkpoint (drive to idle), then run-with-message. Key changes: - _workflow.py: drop _state.clear() and reset_for_new_run() from run(). Reset iteration count and run kwargs on fresh-message runs only. Restore 'Cannot provide both message and checkpoint_id' validation. Add async guard: fresh-message run with un-drained pending executor messages from a prior run is invalid. - _runner.py: clear _state before import_state in restore_from_checkpoint so restore is authoritative (import_state merges, not replaces). - _agent.py: revert checkpoint branch to restore-only (no message forward). - _responses.py (foundry_hosting): two-call host pattern - restore checkpoint silently, then run with new user input. - tests: state-preservation is the new default; rebuild Workflow for clean slate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI lint and mypy issues from prior pivot commit - _workflow.py: collapse nested if (SIM102), drop redundant assignment (RET504) - _declarative_base.py: remove unused last_user_msg = tail assignment whose Message | None type clashed with the prior Message-typed branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: fix Inputs.input update and checkpoint storage path - _declarative_base.py: continuation branch was writing 'Inputs.input' via state.set, which routes to the Custom namespace and never updates the PowerFx-visible Workflow.Inputs.input. Update state_data['Inputs'] in place via get_state_data / set_state_data so =Workflow.Inputs.input and =inputs.input see the new turn's user text on continuation. - _declarative_base.py: refresh docstring to clarify that on a list[Message] trigger, Conversation.messages excludes the current user message at the start of the turn (agent executors append it before invoking the inner agent). - _responses.py: when previous_response_id is supplied (no conversation_id), the prior checkpoint lives under <storage>/<previous_response_id> but new checkpoints must land under <storage>/<current_response_id> for the next turn to find them. Hold onto restore_storage from the get_latest lookup and pass it to the restore-only run; pass write_storage (current id) to the message-delivery run and to checkpoint cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix pyright errors in _declarative_base.py for CI - Replace state._state.get(...) protected access with new public is_initialized() method on DeclarativeWorkflowState (also clearer intent for the continuation detection use case). - Add narrow pyright ignores for the Any-typed trigger paths that pyright cannot fully narrow (the list[Message] isinstance loop and the fallback-DefaultTransform branch). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Copilot review batch: tests + Workflow.reset escape hatch * Add Workflow.reset() public method as recovery escape hatch when an in-flight run aborted (e.g. WorkflowConvergenceException) and the workflow is not checkpointed. Update the in-flight messages guard's error message to point callers at it. * Add test_workflow_run_inflight_messages_guard exercising both the guard (sync + streaming) and the reset() recovery path. * Add test_workflow_reset_rejects_concurrent_runs to lock down the in-progress guard on reset. * Add test_as_agent_continuation_preserves_prior_state covering the is_continuation branch in _ensure_state_initialized: stamps a marker between calls and asserts it survives, while Inputs.input and System.LastMessageText refresh to the new turn. * Add test_powerfx_safe.py regression tests for the Enum branch in _make_powerfx_safe (str-subclass, int-subclass, plain Enum, and Enums nested in dict/list). * Drop redundant @pytest.mark.asyncio on test_as_agent_round_trip_with_last_message_text (asyncio_mode='auto'). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip restore-only pre-pass when checkpoint has pending request_info Address Copilot review on _responses.py: the restore-only checkpoint replay populates self._agent.pending_requests for any request_info events captured in the checkpoint. The follow-up run(input_messages) call would then route through WorkflowAgent._process_pending_requests, which expects function-response content and rejects plain text input as 'unexpected content while awaiting request info responses'. Workflows resumed from a checkpoint that was idle-with-pending-requests would therefore fail every subsequent plain-text user turn. Inspect the loaded checkpoint and skip the pre-pass when its pending_request_info_events dict is non-empty. Workflows that don't use request_info (the current sample set) are unaffected; workflows that do will fall through to a fresh-message run rather than silently corrupting the routing state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Loosen azure-ai-agentserver-* pins to major version The exact-version pins on azure-ai-agentserver-{core,responses,invocations} forced foundry-hosting consumers to upgrade in lockstep with every beta bump from upstream. Switch to '>=current,<next-major' so we pick up patch and feature updates within the same major series without a coordinated release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop Workflow.reset(); checkpointing is the recovery path The in-flight-messages guard prevented silent misbehavior, but the companion Workflow.reset() escape hatch only cleared _messages while leaving iteration count, executor-local state, and shared State mutations in an indeterminate condition after a mid-run failure. That gave a false sense of recovery. Recovery from a mid-run failure is supported only via checkpoint restoration. Keep the guard and reframe its error message accordingly; remove reset() and its tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address Tao's review on PR 5531 - Rename Workflow._run_workflow_with_tracing parameter is_fresh_message_run -> is_continuation (default False, inverted). Fresh-message turns reset per-run accounting; continuations (checkpoint restores, responses replays) preserve it. - Simplify the in-flight-messages guard: _validate_run_params already enforces that 'message' is mutually exclusive with 'checkpoint_id' and 'responses', so the additional checks were dead code. - foundry_hosting _responses: move the restore-only pre-pass above emit_created/emit_in_progress; restore is preparation, not run progress. Drop the skip-restore gate (state preservation requires unconditional restore) and instead clear agent.pending_requests after the restore-only call. Collapse over-conditioned check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Don't clear pending_requests after restore-only pre-pass Pending requests in the restored checkpoint represent genuinely outstanding HITL requests. The next user input may carry function responses (Responses API `function_call_output` items become FunctionResultContent / FunctionApprovalResponseContent), which `WorkflowAgent._process_pending_requests` correctly extracts and matches against the populated `pending_requests`. Clearing them after restore would silently drop that state and force the next turn to be treated as a fresh input even when the caller is responding to the outstanding requests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Ben Thomas ·
2026-04-29 00:51:49 +00:00 -
Python: [BREAKING] Standardize orchestration terminal outputs as AgentResponse (#5301)
* Fix orchestration outputs so as_agent() returns the final answer only. Align other orchestration outputs * Fix orchestration output issues from review comments 1. Sample cleanup: Remove commented-out FoundryChatClient block and update prerequisites to reference OPENAI_CHAT_MODEL_ID instead of FOUNDRY_* vars. 2. Sequential approval output: Change _EndWithConversation.end_with_agent_executor_response from a no-op sink to yield response.agent_response. When the last participant is AgentApprovalExecutor (via with_request_info), _EndWithConversation is the output executor so the yield produces the terminal answer. When the last participant is a regular AgentExecutor, _EndWithConversation is not in output_executors so the yield is silently filtered out. 3. Forward data events through WorkflowExecutor: _process_workflow_result now also forwards 'data' events from sub-workflows so that emit_intermediate_data=True on AgentExecutor works correctly when wrapped in AgentApprovalExecutor. 4. Concurrent docstring: Update _AggregateAgentConversations docstring to say 'deterministic participant order' instead of 'completion order'. 5. Add test_concurrent_intermediate_outputs_emits_data_events verifying that ConcurrentBuilder(intermediate_outputs=True) emits per-participant data events alongside the single aggregated output event. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add tests for sequential workflow with_request_info and intermediate_outputs (#5301) Address PR review comments 2, 3, and 5: - Add test_sequential_request_info_last_participant_emits_output: Verifies that when the last participant is wrapped via with_request_info() (AgentApprovalExecutor), the workflow still emits a terminal output after approval, exercising the _EndWithConversation.end_with_agent_executor_response fallback path. - Add test_sequential_request_info_with_intermediate_outputs_emits_data_events: Verifies that emit_intermediate_data=True works correctly through AgentApprovalExecutor wrapping—WorkflowExecutor._process_result already forwards data events from sub-workflows, so intermediate agent responses surface as data events in the parent workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix pyright type errors from AgentResponse output refactor (#5301) Update cast() calls in _group_chat.py and _magentic.py to use WorkflowContext[Never, AgentResponse] instead of the old WorkflowContext[Never, list[Message]], matching the updated method signatures in _base_group_chat_orchestrator.py. Fix _sequential.py _EndWithConversation.end_with_agent_executor_response to declare WorkflowContext[Any, AgentResponse] so yield_output accepts AgentResponse[None]. Fix _workflow_executor.py data event forwarding to handle nullable executor_id. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix pyright reportUnknownVariableType in _agent.py (#5301) Extract event.data into a typed local variable before the isinstance check to avoid pyright narrowing it to AgentResponse[Unknown]. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix pyright reportMissingImports for orjson in file history samples (#5301) Add pyright: ignore[reportMissingImports] to orjson imports that are already guarded by try/except ImportError, matching the existing pattern used elsewhere in the samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback for #5301: review comment fixes * Address review feedback for #5301: review comment fixes * Revert sequential_workflow_as_agent sample to FoundryChatClient Reverts the mistaken switch from FoundryChatClient to OpenAIChatClient in the sequential workflow as agent sample. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address ultrareview feedback: emit_data_events rename + WorkflowAgent reasoning conversion Layered on top of the prior review-feedback work in this branch. Renames: - AgentExecutor.emit_intermediate_data -> emit_data_events (mechanical rename; orchestration semantics live at the orchestration layer, not the general-purpose executor). Forwarded through MagenticAgentExecutor, AgentApprovalExecutor, and all orchestration call sites. - HandoffAgentExecutor._check_terminate_and_yield -> _should_terminate (pure predicate; no longer yields anything). HandoffBuilder docstring rewritten to describe the new per-agent AgentResponse output contract. WorkflowAgent reasoning-content conversion: - Add _rewrite_text_to_reasoning(contents) and _msg_as_reasoning(msg) helpers; the as_agent() path now reframes text content from data events as text_reasoning Content blocks before merging into the AgentResponse. - Consumers iterate msg.contents and branch on content.type — same path they already use for Claude thinking and OpenAI reasoning. No new field on Message/AgentResponse/WorkflowEvent. - Streaming branch constructs fresh AgentResponseUpdate instances instead of mutating shared payloads (regression test added). - Helper _msg_maybe_reasoning consolidates the conditional rewrite at three call sites in the non-streaming conversion. Tests: - TestWorkflowAgentReasoningHelpers + TestWorkflowAgentDataEventReasoningConversion add 9 new tests covering helpers, non-streaming, streaming, mixed content, already-reasoning passthrough, and mutation-safety regression. - Updated test_sequential_as_agent_with_intermediate_outputs_includes_chain to assert text_reasoning content for intermediate agents. * Fix pyright: widen event.data to Any to avoid partial-unknown narrowing The streaming conversion path narrowed event.data via isinstance against generic AgentResponse, producing AgentResponse[Unknown] and tripping reportUnknownVariableType/reportUnknownMemberType. Binding data: Any before the check keeps runtime behavior identical while restoring a fully known type for downstream access. * Clean up design * Scope to agent output semantics only * yield AgentResponseUpdate streaming, AgentResponse non-streaming * Fix mypy/pyright: widen cast types at GroupChat callsites Eight callsites in _group_chat.py still cast to WorkflowContext[Never, AgentResponse] but the base orchestrator methods now accept the wider WorkflowContext[Never, AgentResponse | AgentResponseUpdate] (mode-aware yields). W_OutT is invariant, so the narrower cast is not assignable. Magentic was widened in the same commit; this catches the GroupChat callsites that were missed. * Python: skip flaky Foundry / Foundry Hosting integration tests (#5553) These two integration tests have been failing in the merge queue across multiple unrelated PRs (5301, 5531). Both are marked `@pytest.mark.flaky` with 3 retries, but all attempts fail back-to-back. Skipping both with a reason pointing to #5553 so they can be fixed properly without continuing to block unrelated merges. - packages/foundry_hosting/tests/test_responses_int.py::TestOptions::test_temperature_and_max_tokens - packages/foundry/tests/foundry/test_foundry_embedding_client.py::TestFoundryEmbeddingIntegration::test_text_embedding_live Also includes a one-line uv.lock specifier-ordering normalization auto-applied by the poe-check pre-commit hook. --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evan Mattson ·
2026-04-29 00:35:36 +00:00