fix(communication): fix errors while import external xml node into input xml

This commit is contained in:
2026-04-20 16:40:36 +08:00
parent ac715602a6
commit d90c514159
5 changed files with 132 additions and 5 deletions

View File

@@ -127,7 +127,7 @@ public class CognitionCore implements StateSerializable {
new BlockContent("recent_chat_messages", "communication_producer") { new BlockContent("recent_chat_messages", "communication_producer") {
@Override @Override
protected void fillXml(@NotNull Document document, @NotNull Element root) { protected void fillXml(@NotNull Document document, @NotNull Element root) {
document.appendChild(document.importNode(messageNotesElement(), true)); root.appendChild(document.importNode(messageNotesElement(), true));
Element chatMessagesElement = document.createElement("chat_messages"); Element chatMessagesElement = document.createElement("chat_messages");
root.appendChild(chatMessagesElement); root.appendChild(chatMessagesElement);
appendRepeatedElements(document, chatMessagesElement, "chat_message", resolveRecentChatMessages(), (messageElement, message) -> { appendRepeatedElements(document, chatMessagesElement, "chat_message", resolveRecentChatMessages(), (messageElement, message) -> {

View File

@@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node;
import work.slhaf.partner.core.cognition.*; import work.slhaf.partner.core.cognition.*;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
@@ -171,7 +172,7 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
Element root = document.createElement("input"); Element root = document.createElement("input");
document.appendChild(root); document.appendChild(root);
document.appendChild(document.importNode(runningFlowContext.encodeInputsBlock().encodeToXml(), true)); root.appendChild(importExternalNode(document, runningFlowContext.encodeInputsBlock().encodeToXml()));
appendTextElement(document, root, "source", runningFlowContext.getSource()); appendTextElement(document, root, "source", runningFlowContext.getSource());
for (Map.Entry<String, String> entry : runningFlowContext.getAdditionalUserInfo().entrySet()) { for (Map.Entry<String, String> entry : runningFlowContext.getAdditionalUserInfo().entrySet()) {
appendTextElement(document, root, sanitizeTagName(entry.getKey()), entry.getValue()); appendTextElement(document, root, sanitizeTagName(entry.getKey()), entry.getValue());
@@ -242,11 +243,25 @@ public class CommunicationProducer extends AbstractAgentModule.Running<PartnerRu
inputRoot.appendChild(groupElement); inputRoot.appendChild(groupElement);
for (CommunicationBlockContent block : entry.getValue()) { for (CommunicationBlockContent block : entry.getValue()) {
Element blockElement = block.encodeToXml(); Element blockElement = block.encodeToXml();
groupElement.appendChild(document.importNode(blockElement, true)); groupElement.appendChild(importExternalNode(document, blockElement));
} }
} }
} }
private Node importExternalNode(Document targetDocument, Node externalNode) {
Node nodeToImport = externalNode;
if (externalNode != null && externalNode.getNodeType() == Node.DOCUMENT_NODE) {
nodeToImport = ((Document) externalNode).getDocumentElement();
}
if (nodeToImport == null) {
throw new IllegalArgumentException("待导入节点不能为空");
}
if (nodeToImport.getOwnerDocument() == targetDocument) {
return nodeToImport.cloneNode(true);
}
return targetDocument.importNode(nodeToImport, true);
}
private String sanitizeTagName(String rawTagName) { private String sanitizeTagName(String rawTagName) {
if (rawTagName == null || rawTagName.isBlank()) { if (rawTagName == null || rawTagName.isBlank()) {
return "meta"; return "meta";

View File

@@ -65,7 +65,7 @@ public class MessageSummarizer extends AbstractAgentModule.Sub<List<Message>, Re
return new TaskBlock() { return new TaskBlock() {
@Override @Override
protected void fillXml(@NotNull Document document, @NotNull Element root) { protected void fillXml(@NotNull Document document, @NotNull Element root) {
document.appendChild(document.importNode(cognitionCapability.messageNotesElement(), true)); root.appendChild(document.importNode(cognitionCapability.messageNotesElement(), true));
appendListElement(document, root, "chat_messages", "message", messages, (element, message) -> { appendListElement(document, root, "chat_messages", "message", messages, (element, message) -> {
element.setAttribute("role", message.roleValue()); element.setAttribute("role", message.roleValue());
element.setTextContent(message.getContent()); element.setTextContent(message.getContent());

View File

@@ -0,0 +1,80 @@
package experimental;
import org.junit.jupiter.api.Test;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilderFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ExternalNodeImportTest {
private static Node importExternalNode(Document targetDocument, Node externalNode) {
Node nodeToImport = externalNode;
if (externalNode != null && externalNode.getNodeType() == Node.DOCUMENT_NODE) {
nodeToImport = ((Document) externalNode).getDocumentElement();
}
if (nodeToImport == null) {
throw new IllegalArgumentException("nodeToImport must not be null");
}
if (nodeToImport.getOwnerDocument() == targetDocument) {
return nodeToImport.cloneNode(true);
}
return targetDocument.importNode(nodeToImport, true);
}
private static Document newDocument() throws Exception {
return DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.newDocument();
}
@Test
void appendForeignElementDirectlyShouldThrow() throws Exception {
Document targetDocument = newDocument();
Element inputRoot = targetDocument.createElement("input");
targetDocument.appendChild(inputRoot);
Document externalDocument = newDocument();
Element externalInputs = externalDocument.createElement("inputs");
externalDocument.appendChild(externalInputs);
assertThrows(DOMException.class, () -> inputRoot.appendChild(externalInputs));
}
@Test
void importForeignElementShouldWork() throws Exception {
Document targetDocument = newDocument();
Element inputRoot = targetDocument.createElement("input");
targetDocument.appendChild(inputRoot);
Document externalDocument = newDocument();
Element externalInputs = externalDocument.createElement("inputs");
externalDocument.appendChild(externalInputs);
Node imported = importExternalNode(targetDocument, externalInputs);
inputRoot.appendChild(imported);
assertEquals("inputs", inputRoot.getFirstChild().getNodeName());
}
@Test
void importExternalDocumentShouldUseDocumentElement() throws Exception {
Document targetDocument = newDocument();
Element inputRoot = targetDocument.createElement("input");
targetDocument.appendChild(inputRoot);
Document externalDocument = newDocument();
Element externalInputs = externalDocument.createElement("inputs");
externalDocument.appendChild(externalInputs);
Node imported = importExternalNode(targetDocument, externalDocument);
inputRoot.appendChild(imported);
assertEquals("inputs", inputRoot.getFirstChild().getNodeName());
}
}

View File

@@ -14,7 +14,7 @@ import java.util.List;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
class CommunicationProducerTest { class CommunicationProducerTest {
@@ -32,6 +32,20 @@ class CommunicationProducerTest {
method.invoke(producer, context, response); method.invoke(producer, context, response);
} }
private static String invokeBuildInputXml(
CommunicationProducer producer,
PartnerRunningFlowContext context,
List<?> communicationBlocks
) throws Exception {
Method method = CommunicationProducer.class.getDeclaredMethod(
"buildInputXml",
PartnerRunningFlowContext.class,
List.class
);
method.setAccessible(true);
return (String) method.invoke(producer, context, communicationBlocks);
}
private static void setField(Object target, String fieldName, Object value) throws Exception { private static void setField(Object target, String fieldName, Object value) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName); Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true); field.setAccessible(true);
@@ -72,6 +86,24 @@ class CommunicationProducerTest {
assertEquals("[[AGENT]: self]:\n\nnormal reply", chatMessages.get(1).getContent()); assertEquals("[[AGENT]: self]:\n\nnormal reply", chatMessages.get(1).getContent());
} }
@Test
void shouldBuildInputXmlWithoutExtraWrapper() throws Exception {
StubCognitionCapability cognitionCapability = new StubCognitionCapability();
CommunicationProducer producer = new CommunicationProducer();
setField(producer, "cognitionCapability", cognitionCapability);
String xml = invokeBuildInputXml(
producer,
PartnerRunningFlowContext.fromUser("user-1", "hello"),
List.of()
);
assertTrue(xml.contains("<input>"));
assertTrue(xml.contains("<inputs>"));
assertTrue(xml.contains("<input interval-to-first=\"0\">hello</input>"));
assertFalse(xml.contains("<wrapper>"));
}
private static final class StubCognitionCapability implements CognitionCapability { private static final class StubCognitionCapability implements CognitionCapability {
private final ContextWorkspace contextWorkspace = new ContextWorkspace(); private final ContextWorkspace contextWorkspace = new ContextWorkspace();
private final List<Message> chatMessages = new ArrayList<>(); private final List<Message> chatMessages = new ArrayList<>();