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