Skip to content

feat: add OpenAI tool calling for Responses, Chat, and Prompt - #2626

Open
Rana Singh (ranadeepsingh) wants to merge 18 commits into
microsoft:masterfrom
ranadeepsingh:feat/openai-responses-tool-calling
Open

feat: add OpenAI tool calling for Responses, Chat, and Prompt#2626
Rana Singh (ranadeepsingh) wants to merge 18 commits into
microsoft:masterfrom
ranadeepsingh:feat/openai-responses-tool-calling

Conversation

@ranadeepsingh

@ranadeepsingh Rana Singh (ranadeepsingh) commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

What this changes

  • Adds function/tool calling to OpenAIResponses, OpenAIChatCompletion, and both OpenAI modes of OpenAIPrompt.
  • Supports scalar and per-row tools, tool_choice, and parallel_tool_calls, plus Responses-only max_tool_calls.
  • Converts one Spark-facing function definition to each endpoint's wire contract:
    • Chat Completions: nested tools[].function
    • Responses: flat function tools
  • Projects Chat and Responses calls into one structured toolCallsCol DataFrame contract.
  • Supports typed Responses function_call_output continuations and Chat assistant tool_calls plus role="tool" continuation messages.
  • Adds modern Responses request fields, Python list/dict ergonomics, generated column helpers, offline/local-HTTP integration coverage, and DataFrame-oriented notebooks.

Endpoint contracts

Concern Chat Completions Responses
Assistant call choices[].message.tool_calls[] output[] item with type: "function_call"
Correlation key tool_calls[].id function_call.call_id
Tool result role: "tool" + tool_call_id type: "function_call_output" + call_id
Arguments JSON-encoded string JSON-encoded string
Stored continuation Replay the message history previous_response_id or conversation
Stateless continuation Replay messages Replay the original input and every prior output item, including encrypted reasoning items

SynapseML transports definitions, calls, and results; it does not execute application functions. User code must validate the model-generated name/arguments, perform authorized work, and return one result for every call ID.

Rebase and hardening

The original 13 commits are rebased onto master at 3498243d0b5534117eb24c0c3cebde3eb1c1a963. The final review head is 6b3b7a026e8b3d4362ca628067b3b74ead51f1cf.

The rebased implementation additionally:

  • preserves target-branch multimodal message validation while retaining nested Chat tool calls;
  • supports Responses continuation-only rows without requiring messagesCol;
  • silently skips null/empty Chat message arrays without an output, error, or HTTP request;
  • keeps request serialization null-safe and isolates malformed row-bound values in errorCol;
  • validates strict function schemas recursively, including additionalProperties: false, complete required sets, and nested/composed object schemas;
  • warns on Azure when strict schemas are combined with parallel calls, while preserving the caller's payload;
  • rejects collisions among public input/output columns before execution;
  • declares Responses continuation array elements, call_id, and output non-nullable while retaining runtime guards;
  • makes Chat reject the Responses-only replayItemsColumn helper with a clear UnsupportedOperationException;
  • keeps the generated Chat Python surface limited to supported helpers;
  • validates Chat content before recursively encoding the complete row so malformed short rows produce stable structural errors; and
  • replays the minimal missing OpenAI multimodal baseline for Spark 4.1 while preserving that branch's Scala 2.13 collection adaptations.

Strict schemas and Azure

For strict: true, every object schema must set additionalProperties: false, every defined property must appear in required, and required cannot name undefined properties. Optional business values remain required keys with nullable types such as ["string", "null"].

Current Azure guidance requires parallel_tool_calls: false with strict Structured Outputs. The notebooks set it explicitly. SynapseML warns rather than silently mutating or rejecting the request because endpoint and proxy capabilities can differ.

Compatibility and deliberate limits

  • Existing text-first OpenAIPrompt behavior and the default Chat Completions mode remain unchanged.
  • Public JVM response case-class arities remain unchanged; widened wire parsing uses internal V2 schemas.
  • gpt-5-mini and gpt-5.1 currently document Chat Completions, Responses, function calling, and Structured Outputs support. SynapseML does not infer deployment capability.
  • The typed Responses continuation column accepts string outputs. Newer image/file output arrays require raw input items or a future typed extension.
  • Provider built-in/custom tools can pass through, but typed projection and automatic orchestration are function-call focused.
  • Spark HTTP work is at least once. Materialize paid turns and make side effects idempotent by call ID.

Validation

Final-head local validation:

  • 60 tests passed across the six offline OpenAI tool-calling suites, including Chat and Responses local-HTTP integrations.
  • 25 tests passed across the two multimodal suites that reproduced the Azure OpenAI shard failures.
  • Cognitive main/test Scala compilation completed under Java 11 / Spark 3.5.
  • Cognitive main and test Scalastyle completed with zero errors, warnings, or infos.
  • The exact Spark 4.1 release replay completed full sbt test:compile under Java 17 / Scala 2.13.
  • All 72 release-pipeline configuration tests passed.
  • Black left all 202 checked Python files unchanged.
  • Exact payload tests cover initial calls, stored/stateless continuations, row-isolated validation errors, shared projections, Prompt delegation, persistence, and generated Python surface rules.

The rebased series also completed sbt codegen, the focused generated-wrapper Python suite (5/5), and notebook JSON/Python-cell validation before the final compatibility follow-up. No credentialed OpenAI request was run locally for this final head. Exact-head Azure validation build 232986628 passed all 77 reported checks, including OpenAI, Spark 4.1 release compatibility, Search1, and LightGBM4.

References

Copilot AI lite review requested due to automatic review settings August 12, 2026 20:30
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions

Copy link
Copy Markdown

Hey Rana Singh (@ranadeepsingh) 👋!
Thank you so much for contributing to our repository 🙌.
Someone from SynapseML Team will be reviewing this pull request soon.

We use semantic commit messages to streamline the release process.
Before your pull request can be merged, you should make sure your first commit and PR title start with a semantic prefix.
This helps us to create release messages and credit you for your hard work!

Examples of commit messages with semantic prefixes:

  • fix: Fix LightGBM crashes with empty partitions
  • feat: Make HTTP on Spark back-offs configurable
  • docs: Update Spark Serving usage
  • build: Add codecov support
  • perf: improve LightGBM memory usage
  • refactor: make python code generation rely on classes
  • style: Remove nulls from CNTKModel
  • test: Add test coverage for CNTKModel

To test your commit locally, please follow our guild on building from source.
Check out the developer guide for additional guidance on testing your change.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends SynapseML’s OpenAI integrations to support the OpenAI Responses API tool/function-calling workflow end-to-end (tool declarations, structured tool calls, and typed continuation outputs), while expanding support for newer Responses parameters and improving schema/DTO compatibility.

Changes:

  • Adds tool normalization/validation utilities and Spark column helpers for tool calls, replay items, and typed function_call_output continuations.
  • Extends OpenAIResponses and OpenAIPrompt to support scalar + per-row tool configuration, modern Responses fields, and compatibility-preserving response parsing (via an internal V2 schema).
  • Adds Scala + Python tests (offline, integration, and opt-in live) plus documentation notebooks illustrating safe, explicit DataFrame workflows.
Show a summary per file
File Description
docs/Explore Algorithms/OpenAI/Quickstart - OpenAI Responses Tool Calling.ipynb New notebook documenting a two-turn tool-calling + continuation Spark workflow, with safety/idempotency guidance.
docs/Explore Algorithms/OpenAI/Quickstart - Multimodal OpenAI Prompter with Responses API.ipynb Adds a “next steps” section pointing users to the tool-calling workflow.
core/src/test/scala/com/microsoft/azure/synapse/ml/param/VerifyUntypedArrayParam.scala Expands test coverage for widened AnyJsonFormat encoding/decoding (null/Long/Float/JsValue).
core/src/main/scala/com/microsoft/azure/synapse/ml/param/UntypedArrayParam.scala Widens AnyJsonFormat to support JsValue, null, Long, and Float serialization, and JsNull reads.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/ToolTestFixtures.scala Introduces shared fixtures for tool JSON and representative Responses payloads.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolUtilsSuite.scala Adds unit tests for tool parsing/normalization, tool choice validation, and schema contracts.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolsLiveSuite.scala Adds credential-gated live smoke tests for real tool calls and continuations.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponsesToolsSuite.scala Adds comprehensive offline tests for serialization, per-row tool params, continuation ordering, and response parsing behavior.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponsesToolsIntegrationSuite.scala Adds local HTTP stub integration tests to verify executor behavior without external dependencies.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponsesSuite.scala Updates tests to use the internal V2 Responses schema.
cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIPromptToolsSuite.scala Adds Prompt-level tests ensuring tool/modern params forwarding and explicit Responses-only validation.
cognitive/src/test/python/synapsemltest/services/openai/test_OpenAIResponsesTools.py Adds Python tests for tool setter ergonomics, column helpers, and save/load behavior.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolUtils.scala Adds pure tool/toolChoice/input-items parsing + normalization + validation utilities for Responses tool payloads.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolPythonOverrides.scala Adds generated Python convenience methods for tool configuration and column helpers.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolParams.scala Adds shared tool params + Prompt opt-in structured outputs (toolCallsCol, responseStructCol).
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolColumns.scala Adds Spark column helpers for extracting tool calls and deterministic replay items from Responses structs.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAISchemas.scala Adds an internal V2 Responses schema to widen parsing while keeping public DTOs stable.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponsesModernParams.scala Adds modern Responses parameters and merging logic (including nested reasoning extras).
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala Implements tool payload support, continuation inputs, V2 schema parsing, tool call projection, and row-level validation.
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIPrompt.scala Wires tool + modern Responses params through Prompt, adds Responses-only validation, and adds optional structured outputs.

