|
10 | 10 | import java.util.regex.Pattern; |
11 | 11 |
|
12 | 12 | import com.fasterxml.jackson.annotation.JsonAutoDetect; |
| 13 | +import com.fasterxml.jackson.annotation.JsonCreator; |
13 | 14 | import com.fasterxml.jackson.annotation.JsonInclude; |
| 15 | +import com.fasterxml.jackson.annotation.JsonProperty; |
| 16 | +import com.fasterxml.jackson.annotation.JsonPropertyOrder; |
| 17 | +import com.fasterxml.jackson.annotation.JsonSubTypes; |
| 18 | +import com.fasterxml.jackson.annotation.JsonTypeInfo; |
14 | 19 | import com.fasterxml.jackson.annotation.PropertyAccessor; |
15 | 20 | import com.fasterxml.jackson.core.JsonParseException; |
16 | 21 | import com.fasterxml.jackson.core.JsonProcessingException; |
|
21 | 26 | import com.fasterxml.jackson.databind.ObjectWriter; |
22 | 27 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; |
23 | 28 | import com.fasterxml.jackson.databind.SerializationFeature; |
| 29 | +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
24 | 30 | import com.fasterxml.jackson.databind.module.SimpleDeserializers; |
25 | 31 | import com.fasterxml.jackson.databind.module.SimpleModule; |
26 | 32 |
|
| 33 | +import dev.langchain4j.agent.tool.ToolExecutionRequest; |
| 34 | +import dev.langchain4j.data.message.AiMessage; |
| 35 | +import dev.langchain4j.data.message.ChatMessage; |
| 36 | +import dev.langchain4j.data.message.ChatMessageType; |
| 37 | +import dev.langchain4j.data.message.CustomMessage; |
| 38 | +import dev.langchain4j.data.message.SystemMessage; |
| 39 | +import dev.langchain4j.data.message.ToolExecutionResultMessage; |
| 40 | +import dev.langchain4j.data.message.UserMessage; |
27 | 41 | import dev.langchain4j.internal.Json; |
28 | 42 | import dev.langchain4j.spi.json.JsonCodecFactory; |
29 | 43 | import io.quarkiverse.langchain4j.runtime.jackson.CustomLocalDateDeserializer; |
@@ -104,16 +118,89 @@ public static class ObjectMapperHolder { |
104 | 118 | public static final ObjectWriter WRITER; |
105 | 119 |
|
106 | 120 | static { |
| 121 | + // Start with Arc container ObjectMapper to preserve Quarkus integration |
107 | 122 | MAPPER = Arc.container().instance(ObjectMapper.class).get() |
108 | 123 | .copy() |
109 | 124 | .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) |
110 | 125 | .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) |
111 | | - .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true) |
112 | | - .registerModule(SnakeCaseObjectMapperHolder.QuarkusLangChain4jModule.INSTANCE); |
| 126 | + .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); |
| 127 | + |
| 128 | + // Add chat message mixins to preserve thinking field deserialization |
| 129 | + MAPPER.addMixIn(ChatMessage.class, ChatMessageMixin.class); |
| 130 | + MAPPER.addMixIn(AiMessage.class, AiMessageMixin.class); |
| 131 | + MAPPER.addMixIn(UserMessage.class, UserMessageMixin.class); |
| 132 | + MAPPER.addMixIn(SystemMessage.class, SystemMessageMixin.class); |
| 133 | + MAPPER.addMixIn(ToolExecutionResultMessage.class, ToolExecutionResultMessageMixin.class); |
| 134 | + MAPPER.addMixIn(CustomMessage.class, CustomMessageMixin.class); |
| 135 | + MAPPER.addMixIn(ToolExecutionRequest.class, ToolExecutionRequestMixin.class); |
| 136 | + |
| 137 | + // Register Quarkus-specific module |
| 138 | + MAPPER.registerModule(SnakeCaseObjectMapperHolder.QuarkusLangChain4jModule.INSTANCE); |
| 139 | + |
113 | 140 | WRITER = MAPPER.writerWithDefaultPrettyPrinter(); |
114 | 141 | } |
115 | 142 | } |
116 | 143 |
|
| 144 | + /** |
| 145 | + * Jackson mixins for chat message deserialization. |
| 146 | + * These enable proper deserialization of chat messages including the thinking field in AiMessage. |
| 147 | + * Based on mixins from dev.langchain4j.data.message.JacksonChatMessageJsonCodec. |
| 148 | + */ |
| 149 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 150 | + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type") |
| 151 | + @JsonSubTypes({ |
| 152 | + @JsonSubTypes.Type(value = SystemMessage.class, name = "SYSTEM"), |
| 153 | + @JsonSubTypes.Type(value = UserMessage.class, name = "USER"), |
| 154 | + @JsonSubTypes.Type(value = AiMessage.class, name = "AI"), |
| 155 | + @JsonSubTypes.Type(value = ToolExecutionResultMessage.class, name = "TOOL_EXECUTION_RESULT"), |
| 156 | + @JsonSubTypes.Type(value = CustomMessage.class, name = "CUSTOM"), |
| 157 | + }) |
| 158 | + private abstract static class ChatMessageMixin { |
| 159 | + @JsonProperty |
| 160 | + public abstract ChatMessageType type(); |
| 161 | + } |
| 162 | + |
| 163 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 164 | + private abstract static class SystemMessageMixin { |
| 165 | + @JsonCreator |
| 166 | + public SystemMessageMixin(@JsonProperty("text") String text) { |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 171 | + @JsonDeserialize(builder = UserMessage.Builder.class) |
| 172 | + private abstract static class UserMessageMixin { |
| 173 | + } |
| 174 | + |
| 175 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 176 | + @JsonDeserialize(builder = AiMessage.Builder.class) |
| 177 | + @JsonPropertyOrder({ "toolExecutionRequests", "text", "attributes", "type" }) |
| 178 | + private abstract static class AiMessageMixin { |
| 179 | + } |
| 180 | + |
| 181 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 182 | + @JsonPropertyOrder({ "text", "id", "toolName", "type" }) |
| 183 | + private static class ToolExecutionResultMessageMixin { |
| 184 | + @JsonCreator |
| 185 | + public ToolExecutionResultMessageMixin( |
| 186 | + @JsonProperty("id") String id, |
| 187 | + @JsonProperty("toolName") String toolName, |
| 188 | + @JsonProperty("text") String text) { |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 193 | + private static class CustomMessageMixin { |
| 194 | + @JsonCreator |
| 195 | + public CustomMessageMixin(@JsonProperty("attributes") Map<String, Object> attributes) { |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + @JsonInclude(JsonInclude.Include.NON_NULL) |
| 200 | + @JsonDeserialize(builder = ToolExecutionRequest.Builder.class) |
| 201 | + private abstract static class ToolExecutionRequestMixin { |
| 202 | + } |
| 203 | + |
117 | 204 | public static class SnakeCaseObjectMapperHolder { |
118 | 205 | public static final ObjectMapper MAPPER = Arc.container().instance(ObjectMapper.class).get() |
119 | 206 | .copy() |
|
0 commit comments