mirror of
https://github.com/slhaf/Partner.git
synced 2026-06-27 17:49:16 +08:00
feat(impression): add core mutation APIs
Expose core-owned entity creation, binding, and impression update methods so updater logic can request mutations without bypassing indexes. Add ActiveEntity snapshots for safe inspection and keep test stubs aligned with CognitionCapability.
This commit is contained in:
@@ -3,10 +3,12 @@ package work.slhaf.partner.core.cognition;
|
||||
import org.w3c.dom.Element;
|
||||
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
|
||||
import work.slhaf.partner.core.cognition.impression.ActiveEntity;
|
||||
import work.slhaf.partner.core.cognition.impression.Entity;
|
||||
import work.slhaf.partner.framework.agent.factory.capability.annotation.Capability;
|
||||
import work.slhaf.partner.framework.agent.model.pojo.Message;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
@@ -29,6 +31,45 @@ public interface CognitionCapability {
|
||||
|
||||
Lock getMessageLock();
|
||||
|
||||
/**
|
||||
* Project user input onto known or currently active entities and append the input as runtime evidence.
|
||||
*/
|
||||
Set<ActiveEntity> projectEntity(String input);
|
||||
|
||||
/**
|
||||
* Return current active entities with their bound known entities when available.
|
||||
* ActiveEntity values are snapshots; Entity values are live known-entity references and should be updated through this capability.
|
||||
*/
|
||||
Map<ActiveEntity, Entity> showEntities();
|
||||
|
||||
/**
|
||||
* Create and register a new known entity by subject, then refresh search indexes for it.
|
||||
*/
|
||||
Entity createEntity(String subject);
|
||||
|
||||
/**
|
||||
* Return a known entity by uuid, or null when it does not exist.
|
||||
*/
|
||||
Entity getEntity(String uuid);
|
||||
|
||||
/**
|
||||
* Bind an active runtime entity to a known entity and refresh the active-entity search document.
|
||||
*/
|
||||
boolean bindActiveEntity(String runtimeId, String entityUuid);
|
||||
|
||||
/**
|
||||
* Add or replace an impression on a known entity and refresh all entity indexes.
|
||||
*/
|
||||
boolean updateEntityImpression(String entityUuid, String impression, String newImpression, double confidence);
|
||||
|
||||
/**
|
||||
* Add or replace a stable feature on a known entity and refresh all entity indexes.
|
||||
*/
|
||||
boolean updateEntityFeature(String entityUuid, String feature, String newFeature, double confidence);
|
||||
|
||||
/**
|
||||
* Add or update a relation from one known entity to another target and refresh all entity indexes.
|
||||
*/
|
||||
boolean updateEntityRelation(String entityUuid, String target, String relation, double strength);
|
||||
|
||||
}
|
||||
|
||||
@@ -64,6 +64,23 @@ class ActiveEntity @JvmOverloads constructor(
|
||||
impressions.forEach { _projectedImpressions[it.first] = it.second }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a detached runtime snapshot for external inspection without exposing mutable internal collections.
|
||||
*/
|
||||
fun snapshot(): ActiveEntity {
|
||||
val copied = ActiveEntity(
|
||||
runtimeId = runtimeId,
|
||||
createdAt = createdAt,
|
||||
boundEntityUuid = boundEntityUuid,
|
||||
_evidences = synchronized(_evidences) { _evidences.toMutableList() },
|
||||
)
|
||||
copied.updateSubject(subject)
|
||||
copied.touch(lastMentionedAt)
|
||||
copied.addProjectedFeatures(*projectedFeatures.entries.map { it.key to it.value }.toTypedArray())
|
||||
copied.addProjectedImpressions(*projectedImpressions.entries.map { it.key to it.value }.toTypedArray())
|
||||
return copied
|
||||
}
|
||||
|
||||
override fun fillXml(document: Document, root: Element) {
|
||||
root.setAttribute("runtime_id", runtimeId)
|
||||
boundEntityUuid?.let { root.setAttribute("bound_entity_uuid", it) }
|
||||
|
||||
@@ -69,6 +69,170 @@ public class ImpressionCore implements StateSerializable {
|
||||
return projected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出当前已存在的 ActiveEntity 以及对应的 Entity。ActiveEntity 返回快照,Entity 返回当前已知实体引用。
|
||||
*
|
||||
* 注意:外部模块不要直接修改返回的 Entity,否则文本索引 / 向量索引不会刷新。
|
||||
* Impression 更新应走 updateEntity* 系列接口。
|
||||
*
|
||||
* @return ActiveEntity 快照与已绑定 Entity 的映射
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public Map<ActiveEntity, Entity> showEntities() {
|
||||
Map<ActiveEntity, Entity> result = new LinkedHashMap<>();
|
||||
List<ActiveEntity> entities;
|
||||
synchronized (activeEntities) {
|
||||
entities = activeEntities.stream()
|
||||
.sorted(Comparator
|
||||
.comparing(ActiveEntity::getLastMentionedAt)
|
||||
.reversed()
|
||||
.thenComparing(ActiveEntity::getRuntimeId))
|
||||
.toList();
|
||||
}
|
||||
|
||||
for (ActiveEntity activeEntity : entities) {
|
||||
Entity boundEntity = Optional.ofNullable(activeEntity.getBoundEntityUuid())
|
||||
.map(knownEntitiesByUuid::get)
|
||||
.orElse(null);
|
||||
result.put(activeEntity.snapshot(), boundEntity);
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new known entity and make it visible to recall/update indexes immediately.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public Entity createEntity(String subject) {
|
||||
if (subject == null || subject.isBlank()) {
|
||||
throw new IllegalArgumentException("subject must not be blank");
|
||||
}
|
||||
|
||||
Entity entity = new Entity(UUID.randomUUID().toString(), subject.trim());
|
||||
entity.register();
|
||||
knownEntitiesByUuid.put(entity.getUuid(), entity);
|
||||
refreshKnownEntityIndexes(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a known entity by stable uuid.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public Entity getEntity(String uuid) {
|
||||
if (uuid == null || uuid.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return knownEntitiesByUuid.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a runtime active entity to a known entity.
|
||||
* This keeps the active entity in current context while giving later updates a stable storage target.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean bindActiveEntity(String runtimeId, String entityUuid) {
|
||||
if (runtimeId == null || runtimeId.isBlank() || entityUuid == null || entityUuid.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Optional<ActiveEntity> activeEntity = findActiveEntityByRuntimeId(runtimeId);
|
||||
if (activeEntity.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ActiveEntity active = activeEntity.get();
|
||||
active.bindEntity(entityUuid);
|
||||
active.updateSubject(entity.getSubject());
|
||||
refreshActiveEntityTextSearch(active);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a known entity impression through the core so text/vector indexes stay consistent.
|
||||
* newImpression can be null or blank to update the existing impression in place.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean updateEntityImpression(
|
||||
String entityUuid,
|
||||
String impression,
|
||||
String newImpression,
|
||||
double confidence
|
||||
) {
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null || impression == null || impression.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity.updateImpression(
|
||||
impression.trim(),
|
||||
normalizeNullableText(newImpression),
|
||||
confidence
|
||||
);
|
||||
refreshKnownEntityIndexes(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a known entity feature through the core so text/vector indexes stay consistent.
|
||||
* newFeature can be null or blank to update the existing feature in place.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean updateEntityFeature(
|
||||
String entityUuid,
|
||||
String feature,
|
||||
String newFeature,
|
||||
double confidence
|
||||
) {
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null || feature == null || feature.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity.updateFeature(
|
||||
feature.trim(),
|
||||
normalizeNullableText(newFeature),
|
||||
confidence
|
||||
);
|
||||
refreshKnownEntityIndexes(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a known entity relation through the core so search documents reflect the changed relation.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean updateEntityRelation(
|
||||
String entityUuid,
|
||||
String target,
|
||||
String relation,
|
||||
double strength
|
||||
) {
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null || target == null || target.isBlank() || relation == null || relation.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entity.updateRelation(target.trim(), relation.trim(), strength);
|
||||
refreshKnownEntityIndexes(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize optional replacement text used by update methods.
|
||||
*/
|
||||
private String normalizeNullableText(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private List<EntityAssociationMatch> aggregateMatches(
|
||||
List<ImpressionSearchHit> hits,
|
||||
int limit
|
||||
@@ -183,6 +347,28 @@ public class ImpressionCore implements StateSerializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh every index derived from a known entity after mutation.
|
||||
*/
|
||||
private void refreshKnownEntityIndexes(Entity entity) {
|
||||
vectorIndex.sync(entity);
|
||||
refreshKnownEntityTextSearch(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace text-search documents for one known entity.
|
||||
*/
|
||||
private void refreshKnownEntityTextSearch(Entity entity) {
|
||||
ImpressionSearchTarget target = new ImpressionSearchTarget(
|
||||
ImpressionSearchTarget.Type.ENTITY,
|
||||
entity.getUuid()
|
||||
);
|
||||
textSearch.removeByTarget(target);
|
||||
for (ImpressionSearchDocument document : ImpressionSearchDocuments.INSTANCE.fromEntity(entity)) {
|
||||
textSearch.upsert(document);
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildTextSearch() {
|
||||
List<ImpressionSearchDocument> documents = new ArrayList<>();
|
||||
knownEntitiesByUuid.values().forEach(entity ->
|
||||
@@ -222,6 +408,7 @@ public class ImpressionCore implements StateSerializable {
|
||||
}
|
||||
|
||||
Entity entity = new Entity(uuid, subject);
|
||||
entity.register();
|
||||
entity.load();
|
||||
vectorIndex.sync(entity);
|
||||
knownEntitiesByUuid.put(uuid, entity);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package work.slhaf.partner.module.impression;
|
||||
|
||||
import work.slhaf.partner.core.cognition.CognitionCapability;
|
||||
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;
|
||||
import work.slhaf.partner.framework.agent.factory.component.abstracts.AbstractAgentModule;
|
||||
import work.slhaf.partner.module.communication.AfterRolling;
|
||||
import work.slhaf.partner.module.communication.RollingResult;
|
||||
|
||||
public class ImpressionUpdater extends AbstractAgentModule.Standalone implements AfterRolling {
|
||||
|
||||
@InjectCapability
|
||||
private CognitionCapability cognitionCapability;
|
||||
|
||||
@Override
|
||||
public void consume(RollingResult result) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -145,5 +145,48 @@ class CommunicationProducerTest {
|
||||
public Lock getMessageLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Set<work.slhaf.partner.core.cognition.impression.ActiveEntity> projectEntity(String input) {
|
||||
return java.util.Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Map<
|
||||
work.slhaf.partner.core.cognition.impression.ActiveEntity,
|
||||
work.slhaf.partner.core.cognition.impression.Entity
|
||||
> showEntities() {
|
||||
return java.util.Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public work.slhaf.partner.core.cognition.impression.Entity createEntity(String subject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public work.slhaf.partner.core.cognition.impression.Entity getEntity(String uuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bindActiveEntity(String runtimeId, String entityUuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityImpression(String entityUuid, String impression, String newImpression, double confidence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityFeature(String entityUuid, String feature, String newFeature, double confidence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityRelation(String entityUuid, String target, String relation, double strength) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,49 @@ class MemoryRuntimeTest {
|
||||
public Lock getMessageLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Set<work.slhaf.partner.core.cognition.impression.ActiveEntity> projectEntity(String input) {
|
||||
return java.util.Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Map<
|
||||
work.slhaf.partner.core.cognition.impression.ActiveEntity,
|
||||
work.slhaf.partner.core.cognition.impression.Entity
|
||||
> showEntities() {
|
||||
return java.util.Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public work.slhaf.partner.core.cognition.impression.Entity createEntity(String subject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public work.slhaf.partner.core.cognition.impression.Entity getEntity(String uuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bindActiveEntity(String runtimeId, String entityUuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityImpression(String entityUuid, String impression, String newImpression, double confidence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityFeature(String entityUuid, String feature, String newFeature, double confidence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEntityRelation(String entityUuid, String target, String relation, double strength) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user