Zhuoxi2000 commented on code in PR #1060:
URL: https://github.com/apache/flink-agents/pull/1060#discussion_r4056318894


##########
python/flink_agents/integrations/chat_models/anthropic/anthropic_chat_model.py:
##########
@@ -426,18 +426,14 @@ def chat(
             ]
 
             extra_args["anthropic_content_blocks"] = message.content
-            return ChatMessage(
-                role=MessageRole(message.role),
-                content=text,
+            return ChatMessage.of(MessageRole(message.role), text,

Review Comment:
   Done. Rebased onto the latest main and fixed the conflicts.



##########
python/flink_agents/api/chat_message.py:
##########
@@ -43,6 +44,88 @@ class MessageRole(str, Enum):
     TOOL = "tool"
 
 
+class TextBlock(BaseModel):
+    """A plain-text, immutable part of a ChatMessage."""
+
+    model_config = ConfigDict(frozen=True)
+
+    type: Literal["text"] = "text"
+    text: str = ""
+
+    def __str__(self) -> str:
+        return self.text
+
+
+class MediaBlock(BaseModel):
+    """Shared shape for binary media blocks: modality is the concrete type,
+    encoding is the media type (RFC 6838; historically called a MIME type).
+
+    Media blocks are immutable, and the payload is carried by exactly one of
+    base64 ``data`` or an externally managed ``url``. URL-backed content is
+    externally managed: URLs may expire, may not be reachable by the model
+    provider, and may be invalid after recovery from a checkpoint. The optional
+    ``name``/``size_bytes``/``sha256`` metadata also serves the Event Log,
+    which records media metadata instead of payload bytes.
+    """
+
+    # Frozen keeps sharing a block (e.g. across a routing context copy) safe,
+    # and matches the validated immutable construction on the Java side.
+    model_config = ConfigDict(frozen=True)
+
+    media_type: str

Review Comment:
   Fixed. Python now rejects empty media_type values up front. Same for empty 
Base64 data and empty URLs.



##########
runtime/src/main/java/org/apache/flink/agents/runtime/eventlog/ChatMessageEventLogSerializer.java:
##########
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.runtime.eventlog;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.apache.flink.agents.api.chat.messages.ChatMessage;
+import org.apache.flink.agents.api.chat.messages.ContentBlock;
+
+import java.io.IOException;
+
+/**
+ * The Event Log's {@link ChatMessage} serializer: each content block is 
written through its own
+ * {@link ContentBlock#sanitize()} projection, so media payload bytes and 
unsanitized URLs never
+ * reach the log — at any log level, VERBOSE included. Everything else (role, 
tool calls, extra
+ * args) keeps the regular shape, and the level-dependent {@link 
JsonTruncator} still applies to the
+ * result afterwards at STANDARD.
+ *
+ * <p>This serializer is registered only on the Event Log mappers via {@link 
#module()}; the global
+ * {@link ChatMessage} wire format — the Java/Python bridge, event 
serialization, state recovery —
+ * is untouched and preserves the complete payload. Logged output is not a 
faithful {@link
+ * ChatMessage} and must never be reconstructed into one: an inline-backed 
media block drops its
+ * {@code data} (so reconstruction fails loudly), and a URL-backed one carries 
only the stripped
+ * URL.
+ */
+public class ChatMessageEventLogSerializer extends JsonSerializer<ChatMessage> 
{
+
+    /** The module Event Log mappers register to apply the sanitized {@link 
ChatMessage} shape. */
+    public static Module module() {
+        return new SimpleModule("flink-agents-event-log-chat-messages")
+                .addSerializer(ChatMessage.class, new 
ChatMessageEventLogSerializer());

Review Comment:
   Yep, #1125 is exactly the gap I hit here.
   
   Right now Event.fromJson() turns Python-originated built-in events into a 
base Event with map attributes, so by the time the logger sees them we've 
already lost the typed event.



##########
api/src/main/java/org/apache/flink/agents/api/chat/messages/MediaBlock.java:
##########
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.api.chat.messages;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import javax.annotation.Nullable;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Shared shape for binary media blocks: modality is the concrete type, 
encoding is the media type
+ * (RFC 6838; historically called a MIME type).
+ *
+ * <p>Media blocks are immutable, and every construction path — the {@code 
fromBase64}/{@code
+ * fromUrl} factories, the full constructors, and Jackson deserialization — 
runs the same
+ * validation, so a block that exists carries exactly one of base64 {@code 
data} or an externally
+ * managed {@code url}. URL-backed content is externally managed: URLs may 
expire, may not be
+ * reachable by the model provider, and may be invalid after recovery from a 
checkpoint.
+ *
+ * <p>The optional {@code name}/{@code sizeBytes}/{@code sha256} metadata also 
serves the Event Log,
+ * which records media metadata instead of payload bytes — see {@link 
#sanitize()}.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class MediaBlock extends ContentBlock {
+
+    @JsonProperty("media_type")
+    private final String mediaType;
+
+    @Nullable private final String data;
+
+    @Nullable private final String url;

Review Comment:
   Done ! This shape is cleaner.



##########
api/src/main/java/org/apache/flink/agents/api/chat/messages/MediaBlock.java:
##########
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.api.chat.messages;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import javax.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Shared shape for binary media blocks: modality is the concrete type, 
encoding is the MIME type.
+ *
+ * <p>The payload is carried by exactly one of base64 {@code data} or an 
externally managed {@code
+ * url} (enforced by the argument constructor and the per-type factories; the 
no-arg bean path is
+ * lenient for deserialization). URL-backed content is externally managed: 
URLs may expire, may not
+ * be reachable by the model provider, and may be invalid after recovery from 
a checkpoint. The
+ * optional {@code name}/{@code sizeBytes}/{@code sha256} metadata also serves 
the Event Log, which
+ * records media metadata instead of payload bytes.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class MediaBlock extends ContentBlock {
+
+    @JsonProperty("mime_type")
+    private String mimeType;

Review Comment:
   Yep, agreed. 



-- 
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