Review details

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (2)

cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIPromptToolsSuite.scala:139

  • The standalone spark expression is a no-op and can be removed (it doesn’t affect the test and can trip style checks for unused expressions).
    spark
    val prompt = new OpenAIPrompt()

cognitive/src/test/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIPromptToolsSuite.scala:161

  • The standalone spark expression is a no-op and can be removed (it doesn’t affect the test and can trip style checks for unused expressions).
    spark
    val prompt = configuredPrompt
  • Files reviewed: 20/20 changed files
  • Comments generated: 2
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 13, 2026
## Summary
Fix the active review findings and failed PR validation for OpenAI Responses tool calling. Empty message arrays now skip correctly, continuation-only requests remain supported, OpenAIPrompt tests initialize Spark explicitly, the Databricks notebook avoids mutating a static Spark setting, and the complete PR patch applies and compiles on spark4.1.

## Prompting Intent
The engineer asked to fix every failed test and active review comment on PR microsoft#2626, rebase onto the latest master branch, preserve the new Responses and OpenAIPrompt tool-calling API, and rerun Azure Pipelines validation.

## Linked Sources
- Pull request and review comments: microsoft#2626
- Failed Azure Pipelines build 230906067: https://msdata.visualstudio.com/SynapseML/_build/results?buildId=230906067
- Requirements and reviewed design: Copilot session ae945b9b-3010-450e-a313-5ff439dad2a5
- Azure Boards: no work item ID was provided for this GitHub contribution

## Rationale
Keep the release replay structural rather than branch-specific. Prompt-only tool parameters are layered through an internal mixin alias so master retains its wrapper model while spark4.1 retains its existing wrapper divergence without merge conflicts. Continuation items are assembled through the optional request map and production transforms inject an empty typed messages column only when needed, preserving the established prepareEntity shape across Scala 2.12 and 2.13. Tests use an explicit Spark-session assertion instead of a bare expression, and the notebook documents cluster-time speculation configuration because managed runtimes reject changing it at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
Copilot AI review requested due to automatic review settings August 13, 2026 02:13
@ranadeepsingh
Rana Singh (ranadeepsingh) force-pushed the feat/openai-responses-tool-calling branch from 7bb0410 to 2953c2c Compare August 13, 2026 02:13
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:161

  • prepareEntity calls .toSeq on the result of r.getAs[Seq[Row]](getMessagesCol). If messagesCol exists but the row value is null (common for continuation-only rows that still carry a nullable messages column), this will throw a NPE before the request is built. Treat null messages as an empty sequence so stored continuations can run without requiring non-null messages per row.
  override protected[openai] def prepareEntity: Row => Option[AbstractHttpEntity] = {
    r =>
      lazy val optionalParams: Map[String, Any] = getOptionalParams(r)
      val messages = r.getAs[scala.collection.Seq[Row]](getMessagesCol).toSeq
      Some(getStringEntity(messages, optionalParams))
  }
  • Files reviewed: 20/20 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 13, 2026
## Summary
Fix the active review findings and failed PR validation for OpenAI Responses tool calling. Empty message arrays now skip correctly, continuation-only requests remain supported, OpenAIPrompt tests initialize Spark explicitly, the Databricks notebook avoids mutating a static Spark setting, and the complete PR patch applies and compiles on spark4.1.

## Prompting Intent
The engineer asked to fix every failed test and active review comment on PR microsoft#2626, rebase onto the latest master branch, preserve the new Responses and OpenAIPrompt tool-calling API, and rerun Azure Pipelines validation.

