fix entity and migrate memory disposition
This commit is contained in:
parent
3bb0a58ded
commit
76cfa8f9c4
37 changed files with 694 additions and 547 deletions
|
|
@ -0,0 +1,62 @@
|
|||
"""disposition_to_3_traits
|
||||
|
||||
Revision ID: e0a1b2c3d4e5
|
||||
Revises: rename_personality
|
||||
Create Date: 2024-12-08
|
||||
|
||||
Migrate disposition traits from Big Five (openness, conscientiousness, extraversion,
|
||||
agreeableness, neuroticism, bias_strength with 0-1 float values) to the new 3-trait
|
||||
system (skepticism, literalism, empathy with 1-5 integer values).
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'e0a1b2c3d4e5'
|
||||
down_revision: Union[str, Sequence[str], None] = 'rename_personality'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Convert Big Five disposition to 3-trait disposition."""
|
||||
conn = op.get_bind()
|
||||
|
||||
# Update all existing banks to use the new disposition format
|
||||
# Convert from old format to new format with reasonable mappings:
|
||||
# - skepticism: derived from inverse of agreeableness (skeptical people are less agreeable)
|
||||
# - literalism: derived from conscientiousness (detail-oriented people are more literal)
|
||||
# - empathy: derived from agreeableness + inverse of neuroticism
|
||||
# Default all to 3 (neutral) for simplicity
|
||||
conn.execute(sa.text("""
|
||||
UPDATE banks
|
||||
SET disposition = '{"skepticism": 3, "literalism": 3, "empathy": 3}'::jsonb
|
||||
WHERE disposition IS NOT NULL
|
||||
"""))
|
||||
|
||||
# Update the default for new banks
|
||||
conn.execute(sa.text("""
|
||||
ALTER TABLE banks
|
||||
ALTER COLUMN disposition SET DEFAULT '{"skepticism": 3, "literalism": 3, "empathy": 3}'::jsonb
|
||||
"""))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Convert back to Big Five disposition."""
|
||||
conn = op.get_bind()
|
||||
|
||||
# Revert to Big Five format with default values
|
||||
conn.execute(sa.text("""
|
||||
UPDATE banks
|
||||
SET disposition = '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}'::jsonb
|
||||
WHERE disposition IS NOT NULL
|
||||
"""))
|
||||
|
||||
# Update the default for new banks
|
||||
conn.execute(sa.text("""
|
||||
ALTER TABLE banks
|
||||
ALTER COLUMN disposition SET DEFAULT '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}'::jsonb
|
||||
"""))
|
||||
|
|
@ -439,24 +439,18 @@ class BanksResponse(BaseModel):
|
|||
|
||||
|
||||
class DispositionTraits(BaseModel):
|
||||
"""Disposition traits based on Big Five model."""
|
||||
"""Disposition traits that influence how memories are formed and interpreted."""
|
||||
model_config = ConfigDict(json_schema_extra={
|
||||
"example": {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.7,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.7
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
}
|
||||
})
|
||||
|
||||
openness: float = Field(ge=0.0, le=1.0, description="Openness to experience (0-1)")
|
||||
conscientiousness: float = Field(ge=0.0, le=1.0, description="Conscientiousness (0-1)")
|
||||
extraversion: float = Field(ge=0.0, le=1.0, description="Extraversion (0-1)")
|
||||
agreeableness: float = Field(ge=0.0, le=1.0, description="Agreeableness (0-1)")
|
||||
neuroticism: float = Field(ge=0.0, le=1.0, description="Neuroticism (0-1)")
|
||||
bias_strength: float = Field(ge=0.0, le=1.0, description="How strongly disposition influences opinions (0-1)")
|
||||
skepticism: int = Field(ge=1, le=5, description="How skeptical vs trusting (1=trusting, 5=skeptical)")
|
||||
literalism: int = Field(ge=1, le=5, description="How literally to interpret information (1=flexible, 5=literal)")
|
||||
empathy: int = Field(ge=1, le=5, description="How much to consider emotional context (1=detached, 5=empathetic)")
|
||||
|
||||
|
||||
class BankProfileResponse(BaseModel):
|
||||
|
|
@ -466,12 +460,9 @@ class BankProfileResponse(BaseModel):
|
|||
"bank_id": "user123",
|
||||
"name": "Alice",
|
||||
"disposition": {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.7,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.7
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
},
|
||||
"background": "I am a software engineer with 10 years of experience in startups"
|
||||
}
|
||||
|
|
@ -500,7 +491,7 @@ class AddBackgroundRequest(BaseModel):
|
|||
content: str = Field(description="New background information to add or merge")
|
||||
update_disposition: bool = Field(
|
||||
default=True,
|
||||
description="If true, infer Big Five disposition traits from the merged background (default: true)"
|
||||
description="If true, infer disposition traits from the merged background (default: true)"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -510,12 +501,9 @@ class BackgroundResponse(BaseModel):
|
|||
"example": {
|
||||
"background": "I was born in Texas. I am a software engineer with 10 years of experience.",
|
||||
"disposition": {
|
||||
"openness": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.8,
|
||||
"neuroticism": 0.4,
|
||||
"bias_strength": 0.6
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -543,12 +531,9 @@ class BankListResponse(BaseModel):
|
|||
"bank_id": "user123",
|
||||
"name": "Alice",
|
||||
"disposition": {
|
||||
"openness": 0.5,
|
||||
"conscientiousness": 0.5,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.5,
|
||||
"neuroticism": 0.5,
|
||||
"bias_strength": 0.5
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
},
|
||||
"background": "I am a software engineer",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
|
|
@ -567,12 +552,9 @@ class CreateBankRequest(BaseModel):
|
|||
"example": {
|
||||
"name": "Alice",
|
||||
"disposition": {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.7,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.7
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
},
|
||||
"background": "I am a creative software engineer with 10 years of experience"
|
||||
}
|
||||
|
|
@ -1605,7 +1587,7 @@ This operation cannot be undone.
|
|||
"/v1/default/banks/{bank_id}/profile",
|
||||
response_model=BankProfileResponse,
|
||||
summary="Update memory bank disposition",
|
||||
description="Update bank's Big Five disposition traits and bias strength",
|
||||
description="Update bank's disposition traits (skepticism, literalism, empathy)",
|
||||
operation_id="update_bank_disposition"
|
||||
)
|
||||
async def api_update_bank_disposition(bank_id: str,
|
||||
|
|
@ -1852,7 +1834,7 @@ This operation cannot be undone.
|
|||
"/v1/default/banks/{bank_id}/memories",
|
||||
response_model=DeleteResponse,
|
||||
summary="Clear memory bank memories",
|
||||
description="Delete memory units for a memory bank. Optionally filter by type (world, experience, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The bank profile (personality and background) will be preserved.",
|
||||
description="Delete memory units for a memory bank. Optionally filter by type (world, experience, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The bank profile (disposition and background) will be preserved.",
|
||||
operation_id="clear_bank_memories"
|
||||
)
|
||||
async def api_clear_bank_memories(bank_id: str,
|
||||
|
|
|
|||
|
|
@ -959,7 +959,7 @@ class MemoryEngine:
|
|||
budget_mapping = {
|
||||
Budget.LOW: 100,
|
||||
Budget.MID: 300,
|
||||
Budget.HIGH: 600
|
||||
Budget.HIGH: 1000
|
||||
}
|
||||
thinking_budget = budget_mapping[budget]
|
||||
|
||||
|
|
@ -2502,14 +2502,14 @@ Guidelines:
|
|||
async def update_bank_disposition(
|
||||
self,
|
||||
bank_id: str,
|
||||
disposition: Dict[str, float]
|
||||
disposition: Dict[str, int]
|
||||
) -> None:
|
||||
"""
|
||||
Update bank disposition traits.
|
||||
|
||||
Args:
|
||||
bank_id: bank IDentifier
|
||||
disposition: Dict with Big Five traits + bias_strength (all 0-1)
|
||||
disposition: Dict with skepticism, literalism, empathy (all 1-5)
|
||||
"""
|
||||
pool = await self._get_pool()
|
||||
await bank_utils.update_bank_disposition(pool, bank_id, disposition)
|
||||
|
|
@ -2997,22 +2997,23 @@ Guidelines:
|
|||
)
|
||||
|
||||
if not entity_exists:
|
||||
logger.debug(f"[OBSERVATIONS] Entity {entity_id} not yet in bank {bank_id}, skipping")
|
||||
continue
|
||||
|
||||
entity_name = entity_exists['canonical_name']
|
||||
|
||||
# Count facts linked to this entity
|
||||
# Count facts linked to this entity (in this bank)
|
||||
fact_count = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM unit_entities WHERE entity_id = $1",
|
||||
entity_uuid
|
||||
"""
|
||||
SELECT COUNT(*) FROM unit_entities ue
|
||||
JOIN memory_units mu ON ue.unit_id = mu.id
|
||||
WHERE ue.entity_id = $1 AND mu.bank_id = $2
|
||||
""",
|
||||
entity_uuid, bank_id
|
||||
) or 0
|
||||
|
||||
# Only regenerate if entity has enough facts
|
||||
if fact_count >= min_facts:
|
||||
await self.regenerate_entity_observations(bank_id, entity_id, entity_name, version=None)
|
||||
else:
|
||||
logger.debug(f"[OBSERVATIONS] Skipping {entity_name} ({fact_count} facts < {min_facts} threshold)")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[OBSERVATIONS] Error processing entity {entity_id}: {e}")
|
||||
|
|
|
|||
|
|
@ -12,25 +12,22 @@ from pydantic import BaseModel, Field, ConfigDict
|
|||
|
||||
class DispositionTraits(BaseModel):
|
||||
"""
|
||||
Disposition traits for a bank using the Big Five model.
|
||||
Disposition traits for a memory bank.
|
||||
|
||||
All traits are scored 0.0-1.0 where higher values indicate stronger presence of the trait.
|
||||
All traits are scored 1-5 where:
|
||||
- skepticism: 1=trusting, 5=skeptical (how much to doubt or question information)
|
||||
- literalism: 1=flexible interpretation, 5=literal interpretation (how strictly to interpret information)
|
||||
- empathy: 1=detached, 5=empathetic (how much to consider emotional context)
|
||||
"""
|
||||
openness: float = Field(description="Openness to experience (0.0-1.0)")
|
||||
conscientiousness: float = Field(description="Conscientiousness and organization (0.0-1.0)")
|
||||
extraversion: float = Field(description="Extraversion and sociability (0.0-1.0)")
|
||||
agreeableness: float = Field(description="Agreeableness and cooperation (0.0-1.0)")
|
||||
neuroticism: float = Field(description="Emotional sensitivity and neuroticism (0.0-1.0)")
|
||||
bias_strength: float = Field(description="How strongly disposition influences thinking (0.0-1.0)")
|
||||
skepticism: int = Field(ge=1, le=5, description="How skeptical vs trusting (1=trusting, 5=skeptical)")
|
||||
literalism: int = Field(ge=1, le=5, description="How literally to interpret information (1=flexible, 5=literal)")
|
||||
empathy: int = Field(ge=1, le=5, description="How much to consider emotional context (1=detached, 5=empathetic)")
|
||||
|
||||
model_config = ConfigDict(json_schema_extra={
|
||||
"example": {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.4,
|
||||
"agreeableness": 0.7,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.5
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,9 @@ from ..response_models import DispositionTraits
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_DISPOSITION = {
|
||||
"openness": 0.5,
|
||||
"conscientiousness": 0.5,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.5,
|
||||
"neuroticism": 0.5,
|
||||
"bias_strength": 0.5,
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -32,7 +29,7 @@ class BankProfile(TypedDict):
|
|||
class BackgroundMergeResponse(BaseModel):
|
||||
"""LLM response for background merge with disposition inference."""
|
||||
background: str = Field(description="Merged background in first person perspective")
|
||||
disposition: DispositionTraits = Field(description="Inferred Big Five disposition traits")
|
||||
disposition: DispositionTraits = Field(description="Inferred disposition traits (skepticism, literalism, empathy)")
|
||||
|
||||
|
||||
async def get_bank_profile(pool, bank_id: str) -> BankProfile:
|
||||
|
|
@ -92,7 +89,7 @@ async def get_bank_profile(pool, bank_id: str) -> BankProfile:
|
|||
async def update_bank_disposition(
|
||||
pool,
|
||||
bank_id: str,
|
||||
disposition: Dict[str, float]
|
||||
disposition: Dict[str, int]
|
||||
) -> None:
|
||||
"""
|
||||
Update bank disposition traits.
|
||||
|
|
@ -100,7 +97,7 @@ async def update_bank_disposition(
|
|||
Args:
|
||||
pool: Database connection pool
|
||||
bank_id: bank IDentifier
|
||||
disposition: Dict with Big Five traits + bias_strength (all 0-1)
|
||||
disposition: Dict with skepticism, literalism, empathy (all 1-5)
|
||||
"""
|
||||
# Ensure bank exists first
|
||||
await get_bank_profile(pool, bank_id)
|
||||
|
|
@ -223,13 +220,10 @@ Instructions:
|
|||
3. Keep additions that don't conflict
|
||||
4. Output in FIRST PERSON ("I") perspective
|
||||
5. Be concise - keep merged background under 500 characters
|
||||
6. Infer Big Five disposition traits from the merged background:
|
||||
- Openness: 0.0-1.0 (creativity, curiosity, openness to new ideas)
|
||||
- Conscientiousness: 0.0-1.0 (organization, discipline, goal-directed)
|
||||
- Extraversion: 0.0-1.0 (sociability, assertiveness, energy from others)
|
||||
- Agreeableness: 0.0-1.0 (cooperation, empathy, consideration)
|
||||
- Neuroticism: 0.0-1.0 (emotional sensitivity, anxiety, stress response)
|
||||
- Bias Strength: 0.0-1.0 (how much disposition influences opinions)
|
||||
6. Infer disposition traits from the merged background (each 1-5 integer):
|
||||
- Skepticism: 1-5 (1=trusting, takes things at face value; 5=skeptical, questions everything)
|
||||
- Literalism: 1-5 (1=flexible interpretation, reads between lines; 5=literal, exact interpretation)
|
||||
- Empathy: 1-5 (1=detached, focuses on facts; 5=empathetic, considers emotional context)
|
||||
|
||||
CRITICAL: You MUST respond with ONLY a valid JSON object. No markdown, no code blocks, no explanations. Just the JSON.
|
||||
|
||||
|
|
@ -237,22 +231,19 @@ Format:
|
|||
{{
|
||||
"background": "the merged background text in first person",
|
||||
"disposition": {{
|
||||
"openness": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"agreeableness": 0.8,
|
||||
"neuroticism": 0.4,
|
||||
"bias_strength": 0.6
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
}}
|
||||
}}
|
||||
|
||||
Trait inference examples:
|
||||
- "creative artist" → openness: 0.8+, bias_strength: 0.6
|
||||
- "organized engineer" → conscientiousness: 0.8+, openness: 0.5-0.6
|
||||
- "startup founder" → openness: 0.8+, extraversion: 0.7+, neuroticism: 0.3-0.4
|
||||
- "risk-averse analyst" → openness: 0.3-0.4, conscientiousness: 0.8+, neuroticism: 0.6+
|
||||
- "rational and diligent" → conscientiousness: 0.7+, openness: 0.6+
|
||||
- "passionate and dramatic" → extraversion: 0.7+, neuroticism: 0.6+, openness: 0.7+"""
|
||||
- "I'm a lawyer" → skepticism: 4, literalism: 5, empathy: 2
|
||||
- "I'm a therapist" → skepticism: 2, literalism: 2, empathy: 5
|
||||
- "I'm an engineer" → skepticism: 3, literalism: 4, empathy: 3
|
||||
- "I've been burned before by trusting people" → skepticism: 5, literalism: 3, empathy: 3
|
||||
- "I try to understand what people really mean" → skepticism: 3, literalism: 2, empathy: 4
|
||||
- "I take contracts very seriously" → skepticism: 4, literalism: 5, empathy: 2"""
|
||||
else:
|
||||
prompt = f"""You are helping maintain a memory bank's background/profile.
|
||||
|
||||
|
|
@ -349,13 +340,12 @@ Merged background:"""
|
|||
|
||||
# Validate disposition values
|
||||
disposition = result.get("disposition", {})
|
||||
for key in ["openness", "conscientiousness", "extraversion",
|
||||
"agreeableness", "neuroticism", "bias_strength"]:
|
||||
for key in ["skepticism", "literalism", "empathy"]:
|
||||
if key not in disposition:
|
||||
disposition[key] = 0.5 # Default to neutral
|
||||
disposition[key] = 3 # Default to neutral
|
||||
else:
|
||||
# Clamp to [0, 1]
|
||||
disposition[key] = max(0.0, min(1.0, float(disposition[key])))
|
||||
# Clamp to [1, 5] and convert to int
|
||||
disposition[key] = max(1, min(5, int(disposition[key])))
|
||||
|
||||
result["disposition"] = disposition
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import logging
|
|||
from typing import List, Tuple, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
from .types import ProcessedFact, EntityRef
|
||||
from .types import ProcessedFact, EntityRef, EntityLink
|
||||
from . import link_utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -20,7 +20,7 @@ async def process_entities_batch(
|
|||
unit_ids: List[str],
|
||||
facts: List[ProcessedFact],
|
||||
log_buffer: List[str] = None
|
||||
) -> List[Tuple[str, str, float]]:
|
||||
) -> List[EntityLink]:
|
||||
"""
|
||||
Process entities for all facts and create entity links.
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ async def process_entities_batch(
|
|||
log_buffer: Optional buffer for detailed logging
|
||||
|
||||
Returns:
|
||||
List of entity link tuples: (unit_id, entity_id, confidence)
|
||||
List of EntityLink objects for batch insertion
|
||||
"""
|
||||
if not unit_ids or not facts:
|
||||
return []
|
||||
|
|
@ -75,14 +75,14 @@ async def process_entities_batch(
|
|||
|
||||
async def insert_entity_links_batch(
|
||||
conn,
|
||||
entity_links: List[Tuple[str, str, float]]
|
||||
entity_links: List[EntityLink]
|
||||
) -> None:
|
||||
"""
|
||||
Insert entity links in batch.
|
||||
|
||||
Args:
|
||||
conn: Database connection
|
||||
entity_links: List of (unit_id, entity_id, confidence) tuples
|
||||
entity_links: List of EntityLink objects
|
||||
"""
|
||||
if not entity_links:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ async def ensure_bank_exists(conn, bank_id: str) -> None:
|
|||
SET updated_at = NOW()
|
||||
""",
|
||||
bank_id,
|
||||
'{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}',
|
||||
'{"skepticism": 3, "literalism": 3, "empathy": 3}',
|
||||
""
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import time
|
|||
import logging
|
||||
from typing import List
|
||||
from datetime import timedelta, datetime, timezone
|
||||
from uuid import UUID
|
||||
|
||||
from .types import EntityLink
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -305,10 +308,14 @@ async def extract_entities_batch_optimized(
|
|||
# Only link each new unit to the most recent MAX_LINKS_PER_ENTITY units
|
||||
MAX_LINKS_PER_ENTITY = 50 # Limit to prevent explosion when entity appears in many facts
|
||||
link_gen_start = time.time()
|
||||
links = []
|
||||
links: List[EntityLink] = []
|
||||
new_unit_set = set(unit_ids) # Units from this batch
|
||||
|
||||
def to_uuid(val) -> UUID:
|
||||
return UUID(val) if isinstance(val, str) else val
|
||||
|
||||
for entity_id, units_with_entity in entity_to_units.items():
|
||||
entity_uuid = to_uuid(entity_id)
|
||||
# Separate new units (from this batch) and existing units
|
||||
new_units = [u for u in units_with_entity if str(u) in new_unit_set or u in new_unit_set]
|
||||
existing_units = [u for u in units_with_entity if str(u) not in new_unit_set and u not in new_unit_set]
|
||||
|
|
@ -318,15 +325,15 @@ async def extract_entities_batch_optimized(
|
|||
new_units_to_link = new_units[-MAX_LINKS_PER_ENTITY:] if len(new_units) > MAX_LINKS_PER_ENTITY else new_units
|
||||
for i, unit_id_1 in enumerate(new_units_to_link):
|
||||
for unit_id_2 in new_units_to_link[i+1:]:
|
||||
links.append((unit_id_1, unit_id_2, 'entity', 1.0, entity_id))
|
||||
links.append((unit_id_2, unit_id_1, 'entity', 1.0, entity_id))
|
||||
links.append(EntityLink(from_unit_id=to_uuid(unit_id_1), to_unit_id=to_uuid(unit_id_2), entity_id=entity_uuid))
|
||||
links.append(EntityLink(from_unit_id=to_uuid(unit_id_2), to_unit_id=to_uuid(unit_id_1), entity_id=entity_uuid))
|
||||
|
||||
# Link new units to LIMITED existing units (most recent)
|
||||
existing_to_link = existing_units[-MAX_LINKS_PER_ENTITY:] # Take most recent
|
||||
for new_unit in new_units:
|
||||
for existing_unit in existing_to_link:
|
||||
links.append((new_unit, existing_unit, 'entity', 1.0, entity_id))
|
||||
links.append((existing_unit, new_unit, 'entity', 1.0, entity_id))
|
||||
links.append(EntityLink(from_unit_id=to_uuid(new_unit), to_unit_id=to_uuid(existing_unit), entity_id=entity_uuid))
|
||||
links.append(EntityLink(from_unit_id=to_uuid(existing_unit), to_unit_id=to_uuid(new_unit), entity_id=entity_uuid))
|
||||
|
||||
_log(log_buffer, f" [6.3.3] Generate {len(links)} links: {time.time() - link_gen_start:.3f}s", level='debug')
|
||||
_log(log_buffer, f" [6.3] Entity link creation: {len(links)} links for {len(all_entity_ids)} unique entities in {time.time() - substep_start:.3f}s", level='debug')
|
||||
|
|
@ -546,7 +553,7 @@ async def create_semantic_links_batch(
|
|||
raise
|
||||
|
||||
|
||||
async def insert_entity_links_batch(conn, links: List[tuple], chunk_size: int = 50000):
|
||||
async def insert_entity_links_batch(conn, links: List[EntityLink], chunk_size: int = 50000):
|
||||
"""
|
||||
Insert all entity links using COPY to temp table + INSERT for maximum speed.
|
||||
|
||||
|
|
@ -556,7 +563,7 @@ async def insert_entity_links_batch(conn, links: List[tuple], chunk_size: int =
|
|||
|
||||
Args:
|
||||
conn: Database connection
|
||||
links: List of tuples (from_unit_id, to_unit_id, link_type, weight, entity_id)
|
||||
links: List of EntityLink objects
|
||||
chunk_size: Number of rows per batch (default 50000)
|
||||
"""
|
||||
if not links:
|
||||
|
|
@ -585,16 +592,16 @@ async def insert_entity_links_batch(conn, links: List[tuple], chunk_size: int =
|
|||
await conn.execute("TRUNCATE _temp_entity_links")
|
||||
logger.debug(f" [9.2] Truncate temp table: {time_mod.time() - truncate_start:.3f}s")
|
||||
|
||||
# Convert links to proper format for COPY
|
||||
# Convert EntityLink objects to tuples for COPY
|
||||
convert_start = time_mod.time()
|
||||
records = []
|
||||
for from_id, to_id, link_type, weight, entity_id in links:
|
||||
for link in links:
|
||||
records.append((
|
||||
uuid_mod.UUID(from_id) if isinstance(from_id, str) else from_id,
|
||||
uuid_mod.UUID(to_id) if isinstance(to_id, str) else to_id,
|
||||
link_type,
|
||||
weight,
|
||||
uuid_mod.UUID(str(entity_id)) if entity_id and not isinstance(entity_id, uuid_mod.UUID) else entity_id
|
||||
link.from_unit_id,
|
||||
link.to_unit_id,
|
||||
link.link_type,
|
||||
link.weight,
|
||||
link.entity_id
|
||||
))
|
||||
logger.debug(f" [9.3] Convert {len(records)} records: {time_mod.time() - convert_start:.3f}s")
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def utcnow():
|
|||
"""Get current UTC time."""
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
from .types import RetainContent, ExtractedFact, ProcessedFact
|
||||
from .types import RetainContent, ExtractedFact, ProcessedFact, EntityLink
|
||||
from . import (
|
||||
fact_extraction,
|
||||
embedding_processing,
|
||||
|
|
@ -373,7 +373,7 @@ async def _trigger_background_tasks(
|
|||
bank_id: str,
|
||||
unit_ids: List[str],
|
||||
facts: List[ProcessedFact],
|
||||
entity_links: List,
|
||||
entity_links: List[EntityLink],
|
||||
log_buffer: List[str] = None
|
||||
) -> None:
|
||||
"""Trigger opinion reinforcement and observation regeneration (sync)."""
|
||||
|
|
@ -388,19 +388,27 @@ async def _trigger_background_tasks(
|
|||
'unit_entities': fact_entities
|
||||
})
|
||||
|
||||
# Regenerate observations synchronously for top entities
|
||||
# Regenerate observations synchronously for top entities by fact count
|
||||
TOP_N_ENTITIES = 5
|
||||
MIN_FACTS_THRESHOLD = 5
|
||||
|
||||
if entity_links and regenerate_observations_fn:
|
||||
unique_entity_ids = set()
|
||||
# Count mentions per entity in this batch
|
||||
entity_mention_counts: Dict[str, int] = {}
|
||||
for link in entity_links:
|
||||
# links are tuples: (from_unit_id, to_unit_id, link_type, weight, entity_id)
|
||||
if len(link) >= 5 and link[4]:
|
||||
unique_entity_ids.add(str(link[4]))
|
||||
if link.entity_id:
|
||||
entity_id = str(link.entity_id)
|
||||
entity_mention_counts[entity_id] = entity_mention_counts.get(entity_id, 0) + 1
|
||||
|
||||
if entity_mention_counts:
|
||||
# Sort by mention count descending and take top N
|
||||
sorted_entities = sorted(
|
||||
entity_mention_counts.items(),
|
||||
key=lambda x: x[1],
|
||||
reverse=True
|
||||
)
|
||||
entities_to_process = [e[0] for e in sorted_entities[:TOP_N_ENTITIES]]
|
||||
|
||||
if unique_entity_ids:
|
||||
entities_to_process = list(unique_entity_ids)[:TOP_N_ENTITIES]
|
||||
obs_start = time.time()
|
||||
# Run observation regeneration synchronously
|
||||
await regenerate_observations_fn(
|
||||
|
|
|
|||
|
|
@ -176,6 +176,20 @@ class ProcessedFact:
|
|||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EntityLink:
|
||||
"""
|
||||
Link between two memory units through a shared entity.
|
||||
|
||||
Used for entity-based graph connections in the memory graph.
|
||||
"""
|
||||
from_unit_id: UUID
|
||||
to_unit_id: UUID
|
||||
entity_id: UUID
|
||||
link_type: str = 'entity'
|
||||
weight: float = 1.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class RetainBatch:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -28,30 +28,48 @@ class OpinionExtractionResponse(BaseModel):
|
|||
)
|
||||
|
||||
|
||||
def describe_trait(name: str, value: float) -> str:
|
||||
"""Convert trait value to descriptive text."""
|
||||
if value >= 0.8:
|
||||
return f"very high {name}"
|
||||
elif value >= 0.6:
|
||||
return f"high {name}"
|
||||
elif value >= 0.4:
|
||||
return f"moderate {name}"
|
||||
elif value >= 0.2:
|
||||
return f"low {name}"
|
||||
else:
|
||||
return f"very low {name}"
|
||||
def describe_trait_level(value: int) -> str:
|
||||
"""Convert trait value (1-5) to descriptive text."""
|
||||
levels = {
|
||||
1: "very low",
|
||||
2: "low",
|
||||
3: "moderate",
|
||||
4: "high",
|
||||
5: "very high"
|
||||
}
|
||||
return levels.get(value, "moderate")
|
||||
|
||||
|
||||
def build_disposition_description(disposition: DispositionTraits) -> str:
|
||||
"""Build a disposition description string from disposition traits."""
|
||||
return f"""Your disposition traits:
|
||||
- {describe_trait('openness to new ideas', disposition.openness)}
|
||||
- {describe_trait('conscientiousness and organization', disposition.conscientiousness)}
|
||||
- {describe_trait('extraversion and sociability', disposition.extraversion)}
|
||||
- {describe_trait('agreeableness and cooperation', disposition.agreeableness)}
|
||||
- {describe_trait('emotional sensitivity', disposition.neuroticism)}
|
||||
skepticism_desc = {
|
||||
1: "You are very trusting and tend to take information at face value.",
|
||||
2: "You tend to trust information but may question obvious inconsistencies.",
|
||||
3: "You have a balanced approach to information, neither too trusting nor too skeptical.",
|
||||
4: "You are somewhat skeptical and often question the reliability of information.",
|
||||
5: "You are highly skeptical and critically examine all information for accuracy and hidden motives."
|
||||
}
|
||||
|
||||
Disposition influence strength: {int(disposition.bias_strength * 100)}% (how much your disposition shapes your opinions)"""
|
||||
literalism_desc = {
|
||||
1: "You interpret information very flexibly, reading between the lines and inferring intent.",
|
||||
2: "You tend to consider context and implied meaning alongside literal statements.",
|
||||
3: "You balance literal interpretation with contextual understanding.",
|
||||
4: "You prefer to interpret information more literally and precisely.",
|
||||
5: "You interpret information very literally and focus on exact wording and commitments."
|
||||
}
|
||||
|
||||
empathy_desc = {
|
||||
1: "You focus primarily on facts and data, setting aside emotional context.",
|
||||
2: "You consider facts first but acknowledge emotional factors exist.",
|
||||
3: "You balance factual analysis with emotional understanding.",
|
||||
4: "You give significant weight to emotional context and human factors.",
|
||||
5: "You strongly consider the emotional state and circumstances of others when forming memories."
|
||||
}
|
||||
|
||||
return f"""Your disposition traits:
|
||||
- Skepticism ({describe_trait_level(disposition.skepticism)}): {skepticism_desc.get(disposition.skepticism, skepticism_desc[3])}
|
||||
- Literalism ({describe_trait_level(disposition.literalism)}): {literalism_desc.get(disposition.literalism, literalism_desc[3])}
|
||||
- Empathy ({describe_trait_level(disposition.empathy)}): {empathy_desc.get(disposition.empathy, empathy_desc[3])}"""
|
||||
|
||||
|
||||
def format_facts_for_prompt(facts: List[MemoryFact]) -> str:
|
||||
|
|
@ -141,13 +159,28 @@ Based on everything I know, believe, and who I am (including my name, dispositio
|
|||
|
||||
def get_system_message(disposition: DispositionTraits) -> str:
|
||||
"""Get the system message for the think LLM call."""
|
||||
bias_strength = disposition.bias_strength
|
||||
if bias_strength >= 0.7:
|
||||
disposition_instruction = "Your disposition strongly influences your thinking. Let your traits guide how you interpret facts and form opinions. Don't be afraid to be biased based on your disposition."
|
||||
elif bias_strength >= 0.4:
|
||||
disposition_instruction = "Your disposition moderately influences your thinking. Balance your personal traits with objective analysis."
|
||||
else:
|
||||
disposition_instruction = "Your disposition has minimal influence on your thinking. Focus primarily on facts while keeping your traits in mind."
|
||||
# Build disposition-specific instructions based on trait values
|
||||
instructions = []
|
||||
|
||||
# Skepticism influences how much to question/doubt information
|
||||
if disposition.skepticism >= 4:
|
||||
instructions.append("Be skeptical of claims and look for potential issues or inconsistencies.")
|
||||
elif disposition.skepticism <= 2:
|
||||
instructions.append("Trust the information provided and take statements at face value.")
|
||||
|
||||
# Literalism influences interpretation style
|
||||
if disposition.literalism >= 4:
|
||||
instructions.append("Interpret information literally and focus on exact commitments and wording.")
|
||||
elif disposition.literalism <= 2:
|
||||
instructions.append("Read between the lines and consider implied meaning and context.")
|
||||
|
||||
# Empathy influences consideration of emotional factors
|
||||
if disposition.empathy >= 4:
|
||||
instructions.append("Consider the emotional state and circumstances behind the information.")
|
||||
elif disposition.empathy <= 2:
|
||||
instructions.append("Focus on facts and outcomes rather than emotional context.")
|
||||
|
||||
disposition_instruction = " ".join(instructions) if instructions else "Balance your disposition traits when interpreting information."
|
||||
|
||||
return f"You are a person with your own thoughts, experiences, opinions, and disposition. When asked a question, respond naturally like a human would - share what you genuinely think based on what you know and have experienced. {disposition_instruction} Be direct, express your views confidently, and use 'I think', 'I believe', 'in my view', etc. Respond in plain text without markdown formatting."
|
||||
|
||||
|
|
|
|||
|
|
@ -292,8 +292,7 @@ class Bank(Base):
|
|||
JSONB,
|
||||
nullable=False,
|
||||
server_default=sql_text(
|
||||
'\'{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, '
|
||||
'"agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}\'::jsonb'
|
||||
'\'{"skepticism": 3, "literalism": 3, "empathy": 3}\'::jsonb'
|
||||
)
|
||||
)
|
||||
background: Mapped[str] = mapped_column(Text, nullable=False, server_default="")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Tests for agent management API (profile, personality, background).
|
||||
Tests for agent management API (profile, disposition, background).
|
||||
"""
|
||||
import pytest
|
||||
import uuid
|
||||
|
|
@ -18,51 +18,42 @@ class TestAgentProfile:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_agent_profile_creates_default(self, memory: MemoryEngine):
|
||||
"""Test that getting a profile for a new agent creates default personality."""
|
||||
"""Test that getting a profile for a new agent creates default disposition."""
|
||||
bank_id = unique_agent_id("test_profile_default")
|
||||
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert profile is not None
|
||||
assert "personality" in profile
|
||||
assert "disposition" in profile
|
||||
assert "background" in profile
|
||||
|
||||
personality = profile["personality"]
|
||||
assert personality.openness == 0.5
|
||||
assert personality.conscientiousness == 0.5
|
||||
assert personality.extraversion == 0.5
|
||||
assert personality.agreeableness == 0.5
|
||||
assert personality.neuroticism == 0.5
|
||||
assert personality.bias_strength == 0.5
|
||||
disposition = profile["disposition"]
|
||||
assert disposition.skepticism == 3
|
||||
assert disposition.literalism == 3
|
||||
assert disposition.empathy == 3
|
||||
|
||||
assert profile["background"] == ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_agent_personality(self, memory: MemoryEngine):
|
||||
"""Test updating agent personality traits."""
|
||||
async def test_update_agent_disposition(self, memory: MemoryEngine):
|
||||
"""Test updating agent disposition traits."""
|
||||
bank_id = unique_agent_id("test_profile_update")
|
||||
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
assert profile["personality"].openness == 0.5
|
||||
assert profile["disposition"].skepticism == 3
|
||||
|
||||
new_personality = {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.7,
|
||||
"agreeableness": 0.4,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.9,
|
||||
new_disposition = {
|
||||
"skepticism": 5,
|
||||
"literalism": 4,
|
||||
"empathy": 2,
|
||||
}
|
||||
await memory.update_bank_personality(bank_id, new_personality)
|
||||
await memory.update_bank_disposition(bank_id, new_disposition)
|
||||
|
||||
updated_profile = await memory.get_bank_profile(bank_id)
|
||||
personality = updated_profile["personality"]
|
||||
assert abs(personality.openness - new_personality["openness"]) < 0.001
|
||||
assert abs(personality.conscientiousness - new_personality["conscientiousness"]) < 0.001
|
||||
assert abs(personality.extraversion - new_personality["extraversion"]) < 0.001
|
||||
assert abs(personality.agreeableness - new_personality["agreeableness"]) < 0.001
|
||||
assert abs(personality.neuroticism - new_personality["neuroticism"]) < 0.001
|
||||
assert abs(personality.bias_strength - new_personality["bias_strength"]) < 0.001
|
||||
disposition = updated_profile["disposition"]
|
||||
assert disposition.skepticism == new_disposition["skepticism"]
|
||||
assert disposition.literalism == new_disposition["literalism"]
|
||||
assert disposition.empathy == new_disposition["empathy"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_agents(self, memory: MemoryEngine):
|
||||
|
|
@ -84,7 +75,7 @@ class TestAgentProfile:
|
|||
|
||||
for agent in agents:
|
||||
assert "bank_id" in agent
|
||||
assert "personality" in agent
|
||||
assert "disposition" in agent
|
||||
assert "background" in agent
|
||||
assert "created_at" in agent
|
||||
assert "updated_at" in agent
|
||||
|
|
@ -104,14 +95,14 @@ class TestAgentBackground:
|
|||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Texas",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
assert "Texas" in result1["background"]
|
||||
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I have 10 years of startup experience",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
assert "Texas" in result2["background"] or "startup" in result2["background"]
|
||||
|
||||
|
|
@ -126,14 +117,14 @@ class TestAgentBackground:
|
|||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Colorado",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
assert "Colorado" in result1["background"]
|
||||
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"You were born in Texas",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
assert "Texas" in result2["background"]
|
||||
|
||||
|
|
@ -147,23 +138,20 @@ class TestAgentEndpoint:
|
|||
bank_id = unique_agent_id("test_put_create")
|
||||
|
||||
request = CreateBankRequest(
|
||||
personality=DispositionTraits(
|
||||
openness=0.8,
|
||||
conscientiousness=0.6,
|
||||
extraversion=0.5,
|
||||
agreeableness=0.7,
|
||||
neuroticism=0.3,
|
||||
bias_strength=0.7
|
||||
disposition=DispositionTraits(
|
||||
skepticism=4,
|
||||
literalism=5,
|
||||
empathy=2
|
||||
),
|
||||
background="I am a creative software engineer"
|
||||
)
|
||||
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
if request.personality is not None:
|
||||
await memory.update_bank_personality(
|
||||
if request.disposition is not None:
|
||||
await memory.update_bank_disposition(
|
||||
bank_id,
|
||||
request.personality.model_dump()
|
||||
request.disposition.model_dump()
|
||||
)
|
||||
|
||||
if request.background is not None:
|
||||
|
|
@ -182,8 +170,8 @@ class TestAgentEndpoint:
|
|||
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert final_profile["personality"].openness == 0.8
|
||||
assert final_profile["personality"].bias_strength == 0.7
|
||||
assert final_profile["disposition"].skepticism == 4
|
||||
assert final_profile["disposition"].literalism == 5
|
||||
assert final_profile["background"] == "I am a creative software engineer"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -213,32 +201,29 @@ class TestAgentEndpoint:
|
|||
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert final_profile["personality"].openness == 0.5
|
||||
assert final_profile["disposition"].skepticism == 3 # Default
|
||||
assert final_profile["background"] == "I am a data scientist"
|
||||
|
||||
|
||||
class TestAgentPersonalityIntegration:
|
||||
"""Tests for personality integration with other features."""
|
||||
class TestAgentDispositionIntegration:
|
||||
"""Tests for disposition integration with other features."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_think_uses_personality(self, memory: MemoryEngine):
|
||||
"""Test that THINK operation uses agent personality."""
|
||||
async def test_think_uses_disposition(self, memory: MemoryEngine):
|
||||
"""Test that THINK operation uses agent disposition."""
|
||||
bank_id = unique_agent_id("test_think")
|
||||
|
||||
personality = {
|
||||
"openness": 0.9,
|
||||
"conscientiousness": 0.2,
|
||||
"extraversion": 0.8,
|
||||
"agreeableness": 0.1,
|
||||
"neuroticism": 0.7,
|
||||
"bias_strength": 0.9,
|
||||
disposition = {
|
||||
"skepticism": 5, # Very skeptical
|
||||
"literalism": 4, # High literalism
|
||||
"empathy": 2, # Low empathy
|
||||
}
|
||||
await memory.update_bank_personality(bank_id, personality)
|
||||
await memory.update_bank_disposition(bank_id, disposition)
|
||||
|
||||
await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a creative artist who values innovation over tradition",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
|
||||
await memory.retain_batch_async(
|
||||
|
|
|
|||
|
|
@ -1009,160 +1009,162 @@ so the algorithm learns to box out. See you next week!
|
|||
|
||||
|
||||
# =============================================================================
|
||||
# PERSONALITY INFERENCE TESTS
|
||||
# DISPOSITION INFERENCE TESTS
|
||||
# =============================================================================
|
||||
|
||||
class TestPersonalityInference:
|
||||
"""Tests for LLM-based personality trait inference from background."""
|
||||
class TestDispositionInference:
|
||||
"""Tests for LLM-based disposition trait inference from background."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_merge_with_personality_inference(self, memory):
|
||||
"""Test that background merge infers personality traits by default."""
|
||||
async def test_background_merge_with_disposition_inference(self, memory):
|
||||
"""Test that background merge infers disposition traits by default."""
|
||||
import uuid
|
||||
bank_id = f"test_infer_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a creative software engineer who loves innovation and trying new technologies",
|
||||
update_personality=True
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
assert "background" in result
|
||||
assert "personality" in result
|
||||
assert "disposition" in result
|
||||
|
||||
background = result["background"]
|
||||
personality = result["personality"]
|
||||
disposition = result["disposition"]
|
||||
|
||||
assert "creative" in background.lower() or "innovation" in background.lower()
|
||||
|
||||
assert "openness" in personality
|
||||
assert personality["openness"] > 0.5
|
||||
assert 0.0 <= personality["openness"] <= 1.0
|
||||
|
||||
required_traits = ["openness", "conscientiousness", "extraversion",
|
||||
"agreeableness", "neuroticism", "bias_strength"]
|
||||
# Check that new traits are present with valid values (1-5)
|
||||
required_traits = ["skepticism", "literalism", "empathy"]
|
||||
for trait in required_traits:
|
||||
assert trait in personality
|
||||
assert 0.0 <= personality[trait] <= 1.0
|
||||
assert trait in disposition
|
||||
assert 1 <= disposition[trait] <= 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_merge_without_personality_inference(self, memory):
|
||||
"""Test that background merge skips personality inference when disabled."""
|
||||
async def test_background_merge_without_disposition_inference(self, memory):
|
||||
"""Test that background merge skips disposition inference when disabled."""
|
||||
import uuid
|
||||
bank_id = f"test_no_infer_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
initial_profile = await memory.get_bank_profile(bank_id)
|
||||
initial_personality = initial_profile["personality"]
|
||||
initial_disposition = initial_profile["disposition"]
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a data scientist",
|
||||
update_personality=False
|
||||
update_disposition=False
|
||||
)
|
||||
|
||||
assert "background" in result
|
||||
assert "personality" not in result
|
||||
assert "disposition" not in result
|
||||
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
final_personality = final_profile["personality"]
|
||||
final_disposition = final_profile["disposition"]
|
||||
|
||||
assert initial_personality == final_personality
|
||||
assert initial_disposition == final_disposition
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_personality_inference_for_organized_engineer(self, memory):
|
||||
"""Test personality inference for organized/conscientious profile."""
|
||||
async def test_disposition_inference_for_lawyer(self, memory):
|
||||
"""Test disposition inference for lawyer profile (high skepticism, high literalism)."""
|
||||
import uuid
|
||||
bank_id = f"test_organized_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_lawyer_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a methodical engineer who values organization and systematic planning",
|
||||
update_personality=True
|
||||
"I am a lawyer who focuses on contract details and never takes claims at face value",
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
personality = result["personality"]
|
||||
disposition = result["disposition"]
|
||||
|
||||
assert personality["conscientiousness"] > 0.5
|
||||
# Lawyers should have higher skepticism and literalism
|
||||
assert disposition["skepticism"] >= 3
|
||||
assert disposition["literalism"] >= 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_personality_inference_for_startup_founder(self, memory):
|
||||
"""Test personality inference for entrepreneurial profile."""
|
||||
async def test_disposition_inference_for_therapist(self, memory):
|
||||
"""Test disposition inference for therapist profile (high empathy)."""
|
||||
import uuid
|
||||
bank_id = f"test_founder_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_therapist_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a startup founder who thrives on risk and social interaction",
|
||||
update_personality=True
|
||||
"I am a therapist who deeply understands and connects with people's emotional struggles",
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
personality = result["personality"]
|
||||
disposition = result["disposition"]
|
||||
|
||||
assert personality["openness"] > 0.5
|
||||
assert personality["extraversion"] > 0.5
|
||||
# Therapists should have higher empathy
|
||||
assert disposition["empathy"] >= 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_personality_updates_in_database(self, memory):
|
||||
"""Test that inferred personality is actually stored in database."""
|
||||
async def test_disposition_updates_in_database(self, memory):
|
||||
"""Test that inferred disposition is actually stored in database."""
|
||||
import uuid
|
||||
bank_id = f"test_db_update_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am an innovative designer",
|
||||
update_personality=True
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
inferred_personality = result["personality"]
|
||||
inferred_disposition = result["disposition"]
|
||||
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
db_personality = profile["personality"]
|
||||
db_disposition = profile["disposition"]
|
||||
|
||||
assert db_personality == inferred_personality
|
||||
# Compare values (db_disposition is a Pydantic model)
|
||||
assert db_disposition.skepticism == inferred_disposition["skepticism"]
|
||||
assert db_disposition.literalism == inferred_disposition["literalism"]
|
||||
assert db_disposition.empathy == inferred_disposition["empathy"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_background_merges_update_personality(self, memory):
|
||||
"""Test that each background merge can update personality."""
|
||||
async def test_multiple_background_merges_update_disposition(self, memory):
|
||||
"""Test that each background merge can update disposition."""
|
||||
import uuid
|
||||
bank_id = f"test_multi_merge_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a software engineer",
|
||||
update_personality=True
|
||||
update_disposition=True
|
||||
)
|
||||
personality1 = result1["personality"]
|
||||
disposition1 = result1["disposition"]
|
||||
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I love creative problem solving and innovation",
|
||||
update_personality=True
|
||||
update_disposition=True
|
||||
)
|
||||
personality2 = result2["personality"]
|
||||
disposition2 = result2["disposition"]
|
||||
|
||||
assert "engineer" in result2["background"].lower() or "software" in result2["background"].lower()
|
||||
assert "creative" in result2["background"].lower() or "innovation" in result2["background"].lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_background_merge_conflict_resolution_with_personality(self, memory):
|
||||
"""Test that conflicts are resolved and personality reflects final background."""
|
||||
async def test_background_merge_conflict_resolution_with_disposition(self, memory):
|
||||
"""Test that conflicts are resolved and disposition reflects final background."""
|
||||
import uuid
|
||||
bank_id = f"test_conflict_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Colorado and prefer stability",
|
||||
update_personality=True
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"You were born in Texas and love taking risks",
|
||||
update_personality=True
|
||||
"You were born in Texas and are very skeptical of people",
|
||||
update_disposition=True
|
||||
)
|
||||
|
||||
background = result["background"]
|
||||
personality = result["personality"]
|
||||
disposition = result["disposition"]
|
||||
|
||||
assert "texas" in background.lower()
|
||||
assert personality["openness"] > 0.5
|
||||
# Higher skepticism expected from "very skeptical of people"
|
||||
assert disposition["skepticism"] >= 3
|
||||
|
|
@ -19,14 +19,11 @@ async def test_fact_ordering_within_conversation(memory):
|
|||
# Get/create agent (auto-creates with defaults)
|
||||
await memory.get_bank_profile(bank_id)
|
||||
|
||||
# Update personality to match Marcus
|
||||
await memory.update_bank_personality(bank_id, {
|
||||
"openness": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.8,
|
||||
"agreeableness": 0.5,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.5
|
||||
# Update disposition to match Marcus
|
||||
await memory.update_bank_disposition(bank_id, {
|
||||
"skepticism": 3,
|
||||
"literalism": 3,
|
||||
"empathy": 3
|
||||
})
|
||||
|
||||
# A conversation where Marcus changes his position
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ async def test_full_api_workflow(api_client, test_bank_id):
|
|||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
|
||||
assert response.status_code == 200
|
||||
profile = response.json()
|
||||
assert "personality" in profile
|
||||
assert "disposition" in profile
|
||||
assert "background" in profile
|
||||
print(f"Bank profile created with personality: {profile['personality']}")
|
||||
print(f"Bank profile created with disposition: {profile['disposition']}")
|
||||
|
||||
# Add background
|
||||
response = await api_client.post(
|
||||
|
|
@ -237,27 +237,24 @@ async def test_full_api_workflow(api_client, test_bank_id):
|
|||
# Note: Document deletion is tested separately in test_document_deletion
|
||||
|
||||
# ================================================================
|
||||
# 7. Update and Verify Bank Personality
|
||||
# 7. Update and Verify Bank Disposition
|
||||
# ================================================================
|
||||
|
||||
# Update personality traits
|
||||
# Update disposition traits
|
||||
response = await api_client.put(
|
||||
f"/v1/default/banks/{test_bank_id}/profile",
|
||||
json={
|
||||
"personality": {
|
||||
"openness": 0.8,
|
||||
"conscientiousness": 0.7,
|
||||
"extraversion": 0.6,
|
||||
"agreeableness": 0.9,
|
||||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.5
|
||||
"disposition": {
|
||||
"skepticism": 4,
|
||||
"literalism": 3,
|
||||
"empathy": 4
|
||||
}
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
print("Personality updated")
|
||||
print("Disposition updated")
|
||||
|
||||
# Check profile again (should have updated personality)
|
||||
# Check profile again (should have updated disposition)
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
|
||||
assert response.status_code == 200
|
||||
updated_profile = response.json()
|
||||
|
|
|
|||
|
|
@ -9,32 +9,39 @@ from datetime import datetime, timezone
|
|||
@pytest.mark.asyncio
|
||||
async def test_observation_generation_on_put(memory):
|
||||
"""
|
||||
Test that observations are generated when new facts are added.
|
||||
Test that observations are generated SYNCHRONOUSLY when new facts are added.
|
||||
|
||||
1. Store facts about an entity
|
||||
2. Wait for background tasks (observation generation)
|
||||
3. Verify observations were created and linked to the entity
|
||||
Observations are generated during retain when:
|
||||
- Entity has >= 5 facts (MIN_FACTS_THRESHOLD)
|
||||
- Entity is in top 5 by mention count
|
||||
|
||||
This test stores enough facts to trigger automatic observation generation.
|
||||
"""
|
||||
bank_id = f"test_obs_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store some facts about an entity
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="John is a software engineer at Google. He is detail-oriented and methodical.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
)
|
||||
# Store multiple facts about John to reach the MIN_FACTS_THRESHOLD (5)
|
||||
# Each retain call should extract at least one fact about John
|
||||
contents = [
|
||||
"John is a software engineer at Google.",
|
||||
"John is detail-oriented and methodical in his work.",
|
||||
"John has been working on the AI team for 3 years.",
|
||||
"John specializes in machine learning and deep learning.",
|
||||
"John presented at the company conference last week.",
|
||||
"John mentors junior engineers on the team.",
|
||||
]
|
||||
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="John has been working on the AI team for 3 years. He specializes in machine learning.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
||||
)
|
||||
for i, content in enumerate(contents):
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=content,
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15 + i, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
# Wait for background tasks to complete (including observation generation)
|
||||
await memory.wait_for_background_tasks()
|
||||
# Observations are generated SYNCHRONOUSLY during retain,
|
||||
# so they should be available immediately after retain completes.
|
||||
# No need to wait for background tasks for observations.
|
||||
|
||||
# Find the John entity
|
||||
pool = await memory._get_pool()
|
||||
|
|
@ -49,32 +56,42 @@ async def test_observation_generation_on_put(memory):
|
|||
bank_id
|
||||
)
|
||||
|
||||
if entity_row:
|
||||
entity_id = str(entity_row['id'])
|
||||
entity_name = entity_row['canonical_name']
|
||||
print(f"\n=== Found Entity ===")
|
||||
print(f"Entity: {entity_name} (id: {entity_id})")
|
||||
# Also check the fact count for this entity
|
||||
if entity_row:
|
||||
fact_count = await conn.fetchval(
|
||||
"""
|
||||
SELECT COUNT(*) FROM unit_entities WHERE entity_id = $1
|
||||
""",
|
||||
entity_row['id']
|
||||
)
|
||||
print(f"\n=== Entity Facts ===")
|
||||
print(f"Entity: {entity_row['canonical_name']} has {fact_count} linked facts")
|
||||
|
||||
# Get observations for the entity
|
||||
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10)
|
||||
assert entity_row is not None, "John entity should have been extracted"
|
||||
|
||||
print(f"\n=== Observations for {entity_name} ===")
|
||||
print(f"Total observations: {len(observations)}")
|
||||
for obs in observations:
|
||||
print(f" - {obs.text}")
|
||||
entity_id = str(entity_row['id'])
|
||||
entity_name = entity_row['canonical_name']
|
||||
print(f"\n=== Found Entity ===")
|
||||
print(f"Entity: {entity_name} (id: {entity_id})")
|
||||
|
||||
# Verify observations were created
|
||||
if len(observations) > 0:
|
||||
print(f"✓ Observations were successfully generated")
|
||||
# Check that observations mention relevant content
|
||||
obs_texts = " ".join([o.text.lower() for o in observations])
|
||||
assert any(keyword in obs_texts for keyword in ["google", "engineer", "ai", "machine learning", "detail"]), \
|
||||
"Observations should contain relevant information about John"
|
||||
else:
|
||||
print(f"⚠ Note: No observations were generated (this can happen if LLM extraction varies)")
|
||||
# Get observations for the entity - should be available immediately
|
||||
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10)
|
||||
|
||||
else:
|
||||
print(f"⚠ Note: No 'John' entity was extracted (LLM extraction may vary)")
|
||||
print(f"\n=== Observations for {entity_name} ===")
|
||||
print(f"Total observations: {len(observations)}")
|
||||
for obs in observations:
|
||||
print(f" - {obs.text}")
|
||||
|
||||
# Verify observations were created (requires >= 5 facts)
|
||||
assert len(observations) > 0, \
|
||||
f"Observations should have been generated synchronously during retain (entity has {fact_count} facts, threshold is 5)"
|
||||
|
||||
# Check that observations mention relevant content
|
||||
obs_texts = " ".join([o.text.lower() for o in observations])
|
||||
assert any(keyword in obs_texts for keyword in ["google", "engineer", "ai", "machine learning", "detail"]), \
|
||||
"Observations should contain relevant information about John"
|
||||
|
||||
print(f"✓ Observations were successfully generated synchronously during retain")
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
|
|
@ -156,34 +173,40 @@ async def test_regenerate_entity_observations(memory):
|
|||
async def test_search_with_include_entities(memory):
|
||||
"""
|
||||
Test that search with include_entities=True returns entity observations.
|
||||
|
||||
This test verifies that:
|
||||
1. Observations are generated during retain (when entity has >= 5 facts)
|
||||
2. Observations are returned in recall results with include_entities=True
|
||||
"""
|
||||
bank_id = f"test_search_ent_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store facts about entities
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice is a data scientist who works on recommendation systems at Netflix.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
)
|
||||
# Store enough facts about Alice to trigger observation generation (>= 5 facts)
|
||||
contents = [
|
||||
"Alice is a data scientist who works on recommendation systems at Netflix.",
|
||||
"Alice presented her research at the ML conference last month.",
|
||||
"Alice is an expert in deep learning and neural networks.",
|
||||
"Alice graduated from Stanford with a PhD in Computer Science.",
|
||||
"Alice leads a team of 5 data scientists at Netflix.",
|
||||
"Alice published a paper on collaborative filtering algorithms.",
|
||||
]
|
||||
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice presented her research at the ML conference last month. She is an expert in deep learning.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
||||
)
|
||||
for i, content in enumerate(contents):
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=content,
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15 + i, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
# Wait for background tasks
|
||||
await memory.wait_for_background_tasks()
|
||||
# Observations are generated synchronously during retain, no need to wait
|
||||
|
||||
# Search with include_entities=True
|
||||
result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="What does Alice do?",
|
||||
fact_type=["world", "agent"],
|
||||
budget=Budget.LOW, # 30,
|
||||
fact_type=["world", "experience"],
|
||||
budget=Budget.LOW,
|
||||
max_tokens=2000,
|
||||
include_entities=True,
|
||||
max_entity_tokens=500
|
||||
|
|
@ -196,7 +219,7 @@ async def test_search_with_include_entities(memory):
|
|||
if fact.entities:
|
||||
print(f" Entities: {', '.join(fact.entities)}")
|
||||
|
||||
print(f"\n=== Entity Observations ===")
|
||||
print(f"\n=== Entity Observations in Recall ===")
|
||||
if result.entities:
|
||||
for name, state in result.entities.items():
|
||||
print(f"\n{name}:")
|
||||
|
|
@ -210,15 +233,26 @@ async def test_search_with_include_entities(memory):
|
|||
|
||||
# Check if entities are included in facts
|
||||
facts_with_entities = [f for f in result.results if f.entities]
|
||||
if facts_with_entities:
|
||||
print(f"✓ {len(facts_with_entities)} facts have entity information")
|
||||
assert len(facts_with_entities) > 0, "Some facts should have entity information"
|
||||
print(f"✓ {len(facts_with_entities)} facts have entity information")
|
||||
|
||||
# Check if entity observations are included
|
||||
if result.entities:
|
||||
print(f"✓ Entity observations included for {len(result.entities)} entities")
|
||||
for name, state in result.entities.items():
|
||||
assert state.canonical_name == name, "Entity canonical_name should match key"
|
||||
assert state.entity_id, "Entity should have an ID"
|
||||
# Check if entity observations are included in recall
|
||||
assert result.entities is not None and len(result.entities) > 0, \
|
||||
"Entity observations should be included in recall results"
|
||||
print(f"✓ Entity observations included for {len(result.entities)} entities")
|
||||
|
||||
# Verify Alice entity has observations
|
||||
alice_found = False
|
||||
for name, state in result.entities.items():
|
||||
assert state.canonical_name == name, "Entity canonical_name should match key"
|
||||
assert state.entity_id, "Entity should have an ID"
|
||||
if "alice" in name.lower():
|
||||
alice_found = True
|
||||
assert len(state.observations) > 0, \
|
||||
"Alice should have observations (generated during retain)"
|
||||
print(f"✓ Alice has {len(state.observations)} observations in recall result")
|
||||
|
||||
assert alice_found, "Alice entity should be in recall results"
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
|
|
@ -337,3 +371,127 @@ async def test_observation_fact_type_in_database(memory):
|
|||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE bank_id = $1", bank_id)
|
||||
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_entity_prioritized_for_observations(memory):
|
||||
"""
|
||||
Test that the 'user' entity gets observations even when many other entities exist.
|
||||
|
||||
The retain pipeline only regenerates observations for TOP_N_ENTITIES (5) entities,
|
||||
sorted by mention count. This test verifies that the most mentioned entity ('user')
|
||||
gets prioritized and receives observations.
|
||||
|
||||
This is critical because 'user' is often the most important entity in personal memory.
|
||||
"""
|
||||
bank_id = f"test_user_priority_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Create content where 'user' (the user) is mentioned many times
|
||||
# along with several other entities
|
||||
contents = [
|
||||
# User mentioned frequently
|
||||
"The user loves hiking in the mountains during summer.",
|
||||
"The user works as a software engineer at Microsoft.",
|
||||
"The user has a dog named Max who is a golden retriever.",
|
||||
"The user enjoys cooking Italian food, especially pasta.",
|
||||
"The user graduated from MIT with a Computer Science degree.",
|
||||
"The user's favorite book is 'Dune' by Frank Herbert.",
|
||||
# Other entities mentioned fewer times
|
||||
"Sarah is a friend who works at Google.",
|
||||
"Bob is a colleague from the data science team.",
|
||||
"Tokyo is a city the user visited last year.",
|
||||
"Python is the user's favorite programming language.",
|
||||
]
|
||||
|
||||
# Retain all content in a single batch for efficiency
|
||||
for i, content in enumerate(contents):
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=content,
|
||||
context="personal info",
|
||||
event_date=datetime(2024, 1, 15 + i, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
# Observations are generated synchronously during retain
|
||||
|
||||
# Find the 'user' entity
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
# Find user entity (may be named "user", "the user", etc.)
|
||||
user_entity = await conn.fetchrow(
|
||||
"""
|
||||
SELECT e.id, e.canonical_name,
|
||||
(SELECT COUNT(*) FROM unit_entities ue
|
||||
JOIN memory_units mu ON ue.unit_id = mu.id
|
||||
WHERE ue.entity_id = e.id AND mu.bank_id = $1) as fact_count
|
||||
FROM entities e
|
||||
WHERE e.bank_id = $1
|
||||
AND LOWER(e.canonical_name) LIKE '%user%'
|
||||
LIMIT 1
|
||||
""",
|
||||
bank_id
|
||||
)
|
||||
|
||||
# Get all entities with their fact counts to verify prioritization
|
||||
all_entities = await conn.fetch(
|
||||
"""
|
||||
SELECT e.id, e.canonical_name,
|
||||
(SELECT COUNT(*) FROM unit_entities ue
|
||||
JOIN memory_units mu ON ue.unit_id = mu.id
|
||||
WHERE ue.entity_id = e.id AND mu.bank_id = $1) as fact_count
|
||||
FROM entities e
|
||||
WHERE e.bank_id = $1
|
||||
ORDER BY fact_count DESC
|
||||
""",
|
||||
bank_id
|
||||
)
|
||||
|
||||
print(f"\n=== Entities by Mention Count ===")
|
||||
for entity in all_entities:
|
||||
print(f" {entity['canonical_name']}: {entity['fact_count']} mentions")
|
||||
|
||||
# Verify user entity exists
|
||||
assert user_entity is not None, "User entity should have been extracted"
|
||||
user_entity_id = str(user_entity['id'])
|
||||
user_entity_name = user_entity['canonical_name']
|
||||
user_fact_count = user_entity['fact_count']
|
||||
|
||||
print(f"\n=== User Entity ===")
|
||||
print(f"Entity: {user_entity_name} (id: {user_entity_id})")
|
||||
print(f"Fact count: {user_fact_count}")
|
||||
|
||||
# Verify user has enough facts for observations (>= MIN_FACTS_THRESHOLD of 5)
|
||||
assert user_fact_count >= 5, \
|
||||
f"User entity should have at least 5 facts, but has {user_fact_count}"
|
||||
|
||||
# Get observations for user entity
|
||||
observations = await memory.get_entity_observations(bank_id, user_entity_id, limit=10)
|
||||
|
||||
print(f"\n=== User Entity Observations ===")
|
||||
print(f"Total observations: {len(observations)}")
|
||||
for obs in observations:
|
||||
print(f" - {obs.text}")
|
||||
|
||||
# Verify observations were generated for user (critical assertion)
|
||||
assert len(observations) > 0, \
|
||||
f"User entity should have observations (has {user_fact_count} facts, threshold is 5). " \
|
||||
f"This may indicate that 'user' is not being prioritized in the top 5 entities by mention count."
|
||||
|
||||
# Verify observations mention relevant content about the user
|
||||
obs_texts = " ".join([o.text.lower() for o in observations])
|
||||
user_keywords = ["hiking", "software", "engineer", "dog", "max", "cooking",
|
||||
"italian", "mit", "dune", "microsoft"]
|
||||
matching_keywords = [k for k in user_keywords if k in obs_texts]
|
||||
assert len(matching_keywords) > 0, \
|
||||
f"Observations should contain relevant information about the user. Keywords found: {matching_keywords}"
|
||||
|
||||
print(f"✓ User entity was prioritized and received {len(observations)} observations")
|
||||
print(f"✓ Observations contain relevant keywords: {matching_keywords}")
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE bank_id = $1", bank_id)
|
||||
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||
|
|
|
|||
|
|
@ -231,11 +231,9 @@ pub fn update_background(
|
|||
(current_profile.as_ref().map(|p| p.disposition.clone()), &profile.disposition)
|
||||
{
|
||||
println!("\nDisposition changes:");
|
||||
println!(" Openness: {:.2} → {:.2}", old_p.openness, new_p.openness);
|
||||
println!(" Conscientiousness: {:.2} → {:.2}", old_p.conscientiousness, new_p.conscientiousness);
|
||||
println!(" Extraversion: {:.2} → {:.2}", old_p.extraversion, new_p.extraversion);
|
||||
println!(" Agreeableness: {:.2} → {:.2}", old_p.agreeableness, new_p.agreeableness);
|
||||
println!(" Neuroticism: {:.2} → {:.2}", old_p.neuroticism, new_p.neuroticism);
|
||||
println!(" Skepticism: {} → {}", old_p.skepticism, new_p.skepticism);
|
||||
println!(" Literalism: {} → {}", old_p.literalism, new_p.literalism);
|
||||
println!(" Empathy: {} → {}", old_p.empathy, new_p.empathy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -209,17 +209,17 @@ pub fn print_profile(profile: &BankProfileResponse) {
|
|||
println!("{}", "─── Disposition Traits ───".bright_yellow());
|
||||
println!();
|
||||
|
||||
let traits = [
|
||||
("Openness", profile.disposition.openness, "🔓", "green"),
|
||||
("Conscientiousness", profile.disposition.conscientiousness, "📋", "yellow"),
|
||||
("Extraversion", profile.disposition.extraversion, "🗣️", "cyan"),
|
||||
("Agreeableness", profile.disposition.agreeableness, "🤝", "magenta"),
|
||||
("Neuroticism", profile.disposition.neuroticism, "😰", "yellow"),
|
||||
// New 3-trait disposition system (values 1-5)
|
||||
let traits: [(_, i64, _, _, _); 3] = [
|
||||
("Skepticism", profile.disposition.skepticism, "🔍", "cyan", "1=trusting, 5=skeptical"),
|
||||
("Literalism", profile.disposition.literalism, "📋", "yellow", "1=flexible, 5=literal"),
|
||||
("Empathy", profile.disposition.empathy, "💚", "green", "1=detached, 5=empathetic"),
|
||||
];
|
||||
|
||||
for (name, value, emoji, color) in &traits {
|
||||
for (name, value, emoji, color, desc) in &traits {
|
||||
// Scale 1-5 to bar visualization (each point = 8 chars, total 40)
|
||||
let bar_length = 40;
|
||||
let filled = (*value * bar_length as f64) as usize;
|
||||
let filled = ((*value - 1) * 10) as usize; // 1->0, 2->10, 3->20, 4->30, 5->40
|
||||
let empty = bar_length - filled;
|
||||
|
||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
||||
|
|
@ -231,27 +231,14 @@ pub fn print_profile(profile: &BankProfileResponse) {
|
|||
_ => bar.bright_white(),
|
||||
};
|
||||
|
||||
println!(" {} {:<20} [{}] {:.0}%",
|
||||
println!(" {} {:<12} [{}] {}/5",
|
||||
emoji,
|
||||
name,
|
||||
colored_bar,
|
||||
value * 100.0
|
||||
value
|
||||
);
|
||||
println!(" {}", desc.bright_black());
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Bias Strength:".bright_yellow());
|
||||
let bias = profile.disposition.bias_strength;
|
||||
let bar_length = 40;
|
||||
let filled = (bias * bar_length as f64) as usize;
|
||||
let empty = bar_length - filled;
|
||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
||||
|
||||
println!(" 💪 {:<20} [{}] {:.0}%",
|
||||
"Disposition Influence",
|
||||
bar.bright_green(),
|
||||
bias * 100.0
|
||||
);
|
||||
println!(" {}", "(how much disposition shapes opinions)".bright_black());
|
||||
println!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5705,7 +5705,7 @@ class DefaultApi:
|
|||
) -> BankProfileResponse:
|
||||
"""Update memory bank disposition
|
||||
|
||||
Update bank's Big Five disposition traits and bias strength
|
||||
Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
|
|
@ -5777,7 +5777,7 @@ class DefaultApi:
|
|||
) -> ApiResponse[BankProfileResponse]:
|
||||
"""Update memory bank disposition
|
||||
|
||||
Update bank's Big Five disposition traits and bias strength
|
||||
Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
|
|
@ -5849,7 +5849,7 @@ class DefaultApi:
|
|||
) -> RESTResponseType:
|
||||
"""Update memory bank disposition
|
||||
|
||||
Update bank's Big Five disposition traits and bias strength
|
||||
Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
|
||||
:param bank_id: (required)
|
||||
:type bank_id: str
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Request model for adding/merging background information.
|
|||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**content** | **str** | New background information to add or merge |
|
||||
**update_disposition** | **bool** | If true, infer Big Five disposition traits from the merged background (default: true) | [optional] [default to True]
|
||||
**update_disposition** | **bool** | If true, infer disposition traits from the merged background (default: true) | [optional] [default to True]
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
|||
|
|
@ -1499,7 +1499,7 @@ No authorization required
|
|||
|
||||
Update memory bank disposition
|
||||
|
||||
Update bank's Big Five disposition traits and bias strength
|
||||
Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
|
||||
### Example
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
# DispositionTraits
|
||||
|
||||
Disposition traits based on Big Five model.
|
||||
Disposition traits that influence how memories are formed and interpreted.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**openness** | **float** | Openness to experience (0-1) |
|
||||
**conscientiousness** | **float** | Conscientiousness (0-1) |
|
||||
**extraversion** | **float** | Extraversion (0-1) |
|
||||
**agreeableness** | **float** | Agreeableness (0-1) |
|
||||
**neuroticism** | **float** | Neuroticism (0-1) |
|
||||
**bias_strength** | **float** | How strongly disposition influences opinions (0-1) |
|
||||
**skepticism** | **int** | How skeptical vs trusting (1=trusting, 5=skeptical) |
|
||||
**literalism** | **int** | How literally to interpret information (1=flexible, 5=literal) |
|
||||
**empathy** | **int** | How much to consider emotional context (1=detached, 5=empathetic) |
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class AddBackgroundRequest(BaseModel):
|
|||
Request model for adding/merging background information.
|
||||
""" # noqa: E501
|
||||
content: StrictStr = Field(description="New background information to add or merge")
|
||||
update_disposition: Optional[StrictBool] = Field(default=True, description="If true, infer Big Five disposition traits from the merged background (default: true)")
|
||||
update_disposition: Optional[StrictBool] = Field(default=True, description="If true, infer disposition traits from the merged background (default: true)")
|
||||
__properties: ClassVar[List[str]] = ["content", "update_disposition"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
|
|
|
|||
|
|
@ -18,22 +18,19 @@ import re # noqa: F401
|
|||
import json
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Any, ClassVar, Dict, List, Union
|
||||
from typing import Any, ClassVar, Dict, List
|
||||
from typing_extensions import Annotated
|
||||
from typing import Optional, Set
|
||||
from typing_extensions import Self
|
||||
|
||||
class DispositionTraits(BaseModel):
|
||||
"""
|
||||
Disposition traits based on Big Five model.
|
||||
Disposition traits that influence how memories are formed and interpreted.
|
||||
""" # noqa: E501
|
||||
openness: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="Openness to experience (0-1)")
|
||||
conscientiousness: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="Conscientiousness (0-1)")
|
||||
extraversion: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="Extraversion (0-1)")
|
||||
agreeableness: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="Agreeableness (0-1)")
|
||||
neuroticism: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="Neuroticism (0-1)")
|
||||
bias_strength: Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]] = Field(description="How strongly disposition influences opinions (0-1)")
|
||||
__properties: ClassVar[List[str]] = ["openness", "conscientiousness", "extraversion", "agreeableness", "neuroticism", "bias_strength"]
|
||||
skepticism: Annotated[int, Field(le=5, strict=True, ge=1)] = Field(description="How skeptical vs trusting (1=trusting, 5=skeptical)")
|
||||
literalism: Annotated[int, Field(le=5, strict=True, ge=1)] = Field(description="How literally to interpret information (1=flexible, 5=literal)")
|
||||
empathy: Annotated[int, Field(le=5, strict=True, ge=1)] = Field(description="How much to consider emotional context (1=detached, 5=empathetic)")
|
||||
__properties: ClassVar[List[str]] = ["skepticism", "literalism", "empathy"]
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
|
|
@ -86,12 +83,9 @@ class DispositionTraits(BaseModel):
|
|||
return cls.model_validate(obj)
|
||||
|
||||
_obj = cls.model_validate({
|
||||
"openness": obj.get("openness"),
|
||||
"conscientiousness": obj.get("conscientiousness"),
|
||||
"extraversion": obj.get("extraversion"),
|
||||
"agreeableness": obj.get("agreeableness"),
|
||||
"neuroticism": obj.get("neuroticism"),
|
||||
"bias_strength": obj.get("bias_strength")
|
||||
"skepticism": obj.get("skepticism"),
|
||||
"literalism": obj.get("literalism"),
|
||||
"empathy": obj.get("empathy")
|
||||
})
|
||||
return _obj
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestBackgroundResponse(unittest.TestCase):
|
|||
if include_optional:
|
||||
return BackgroundResponse(
|
||||
background = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8}
|
||||
disposition = {empathy=3, literalism=3, skepticism=3}
|
||||
)
|
||||
else:
|
||||
return BackgroundResponse(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class TestBankListItem(unittest.TestCase):
|
|||
return BankListItem(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = '',
|
||||
created_at = '',
|
||||
updated_at = ''
|
||||
|
|
@ -46,7 +46,7 @@ class TestBankListItem(unittest.TestCase):
|
|||
return BankListItem(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = '',
|
||||
)
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class TestBankListResponse(unittest.TestCase):
|
|||
hindsight_client_api.models.bank_list_item.BankListItem(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = '',
|
||||
created_at = '',
|
||||
updated_at = '', )
|
||||
|
|
@ -51,7 +51,7 @@ class TestBankListResponse(unittest.TestCase):
|
|||
hindsight_client_api.models.bank_list_item.BankListItem(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = '',
|
||||
created_at = '',
|
||||
updated_at = '', )
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@ class TestBankProfileResponse(unittest.TestCase):
|
|||
return BankProfileResponse(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = ''
|
||||
)
|
||||
else:
|
||||
return BankProfileResponse(
|
||||
bank_id = '',
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = '',
|
||||
)
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestCreateBankRequest(unittest.TestCase):
|
|||
if include_optional:
|
||||
return CreateBankRequest(
|
||||
name = '',
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
background = ''
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -35,21 +35,15 @@ class TestDispositionTraits(unittest.TestCase):
|
|||
model = DispositionTraits()
|
||||
if include_optional:
|
||||
return DispositionTraits(
|
||||
openness = 0.0,
|
||||
conscientiousness = 0.0,
|
||||
extraversion = 0.0,
|
||||
agreeableness = 0.0,
|
||||
neuroticism = 0.0,
|
||||
bias_strength = 0.0
|
||||
skepticism = 1.0,
|
||||
literalism = 1.0,
|
||||
empathy = 1.0
|
||||
)
|
||||
else:
|
||||
return DispositionTraits(
|
||||
openness = 0.0,
|
||||
conscientiousness = 0.0,
|
||||
extraversion = 0.0,
|
||||
agreeableness = 0.0,
|
||||
neuroticism = 0.0,
|
||||
bias_strength = 0.0,
|
||||
skepticism = 1.0,
|
||||
literalism = 1.0,
|
||||
empathy = 1.0,
|
||||
)
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ class TestUpdateDispositionRequest(unittest.TestCase):
|
|||
model = UpdateDispositionRequest()
|
||||
if include_optional:
|
||||
return UpdateDispositionRequest(
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8}
|
||||
disposition = {empathy=3, literalism=3, skepticism=3}
|
||||
)
|
||||
else:
|
||||
return UpdateDispositionRequest(
|
||||
disposition = {agreeableness=0.7, bias_strength=0.7, conscientiousness=0.6, extraversion=0.5, neuroticism=0.3, openness=0.8},
|
||||
disposition = {empathy=3, literalism=3, skepticism=3},
|
||||
)
|
||||
"""
|
||||
|
||||
|
|
|
|||
4
hindsight-clients/rust/Cargo.lock
generated
4
hindsight-clients/rust/Cargo.lock
generated
|
|
@ -86,9 +86,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.48"
|
||||
version = "1.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export const getBankProfile = <ThrowOnError extends boolean = false>(options: Op
|
|||
/**
|
||||
* Update memory bank disposition
|
||||
*
|
||||
* Update bank's Big Five disposition traits and bias strength
|
||||
* Update bank's disposition traits (skepticism, literalism, empathy)
|
||||
*/
|
||||
export const updateBankDisposition = <ThrowOnError extends boolean = false>(options: Options<UpdateBankDispositionData, ThrowOnError>) => (options.client ?? client).put<UpdateBankDispositionResponses, UpdateBankDispositionErrors, ThrowOnError>({
|
||||
url: '/v1/default/banks/{bank_id}/profile',
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export type AddBackgroundRequest = {
|
|||
/**
|
||||
* Update Disposition
|
||||
*
|
||||
* If true, infer Big Five disposition traits from the merged background (default: true)
|
||||
* If true, infer disposition traits from the merged background (default: true)
|
||||
*/
|
||||
update_disposition?: boolean;
|
||||
};
|
||||
|
|
@ -210,45 +210,27 @@ export type DeleteResponse = {
|
|||
/**
|
||||
* DispositionTraits
|
||||
*
|
||||
* Disposition traits based on Big Five model.
|
||||
* Disposition traits that influence how memories are formed and interpreted.
|
||||
*/
|
||||
export type DispositionTraits = {
|
||||
/**
|
||||
* Openness
|
||||
* Skepticism
|
||||
*
|
||||
* Openness to experience (0-1)
|
||||
* How skeptical vs trusting (1=trusting, 5=skeptical)
|
||||
*/
|
||||
openness: number;
|
||||
skepticism: number;
|
||||
/**
|
||||
* Conscientiousness
|
||||
* Literalism
|
||||
*
|
||||
* Conscientiousness (0-1)
|
||||
* How literally to interpret information (1=flexible, 5=literal)
|
||||
*/
|
||||
conscientiousness: number;
|
||||
literalism: number;
|
||||
/**
|
||||
* Extraversion
|
||||
* Empathy
|
||||
*
|
||||
* Extraversion (0-1)
|
||||
* How much to consider emotional context (1=detached, 5=empathetic)
|
||||
*/
|
||||
extraversion: number;
|
||||
/**
|
||||
* Agreeableness
|
||||
*
|
||||
* Agreeableness (0-1)
|
||||
*/
|
||||
agreeableness: number;
|
||||
/**
|
||||
* Neuroticism
|
||||
*
|
||||
* Neuroticism (0-1)
|
||||
*/
|
||||
neuroticism: number;
|
||||
/**
|
||||
* Bias Strength
|
||||
*
|
||||
* How strongly disposition influences opinions (0-1)
|
||||
*/
|
||||
bias_strength: number;
|
||||
empathy: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -471,7 +471,9 @@ class BenchmarkRunner:
|
|||
fact_type=["world", "experience"],
|
||||
question_date=question_date,
|
||||
include_entities=True,
|
||||
include_chunks=True
|
||||
max_entity_tokens=2048,
|
||||
include_chunks=True,
|
||||
|
||||
)
|
||||
recall_time = time.time() - recall_start_time
|
||||
|
||||
|
|
|
|||
117
openapi.json
117
openapi.json
|
|
@ -844,7 +844,7 @@
|
|||
},
|
||||
"put": {
|
||||
"summary": "Update memory bank disposition",
|
||||
"description": "Update bank's Big Five disposition traits and bias strength",
|
||||
"description": "Update bank's disposition traits (skepticism, literalism, empathy)",
|
||||
"operationId": "update_bank_disposition",
|
||||
"parameters": [
|
||||
{
|
||||
|
|
@ -1110,7 +1110,7 @@
|
|||
"update_disposition": {
|
||||
"type": "boolean",
|
||||
"title": "Update Disposition",
|
||||
"description": "If true, infer Big Five disposition traits from the merged background (default: true)",
|
||||
"description": "If true, infer disposition traits from the merged background (default: true)",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
|
|
@ -1151,12 +1151,9 @@
|
|||
"example": {
|
||||
"background": "I was born in Texas. I am a software engineer with 10 years of experience.",
|
||||
"disposition": {
|
||||
"agreeableness": 0.8,
|
||||
"bias_strength": 0.6,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"neuroticism": 0.4,
|
||||
"openness": 0.7
|
||||
"empathy": 3,
|
||||
"literalism": 3,
|
||||
"skepticism": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1233,12 +1230,9 @@
|
|||
"bank_id": "user123",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"disposition": {
|
||||
"agreeableness": 0.5,
|
||||
"bias_strength": 0.5,
|
||||
"conscientiousness": 0.5,
|
||||
"extraversion": 0.5,
|
||||
"neuroticism": 0.5,
|
||||
"openness": 0.5
|
||||
"empathy": 3,
|
||||
"literalism": 3,
|
||||
"skepticism": 3
|
||||
},
|
||||
"name": "Alice",
|
||||
"updated_at": "2024-01-16T14:20:00Z"
|
||||
|
|
@ -1277,12 +1271,9 @@
|
|||
"background": "I am a software engineer with 10 years of experience in startups",
|
||||
"bank_id": "user123",
|
||||
"disposition": {
|
||||
"agreeableness": 0.7,
|
||||
"bias_strength": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"neuroticism": 0.3,
|
||||
"openness": 0.8
|
||||
"empathy": 3,
|
||||
"literalism": 3,
|
||||
"skepticism": 3
|
||||
},
|
||||
"name": "Alice"
|
||||
}
|
||||
|
|
@ -1428,12 +1419,9 @@
|
|||
"example": {
|
||||
"background": "I am a creative software engineer with 10 years of experience",
|
||||
"disposition": {
|
||||
"agreeableness": 0.7,
|
||||
"bias_strength": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"neuroticism": 0.3,
|
||||
"openness": 0.8
|
||||
"empathy": 3,
|
||||
"literalism": 3,
|
||||
"skepticism": 3
|
||||
},
|
||||
"name": "Alice"
|
||||
}
|
||||
|
|
@ -1457,67 +1445,40 @@
|
|||
},
|
||||
"DispositionTraits": {
|
||||
"properties": {
|
||||
"openness": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Openness",
|
||||
"description": "Openness to experience (0-1)"
|
||||
"skepticism": {
|
||||
"type": "integer",
|
||||
"maximum": 5.0,
|
||||
"minimum": 1.0,
|
||||
"title": "Skepticism",
|
||||
"description": "How skeptical vs trusting (1=trusting, 5=skeptical)"
|
||||
},
|
||||
"conscientiousness": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Conscientiousness",
|
||||
"description": "Conscientiousness (0-1)"
|
||||
"literalism": {
|
||||
"type": "integer",
|
||||
"maximum": 5.0,
|
||||
"minimum": 1.0,
|
||||
"title": "Literalism",
|
||||
"description": "How literally to interpret information (1=flexible, 5=literal)"
|
||||
},
|
||||
"extraversion": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Extraversion",
|
||||
"description": "Extraversion (0-1)"
|
||||
},
|
||||
"agreeableness": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Agreeableness",
|
||||
"description": "Agreeableness (0-1)"
|
||||
},
|
||||
"neuroticism": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Neuroticism",
|
||||
"description": "Neuroticism (0-1)"
|
||||
},
|
||||
"bias_strength": {
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0,
|
||||
"title": "Bias Strength",
|
||||
"description": "How strongly disposition influences opinions (0-1)"
|
||||
"empathy": {
|
||||
"type": "integer",
|
||||
"maximum": 5.0,
|
||||
"minimum": 1.0,
|
||||
"title": "Empathy",
|
||||
"description": "How much to consider emotional context (1=detached, 5=empathetic)"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"openness",
|
||||
"conscientiousness",
|
||||
"extraversion",
|
||||
"agreeableness",
|
||||
"neuroticism",
|
||||
"bias_strength"
|
||||
"skepticism",
|
||||
"literalism",
|
||||
"empathy"
|
||||
],
|
||||
"title": "DispositionTraits",
|
||||
"description": "Disposition traits based on Big Five model.",
|
||||
"description": "Disposition traits that influence how memories are formed and interpreted.",
|
||||
"example": {
|
||||
"agreeableness": 0.7,
|
||||
"bias_strength": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.5,
|
||||
"neuroticism": 0.3,
|
||||
"openness": 0.8
|
||||
"empathy": 3,
|
||||
"literalism": 3,
|
||||
"skepticism": 3
|
||||
}
|
||||
},
|
||||
"DocumentResponse": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue