fix: MCP tools migrated from StateVector V1 to StateTensor V2
- identity_tools.py: rewritten to read StateTensor (8x1024 named vectors) instead of StateVector (single 1024-dim). Uses CATEGORY_TO_DIMENSION mapping. - mcp_server.py: get_state_vector renamed to get_state_tensor - __init__.py: updated exports Now returns S(30) with architecture v2_tensor instead of S(2) from V1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,17 @@ from memory.mcp.unified_tools import (
|
||||
update_thought_evolution_stage_handler,
|
||||
)
|
||||
|
||||
from memory.mcp.identity_tools import (
|
||||
GetStateProfileInput,
|
||||
GetDavidProfileInput,
|
||||
CompareProfilesInput,
|
||||
GetStateTensorInput,
|
||||
get_state_profile_handler,
|
||||
get_david_profile_handler,
|
||||
compare_profiles_handler,
|
||||
get_state_tensor_handler,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Thought tools
|
||||
"AddThoughtInput",
|
||||
@@ -74,4 +85,14 @@ __all__ = [
|
||||
"trace_concept_evolution_handler",
|
||||
"check_consistency_handler",
|
||||
"update_thought_evolution_stage_handler",
|
||||
|
||||
# Identity tools (state tensors and profiles)
|
||||
"GetStateProfileInput",
|
||||
"GetDavidProfileInput",
|
||||
"CompareProfilesInput",
|
||||
"GetStateTensorInput",
|
||||
"get_state_profile_handler",
|
||||
"get_david_profile_handler",
|
||||
"compare_profiles_handler",
|
||||
"get_state_tensor_handler",
|
||||
]
|
||||
|
||||
659
memory/mcp/identity_tools.py
Normal file
659
memory/mcp/identity_tools.py
Normal file
@@ -0,0 +1,659 @@
|
||||
"""
|
||||
Identity MCP Tools - Handlers for reading Ikario and David state tensors.
|
||||
|
||||
Provides tools for:
|
||||
- get_state_profile: Read Ikario's state tensor projected onto 109 interpretable directions
|
||||
- get_david_profile: Read David's profile from messages + declared profile
|
||||
- compare_profiles: Compare Ikario and David profiles
|
||||
- get_state_tensor: Get raw 8x1024 state tensor (advanced usage)
|
||||
|
||||
Architecture v2: StateTensor (8 named vectors x 1024 dims) replaces StateVector (single 1024-dim).
|
||||
Each category maps to a dimension via CATEGORY_TO_DIMENSION for proper projection.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import numpy as np
|
||||
import weaviate
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from memory.core import get_embedder
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Category -> Dimension mapping (must match state_to_language.py)
|
||||
# =============================================================================
|
||||
|
||||
CATEGORY_TO_DIMENSION = {
|
||||
'epistemic': 'firstness',
|
||||
'affective': 'dispositions',
|
||||
'cognitive': 'thirdness',
|
||||
'relational': 'engagements',
|
||||
'ethical': 'valeurs',
|
||||
'temporal': 'orientations',
|
||||
'thematic': 'pertinences',
|
||||
'metacognitive': 'secondness',
|
||||
'vital': 'dispositions',
|
||||
'ecosystemic': 'engagements',
|
||||
'philosophical': 'thirdness',
|
||||
}
|
||||
|
||||
DIMENSION_NAMES = [
|
||||
'firstness', 'secondness', 'thirdness',
|
||||
'dispositions', 'orientations', 'engagements',
|
||||
'pertinences', 'valeurs',
|
||||
]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Input Models
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class GetStateProfileInput(BaseModel):
|
||||
"""Input for get_state_profile tool."""
|
||||
|
||||
state_id: Optional[int] = Field(
|
||||
default=None,
|
||||
description="State ID to retrieve (default: latest state)"
|
||||
)
|
||||
|
||||
|
||||
class GetDavidProfileInput(BaseModel):
|
||||
"""Input for get_david_profile tool."""
|
||||
|
||||
include_declared: bool = Field(
|
||||
default=True,
|
||||
description="Include declared profile values from david_profile_declared.json"
|
||||
)
|
||||
max_messages: int = Field(
|
||||
default=500,
|
||||
ge=10,
|
||||
le=1000,
|
||||
description="Maximum number of David's messages to analyze"
|
||||
)
|
||||
|
||||
|
||||
class CompareProfilesInput(BaseModel):
|
||||
"""Input for compare_profiles tool."""
|
||||
|
||||
categories: Optional[List[str]] = Field(
|
||||
default=None,
|
||||
description="Filter to specific categories (e.g., ['epistemic', 'affective'])"
|
||||
)
|
||||
state_id: Optional[int] = Field(
|
||||
default=None,
|
||||
description="Ikario state ID to compare (default: latest)"
|
||||
)
|
||||
|
||||
|
||||
class GetStateTensorInput(BaseModel):
|
||||
"""Input for get_state_tensor tool (advanced usage)."""
|
||||
|
||||
state_id: Optional[int] = Field(
|
||||
default=None,
|
||||
description="State ID (default: latest)"
|
||||
)
|
||||
entity: str = Field(
|
||||
default="ikario",
|
||||
description="Entity to retrieve: 'ikario' or 'david'"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helper Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def get_latest_state_tensor(client: weaviate.WeaviateClient) -> tuple[dict, dict]:
|
||||
"""
|
||||
Get the latest StateTensor from Weaviate (v2 architecture).
|
||||
|
||||
Returns:
|
||||
Tuple of (properties dict, named_vectors dict[dim_name -> list[float]])
|
||||
"""
|
||||
collection = client.collections.get("StateTensor")
|
||||
|
||||
result = collection.query.fetch_objects(
|
||||
limit=100,
|
||||
include_vector=True
|
||||
)
|
||||
|
||||
if not result.objects:
|
||||
raise ValueError("No StateTensor found in Weaviate")
|
||||
|
||||
# Find the one with highest state_id
|
||||
latest = max(result.objects, key=lambda o: o.properties.get("state_id", -1))
|
||||
|
||||
# Extract named vectors
|
||||
named_vectors = {}
|
||||
if isinstance(latest.vector, dict):
|
||||
for dim_name in DIMENSION_NAMES:
|
||||
if dim_name in latest.vector:
|
||||
named_vectors[dim_name] = latest.vector[dim_name]
|
||||
|
||||
if not named_vectors:
|
||||
raise ValueError(f"StateTensor S({latest.properties.get('state_id')}) has no named vectors")
|
||||
|
||||
return latest.properties, named_vectors
|
||||
|
||||
|
||||
def get_state_tensor_by_id(
|
||||
client: weaviate.WeaviateClient,
|
||||
state_id: int
|
||||
) -> tuple[dict, dict]:
|
||||
"""
|
||||
Get a specific StateTensor by state_id.
|
||||
|
||||
Returns:
|
||||
Tuple of (properties dict, named_vectors dict[dim_name -> list[float]])
|
||||
"""
|
||||
collection = client.collections.get("StateTensor")
|
||||
|
||||
from weaviate.classes.query import Filter
|
||||
|
||||
result = collection.query.fetch_objects(
|
||||
filters=Filter.by_property("state_id").equal(state_id),
|
||||
limit=1,
|
||||
include_vector=True
|
||||
)
|
||||
|
||||
if not result.objects:
|
||||
raise ValueError(f"StateTensor with state_id={state_id} not found")
|
||||
|
||||
obj = result.objects[0]
|
||||
|
||||
named_vectors = {}
|
||||
if isinstance(obj.vector, dict):
|
||||
for dim_name in DIMENSION_NAMES:
|
||||
if dim_name in obj.vector:
|
||||
named_vectors[dim_name] = obj.vector[dim_name]
|
||||
|
||||
if not named_vectors:
|
||||
raise ValueError(f"StateTensor S({state_id}) has no named vectors")
|
||||
|
||||
return obj.properties, named_vectors
|
||||
|
||||
|
||||
def get_all_projection_directions(client: weaviate.WeaviateClient) -> list[dict]:
|
||||
"""
|
||||
Get all ProjectionDirection objects from Weaviate.
|
||||
|
||||
Returns:
|
||||
List of direction objects with properties and vectors
|
||||
"""
|
||||
collection = client.collections.get("ProjectionDirection")
|
||||
|
||||
result = collection.query.fetch_objects(
|
||||
limit=200,
|
||||
include_vector=True
|
||||
)
|
||||
|
||||
directions = []
|
||||
for obj in result.objects:
|
||||
directions.append({
|
||||
"name": obj.properties.get("name"),
|
||||
"category": obj.properties.get("category"),
|
||||
"pole_positive": obj.properties.get("pole_positive"),
|
||||
"pole_negative": obj.properties.get("pole_negative"),
|
||||
"description": obj.properties.get("description"),
|
||||
"vector": obj.vector["default"]
|
||||
})
|
||||
|
||||
return directions
|
||||
|
||||
|
||||
def compute_projection(state_vector: list, direction_vector: list) -> float:
|
||||
"""
|
||||
Compute projection (dot product) of state onto direction.
|
||||
|
||||
Both vectors should be normalized (cosine similarity).
|
||||
"""
|
||||
state = np.array(state_vector)
|
||||
direction = np.array(direction_vector)
|
||||
|
||||
return float(np.dot(state, direction))
|
||||
|
||||
|
||||
def build_tensor_profile(
|
||||
named_vectors: dict,
|
||||
directions: list[dict]
|
||||
) -> dict[str, dict[str, float]]:
|
||||
"""
|
||||
Build a profile by projecting each direction onto the correct tensor dimension.
|
||||
|
||||
Uses CATEGORY_TO_DIMENSION to map each direction's category to the right
|
||||
dimension of the 8x1024 state tensor.
|
||||
|
||||
Returns:
|
||||
Dict[category, Dict[direction_name, projection_value]]
|
||||
"""
|
||||
profile = {}
|
||||
|
||||
for direction in directions:
|
||||
category = direction["category"]
|
||||
name = direction["name"]
|
||||
dir_vector = direction["vector"]
|
||||
|
||||
# Map category to tensor dimension
|
||||
dim_name = CATEGORY_TO_DIMENSION.get(category, "thirdness")
|
||||
state_vector = named_vectors.get(dim_name)
|
||||
|
||||
if state_vector is None:
|
||||
continue
|
||||
|
||||
projection = compute_projection(state_vector, dir_vector)
|
||||
|
||||
if category not in profile:
|
||||
profile[category] = {}
|
||||
|
||||
profile[category][name] = round(projection, 4)
|
||||
|
||||
return profile
|
||||
|
||||
|
||||
def get_david_messages(client: weaviate.WeaviateClient, max_messages: int) -> list[str]:
|
||||
"""
|
||||
Get David's messages from Weaviate Message collection.
|
||||
|
||||
Returns:
|
||||
List of message contents
|
||||
"""
|
||||
collection = client.collections.get("Message")
|
||||
|
||||
from weaviate.classes.query import Filter
|
||||
|
||||
result = collection.query.fetch_objects(
|
||||
filters=Filter.by_property("role").equal("user"),
|
||||
limit=max_messages
|
||||
)
|
||||
|
||||
messages = []
|
||||
for obj in result.objects:
|
||||
content = obj.properties.get("content", "")
|
||||
if len(content) > 20:
|
||||
messages.append(content)
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def load_declared_profile() -> dict | None:
|
||||
"""
|
||||
Load David's declared profile from JSON file.
|
||||
|
||||
Returns:
|
||||
Profile dict or None if not found
|
||||
"""
|
||||
possible_paths = [
|
||||
Path(__file__).parent.parent.parent / "ikario_processual" / "david_profile_declared.json",
|
||||
Path("ikario_processual/david_profile_declared.json"),
|
||||
Path("david_profile_declared.json"),
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if path.exists():
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Handlers
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def get_state_profile_handler(input_data: GetStateProfileInput) -> Dict[str, Any]:
|
||||
"""
|
||||
Get Ikario's state profile projected onto interpretable directions.
|
||||
|
||||
Uses StateTensor (8x1024) with CATEGORY_TO_DIMENSION mapping for proper projection.
|
||||
Returns profile organized by categories (epistemic, affective, etc.)
|
||||
with values for each direction (curiosity, certainty, etc.).
|
||||
"""
|
||||
try:
|
||||
client = weaviate.connect_to_local()
|
||||
|
||||
try:
|
||||
# 1. Get StateTensor (8 named vectors)
|
||||
if input_data.state_id is not None:
|
||||
properties, named_vectors = get_state_tensor_by_id(
|
||||
client, input_data.state_id
|
||||
)
|
||||
else:
|
||||
properties, named_vectors = get_latest_state_tensor(client)
|
||||
|
||||
# 2. Get all ProjectionDirections
|
||||
directions = get_all_projection_directions(client)
|
||||
|
||||
if not directions:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No ProjectionDirection found in Weaviate. Run phase2_projection_directions.py first."
|
||||
}
|
||||
|
||||
# 3. Build profile using tensor dimensions
|
||||
profile = build_tensor_profile(named_vectors, directions)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"state_id": properties.get("state_id"),
|
||||
"timestamp": str(properties.get("timestamp", "")),
|
||||
"trigger_type": properties.get("trigger_type", "unknown"),
|
||||
"profile": profile,
|
||||
"directions_count": len(directions),
|
||||
"categories": list(profile.keys()),
|
||||
"architecture": "v2_tensor",
|
||||
"dimensions_loaded": list(named_vectors.keys())
|
||||
}
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
async def get_david_profile_handler(input_data: GetDavidProfileInput) -> Dict[str, Any]:
|
||||
"""
|
||||
Get David's profile from his messages and optionally declared profile.
|
||||
|
||||
Computes David's embedding from his messages, projects onto directions,
|
||||
and optionally merges with declared profile values.
|
||||
"""
|
||||
try:
|
||||
client = weaviate.connect_to_local()
|
||||
|
||||
try:
|
||||
# 1. Get David's messages
|
||||
messages = get_david_messages(client, input_data.max_messages)
|
||||
|
||||
if not messages:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No messages from David found in Weaviate"
|
||||
}
|
||||
|
||||
# 2. Concatenate and embed
|
||||
text = "\n\n".join(messages)[:5000]
|
||||
|
||||
embedder = get_embedder()
|
||||
david_vector = embedder.embed_batch([text])[0].tolist()
|
||||
|
||||
# 3. Get directions and compute profile
|
||||
directions = get_all_projection_directions(client)
|
||||
|
||||
if not directions:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No ProjectionDirection found. Run phase2_projection_directions.py first."
|
||||
}
|
||||
|
||||
# For David, use same vector for all dimensions (single embedding)
|
||||
david_named_vectors = {dim: david_vector for dim in DIMENSION_NAMES}
|
||||
computed_profile = build_tensor_profile(david_named_vectors, directions)
|
||||
|
||||
# 4. Load declared profile if requested
|
||||
declared_profile = None
|
||||
has_declared = False
|
||||
|
||||
if input_data.include_declared:
|
||||
declared_data = load_declared_profile()
|
||||
if declared_data:
|
||||
declared_profile = declared_data.get("profile", {})
|
||||
has_declared = True
|
||||
|
||||
# 5. Merge profiles (declared takes precedence for display)
|
||||
final_profile = {}
|
||||
for category, directions_dict in computed_profile.items():
|
||||
final_profile[category] = {}
|
||||
for name, computed_value in directions_dict.items():
|
||||
entry = {
|
||||
"computed": computed_value,
|
||||
}
|
||||
|
||||
if declared_profile and category in declared_profile:
|
||||
declared_value = declared_profile[category].get(name)
|
||||
if declared_value is not None:
|
||||
entry["declared"] = declared_value
|
||||
entry["declared_normalized"] = round(declared_value / 10, 2)
|
||||
|
||||
final_profile[category][name] = entry
|
||||
|
||||
# 6. Compute similarity with Ikario
|
||||
try:
|
||||
_, ikario_vectors = get_latest_state_tensor(client)
|
||||
# Cosine similarity across all dimensions
|
||||
similarities = []
|
||||
for dim_name in DIMENSION_NAMES:
|
||||
if dim_name in ikario_vectors:
|
||||
sim = float(np.dot(david_vector, ikario_vectors[dim_name]))
|
||||
similarities.append(sim)
|
||||
similarity_percent = round(np.mean(similarities) * 100, 1) if similarities else None
|
||||
except Exception:
|
||||
similarity_percent = None
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"profile": final_profile,
|
||||
"similarity_with_ikario": similarity_percent,
|
||||
"messages_analyzed": len(messages),
|
||||
"has_declared_profile": has_declared,
|
||||
"categories": list(final_profile.keys()),
|
||||
"directions_count": len(directions)
|
||||
}
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
async def compare_profiles_handler(input_data: CompareProfilesInput) -> Dict[str, Any]:
|
||||
"""
|
||||
Compare Ikario and David profiles.
|
||||
|
||||
Returns similarity score and detailed comparison by direction,
|
||||
including convergent and divergent dimensions.
|
||||
"""
|
||||
try:
|
||||
client = weaviate.connect_to_local()
|
||||
|
||||
try:
|
||||
# 1. Get Ikario's state tensor
|
||||
if input_data.state_id is not None:
|
||||
_, ikario_vectors = get_state_tensor_by_id(client, input_data.state_id)
|
||||
else:
|
||||
_, ikario_vectors = get_latest_state_tensor(client)
|
||||
|
||||
# 2. Get David's messages and embed
|
||||
messages = get_david_messages(client, max_messages=100)
|
||||
if not messages:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No messages from David found"
|
||||
}
|
||||
|
||||
text = "\n\n".join(messages)[:5000]
|
||||
embedder = get_embedder()
|
||||
david_vector = embedder.embed_batch([text])[0].tolist()
|
||||
david_named_vectors = {dim: david_vector for dim in DIMENSION_NAMES}
|
||||
|
||||
# 3. Get directions
|
||||
directions = get_all_projection_directions(client)
|
||||
if not directions:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No ProjectionDirection found"
|
||||
}
|
||||
|
||||
# 4. Filter directions by category if specified
|
||||
if input_data.categories:
|
||||
directions = [
|
||||
d for d in directions
|
||||
if d["category"] in input_data.categories
|
||||
]
|
||||
|
||||
# 5. Compute projections for both using tensor dimensions
|
||||
ikario_profile = build_tensor_profile(ikario_vectors, directions)
|
||||
david_profile = build_tensor_profile(david_named_vectors, directions)
|
||||
|
||||
# 6. Build comparison
|
||||
comparison = {}
|
||||
all_deltas = []
|
||||
|
||||
for category in ikario_profile.keys():
|
||||
comparison[category] = {}
|
||||
for name in ikario_profile[category].keys():
|
||||
ikario_val = ikario_profile[category][name]
|
||||
david_val = david_profile[category].get(name, 0)
|
||||
delta = round(abs(ikario_val - david_val), 4)
|
||||
|
||||
comparison[category][name] = {
|
||||
"ikario": ikario_val,
|
||||
"david": david_val,
|
||||
"delta": delta
|
||||
}
|
||||
|
||||
all_deltas.append({
|
||||
"name": name,
|
||||
"category": category,
|
||||
"ikario": ikario_val,
|
||||
"david": david_val,
|
||||
"delta": delta
|
||||
})
|
||||
|
||||
# 7. Find convergent and divergent dimensions
|
||||
sorted_by_delta = sorted(all_deltas, key=lambda x: x["delta"])
|
||||
|
||||
convergent = sorted_by_delta[:5]
|
||||
divergent = sorted_by_delta[-5:][::-1]
|
||||
|
||||
# 8. Compute overall similarity (mean across dimensions)
|
||||
similarities = []
|
||||
for dim_name in DIMENSION_NAMES:
|
||||
if dim_name in ikario_vectors:
|
||||
sim = float(np.dot(david_vector, ikario_vectors[dim_name]))
|
||||
similarities.append(sim)
|
||||
similarity_percent = round(np.mean(similarities) * 100, 1) if similarities else 0
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"similarity": similarity_percent,
|
||||
"comparison": comparison,
|
||||
"convergent_dimensions": [
|
||||
{
|
||||
"name": d["name"],
|
||||
"category": d["category"],
|
||||
"ikario": d["ikario"],
|
||||
"david": d["david"]
|
||||
}
|
||||
for d in convergent
|
||||
],
|
||||
"divergent_dimensions": [
|
||||
{
|
||||
"name": d["name"],
|
||||
"category": d["category"],
|
||||
"ikario": d["ikario"],
|
||||
"david": d["david"]
|
||||
}
|
||||
for d in divergent
|
||||
],
|
||||
"categories_compared": list(comparison.keys()),
|
||||
"directions_compared": len(all_deltas)
|
||||
}
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
async def get_state_tensor_handler(input_data: GetStateTensorInput) -> Dict[str, Any]:
|
||||
"""
|
||||
Get raw 8x1024 state tensor (advanced usage).
|
||||
|
||||
Returns the 8 named dimension vectors for Ikario or a single embedding for David.
|
||||
"""
|
||||
try:
|
||||
client = weaviate.connect_to_local()
|
||||
|
||||
try:
|
||||
if input_data.entity == "ikario":
|
||||
if input_data.state_id is not None:
|
||||
properties, named_vectors = get_state_tensor_by_id(
|
||||
client, input_data.state_id
|
||||
)
|
||||
else:
|
||||
properties, named_vectors = get_latest_state_tensor(client)
|
||||
|
||||
# Return first 10 values per dimension (truncated for readability)
|
||||
truncated = {
|
||||
dim: list(vec[:10]) if hasattr(vec, '__len__') else vec
|
||||
for dim, vec in named_vectors.items()
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"entity": "ikario",
|
||||
"dimensions": truncated,
|
||||
"dimension_count": len(named_vectors),
|
||||
"vector_size": 1024,
|
||||
"metadata": {
|
||||
"state_id": properties.get("state_id"),
|
||||
"timestamp": str(properties.get("timestamp", "")),
|
||||
"trigger_type": properties.get("trigger_type")
|
||||
}
|
||||
}
|
||||
|
||||
elif input_data.entity == "david":
|
||||
messages = get_david_messages(client, max_messages=100)
|
||||
if not messages:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No messages from David found"
|
||||
}
|
||||
|
||||
text = "\n\n".join(messages)[:5000]
|
||||
embedder = get_embedder()
|
||||
david_vector = embedder.embed_batch([text])[0].tolist()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"entity": "david",
|
||||
"vector": david_vector[:10], # Truncated
|
||||
"dimension": len(david_vector),
|
||||
"metadata": {
|
||||
"source": "messages_embedding",
|
||||
"messages_count": len(messages)
|
||||
}
|
||||
}
|
||||
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Unknown entity: {input_data.entity}. Use 'ikario' or 'david'."
|
||||
}
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
Reference in New Issue
Block a user