wenjin272 commented on code in PR #1185:
URL: https://github.com/apache/flink-agents/pull/1185#discussion_r4236463843


##########
python/flink_agents/runtime/python_java_utils.py:
##########
@@ -248,6 +245,7 @@ def _encode_python_tool_result(result: Any) -> Dict[str, 
Any]:
         "error": result.error_message,
         "execution_time_ms": result.execution_time_ms,
         "tool_name": result.tool_name,
+        "blocks": result.model_dump(mode="json")["blocks"],

Review Comment:
   Thanks for catching this. We refactored `ToolResponse` to use `blocks` for 
model-facing content and `metadata` for application data, removing `result`. 
The bridge now converts only the blocks and passes metadata through Pemja 
without JSON serialization. Added a regression test covering non-UTF-8 bytes in 
metadata across both bridge directions.



##########
integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnection.java:
##########
@@ -258,29 +260,21 @@ private ChatMessage doChat(
         ChatMessage response =
                 
OpenAIChatCompletionsUtils.convertFromOpenAIMessage(choice.message());
 
-        // ChatCompletion.Choice#finishReason throws 
OpenAIInvalidDataException when the member is
-        // absent or null, so the value is read through the raw field.
-        choice._finishReason()
-                .asKnown()
-                .ifPresent(
-                        reason -> response.getExtraArgs().put("finish_reason", 
reason.asString()));
-
-        // Stash token usage
-        if (completion.usage().isPresent()) {
-            String modelName = modelParams != null ? (String) 
modelParams.get("model") : null;
-            if (modelName == null || modelName.isBlank()) {
-                modelName = this.defaultModel;
-            }
-            if (modelName != null && !modelName.isBlank()) {
-                response.getExtraArgs().put("model_name", modelName);
-                response.getExtraArgs()
-                        .put("promptTokens", 
completion.usage().get().promptTokens());
-                response.getExtraArgs()
-                        .put("completionTokens", 
completion.usage().get().completionTokens());
-            }
-        }
-
-        return response;
+        return new ChatResult(
+                response,
+                (modelParams != null && modelParams.get("model") != null
+                        ? (String) modelParams.get("model")
+                        : this.defaultModel),

Review Comment:
   Fixed: request construction and `ChatResult.model` now use the same 
`effectiveModelFor()` resolution. Added local HTTP tests for OpenAI and vLLM 
covering missing, null, empty, whitespace and explicit model overrides, 
checking both the outgoing request and result attribution.



##########
api/src/main/java/org/apache/flink/agents/api/chat/messages/ContentBlock.java:
##########
@@ -27,28 +27,34 @@
 /**
  * A single, typed part of a {@link ChatMessage}'s content.
  *
- * <p>Blocks are ordered within a message and are immutable value objects: 
every construction path,
- * including Jackson deserialization, runs the same validation, so sharing a 
block instance never
- * shares mutable state. The concrete type answers how providers route the 
content ({@link
+ * <p>Blocks are ordered within a message. Block fields are read-only, while 
metadata and tool
+ * inputs use ordinary maps. Every construction path, including Jackson 
deserialization, runs the
+ * same structural validation. The concrete type answers how providers route 
the content ({@link
  * TextBlock}, {@link ImageBlock}, {@link AudioBlock}, {@link VideoBlock}, 
{@link DocumentBlock}),
  * while media encoding is carried by the media type on {@link MediaBlock}.
  *
  * <p>The serialized form carries a {@code type} discriminator with fixed 
values ({@code text},
  * {@code image}, {@code audio}, {@code video}, {@code document}) shared with 
the Python API, so
  * blocks cross the Java/Python boundary as plain JSON.
  */
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, 
property = "type")
+@JsonTypeInfo(
+        use = JsonTypeInfo.Id.NAME,
+        include = JsonTypeInfo.As.EXISTING_PROPERTY,
+        property = "type")
 @JsonSubTypes({
     @JsonSubTypes.Type(value = TextBlock.class, name = "text"),
     @JsonSubTypes.Type(value = ImageBlock.class, name = "image"),
     @JsonSubTypes.Type(value = AudioBlock.class, name = "audio"),
     @JsonSubTypes.Type(value = VideoBlock.class, name = "video"),
-    @JsonSubTypes.Type(value = DocumentBlock.class, name = "document")
+    @JsonSubTypes.Type(value = DocumentBlock.class, name = "document"),
+    @JsonSubTypes.Type(value = ReasoningBlock.class, name = "reasoning"),
+    @JsonSubTypes.Type(value = ToolCallBlock.class, name = "tool_call"),
+    @JsonSubTypes.Type(value = ToolResultBlock.class, name = "tool_result")

Review Comment:
   Thanks, agreed that tool-result content should have a narrower type. We 
introduced `DataContentBlock` for text/media and restricted 
`ToolResultBlock.blocks` and `ToolResponse.blocks` to that subset in both Java 
and Python.
   
   We kept a single ordered `ContentBlock` list in `ChatMessage` to preserve 
content order and avoid separate content fields. This broad content-block model 
is also used by 
[AgentScope](https://github.com/agentscope-ai/agentscope/blob/main/src/agentscope/message/_block.py#L219-L226)
 and 
[LangChain](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/messages/content.py#L832-L853),
 whose unions include text/media, reasoning and tool-related blocks.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to