4 Commits
5 changed files with 94 additions and 27 deletions
@@ -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) {
@@ -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
@@ -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;
} }
} }
} }
@@ -1,5 +1,6 @@
package work.slhaf.partner.core.cognition.impression package work.slhaf.partner.core.cognition.impression
import com.alibaba.fastjson2.JSONArray
import com.alibaba.fastjson2.JSONObject import com.alibaba.fastjson2.JSONObject
import work.slhaf.partner.framework.agent.state.State import work.slhaf.partner.framework.agent.state.State
import work.slhaf.partner.framework.agent.state.StateSerializable import work.slhaf.partner.framework.agent.state.StateSerializable
@@ -110,7 +111,6 @@ class Entity @JvmOverloads constructor(
}.toSet() }.toSet()
} }
fun showFeatures(): Set<FeatureView> = featureLock.withLock { fun showFeatures(): Set<FeatureView> = featureLock.withLock {
features.map { features.map {
FeatureView( FeatureView(
@@ -120,6 +120,14 @@ class Entity @JvmOverloads constructor(
}.toSet() }.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 statePath(): Path = Path.of("core", "impression", "entity-$uuid.json")
override fun load(state: JSONObject) { override fun load(state: JSONObject) {
@@ -130,7 +138,7 @@ class Entity @JvmOverloads constructor(
val relationObject = relationValue as? JSONObject ?: return@forEach val relationObject = relationValue as? JSONObject ?: return@forEach
val relationMap = mutableMapOf<String, Double>() val relationMap = mutableMapOf<String, Double>()
relationObject.forEach { (relation, strengthValue) -> relationObject.forEach { (relation, strengthValue) ->
numberValue(strengthValue)?.let { relationMap[relation] = it } doubleValue(strengthValue)?.let { relationMap[relation] = it }
} }
if (relationMap.isNotEmpty()) { if (relationMap.isNotEmpty()) {
relations[target] = relationMap relations[target] = relationMap
@@ -182,39 +190,67 @@ class Entity @JvmOverloads constructor(
private fun loadIndexableDataMap(state: JSONObject): Map<String, IndexableData> { private fun loadIndexableDataMap(state: JSONObject): Map<String, IndexableData> {
val loaded = mutableMapOf<String, IndexableData>() val loaded = mutableMapOf<String, IndexableData>()
state.forEach { (key, value) -> state.forEach { (key, value) ->
val confidence = when (value) { val data = when (value) {
is JSONObject -> value.getDouble("confidence")?.toDouble() ?: 1.0 is JSONObject -> loadIndexableData(value)
else -> numberValue(value) ?: return@forEach else -> IndexableData(doubleValue(value) ?: return@forEach)
} }
loaded[key] = IndexableData(confidence) loaded[key] = data
} }
return loaded return loaded
} }
private fun indexableDataState(source: Map<String, IndexableData>): Map<String, Map<String, Double>> = private fun loadIndexableData(state: JSONObject): IndexableData {
source.mapValues { (_, data) -> mapOf("confidence" to data.confidence) } 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 numberValue(value: Any?): Double? = when (value) { 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 Number -> value.toDouble()
is String -> value.toDoubleOrNull() is String -> value.toDoubleOrNull()
else -> null else -> null
} }
private fun floatValue(value: Any?): Float? = when (value) {
is Number -> value.toFloat()
is String -> value.toFloatOrNull()
else -> null
}
data class IndexableData( data class IndexableData(
var confidence: Double var confidence: Double
) { ) {
private val vectors: ConcurrentHashMap<String, DoubleArray> = ConcurrentHashMap() private val vectors: ConcurrentHashMap<String, FloatArray> = ConcurrentHashMap()
fun updateVector( fun updateVector(
embeddingModel: String, embeddingModel: String,
vector: DoubleArray vector: FloatArray
) { ) {
vectors[embeddingModel] = vector vectors[embeddingModel] = vector.copyOf()
} }
fun getVector(embeddingModel: String): DoubleArray? { fun getVector(embeddingModel: String): FloatArray? {
return vectors[embeddingModel]?.copyOf() return vectors[embeddingModel]?.copyOf()
} }
fun snapshotVectors(): Map<String, FloatArray> {
return vectors.mapValues { (_, vector) -> vector.copyOf() }
}
} }
data class RelationView( data class RelationView(
@@ -231,6 +267,6 @@ class Entity @JvmOverloads constructor(
data class ImpressionView( data class ImpressionView(
val impression: String, val impression: String,
val confidence: Double, val confidence: Double,
val vector: DoubleArray? val vector: FloatArray?
) )
} }
@@ -1,13 +1,38 @@
package work.slhaf.partner.core.cognition.impression; 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 { public class ImpressionVectorIndex {
public void sync(Entity entity){ private final Executor executor = Executors.newFixedThreadPool(2, r -> {
// TODO sync entity impressions/features with vector index. 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 content, Entity.IndexableData indexableData){ public void upsert(String text, Entity.IndexableData indexableData){
// TODO update vector for content when embedding/vector client boundary is finalized. 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);
});
} }
} }