feat: add OpenAI tool calling for Responses, Chat, and Prompt - #2626
feat: add OpenAI tool calling for Responses, Chat, and Prompt#2626Rana Singh (ranadeepsingh) wants to merge 18 commits into
Conversation
|
/azp run |
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
Hey Rana Singh (@ranadeepsingh) 👋! We use semantic commit messages to streamline the release process. Examples of commit messages with semantic prefixes:
To test your commit locally, please follow our guild on building from source. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
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_outputcontinuations. - Extends
OpenAIResponsesandOpenAIPromptto 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
sparkexpression 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
sparkexpression 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
## 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
7bb0410 to
2953c2c
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Review details
Suppressed comments (1)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIResponses.scala:161
prepareEntitycalls.toSeqon the result ofr.getAs[Seq[Row]](getMessagesCol). IfmessagesColexists 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
## 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
## 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
2953c2c to
dffdc0d
Compare
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Review details
Suppressed comments (1)
cognitive/src/main/scala/com/microsoft/azure/synapse/ml/services/openai/OpenAIToolColumns.scala:37
FunctionCallOutputStructTypedoes not setcontainsNull = false, buttoFunctionCallOutputsassumes every array element is a non-nullRowand 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
## 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
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
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
## 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>
87f5798 to
f70205d
Compare
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>
There was a problem hiding this comment.
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
ResponseFormatuses the misspelled identifierpaylodName. To improve readability without forcing updates outside this diff hunk, rename the constructor field topayloadNameand add a backward-compatiblepaylodNamealias 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
|
/azp run |
|
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>
There was a problem hiding this comment.
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
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
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>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
## 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>
There was a problem hiding this comment.
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)), butparallelToolCallscan also be column-bound. WithsetParallelToolCallsCol(...), this message is misleading (and may warn even when all rows setfalse). 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), butparallelToolCallscan be set per-row viasetParallelToolCallsCol(...). 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
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
What this changes
OpenAIResponses,OpenAIChatCompletion, and both OpenAI modes ofOpenAIPrompt.tools,tool_choice, andparallel_tool_calls, plus Responses-onlymax_tool_calls.tools[].functiontoolCallsColDataFrame contract.function_call_outputcontinuations and Chat assistanttool_callsplusrole="tool"continuation messages.Endpoint contracts
choices[].message.tool_calls[]output[]item withtype: "function_call"tool_calls[].idfunction_call.call_idrole: "tool"+tool_call_idtype: "function_call_output"+call_idprevious_response_idorconversationSynapseML 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
masterat3498243d0b5534117eb24c0c3cebde3eb1c1a963. The final review head is6b3b7a026e8b3d4362ca628067b3b74ead51f1cf.The rebased implementation additionally:
messagesCol;errorCol;additionalProperties: false, completerequiredsets, and nested/composed object schemas;call_id, andoutputnon-nullable while retaining runtime guards;replayItemsColumnhelper with a clearUnsupportedOperationException;Strict schemas and Azure
For
strict: true, every object schema must setadditionalProperties: false, every defined property must appear inrequired, andrequiredcannot name undefined properties. Optional business values remain required keys with nullable types such as["string", "null"].Current Azure guidance requires
parallel_tool_calls: falsewith 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
OpenAIPromptbehavior and the default Chat Completions mode remain unchanged.gpt-5-miniandgpt-5.1currently document Chat Completions, Responses, function calling, and Structured Outputs support. SynapseML does not infer deployment capability.Validation
Final-head local validation:
sbt test:compileunder Java 17 / Scala 2.13.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