Skip to content

[API][Tool] Align Java and Python Tool outcome contracts - #1074

Open
joeyutong wants to merge 5 commits into
apache:mainfrom
joeyutong:codex/align-tool-outcomes
Open

[API][Tool] Align Java and Python Tool outcome contracts#1074
joeyutong wants to merge 5 commits into
apache:mainfrom
joeyutong:codex/align-tool-outcomes

Conversation

@joeyutong

@joeyutong joeyutong commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Linked issue: #956

Purpose of change

Java can distinguish whether a Tool invocation returned from whether the returned ToolResponse represents a successful Tool operation. Python previously exposed only raw return values, so every normal return was recorded as a successful Tool operation.

This change aligns the Java and Python Tool outcome contracts:

  • adds Python ToolResponse.success(...) and ToolResponse.error(...) while preserving raw Python returns as successful results;
  • normalizes sequential and parallel Python Tool calls through the same result-recording path;
  • preserves explicit Tool outcomes across both Java-to-Python and Python-to-Java bridges without inspecting arbitrary user payloads;
  • records missing Skill resources and MCP protocol error results as failed Tool operations while retaining their existing model-facing messages;
  • keeps Outcome<T> focused on whether the durable invocation returned or raised.

ToolResponseEvent, Execution Events, and downstream metrics can therefore consume the same normalized Tool outcome in both languages. The existing ToolResponseEvent.responses wire representation is unchanged.

Tests

  • mvn -pl api,plan,runtime,integrations/mcp -am -DskipITs test
  • pytest for the affected Python Tool API, Tool call, MCP, Skill, and Java bridge suites (66 passed)
  • Ruff format and lint checks for all changed Python files

API

Adds the public Python flink_agents.api.tools.ToolResponse type. Existing Python Tools that return raw values remain source- and behavior-compatible.

Documentation

  • doc-needed
  • doc-not-needed
  • doc-included

Was this patch authored or co-authored using generative AI tooling?

  • Yes
  • No

Generated-by: Codex 0.151.0-alpha.7.2 (GPT-5.6 Sol)

@github-actions github-actions Bot added doc-included Your PR already contains the necessary documentation updates. fixVersion/0.4.0 priority/major Default priority of the PR or issue. and removed doc-included Your PR already contains the necessary documentation updates. labels Aug 31, 2026
@joeyutong
joeyutong force-pushed the codex/align-tool-outcomes branch from 82adfd8 to a186ab5 Compare August 31, 2026 08:09
@github-actions github-actions Bot added doc-included Your PR already contains the necessary documentation updates. and removed doc-included Your PR already contains the necessary documentation updates. labels Aug 31, 2026
@joeyutong
joeyutong marked this pull request as ready for review August 31, 2026 08:11

@wenjin272 wenjin272 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.

Thanks for working on aligning the Java and Python tool outcome contracts!

return asyncio.run(
self.mcp_server.call_tool_async(self.metadata.name, *args, **kwargs)
)
except Exception as e:

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.

Could we also normalize explicit outcomes in PythonMCPTool.call()? That Java wrapper still wraps every normal Python return in ToolResponse.success(...). Returning ToolResponse.error(...) here therefore causes Java agents using Python MCP tools to record failures as successes. This reproduces with real Pemja for both transport exceptions and MCP protocol errors. Please cover this path with a cross-language regression test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. PythonMCPTool now uses the same bridge encoder/decoder as FunctionTool, so an explicit Python ToolResponse.error(...) remains a failed Java ToolResponse. I also added a unit regression and extended MCPCrossLanguageTest with a real FastMCP protocol error over Pemja.


content = [extract_mcp_content_item(item) for item in result.content]

if result.isError:

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.

Could we move the protocol-error check outside async with self._get_session()? With a real ClientSession, raising here gets wrapped in an AnyIO ExceptionGroup. MCPTool.call() then retains only the outer message, “unhandled errors in a TaskGroup”, losing the original MCP error content and recovery hints. A regression test retaining the real session lifecycle would catch this; the current fake session does not.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed by moving MCP content/error interpretation outside the session context. The regression now uses a real ClientSession lifecycle and verifies that the original protocol error content is retained instead of only the outer ExceptionGroup message.

@joeyutong
joeyutong force-pushed the codex/align-tool-outcomes branch from a186ab5 to 6546715 Compare September 9, 2026 12:07
joeyutong and others added 5 commits September 9, 2026 20:13
Preserve explicit Tool operation failures across native and cross-language
execution without changing existing raw Python Tool return behavior.

Generated-by: Codex 0.151.0-alpha.7.2 (GPT-5.6 Sol)
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Model: gpt-5.6-sol
AI-Contributed/Feature: 274/274
AI-Contributed/UT: 315/315
Use error and is_error consistently with the established Java ToolResponse
contract while keeping the bridge wire field named error.

Generated-by: Codex 0.151.0-alpha.7.2 (GPT-5.6 Sol)
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Model: gpt-5.6-sol
AI-Contributed/Feature: 28/28
AI-Contributed/UT: 24/24
Keep Java ToolResponse success and error states intact across the Java-to-Python bridge, preserve empty error messages, and add symmetric bridge coverage.

Generated-by: Codex 0.151.0-alpha.7.2 (GPT-5.6 Sol)
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Model: gpt-5.6-sol
AI-Contributed/Feature: 53/53
AI-Contributed/UT: 148/148
Interpret MCP protocol failures after the ClientSession context closes so AnyIO cleanup does not replace the original error with an ExceptionGroup summary.

Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Model: gpt-5
AI-Contributed/Feature: 9/9
AI-Contributed/UT: 17/17
Route Python MCP calls through the shared ToolResponse bridge envelope so explicit Python failures remain failed Java ToolResponses. Cover the result conversion with unit and real FastMCP/Pemja tests.

Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Model: gpt-5
AI-Contributed/Feature: 111/111
AI-Contributed/UT: 103/103
@joeyutong
joeyutong force-pushed the codex/align-tool-outcomes branch from 6546715 to b26e162 Compare September 9, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc-included Your PR already contains the necessary documentation updates. fixVersion/0.4.0 priority/major Default priority of the PR or issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants