Commit 310def1
[agentserver-responses] Harden response model, type safety, and builder API (#46302)
* Always include model field in response payload
Default model to empty string when not provided in the request,
ensuring the field is always present in the response payload.
The OpenAI SDK requires model to be present to deserialize the
response object.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add e2e tests for model field always present in response
Address PR review feedback: add contract tests verifying the model
field is present in the response payload when omitted from the request,
for both sync (stream=False) and streaming (stream=True) modes.
* fix: DELETE response returns object='response' instead of 'response.deleted'
The OpenAI spec returns {id, object: 'response', deleted: true} for
DELETE /responses/{id}. Our handler was returning 'response.deleted'
which doesn't match. Fixed the handler and updated all 5 test
assertions.
* fix: stamp agent_session_id and conversation_id on to_snapshot fallback
ResponseExecution now carries agent_session_id and conversation_id so
that _RuntimeState.to_snapshot can forcibly stamp them (S-038/S-040)
on both the response.as_dict() path and the minimal fallback dict.
All four orchestrator ResponseExecution creation sites pass both
fields from the execution context.
* fix: remove output:list override breaking get_history deserialization
The manual _patch.py override of ResponseObject.output erased the
element type (list instead of list[OutputItem]), preventing the model
framework from deserializing nested dicts into OutputItem instances.
This caused get_history to return plain dicts instead of typed models.
Changes:
- Remove output:list override; use generated list[OutputItem]
- Remove ToolChoiceAllowed override (generated type is identical)
- Move Sphinx docstring fixes into models_patch.py shim so
make generate-models preserves them instead of overwriting
- Accept emitter upgrade to model_base.py (XML refactor)
- Regenerate _validators.py from current TypeSpec sources
* fix: use polymorphic _deserialize for OutputItem subtypes + contract type tests
- Fix track_completed_output_item to use OutputItem._deserialize(dict, [])
instead of OutputItem(dict) so response.output contains proper
discriminated subtypes (OutputItemMessage, OutputItemFunctionToolCall,
etc.) instead of base OutputItem instances. This ensures handler devs
can use isinstance() and attribute access on output items.
- Add test_public_contract_types.py with 22 tests covering every public
handler/consumer surface for type fidelity:
* context.request → CreateResponse
* context.get_input_items() → Item subtypes
* context.get_input_text() → str
* context.get_history() → OutputItem subtypes (first-ever coverage)
* stream.response → ResponseObject
* stream.response.output → OutputItem subtypes
* Builder emit_* → ResponseStreamEvent subtypes
* Generator convenience → ResponseStreamEvent subtypes
* InMemoryProvider round-trip preserves subtypes
- Add isinstance assertions to existing tests in test_builders.py,
test_event_stream_generators.py, and test_response_event_stream_builder.py
* feat: deterministic session ID derivation from conversational context
Replace random UUID fallback for agent_session_id with deterministic
SHA-256 derivation matching .NET SessionIdDerivation logic:
Priority chain:
1. Explicit agent_session_id from payload (unchanged)
2. Platform env FOUNDRY_AGENT_SESSION_ID (unchanged)
3. Deterministic: SHA256(agent_name:agent_version:partition_hint)
where partition_hint is extracted from conversation_id or
previous_response_id via IdGenerator.extract_partition_key
4. Random 63-char lowercase hex (one-shot, no conversational context)
This ensures session affinity: the same conversation + agent identity
always resolves to the same session ID, enabling stateful backends to
route consistently without requiring explicit session IDs.
New functions in _request_parsing.py:
- derive_session_id() — public deterministic derivation
- _compute_hex_hash() — SHA-256 → 63-char hex
- _generate_random_hex() — os.urandom fallback
- _extract_agent_identity() — name/version from agent_reference
Updated _resolve_session_id() signature to accept agent_reference.
Updated call site in _endpoint_handler.py to pass agent_reference.
Updated all tests (unit + contract) from UUID to 63-char hex format.
Added 14 new derivation tests covering determinism, agent isolation,
version isolation, priority, and non-standard ID formats.
* feat: strongly-typed return types for all emit_* builder methods
Port .NET pattern: every emit_* method now returns its specific event
subtype (e.g. ResponseCreatedEvent, ResponseOutputItemAddedEvent) via
typing.cast() instead of the base ResponseStreamEvent.
Covers all builders:
- ResponseEventStream: 6 lifecycle methods
- OutputItemBuilder / BaseOutputItemBuilder: emit_added, emit_done
- OutputItemMessageBuilder, TextContentBuilder, RefusalContentBuilder
- FunctionCallBuilder, FunctionCallOutputBuilder
- ReasoningSummaryPartBuilder, ReasoningItemBuilder
- FileSearchCall, WebSearchCall, CodeInterpreter, ImageGen,
McpCall, McpListTools, CustomToolCall builders
Adds test_emit_return_types.py with 70 isinstance assertions covering
every public emit_* method across all 16 builder classes.
* refactor: tighten OutputItemBuilder.emit_added/emit_done to accept OutputItem only
Remove dict[str, Any] from the public signature — all item types are
generated models. Internal callers use _emit_added/_emit_done directly.
Also: fix handler guide (emit_failed/emit_incomplete kwargs, request=
pattern), revert CHANGELOG to initial-release form, remove session ID
derivation docs (internal detail).
* refactor: tighten all public API parameters from dict to generated model types
- ResponseEventStream constructor: agent_reference, request, response now
accept only their respective model types (no dict[str, Any])
- Terminal methods (emit_completed/failed/incomplete): usage accepts only
ResponseUsage (no dict[str, Any])
- Convenience generators (output_item_computer_call, _computer_call_output,
_local_shell_call, _function_shell_call, _function_shell_call_output,
_apply_patch_call): all action/output/environment params accept only their
respective generated model types (no dict[str, Any])
- Async mirrors: same tightening as sync counterparts
- emit_annotation_added: annotation accepts only Annotation (no dict)
- _set_terminal_fields: usage tightened
- Internal _build_events: coerce dict→AgentReference before passing to
ResponseEventStream
- Tests updated to use model constructors instead of raw dicts
- Docs updated to show ResponseUsage model usage
* refactor: internalize escape-hatch methods and tighten remaining list[Any] types
- emit_event → _emit_event: internal only, all callers are sibling
emit_* methods and _builders subpackage
- with_output_item_defaults → _with_output_item_defaults: internal only,
called only by _builders._base
- validate_response_event_stream → _validate_response_event_stream:
internal only, called only by _normalize_lifecycle_events
- normalize_lifecycle_events → _normalize_lifecycle_events: internal only,
called only by hosting._endpoint_handler
- Removed both from streaming/__init__.py exports
- output_item_custom_tool_call_output: output tightened from
str | list[Any] to str | list[FunctionAndCustomToolCallOutput]
- OutputItemFunctionCallOutputBuilder.emit_added/emit_done: output
tightened from str | list[Any] to str | list[InputTextContentParam |
InputImageContentParamAutoParam | InputFileContentParam]
- Removed unused Any import from _function.py
* refactor: reduce public API surface — remove EVENT_TYPE alias, internalize 22 symbols
- Remove EVENT_TYPE alias: replaced all ~80 usages across 10 files with
generated_models.ResponseStreamEventType directly
- Remove from streaming exports: EVENT_TYPE, encode_sse_event,
encode_keep_alive_comment
- Remove from hosting exports: CreateSpan, CreateSpanHook,
InMemoryCreateSpanHook, RecordedSpan, build_create_span_tags,
build_platform_server_header, start_create_span,
build_api_error_response, build_invalid_mode_error_response,
build_not_found_error_response, parse_and_validate_create_response,
parse_create_response, to_api_error_response, validate_create_response
- Remove from models exports: ResponseExecution, StreamEventRecord,
StreamReplayState, get_instruction_items, get_output_item_id
- Remove from top-level exports: to_output_item
- Keep public: get_conversation_id, get_input_expanded,
get_content_expanded, get_conversation_expanded,
get_tool_choice_expanded, all builder classes, ResponseEventStream,
TextResponse, all store/Foundry types
---------
Co-authored-by: Ankit Sinha <anksinha@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: rapida <rapida@microsoft.com>1 parent 6b9ea94 commit 310def1
35 files changed
Lines changed: 3108 additions & 788 deletions
File tree
- sdk/agentserver/azure-ai-agentserver-responses
- azure/ai/agentserver/responses
- hosting
- models
- _generated/sdk/models
- _utils
- models
- streaming
- _builders
- docs
- samples
- scripts/generated_shims
- tests
- contract
- e2e
- unit
Lines changed: 0 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | 17 | | |
19 | 18 | | |
20 | 19 | | |
| |||
51 | 50 | | |
52 | 51 | | |
53 | 52 | | |
54 | | - | |
55 | 53 | | |
Lines changed: 0 additions & 32 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | 5 | | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | 6 | | |
25 | 7 | | |
26 | 8 | | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | 9 | | |
Lines changed: 15 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
32 | 36 | | |
33 | | - | |
| 37 | + | |
34 | 38 | | |
35 | | - | |
| 39 | + | |
36 | 40 | | |
37 | 41 | | |
38 | 42 | | |
| |||
208 | 212 | | |
209 | 213 | | |
210 | 214 | | |
211 | | - | |
| 215 | + | |
212 | 216 | | |
213 | 217 | | |
214 | | - | |
215 | | - | |
| 218 | + | |
| 219 | + | |
216 | 220 | | |
217 | 221 | | |
218 | 222 | | |
| |||
342 | 346 | | |
343 | 347 | | |
344 | 348 | | |
345 | | - | |
| 349 | + | |
346 | 350 | | |
347 | 351 | | |
348 | 352 | | |
| |||
469 | 473 | | |
470 | 474 | | |
471 | 475 | | |
472 | | - | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
473 | 479 | | |
474 | 480 | | |
475 | 481 | | |
| |||
833 | 839 | | |
834 | 840 | | |
835 | 841 | | |
836 | | - | |
| 842 | + | |
837 | 843 | | |
838 | 844 | | |
839 | 845 | | |
| |||
Lines changed: 23 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | 36 | | |
38 | 37 | | |
39 | 38 | | |
| |||
126 | 125 | | |
127 | 126 | | |
128 | 127 | | |
129 | | - | |
130 | | - | |
| 128 | + | |
| 129 | + | |
131 | 130 | | |
132 | 131 | | |
133 | 132 | | |
134 | 133 | | |
135 | 134 | | |
136 | 135 | | |
137 | 136 | | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
143 | 142 | | |
144 | 143 | | |
145 | 144 | | |
| |||
308 | 307 | | |
309 | 308 | | |
310 | 309 | | |
311 | | - | |
| 310 | + | |
| 311 | + | |
312 | 312 | | |
313 | 313 | | |
314 | 314 | | |
| |||
510 | 510 | | |
511 | 511 | | |
512 | 512 | | |
513 | | - | |
514 | | - | |
515 | | - | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
516 | 516 | | |
517 | 517 | | |
518 | 518 | | |
| |||
619 | 619 | | |
620 | 620 | | |
621 | 621 | | |
622 | | - | |
| 622 | + | |
623 | 623 | | |
624 | 624 | | |
625 | 625 | | |
| |||
640 | 640 | | |
641 | 641 | | |
642 | 642 | | |
643 | | - | |
| 643 | + | |
644 | 644 | | |
645 | 645 | | |
646 | 646 | | |
| |||
685 | 685 | | |
686 | 686 | | |
687 | 687 | | |
| 688 | + | |
| 689 | + | |
688 | 690 | | |
689 | 691 | | |
690 | 692 | | |
| |||
899 | 901 | | |
900 | 902 | | |
901 | 903 | | |
902 | | - | |
| 904 | + | |
903 | 905 | | |
904 | 906 | | |
905 | 907 | | |
| |||
1105 | 1107 | | |
1106 | 1108 | | |
1107 | 1109 | | |
| 1110 | + | |
| 1111 | + | |
1108 | 1112 | | |
1109 | 1113 | | |
1110 | 1114 | | |
| |||
1381 | 1385 | | |
1382 | 1386 | | |
1383 | 1387 | | |
| 1388 | + | |
| 1389 | + | |
1384 | 1390 | | |
1385 | 1391 | | |
1386 | 1392 | | |
| |||
1444 | 1450 | | |
1445 | 1451 | | |
1446 | 1452 | | |
| 1453 | + | |
| 1454 | + | |
1447 | 1455 | | |
1448 | 1456 | | |
1449 | 1457 | | |
| |||
0 commit comments