## Linked Sources
- Pull request and review comments: microsoft#2626
- Failed Azure Pipelines build 230906067: https://msdata.visualstudio.com/SynapseML/_build/results?buildId=230906067
- Requirements and reviewed design: Copilot session ae945b9b-3010-450e-a313-5ff439dad2a5
- Azure Boards: no work item ID was provided for this GitHub contribution

## Rationale
Keep the release replay structural rather than branch-specific. Prompt-only tool parameters are layered through an internal mixin alias so master retains its wrapper model while spark4.1 retains its existing wrapper divergence without merge conflicts. Continuation items are assembled through the optional request map and production transforms inject an empty typed messages column only when needed, preserving the established prepareEntity shape across Scala 2.12 and 2.13. Tests use an explicit Spark-session assertion instead of a bare expression, and the notebook documents cluster-time speculation configuration because managed runtimes reject changing it at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 13, 2026
## Summary
Add tool and function calling to OpenAIChatCompletion and the default Chat Completions path in OpenAIPrompt. Function definitions and named choices are converted to the Chat wire schema, assistant tool calls and tool result messages are preserved for continuations, and tool calls project to the same DataFrame contract as Responses. Update the live Responses bad-input assertion to match intentional empty-message skipping.

## Prompting Intent
The engineer asked to fix the failing tests on PR microsoft#2626 without unnecessary blank-line churn, rebase onto current master, and support tool calling through Chat Completions rather than rejecting it when OpenAIPrompt uses its default API type.

## Linked Sources
- Pull request: microsoft#2626
- Failed Azure Pipelines build 230948799: https://msdata.visualstudio.com/SynapseML/_build/results?buildId=230948799
- OpenAI Chat Completions API: https://developers.openai.com/api/reference/resources/chat
- OpenAI function calling guide: https://developers.openai.com/api/docs/guides/function-calling
- Requirements and review context: Copilot session ae945b9b-3010-450e-a313-5ff439dad2a5
- Azure Boards: no work item ID was provided for this GitHub contribution

## Rationale
Keep one Spark-facing tools and toolCallsCol contract while translating only at the service boundary: Responses uses flat function definitions and Chat Completions uses nested function objects. Preserve public response DTO arities through an internal Chat response schema, and require explicit assistant tool_calls plus role=tool messages for Chat continuations rather than executing tools automatically. Separate common tool parameters from Responses-only maxToolCalls so generated Chat APIs match the endpoint signature. Per-row validation shadows invalid message rows before HTTP execution, preserving DataFrame error isolation and at-least-once request semantics.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
Copilot AI review requested due to automatic review settings August 13, 2026 05:50
@ranadeepsingh
Rana Singh (ranadeepsingh) force-pushed the feat/openai-responses-tool-calling branch from 2953c2c to dffdc0d Compare August 13, 2026 05:50
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@ranadeepsingh Rana Singh (ranadeepsingh) changed the title feat: add tool calling to OpenAI Responses and Prompt feat: add OpenAI tool calling for Responses, Chat, and Prompt Aug 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolColumns.scala:37

  • FunctionCallOutputStructType does not set containsNull = false, but toFunctionCallOutputs assumes every array element is a non-null Row and will throw if a null element is present. Mark the array as non-nullable and fail fast with a clear error if a null element still appears.
  val FunctionCallOutputStructType: ArrayType = ArrayType(StructType(Seq(
    StructField("call_id", StringType),
    StructField("output", StringType),
    StructField("status", StringType)
  )))
  • Files reviewed: 23/23 changed files
  • Comments generated: 1
  • Review effort level: Lite

Rana Singh (ranadeepsingh) pushed a commit to ranadeepsingh/SynapseML that referenced this pull request Aug 13, 2026
## Summary
Update the Python tool-calling tests for Chat Completions support and synchronize Python-side OpenAIPrompt parameters before API-dependent toolCallsColumn and replayItemsColumn helpers call the JVM.

## Prompting Intent
The engineer asked to fix all failing tests on PR microsoft#2626 while preserving dual Responses and Chat Completions tool calling, avoiding unnecessary whitespace changes, and rerunning Azure Pipelines validation.

## Linked Sources
- Pull request: microsoft#2626
- Failed Azure Pipelines build 230974693: https://msdata.visualstudio.com/SynapseML/_build/results?buildId=230974693
- Failing shard: PythonTests cognitive
- Requirements and review context: Copilot session ae945b9b-3010-450e-a313-5ff439dad2a5
- Azure Boards: no work item ID was provided for this GitHub contribution

