Skip to content

fix(prompt_deploy): normalize Foundry definition to dict for SDK 2.x compatibility#216

Merged
placerda merged 1 commit into
developfrom
feature/prompt-deploy-sdk-2x-compat
May 31, 2026
Merged

fix(prompt_deploy): normalize Foundry definition to dict for SDK 2.x compatibility#216
placerda merged 1 commit into
developfrom
feature/prompt-deploy-sdk-2x-compat

Conversation

@placerda
Copy link
Copy Markdown
Contributor

Summary

Fixes a hard failure in prompt_deploy stage that fires when the user's prompt differs from the seed (the created action path) and the runner installs azure-ai-projects >= 2.x.

Symptom hit during a live tutorial recording at step 14 of tutorial-prompt-agent-quickstart.md:

azure.core.exceptions.HttpResponseError: (invalid_payload) required:
Required properties ["kind"] are not present

Root cause

Two compounding issues in src/agentops/pipeline/prompt_deploy.py:

  1. _copy_definition returned the wrong shape under SDK 2.x. It called .copy() on the typed PromptAgentDefinition returned by get_version. In SDK 1.x that preserved the typed model so the body serialized as a flat {"kind": "prompt", "model": ..., "instructions": ...}. In SDK 2.x the same .copy() returns a stripped base Model whose JSON shape is {"_data": {"kind": "prompt", ...}}.
  2. _create_agent_version wrote kind at the body root via definition.get("kind"). With the post-.copy() stripped model that returns None. The new Foundry API also treats kind strictly as the polymorphic discriminator inside definition, so a root-level kind is at best ignored.

Combined effect — the JSON that reached the service looked like:

{
  "kind": null,
  "definition": {"_data": {"kind": "prompt", "model": "...", "instructions": "..."}},
  "metadata": {...},
  "description": "..."
}

…which the service rejected as malformed.

Why this only fired on one path

Only the created action path runs _copy_definition + _create_agent_version with the typed SDK model:

Path Definition source Affected?
reused uses existing current directly, no create_version call No
bootstrapped builds a plain dict from prompt_agent_bootstrap No
created round-trips a typed PromptAgentDefinition through .copy() Yes

That matches what the user saw: the first deploy (where the prompt hadn't changed) returned action: reused and succeeded; the second deploy after they edited the prompt blew up.

Fix

In src/agentops/pipeline/prompt_deploy.py:

  • New _definition_to_dict helper normalizes any SDK definition object (dict, typed model with _data, mapping with .items(), or anything with as_dict()) to a plain dict.
  • _copy_definition now always returns a deep-copied plain dict.
  • _create_agent_version drops the root-level kind from the body and sends a clean {definition, metadata, description} shape.

Verified locally with SDK 2.2.0: serialization of a real PromptAgentDefinition through the fixed path produces the expected flat shape with kind inside definition.

Tests

  • Added test_copy_definition_returns_plain_dict_from_sdk_typed_model — regression test that simulates the SDK 2.x .copy() semantics and asserts the result is a flat dict without _data.
  • Added test_create_agent_version_body_uses_flat_definition_dict — asserts the body sent to client.agents.create_version has no root-level kind and a flat definition dict.
  • Full suite: 808 passed, 1 skipped.

Follow-up

This needs a patch release (v0.3.2) so the workflow templates pick the fix up via PyPI on the next pip install agentops-toolkit on CI.

…compatibility

The `created` action path of `prompt_deploy stage` was producing an invalid
request body against azure-ai-projects 2.x because:

1. `_copy_definition` called `.copy()` on the typed `PromptAgentDefinition`
   returned by `get_version`. In SDK 2.x that returns a stripped base
   `Model` whose JSON shape is `{"_data": {...}}` instead of the flat
   payload the Foundry Agents service expects.
2. `_create_agent_version` also wrote `kind` at the body root via
   `definition.get("kind")` — but the post-`.copy()` model returned
   `None` for that key, AND the new API treats `kind` strictly as the
   polymorphic discriminator inside `definition`.

The combined effect was a request body like
`{"kind": null, "definition": {"_data": {"kind": "prompt", ...}}, ...}`,
which the service rejected with
'invalid_payload — Required properties ["kind"] are not present'.

This regression only fired on the `created` path (user changed the prompt
relative to the seed). The `reused` and bootstrap paths never round-trip
the typed model through `.copy()`, so they were unaffected.

Fix:
- New `_definition_to_dict` helper accepts dicts, typed SDK models with
  `_data`, mapping-like objects with `.items()`, or anything with
  `as_dict()`.
- `_copy_definition` now always returns a deep-copied plain dict.
- `_create_agent_version` drops the root-level `kind` from the body and
  sends a clean `{definition, metadata, description}` shape.

Verified locally: serialization of a real `PromptAgentDefinition` (2.2.0)
through the fixed path produces the expected flat shape with `kind`
inside `definition`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@placerda placerda merged commit 52bb6a1 into develop May 31, 2026
7 of 8 checks passed
@placerda placerda deleted the feature/prompt-deploy-sdk-2x-compat branch May 31, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant