Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
197 KiB
JSON

{"repo": "alibaba/spring-ai-alibaba", "files": {"spring-ai-alibaba-graph-core/src/test/java/com/alibaba/cloud/ai/graph/plain_text/DeepSeekAssistantMessageSerializationTest.java": {"1859779d65fd92db31959cbe560d133a45f53ee7": {"t": 1785745673.930048, "n": 11708, "h": "b28c31b63d49ee43", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.deepseek.DeepSeekAssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance.\n\t */\n\tprivate DeepSeekAssistantMessage createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) {\n\t\treturn new DeepSeekAssistantMessage.Builder()\n\t\t\t\t.content(text)\n\t\t\t\t.reasoningContent(reasoningContent)\n\t\t\t\t.properties(metadata)\n\t\t\t\t.toolCalls(toolCalls)\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deserializedMessage1 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg1 = (DeepSeekAssistantMessage) deserializedMessage1;\n\t\tassertEquals(\"Let me calculate that for you.\", msg1.getText());\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", msg1.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = msg1.getMetadata();\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, msg1.getToolCalls().size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deserializedMessage2 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg2 = (DeepSeekAssistantMessage) deserializedMessage2;\n\t\tassertEquals(\"The weather in Beijing is sunny.\", msg2.getText());\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", msg2.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = msg2.getMetadata();\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(msg2.getToolCalls().isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Nested structure test\", msg.getText());\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deserializedMessages.get(0) instanceof DeepSeekAssistantMessage);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Top level message\", msg.getText());\n\t\tassertEquals(\"Reasoning content\", msg.getReasoningContent());\n\t\tassertEquals(1, msg.getToolCalls().size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Message without reasoning\", msg.getText());\n\t\tassertEquals(null, msg.getReasoningContent());\n\t}\n}\n\n"}, "c9414cb0070234ecdb45183bfea8bdce37ea62d0": {"t": 1785755641.1769624, "n": 11708, "h": "b28c31b63d49ee43", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.deepseek.DeepSeekAssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance.\n\t */\n\tprivate DeepSeekAssistantMessage createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) {\n\t\treturn new DeepSeekAssistantMessage.Builder()\n\t\t\t\t.content(text)\n\t\t\t\t.reasoningContent(reasoningContent)\n\t\t\t\t.properties(metadata)\n\t\t\t\t.toolCalls(toolCalls)\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deserializedMessage1 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg1 = (DeepSeekAssistantMessage) deserializedMessage1;\n\t\tassertEquals(\"Let me calculate that for you.\", msg1.getText());\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", msg1.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = msg1.getMetadata();\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, msg1.getToolCalls().size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deserializedMessage2 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg2 = (DeepSeekAssistantMessage) deserializedMessage2;\n\t\tassertEquals(\"The weather in Beijing is sunny.\", msg2.getText());\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", msg2.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = msg2.getMetadata();\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(msg2.getToolCalls().isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Nested structure test\", msg.getText());\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deserializedMessages.get(0) instanceof DeepSeekAssistantMessage);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Top level message\", msg.getText());\n\t\tassertEquals(\"Reasoning content\", msg.getReasoningContent());\n\t\tassertEquals(1, msg.getToolCalls().size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Message without reasoning\", msg.getText());\n\t\tassertEquals(null, msg.getReasoningContent());\n\t}\n}\n\n"}, "95c82b9550813f19a73e2b337355309b2782d0c6": {"t": 1785755642.52219, "n": 11708, "h": "171e8e0065f05133", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.deepseek.DeepSeekAssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance.\n\t */\n\tprivate DeepSeekAssistantMessage createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) {\n\t\treturn new DeepSeekAssistantMessage.Builder()\n\t\t\t\t.content(text)\n\t\t\t\t.reasoningContent(reasoningContent)\n\t\t\t\t.properties(metadata)\n\t\t\t\t.toolCalls(toolCalls)\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deserializedMessage1 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg1 = (DeepSeekAssistantMessage) deserializedMessage1;\n\t\tassertEquals(\"Let me calculate that for you.\", msg1.getText());\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", msg1.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = msg1.getMetadata();\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, msg1.getToolCalls().size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deserializedMessage2 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg2 = (DeepSeekAssistantMessage) deserializedMessage2;\n\t\tassertEquals(\"The weather in Beijing is sunny.\", msg2.getText());\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", msg2.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = msg2.getMetadata();\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(msg2.getToolCalls().isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Nested structure test\", msg.getText());\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deserializedMessages.get(0) instanceof DeepSeekAssistantMessage);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Top level message\", msg.getText());\n\t\tassertEquals(\"Reasoning content\", msg.getReasoningContent());\n\t\tassertEquals(1, msg.getToolCalls().size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Message without reasoning\", msg.getText());\n\t\tassertEquals(null, msg.getReasoningContent());\n\t}\n}\n\n"}, "cb2951bb2786ef89e17f26cc7eb1a168735bedf3": {"t": 1785755643.8879168, "n": 13310, "h": "e86b46c636693f1a", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\tprivate Class<?> deepSeekClass;\n\tprivate Constructor<?> deepSeekConstructor;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t\t\n\t\t// Try to load DeepSeekAssistantMessage class\n\t\ttry {\n\t\t\tdeepSeekClass = Class.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\t// Constructor: DeepSeekAssistantMessage(String text, String reasoningContent, Map<String, Object> metadata, List<ToolCall> toolCalls)\n\t\t\tdeepSeekConstructor = deepSeekClass.getConstructor(String.class, String.class, \n\t\t\t\tMap.class, List.class);\n\t\t}\n\t\tcatch (ClassNotFoundException | NoSuchMethodException e) {\n\t\t\t// DeepSeekAssistantMessage not available, tests will be skipped\n\t\t\tdeepSeekClass = null;\n\t\t\tdeepSeekConstructor = null;\n\t\t}\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance using reflection.\n\t */\n\tprivate Object createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) throws Exception {\n\t\tif (deepSeekConstructor == null) {\n\t\t\tthrow new IllegalStateException(\"DeepSeekAssistantMessage not available\");\n\t\t}\n\t\treturn deepSeekConstructor.newInstance(text, reasoningContent, metadata, toolCalls);\n\t}\n\n\t/**\n\t * Get text from DeepSeekAssistantMessage using reflection.\n\t */\n\tprivate String getText(Object message) throws Exception {\n\t\tMethod getTextMethod = deepSeekClass.getMethod(\"getText\");\n\t\treturn (String) getTextMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get reasoning content from DeepSeekAssistantMessage using reflection.\n\t */\n\tprivate String getReasoningContent(Object message) throws Exception {\n\t\tMethod getReasoningContentMethod = deepSeekClass.getMethod(\"getReasoningContent\");\n\t\treturn (String) getReasoningContentMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get metadata from DeepSeekAssistantMessage using reflection.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate Map<String, Object> getMetadata(Object message) throws Exception {\n\t\tMethod getMetadataMethod = deepSeekClass.getMethod(\"getMetadata\");\n\t\treturn (Map<String, Object>) getMetadataMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get tool calls from DeepSeekAssistantMessage using reflection.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate List<AssistantMessage.ToolCall> getToolCalls(Object message) throws Exception {\n\t\tMethod getToolCallsMethod = deepSeekClass.getMethod(\"getToolCalls\");\n\t\treturn (List<AssistantMessage.ToolCall>) getToolCallsMethod.invoke(message);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tObject originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tObject originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage1));\n\t\tassertEquals(\"Let me calculate that for you.\", getText(deserializedMessage1));\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", getReasoningContent(deserializedMessage1));\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = getMetadata(deserializedMessage1);\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, getToolCalls(deserializedMessage1).size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage2));\n\t\tassertEquals(\"The weather in Beijing is sunny.\", getText(deserializedMessage2));\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", getReasoningContent(deserializedMessage2));\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = getMetadata(deserializedMessage2);\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(getToolCalls(deserializedMessage2).isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Nested structure test\", getText(deserializedMessage));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessages.get(0)));\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Top level message\", getText(deserializedMessage));\n\t\tassertEquals(\"Reasoning content\", getReasoningContent(deserializedMessage));\n\t\tassertEquals(1, getToolCalls(deserializedMessage).size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Message without reasoning\", getText(deserializedMessage));\n\t\tassertEquals(null, getReasoningContent(deserializedMessage));\n\t}\n}\n\n"}, "7de1fe39807cdeb23a98988f18edaf0f544436ff": {"t": 1785755645.3300397, "n": 13164, "h": "fdca2fb14380f9ea", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\tprivate Class<?> deepSeekClass;\n\tprivate Constructor<?> deepSeekConstructor;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t\t\n\t\t// Try to load DeepSeekAssistantMessage class\n\t\ttry {\n\t\t\tdeepSeekClass = Class.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\t// Constructor: DeepSeekAssistantMessage(String text, String reasoningContent, Map<String, Object> metadata, List<ToolCall> toolCalls)\n\t\t\tdeepSeekConstructor = deepSeekClass.getConstructor(String.class, String.class, \n\t\t\t\tMap.class, List.class);\n\t\t}\n\t\tcatch (ClassNotFoundException | NoSuchMethodException e) {\n\t\t\t// DeepSeekAssistantMessage not available, tests will be skipped\n\t\t\tdeepSeekClass = null;\n\t\t\tdeepSeekConstructor = null;\n\t\t}\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance using reflection.\n\t */\n\tprivate Object createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) throws Exception {\n\t\tif (deepSeekConstructor == null) {\n\t\t\tthrow new IllegalStateException(\"DeepSeekAssistantMessage not available\");\n\t\t}\n\t\treturn deepSeekConstructor.newInstance(text, reasoningContent, metadata, toolCalls);\n\t}\n\n\t/**\n\t * Get text from DeepSeekAssistantMessage using reflection.\n\t */\n\tprivate String getText(Object message) throws Exception {\n\t\tMethod getTextMethod = deepSeekClass.getMethod(\"getText\");\n\t\treturn (String) getTextMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get reasoning content from DeepSeekAssistantMessage using reflection.\n\t */\n\tprivate String getReasoningContent(Object message) throws Exception {\n\t\tMethod getReasoningContentMethod = deepSeekClass.getMethod(\"getReasoningContent\");\n\t\treturn (String) getReasoningContentMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get metadata from DeepSeekAssistantMessage using reflection.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate Map<String, Object> getMetadata(Object message) throws Exception {\n\t\tMethod getMetadataMethod = deepSeekClass.getMethod(\"getMetadata\");\n\t\treturn (Map<String, Object>) getMetadataMethod.invoke(message);\n\t}\n\n\t/**\n\t * Get tool calls from DeepSeekAssistantMessage using reflection.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate List<AssistantMessage.ToolCall> getToolCalls(Object message) throws Exception {\n\t\tMethod getToolCallsMethod = deepSeekClass.getMethod(\"getToolCalls\");\n\t\treturn (List<AssistantMessage.ToolCall>) getToolCallsMethod.invoke(message);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tObject originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tObject originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage1));\n\t\tassertEquals(\"Let me calculate that for you.\", getText(deserializedMessage1));\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", getReasoningContent(deserializedMessage1));\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = getMetadata(deserializedMessage1);\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, getToolCalls(deserializedMessage1).size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage2));\n\t\tassertEquals(\"The weather in Beijing is sunny.\", getText(deserializedMessage2));\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", getReasoningContent(deserializedMessage2));\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = getMetadata(deserializedMessage2);\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(getToolCalls(deserializedMessage2).isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Nested structure test\", getText(deserializedMessage));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessages.get(0)));\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Top level message\", getText(deserializedMessage));\n\t\tassertEquals(\"Reasoning content\", getReasoningContent(deserializedMessage));\n\t\tassertEquals(1, getToolCalls(deserializedMessage).size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tObject originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deepSeekClass.isInstance(deserializedMessage));\n\t\tassertEquals(\"Message without reasoning\", getText(deserializedMessage));\n\t\tassertEquals(null, getReasoningContent(deserializedMessage));\n\t}\n}\n\n"}, "dc6fcadca81ed48e0c7a8ed35920ecb06c26f0e3": {"t": 1786067433.5687182, "n": 11708, "h": "b28c31b63d49ee43", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.deepseek.DeepSeekAssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance.\n\t */\n\tprivate DeepSeekAssistantMessage createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) {\n\t\treturn new DeepSeekAssistantMessage.Builder()\n\t\t\t\t.content(text)\n\t\t\t\t.reasoningContent(reasoningContent)\n\t\t\t\t.properties(metadata)\n\t\t\t\t.toolCalls(toolCalls)\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deserializedMessage1 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg1 = (DeepSeekAssistantMessage) deserializedMessage1;\n\t\tassertEquals(\"Let me calculate that for you.\", msg1.getText());\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", msg1.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = msg1.getMetadata();\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, msg1.getToolCalls().size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deserializedMessage2 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg2 = (DeepSeekAssistantMessage) deserializedMessage2;\n\t\tassertEquals(\"The weather in Beijing is sunny.\", msg2.getText());\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", msg2.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = msg2.getMetadata();\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(msg2.getToolCalls().isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Nested structure test\", msg.getText());\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deserializedMessages.get(0) instanceof DeepSeekAssistantMessage);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Top level message\", msg.getText());\n\t\tassertEquals(\"Reasoning content\", msg.getReasoningContent());\n\t\tassertEquals(1, msg.getToolCalls().size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Message without reasoning\", msg.getText());\n\t\tassertEquals(null, msg.getReasoningContent());\n\t}\n}\n\n"}, "9aee0f1a86f59fcf284628a922d68ef71a4e2c85": {"t": 1786068301.1955318, "n": 11708, "h": "b28c31b63d49ee43", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.plain_text;\n\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.serializer.plain_text.jackson.SpringAIJacksonStateSerializer;\nimport com.alibaba.cloud.ai.graph.state.AgentStateFactory;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.deepseek.DeepSeekAssistantMessage;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\nimport static org.junit.Assert.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Test case for DeepSeekAssistantMessage serialization/deserialization bug fix.\n * This test reproduces issue #3186 where DeepSeekAssistantMessage fails to deserialize\n * when it appears in arrays or nested structures.\n */\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass DeepSeekAssistantMessageSerializationTest {\n\n\tprivate SpringAIJacksonStateSerializer serializer;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tAgentStateFactory<OverAllState> stateFactory = OverAllState::new;\n\t\tserializer = new SpringAIJacksonStateSerializer(stateFactory);\n\t}\n\n\t/**\n\t * Check if DeepSeekAssistantMessage is available on the classpath.\n\t */\n\tprivate static boolean isDeepSeekAvailable() {\n\t\ttry {\n\t\t\tClass.forName(\"org.springframework.ai.deepseek.DeepSeekAssistantMessage\");\n\t\t\treturn true;\n\t\t}\n\t\tcatch (ClassNotFoundException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Create a DeepSeekAssistantMessage instance.\n\t */\n\tprivate DeepSeekAssistantMessage createDeepSeekMessage(String text, String reasoningContent, \n\t\t\tMap<String, Object> metadata, List<AssistantMessage.ToolCall> toolCalls) {\n\t\treturn new DeepSeekAssistantMessage.Builder()\n\t\t\t\t.content(text)\n\t\t\t\t.reasoningContent(reasoningContent)\n\t\t\t\t.properties(metadata)\n\t\t\t\t.toolCalls(toolCalls)\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInArray() throws Exception {\n\t\t// This test reproduces the bug reported in issue #3186\n\t\t// The bug occurs when DeepSeekAssistantMessage is in an array/list\n\t\t\n\t\t// Create a DeepSeekAssistantMessage with tool calls\n\t\tMap<String, Object> metadata = new HashMap<>();\n\t\tmetadata.put(\"model\", \"deepseek-chat\");\n\t\tmetadata.put(\"temperature\", 0.7);\n\t\t\n\t\tList<AssistantMessage.ToolCall> toolCalls = List.of(\n\t\t\tnew AssistantMessage.ToolCall(\"call_1\", \"function\", \"calculator\", \"{\\\"a\\\": 1, \\\"b\\\": 2}\"),\n\t\t\tnew AssistantMessage.ToolCall(\"call_2\", \"function\", \"weather\", \"{\\\"city\\\": \\\"Beijing\\\"}\")\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage1 = createDeepSeekMessage(\n\t\t\t\"Let me calculate that for you.\",\n\t\t\t\"I need to use the calculator tool to add 1 and 2.\",\n\t\t\tmetadata,\n\t\t\ttoolCalls\n\t\t);\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage2 = createDeepSeekMessage(\n\t\t\t\"The weather in Beijing is sunny.\",\n\t\t\t\"I used the weather tool to get the current weather.\",\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create a list containing DeepSeekAssistantMessage objects\n\t\t// This is the scenario that triggers the bug\n\t\tList<Object> messages = List.of(originalMessage1, originalMessage2);\n\t\t\n\t\t// Wrap in a map for serialization\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize - this should work after the fix\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify deserialization succeeded\n\t\tassertNotNull(deserializedData);\n\t\tassertTrue(deserializedData.containsKey(\"messages\"));\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(2, deserializedMessages.size());\n\t\t\n\t\t// Verify first message\n\t\tObject deserializedMessage1 = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage1);\n\t\tassertTrue(deserializedMessage1 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg1 = (DeepSeekAssistantMessage) deserializedMessage1;\n\t\tassertEquals(\"Let me calculate that for you.\", msg1.getText());\n\t\tassertEquals(\"I need to use the calculator tool to add 1 and 2.\", msg1.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata1 = msg1.getMetadata();\n\t\tassertNotNull(deserializedMetadata1);\n\t\tassertEquals(\"deepseek-chat\", deserializedMetadata1.get(\"model\"));\n\t\tassertEquals(0.7, deserializedMetadata1.get(\"temperature\"));\n\t\t// messageType may be present in deserialized metadata\n\t\t\n\t\tassertEquals(2, msg1.getToolCalls().size());\n\t\t\n\t\t// Verify second message\n\t\tObject deserializedMessage2 = deserializedMessages.get(1);\n\t\tassertNotNull(deserializedMessage2);\n\t\tassertTrue(deserializedMessage2 instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg2 = (DeepSeekAssistantMessage) deserializedMessage2;\n\t\tassertEquals(\"The weather in Beijing is sunny.\", msg2.getText());\n\t\tassertEquals(\"I used the weather tool to get the current weather.\", msg2.getReasoningContent());\n\t\t\n\t\t// Verify metadata - messageType may be automatically added during deserialization\n\t\tMap<String, Object> deserializedMetadata2 = msg2.getMetadata();\n\t\tassertNotNull(deserializedMetadata2);\n\t\t// Original metadata was empty, but messageType may be added during deserialization\n\t\t// So we only check that our original fields are not present\n\t\tassertNull(deserializedMetadata2.get(\"model\"));\n\t\tassertNull(deserializedMetadata2.get(\"temperature\"));\n\t\t\n\t\tassertTrue(msg2.getToolCalls().isEmpty());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageInNestedStructure() throws Exception {\n\t\t// Test DeepSeekAssistantMessage in nested structures (Map containing List)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Nested structure test\",\n\t\t\t\"Testing nested serialization\",\n\t\t\tMap.of(\"test\", \"nested\"),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\t// Create nested structure\n\t\tMap<String, Object> innerMap = new HashMap<>();\n\t\tinnerMap.put(\"deepSeekMessage\", originalMessage);\n\t\tinnerMap.put(\"otherData\", \"test\");\n\t\t\n\t\tMap<String, Object> outerMap = new HashMap<>();\n\t\touterMap.put(\"inner\", innerMap);\n\t\touterMap.put(\"messages\", List.of(originalMessage));\n\t\t\n\t\t// Serialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(outerMap, oos);\n\t\toos.flush();\n\t\t\n\t\t// Deserialize\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tMap<String, Object> deserializedInner = (Map<String, Object>) deserializedData.get(\"inner\");\n\t\tassertNotNull(deserializedInner);\n\t\t\n\t\tObject deserializedMessage = deserializedInner.get(\"deepSeekMessage\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Nested structure test\", msg.getText());\n\t\t\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\tassertTrue(deserializedMessages.get(0) instanceof DeepSeekAssistantMessage);\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageAsTopLevelObject() throws Exception {\n\t\t// Test DeepSeekAssistantMessage as a top-level object (should work even before fix)\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Top level message\",\n\t\t\t\"Reasoning content\",\n\t\t\tMap.of(\"key\", \"value\"),\n\t\t\tList.of(new AssistantMessage.ToolCall(\"id\", \"function\", \"name\", \"args\"))\n\t\t);\n\t\t\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"message\", originalMessage);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\tassertNotNull(deserializedData);\n\t\tObject deserializedMessage = deserializedData.get(\"message\");\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Top level message\", msg.getText());\n\t\tassertEquals(\"Reasoning content\", msg.getReasoningContent());\n\t\tassertEquals(1, msg.getToolCalls().size());\n\t}\n\n\t@Test\n\t@EnabledIf(\"isDeepSeekAvailable\")\n\tvoid testDeepSeekAssistantMessageWithNullReasoningContent() throws Exception {\n\t\t// Test DeepSeekAssistantMessage with null reasoning content\n\t\t\n\t\tDeepSeekAssistantMessage originalMessage = createDeepSeekMessage(\n\t\t\t\"Message without reasoning\",\n\t\t\tnull, // null reasoning content\n\t\t\tnew HashMap<>(),\n\t\t\tList.of()\n\t\t);\n\t\t\n\t\tList<Object> messages = List.of(originalMessage);\n\t\tMap<String, Object> data = new HashMap<>();\n\t\tdata.put(\"messages\", messages);\n\t\t\n\t\t// Serialize and deserialize\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\tObjectOutputStream oos = new ObjectOutputStream(baos);\n\t\tserializer.writeData(data, oos);\n\t\toos.flush();\n\t\t\n\t\tByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());\n\t\tObjectInputStream ois = new ObjectInputStream(bais);\n\t\tMap<String, Object> deserializedData = serializer.readData(ois);\n\t\t\n\t\t// Verify\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tList<Object> deserializedMessages = (List<Object>) deserializedData.get(\"messages\");\n\t\tassertNotNull(deserializedMessages);\n\t\tassertEquals(1, deserializedMessages.size());\n\t\t\n\t\tObject deserializedMessage = deserializedMessages.get(0);\n\t\tassertNotNull(deserializedMessage);\n\t\tassertTrue(deserializedMessage instanceof DeepSeekAssistantMessage);\n\t\tDeepSeekAssistantMessage msg = (DeepSeekAssistantMessage) deserializedMessage;\n\t\tassertEquals(\"Message without reasoning\", msg.getText());\n\t\tassertEquals(null, msg.getReasoningContent());\n\t}\n}\n\n"}}, "spring-ai-alibaba-agent-framework/src/test/java/com/alibaba/cloud/ai/graph/agent/model/ReactAgentDeepSeekTest.java": {"1859779d65fd92db31959cbe560d133a45f53ee7": {"t": 1785745682.958725, "n": 13386, "h": "ccf300bc156eb0ca", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tMemorySaver saver = new MemorySaver();\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的抒情散文,大约150字,直接写作不要再询问我了\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "c9414cb0070234ecdb45183bfea8bdce37ea62d0": {"t": 1785755640.7669697, "n": 13386, "h": "ccf300bc156eb0ca", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tMemorySaver saver = new MemorySaver();\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的抒情散文,大约150字,直接写作不要再询问我了\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "062eaa52c5f645d7cdd969f2a87e985dec1faca4": {"t": 1785755642.32343, "n": 13386, "h": "d817ddaad7918b1a", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tMemorySaver saver = new MemorySaver();\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的抒情散文,大约150字,直接写作不要再询问我了\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "9a9b64d721b4abf4575b4be19dc76cfce4fe73e7": {"t": 1785755643.8035529, "n": 13355, "h": "2d05f6085fffb76b", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的散文,大约150字\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "8289920a259f258d96e0b15352bfbbaac601fb28": {"t": 1785755645.1538837, "n": 7881, "h": "88823385c467d7e1", "c": "/*\n * Copyright 2024-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\t\tCompileConfig compileConfig = getCompileConfig();\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\t\tCompileConfig compileConfig = getCompileConfig();\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\t\tCompileConfig compileConfig = getCompileConfig();\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t请按照以下JSON格式输出:\n\t\t\t\t{\n\t\t\t\t\t\"title\": \"诗歌标题\",\n\t\t\t\t\t\"content\": \"诗歌正文内容\",\n\t\t\t\t\t\"style\": \"诗歌风格(如:现代诗、古体诗等)\"\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\t\tCompileConfig compileConfig = getCompileConfig();\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\t\tCompileConfig compileConfig = getCompileConfig();\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t请严格按照以下JSON格式返回结果:\n\t\t\t\t{\n\t\t\t\t\t\"summary\": \"内容摘要\",\n\t\t\t\t\t\"keywords\": [\"关键词1\", \"关键词2\", \"关键词3\"],\n\t\t\t\t\t\"sentiment\": \"情感倾向(正面/负面/中性)\"\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "dc6fcadca81ed48e0c7a8ed35920ecb06c26f0e3": {"t": 1786067436.2546446, "n": 13386, "h": "ccf300bc156eb0ca", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tMemorySaver saver = new MemorySaver();\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的抒情散文,大约150字,直接写作不要再询问我了\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}, "9aee0f1a86f59fcf284628a922d68ef71a4e2c85": {"t": 1786068301.768469, "n": 13386, "h": "ccf300bc156eb0ca", "c": "/*\n * Copyright 2024-2026 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.alibaba.cloud.ai.graph.agent.model;\n\nimport com.alibaba.cloud.ai.graph.CompileConfig;\nimport com.alibaba.cloud.ai.graph.OverAllState;\nimport com.alibaba.cloud.ai.graph.agent.AgentTool;\nimport com.alibaba.cloud.ai.graph.agent.ReactAgent;\nimport com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;\nimport com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.springframework.ai.chat.messages.AssistantMessage;\nimport org.springframework.ai.chat.messages.UserMessage;\nimport org.springframework.ai.chat.model.ChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatModel;\nimport org.springframework.ai.deepseek.DeepSeekChatOptions;\nimport org.springframework.ai.deepseek.api.DeepSeekApi;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\n@EnabledIfEnvironmentVariable(named = \"AI_DEEPSEEK_API_KEY\", matches = \".+\")\nclass ReactAgentDeepSeekTest {\n\n\tprivate ChatModel chatModel;\n\n\t// Inner class for outputType example\n\tpublic static class PoemOutput {\n\t\tprivate String title;\n\t\tprivate String content;\n\t\tprivate String style;\n\n\t\tpublic String getTitle() {\n\t\t\treturn title;\n\t\t}\n\n\t\tpublic void setTitle(String title) {\n\t\t\tthis.title = title;\n\t\t}\n\n\t\tpublic String getContent() {\n\t\t\treturn content;\n\t\t}\n\n\t\tpublic void setContent(String content) {\n\t\t\tthis.content = content;\n\t\t}\n\n\t\tpublic String getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\tpublic void setStyle(String style) {\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"PoemOutput{\" +\n\t\t\t\t\t\"title='\" + title + '\\'' +\n\t\t\t\t\t\", content='\" + content + '\\'' +\n\t\t\t\t\t\", style='\" + style + '\\'' +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\t// Create DashScopeApi instance using the API key from environment variable\n\t\tDeepSeekApi deepSeekApi = DeepSeekApi.builder()\n\t\t\t.apiKey(System.getenv(\"AI_DEEPSEEK_API_KEY\"))\n//\t\t\t.baseUrl(System.getenv(\"AI_DEEPSEEK_API_BASE_URL\"))\n\t\t\t.build();\n\n\t\tDeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()\n\t\t\t\t.defaultOptions(DeepSeekChatOptions.builder()\n\t\t\t\t\t\t.model(\"deepseek-chat\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.deepSeekApi(deepSeekApi).build();\n\n\t\t// Create DashScope ChatModel instance\n\t\tthis.chatModel = deepSeekChatModel;\n\t}\n\n\t@Test\n\tpublic void testReactAgent() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver()).build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = agent.invoke(\"帮我写一篇100字左右散文。\");\n\t\t\tOptional<OverAllState> result2 = agent.invoke(new UserMessage(\"帮我写一首现代诗歌。\"));\n\t\t\tOptional<OverAllState> result3 = agent.invoke(\"帮我写一首现代诗歌2。\");\n\n\t\t\tassertTrue(result.isPresent(), \"First result should be present\");\n\t\t\tOverAllState state1 = result.get();\n\t\t\tassertTrue(state1.value(\"messages\").isPresent(), \"Messages should be present in first result\");\n\t\t\tassertEquals(2, ((List)state1.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages1 = state1.value(\"messages\").get();\n\t\t\tassertNotNull(messages1, \"Messages should not be null in first result\");\n\n\t\t\tassertTrue(result2.isPresent(), \"Second result should be present\");\n\t\t\tOverAllState state2 = result2.get();\n\t\t\tassertTrue(state2.value(\"messages\").isPresent(), \"Messages should be present in second result\");\n\t\t\tassertEquals(4, ((List<?>)state2.value(\"messages\").get()).size(), \"There should be 2 messages in the first result\");\n\t\t\tObject messages2 = state2.value(\"messages\").get();\n\t\t\tassertNotNull(messages2, \"Messages should not be null in second result\");\n\n\t\t\tassertNotEquals(messages1, messages2, \"Results should be different for different inputs\");\n\n\t\t\tSystem.out.println(result.get());\n\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"ReactAgent execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testReactAgentMessage() throws Exception {\n\n\t\tReactAgent agent = ReactAgent.builder().name(\"single_agent\").model(chatModel).saver(new MemorySaver())\n\t\t\t\t.build();\n\t\tAssistantMessage message = agent.call(\"帮我写一篇100字左右散文。\");\n\t\tSystem.out.println(message.getText());\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchema() throws Exception {\n\n\n\t\t// Customized outputSchema\n\t\tString customSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"schema_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(customSchema)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于春天的诗歌。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with custom schema ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"标题\"),\n\t\t\t\t\"Output should contain title field\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputType() throws Exception {\n\n\n\t\t// outputType will be automatically convert to schema\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"type_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.build();\n\n\t\tAssistantMessage message = agent.call(\"帮我写一首关于秋天的现代诗。\");\n\t\tassertNotNull(message, \"Message should not be null\");\n\t\tassertNotNull(message.getText(), \"Message text should not be null\");\n\t\tSystem.out.println(\"=== Output with outputType (auto-generated schema) ===\");\n\t\tSystem.out.println(message.getText());\n\n\t\tassertTrue(message.getText().contains(\"title\") || message.getText().contains(\"content\") ||\n\t\t\t\tmessage.getText().contains(\"style\"),\n\t\t\t\t\"Output should contain structured fields\");\n\t}\n\n\t@Test\n\tpublic void testReactAgentWithOutputSchemaAndInvoke() throws Exception {\n\n\n\t\tString jsonSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"summary\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"keywords\": {\n\t\t\t\t\t\t\t\"type\": \"array\",\n\t\t\t\t\t\t\t\"items\": {\n\t\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"sentiment\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"additionalProperties\": false\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent agent = ReactAgent.builder()\n\t\t\t\t.name(\"analysis_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.outputSchema(jsonSchema)\n\t\t\t\t.build();\n\n\t\tOptional<OverAllState> result = agent.invoke(\"分析这句话:春天来了,万物复苏,生机勃勃。\");\n\n\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\tSystem.out.println(\"=== Full state output ===\");\n\t\tSystem.out.println(result.get());\n\t}\n\n\t@Test\n\tpublic void testAgentToolBasic() throws Exception {\n\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以写文章。\")\n\t\t\t\t.instruction(\"你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent reviewerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"reviewer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"可以对文章进行评论和修改。\")\n\t\t\t\t.instruction(\"你是一个知名的评论家,擅长对文章进行评论和修改。对于散文类文章,请确保文章中必须包含对于西湖风景的描述。\")\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent blogAgent = ReactAgent.builder()\n\t\t\t\t.name(\"blog_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"首先,根据用户给定的主题写一篇文章,然后将文章交给评论员进行审核,必要时做出修改。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent),\n\t\t\t\t\t\tAgentTool.getFunctionToolCallback(reviewerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = blogAgent\n\t\t\t\t\t.invoke(new UserMessage(\"帮我写一个100字左右的散文\"));\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\n\t\t\tOverAllState state = result.get();\n\n\t\t\tassertTrue(state.value(\"messages\").isPresent(), \"Messages should be present in state\");\n\n\t\t\tObject messages = state.value(\"messages\").get();\n\t\t\tassertNotNull(messages, \"Messages should not be null\");\n\n\t\t\tSystem.out.println(\"=== Basic Agent Tool Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (java.util.concurrent.CompletionException e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithInputSchema() throws Exception {\n\n\n\t\t// 使用 inputSchema 定义工具的输入格式\n\t\tString writerInputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"wordCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"required\": [\"topic\", \"wordCount\", \"style\"]\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tMemorySaver saver = new MemorySaver();\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"structured_writer_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"根据结构化输入写文章\")\n\t\t\t\t.instruction(\"你是一个专业作家。请严格按照输入的主题、字数和风格要求创作文章。\")\n\t\t\t\t.inputSchema(writerInputSchema)\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_agent\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"你需要调用写作工具来完成用户的写作请求。请根据用户需求,使用结构化的参数调用写作工具。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(saver)\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"请写一篇关于春天的抒情散文,大约150字,直接写作不要再询问我了\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with InputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with inputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputSchema() throws Exception {\n\n\n\t\t// 使用 outputSchema 定义工具的输出格式\n\t\tString writerOutputSchema = \"\"\"\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"content\": {\n\t\t\t\t\t\t\t\"type\": \"string\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"characterCount\": {\n\t\t\t\t\t\t\t\"type\": \"integer\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回结构化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并严格按照指定的JSON格式返回结果。\")\n\t\t\t\t.outputSchema(writerOutputSchema)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_schema\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求,工具会返回结构化的文章数据。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于冬天的短文\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputSchema Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputSchema execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAgentToolWithOutputType() throws Exception {\n\n\n\t\t// 使用 outputType,框架会自动生成输出 schema\n\t\tReactAgent writerAgent = ReactAgent.builder()\n\t\t\t\t.name(\"writer_with_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.description(\"写文章并返回类型化输出\")\n\t\t\t\t.instruction(\"你是一个专业作家。请创作文章并返回包含 title、content 和 style 的结构化结果。\")\n\t\t\t\t.outputType(PoemOutput.class)\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\tReactAgent coordinatorAgent = ReactAgent.builder()\n\t\t\t\t.name(\"coordinator_output_type\")\n\t\t\t\t.model(chatModel)\n\t\t\t\t.instruction(\"调用写作工具完成用户请求。\")\n\t\t\t\t.tools(List.of(AgentTool.getFunctionToolCallback(writerAgent)))\n\t\t\t\t.saver(new MemorySaver())\n\t\t\t\t.build();\n\n\t\ttry {\n\t\t\tOptional<OverAllState> result = coordinatorAgent\n\t\t\t\t\t.invoke(\"写一篇关于夏天的小诗\");\n\n\t\t\tassertTrue(result.isPresent(), \"Result should be present\");\n\t\t\tSystem.out.println(\"=== Agent Tool with OutputType Test ===\");\n\t\t\tSystem.out.println(result.get());\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t\tfail(\"Agent tool with outputType execution failed: \" + e.getMessage());\n\t\t}\n\t}\n\n\tprivate static CompileConfig getCompileConfig() {\n\t\tSaverConfig saverConfig = SaverConfig.builder()\n\t\t\t\t.register(new MemorySaver())\n\t\t\t\t.build();\n\t\tCompileConfig compileConfig = CompileConfig.builder().saverConfig(saverConfig).build();\n\t\treturn compileConfig;\n\t}\n\n}\n"}}}}