## Rationale
The previous Python assertions treated tools and replay output as Responses-only, but tools are now shared with Chat Completions while maxToolCalls and replayItemsColumn remain Responses-only. Direct Python column-helper calls also bypassed PySpark's normal transform-time parameter transfer, so setApiType("responses") was not visible to the Java projection method. Reusing PySpark's standard parameter synchronization fixes both Chat and Responses projection selection without changing the public API or duplicating endpoint logic in Python.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
Copilot AI review requested due to automatic review settings August 13, 2026 07:43
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolColumns.scala:30

  • FunctionCallOutputStructType currently allows null array elements and marks call_id/output/status as nullable, but toFunctionCallOutputs later requires non-blank call_id and non-null output (and will NPE if an array element itself is null). Tightening the schema to containsNull=false and making call_id/output non-nullable helps catch invalid continuation rows earlier (schema/analysis) and keeps the contract consistent with ToolCallStructType.
  val FunctionCallOutputStructType: ArrayType = ArrayType(StructType(Seq(
    StructField("call_id", StringType),
    StructField("output", StringType),
    StructField("status", StringType)
  )))
  • Files reviewed: 23/23 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

SynapseML CI and others added 4 commits August 26, 2026 02:11
## Summary
Add a dedicated OpenAI tool-use tutorial covering Chat Completions and Responses API DataFrame workflows, then link it from the main OpenAI guide and website sidebar.

## Prompting Intent
The engineer asked for a new notebook like the existing OpenAI tutorial that demonstrates tool use, with meaningful end-to-end patterns for both supported OpenAI APIs and clear, intuitive Spark DataFrame usage.

## Linked Sources
- Pull request: microsoft#2626
- Main OpenAI tutorial: docs/Explore Algorithms/OpenAI/OpenAI.ipynb
- Responses tool-calling quickstart: docs/Explore Algorithms/OpenAI/Quickstart - OpenAI Responses Tool Calling.ipynb

## Rationale
Use a focused notebook instead of expanding the already broad OpenAI guide. A shared weather tool keeps the two API protocols directly comparable, while explicit argument validation, tool-result joins, continuation messages, materialization, and call-id idempotency show the production concerns unique to distributed Spark execution without hiding application-owned tool execution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
## Summary
Use generated Docusaurus document routes for the OpenAI tool-use links so the website build resolves both the tutorial entry point and its deeper Responses quickstart.

## Prompting Intent
The engineer asked for the tool-use notebook to be published in PR microsoft#2626 with failing tests and checks fixed, while avoiding unrelated whitespace or notebook churn.

## Linked Sources
- Pull request: microsoft#2626
- Failed website run: https://github.com/microsoft/SynapseML/actions/runs/31747799767
- Azure validation build: https://msdata.visualstudio.com/A365/_build/results?buildId=231080451

## Rationale
Docusaurus serves converted notebooks as extensionless document routes. Removing the source `.ipynb` suffix and using a parent-relative sibling route fixes both broken links while retaining useful relative navigation in generated documentation and changing only the two failing link targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae945b9b-3010-450e-a313-5ff439dad2a5
Addresses the two suppressed Copilot review comments on this PR.

`FunctionCallOutputStructType` is declared as `ArrayType(StructType(...))`,
which defaults to `containsNull = true`, so a user-supplied outputs array may
legally contain null elements. `toFunctionCallOutputs` then called
`row.getAs[String]("call_id")` directly and threw a bare NullPointerException
instead of the actionable IllegalArgumentException the surrounding validation
already produces for blank call_id, null output, and duplicate call_id.

Guard each element with `Option(row)` and raise the same
`function_call_output <index>: ...` style error used by the neighbouring
checks. The guard is enforced at runtime rather than by tightening the public
schema to `containsNull = false`, because that schema is part of the
user-facing contract and Spark does not reliably enforce declared nullability
on incoming data, so a runtime check is the stronger fix.

Verified by reverting only the source change: the new assertion fails with
"Expected exception java.lang.IllegalArgumentException to be thrown, but
java.lang.NullPointerException was thrown", and passes with the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GitHub-PR: microsoft#2626

## Summary
Fix Responses continuation-only execution after rebasing, validate strict
function schemas recursively, prevent public output-column collisions, and
document Azure's strict-schema parallel-call restriction. Extract Responses
message encoding into a focused helper to keep the service implementation
reviewable.

