mirror of
https://github.com/slhaf/Partner.git
synced 2026-06-27 17:49:16 +08:00
Compare commits
4 Commits
0567837dfe
...
9f9f7247f0
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f9f7247f0 | |||
| 15c24154f8 | |||
| a23657ec0c | |||
| 371b4a01d7 |
@@ -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,55 @@ 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);
|
||||
|
||||
/**
|
||||
* Rename the canonical subject of a known entity and refresh entity/active-entity indexes.
|
||||
*/
|
||||
boolean renameEntitySubject(String entityUuid, String newSubject);
|
||||
|
||||
/**
|
||||
* Add an alias or mention form for a known entity and refresh entity indexes.
|
||||
*/
|
||||
boolean addEntityAlias(String entityUuid, String alias, boolean deprecated);
|
||||
|
||||
/**
|
||||
* 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) }
|
||||
|
||||
@@ -6,6 +6,7 @@ import work.slhaf.partner.framework.agent.state.State
|
||||
import work.slhaf.partner.framework.agent.state.StateSerializable
|
||||
import work.slhaf.partner.framework.agent.state.StateValue
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -13,15 +14,67 @@ import kotlin.concurrent.withLock
|
||||
|
||||
class Entity @JvmOverloads constructor(
|
||||
val uuid: String = UUID.randomUUID().toString(),
|
||||
val subject: String,
|
||||
subject: String,
|
||||
private val relations: MutableMap<String, MutableMap<String, Double>> = mutableMapOf(),
|
||||
private val impressions: MutableMap<String, IndexableData> = mutableMapOf(),
|
||||
private val features: MutableMap<String, IndexableData> = mutableMapOf()
|
||||
private val features: MutableMap<String, IndexableData> = mutableMapOf(),
|
||||
private val aliases: MutableMap<String, AliasMetadata> = mutableMapOf()
|
||||
) : StateSerializable {
|
||||
|
||||
private var _subject: String = normalizeIdentityText(subject)
|
||||
|
||||
private val impressionLock = ReentrantLock()
|
||||
private val relationLock = ReentrantLock()
|
||||
private val featureLock = ReentrantLock()
|
||||
private val identityLock = ReentrantLock()
|
||||
|
||||
val subject: String
|
||||
get() = identityLock.withLock { _subject }
|
||||
|
||||
@JvmOverloads
|
||||
fun renameSubject(newSubject: String, keepOldSubjectAsAlias: Boolean = true): Boolean = identityLock.withLock {
|
||||
val normalizedSubject = normalizeIdentityText(newSubject)
|
||||
if (normalizedSubject.isBlank() || normalizedSubject == _subject) {
|
||||
return@withLock false
|
||||
}
|
||||
|
||||
val previousSubject = _subject
|
||||
if (keepOldSubjectAsAlias && previousSubject.isNotBlank()) {
|
||||
aliases[previousSubject] = aliases[previousSubject]?.copy(deprecated = true)
|
||||
?: AliasMetadata(Instant.now(), deprecated = true)
|
||||
}
|
||||
|
||||
aliases.remove(normalizedSubject)
|
||||
_subject = normalizedSubject
|
||||
true
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun addAlias(alias: String, deprecated: Boolean = false): Boolean = identityLock.withLock {
|
||||
val normalizedAlias = normalizeIdentityText(alias)
|
||||
if (normalizedAlias.isBlank() || normalizedAlias == _subject) {
|
||||
return@withLock false
|
||||
}
|
||||
|
||||
aliases[normalizedAlias] = aliases[normalizedAlias]?.copy(deprecated = deprecated)
|
||||
?: AliasMetadata(Instant.now(), deprecated)
|
||||
true
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun showAliases(includeDeprecated: Boolean = false): Set<AliasView> = identityLock.withLock {
|
||||
aliases.asSequence()
|
||||
.filter { (_, metadata) -> includeDeprecated || !metadata.deprecated }
|
||||
.map { (alias, metadata) ->
|
||||
AliasView(alias, metadata.instant, metadata.deprecated)
|
||||
}
|
||||
.sortedWith(compareBy<AliasView> { it.createdAt }.thenBy { it.alias })
|
||||
.toCollection(LinkedHashSet())
|
||||
}
|
||||
|
||||
fun snapshotAliases(): Map<String, AliasMetadata> = identityLock.withLock {
|
||||
aliases.mapValues { (_, metadata) -> metadata.copy() }
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun updateRelation(
|
||||
@@ -154,18 +207,56 @@ class Entity @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
identityLock.withLock {
|
||||
state.getString("subject")
|
||||
?.let(::normalizeIdentityText)
|
||||
?.takeIf(String::isNotBlank)
|
||||
?.let { _subject = it }
|
||||
}
|
||||
|
||||
state.getJSONObject("features")?.let { loadedFeatures ->
|
||||
featureLock.withLock {
|
||||
features.clear()
|
||||
features.putAll(loadIndexableDataMap(loadedFeatures))
|
||||
}
|
||||
}
|
||||
|
||||
state.getJSONObject("aliases")?.let { loadedAliases ->
|
||||
identityLock.withLock {
|
||||
aliases.clear()
|
||||
loadedAliases.forEach { (alias, metadataValue) ->
|
||||
val normalizedAlias = normalizeIdentityText(alias)
|
||||
if (normalizedAlias.isBlank() || normalizedAlias == _subject) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val metadata = when (metadataValue) {
|
||||
is JSONObject -> loadAliasMetadata(metadataValue)
|
||||
else -> AliasMetadata(Instant.now(), deprecated = false)
|
||||
}
|
||||
aliases[normalizedAlias] = metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun convert(): State {
|
||||
val state = State()
|
||||
state.append("uuid", StateValue.str(uuid))
|
||||
state.append("subject", StateValue.str(subject))
|
||||
|
||||
val identityState = identityLock.withLock {
|
||||
IdentityState(
|
||||
subject = _subject,
|
||||
aliases = aliases.mapValues { (_, metadata) ->
|
||||
mapOf(
|
||||
"timestamp" to metadata.instant.toEpochMilli(),
|
||||
"deprecated" to metadata.deprecated
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
state.append("subject", StateValue.str(identityState.subject))
|
||||
state.append("aliases", StateValue.obj(identityState.aliases))
|
||||
|
||||
val relationState = relationLock.withLock {
|
||||
relations.mapValues { (_, relationMap) -> relationMap.toMap() }
|
||||
@@ -187,6 +278,22 @@ class Entity @JvmOverloads constructor(
|
||||
|
||||
override fun autoLoadOnRegister(): Boolean = false
|
||||
|
||||
private fun normalizeIdentityText(value: String): String =
|
||||
value.replace(IDENTITY_WHITESPACE_REGEX, " ").trim()
|
||||
|
||||
private fun loadAliasMetadata(state: JSONObject): AliasMetadata {
|
||||
val instant = state.getLong("timestamp")
|
||||
?.let(Instant::ofEpochMilli)
|
||||
?: state.getString("instant")
|
||||
?.let { runCatching { Instant.parse(it) }.getOrNull() }
|
||||
?: Instant.now()
|
||||
|
||||
return AliasMetadata(
|
||||
instant = instant,
|
||||
deprecated = state.getBoolean("deprecated") ?: false
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadIndexableDataMap(state: JSONObject): Map<String, IndexableData> {
|
||||
val loaded = mutableMapOf<String, IndexableData>()
|
||||
state.forEach { (key, value) ->
|
||||
@@ -269,4 +376,24 @@ class Entity @JvmOverloads constructor(
|
||||
val confidence: Double,
|
||||
val vector: FloatArray?
|
||||
)
|
||||
|
||||
private data class IdentityState(
|
||||
val subject: String,
|
||||
val aliases: Map<String, Map<String, Any>>
|
||||
)
|
||||
|
||||
data class AliasView(
|
||||
val alias: String,
|
||||
val createdAt: Instant,
|
||||
val deprecated: Boolean
|
||||
)
|
||||
|
||||
data class AliasMetadata(
|
||||
val instant: Instant,
|
||||
val deprecated: Boolean
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val IDENTITY_WHITESPACE_REGEX = Regex("\\s+")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,209 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the canonical subject of a known entity and keep its previous subject as a historical alias.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean renameEntitySubject(String entityUuid, String newSubject) {
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null || newSubject == null || newSubject.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean renamed = entity.renameSubject(newSubject.trim());
|
||||
if (!renamed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
refreshKnownEntityIndexes(entity);
|
||||
syncBoundActiveEntitySubjects(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alias or mention form for a known entity and refresh search indexes.
|
||||
*/
|
||||
@CapabilityMethod
|
||||
public boolean addEntityAlias(String entityUuid, String alias, boolean deprecated) {
|
||||
Entity entity = knownEntitiesByUuid.get(entityUuid);
|
||||
if (entity == null || alias == null || alias.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean added = entity.addAlias(alias.trim(), deprecated);
|
||||
if (!added) {
|
||||
return false;
|
||||
}
|
||||
|
||||
refreshKnownEntityIndexes(entity);
|
||||
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 +386,42 @@ 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);
|
||||
}
|
||||
|
||||
private void syncBoundActiveEntitySubjects(Entity entity) {
|
||||
List<ActiveEntity> boundEntities;
|
||||
synchronized (activeEntities) {
|
||||
boundEntities = activeEntities.stream()
|
||||
.filter(activeEntity -> entity.getUuid().equals(activeEntity.getBoundEntityUuid()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
boundEntities.forEach(activeEntity -> {
|
||||
activeEntity.updateSubject(entity.getSubject());
|
||||
refreshActiveEntityTextSearch(activeEntity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +461,7 @@ public class ImpressionCore implements StateSerializable {
|
||||
}
|
||||
|
||||
Entity entity = new Entity(uuid, subject);
|
||||
entity.register();
|
||||
entity.load();
|
||||
vectorIndex.sync(entity);
|
||||
knownEntitiesByUuid.put(uuid, entity);
|
||||
|
||||
@@ -84,6 +84,18 @@ object ImpressionSearchDocuments {
|
||||
)
|
||||
)
|
||||
|
||||
entity.showAliases(includeDeprecated = true).forEachIndexed { index, alias ->
|
||||
add(
|
||||
ImpressionSearchDocument(
|
||||
id = "entity:${entity.uuid}:alias:$index",
|
||||
target = target,
|
||||
field = ImpressionSearchField.SUBJECT,
|
||||
text = alias.alias,
|
||||
weight = SUBJECT_WEIGHT * ALIAS_WEIGHT_FACTOR,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
entity.snapshotFeatures().keys.forEachIndexed { index, feature ->
|
||||
add(
|
||||
ImpressionSearchDocument(
|
||||
@@ -131,6 +143,7 @@ object ImpressionSearchDocuments {
|
||||
}
|
||||
|
||||
private const val SUBJECT_WEIGHT = 1.0
|
||||
private const val ALIAS_WEIGHT_FACTOR = 0.9
|
||||
private const val FEATURE_WEIGHT = 0.85
|
||||
private const val IMPRESSION_WEIGHT = 0.75
|
||||
private const val RELATION_WEIGHT = 0.65
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import work.slhaf.partner.core.cognition.impression.ActiveEntity
|
||||
import work.slhaf.partner.core.cognition.impression.Entity
|
||||
|
||||
class SimpleTextSearchTest {
|
||||
|
||||
@@ -116,6 +117,21 @@ class SimpleTextSearchTest {
|
||||
assertEquals("report", hits.first().document.target.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `search recalls known entity by alias documents`() {
|
||||
val search = SimpleTextSearch(TestTokenizer())
|
||||
val entity = Entity("entity-1", "Partner")
|
||||
entity.addAlias("智能体项目")
|
||||
|
||||
search.rebuild(ImpressionSearchDocuments.fromEntity(entity))
|
||||
|
||||
val hits = search.search("智能体项目", limit = 10)
|
||||
|
||||
assertFalse(hits.isEmpty())
|
||||
assertEquals(ImpressionSearchTarget.Type.ENTITY, hits.first().document.target.type)
|
||||
assertEquals("entity-1", hits.first().document.target.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `upsert replaces previous index terms for the same document id`() {
|
||||
val search = SimpleTextSearch(TestTokenizer())
|
||||
@@ -207,7 +223,8 @@ class SimpleTextSearchTest {
|
||||
private val dictionary = listOf(
|
||||
"城南", "旧书店", "老板", "推荐", "工程", "教材", "水利", "熟悉", "旧书",
|
||||
"java", "kotlin", "jieba", "分词", "simpletextsearch", "倒排", "索引", "检索", "测试", "召回",
|
||||
"vivado", "实验报告", "实验", "报告", "模板", "docx", "室友", "整理", "文件"
|
||||
"vivado", "实验报告", "实验", "报告", "模板", "docx", "室友", "整理", "文件",
|
||||
"智能体", "项目", "智能体项目"
|
||||
)
|
||||
private val alphaNumericRegex = Regex("[a-z0-9]+(?:[-_./][a-z0-9]+)*")
|
||||
|
||||
|
||||
@@ -145,5 +145,58 @@ 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 renameEntitySubject(String entityUuid, String newSubject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addEntityAlias(String entityUuid, String alias, boolean deprecated) {
|
||||
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,59 @@ 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 renameEntitySubject(String entityUuid, String newSubject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addEntityAlias(String entityUuid, String alias, boolean deprecated) {
|
||||
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