24 Commits

Author SHA1 Message Date
96817d84fe feat(impression): introduce active entity recall model 2026-05-30 23:26:08 +08:00
dd64599154 feat(impression): add structured entity evidence metadata 2026-05-30 21:34:32 +08:00
4b638b756e fix(vector): skip upsert when vector client is unavailable 2026-05-30 21:04:22 +08:00
23a1b7093e feat(vector): implement Impression vector upsert and sync 2026-05-28 22:41:41 +08:00
9de46f3589 refactor(vector): add model id assigning support 2026-05-28 21:57:11 +08:00
fd8a0642b3 chore: remove meaningless warn 2026-05-28 21:05:26 +08:00
cffb369aef feat(impression): persist entity vectors 2026-05-28 20:59:20 +08:00
a929b3e0e6 feat(impression): add vector index skeleton 2026-05-27 23:27:51 +08:00
fe6895d10b feat(impression): serialize entity state 2026-05-27 23:11:41 +08:00
8323f8ed13 feat(impression): add entity overview state 2026-05-26 21:50:23 +08:00
0e1201253d refactor(impression): Improve impression entity snapshots 2026-05-26 21:29:09 +08:00
f3213675ff feat(impression): Add ActiveEntity to support runtime entity-discovering and block production 2026-05-23 23:42:06 +08:00
26ef5d875d fix(impression): correct impression update behavior 2026-05-23 23:22:15 +08:00
047d1b56fe refactor(impression): add feature attribute as index, which will work with high-confidence impressions 2026-05-18 23:05:35 +08:00
11aae1a353 feat(impression): introduce entity relation and impression model 2026-05-18 00:01:00 +08:00
e5dcb49028 chore: update gitignore 2026-05-17 21:07:21 +08:00
70a94d9c30 refactor(cognition): move context classes into context package 2026-05-16 21:57:29 +08:00
ef096e76b3 refactor(state): optimize StateValue building methods 2026-05-15 23:07:40 +08:00
ed743521ec chore: rename cognition core into context core 2026-05-15 16:16:44 +08:00
cb8ddfe4e2 docs: update README startup guide for PartnerCtl 2026-05-14 22:05:37 +08:00
756c0a12ad fix(partnerctl): include zh-CN locale in native image build 2026-05-14 20:41:53 +08:00
8a5b844a4a feat(partnerctl-init): add release download install option 2026-05-14 19:47:18 +08:00
github-actions[bot]
8d29ea4c9e chore(registry): update latest core release to release-core/0.9.0-preview 2026-05-14 09:08:43 +00:00
github-actions[bot]
4770eaf42f chore(registry): update latest buildable to buildable/0.9.0-preview 2026-05-14 09:07:33 +00:00
58 changed files with 1348 additions and 164 deletions

3
.gitignore vendored
View File

@@ -60,4 +60,5 @@ build/
/.codex /.codex
# Maven / build outputs # Maven / build outputs
dependency-reduced-pom.xml dependency-reduced-pom.xml
/.backup/

View File

