Radiology samples: contract update and Quickstart / Advance sample projects#246
Conversation
…sample projects - Sync radiology-extensibility-api.yaml, manifest schema, wire samples, and C# models with internal source-of-truth (envelope: replace wire schemaVersion with extensibilityApiVersion; payloads: drop nested schemaVersion; spec: add x-ms-schema-version annotations) - Update CLI manifest schema, types, prompts, init/generate commands, and quality-check template to require radiologyExtensibilityApiVersion + per-IO schemaVersion (camelCase names) - Add SampleExtension.Radiology.Web.Quickstart (mock-data) and SampleExtension.Radiology.Web.Ai (Azure OpenAI + Foundry Local) sample projects with solution file, READMEs, and .http requests - Add 8 schema-validation tests covering the new radiology contract requirements - Refresh radiology README versioning section and per-sample contract notes for partner clarity
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates the radiology extension contract and CLI scaffolding to use camelCase naming, introduce explicit API/payload schema versioning, and refresh samples accordingly.
Changes:
- Added
radiologyExtensibilityApiVersionto manifests and requiredschemaVersionper tool input/output. - Switched extension/tool naming rules from kebab-case to camelCase and updated radiology media type from
patient-infotopatient-information. - Added new ASP.NET Core workflow sample projects (Quickstart + AI) and aligned radiology OpenAPI/models/samples to the updated contract.
Reviewed changes
Copilot reviewed 58 out of 58 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/dragon-copilot-cli/src/schemas/radiology/radiology-extension-manifest-schema.json | Enforces new manifest fields, name patterns, and per-payload schema versioning. |
| tools/dragon-copilot-cli/src/domains/radiology/types.ts | Updates TypeScript domain types to include new manifest/input/output fields. |
| tools/dragon-copilot-cli/src/domains/radiology/templates/index.ts | Updates radiology template defaults to camelCase, new media type, and schema versions. |
| tools/dragon-copilot-cli/src/domains/radiology/shared/prompts.ts | Updates prompts/choices for new naming and content-type + adds API version prompt. |
| tools/dragon-copilot-cli/src/domains/radiology/commands/init.ts | Includes new manifest field and emits schema versions on generated inputs. |
| tools/dragon-copilot-cli/src/domains/radiology/commands/generate.ts | Includes new manifest field and emits schema versions on generated inputs. |
| tools/dragon-copilot-cli/src/tests/manifest-validation.test.ts | Extends schema-validation tests for new required fields/patterns. |
| tools/dragon-copilot-cli/src/tests/cli-integration.test.ts | Updates integration manifest fixture to match new schema and naming. |
| radiology/src/samples/requests/sample-requests-responses.md | Updates sample docs to new input naming/content-type and schemaVersion usage. |
| radiology/src/samples/requests/ReportRequest-Example.json | Updates sample request envelope field from schemaVersion to extensibilityApiVersion. |
| radiology/src/samples/requests/QualityCheckResultResponse-Example.json | Removes schemaVersion from response sample to match updated response envelope. |
| radiology/src/samples/requests/PatientInfoRequest-Example.json | Updates sample request to patientInformation and new envelope version field. |
| radiology/src/samples/requests/FullRequest-Example.json | Updates combined request sample to patientInformation and new envelope version field. |
| radiology/src/samples/Workflow/SampleExtensions.Radiology.Web.slnx | Adds a solution wrapper for the new workflow sample projects. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/nuget.config | Defines NuGet sources for the Quickstart sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/appsettings.json | Adds sample configuration including auth toggles and mock data path. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/appsettings.Development.json | Development logging defaults for Quickstart. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Services/QualityCheckService.cs | Implements mock-response-based processing for Quickstart sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Services/IQualityCheckService.cs | Adds abstraction for request processing in Quickstart sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/SampleExtension.Radiology.Web.Quickstart.http | Adds HTTP file for local testing Quickstart endpoints. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/SampleExtension.Radiology.Web.Quickstart.csproj | Adds Quickstart ASP.NET Core sample project definition. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/README.md | Documents how to run/configure the Quickstart sample and its contract. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Properties/launchSettings.json | Adds launch profiles/ports for Quickstart sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Program.cs | Configures controllers, swagger, CORS, health, and auth pipeline for Quickstart. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/MockData/qualitycheck-response.json | Provides canned sample responses for Quickstart. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Extensions/WebApplicationExtensions.cs | Adds conditional auth middleware application based on request path. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Extensions/ServiceCollectionExtensions.cs | Adds auth setup and authorization policy wiring (Quickstart). |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Controllers/QualityCheckController.cs | Adds the /v1/process endpoint for Quickstart. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Configuration/AuthenticationOptions.cs | Adds settings model for JWT auth configuration (Quickstart). |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/nuget.config | Defines NuGet sources for AI sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/appsettings.json | Adds auth + Azure OpenAI/Foundry Local configuration knobs. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/appsettings.Development.json | Development logging defaults for AI sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/QualityCheckService.cs | Implements model-backed quality checking and response mapping. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/IQualityCheckService.cs | Adds abstraction for request processing (AI sample). |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/IFoundryLocalService.cs | Defines Foundry Local chat client provider abstraction. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/IAzureOpenAIService.cs | Defines Azure OpenAI chat client provider abstraction. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/FoundryLocalService.cs | Implements Foundry Local initialization, download/load, and lifecycle management. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Services/AzureOpenAIService.cs | Implements Azure OpenAI chat client creation based on config. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/SampleExtension.Radiology.Web.Ai.http | Adds HTTP file for local testing AI sample endpoints. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/SampleExtension.Radiology.Web.Ai.csproj | Adds AI ASP.NET Core sample project definition and dependencies. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/README.md | Documents how to run/configure AI providers and the contract. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Properties/launchSettings.json | Adds launch profiles/ports for AI sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Program.cs | Configures controllers, swagger, CORS, health, and auth pipeline for AI sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Extensions/WebApplicationExtensions.cs | Adds conditional auth middleware application based on request path (AI sample). |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Extensions/ServiceCollectionExtensions.cs | Adds auth setup and authorization policy wiring (AI sample). |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Controllers/QualityCheckController.cs | Adds the /v1/process endpoint for AI sample. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Configuration/OpenAiSettings.cs | Adds configuration model for Azure OpenAI settings. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Configuration/FoundryLocalSettings.cs | Adds configuration model for Foundry Local settings. |
| radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Ai/Configuration/AuthenticationOptions.cs | Adds settings model for JWT auth configuration (AI sample). |
| radiology/src/samples/Workflow/README.md | Adds overview for workflow sample projects, build, and run instructions. |
| radiology/src/models/Dragon.Copilot.Radiology.Models/SessionData.cs | Improves docs and clarifies snake_case JSON fields in the session contract. |
| radiology/src/models/Dragon.Copilot.Radiology.Models/Recommendation.cs | Updates wording and minor doc normalization. |
| radiology/src/models/Dragon.Copilot.Radiology.Models/ProcessResponse.cs | Removes wire schemaVersion and adjusts response properties for updated spec. |
| radiology/src/models/Dragon.Copilot.Radiology.Models/ProcessRequest.cs | Renames envelope fields, adds extension data, and aligns with updated spec. |
| radiology/src/models/Dragon.Copilot.Radiology.Models/PatientInfo.cs | Renames model type to PatientInformation to align with schema naming. |
| radiology/radiology-extensibility-api.yaml | Bumps API version, renames schemas, and updates request/response envelopes and schema version annotations. |
| radiology/README.md | Adds versioning explanation for manifest/API/payload schema axes. |
| radiology/Directory.Packages.props | Adds central package versions for radiology workflow samples. |
Comments suppressed due to low confidence (7)
radiology/src/samples/requests/sample-requests-responses.md:1
- The markdown links to
PatientInformationRequest-Example.json, but the changed sample file in this PR is still namedPatientInfoRequest-Example.json. This will produce a broken link in the docs—either rename the JSON file to match the new link, or update the link to the actual file name.
radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Controllers/QualityCheckController.cs:1 - The controller doc comment says the sample has "no authentication", but the controller itself is protected with
[Authorize(Policy = \"RequiredClaims\")](even if auth can be disabled via config). Update the comment to reflect that authentication/authorization is enforced when enabled.
radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Controllers/QualityCheckController.cs:1 - The controller doc comment says the sample has "no authentication", but the controller itself is protected with
[Authorize(Policy = \"RequiredClaims\")](even if auth can be disabled via config). Update the comment to reflect that authentication/authorization is enforced when enabled.
radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Extensions/WebApplicationExtensions.cs:1 - Using
StartsWithon raw strings for public-route filtering can unintentionally mark unrelated endpoints as public (e.g.,/healthcarestarts with/health) and can be error-prone with path normalization. PreferPathString.StartsWithSegments(...)(or equivalent) for route matching. Also, both samples serve Swagger UI (often at/and/swagger); if Swagger should be reachable when auth is enabled, add explicit public-route handling for those paths (or update the documentation if Swagger is intended to be protected).
radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Extensions/WebApplicationExtensions.cs:1 - Using
StartsWithon raw strings for public-route filtering can unintentionally mark unrelated endpoints as public (e.g.,/healthcarestarts with/health) and can be error-prone with path normalization. PreferPathString.StartsWithSegments(...)(or equivalent) for route matching. Also, both samples serve Swagger UI (often at/and/swagger); if Swagger should be reachable when auth is enabled, add explicit public-route handling for those paths (or update the documentation if Swagger is intended to be protected).
radiology/src/samples/Workflow/SampleExtension.Radiology.Web.Quickstart/Services/QualityCheckService.cs:1 response.Payload?[QualityCheckPayloadKey]can throwKeyNotFoundExceptionwhenPayloadis non-null but does not containqualityCheckResult(the null-conditional only guardsPayload, not the indexer). UseTryGetValue(or a safe helper) when computing the log count to avoid failing startup/first use when the mock JSON shape changes.
tools/dragon-copilot-cli/src/domains/radiology/shared/prompts.ts:1- The CLI prompt captures
schemaVersionbut does not validate the requiredmajor.minorformat (the schema enforces^\\d+\\.\\d+$). Add a validator here so users get immediate feedback instead of generating a manifest that later fails schema validation.
- README: clarify versioning section; extensibilityApiVersion may appear on the request envelope as informational metadata - AI sample: async end-to-end pipeline (IQualityCheckService.ProcessAsync) with CancellationToken propagation through to chat completion - AI sample: gracefully handle malformed model output (try/catch around JSON parse + TryGetProperty) so the extension returns a well-formed response with empty recommendations instead of a 500
| toolName: 'my-radiology-tool', | ||
| toolName: 'myRadiologistsTool', | ||
| toolDescription: 'Processes radiology reports and imaging data', | ||
| endpoint: 'https://api.example.com/radiology/v1/process' |
There was a problem hiding this comment.
Shall we update the sample endpoint to be 'https://api.example.com/radiologists/v1/process'
| /// <summary> | ||
| /// Single entry point of the Radiologists simple extension. | ||
| /// Demonstrates a single-endpoint extension with model binding | ||
| /// performed by the framework and no authentication. |
There was a problem hiding this comment.
Because we actually do have authentication (we have [Authorize(Policy = "RequiredClaims")] and the project configures Entra ID JWT), shall we update the comment to reflect that.
| /// <summary> | ||
| /// Single entry point of the Radiologists simple extension. | ||
| /// Demonstrates a single-endpoint extension with model binding | ||
| /// performed by the framework and no authentication. |
There was a problem hiding this comment.
Because we actually do have authentication (we have [Authorize(Policy = "RequiredClaims")]), shall we update the comment to reflect that.
|
|
||
| var result = await _qualityCheckService.ProcessAsync(payload, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| _logger.LogInformation( |
There was a problem hiding this comment.
This success-path log serializes the entire ProcessResponse (JsonSerializer.Serialize(result)) at Information. Because the response payload can contain report-derived clinical text -- provenance.text is a verbatim excerpt of the report, and the description/reason fields often quote it too -- I think it would be good to keep clinical text out of the logs, to provide a good example to our Partners in case they use it as a template.
So, shall we just log success and message here (like the Python quickstart does), or reduce the logging-level to Debug and omit the report-derived fields?
|
|
||
| var result = await _qualityCheckService.ProcessAsync(payload, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| _logger.LogInformation( |
There was a problem hiding this comment.
This success-path log serializes the entire ProcessResponse (JsonSerializer.Serialize(result)) at Information. Because the response payload can contain report-derived clinical text -- provenance.text is a verbatim excerpt of the report, and the description/reason fields often quote it too -- I think it would be good to keep clinical text out of the logs, to provide a good example to our Partners in case they use it as a template.
So, shall we just log success and message here (like the Python quickstart does), or reduce the logging-level to Debug and omit the report-derived fields?
| - name: qualityCheckResult | ||
| description: Quality check findings and score | ||
| content-type: application/vnd.ms-dragon.rad.quality-check-result+json | ||
| schemaVersion: "1.0" |
There was a problem hiding this comment.
Should we add a 'relevanceFilteringCriteria' section here as well?
| <sample-name>/ | ||
| ├── README.md | ||
| ├── requirements.txt | ||
| └── app/ |
There was a problem hiding this comment.
The app/ layout lists config.py, models.py, service.py but not auth.py, yet the shipped sample puts the Entra ID JWT validation in app/auth.py (and the scaffold prompt requires it). Could we add it to the tree so the canonical layout matches?
e.g.
├── auth.py # Entra ID JWT bearer validation (toggleable)?
| "report": {"reportText": "<radiology report text>"}, | ||
| "patientInformation": { | ||
| "dateOfBirth": "<YYYY-MM-DD>", | ||
| "biologicalSex": "<Male|Female|Other>" |
There was a problem hiding this comment.
The system prompt's input-format example shows biologicalSex as Male|Female|Other, but the BiologicalSex enum (and the scaffold prompt) also include Unknown.
Shall we list all four (Male|Female|Unknown|Other) so the example matches the contract?
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net10.0-windows10.0.26100</TargetFramework> |
There was a problem hiding this comment.
I think this sample is locked to net10.0-windows10.0.26100 / win-x64 because of the Microsoft.AI.Foundry.Local.WinML dependency. Other dependencies like Azure OpenAI, Identity.Web, Swashbuckle are cross-platform, and the service actually prefers Azure OpenAI first and only falls back to Foundry Local. So as it stands, a macOS or Linux Partner can't build or run any of this sample, including the Azure OpenAI path that would work fine for them -- and since the Quickstart and Python samples are mock-only, this is our only "calls a real model" sample, so there's no real-AI example a non-Windows Partner can run today.
I think it would be nice if the cross-platform path weren't gated behind Windows. There are a few ways we could go (e.g. multi-targeting so the Windows-only Foundry piece is isolated, or splitting the on-device path into its own sample).
| <ProjectReference Include="..\..\..\models\Dragon.Copilot.Radiologists.Models\Dragon.Copilot.Radiologists.Models.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
There was a problem hiding this comment.
I think we will also likely want to include our new Radiologists models and extensions as part of the ci.yaml to enable them to build in a pipeline.
(That change could be part of this PR or could be part of a future PR).
Updates the radiology extension samples to the latest contract and adds two runnable sample projects.
Contract Changes:
• ProcessRequest has extensibilityApiVersion (optional) and sessionData (required). ProcessResponse has no wire-level version.
• Payload schemas (Report, PatientInformation, QualityCheckResult) declare versions via the x-ms-schema-version OpenAPI annotation
• Manifest requires top-level radiologyExtensibilityApiVersion and per-input/output schemaVersion
• Spec, manifest schema, sample JSONs, C# models, and CLI all align with this contract
New sample projects:
• Web.Quickstart: minimal ASP.NET Core extension returning a canned mock response
• Web.Ai: AI-powered extension using Azure OpenAI with on-device Foundry Local fallback