## Prompting Intent
The engineer asked to rebase PR microsoft#2626, verify current Chat Completions and
Responses tool calling for gpt-5-mini and gpt-5.1, correct discrepancies, and
prepare an explanatory PDF with properly formatted and highlighted examples.
All work had to remain isolated in the existing PR worktree.

## Linked Sources
- Pull request: microsoft#2626
- OpenAI function calling guide: https://developers.openai.com/api/docs/guides/function-calling
- OpenAI conversation state guide: https://developers.openai.com/api/docs/guides/conversation-state
- OpenAI reasoning guide: https://developers.openai.com/api/docs/guides/reasoning
- OpenAI gpt-5-mini model page: https://developers.openai.com/api/docs/models/gpt-5-mini
- OpenAI gpt-5.1 model page: https://developers.openai.com/api/docs/models/gpt-5.1
- Azure OpenAI function calling: https://learn.microsoft.com/azure/ai-foundry/openai/how-to/function-calling
- Azure structured outputs: https://learn.microsoft.com/azure/ai-foundry/openai/how-to/structured-outputs
- PR review threads: microsoft#2626 (comment)

## Rationale
Preserve the PR's API-neutral Spark surface while adapting function definitions
to each endpoint's wire format. Fail locally for provider-independent strict
JSON Schema violations, but warn rather than reject provider-specific Azure
strict-plus-parallel combinations because custom proxies and endpoint
capabilities can differ. Keep function execution application-controlled and
correlate every result by call ID, not function name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 26, 2026 12:37
@ranadeepsingh
Rana Singh (ranadeepsingh) force-pushed the feat/openai-responses-tool-calling branch from 87f5798 to f70205d Compare August 26, 2026 12:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 29/29 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread docs/Explore Algorithms/OpenAI/OpenAI_ToolUse.ipynb
GitHub-PR: microsoft#2626

## Summary
Declare Responses function-call output arrays and their required fields as
non-nullable, and make Chat Completions reject the Responses-only replay helper
with a clear unsupported-operation error.

## Prompting Intent
The engineer asked to rebase PR microsoft#2626, verify modern Chat Completions and
Responses tool calling for gpt-5-mini and gpt-5.1, correct all discrepancies,
and leave the pull request merge-ready with an explanatory PDF. This follow-up
addresses the two remaining unresolved review findings on the rebased head.

## Linked Sources
- Pull request: microsoft#2626
- Function-call output schema review: microsoft#2626 (comment)
- Chat replay helper review: microsoft#2626 (comment)
- OpenAI function calling guide: https://developers.openai.com/api/docs/guides/function-calling

## Rationale
The continuation schema is new in this unmerged change, so its declared Spark
type should accurately state that array elements, call IDs, and outputs are
required. Runtime validation remains in place because Spark nullability metadata
is not an enforcement boundary and inferred schemas may be more permissive.
Overriding the inherited replay helper on Chat preserves the shared tool-call
projection surface while preventing a Responses-shaped expression from failing
later during Spark analysis; generated Python continues to omit that helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:28

  • ResponseFormat uses the misspelled identifier paylodName. To improve readability without forcing updates outside this diff hunk, rename the constructor field to payloadName and add a backward-compatible paylodName alias so existing references still compile.
object OpenAIResponseFormat extends Enumeration {
  case class ResponseFormat(paylodName: String) extends super.Val(paylodName)
  • Files reviewed: 29/29 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

GitHub-PR: microsoft#2626

## Summary
Treat empty Chat Completions message arrays like null input rows during
transform validation so they are skipped without an output, error, or HTTP
request. Add a local HTTP executor regression for both null and empty arrays.

## Prompting Intent
The engineer asked to make PR microsoft#2626 merge-ready after rebasing and to correct
any discrepancies in SynapseML's modern OpenAI tool-calling behavior. A
final-head review found that the public bad-input contract expected empty
message arrays to be silently skipped while structured validation emitted an
error before the inherited skip logic ran.

## Linked Sources
- Pull request: microsoft#2626
- Review finding: microsoft#2626 (comment)
- OpenAI Chat Completions API: https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create

## Rationale
The transformer already treats null and empty arrays as rows with no request to
send. Applying structural validation only to present messages makes that
behavior consistent across validation, shouldSkip, and the existing robustness
test. Non-empty malformed messages still produce row-isolated errors, while
the executor-level test proves skipped rows never reach the HTTP server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (5)

Previously missed (5) — in code that hasn't changed since the last review.

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolUtils.scala:433