@@ -12,6 +12,7 @@ public abstract class VectorClient {
public static boolean status = false; public static boolean status = false;
public static VectorClient INSTANCE; public static VectorClient INSTANCE;
public static String VECTOR_MODEL_ID;
public static void startClient(VectorConfig config) { public static void startClient(VectorConfig config) {
try { try {
@@ -23,6 +24,7 @@ public abstract class VectorClient {
return; return;
} }
status = true; status = true;
VECTOR_MODEL_ID = config.modelId;
} catch (VectorClientStartupException e) { } catch (VectorClientStartupException e) {
throw e; throw e;
} catch (VectorClientExecutionException e) { } catch (VectorClientExecutionException e) {

View File

@@ -39,7 +39,7 @@ public class VectorClientRegistry implements Configurable, ConfigRegistration<Ve
@Nullable @Nullable
@Override @Override
public VectorConfig defaultConfig() { public VectorConfig defaultConfig() {
return new VectorConfig(false, null); return new VectorConfig(false, null, null);
} }
@Override @Override

View File

@@ -5,10 +5,16 @@ import work.slhaf.partner.framework.agent.config.Config;
public sealed class VectorConfig extends Config permits VectorConfig.Ollama, VectorConfig.Onnx { public sealed class VectorConfig extends Config permits VectorConfig.Ollama, VectorConfig.Onnx {
final boolean enabled; final boolean enabled;
final Type type; final Type type;
final String modelId;
public VectorConfig(boolean enabled, Type type) { public VectorConfig(boolean enabled, Type type, String modelId) {
this.enabled = enabled; this.enabled = enabled;
this.type = type; this.type = type;
this.modelId = modelId;
}
protected static String fallbackModelId(String modelId, String fallback) {
return modelId == null || modelId.isBlank() ? fallback : modelId;
} }
public enum Type { public enum Type {
@@ -21,8 +27,8 @@ public sealed class VectorConfig extends Config permits VectorConfig.Ollama, Vec
final String tokenizerPath; final String tokenizerPath;
final String embeddingModelPath; final String embeddingModelPath;
public Onnx(boolean enabled, Type type, String tokenizerPath, String embeddingModelPath) { public Onnx(boolean enabled, Type type, String tokenizerPath, String embeddingModelPath, String modelId) {
super(enabled, type); super(enabled, type, fallbackModelId(modelId, embeddingModelPath));
this.tokenizerPath = tokenizerPath; this.tokenizerPath = tokenizerPath;
this.embeddingModelPath = embeddingModelPath; this.embeddingModelPath = embeddingModelPath;
} }
@@ -33,12 +39,10 @@ public sealed class VectorConfig extends Config permits VectorConfig.Ollama, Vec
final String ollamaEmbeddingUrl; final String ollamaEmbeddingUrl;
final String ollamaEmbeddingModel; final String ollamaEmbeddingModel;
public Ollama(boolean enabled, Type type, String ollamaEmbeddingUrl, String ollamaEmbeddingModel) { public Ollama(boolean enabled, Type type, String ollamaEmbeddingUrl, String ollamaEmbeddingModel, String modelId) {
super(enabled, type); super(enabled, type, fallbackModelId(modelId, ollamaEmbeddingModel));
this.ollamaEmbeddingUrl = ollamaEmbeddingUrl; this.ollamaEmbeddingUrl = ollamaEmbeddingUrl;
this.ollamaEmbeddingModel = ollamaEmbeddingModel; this.ollamaEmbeddingModel = ollamaEmbeddingModel;
} }
} }
} }

View File

@@ -50,70 +50,70 @@ final class ActionPoolStateCodec {
} }
private static StateValue.Obj encodeExecutableAction(ExecutableAction action) { private static StateValue.Obj encodeExecutableAction(ExecutableAction action) {
Map<String, StateValue> actionMap = new LinkedHashMap<>(); Map<String, Object> actionMap = new LinkedHashMap<>();
actionMap.put("kind", StateValue.str(action instanceof SchedulableExecutableAction ? "schedulable" : "immediate")); actionMap.put("kind", action instanceof SchedulableExecutableAction ? "schedulable" : "immediate");
actionMap.put("uuid", StateValue.str(action.getUuid())); actionMap.put("uuid", action.getUuid());
actionMap.put("source", StateValue.str(action.getSource())); actionMap.put("source", action.getSource());
actionMap.put("reason", StateValue.str(action.getReason())); actionMap.put("reason", action.getReason());
actionMap.put("description", StateValue.str(action.getDescription())); actionMap.put("description", action.getDescription());
actionMap.put("status", StateValue.str(action.getStatus().name())); actionMap.put("status", action.getStatus().name());
actionMap.put("tendency", StateValue.str(action.getTendency())); actionMap.put("tendency", action.getTendency());
actionMap.put("executing_stage", StateValue.num(action.getExecutingStage())); actionMap.put("executing_stage", action.getExecutingStage());
String result = resolveExecutableResult(action); String result = resolveExecutableResult(action);
if (result != null) { if (result != null) {
actionMap.put("result", StateValue.str(result)); actionMap.put("result", result);
} }
if (action instanceof SchedulableExecutableAction schedulableAction) { if (action instanceof SchedulableExecutableAction schedulableAction) {
actionMap.put("schedule_type", StateValue.str(schedulableAction.getScheduleType().name())); actionMap.put("schedule_type", schedulableAction.getScheduleType().name());
actionMap.put("schedule_content", StateValue.str(schedulableAction.getScheduleContent())); actionMap.put("schedule_content", schedulableAction.getScheduleContent());
actionMap.put("enabled", StateValue.bool(schedulableAction.getEnabled())); actionMap.put("enabled", schedulableAction.getEnabled());
actionMap.put("schedule_histories", StateValue.arr(encodeScheduleHistories(schedulableAction))); actionMap.put("schedule_histories", encodeScheduleHistories(schedulableAction));
} }
List<StateValue> chainStates = action.getActionChain().entrySet().stream() List<StateValue.Obj> chainStates = action.getActionChain().entrySet().stream()
.sorted(Map.Entry.comparingByKey()) .sorted(Map.Entry.comparingByKey())
.<StateValue>map(entry -> { .map(entry -> {
Map<String, StateValue> stageMap = new LinkedHashMap<>(); Map<String, Object> stageMap = new LinkedHashMap<>();
stageMap.put("stage", StateValue.num(entry.getKey())); stageMap.put("stage", entry.getKey());
String stageDescription = action.getStageDescriptions().get(entry.getKey()); String stageDescription = action.getStageDescriptions().get(entry.getKey());
if (stageDescription != null && !stageDescription.isBlank()) { if (stageDescription != null && !stageDescription.isBlank()) {
stageMap.put("description", StateValue.str(stageDescription)); stageMap.put("description", stageDescription);
} }
stageMap.put("actions", StateValue.arr(entry.getValue().stream() stageMap.put("actions", entry.getValue().stream()
.map(metaAction -> (StateValue) encodeMetaAction(metaAction)) .map(ActionPoolStateCodec::encodeMetaAction)
.toList())); .toList());
return StateValue.obj(stageMap); return StateValue.obj(stageMap);
}).toList(); }).toList();
actionMap.put("action_chain", StateValue.arr(chainStates)); actionMap.put("action_chain", chainStates);
actionMap.put("history", StateValue.arr(encodeHistoryStages(action.getHistory()))); actionMap.put("history", encodeHistoryStages(action.getHistory()));
return StateValue.obj(actionMap); return StateValue.obj(actionMap);
} }
private static StateValue.Obj encodeMetaAction(MetaAction metaAction) { private static StateValue.Obj encodeMetaAction(MetaAction metaAction) {
Map<String, StateValue> metaMap = new LinkedHashMap<>(); Map<String, Object> metaMap = new LinkedHashMap<>();
metaMap.put("name", StateValue.str(metaAction.getName())); metaMap.put("name", metaAction.getName());
metaMap.put("io", StateValue.bool(metaAction.getIo())); metaMap.put("io", metaAction.getIo());
if (metaAction.getLauncher() != null) { if (metaAction.getLauncher() != null) {
metaMap.put("launcher", StateValue.str(metaAction.getLauncher())); metaMap.put("launcher", metaAction.getLauncher());
} }
metaMap.put("type", StateValue.str(metaAction.getType().name())); metaMap.put("type", metaAction.getType().name());
metaMap.put("location", StateValue.str(metaAction.getLocation())); metaMap.put("location", metaAction.getLocation());
metaMap.put("params_json", StateValue.str(JSONObject.toJSONString(metaAction.getParams()))); metaMap.put("params_json", JSONObject.toJSONString(metaAction.getParams()));
metaMap.put("result_status", StateValue.str(metaAction.getResult().getStatus().name())); metaMap.put("result_status", metaAction.getResult().getStatus().name());
if (metaAction.getResult().getData() != null) { if (metaAction.getResult().getData() != null) {
metaMap.put("result_data", StateValue.str(metaAction.getResult().getData())); metaMap.put("result_data", metaAction.getResult().getData());
} }
return StateValue.obj(metaMap); return StateValue.obj(metaMap);
} }
private static StateValue.Obj encodeHistoryAction(HistoryAction historyAction) { private static StateValue.Obj encodeHistoryAction(HistoryAction historyAction) {
Map<String, StateValue> historyMap = new LinkedHashMap<>(); Map<String, Object> historyMap = new LinkedHashMap<>();
historyMap.put("action_key", StateValue.str(historyAction.actionKey())); historyMap.put("action_key", historyAction.actionKey());
historyMap.put("description", StateValue.str(historyAction.description())); historyMap.put("description", historyAction.description());
historyMap.put("result", StateValue.str(historyAction.result())); historyMap.put("result", historyAction.result());
return StateValue.obj(historyMap); return StateValue.obj(historyMap);
} }
@@ -288,26 +288,26 @@ final class ActionPoolStateCodec {
return restored; return restored;
} }
private static List<StateValue> encodeHistoryStages(Map<Integer, ? extends List<HistoryAction>> historyMap) { private static List<StateValue.Obj> encodeHistoryStages(Map<Integer, ? extends List<HistoryAction>> historyMap) {
return historyMap.entrySet().stream() return historyMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey()) .sorted(Map.Entry.comparingByKey())
.<StateValue>map(entry -> { .map(entry -> {
Map<String, StateValue> stageMap = new LinkedHashMap<>(); Map<String, Object> stageMap = new LinkedHashMap<>();
stageMap.put("stage", StateValue.num(entry.getKey())); stageMap.put("stage", entry.getKey());
stageMap.put("actions", StateValue.arr(entry.getValue().stream() stageMap.put("actions", entry.getValue().stream()
.map(historyAction -> (StateValue) encodeHistoryAction(historyAction)) .map(ActionPoolStateCodec::encodeHistoryAction)
.toList())); .toList());
return StateValue.obj(stageMap); return StateValue.obj(stageMap);
}).toList(); }).toList();
} }
private static List<StateValue> encodeScheduleHistories(SchedulableExecutableAction schedulableAction) { private static List<StateValue.Obj> encodeScheduleHistories(SchedulableExecutableAction schedulableAction) {
return schedulableAction.getScheduleHistories().stream() return schedulableAction.getScheduleHistories().stream()
.<StateValue>map(scheduleHistory -> { .map(scheduleHistory -> {
Map<String, StateValue> historyMap = new LinkedHashMap<>(); Map<String, Object> historyMap = new LinkedHashMap<>();
historyMap.put("end_time", StateValue.str(scheduleHistory.getEndTime().toString())); historyMap.put("end_time", scheduleHistory.getEndTime().toString());
historyMap.put("result", StateValue.str(scheduleHistory.getResult())); historyMap.put("result", scheduleHistory.getResult());
historyMap.put("history", StateValue.arr(encodeHistoryStages(scheduleHistory.getHistory()))); historyMap.put("history", encodeHistoryStages(scheduleHistory.getHistory()));
return StateValue.obj(historyMap); return StateValue.obj(historyMap);
}) })
.toList(); .toList();

View File

@@ -1,6 +1,7 @@
package work.slhaf.partner.core.cognition; package work.slhaf.partner.core.cognition;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.factory.capability.annotation.Capability; import work.slhaf.partner.framework.agent.factory.capability.annotation.Capability;
import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.pojo.Message;

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition; package work.slhaf.partner.core.cognition.context;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
@@ -27,7 +27,7 @@ import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
@CapabilityCore(value = "cognition") @CapabilityCore(value = "cognition")
public class CognitionCore implements StateSerializable { public class ContextCore implements StateSerializable {
private static final String RECENT_CHAT_MESSAGE_NOTES = """ private static final String RECENT_CHAT_MESSAGE_NOTES = """
消息格式: 消息格式:
@@ -58,7 +58,7 @@ public class CognitionCore implements StateSerializable {
private final ContextWorkspace contextWorkspace = new ContextWorkspace(); private final ContextWorkspace contextWorkspace = new ContextWorkspace();
public CognitionCore() { public ContextCore() {
register(); register();
} }
@@ -200,13 +200,12 @@ public class CognitionCore implements StateSerializable {
public @NotNull State convert() { public @NotNull State convert() {
State state = new State(); State state = new State();
List<StateValue.Obj> convertedMessageList = chatMessages.stream().map(message -> { List<StateValue.Obj> convertedMessageList = chatMessages.stream()
Map<String, StateValue> convertedMap = Map.of( .map(message -> StateValue.obj(Map.of(
"role", StateValue.str(message.roleValue()), "role", message.roleValue(),
"content", StateValue.str(message.getContent()) "content", message.getContent()
); )))
return StateValue.obj(convertedMap); .toList();
}).toList();
state.append("chat_messages", StateValue.arr(convertedMessageList)); state.append("chat_messages", StateValue.arr(convertedMessageList));
return state; return state;

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition package work.slhaf.partner.core.cognition.context
import com.alibaba.fastjson2.JSONObject import com.alibaba.fastjson2.JSONObject
import org.w3c.dom.Document import org.w3c.dom.Document

View File

@@ -1,4 +1,4 @@
package work.slhaf.partner.core.cognition package work.slhaf.partner.core.cognition.context
import org.w3c.dom.Document import org.w3c.dom.Document
import work.slhaf.partner.framework.agent.model.pojo.Message import work.slhaf.partner.framework.agent.model.pojo.Message

View File

@@ -0,0 +1,118 @@
package work.slhaf.partner.core.cognition.impression
import org.w3c.dom.Document
import org.w3c.dom.Element
import work.slhaf.partner.core.cognition.context.BlockContent
import java.time.Instant
import java.time.ZoneId
import java.util.*
import java.util.concurrent.atomic.AtomicReference
class ActiveEntity @JvmOverloads constructor(
val runtimeId: String = newActiveEntityRuntimeId(),
val createdAt: Instant = Instant.now(),
boundEntityUuid: String? = null,
private val _evidences: MutableList<EntityEvidence> = mutableListOf(),
) : BlockContent("active_entity_$runtimeId", "impression") {
val evidences: List<EntityEvidence>
get() = synchronized(_evidences) { _evidences.toList() }
@Volatile
var lastMentionedAt: Instant = createdAt
private set
private val _subject = AtomicReference("UNKNOWN")
val subject: String get() = _subject.get()
private val _boundEntityUuid = AtomicReference<String?>(boundEntityUuid)
val boundEntityUuid: String? get() = _boundEntityUuid.get()
private val _projectedFeatures: MutableMap<String, Double> = mutableMapOf()
val projectedFeatures: Map<String, Double>
get() = synchronized(_projectedFeatures) { _projectedFeatures.toMap() }
private val _projectedImpressions: MutableMap<String, Double> = mutableMapOf()
val projectedImpressions: Map<String, Double>
get() = synchronized(_projectedImpressions) { _projectedImpressions.toMap() }
@JvmOverloads
fun addEvidence(
content: String,
associationConfidence: Double = 1.0,
source: EntityEvidence.Source = EntityEvidence.Source.USER_INPUT,
timestamp: Long = System.currentTimeMillis(),
) = addEvidence(EntityEvidence(content, associationConfidence, source, timestamp))
fun addEvidence(evidence: EntityEvidence) = synchronized(_evidences) {
_evidences.add(evidence)
touch(Instant.ofEpochMilli(evidence.timestamp))
}
fun updateSubject(subject: String) = _subject.set(subject)
fun bindEntity(uuid: String?) = _boundEntityUuid.set(uuid)
fun touch(time: Instant = Instant.now()) {
lastMentionedAt = time
}
fun addProjectedFeatures(vararg features: Pair<String, Double>) = synchronized(_projectedFeatures) {
features.forEach { _projectedFeatures[it.first] = it.second }
}
fun addProjectedImpressions(vararg impressions: Pair<String, Double>) = synchronized(_projectedImpressions) {
impressions.forEach { _projectedImpressions[it.first] = it.second }
}
override fun fillXml(document: Document, root: Element) {
root.setAttribute("runtime_id", runtimeId)
boundEntityUuid?.let { root.setAttribute("bound_entity_uuid", it) }
root.setAttribute("created_at", modelTime(createdAt))
root.setAttribute("last_mentioned_at", modelTime(lastMentionedAt))
appendTextElement(document, root, "subject", subject)
appendListElement(
document,
root,
"evidences",
"evidence",
synchronized(_evidences) { _evidences.toList() }
) { evidence ->
setAttribute("association_confidence", evidence.associationConfidence.toString())
setAttribute("source", evidence.source.name)
setAttribute("timestamp", evidence.timestamp.toString())
setAttribute("truncated", evidence.isContentTruncated().toString())
setAttribute("original_length", evidence.content.length.toString())
textContent = evidence.contentForContext()
}
appendListElement(
document,
root,
"projected_features",
"feature",
synchronized(_projectedFeatures) { _projectedFeatures.entries.toList() }
) { entry ->
setAttribute("confidence", entry.value.toString())
textContent = entry.key
}
appendListElement(
document,
root,
"projected_impressions",
"impression",
synchronized(_projectedImpressions) { _projectedImpressions.entries.toList() }
) { entry ->
setAttribute("confidence", entry.value.toString())
textContent = entry.key
}
}
private fun modelTime(time: Instant): String =
time.atZone(ZoneId.systemDefault()).toString()
}
private fun newActiveEntityRuntimeId(): String =
UUID.randomUUID().toString().replace("-", "").take(12)

View File

@@ -0,0 +1,272 @@
package work.slhaf.partner.core.cognition.impression
import com.alibaba.fastjson2.JSONArray
import com.alibaba.fastjson2.JSONObject
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.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class Entity @JvmOverloads constructor(
val uuid: String = UUID.randomUUID().toString(),
val 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()
) : StateSerializable {
private val impressionLock = ReentrantLock()
private val relationLock = ReentrantLock()
private val featureLock = ReentrantLock()
@JvmOverloads
fun updateRelation(
target: String,
relation: String,
strength: Double = 1.0
) = relationLock.withLock {
relations.computeIfAbsent(target) { mutableMapOf() }[relation] = strength
}
@JvmOverloads
fun updateImpression(
impression: String,
newImpression: String? = null,
confidence: Double = 1.0
): IndexableData = impressionLock.withLock {
if (newImpression == null) {
impressions.computeIfAbsent(impression) { IndexableData(confidence) }
.also {
it.confidence = confidence
if (it.confidence >= 0.9) {
featureLock.withLock { features[impression] = it }
}
}
} else {
impressions.remove(impression)
IndexableData(confidence).also {
impressions[newImpression] = it
if (it.confidence >= 0.9) {
featureLock.withLock { features[newImpression] = it }
}
}
}
}
fun updateFeature(feature: String, newFeature: String? = null, confidence: Double = 1.0) = featureLock.withLock {
if (newFeature == null) {
features.computeIfAbsent(feature) { IndexableData(confidence) }
.also { it.confidence = confidence }
} else {
features.remove(feature)
IndexableData(confidence).also {
features[newFeature] = it
}
}
}
fun removeFeature(feature: String) = featureLock.withLock {
features.remove(feature)
}
fun removeImpression(impression: String) = impressionLock.withLock {
impressions.remove(impression)
}
@JvmOverloads
fun removeRelation(
target: String,
relation: String? = null
) = relationLock.withLock {
if (relation == null) {
relations.remove(target)
} else {
relations[target]?.remove(relation)
if (relations[target].isNullOrEmpty()) {
relations.remove(target)
}
}
}
fun showRelations(): Set<RelationView> = relationLock.withLock {
relations.map {
RelationView(
it.key,
it.value.toMap()
)
}.toSet()
}
fun showImpressions(embeddingModel: String): Set<ImpressionView> = impressionLock.withLock {
impressions.map {
ImpressionView(
it.key,
it.value.confidence,
it.value.getVector(embeddingModel)
)
}.toSet()
}
fun showFeatures(): Set<FeatureView> = featureLock.withLock {
features.map {
FeatureView(
it.key,
it.value.confidence
)
}.toSet()
}
fun snapshotImpressions(): Map<String, IndexableData> = impressionLock.withLock {
impressions.toMap()
}
fun snapshotFeatures(): Map<String, IndexableData> = featureLock.withLock {
features.toMap()
}
override fun statePath(): Path = Path.of("core", "impression", "entity-$uuid.json")
override fun load(state: JSONObject) {
state.getJSONObject("relations")?.let { loadedRelations ->
relationLock.withLock {
relations.clear()
loadedRelations.forEach { (target, relationValue) ->
val relationObject = relationValue as? JSONObject ?: return@forEach
val relationMap = mutableMapOf<String, Double>()
relationObject.forEach { (relation, strengthValue) ->
doubleValue(strengthValue)?.let { relationMap[relation] = it }
}
if (relationMap.isNotEmpty()) {
relations[target] = relationMap
}
}
}
}
state.getJSONObject("impressions")?.let { loadedImpressions ->
impressionLock.withLock {
impressions.clear()
impressions.putAll(loadIndexableDataMap(loadedImpressions))
}
}
state.getJSONObject("features")?.let { loadedFeatures ->
featureLock.withLock {
features.clear()
features.putAll(loadIndexableDataMap(loadedFeatures))
}
}
}
override fun convert(): State {
val state = State()
state.append("uuid", StateValue.str(uuid))
state.append("subject", StateValue.str(subject))
val relationState = relationLock.withLock {
relations.mapValues { (_, relationMap) -> relationMap.toMap() }
}
state.append("relations", StateValue.obj(relationState))
val impressionState = impressionLock.withLock {
indexableDataState(impressions)
}
state.append("impressions", StateValue.obj(impressionState))
val featureState = featureLock.withLock {
indexableDataState(features)
}
state.append("features", StateValue.obj(featureState))
return state
}
override fun autoLoadOnRegister(): Boolean = false
private fun loadIndexableDataMap(state: JSONObject): Map<String, IndexableData> {
val loaded = mutableMapOf<String, IndexableData>()
state.forEach { (key, value) ->
val data = when (value) {
is JSONObject -> loadIndexableData(value)
else -> IndexableData(doubleValue(value) ?: return@forEach)
}
loaded[key] = data
}
return loaded
}
private fun loadIndexableData(state: JSONObject): IndexableData {
val data = IndexableData(state.getDouble("confidence") ?: 1.0)
state.getJSONObject("vectors")?.forEach { (embeddingModel, vectorValue) ->
val vectorArray = vectorValue as? JSONArray ?: return@forEach
val vector = FloatArray(vectorArray.size)
for (index in vectorArray.indices) {
vector[index] = floatValue(vectorArray[index]) ?: return@forEach
}
data.updateVector(embeddingModel, vector)
}
return data
}
private fun indexableDataState(source: Map<String, IndexableData>): Map<String, Map<String, Any>> =
source.mapValues { (_, data) ->
mapOf(
"confidence" to data.confidence,
"vectors" to data.snapshotVectors().mapValues { (_, vector) -> vector.toList() }
)
}
private fun doubleValue(value: Any?): Double? = when (value) {
is Number -> value.toDouble()
is String -> value.toDoubleOrNull()
else -> null
}
private fun floatValue(value: Any?): Float? = when (value) {
is Number -> value.toFloat()
is String -> value.toFloatOrNull()
else -> null
}
data class IndexableData(
var confidence: Double
) {
private val vectors: ConcurrentHashMap<String, FloatArray> = ConcurrentHashMap()
fun updateVector(
embeddingModel: String,
vector: FloatArray
) {
vectors[embeddingModel] = vector.copyOf()
}
fun getVector(embeddingModel: String): FloatArray? {
return vectors[embeddingModel]?.copyOf()
}
fun snapshotVectors(): Map<String, FloatArray> {
return vectors.mapValues { (_, vector) -> vector.copyOf() }
}
}
data class RelationView(
val target: String,
val relations: Map<String, Double>
)
data class FeatureView(
val feature: String,
val confidence: Double
)
@Suppress("ArrayInDataClass")
data class ImpressionView(
val impression: String,
val confidence: Double,
val vector: FloatArray?
)
}

View File

@@ -0,0 +1,112 @@
package work.slhaf.partner.core.cognition.impression
/**
* Runtime evidence associated with an active entity.
*
* The confidence describes how strongly this evidence is associated with the
* current active entity, not whether the evidence content itself is true.
*/
data class EntityEvidence @JvmOverloads constructor(
val content: String,
val associationConfidence: Double = 1.0,
val source: Source = Source.USER_INPUT,
val timestamp: Long = System.currentTimeMillis(),
) {
enum class Source {
USER_INPUT,
ASSISTANT_REPLY
}
fun isContentTruncated(maxLength: Int = CONTEXT_CONTENT_MAX_LENGTH): Boolean =
content.length > maxLength
fun contentForContext(maxLength: Int = CONTEXT_CONTENT_MAX_LENGTH): String {
if (content.length <= maxLength) {
return content
}
val available = maxLength - OMITTED_MARKER.length
if (available <= 0) {
return content.take(maxLength)
}
val headBudget = available / 2
val tailBudget = available - headBudget
val headEnd = adjustHeadEnd(content, headBudget)
val tailStart = adjustTailStart(content, content.length - tailBudget)
if (tailStart <= headEnd) {
return content.take(maxLength).trimEnd()
}
return content.substring(0, headEnd).trimEnd() +
OMITTED_MARKER +
content.substring(tailStart).trimStart()
}
private fun adjustHeadEnd(source: String, preferredEnd: Int): Int {
val safePreferredEnd = preferredEnd.coerceIn(0, source.length)
findForwardBoundary(source, safePreferredEnd, STRONG_BOUNDARY_SEARCH_WINDOW, ::isStrongBoundary)?.let {
return it + 1
}
findForwardBoundary(source, safePreferredEnd, SOFT_BOUNDARY_SEARCH_WINDOW, ::isSoftBoundary)?.let {
return it + 1
}
return safePreferredEnd
}
private fun adjustTailStart(source: String, preferredStart: Int): Int {
val safePreferredStart = preferredStart.coerceIn(0, source.length)
findBackwardBoundary(source, safePreferredStart, STRONG_BOUNDARY_SEARCH_WINDOW, ::isStrongBoundary)?.let {
return it
}
findBackwardBoundary(source, safePreferredStart, SOFT_BOUNDARY_SEARCH_WINDOW, ::isSoftBoundary)?.let {
return it
}
return safePreferredStart
}
private fun findForwardBoundary(
source: String,
start: Int,
window: Int,
predicate: (Char) -> Boolean,
): Int? {
val end = (start + window).coerceAtMost(source.length)
for (index in start until end) {
if (predicate(source[index])) {
return index
}
}
return null
}
private fun findBackwardBoundary(
source: String,
start: Int,
window: Int,
predicate: (Char) -> Boolean,
): Int? {
val end = (start - window).coerceAtLeast(0)
for (index in start downTo end + 1) {
if (predicate(source[index - 1])) {
return index
}
}
return null
}
private fun isStrongBoundary(char: Char): Boolean = char == '\n'
private fun isSoftBoundary(char: Char): Boolean = when (char) {
'。', '', '', ';', '', '.' -> true
else -> false
}
companion object {
const val CONTEXT_CONTENT_MAX_LENGTH = 480
private const val OMITTED_MARKER = "\n...[omitted]...\n"
private const val STRONG_BOUNDARY_SEARCH_WINDOW = 120
private const val SOFT_BOUNDARY_SEARCH_WINDOW = 80
}
}

View File

@@ -0,0 +1,90 @@
package work.slhaf.partner.core.cognition.impression;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.jetbrains.annotations.NotNull;
import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityCore;
import work.slhaf.partner.framework.agent.factory.capability.annotation.CapabilityMethod;
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.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@CapabilityCore(value = "cognition")
public class ImpressionCore implements StateSerializable {
/**
* Keyed by entity uuid. Subject can be revised or merged later, so it should not be used as the stable key.
*/
private final ConcurrentHashMap<String, Entity> knownEntitiesByUuid = new ConcurrentHashMap<>();
private final ImpressionVectorIndex vectorIndex = new ImpressionVectorIndex();
@CapabilityMethod
public void updateRelation() {
}
@CapabilityMethod
public void updateImpression() {
}
@CapabilityMethod
public void showImpressions() {
}
@CapabilityMethod
public void projectEntity(Set<ActiveEntity> activeEntities) {
}
@Override
public @NotNull Path statePath() {
return Path.of("core", "impression.json");
}
@Override
public void load(@NotNull JSONObject state) {
JSONArray entityArray = state.getJSONArray("entities");
if (entityArray == null) {
return;
}
knownEntitiesByUuid.clear();
for (int i = 0; i < entityArray.size(); i++) {
JSONObject entityObject = entityArray.getJSONObject(i);
if (entityObject == null) {
continue;
}
String uuid = entityObject.getString("uuid");
String subject = entityObject.getString("subject");
if (uuid == null || uuid.isBlank() || subject == null || subject.isBlank()) {
continue;
}
Entity entity = new Entity(uuid, subject);
entity.load();
vectorIndex.sync(entity);
knownEntitiesByUuid.put(uuid, entity);
}
}
@Override
public @NotNull State convert() {
State state = new State();
List<StateValue.Obj> entities = knownEntitiesByUuid.values().stream()
.map(entity -> StateValue.obj(Map.of(
"uuid", entity.getUuid(),
"subject", entity.getSubject()
)))
.toList();
state.append("entities", StateValue.arr(entities));
return state;
}
}

View File

@@ -0,0 +1,38 @@
package work.slhaf.partner.core.cognition.impression;
import work.slhaf.partner.common.vector.VectorClient;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ImpressionVectorIndex {
private final Executor executor = Executors.newFixedThreadPool(2, r -> {
Thread thread = new Thread(r, "impression-vector-index");
thread.setDaemon(true);
return thread;
});
public void sync(Entity entity) {
if (!VectorClient.status){
return;
}
entity.snapshotFeatures().forEach(this::upsert);
entity.snapshotImpressions().forEach(this::upsert);
}
public void upsert(String text, Entity.IndexableData indexableData){
if (!VectorClient.status){
return;
}
String modelId = VectorClient.VECTOR_MODEL_ID;
if (indexableData.getVector(modelId) != null) {
return;
}
executor.execute(() -> {
float[] vector = VectorClient.INSTANCE.compute(text);
indexableData.updateVector(modelId,vector);
});
}
}

View File

@@ -0,0 +1,7 @@
package work.slhaf.partner.core.cognition.impression.search
data class EntityAssociationMatch(
val target: ImpressionSearchTarget,
val score: Double,
val hits: List<ImpressionSearchHit> = emptyList(),
)

View File

@@ -0,0 +1,10 @@
package work.slhaf.partner.core.cognition.impression.search
data class ImpressionSearchDocument(
val id: String,
val target: ImpressionSearchTarget,
val field: ImpressionSearchField,
val text: String,
val weight: Double = 1.0,
val metadata: Map<String, String> = emptyMap(),
)

View File

@@ -0,0 +1,138 @@
package work.slhaf.partner.core.cognition.impression.search
import work.slhaf.partner.core.cognition.impression.ActiveEntity
import work.slhaf.partner.core.cognition.impression.Entity
object ImpressionSearchDocuments {
fun fromActiveEntity(activeEntity: ActiveEntity): List<ImpressionSearchDocument> {
val target = ImpressionSearchTarget(
ImpressionSearchTarget.Type.ACTIVE_ENTITY,
activeEntity.runtimeId
)
val metadata = activeEntity.boundEntityUuid
?.let { mapOf("boundEntityUuid" to it) }
.orEmpty()
return buildList {
add(
ImpressionSearchDocument(
id = "active:${activeEntity.runtimeId}:subject",
target = target,
field = ImpressionSearchField.SUBJECT,
text = activeEntity.subject,
weight = SUBJECT_WEIGHT,
metadata = metadata,
)
)
activeEntity.evidences.forEachIndexed { index, evidence ->
add(
ImpressionSearchDocument(
id = "active:${activeEntity.runtimeId}:evidence:$index",
target = target,
field = ImpressionSearchField.EVIDENCE,
text = evidence.contentForContext(),
weight = EVIDENCE_WEIGHT * evidence.associationConfidence,
metadata = metadata,
)
)
}
activeEntity.projectedFeatures.entries.forEachIndexed { index, entry ->
add(
ImpressionSearchDocument(
id = "active:${activeEntity.runtimeId}:feature:$index",
target = target,
field = ImpressionSearchField.FEATURE,
text = entry.key,
weight = FEATURE_WEIGHT * entry.value,
metadata = metadata,
)
)
}
activeEntity.projectedImpressions.entries.forEachIndexed { index, entry ->
add(
ImpressionSearchDocument(
id = "active:${activeEntity.runtimeId}:impression:$index",
target = target,
field = ImpressionSearchField.IMPRESSION,
text = entry.key,
weight = IMPRESSION_WEIGHT * entry.value,
metadata = metadata,
)
)
}
}
}
fun fromEntity(entity: Entity): List<ImpressionSearchDocument> {
val target = ImpressionSearchTarget(
ImpressionSearchTarget.Type.ENTITY,
entity.uuid
)
return buildList {
add(
ImpressionSearchDocument(
id = "entity:${entity.uuid}:subject",
target = target,
field = ImpressionSearchField.SUBJECT,
text = entity.subject,
weight = SUBJECT_WEIGHT,
)
)
entity.snapshotFeatures().keys.forEachIndexed { index, feature ->
add(
ImpressionSearchDocument(
id = "entity:${entity.uuid}:feature:$index",
target = target,
field = ImpressionSearchField.FEATURE,
text = feature,
weight = FEATURE_WEIGHT,
)
)
}
entity.snapshotImpressions().keys.forEachIndexed { index, impression ->
add(
ImpressionSearchDocument(
id = "entity:${entity.uuid}:impression:$index",
target = target,
field = ImpressionSearchField.IMPRESSION,
text = impression,
weight = IMPRESSION_WEIGHT,
)
)
}
entity.showRelations().forEachIndexed { index, relation ->
val relationText = buildString {
append(relation.target)
relation.relations.keys.forEach { name ->
append(' ')
append(name)
}
}
add(
ImpressionSearchDocument(
id = "entity:${entity.uuid}:relation:$index",
target = target,
field = ImpressionSearchField.RELATION,
text = relationText,
weight = RELATION_WEIGHT,
)
)
}
}
}
private const val SUBJECT_WEIGHT = 1.0
private const val FEATURE_WEIGHT = 0.85
private const val IMPRESSION_WEIGHT = 0.75
private const val RELATION_WEIGHT = 0.65
private const val EVIDENCE_WEIGHT = 0.8
}

View File

@@ -0,0 +1,9 @@
package work.slhaf.partner.core.cognition.impression.search
enum class ImpressionSearchField {
SUBJECT,
FEATURE,
IMPRESSION,
RELATION,
EVIDENCE
}

View File

@@ -0,0 +1,7 @@
package work.slhaf.partner.core.cognition.impression.search
data class ImpressionSearchHit(
val document: ImpressionSearchDocument,
val score: Double,
val matchedTerms: Set<String> = emptySet(),
)

View File

@@ -0,0 +1,11 @@
package work.slhaf.partner.core.cognition.impression.search
data class ImpressionSearchTarget(
val type: Type,
val id: String,
) {
enum class Type {
ACTIVE_ENTITY,
ENTITY
}
}

View File

@@ -0,0 +1,15 @@
package work.slhaf.partner.core.cognition.impression.search
interface ImpressionTextSearch {
fun rebuild(documents: Collection<ImpressionSearchDocument>)
fun upsert(document: ImpressionSearchDocument)
fun removeByTarget(target: ImpressionSearchTarget)
fun search(
query: String,
limit: Int = 20,
): List<ImpressionSearchHit>
}

View File

@@ -175,8 +175,7 @@ public class MemoryCore implements StateSerializable {
State state = new State(); State state = new State();
state.append("memory_session_id", StateValue.str(memorySessionId)); state.append("memory_session_id", StateValue.str(memorySessionId));
List<StateValue.Str> unitOverview = memoryUnits.keySet().stream() List<String> unitOverview = memoryUnits.keySet().stream()
.map(StateValue::str)
.toList(); .toList();
state.append("memory_unit_uuid_set", StateValue.arr(unitOverview)); state.append("memory_unit_uuid_set", StateValue.arr(unitOverview));
return state; return state;

View File

@@ -95,25 +95,23 @@ public class MemoryUnit implements StateSerializable {
state.append("id", StateValue.str(id)); state.append("id", StateValue.str(id));
state.append("update_timestamp", StateValue.num(timestamp)); state.append("update_timestamp", StateValue.num(timestamp));
List<StateValue.Obj> convertedMessageList = conversationMessages.stream().map(message -> { List<StateValue.Obj> convertedMessageList = conversationMessages.stream()
Map<String, StateValue> convertedMap = Map.of( .map(message -> StateValue.obj(Map.of(
"role", StateValue.str(message.roleValue()), "role", message.roleValue(),
"content", StateValue.str(message.getContent()) "content", message.getContent()
); )))
return StateValue.obj(convertedMap); .toList();
}).toList();
state.append("conversation_messages", StateValue.arr(convertedMessageList)); state.append("conversation_messages", StateValue.arr(convertedMessageList));
List<StateValue.Obj> convertedSliceList = slices.stream().map(slice -> { List<StateValue.Obj> convertedSliceList = slices.stream()
Map<String, StateValue> convertedMap = Map.of( .map(slice -> StateValue.obj(Map.of(
"id", StateValue.str(slice.getId()), "id", slice.getId(),
"start_index", StateValue.num(slice.getStartIndex()), "start_index", slice.getStartIndex(),
"end_index", StateValue.num(slice.getEndIndex()), "end_index", slice.getEndIndex(),
"summary", StateValue.str(slice.getSummary()), "summary", slice.getSummary(),
"created_timestamp", StateValue.num(slice.getTimestamp()) "created_timestamp", slice.getTimestamp()
); )))
return StateValue.obj(convertedMap); .toList();
}).toList();
state.append("memory_slices", StateValue.arr(convertedSliceList)); state.append("memory_slices", StateValue.arr(convertedSliceList));
return state; return state;
} }

View File

@@ -6,9 +6,9 @@ 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 work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.memory.MemoryCapability; import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice; import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit; import work.slhaf.partner.core.memory.pojo.MemoryUnit;

View File

@@ -12,7 +12,11 @@ import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.intervention.InterventionType; import work.slhaf.partner.core.action.entity.intervention.InterventionType;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.cognition.*; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -5,7 +5,7 @@ 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 work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -6,7 +6,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -6,9 +6,9 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.cognition.BlockContent; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.ContextWorkspace; import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.module.StateHintContent; import work.slhaf.partner.module.StateHintContent;
import work.slhaf.partner.module.action.executor.entity.HistoryAction; import work.slhaf.partner.module.action.executor.entity.HistoryAction;

View File

@@ -6,7 +6,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.model.ActivateModel; import work.slhaf.partner.framework.agent.model.ActivateModel;

View File

@@ -8,9 +8,9 @@ import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -6,10 +6,10 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.ResolvedContext; import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ResolvedContext;
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;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -2,7 +2,7 @@ package work.slhaf.partner.module.action.planner.extractor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentRuntimeException; import work.slhaf.partner.framework.agent.exception.AgentRuntimeException;
import work.slhaf.partner.framework.agent.exception.ModuleExecutionException; import work.slhaf.partner.framework.agent.exception.ModuleExecutionException;
import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability;

View File

@@ -3,8 +3,8 @@ package work.slhaf.partner.module
import org.w3c.dom.Document import org.w3c.dom.Document
import org.w3c.dom.Element import org.w3c.dom.Element
import work.slhaf.partner.common.base.Block import work.slhaf.partner.common.base.Block
import work.slhaf.partner.core.cognition.CommunicationBlockContent import work.slhaf.partner.core.cognition.context.CommunicationBlockContent
import work.slhaf.partner.core.cognition.ContextBlock import work.slhaf.partner.core.cognition.context.ContextBlock
import work.slhaf.partner.framework.agent.model.pojo.Message import work.slhaf.partner.framework.agent.model.pojo.Message
abstract class TaskBlock @JvmOverloads constructor( abstract class TaskBlock @JvmOverloads constructor(

View File

@@ -5,7 +5,11 @@ 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 org.w3c.dom.Node;
import work.slhaf.partner.core.cognition.*; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ResolvedContext;
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;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -9,9 +9,9 @@ import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.Schedulable; import work.slhaf.partner.core.action.entity.Schedulable;
import work.slhaf.partner.core.action.entity.StateAction; import work.slhaf.partner.core.action.entity.StateAction;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.memory.MemoryCapability; import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice; import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit; import work.slhaf.partner.core.memory.pojo.MemoryUnit;

View File

@@ -69,8 +69,8 @@ final class MemoryRuntimeStateCodec {
List<StateValue.Obj> dateIndexStates = dateIndex.entries().entrySet().stream() List<StateValue.Obj> dateIndexStates = dateIndex.entries().entrySet().stream()
.sorted(Map.Entry.comparingByKey()) .sorted(Map.Entry.comparingByKey())
.map(entry -> StateValue.obj(Map.of( .map(entry -> StateValue.obj(Map.of(
"date", StateValue.str(entry.getKey().toString()), "date", entry.getKey().toString(),
"refs", StateValue.arr(encodeSliceRefs(entry.getValue())) "refs", encodeSliceRefs(entry.getValue())
))) )))
.toList(); .toList();
state.append("date_index", StateValue.arr(dateIndexStates)); state.append("date_index", StateValue.arr(dateIndexStates));
@@ -82,8 +82,8 @@ final class MemoryRuntimeStateCodec {
TopicMemoryIndex.TopicTreeNode topicNode, TopicMemoryIndex.TopicTreeNode topicNode,
List<StateValue.Obj> topicStates) { List<StateValue.Obj> topicStates) {
topicStates.add(StateValue.obj(Map.of( topicStates.add(StateValue.obj(Map.of(
"topic_path", StateValue.str(path), "topic_path", path,
"bindings", StateValue.arr(encodeTopicBindings(topicNode.bindings())) "bindings", encodeTopicBindings(topicNode.bindings())
))); )));
for (Map.Entry<String, TopicMemoryIndex.TopicTreeNode> childEntry : topicNode.children().entrySet()) { for (Map.Entry<String, TopicMemoryIndex.TopicTreeNode> childEntry : topicNode.children().entrySet()) {
collectTopicStates(path + "->" + childEntry.getKey(), childEntry.getValue(), topicStates); collectTopicStates(path + "->" + childEntry.getKey(), childEntry.getValue(), topicStates);
@@ -93,18 +93,16 @@ final class MemoryRuntimeStateCodec {
private List<StateValue> encodeTopicBindings(List<TopicMemoryIndex.TopicBinding> bindings) { private List<StateValue> encodeTopicBindings(List<TopicMemoryIndex.TopicBinding> bindings) {
return bindings.stream() return bindings.stream()
.map(binding -> (StateValue) StateValue.obj(Map.of( .map(binding -> (StateValue) StateValue.obj(Map.of(
"unit_id", StateValue.str(binding.sliceRef().getUnitId()), "unit_id", binding.sliceRef().getUnitId(),
"slice_id", StateValue.str(binding.sliceRef().getSliceId()), "slice_id", binding.sliceRef().getSliceId(),
"timestamp", StateValue.num(binding.timestamp()), "timestamp", binding.timestamp(),
"activation_profile", StateValue.obj(Map.of( "activation_profile", StateValue.obj(Map.of(
"activation_weight", StateValue.num(binding.activationProfile().getActivationWeight()), "activation_weight", binding.activationProfile().getActivationWeight(),
"diffusion_weight", StateValue.num(binding.activationProfile().getDiffusionWeight()), "diffusion_weight", binding.activationProfile().getDiffusionWeight(),
"context_independence_weight", "context_independence_weight",
StateValue.num(binding.activationProfile().getContextIndependenceWeight()) binding.activationProfile().getContextIndependenceWeight()
)), )),
"related_topic_paths", StateValue.arr(binding.relatedTopicPaths().stream() "related_topic_paths", binding.relatedTopicPaths()
.map(StateValue::str)
.toList())
))) )))
.toList(); .toList();
} }
@@ -156,8 +154,8 @@ final class MemoryRuntimeStateCodec {
private List<StateValue> encodeSliceRefs(List<SliceRef> refs) { private List<StateValue> encodeSliceRefs(List<SliceRef> refs) {
return refs.stream() return refs.stream()
.map(ref -> (StateValue) StateValue.obj(Map.of( .map(ref -> (StateValue) StateValue.obj(Map.of(
"unit_id", StateValue.str(ref.getUnitId()), "unit_id", ref.getUnitId(),
"slice_id", StateValue.str(ref.getSliceId()) "slice_id", ref.getSliceId()
))) )))
.toList(); .toList();
} }

View File

@@ -7,9 +7,9 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule; import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;

View File

@@ -7,7 +7,7 @@ import org.w3c.dom.Element;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore; import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.factory.component.annotation.Init; import work.slhaf.partner.framework.agent.factory.component.annotation.Init;

View File

@@ -5,7 +5,7 @@ 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 work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule; import work.slhaf.partner.framework.agent.factory.component.annotation.InjectModule;

View File

@@ -3,10 +3,10 @@ package work.slhaf.partner.module.perceive;
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 work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.CommunicationBlockContent; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.CommunicationBlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.perceive.PerceiveCapability; import work.slhaf.partner.core.perceive.PerceiveCapability;
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;

View File

@@ -4,9 +4,9 @@ import kotlin.Unit;
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 work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
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;
import work.slhaf.partner.runtime.PartnerRunningFlowContext; import work.slhaf.partner.runtime.PartnerRunningFlowContext;

View File

@@ -3,9 +3,9 @@ package work.slhaf.partner.runtime.exception;
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 work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.core.cognition.context.BlockContent;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.framework.agent.exception.AgentException; import work.slhaf.partner.framework.agent.exception.AgentException;
import work.slhaf.partner.framework.agent.exception.ExceptionReport; import work.slhaf.partner.framework.agent.exception.ExceptionReport;
import work.slhaf.partner.framework.agent.exception.ExceptionReporter; import work.slhaf.partner.framework.agent.exception.ExceptionReporter;

View File

@@ -3,6 +3,8 @@ package work.slhaf.partner.core.cognition;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import work.slhaf.partner.core.cognition.context.ContextBlock;
import work.slhaf.partner.core.cognition.context.ContextCore;
import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.pojo.Message;
import java.nio.file.Path; import java.nio.file.Path;
@@ -20,15 +22,15 @@ class CognitionCoreTest {
@Test @Test
void shouldRenderRecentChatMessagesWithWrapperAndNotes() { void shouldRenderRecentChatMessagesWithWrapperAndNotes() {
CognitionCore cognitionCore = new CognitionCore(); ContextCore contextCore = new ContextCore();
cognitionCore.getChatMessages().addAll(List.of( contextCore.getChatMessages().addAll(List.of(
new Message(Message.Character.USER, "[[USER]: user-1]: hello"), new Message(Message.Character.USER, "[[USER]: user-1]: hello"),
new Message(Message.Character.ASSISTANT, "[NOT_REPLIED]: wait"), new Message(Message.Character.ASSISTANT, "[NOT_REPLIED]: wait"),
new Message(Message.Character.ASSISTANT, "latest message") new Message(Message.Character.ASSISTANT, "latest message")
)); ));
cognitionCore.refreshRecentChatMessagesContext(); contextCore.refreshRecentChatMessagesContext();
String content = cognitionCore.contextWorkspace() String content = contextCore.contextWorkspace()
.resolve(List.of(ContextBlock.FocusedDomain.COMMUNICATION)) .resolve(List.of(ContextBlock.FocusedDomain.COMMUNICATION))
.encodeToMessage() .encodeToMessage()
.getContent(); .getContent();

View File

@@ -2,6 +2,9 @@ package work.slhaf.partner.core.cognition
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import work.slhaf.partner.core.cognition.context.BlockContent
import work.slhaf.partner.core.cognition.context.ContextBlock
import work.slhaf.partner.core.cognition.context.ContextWorkspace
class ContextWorkspaceTest { class ContextWorkspaceTest {

View File

@@ -10,7 +10,7 @@ import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextWorkspace; import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.support.Result; import work.slhaf.partner.framework.agent.support.Result;
import work.slhaf.partner.module.action.executor.entity.ExtractorResult; import work.slhaf.partner.module.action.executor.entity.ExtractorResult;
import work.slhaf.partner.module.action.executor.entity.HistoryAction; import work.slhaf.partner.module.action.executor.entity.HistoryAction;

View File

@@ -3,7 +3,7 @@ package work.slhaf.partner.module.communication;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextWorkspace; import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.framework.agent.model.pojo.Message; import work.slhaf.partner.framework.agent.model.pojo.Message;
import work.slhaf.partner.runtime.PartnerRunningFlowContext; import work.slhaf.partner.runtime.PartnerRunningFlowContext;

View File

@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.context.ContextWorkspace;
import work.slhaf.partner.core.memory.MemoryCapability; import work.slhaf.partner.core.memory.MemoryCapability;
import work.slhaf.partner.core.memory.pojo.MemorySlice; import work.slhaf.partner.core.memory.pojo.MemorySlice;
import work.slhaf.partner.core.memory.pojo.MemoryUnit; import work.slhaf.partner.core.memory.pojo.MemoryUnit;
@@ -65,8 +66,8 @@ class MemoryRuntimeTest {
} }
@Override @Override
public work.slhaf.partner.core.cognition.ContextWorkspace contextWorkspace() { public ContextWorkspace contextWorkspace() {
return new work.slhaf.partner.core.cognition.ContextWorkspace(); return new ContextWorkspace();
} }
@Override @Override

View File

@@ -148,10 +148,130 @@ sealed interface StateValue {
fun str(value: String) = Str(value) fun str(value: String) = Str(value)
@JvmStatic @JvmStatic
fun arr(value: List<StateValue>) = Arr(value) fun arr(value: List<*>): Arr {
val visiting = java.util.IdentityHashMap<Any, Unit>()
return Arr(convertList(value, visiting))
}
@JvmStatic @JvmStatic
fun obj(value: Map<String, StateValue>) = Obj(value) fun obj(value: Map<String, *>): Obj {
val visiting = java.util.IdentityHashMap<Any, Unit>()
return Obj(convertMap(value, visiting))
}
private fun convertValue(
value: Any?,
visiting: java.util.IdentityHashMap<Any, Unit>
): StateValue {
return when (value) {
null -> error("StateValue does not support null")
is StateValue -> normalizeStateValue(value, visiting)
is String -> Str(value)
is Number -> Num(value)
is Boolean -> Bool(value)
is List<*> -> Arr(convertList(value, visiting))
is Map<*, *> -> Obj(convertGenericMap(value, visiting))
else -> error("Unsupported state value type: ${value::class.qualifiedName}")
}
}
private fun normalizeStateValue(
value: StateValue,
visiting: java.util.IdentityHashMap<Any, Unit>
): StateValue {
return when (value) {
is Num -> value
is Bool -> value
is Str -> value
is Arr -> Arr(convertStateValueList(value.value, visiting))
is Obj -> Obj(convertStateValueMap(value.value, visiting))
}
}
private fun convertList(
value: List<*>,
visiting: java.util.IdentityHashMap<Any, Unit>
): List<StateValue> {
enterContainer(value, visiting)
try {
return value.map { convertValue(it, visiting) }
} finally {
leaveContainer(value, visiting)
}
}
private fun convertMap(
value: Map<String, *>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
key to convertValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun convertGenericMap(
value: Map<*, *>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
check(key is String) {
"StateValue object key must be String, but got: ${key?.let { it::class.qualifiedName }}"
}
key to convertValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun convertStateValueList(
value: List<StateValue>,
visiting: java.util.IdentityHashMap<Any, Unit>
): List<StateValue> {
enterContainer(value, visiting)
try {
return value.map { normalizeStateValue(it, visiting) }
} finally {
leaveContainer(value, visiting)
}
}
private fun convertStateValueMap(
value: Map<String, StateValue>,
visiting: java.util.IdentityHashMap<Any, Unit>
): Map<String, StateValue> {
enterContainer(value, visiting)
try {
return value.entries.associateTo(LinkedHashMap()) { (key, mapValue) ->
key to normalizeStateValue(mapValue, visiting)
}
} finally {
leaveContainer(value, visiting)
}
}
private fun enterContainer(
container: Any,
visiting: java.util.IdentityHashMap<Any, Unit>
) {
check(visiting.put(container, Unit) == null) {
"Circular reference detected while constructing StateValue"
}
}
private fun leaveContainer(
container: Any,
visiting: java.util.IdentityHashMap<Any, Unit>
) {
visiting.remove(container)
}
} }
} }

View File

@@ -0,0 +1,60 @@
package work.slhaf.partner.framework.agent.state
fun main() {
testNormalStateJson()
println()
testCircularReference()
}
private fun testNormalStateJson() {
val nestedMap = linkedMapOf(
"name" to "partner",
"enabled" to true,
"count" to 3,
"tags" to listOf("agent", "runtime", "state-center"),
"meta" to linkedMapOf(
"version" to "0.1.0",
"experimental" to false
)
)
val state = State()
state.append("root", StateValue.obj(nestedMap))
state.append(
"arr",
StateValue.arr(
listOf(
"hello",
123,
true,
linkedMapOf(
"nested" to "value"
)
)
)
)
println("=== normal state ===")
println(state.toString())
}
private fun testCircularReference() {
val cyclicMap = linkedMapOf<String, Any>()
cyclicMap["name"] = "cyclic"
cyclicMap["self"] = cyclicMap
println("=== circular reference ===")
try {
val state = State()
state.append("cyclic", StateValue.obj(cyclicMap))
// 如果前面没有抛错,这里再触发最终 JSON 输出
println(state.toString())
error("Expected circular reference detection, but no exception was thrown.")
} catch (e: IllegalStateException) {
println("circular reference detected as expected:")
println(e.message)
}
}

View File

@@ -10,7 +10,7 @@
</parent> </parent>
<artifactId>partnerctl</artifactId> <artifactId>partnerctl</artifactId>
<version>1.0.0</version> <version>1.0.1</version>
<properties> <properties>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
@@ -123,6 +123,7 @@
<buildArg>-H:+ReportExceptionStackTraces</buildArg> <buildArg>-H:+ReportExceptionStackTraces</buildArg>
<buildArg>--initialize-at-build-time=kotlin.DeprecationLevel</buildArg> <buildArg>--initialize-at-build-time=kotlin.DeprecationLevel</buildArg>
<buildArg>-H:IncludeResourceBundles=i18n.messages</buildArg> <buildArg>-H:IncludeResourceBundles=i18n.messages</buildArg>
<buildArg>-H:IncludeLocales=zh-CN</buildArg>
</buildArgs> </buildArgs>
</configuration> </configuration>
</plugin> </plugin>

View File

@@ -2,13 +2,12 @@ package work.slhaf.partner.ctl.commands
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import picocli.CommandLine import picocli.CommandLine
import work.slhaf.partner.ctl.commands.InitCommand.InstallChoice.BUILD_FROM_SOURCE
import work.slhaf.partner.ctl.commands.InitCommand.InstallChoice.DOWNLOAD_JAR
import work.slhaf.partner.ctl.commands.data.GatewayConfig import work.slhaf.partner.ctl.commands.data.GatewayConfig
import work.slhaf.partner.ctl.commands.data.OpenAiCompatible import work.slhaf.partner.ctl.commands.data.OpenAiCompatible
import work.slhaf.partner.ctl.commands.data.ProviderConfig import work.slhaf.partner.ctl.commands.data.ProviderConfig
import work.slhaf.partner.ctl.commands.init.buildFromSource import work.slhaf.partner.ctl.commands.init.*
import work.slhaf.partner.ctl.commands.init.configureExternalGateway
import work.slhaf.partner.ctl.commands.init.configureOpenAiCompatible
import work.slhaf.partner.ctl.commands.init.configureWebSocketGateway
import work.slhaf.partner.ctl.i18n.I18n.text import work.slhaf.partner.ctl.i18n.I18n.text
import work.slhaf.partner.ctl.support.CommandInterrupted import work.slhaf.partner.ctl.support.CommandInterrupted
import work.slhaf.partner.ctl.support.inheritCommand import work.slhaf.partner.ctl.support.inheritCommand
@@ -176,11 +175,15 @@ class InitCommand : Runnable {
val installChoice = prompt.select( val installChoice = prompt.select(
label = text("init.install.method.label"), label = text("init.install.method.label"),
choices = listOf(Choice(text("init.install.method.buildFromSource"), InstallChoice.BUILD_FROM_SOURCE)) choices = listOf(
Choice(text("init.install.method.buildFromSource"), BUILD_FROM_SOURCE),
Choice(text("init.install.method.downloadFromRelease"), DOWNLOAD_JAR)
)
) )
when (installChoice) { when (installChoice) {
InstallChoice.BUILD_FROM_SOURCE -> buildFromSource(home, prompt) BUILD_FROM_SOURCE -> buildFromSource(home, prompt)
DOWNLOAD_JAR -> downloadFromRelease(home, prompt)
} }
} }
@@ -348,7 +351,8 @@ class InitCommand : Runnable {
} }
private enum class InstallChoice { private enum class InstallChoice {
BUILD_FROM_SOURCE BUILD_FROM_SOURCE,
DOWNLOAD_JAR
} }
private enum class ModelProviderChoice(val display: String) { private enum class ModelProviderChoice(val display: String) {

View File

@@ -1,11 +1,15 @@
package work.slhaf.partner.ctl.commands.init package work.slhaf.partner.ctl.commands.init
import work.slhaf.partner.ctl.i18n.I18n.text
import work.slhaf.partner.ctl.support.SourceBuildInstallSpec import work.slhaf.partner.ctl.support.SourceBuildInstallSpec
import work.slhaf.partner.ctl.support.buildAndInstallFromSource import work.slhaf.partner.ctl.support.buildAndInstallFromSource
import work.slhaf.partner.ctl.support.downloadTo
import work.slhaf.partner.ctl.support.registryIndex
import work.slhaf.partner.ctl.ui.Prompt import work.slhaf.partner.ctl.ui.Prompt
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.isDirectory import kotlin.io.path.isDirectory
import kotlin.io.path.name import kotlin.io.path.name
@@ -40,3 +44,41 @@ private fun findLargestJar(directory: Path): Path? {
.orElse(null) .orElse(null)
} }
} }
fun downloadFromRelease(home: Path, prompt: Prompt) {
prompt.info(text("init.install.method.downloadFromRelease.startDownloading"))
val path = home.resolve("resources/partner-core.jar").toAbsolutePath().normalize()
downloadTo(registryIndex.partner.latestRelease.url, path) { downloaded, total ->
if (total != null && total > 0) {
val percent = downloaded * 100 / total
updateLine(
text(
"init.install.method.downloadFromRelease.progress.percent",
percent
)
)
} else {
updateLine(
text(
"init.install.method.downloadFromRelease.progress.size",
downloaded / 1024
)
)
}
}
finishLine(text("init.install.method.downloadFromRelease.done"))
if (!path.exists()) {
throw IllegalStateException("Unable to find downloaded partner release at $path")
}
prompt.success(text("init.install.method.downloadFromRelease.success"))
}
fun updateLine(text: String) {
print("\r\u001B[2K$text")
System.out.flush()
}
fun finishLine(text: String) {
updateLine(text)
println()
}

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.json.Json
private const val registryUrl = "https://raw.githubusercontent.com/slhaf/Partner/refs/heads/master/registry" private const val registryUrl = "https://raw.githubusercontent.com/slhaf/Partner/refs/heads/master/registry"
private const val indexUrl = "$registryUrl/index.json" private const val indexUrl = "$registryUrl/index.json"
private val registryIndex = run { val registryIndex = run {
Json.decodeFromString<RegistryIndex>(fetchText(indexUrl)) Json.decodeFromString<RegistryIndex>(fetchText(indexUrl))
} }

View File

@@ -5,11 +5,15 @@ import java.net.InetSocketAddress
import java.net.ProxySelector import java.net.ProxySelector
import java.net.URI import java.net.URI
import java.net.http.* import java.net.http.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.time.Duration import java.time.Duration
import kotlin.io.path.isDirectory
private val httpClient: HttpClient = HttpClient.newBuilder() private val httpClient: HttpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(20)) .connectTimeout(Duration.ofSeconds(20))
.followRedirects(HttpClient.Redirect.NEVER) .followRedirects(HttpClient.Redirect.NORMAL)
.apply { .apply {
proxySelectorFromEnv()?.let(::proxy) proxySelectorFromEnv()?.let(::proxy)
} }
@@ -72,3 +76,60 @@ fun fetchText(url: String): String {
throw IOException("Failed to fetch $url after retries", lastError) throw IOException("Failed to fetch $url after retries", lastError)
} }
fun downloadTo(
url: String,
targetPath: Path,
onProgress: (downloaded: Long, total: Long?) -> Unit = { _, _ -> }
) {
if (targetPath.isDirectory()) {
throw IllegalArgumentException("Target path must be a file")
}
val targetPath = targetPath.toAbsolutePath().normalize()
val targetFile = targetPath.toFile()
val temp = Files.createTempFile(
"${targetFile.name}-${System.currentTimeMillis()}", ".${targetFile.extension}.download"
)
try {
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build()
val response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofInputStream()
)
if (response.statusCode() !in 200..299) {
throw IllegalStateException("Failed to download from $url: HTTP ${response.statusCode()}")
}
val totalBytes = response.headers()
.firstValue("Content-Length")
.orElse(null)
?.toLongOrNull()
response.body().use { input ->
Files.newOutputStream(temp).use { output ->
val buffer = ByteArray(8192)
var downloaded = 0L
while (true) {
val read = input.read(buffer)
if (read < 0) break
output.write(buffer, 0, read)
downloaded += read
onProgress(downloaded, totalBytes)
}
}
}
Files.move(temp, targetPath, StandardCopyOption.REPLACE_EXISTING)
} catch (e: Exception) {
Files.deleteIfExists(temp)
throw e
}
}

View File

@@ -31,6 +31,12 @@ init.home.overwrite.refuseBroadDirectory=Refuse to overwrite suspiciously broad
init.install.section=Install Partner init.install.section=Install Partner
init.install.method.label=Choose an installation method init.install.method.label=Choose an installation method
init.install.method.buildFromSource=Build Partner from source init.install.method.buildFromSource=Build Partner from source
init.install.method.downloadFromRelease=Download Partner release
init.install.method.downloadFromRelease.startDownloading=Downloading Partner release...
init.install.method.downloadFromRelease.success=Partner release downloaded successfully.
init.install.method.downloadFromRelease.progress.percent=Downloading Partner release... {0}%
init.install.method.downloadFromRelease.progress.size=Downloading Partner release... {0} KB
init.install.method.downloadFromRelease.done=Downloading Partner release... Done
init.gateway.section=Configure Gateway init.gateway.section=Configure Gateway
init.gateway.select.label=Select gateway init.gateway.select.label=Select gateway
init.gateway.websocket.choice=WebSocket Gateway init.gateway.websocket.choice=WebSocket Gateway

View File

@@ -31,6 +31,12 @@ init.home.overwrite.refuseBroadDirectory=拒绝覆盖范围过大的目录:{0}
init.install.section=安装 Partner init.install.section=安装 Partner
init.install.method.label=选择安装方式 init.install.method.label=选择安装方式
init.install.method.buildFromSource=从源码构建 Partner init.install.method.buildFromSource=从源码构建 Partner
init.install.method.downloadFromRelease=下载 Partner 发布包
init.install.method.downloadFromRelease.startDownloading=正在下载 Partner 发布包...
init.install.method.downloadFromRelease.success=Partner 发布包下载完成。
init.install.method.downloadFromRelease.progress.percent=正在下载 Partner 发布包... {0}%
init.install.method.downloadFromRelease.progress.size=正在下载 Partner 发布包... {0} KB
init.install.method.downloadFromRelease.done=正在下载 Partner 发布包... 完成
init.gateway.section=配置网关 init.gateway.section=配置网关
init.gateway.select.label=选择网关 init.gateway.select.label=选择网关
init.gateway.websocket.choice=WebSocket Gateway init.gateway.websocket.choice=WebSocket Gateway

View File

@@ -25,12 +25,54 @@ Partner 分为 `Partner-Framework` 与 `Partner-Core` 两层。前者提供配
## 项目启动 ## 项目启动
**环境要求** ### 环境要求
**基础运行要求**
- JDK 21 - JDK 21
- Maven 3.x
### 手动准备环境并启动 **仅在从源码构建 Partner Runtime 或外部模块时需要**
- Maven 3.x
- Git
### 推荐方式PartnerCtl
`PartnerCtl` 用于完成 Partner 的首次初始化、运行时安装与启动管理。相比手动准备运行目录和配置文件,使用它可以更快完成最小可运行环境的搭建。
#### 初始化
```bash
partnerctl init
```
初始化流程会引导完成:
- 选择 `PARTNER_HOME`
- 安装 Partner Runtime
- 从源码构建
- 下载发布版 jar
- 配置 Gateway
- 配置模型 Provider
- 可选立即启动 Partner
#### 启动
如果初始化完成后未选择立即启动,可执行:
```bash
partnerctl run
```
如需后台运行:
```bash
partnerctl run -d
```
PartnerCtl 默认读取 `PARTNER_HOME` 指定的运行目录;若未设置,则使用 `~/.partner`
### 手动方式:从源码构建并启动
#### 克隆项目并构建 #### 克隆项目并构建
@@ -162,4 +204,3 @@ Partner/
## License ## License
暂未指定。 暂未指定。

View File

@@ -2,11 +2,11 @@
"partner": { "partner": {
"latestBuildable": { "latestBuildable": {
"url": "https://github.com/slhaf/Partner.git", "url": "https://github.com/slhaf/Partner.git",
"ref": "buildable/0.5.0" "ref": "buildable/0.9.0-preview"
}, },
"latestRelease": { "latestRelease": {
"url": "https://github.com/slhaf/Partner/releases/download/release-core%2F0.5.0/partner-core-0.5.0.jar", "url": "https://github.com/slhaf/Partner/releases/download/release-core%2F0.9.0-preview/partner-core-0.9.0-preview.jar",
"version": "0.5.0" "version": "0.9.0-preview"
} }
}, },
"externalModules": [ "externalModules": [