  • parseInputItems accepts either a JSON array or a single JSON object (returning Vector(obj)), but the thrown error message only mentions arrays. This is misleading for users debugging malformed row-bound values.
  def parseInputItems(json: String): Vector[JsValue] =
    Option(json).map(_.trim).filter(_.nonEmpty).map { text =>
      Try(text.parseJson).toOption match {
        case Some(JsArray(items)) => items
        case Some(obj: JsObject) => Vector(obj)
        case _ =>
          throw new IllegalArgumentException(
            "inputItemsCol must contain a JSON array of Responses input items")
      }

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolParams.scala:348

  • validatePromptToolOutputColumns checks for existing columns using schema.fieldNames.contains, which is case-sensitive. In Spark's default case-insensitive mode, this can allow toolCallsCol/responseStructCol to collide with an existing column that differs only by case, leading to overwrite or analysis errors at runtime.
    val requested = Seq(get(toolCallsCol), get(responseStructCol)).flatten
    requested.foreach { columnName =>
      require(
        !schema.fieldNames.contains(columnName),
        s"Column '$columnName' already exists in the input DataFrame")
    }

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:631

  • The toolCallsCol collision check uses schema.fieldNames.contains, which is case-sensitive. With Spark's default case-insensitive resolver, this can miss an existing column that differs only by case and allow overwriting it.
    get(toolCallsCol).foreach { columnName =>
      require(
        !schema.fieldNames.contains(columnName),
        s"Column '$columnName' already exists in the input DataFrame")
    }

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:701

  • requireFieldShape looks up a column with schema.find(_.name == columnName), which is case-sensitive. In Spark's default case-insensitive mode this can incorrectly report that a configured column doesn't exist (or fail to validate the correct one).
  private def requireFieldShape(
      schema: StructType,
      columnName: String,
      expectedType: DataType): Unit = {
    val actualType = schema.find(_.name == columnName).map(_.dataType).getOrElse {
      throw new IllegalArgumentException(
        s"Column '$columnName' was not found in the input DataFrame")
    }

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIChatCompletion.scala:668

  • validateChatToolSetup checks for existing toolCallsCol using schema.fieldNames.contains, which is case-sensitive. In Spark's default case-insensitive mode, this can miss collisions with existing columns that differ only by case and lead to overwriting/analysis errors.
  private def validateChatToolSetup(schema: StructType): Unit = {
    get(toolCallsCol).foreach { columnName =>
      require(
        !schema.fieldNames.contains(columnName),
        s"Column '$columnName' already exists in the input DataFrame")
    }
  • Files reviewed: 29/29 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

## Summary
Replay the minimal missing OpenAI multimodal prerequisite chain on spark4.1
and keep the Responses helper block mergeable with that branch's Scala 2.13
collection adaptations.

## Prompting Intent
The engineer asked to rebase GitHub PR microsoft#2626, verify current Chat Completions
and Responses tool calling, correct discrepancies, and make the PR
merge-ready from an isolated worktree.

## Linked Sources
- Pull request: microsoft#2626
- OpenAI function calling guide: https://platform.openai.com/docs/guides/function-calling
- OpenAI Responses API reference: https://platform.openai.com/docs/api-reference/responses
- OpenAI Chat Completions API reference: https://platform.openai.com/docs/api-reference/chat/create

## Rationale
Use the repository's existing release-compatibility prerequisite mechanism
instead of changing pipeline logic or adding port-specific behavior to
master. Alternate-index replay reduced the prerequisite set from 21
candidates to the 15 commits actually required. Relocating private helper
methods is behavior-neutral and lets three-way replay preserve spark4.1's
Scala 2.13 Seq conversions; normalizing null messages in the shared entity
builder preserves continuation-only requests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 30/30 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

## Summary
Validate Chat message content before recursively encoding the full row so
short rows produce stable structural errors, and align the multimodal suites
with the documented null/empty-message skip contract.

## Prompting Intent
The engineer asked to make GitHub PR microsoft#2626 merge-ready after rebasing it,
verify modern OpenAI tool calling, correct discrepancies, and preserve
cross-runtime SynapseML behavior.

## Linked Sources
- Pull request: microsoft#2626
- Exact-head Azure build: https://msdata.visualstudio.com/b9b2accc-2d1c-45b3-9d24-0eb5d78cc47f/_build/results?buildId=232941908
- Failed OpenAI job: https://msdata.visualstudio.com/b9b2accc-2d1c-45b3-9d24-0eb5d78cc47f/_build/results?buildId=232941908&view=logs&jobId=30886130-6c10-51cc-2b7c-fdfe28a165e1

## Rationale
The recursive serializer accessed schema fields before the dedicated
role/content validators, leaking ArrayIndexOutOfBoundsException for malformed
Rows. Computing validated content first preserves valid payloads while
restoring the intended IllegalArgumentException contract. Empty message arrays
already skip HTTP execution by design, so the older multimodal assertions now
match the public no-request/no-output/no-error behavior instead of
dereferencing a deliberately absent error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:704

  • The Azure strict-schema warning suggests only the scalar setter (setParallelToolCalls(false)), but parallelToolCalls can also be column-bound. With setParallelToolCallsCol(...), this message is misleading (and may warn even when all rows set false). Adjust the wording to mention both scalar and per-row configuration so users can fix the issue correctly.
  private def warnOnAzureStrictParallel(isAzure: Boolean): Unit = {
    if (isAzure && strictToolsNeedSequentialCalls) {
      logWarning(
        "Azure OpenAI strict function schemas require parallel_tool_calls=false; " +
          "call setParallelToolCalls(false) to avoid HTTP 400")
    }

cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIChatCompletion.scala:675

  • This Azure strict-schema warning text only mentions setParallelToolCalls(false), but parallelToolCalls can be set per-row via setParallelToolCallsCol(...). Updating the message avoids pushing callers toward the wrong configuration approach when they intentionally use row-bound params.
    val configuredUrl = get(url).orElse(getDefault(url)).map(_.toLowerCase)
    if (configuredUrl.exists(_.contains("azure.com")) && strictToolsNeedSequentialCalls) {
      logWarning(
        "Azure OpenAI strict function schemas require parallel_tool_calls=false; " +
          "call setParallelToolCalls(false) to avoid HTTP 400")
    }
  • Files reviewed: 32/32 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@ranadeepsingh

Copy link
Copy Markdown
Collaborator Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.22316% with 94 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.57%. Comparing base (3498243) to head (6b3b7a0).

Files with missing lines Patch % Lines
...e/synapse/ml/services/openai/OpenAIToolUtils.scala 81.43% 44 Missing ⚠️
.../services/openai/OpenAIResponsesMessageUtils.scala 84.21% 21 Missing ⚠️
.../synapse/ml/services/openai/OpenAIToolParams.scala 92.53% 10 Missing ⚠️
...e/synapse/ml/services/openai/OpenAIResponses.scala 96.37% 9 Missing ⚠️
.../services/openai/OpenAIResponsesModernParams.scala 96.06% 5 Missing ⚠️
...apse/ml/services/openai/OpenAIChatCompletion.scala 95.65% 4 Missing ⚠️
...synapse/ml/services/openai/OpenAIToolColumns.scala 98.38% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2626      +/-   ##
==========================================
+ Coverage   87.33%   87.57%   +0.24%     
==========================================
  Files         338      344       +6     
  Lines       20234    21132     +898     
  Branches     2059     2192     +133     
==========================================
+ Hits        17671    18506     +835     
- Misses       2563     2626      +63     
Files with missing lines Coverage Δ
...zure/synapse/ml/services/openai/OpenAIPrompt.scala 95.54% <100.00%> (+1.59%) ⬆️
...ure/synapse/ml/services/openai/OpenAISchemas.scala 100.00% <ø> (ø)
...ml/services/openai/OpenAIToolPythonOverrides.scala 100.00% <100.00%> (ø)
...oft/azure/synapse/ml/param/UntypedArrayParam.scala 83.33% <100.00%> (+5.91%) ⬆️
...synapse/ml/services/openai/OpenAIToolColumns.scala 98.38% <98.38%> (ø)
...apse/ml/services/openai/OpenAIChatCompletion.scala 89.87% <95.65%> (+1.39%) ⬆️
.../services/openai/OpenAIResponsesModernParams.scala 96.06% <96.06%> (ø)
...e/synapse/ml/services/openai/OpenAIResponses.scala 94.03% <96.37%> (+6.02%) ⬆️
.../synapse/ml/services/openai/OpenAIToolParams.scala 92.53% <92.53%> (ø)
.../services/openai/OpenAIResponsesMessageUtils.scala 84.21% <84.21%> (ø)
... and 1 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

3 participants