rename to new names
This commit is contained in:
parent
a3ad76d165
commit
d099ef870d
1939 changed files with 28551 additions and 27114 deletions
11
README.md
11
README.md
|
|
@ -34,12 +34,15 @@ from hindsight import HindsightServer, HindsightClient
|
|||
with HindsightServer(llm_provider="openai", llm_model="gpt-5.1-mini", llm_api_key=os.environ["OPENAI_API_KEY"]) as server:
|
||||
client = HindsightClient(base_url=server.url)
|
||||
|
||||
client.put(agent_id="my-agent", content="Alice works at Google")
|
||||
client.put(agent_id="my-agent", content="Bob prefers Python over JavaScript")
|
||||
# Retain memories
|
||||
client.retain(bank_id="my-agent", content="Alice works at Google")
|
||||
client.retain(bank_id="my-agent", content="Bob prefers Python over JavaScript")
|
||||
|
||||
client.search(agent_id="my-agent", query="What does Alice do?")
|
||||
# Recall memories
|
||||
client.recall(bank_id="my-agent", query="What does Alice do?")
|
||||
|
||||
client.think(agent_id="my-agent", query="Tell me about Alice")
|
||||
# Get memory perspective
|
||||
client.reflect(bank_id="my-agent", query="Tell me about Alice")
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
"""Fix memory_links entity_id to be nullable
|
||||
|
||||
Revision ID: 01f989db9079
|
||||
Revises: af0413383b3e
|
||||
Create Date: 2025-11-03 14:43:18.721430
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '01f989db9079'
|
||||
down_revision: Union[str, Sequence[str], None] = 'af0413383b3e'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Drop the existing primary key
|
||||
op.execute('ALTER TABLE memory_links DROP CONSTRAINT memory_links_pkey')
|
||||
|
||||
# Change entity_id to nullable
|
||||
op.alter_column('memory_links', 'entity_id',
|
||||
existing_type=sa.UUID(),
|
||||
nullable=True)
|
||||
|
||||
# Create a unique index with COALESCE expression to handle NULL entity_id
|
||||
op.execute("""
|
||||
CREATE UNIQUE INDEX idx_memory_links_unique
|
||||
ON memory_links (from_unit_id, to_unit_id, link_type, COALESCE(entity_id, '00000000-0000-0000-0000-000000000000'::uuid))
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
pass
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
"""add_async_operations_table
|
||||
|
||||
Revision ID: 0e96398aae9e
|
||||
Revises: 1a35a4fa1950
|
||||
Create Date: 2025-11-07 14:54:21.224968
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '0e96398aae9e'
|
||||
down_revision: Union[str, Sequence[str], None] = '1a35a4fa1950'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Create async_operations table
|
||||
op.execute("""
|
||||
CREATE TABLE async_operations (
|
||||
id UUID PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL,
|
||||
task_type TEXT NOT NULL,
|
||||
items_count INTEGER NOT NULL,
|
||||
document_id TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
|
||||
# Create index on agent_id for fast lookups by agent
|
||||
op.execute("""
|
||||
CREATE INDEX idx_async_operations_agent_id
|
||||
ON async_operations(agent_id)
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop index
|
||||
op.execute("DROP INDEX IF EXISTS idx_async_operations_agent_id")
|
||||
|
||||
# Drop table
|
||||
op.execute("DROP TABLE IF EXISTS async_operations")
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
"""add_agents_table
|
||||
|
||||
Revision ID: 1680fc9768b4
|
||||
Revises: 8c55f5602451
|
||||
Create Date: 2025-11-12 16:18:06.620862
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '1680fc9768b4'
|
||||
down_revision: Union[str, Sequence[str], None] = '8c55f5602451'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Create agents table
|
||||
op.execute("""
|
||||
CREATE TABLE agents (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
personality JSONB NOT NULL DEFAULT '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}'::jsonb,
|
||||
background TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
|
||||
# Create index on agent_id for fast lookups
|
||||
op.execute("""
|
||||
CREATE INDEX idx_agents_agent_id
|
||||
ON agents(agent_id)
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop index
|
||||
op.execute("DROP INDEX IF EXISTS idx_agents_agent_id")
|
||||
|
||||
# Drop table
|
||||
op.execute("DROP TABLE IF EXISTS agents")
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
"""add_bm25_fulltext_search
|
||||
|
||||
Revision ID: 1a35a4fa1950
|
||||
Revises: 01f989db9079
|
||||
Create Date: 2025-11-06 11:19:48.627698
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '1a35a4fa1950'
|
||||
down_revision: Union[str, Sequence[str], None] = '01f989db9079'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Add tsvector column for full-text search
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD COLUMN search_vector tsvector
|
||||
""")
|
||||
|
||||
# Populate tsvector with existing data (text + context combined)
|
||||
op.execute("""
|
||||
UPDATE memory_units
|
||||
SET search_vector =
|
||||
setweight(to_tsvector('english', COALESCE(text, '')), 'A') ||
|
||||
setweight(to_tsvector('english', COALESCE(context, '')), 'B')
|
||||
""")
|
||||
|
||||
# Create GIN index for fast full-text search
|
||||
op.execute("""
|
||||
CREATE INDEX idx_memory_units_search_vector
|
||||
ON memory_units
|
||||
USING GIN(search_vector)
|
||||
""")
|
||||
|
||||
# Create trigger to auto-update tsvector on INSERT/UPDATE
|
||||
op.execute("""
|
||||
CREATE OR REPLACE FUNCTION memory_units_search_vector_trigger() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.search_vector :=
|
||||
setweight(to_tsvector('english', COALESCE(NEW.text, '')), 'A') ||
|
||||
setweight(to_tsvector('english', COALESCE(NEW.context, '')), 'B');
|
||||
RETURN NEW;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
""")
|
||||
|
||||
op.execute("""
|
||||
CREATE TRIGGER update_memory_units_search_vector
|
||||
BEFORE INSERT OR UPDATE ON memory_units
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION memory_units_search_vector_trigger();
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop trigger
|
||||
op.execute("DROP TRIGGER IF EXISTS update_memory_units_search_vector ON memory_units")
|
||||
op.execute("DROP FUNCTION IF EXISTS memory_units_search_vector_trigger()")
|
||||
|
||||
# Drop index
|
||||
op.execute("DROP INDEX IF EXISTS idx_memory_units_search_vector")
|
||||
|
||||
# Drop column
|
||||
op.execute("ALTER TABLE memory_units DROP COLUMN IF EXISTS search_vector")
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
"""merge agents and temporal ranges branches
|
||||
|
||||
Revision ID: 217b2227771f
|
||||
Revises: 3b9c4d8e7f21, 9d42e6f91234
|
||||
Create Date: 2025-11-17 14:59:01.254543
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '217b2227771f'
|
||||
down_revision: Union[str, Sequence[str], None] = ('3b9c4d8e7f21', '9d42e6f91234')
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
pass
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
"""add_status_and_error_to_async_operations
|
||||
|
||||
Revision ID: 2a76a5bc2f09
|
||||
Revises: 0e96398aae9e
|
||||
Create Date: 2025-11-07 16:03:19.078561
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '2a76a5bc2f09'
|
||||
down_revision: Union[str, Sequence[str], None] = '0e96398aae9e'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Add status column (default 'pending' for existing rows)
|
||||
op.execute("""
|
||||
ALTER TABLE async_operations
|
||||
ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'
|
||||
""")
|
||||
|
||||
# Add error_message column
|
||||
op.execute("""
|
||||
ALTER TABLE async_operations
|
||||
ADD COLUMN error_message TEXT
|
||||
""")
|
||||
|
||||
# Add index on status for filtering failed/pending operations
|
||||
op.execute("""
|
||||
CREATE INDEX idx_async_operations_status
|
||||
ON async_operations(status)
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop index
|
||||
op.execute("DROP INDEX IF EXISTS idx_async_operations_status")
|
||||
|
||||
# Drop columns
|
||||
op.execute("ALTER TABLE async_operations DROP COLUMN IF EXISTS error_message")
|
||||
op.execute("ALTER TABLE async_operations DROP COLUMN IF EXISTS status")
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
"""add_name_to_agents
|
||||
|
||||
Revision ID: 3b9c4d8e7f21
|
||||
Revises: 1680fc9768b4
|
||||
Create Date: 2025-11-13 14:52:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '3b9c4d8e7f21'
|
||||
down_revision: Union[str, Sequence[str], None] = '1680fc9768b4'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Add name column to agents table
|
||||
op.execute("""
|
||||
ALTER TABLE agents
|
||||
ADD COLUMN name TEXT NOT NULL DEFAULT ''
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Remove name column from agents table
|
||||
op.execute("""
|
||||
ALTER TABLE agents
|
||||
DROP COLUMN name
|
||||
""")
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
"""add metadata to memory_units
|
||||
|
||||
Revision ID: 4a8b3c5d6e7f
|
||||
Revises: 217b2227771f
|
||||
Create Date: 2025-11-21 10:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '4a8b3c5d6e7f'
|
||||
down_revision: Union[str, Sequence[str], None] = '217b2227771f'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add metadata column to memory_units table."""
|
||||
op.add_column(
|
||||
'memory_units',
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove metadata column from memory_units table."""
|
||||
op.drop_column('memory_units', 'metadata')
|
||||
275
hindsight-api/alembic/versions/5a366d414dce_initial_schema.py
Normal file
275
hindsight-api/alembic/versions/5a366d414dce_initial_schema.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
"""initial_schema
|
||||
|
||||
Revision ID: 5a366d414dce
|
||||
Revises:
|
||||
Create Date: 2025-11-27 11:54:19.228030
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from pgvector.sqlalchemy import Vector
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '5a366d414dce'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema - create all tables from scratch."""
|
||||
|
||||
# Enable required extensions
|
||||
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')
|
||||
op.execute('CREATE EXTENSION IF NOT EXISTS vector')
|
||||
|
||||
# Create banks table
|
||||
op.create_table(
|
||||
'banks',
|
||||
sa.Column('bank_id', sa.Text(), nullable=False),
|
||||
sa.Column('name', sa.Text(), nullable=True),
|
||||
sa.Column('personality', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('background', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('bank_id', name=op.f('pk_banks'))
|
||||
)
|
||||
|
||||
# Create documents table
|
||||
op.create_table(
|
||||
'documents',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('bank_id', sa.Text(), nullable=False),
|
||||
sa.Column('original_text', sa.Text(), nullable=True),
|
||||
sa.Column('content_hash', sa.Text(), nullable=True),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', 'bank_id', name=op.f('pk_documents'))
|
||||
)
|
||||
op.create_index('idx_documents_bank_id', 'documents', ['bank_id'])
|
||||
op.create_index('idx_documents_content_hash', 'documents', ['content_hash'])
|
||||
|
||||
# Create async_operations table
|
||||
op.create_table(
|
||||
'async_operations',
|
||||
sa.Column('operation_id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('bank_id', sa.Text(), nullable=False),
|
||||
sa.Column('operation_type', sa.Text(), nullable=False),
|
||||
sa.Column('status', sa.Text(), server_default='pending', nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('completed_at', postgresql.TIMESTAMP(timezone=True), nullable=True),
|
||||
sa.Column('error_message', sa.Text(), nullable=True),
|
||||
sa.Column('result_metadata', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.PrimaryKeyConstraint('operation_id', name=op.f('pk_async_operations')),
|
||||
sa.CheckConstraint("status IN ('pending', 'processing', 'completed', 'failed')", name='async_operations_status_check')
|
||||
)
|
||||
op.create_index('idx_async_operations_bank_id', 'async_operations', ['bank_id'])
|
||||
op.create_index('idx_async_operations_status', 'async_operations', ['status'])
|
||||
op.create_index('idx_async_operations_bank_status', 'async_operations', ['bank_id', 'status'])
|
||||
|
||||
# Create entities table
|
||||
op.create_table(
|
||||
'entities',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('canonical_name', sa.Text(), nullable=False),
|
||||
sa.Column('bank_id', sa.Text(), nullable=False),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('first_seen', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('last_seen', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('mention_count', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_entities'))
|
||||
)
|
||||
op.create_index('idx_entities_bank_id', 'entities', ['bank_id'])
|
||||
op.create_index('idx_entities_canonical_name', 'entities', ['canonical_name'])
|
||||
op.create_index('idx_entities_bank_name', 'entities', ['bank_id', 'canonical_name'])
|
||||
# Create unique index on (bank_id, LOWER(canonical_name)) for entity resolution
|
||||
op.execute('CREATE UNIQUE INDEX idx_entities_bank_lower_name ON entities (bank_id, LOWER(canonical_name))')
|
||||
|
||||
# Create memory_units table
|
||||
op.create_table(
|
||||
'memory_units',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('bank_id', sa.Text(), nullable=False),
|
||||
sa.Column('document_id', sa.Text(), nullable=True),
|
||||
sa.Column('text', sa.Text(), nullable=False),
|
||||
sa.Column('embedding', Vector(384), nullable=True),
|
||||
sa.Column('context', sa.Text(), nullable=True),
|
||||
sa.Column('event_date', postgresql.TIMESTAMP(timezone=True), nullable=False),
|
||||
sa.Column('occurred_start', postgresql.TIMESTAMP(timezone=True), nullable=True),
|
||||
sa.Column('occurred_end', postgresql.TIMESTAMP(timezone=True), nullable=True),
|
||||
sa.Column('mentioned_at', postgresql.TIMESTAMP(timezone=True), nullable=True),
|
||||
sa.Column('fact_type', sa.Text(), server_default='world', nullable=False),
|
||||
sa.Column('confidence_score', sa.Float(), nullable=True),
|
||||
sa.Column('access_count', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['document_id', 'bank_id'], ['documents.id', 'documents.bank_id'], name='memory_units_document_fkey', ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_memory_units')),
|
||||
sa.CheckConstraint("fact_type IN ('world', 'bank', 'opinion', 'observation')", name='memory_units_fact_type_check'),
|
||||
sa.CheckConstraint("confidence_score IS NULL OR (confidence_score >= 0.0 AND confidence_score <= 1.0)", name='memory_units_confidence_range_check'),
|
||||
sa.CheckConstraint(
|
||||
"(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR "
|
||||
"(fact_type = 'observation') OR "
|
||||
"(fact_type NOT IN ('opinion', 'observation') AND confidence_score IS NULL)",
|
||||
name='confidence_score_fact_type_check'
|
||||
)
|
||||
)
|
||||
|
||||
# Add search_vector column for full-text search
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD COLUMN search_vector tsvector
|
||||
GENERATED ALWAYS AS (to_tsvector('english', COALESCE(text, '') || ' ' || COALESCE(context, ''))) STORED
|
||||
""")
|
||||
|
||||
op.create_index('idx_memory_units_bank_id', 'memory_units', ['bank_id'])
|
||||
op.create_index('idx_memory_units_document_id', 'memory_units', ['document_id'])
|
||||
op.create_index('idx_memory_units_event_date', 'memory_units', [sa.text('event_date DESC')])
|
||||
op.create_index('idx_memory_units_bank_date', 'memory_units', ['bank_id', sa.text('event_date DESC')])
|
||||
op.create_index('idx_memory_units_access_count', 'memory_units', [sa.text('access_count DESC')])
|
||||
op.create_index('idx_memory_units_fact_type', 'memory_units', ['fact_type'])
|
||||
op.create_index('idx_memory_units_bank_fact_type', 'memory_units', ['bank_id', 'fact_type'])
|
||||
op.create_index('idx_memory_units_bank_type_date', 'memory_units', ['bank_id', 'fact_type', sa.text('event_date DESC')])
|
||||
op.create_index('idx_memory_units_opinion_confidence', 'memory_units', ['bank_id', sa.text('confidence_score DESC')], postgresql_where=sa.text("fact_type = 'opinion'"))
|
||||
op.create_index('idx_memory_units_opinion_date', 'memory_units', ['bank_id', sa.text('event_date DESC')], postgresql_where=sa.text("fact_type = 'opinion'"))
|
||||
op.create_index('idx_memory_units_observation_date', 'memory_units', ['bank_id', sa.text('event_date DESC')], postgresql_where=sa.text("fact_type = 'observation'"))
|
||||
op.create_index('idx_memory_units_embedding', 'memory_units', ['embedding'], postgresql_using='hnsw', postgresql_ops={'embedding': 'vector_cosine_ops'})
|
||||
|
||||
# Create BM25 full-text search index on search_vector
|
||||
op.execute("""
|
||||
CREATE INDEX idx_memory_units_text_search ON memory_units
|
||||
USING gin(search_vector)
|
||||
""")
|
||||
|
||||
op.execute("""
|
||||
CREATE MATERIALIZED VIEW memory_units_bm25 AS
|
||||
SELECT
|
||||
id,
|
||||
bank_id,
|
||||
text,
|
||||
to_tsvector('english', text) AS text_vector,
|
||||
log(1.0 + length(text)::float / (SELECT avg(length(text)) FROM memory_units)) AS doc_length_factor
|
||||
FROM memory_units
|
||||
""")
|
||||
|
||||
op.create_index('idx_memory_units_bm25_bank', 'memory_units_bm25', ['bank_id'])
|
||||
op.create_index('idx_memory_units_bm25_text_vector', 'memory_units_bm25', ['text_vector'], postgresql_using='gin')
|
||||
|
||||
# Create entity_cooccurrences table
|
||||
op.create_table(
|
||||
'entity_cooccurrences',
|
||||
sa.Column('entity_id_1', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('entity_id_2', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('cooccurrence_count', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.Column('last_cooccurred', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id_1'], ['entities.id'], name=op.f('fk_entity_cooccurrences_entity_id_1_entities'), ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['entity_id_2'], ['entities.id'], name=op.f('fk_entity_cooccurrences_entity_id_2_entities'), ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('entity_id_1', 'entity_id_2', name=op.f('pk_entity_cooccurrences')),
|
||||
sa.CheckConstraint('entity_id_1 < entity_id_2', name='entity_cooccurrence_order_check')
|
||||
)
|
||||
op.create_index('idx_entity_cooccurrences_entity1', 'entity_cooccurrences', ['entity_id_1'])
|
||||
op.create_index('idx_entity_cooccurrences_entity2', 'entity_cooccurrences', ['entity_id_2'])
|
||||
op.create_index('idx_entity_cooccurrences_count', 'entity_cooccurrences', [sa.text('cooccurrence_count DESC')])
|
||||
|
||||
# Create memory_links table
|
||||
op.create_table(
|
||||
'memory_links',
|
||||
sa.Column('from_unit_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('to_unit_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('link_type', sa.Text(), nullable=False),
|
||||
sa.Column('entity_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('weight', sa.Float(), server_default='1.0', nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], name=op.f('fk_memory_links_entity_id_entities'), ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['from_unit_id'], ['memory_units.id'], name=op.f('fk_memory_links_from_unit_id_memory_units'), ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['to_unit_id'], ['memory_units.id'], name=op.f('fk_memory_links_to_unit_id_memory_units'), ondelete='CASCADE'),
|
||||
sa.CheckConstraint("link_type IN ('temporal', 'semantic', 'entity', 'causes', 'caused_by', 'enables', 'prevents')", name='memory_links_link_type_check'),
|
||||
sa.CheckConstraint('weight >= 0.0 AND weight <= 1.0', name='memory_links_weight_check')
|
||||
)
|
||||
# Create unique constraint using COALESCE for nullable entity_id
|
||||
op.execute("CREATE UNIQUE INDEX idx_memory_links_unique ON memory_links (from_unit_id, to_unit_id, link_type, COALESCE(entity_id, '00000000-0000-0000-0000-000000000000'::uuid))")
|
||||
op.create_index('idx_memory_links_from_unit', 'memory_links', ['from_unit_id'])
|
||||
op.create_index('idx_memory_links_to_unit', 'memory_links', ['to_unit_id'])
|
||||
op.create_index('idx_memory_links_entity', 'memory_links', ['entity_id'])
|
||||
op.create_index('idx_memory_links_link_type', 'memory_links', ['link_type'])
|
||||
|
||||
# Create unit_entities table
|
||||
op.create_table(
|
||||
'unit_entities',
|
||||
sa.Column('unit_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('entity_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], name=op.f('fk_unit_entities_entity_id_entities'), ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['unit_id'], ['memory_units.id'], name=op.f('fk_unit_entities_unit_id_memory_units'), ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('unit_id', 'entity_id', name=op.f('pk_unit_entities'))
|
||||
)
|
||||
op.create_index('idx_unit_entities_unit', 'unit_entities', ['unit_id'])
|
||||
op.create_index('idx_unit_entities_entity', 'unit_entities', ['entity_id'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema - drop all tables."""
|
||||
|
||||
# Drop tables in reverse dependency order
|
||||
op.drop_index('idx_unit_entities_entity', table_name='unit_entities')
|
||||
op.drop_index('idx_unit_entities_unit', table_name='unit_entities')
|
||||
op.drop_table('unit_entities')
|
||||
|
||||
op.drop_index('idx_memory_links_link_type', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_entity', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_to_unit', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_from_unit', table_name='memory_links')
|
||||
op.execute('DROP INDEX IF EXISTS idx_memory_links_unique')
|
||||
op.drop_table('memory_links')
|
||||
|
||||
op.drop_index('idx_entity_cooccurrences_count', table_name='entity_cooccurrences')
|
||||
op.drop_index('idx_entity_cooccurrences_entity2', table_name='entity_cooccurrences')
|
||||
op.drop_index('idx_entity_cooccurrences_entity1', table_name='entity_cooccurrences')
|
||||
op.drop_table('entity_cooccurrences')
|
||||
|
||||
# Drop BM25 materialized view and index
|
||||
op.drop_index('idx_memory_units_bm25_text_vector', table_name='memory_units_bm25')
|
||||
op.drop_index('idx_memory_units_bm25_bank', table_name='memory_units_bm25')
|
||||
op.execute('DROP MATERIALIZED VIEW IF EXISTS memory_units_bm25')
|
||||
|
||||
op.drop_index('idx_memory_units_embedding', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_observation_date', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_opinion_date', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_opinion_confidence', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_bank_type_date', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_bank_fact_type', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_fact_type', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_access_count', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_bank_date', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_event_date', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_document_id', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_bank_id', table_name='memory_units')
|
||||
op.execute('DROP INDEX IF EXISTS idx_memory_units_text_search')
|
||||
op.drop_table('memory_units')
|
||||
|
||||
op.execute('DROP INDEX IF EXISTS idx_entities_bank_lower_name')
|
||||
op.drop_index('idx_entities_bank_name', table_name='entities')
|
||||
op.drop_index('idx_entities_canonical_name', table_name='entities')
|
||||
op.drop_index('idx_entities_bank_id', table_name='entities')
|
||||
op.drop_table('entities')
|
||||
|
||||
op.drop_index('idx_async_operations_bank_status', table_name='async_operations')
|
||||
op.drop_index('idx_async_operations_status', table_name='async_operations')
|
||||
op.drop_index('idx_async_operations_bank_id', table_name='async_operations')
|
||||
op.drop_table('async_operations')
|
||||
|
||||
op.drop_index('idx_documents_content_hash', table_name='documents')
|
||||
op.drop_index('idx_documents_bank_id', table_name='documents')
|
||||
op.drop_table('documents')
|
||||
|
||||
op.drop_table('banks')
|
||||
|
||||
# Drop extensions (optional - comment out if you want to keep them)
|
||||
# op.execute('DROP EXTENSION IF EXISTS vector')
|
||||
# op.execute('DROP EXTENSION IF EXISTS "uuid-ossp"')
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
"""add_observation_fact_type
|
||||
|
||||
Revision ID: 5b2c6d8e9f01
|
||||
Revises: 4a8b3c5d6e7f
|
||||
Create Date: 2025-11-26 10:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '5b2c6d8e9f01'
|
||||
down_revision: Union[str, Sequence[str], None] = '4a8b3c5d6e7f'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Drop old constraints
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
DROP CONSTRAINT IF EXISTS confidence_score_fact_type_check
|
||||
""")
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
DROP CONSTRAINT IF EXISTS memory_units_fact_type_check
|
||||
""")
|
||||
|
||||
# Add new fact_type constraint including 'observation'
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD CONSTRAINT memory_units_fact_type_check
|
||||
CHECK (fact_type IN ('world', 'agent', 'opinion', 'observation'))
|
||||
""")
|
||||
|
||||
# Add new confidence_score constraint allowing observation to have optional confidence
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD CONSTRAINT confidence_score_fact_type_check
|
||||
CHECK (
|
||||
(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR
|
||||
(fact_type = 'observation') OR
|
||||
(fact_type NOT IN ('opinion', 'observation') AND confidence_score IS NULL)
|
||||
)
|
||||
""")
|
||||
|
||||
# Add index for observation fact_type queries
|
||||
op.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_units_observation_date
|
||||
ON memory_units (agent_id, event_date DESC)
|
||||
WHERE fact_type = 'observation'
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Drop observation index
|
||||
op.execute("DROP INDEX IF EXISTS idx_memory_units_observation_date")
|
||||
|
||||
# Drop new constraints
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
DROP CONSTRAINT IF EXISTS confidence_score_fact_type_check
|
||||
""")
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
DROP CONSTRAINT IF EXISTS memory_units_fact_type_check
|
||||
""")
|
||||
|
||||
# Restore old fact_type constraint
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD CONSTRAINT memory_units_fact_type_check
|
||||
CHECK (fact_type IN ('world', 'agent', 'opinion'))
|
||||
""")
|
||||
|
||||
# Restore old confidence_score constraint
|
||||
op.execute("""
|
||||
ALTER TABLE memory_units
|
||||
ADD CONSTRAINT confidence_score_fact_type_check
|
||||
CHECK (
|
||||
(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR
|
||||
(fact_type != 'opinion' AND confidence_score IS NULL)
|
||||
)
|
||||
""")
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
"""add unique constraint on entities (agent_id, canonical_name)
|
||||
|
||||
Revision ID: 7d4e6f0a3b12
|
||||
Revises: 5b2c6d8e9f01
|
||||
Create Date: 2024-01-01 00:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '7d4e6f0a3b12'
|
||||
down_revision: Union[str, None] = '5b2c6d8e9f01'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# First, deduplicate existing entities by merging duplicates
|
||||
# Keep the one with highest mention_count, update unit_entities to point to it
|
||||
op.execute("""
|
||||
-- Create temp table with canonical entity per (agent_id, name)
|
||||
CREATE TEMP TABLE canonical_entities AS
|
||||
SELECT DISTINCT ON (agent_id, LOWER(canonical_name))
|
||||
id as keep_id,
|
||||
agent_id,
|
||||
LOWER(canonical_name) as name_lower
|
||||
FROM entities
|
||||
ORDER BY agent_id, LOWER(canonical_name), mention_count DESC, first_seen ASC;
|
||||
|
||||
-- Get all entity IDs that will be removed (duplicates)
|
||||
CREATE TEMP TABLE duplicate_entities AS
|
||||
SELECT e.id as dup_id, ce.keep_id
|
||||
FROM entities e
|
||||
JOIN canonical_entities ce ON e.agent_id = ce.agent_id AND LOWER(e.canonical_name) = ce.name_lower
|
||||
WHERE e.id != ce.keep_id;
|
||||
|
||||
-- Update unit_entities to point to canonical entity
|
||||
UPDATE unit_entities ue
|
||||
SET entity_id = de.keep_id
|
||||
FROM duplicate_entities de
|
||||
WHERE ue.entity_id = de.dup_id;
|
||||
|
||||
-- Delete duplicate unit_entities that now exist
|
||||
DELETE FROM unit_entities a
|
||||
USING unit_entities b
|
||||
WHERE a.unit_id = b.unit_id
|
||||
AND a.entity_id = b.entity_id
|
||||
AND a.ctid < b.ctid;
|
||||
|
||||
-- For entity_cooccurrences, we need to be careful about the check constraint
|
||||
-- First, collect all cooccurrences that need updating into a temp table with correct ordering
|
||||
CREATE TEMP TABLE new_cooccurrences AS
|
||||
SELECT DISTINCT
|
||||
LEAST(
|
||||
COALESCE(de1.keep_id, ec.entity_id_1),
|
||||
COALESCE(de2.keep_id, ec.entity_id_2)
|
||||
) as entity_id_1,
|
||||
GREATEST(
|
||||
COALESCE(de1.keep_id, ec.entity_id_1),
|
||||
COALESCE(de2.keep_id, ec.entity_id_2)
|
||||
) as entity_id_2,
|
||||
SUM(ec.cooccurrence_count) as cooccurrence_count,
|
||||
MAX(ec.last_cooccurred) as last_cooccurred
|
||||
FROM entity_cooccurrences ec
|
||||
LEFT JOIN duplicate_entities de1 ON ec.entity_id_1 = de1.dup_id
|
||||
LEFT JOIN duplicate_entities de2 ON ec.entity_id_2 = de2.dup_id
|
||||
GROUP BY
|
||||
LEAST(COALESCE(de1.keep_id, ec.entity_id_1), COALESCE(de2.keep_id, ec.entity_id_2)),
|
||||
GREATEST(COALESCE(de1.keep_id, ec.entity_id_1), COALESCE(de2.keep_id, ec.entity_id_2));
|
||||
|
||||
-- Delete rows where entity_id_1 = entity_id_2 (self-references after merge)
|
||||
DELETE FROM new_cooccurrences WHERE entity_id_1 = entity_id_2;
|
||||
|
||||
-- Delete all old cooccurrences
|
||||
DELETE FROM entity_cooccurrences;
|
||||
|
||||
-- Insert the merged cooccurrences
|
||||
INSERT INTO entity_cooccurrences (entity_id_1, entity_id_2, cooccurrence_count, last_cooccurred)
|
||||
SELECT entity_id_1, entity_id_2, cooccurrence_count, last_cooccurred
|
||||
FROM new_cooccurrences;
|
||||
|
||||
-- Update mention counts on canonical entities
|
||||
UPDATE entities e
|
||||
SET mention_count = e.mention_count + COALESCE(
|
||||
(SELECT SUM(e2.mention_count)
|
||||
FROM entities e2
|
||||
JOIN duplicate_entities de ON e2.id = de.dup_id
|
||||
WHERE de.keep_id = e.id),
|
||||
0
|
||||
)
|
||||
WHERE e.id IN (SELECT keep_id FROM duplicate_entities);
|
||||
|
||||
-- Delete duplicate entities
|
||||
DELETE FROM entities
|
||||
WHERE id IN (SELECT dup_id FROM duplicate_entities);
|
||||
|
||||
-- Cleanup temp tables
|
||||
DROP TABLE new_cooccurrences;
|
||||
DROP TABLE duplicate_entities;
|
||||
DROP TABLE canonical_entities;
|
||||
""")
|
||||
|
||||
# Add unique constraint (case-insensitive)
|
||||
op.create_index(
|
||||
'idx_entities_agent_canonical_unique',
|
||||
'entities',
|
||||
[sa.text('agent_id'), sa.text('LOWER(canonical_name)')],
|
||||
unique=True
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('idx_entities_agent_canonical_unique', table_name='entities')
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
"""remove_entity_type_column
|
||||
|
||||
Revision ID: 8c55f5602451
|
||||
Revises: 2a76a5bc2f09
|
||||
Create Date: 2025-11-07 17:08:07.329740
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '8c55f5602451'
|
||||
down_revision: Union[str, Sequence[str], None] = '2a76a5bc2f09'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Remove entity_type column from entities table
|
||||
op.execute("ALTER TABLE entities DROP COLUMN IF EXISTS entity_type")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# Re-add entity_type column (default to 'OTHER' for existing rows)
|
||||
op.execute("ALTER TABLE entities ADD COLUMN entity_type TEXT DEFAULT 'OTHER'")
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
"""add_temporal_ranges_to_memory_units
|
||||
|
||||
Revision ID: 9d42e6f91234
|
||||
Revises: 8c55f5602451
|
||||
Create Date: 2025-11-17 00:00:00.000000
|
||||
|
||||
This migration adds temporal range support to memory_units table:
|
||||
- occurred_start: When the fact/event started
|
||||
- occurred_end: When the fact/event ended
|
||||
- mentioned_at: When the fact was mentioned/learned
|
||||
|
||||
For existing rows, these are initialized from event_date (point events).
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import TIMESTAMP
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '9d42e6f91234'
|
||||
down_revision: Union[str, Sequence[str], None] = '8c55f5602451'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema: add temporal range columns to memory_units."""
|
||||
|
||||
# Add new temporal range columns (nullable initially)
|
||||
op.add_column(
|
||||
'memory_units',
|
||||
sa.Column('occurred_start', TIMESTAMP(timezone=True), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
'memory_units',
|
||||
sa.Column('occurred_end', TIMESTAMP(timezone=True), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
'memory_units',
|
||||
sa.Column('mentioned_at', TIMESTAMP(timezone=True), nullable=True)
|
||||
)
|
||||
|
||||
# Populate new columns from existing event_date for backward compatibility
|
||||
# For existing facts, treat them as point events (start = end = event_date)
|
||||
# and assume they were mentioned at the same time
|
||||
op.execute("""
|
||||
UPDATE memory_units
|
||||
SET
|
||||
occurred_start = event_date,
|
||||
occurred_end = event_date,
|
||||
mentioned_at = event_date
|
||||
WHERE occurred_start IS NULL
|
||||
""")
|
||||
|
||||
# Optional: Make columns non-nullable after populating
|
||||
# Uncomment if you want to enforce NOT NULL constraint
|
||||
# op.alter_column('memory_units', 'occurred_start', nullable=False)
|
||||
# op.alter_column('memory_units', 'occurred_end', nullable=False)
|
||||
# op.alter_column('memory_units', 'mentioned_at', nullable=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema: remove temporal range columns from memory_units."""
|
||||
|
||||
# Remove the temporal range columns
|
||||
op.drop_column('memory_units', 'mentioned_at')
|
||||
op.drop_column('memory_units', 'occurred_end')
|
||||
op.drop_column('memory_units', 'occurred_start')
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
"""Initial schema
|
||||
|
||||
Revision ID: af0413383b3e
|
||||
Revises:
|
||||
Create Date: 2025-11-03 14:31:53.245542
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
import pgvector.sqlalchemy
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'af0413383b3e'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# Create pgvector extension
|
||||
op.execute('CREATE EXTENSION IF NOT EXISTS vector')
|
||||
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')
|
||||
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('documents',
|
||||
sa.Column('id', sa.Text(), nullable=False),
|
||||
sa.Column('agent_id', sa.Text(), nullable=False),
|
||||
sa.Column('original_text', sa.Text(), nullable=True),
|
||||
sa.Column('content_hash', sa.Text(), nullable=True),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()),
|
||||
server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', 'agent_id')
|
||||
)
|
||||
op.create_index('idx_documents_agent_id', 'documents', ['agent_id'], unique=False)
|
||||
op.create_index('idx_documents_content_hash', 'documents', ['content_hash'], unique=False)
|
||||
op.create_table('entities',
|
||||
sa.Column('id', sa.UUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('canonical_name', sa.Text(), nullable=False),
|
||||
sa.Column('entity_type', sa.Text(), nullable=False),
|
||||
sa.Column('agent_id', sa.Text(), nullable=False),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()),
|
||||
server_default=sa.text("'{}'::jsonb"), nullable=False),
|
||||
sa.Column('first_seen', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.Column('last_seen', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.Column('mention_count', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_entities_agent_id', 'entities', ['agent_id'], unique=False)
|
||||
op.create_index('idx_entities_agent_name_type', 'entities', ['agent_id', 'canonical_name', 'entity_type'],
|
||||
unique=False)
|
||||
op.create_index('idx_entities_canonical_name', 'entities', ['canonical_name'], unique=False)
|
||||
op.create_index('idx_entities_type', 'entities', ['entity_type'], unique=False)
|
||||
op.create_table('entity_cooccurrences',
|
||||
sa.Column('entity_id_1', sa.UUID(), nullable=False),
|
||||
sa.Column('entity_id_2', sa.UUID(), nullable=False),
|
||||
sa.Column('cooccurrence_count', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.Column('last_cooccurred', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.CheckConstraint('entity_id_1 < entity_id_2', name='entity_cooccurrence_order_check'),
|
||||
sa.ForeignKeyConstraint(['entity_id_1'], ['entities.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['entity_id_2'], ['entities.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('entity_id_1', 'entity_id_2')
|
||||
)
|
||||
op.create_index('idx_entity_cooccurrences_count', 'entity_cooccurrences', ['cooccurrence_count'], unique=False,
|
||||
postgresql_ops={'cooccurrence_count': 'DESC'})
|
||||
op.create_index('idx_entity_cooccurrences_entity1', 'entity_cooccurrences', ['entity_id_1'], unique=False)
|
||||
op.create_index('idx_entity_cooccurrences_entity2', 'entity_cooccurrences', ['entity_id_2'], unique=False)
|
||||
op.create_table('memory_units',
|
||||
sa.Column('id', sa.UUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('agent_id', sa.Text(), nullable=False),
|
||||
sa.Column('document_id', sa.Text(), nullable=True),
|
||||
sa.Column('text', sa.Text(), nullable=False),
|
||||
sa.Column('embedding', pgvector.sqlalchemy.vector.VECTOR(dim=384), nullable=True),
|
||||
sa.Column('context', sa.Text(), nullable=True),
|
||||
sa.Column('event_date', postgresql.TIMESTAMP(timezone=True), nullable=False),
|
||||
sa.Column('fact_type', sa.Text(), server_default='world', nullable=False),
|
||||
sa.Column('confidence_score', sa.Float(), nullable=True),
|
||||
sa.Column('access_count', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.CheckConstraint(
|
||||
"(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR (fact_type != 'opinion' AND confidence_score IS NULL)",
|
||||
name='confidence_score_fact_type_check'),
|
||||
sa.CheckConstraint("fact_type IN ('world', 'agent', 'opinion')"),
|
||||
sa.CheckConstraint(
|
||||
'confidence_score IS NULL OR (confidence_score >= 0.0 AND confidence_score <= 1.0)'),
|
||||
sa.ForeignKeyConstraint(['document_id', 'agent_id'], ['documents.id', 'documents.agent_id'],
|
||||
name='memory_units_document_fkey', ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_memory_units_access_count', 'memory_units', ['access_count'], unique=False,
|
||||
postgresql_ops={'access_count': 'DESC'})
|
||||
op.create_index('idx_memory_units_agent_date', 'memory_units', ['agent_id', 'event_date'], unique=False,
|
||||
postgresql_ops={'event_date': 'DESC'})
|
||||
op.create_index('idx_memory_units_agent_fact_type', 'memory_units', ['agent_id', 'fact_type'], unique=False)
|
||||
op.create_index('idx_memory_units_agent_id', 'memory_units', ['agent_id'], unique=False)
|
||||
op.create_index('idx_memory_units_agent_type_date', 'memory_units', ['agent_id', 'fact_type', 'event_date'],
|
||||
unique=False, postgresql_ops={'event_date': 'DESC'})
|
||||
op.create_index('idx_memory_units_document_id', 'memory_units', ['document_id'], unique=False)
|
||||
op.create_index('idx_memory_units_embedding', 'memory_units', ['embedding'], unique=False, postgresql_using='hnsw',
|
||||
postgresql_ops={'embedding': 'vector_cosine_ops'})
|
||||
op.create_index('idx_memory_units_event_date', 'memory_units', ['event_date'], unique=False,
|
||||
postgresql_ops={'event_date': 'DESC'})
|
||||
op.create_index('idx_memory_units_fact_type', 'memory_units', ['fact_type'], unique=False)
|
||||
op.create_index('idx_memory_units_opinion_confidence', 'memory_units', ['agent_id', 'confidence_score'],
|
||||
unique=False, postgresql_where=sa.text("fact_type = 'opinion'"),
|
||||
postgresql_ops={'confidence_score': 'DESC'})
|
||||
op.create_index('idx_memory_units_opinion_date', 'memory_units', ['agent_id', 'event_date'], unique=False,
|
||||
postgresql_where=sa.text("fact_type = 'opinion'"), postgresql_ops={'event_date': 'DESC'})
|
||||
op.create_table('memory_links',
|
||||
sa.Column('from_unit_id', sa.UUID(), nullable=False),
|
||||
sa.Column('to_unit_id', sa.UUID(), nullable=False),
|
||||
sa.Column('link_type', sa.Text(), nullable=False),
|
||||
sa.Column('entity_id', sa.UUID(), nullable=False),
|
||||
sa.Column('weight', sa.Float(), server_default='1.0', nullable=False),
|
||||
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'),
|
||||
nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['from_unit_id'], ['memory_units.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['to_unit_id'], ['memory_units.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('from_unit_id', 'to_unit_id', 'link_type', 'entity_id')
|
||||
)
|
||||
op.create_index('idx_memory_links_entity', 'memory_links', ['entity_id'], unique=False,
|
||||
postgresql_where=sa.text('entity_id IS NOT NULL'))
|
||||
op.create_index('idx_memory_links_from', 'memory_links', ['from_unit_id'], unique=False)
|
||||
op.create_index('idx_memory_links_from_weight', 'memory_links', ['from_unit_id', 'weight'], unique=False,
|
||||
postgresql_where=sa.text('weight >= 0.1'), postgresql_ops={'weight': 'DESC'})
|
||||
op.create_index('idx_memory_links_to', 'memory_links', ['to_unit_id'], unique=False)
|
||||
op.create_index('idx_memory_links_type', 'memory_links', ['link_type'], unique=False)
|
||||
op.create_table('unit_entities',
|
||||
sa.Column('unit_id', sa.UUID(), nullable=False),
|
||||
sa.Column('entity_id', sa.UUID(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['unit_id'], ['memory_units.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('unit_id', 'entity_id')
|
||||
)
|
||||
op.create_index('idx_unit_entities_entity', 'unit_entities', ['entity_id'], unique=False)
|
||||
op.create_index('idx_unit_entities_unit', 'unit_entities', ['unit_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index('idx_unit_entities_unit', table_name='unit_entities')
|
||||
op.drop_index('idx_unit_entities_entity', table_name='unit_entities')
|
||||
op.drop_table('unit_entities')
|
||||
op.drop_index('idx_memory_links_type', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_to', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_from_weight', table_name='memory_links', postgresql_where=sa.text('weight >= 0.1'),
|
||||
postgresql_ops={'weight': 'DESC'})
|
||||
op.drop_index('idx_memory_links_from', table_name='memory_links')
|
||||
op.drop_index('idx_memory_links_entity', table_name='memory_links',
|
||||
postgresql_where=sa.text('entity_id IS NOT NULL'))
|
||||
op.drop_table('memory_links')
|
||||
op.drop_index('idx_memory_units_opinion_date', table_name='memory_units',
|
||||
postgresql_where=sa.text("fact_type = 'opinion'"), postgresql_ops={'event_date': 'DESC'})
|
||||
op.drop_index('idx_memory_units_opinion_confidence', table_name='memory_units',
|
||||
postgresql_where=sa.text("fact_type = 'opinion'"), postgresql_ops={'confidence_score': 'DESC'})
|
||||
op.drop_index('idx_memory_units_fact_type', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_event_date', table_name='memory_units', postgresql_ops={'event_date': 'DESC'})
|
||||
op.drop_index('idx_memory_units_embedding', table_name='memory_units', postgresql_using='hnsw',
|
||||
postgresql_ops={'embedding': 'vector_cosine_ops'})
|
||||
op.drop_index('idx_memory_units_document_id', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_agent_type_date', table_name='memory_units', postgresql_ops={'event_date': 'DESC'})
|
||||
op.drop_index('idx_memory_units_agent_id', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_agent_fact_type', table_name='memory_units')
|
||||
op.drop_index('idx_memory_units_agent_date', table_name='memory_units', postgresql_ops={'event_date': 'DESC'})
|
||||
op.drop_index('idx_memory_units_access_count', table_name='memory_units', postgresql_ops={'access_count': 'DESC'})
|
||||
op.drop_table('memory_units')
|
||||
op.drop_index('idx_entity_cooccurrences_entity2', table_name='entity_cooccurrences')
|
||||
op.drop_index('idx_entity_cooccurrences_entity1', table_name='entity_cooccurrences')
|
||||
op.drop_index('idx_entity_cooccurrences_count', table_name='entity_cooccurrences',
|
||||
postgresql_ops={'cooccurrence_count': 'DESC'})
|
||||
op.drop_table('entity_cooccurrences')
|
||||
op.drop_index('idx_entities_type', table_name='entities')
|
||||
op.drop_index('idx_entities_canonical_name', table_name='entities')
|
||||
op.drop_index('idx_entities_agent_name_type', table_name='entities')
|
||||
op.drop_index('idx_entities_agent_id', table_name='entities')
|
||||
op.drop_table('entities')
|
||||
op.drop_index('idx_documents_content_hash', table_name='documents')
|
||||
op.drop_index('idx_documents_agent_id', table_name='documents')
|
||||
op.drop_table('documents')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -80,26 +80,26 @@ def create_app(
|
|||
|
||||
# Re-export commonly used items for backwards compatibility
|
||||
from .http import (
|
||||
SearchRequest,
|
||||
SearchResult,
|
||||
SearchResponse,
|
||||
RecallRequest,
|
||||
RecallResult,
|
||||
RecallResponse,
|
||||
MemoryItem,
|
||||
BatchPutRequest,
|
||||
ThinkRequest,
|
||||
ThinkResponse,
|
||||
CreateAgentRequest,
|
||||
RetainRequest,
|
||||
ReflectRequest,
|
||||
ReflectResponse,
|
||||
CreateBankRequest,
|
||||
PersonalityTraits,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"create_app",
|
||||
"SearchRequest",
|
||||
"SearchResult",
|
||||
"SearchResponse",
|
||||
"RecallRequest",
|
||||
"RecallResult",
|
||||
"RecallResponse",
|
||||
"MemoryItem",
|
||||
"BatchPutRequest",
|
||||
"ThinkRequest",
|
||||
"ThinkResponse",
|
||||
"CreateAgentRequest",
|
||||
"RetainRequest",
|
||||
"ReflectRequest",
|
||||
"ReflectResponse",
|
||||
"CreateBankRequest",
|
||||
"PersonalityTraits",
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -24,15 +24,15 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
mcp = FastMCP("hindsight-mcp-server")
|
||||
|
||||
@mcp.tool()
|
||||
async def hindsight_put(agent_id: str, content: str, context: str, explanation: str = "") -> str:
|
||||
async def hindsight_put(bank_id: str, content: str, context: str, explanation: str = "") -> str:
|
||||
"""
|
||||
**CRITICAL: Store important user information to long-term memory.**
|
||||
|
||||
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
||||
- This tool is STRICTLY per-user. Each user MUST have a unique `agent_id`.
|
||||
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `agent_id`.
|
||||
- This tool is STRICTLY per-user. Each user MUST have a unique `bank_id`.
|
||||
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `bank_id`.
|
||||
- DO NOT use this tool if you cannot identify the specific user.
|
||||
- DO NOT share memories between different users - each user's memories are isolated by their `agent_id`.
|
||||
- DO NOT share memories between different users - each user's memories are isolated by their `bank_id`.
|
||||
- If you don't have a user identifier, DO NOT use this tool at all.
|
||||
|
||||
Use this tool PROACTIVELY whenever the user shares:
|
||||
|
|
@ -51,7 +51,7 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
"career_goals", "project_details", etc. This helps organize and retrieve related memories later.
|
||||
|
||||
Args:
|
||||
agent_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
|
||||
bank_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
|
||||
This MUST be consistent across all interactions with the same user.
|
||||
Example: "user_12345", "alice@example.com", "session_abc123"
|
||||
content: The fact/memory to store (be specific and include relevant details)
|
||||
|
|
@ -65,7 +65,7 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
|
||||
# Store memory using put_batch_async
|
||||
await memory.put_batch_async(
|
||||
agent_id=agent_id,
|
||||
bank_id=bank_id,
|
||||
contents=[{"content": content, "context": context}]
|
||||
)
|
||||
return f"Fact stored successfully"
|
||||
|
|
@ -74,15 +74,15 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
return f"Error: {str(e)}"
|
||||
|
||||
@mcp.tool()
|
||||
async def hindsight_search(agent_id: str, query: str, max_tokens: int = 4096, explanation: str = "") -> str:
|
||||
async def hindsight_search(bank_id: str, query: str, max_tokens: int = 4096, explanation: str = "") -> str:
|
||||
"""
|
||||
**CRITICAL: Search user's memory to provide personalized, context-aware responses.**
|
||||
|
||||
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
||||
- This tool is STRICTLY per-user. Each user MUST have a unique `agent_id`.
|
||||
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `agent_id`.
|
||||
- This tool is STRICTLY per-user. Each user MUST have a unique `bank_id`.
|
||||
- ONLY use this tool if you have a valid user identifier (user ID, email, session ID, etc.) to map to `bank_id`.
|
||||
- DO NOT use this tool if you cannot identify the specific user.
|
||||
- DO NOT search across multiple users - each user's memories are isolated by their `agent_id`.
|
||||
- DO NOT search across multiple users - each user's memories are isolated by their `bank_id`.
|
||||
- If you don't have a user identifier, DO NOT use this tool at all.
|
||||
|
||||
Use this tool PROACTIVELY at the start of conversations or when making recommendations to:
|
||||
|
|
@ -103,7 +103,7 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
"user's work experience", "user's dietary restrictions", "what does the user know about X?"
|
||||
|
||||
Args:
|
||||
agent_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
|
||||
bank_id: **REQUIRED** - The unique, persistent identifier for this specific user (e.g., user_id, email, session_id).
|
||||
This MUST be consistent across all interactions with the same user.
|
||||
Example: "user_12345", "alice@example.com", "session_abc123"
|
||||
query: Natural language search query to find relevant memories
|
||||
|
|
@ -118,13 +118,14 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
|||
if explanation:
|
||||
logger.debug(f"Explanation: {explanation}")
|
||||
|
||||
# Search using search_async
|
||||
search_result = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
# Search using recall_async
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
search_result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query=query,
|
||||
fact_type=["world", "agent", "opinion"], # Search all fact types
|
||||
fact_type=["world", "bank", "opinion"], # Search all fact types
|
||||
max_tokens=max_tokens,
|
||||
thinking_budget=100
|
||||
budget=Budget.LOW
|
||||
)
|
||||
|
||||
# Convert results to dict format
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Memory Engine - Core implementation of the memory system.
|
|||
|
||||
This package contains all the implementation details of the memory engine:
|
||||
- MemoryEngine: Main class for memory operations
|
||||
- Utility modules: embedding_utils, link_utils, think_utils, agent_utils
|
||||
- Utility modules: embedding_utils, link_utils, think_utils, bank_utils
|
||||
- Supporting modules: embeddings, cross_encoder, entity_resolver, etc.
|
||||
"""
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ from .search_trace import (
|
|||
)
|
||||
from .search_tracer import SearchTracer
|
||||
from .llm_wrapper import LLMConfig
|
||||
from .response_models import SearchResult, ThinkResult, MemoryFact
|
||||
from .response_models import RecallResult, ReflectResult, MemoryFact
|
||||
|
||||
__all__ = [
|
||||
"MemoryEngine",
|
||||
|
|
@ -41,7 +41,7 @@ __all__ = [
|
|||
"SearchSummary",
|
||||
"SearchPhaseMetrics",
|
||||
"LLMConfig",
|
||||
"SearchResult",
|
||||
"ThinkResult",
|
||||
"RecallResult",
|
||||
"ReflectResult",
|
||||
"MemoryFact",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Agent profile utilities for personality and background management.
|
||||
bank profile utilities for personality and background management.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
|
@ -37,27 +37,26 @@ class BackgroundMergeResponse(BaseModel):
|
|||
personality: PersonalityTraits = Field(description="Inferred Big Five personality traits")
|
||||
|
||||
|
||||
async def get_agent_profile(pool, agent_id: str) -> Dict:
|
||||
async def get_bank_profile(pool, bank_id: str) -> Dict:
|
||||
"""
|
||||
Get agent profile (name, personality + background).
|
||||
Auto-creates agent with default values if not exists.
|
||||
Get bank profile (name, personality + background).
|
||||
Auto-creates bank with default values if not exists.
|
||||
|
||||
Args:
|
||||
pool: Database connection pool
|
||||
agent_id: Agent identifier
|
||||
bank_id: bank IDentifier
|
||||
|
||||
Returns:
|
||||
Dict with 'name' (str), 'personality' (dict) and 'background' (str) keys
|
||||
"""
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
# Try to get existing agent
|
||||
# Try to get existing bank
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT name, personality, background
|
||||
FROM agents
|
||||
WHERE agent_id = $1
|
||||
FROM banks WHERE bank_id = $1
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
if row:
|
||||
|
|
@ -72,59 +71,59 @@ async def get_agent_profile(pool, agent_id: str) -> Dict:
|
|||
"background": row["background"]
|
||||
}
|
||||
|
||||
# Agent doesn't exist, create with defaults
|
||||
# Bank doesn't exist, create with defaults
|
||||
await conn.execute(
|
||||
"""
|
||||
INSERT INTO agents (agent_id, name, personality, background)
|
||||
INSERT INTO banks (bank_id, name, personality, background)
|
||||
VALUES ($1, $2, $3::jsonb, $4)
|
||||
ON CONFLICT (agent_id) DO NOTHING
|
||||
ON CONFLICT (bank_id) DO NOTHING
|
||||
""",
|
||||
agent_id,
|
||||
agent_id, # Default name is the agent_id
|
||||
bank_id,
|
||||
bank_id, # Default name is the bank_id
|
||||
json.dumps(DEFAULT_PERSONALITY),
|
||||
""
|
||||
)
|
||||
|
||||
return {
|
||||
"name": agent_id,
|
||||
"name": bank_id,
|
||||
"personality": DEFAULT_PERSONALITY.copy(),
|
||||
"background": ""
|
||||
}
|
||||
|
||||
|
||||
async def update_agent_personality(
|
||||
async def update_bank_personality(
|
||||
pool,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
personality: Dict[str, float]
|
||||
) -> None:
|
||||
"""
|
||||
Update agent personality traits.
|
||||
Update bank personality traits.
|
||||
|
||||
Args:
|
||||
pool: Database connection pool
|
||||
agent_id: Agent identifier
|
||||
bank_id: bank IDentifier
|
||||
personality: Dict with Big Five traits + bias_strength (all 0-1)
|
||||
"""
|
||||
# Ensure agent exists first
|
||||
await get_agent_profile(pool, agent_id)
|
||||
# Ensure bank exists first
|
||||
await get_bank_profile(pool, bank_id)
|
||||
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agents
|
||||
UPDATE banks
|
||||
SET personality = $2::jsonb,
|
||||
updated_at = NOW()
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
json.dumps(personality)
|
||||
)
|
||||
|
||||
|
||||
async def merge_agent_background(
|
||||
async def merge_bank_background(
|
||||
pool,
|
||||
llm_config,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
new_info: str,
|
||||
update_personality: bool = True
|
||||
) -> dict:
|
||||
|
|
@ -136,7 +135,7 @@ async def merge_agent_background(
|
|||
Args:
|
||||
pool: Database connection pool
|
||||
llm_config: LLM configuration for background merging
|
||||
agent_id: Agent identifier
|
||||
bank_id: bank IDentifier
|
||||
new_info: New background information to add/merge
|
||||
update_personality: If True, infer Big Five traits from background (default: True)
|
||||
|
||||
|
|
@ -144,7 +143,7 @@ async def merge_agent_background(
|
|||
Dict with 'background' (str) and optionally 'personality' (dict) keys
|
||||
"""
|
||||
# Get current profile
|
||||
profile = await get_agent_profile(pool, agent_id)
|
||||
profile = await get_bank_profile(pool, bank_id)
|
||||
current_background = profile["background"]
|
||||
|
||||
# Use LLM to merge backgrounds and optionally infer personality
|
||||
|
|
@ -164,13 +163,13 @@ async def merge_agent_background(
|
|||
# Update both background and personality
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agents
|
||||
UPDATE banks
|
||||
SET background = $2,
|
||||
personality = $3::jsonb,
|
||||
updated_at = NOW()
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
merged_background,
|
||||
json.dumps(inferred_personality)
|
||||
)
|
||||
|
|
@ -178,12 +177,12 @@ async def merge_agent_background(
|
|||
# Update only background
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agents
|
||||
UPDATE banks
|
||||
SET background = $2,
|
||||
updated_at = NOW()
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
merged_background
|
||||
)
|
||||
|
||||
|
|
@ -214,7 +213,7 @@ async def _llm_merge_background(
|
|||
Dict with 'background' (str) and optionally 'personality' (dict) keys
|
||||
"""
|
||||
if infer_personality:
|
||||
prompt = f"""You are helping maintain an agent's background/profile and infer their personality. You MUST respond with ONLY valid JSON.
|
||||
prompt = f"""You are helping maintain a memory bank's background/profile and infer their personality. You MUST respond with ONLY valid JSON.
|
||||
|
||||
Current background: {current if current else "(empty)"}
|
||||
|
||||
|
|
@ -257,7 +256,7 @@ Trait inference examples:
|
|||
- "rational and diligent" → conscientiousness: 0.7+, openness: 0.6+
|
||||
- "passionate and dramatic" → extraversion: 0.7+, neuroticism: 0.6+, openness: 0.7+"""
|
||||
else:
|
||||
prompt = f"""You are helping maintain an agent's background/profile.
|
||||
prompt = f"""You are helping maintain a memory bank's background/profile.
|
||||
|
||||
Current background: {current if current else "(empty)"}
|
||||
|
||||
|
|
@ -283,7 +282,7 @@ Merged background:"""
|
|||
parsed = await llm_config.call(
|
||||
messages=messages,
|
||||
response_format=BackgroundMergeResponse,
|
||||
scope="agent_background",
|
||||
scope="bank_background",
|
||||
temperature=0.3,
|
||||
max_tokens=8192
|
||||
)
|
||||
|
|
@ -301,7 +300,7 @@ Merged background:"""
|
|||
# Manual parsing fallback or non-personality merge
|
||||
content = await llm_config.call(
|
||||
messages=messages,
|
||||
scope="agent_background",
|
||||
scope="bank_background",
|
||||
temperature=0.3,
|
||||
max_tokens=8192
|
||||
)
|
||||
|
|
@ -388,21 +387,21 @@ Merged background:"""
|
|||
return result
|
||||
|
||||
|
||||
async def list_agents(pool) -> list:
|
||||
async def list_banks(pool) -> list:
|
||||
"""
|
||||
List all agents in the system.
|
||||
List all banks in the system.
|
||||
|
||||
Args:
|
||||
pool: Database connection pool
|
||||
|
||||
Returns:
|
||||
List of dicts with agent_id, name, personality, background, created_at, updated_at
|
||||
List of dicts with bank_id, name, personality, background, created_at, updated_at
|
||||
"""
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT agent_id, name, personality, background, created_at, updated_at
|
||||
FROM agents
|
||||
SELECT bank_id, name, personality, background, created_at, updated_at
|
||||
FROM banks
|
||||
ORDER BY updated_at DESC
|
||||
"""
|
||||
)
|
||||
|
|
@ -415,7 +414,7 @@ async def list_agents(pool) -> list:
|
|||
personality_data = json.loads(personality_data)
|
||||
|
||||
result.append({
|
||||
"agent_id": row["agent_id"],
|
||||
"bank_id": row["bank_id"],
|
||||
"name": row["name"],
|
||||
"personality": personality_data,
|
||||
"background": row["background"],
|
||||
|
|
@ -31,7 +31,7 @@ class EntityResolver:
|
|||
|
||||
async def resolve_entities_batch(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
entities_data: List[Dict],
|
||||
context: str,
|
||||
unit_event_date,
|
||||
|
|
@ -44,7 +44,7 @@ class EntityResolver:
|
|||
all entities with minimal DB queries.
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID
|
||||
bank_id: bank ID
|
||||
entities_data: List of dicts with 'text', 'type', 'nearby_entities'
|
||||
context: Context where entities appear
|
||||
unit_event_date: When this unit was created
|
||||
|
|
@ -58,34 +58,34 @@ class EntityResolver:
|
|||
|
||||
if conn is None:
|
||||
async with acquire_with_retry(self.pool) as conn:
|
||||
return await self._resolve_entities_batch_impl(conn, agent_id, entities_data, context, unit_event_date)
|
||||
return await self._resolve_entities_batch_impl(conn, bank_id, entities_data, context, unit_event_date)
|
||||
else:
|
||||
return await self._resolve_entities_batch_impl(conn, agent_id, entities_data, context, unit_event_date)
|
||||
return await self._resolve_entities_batch_impl(conn, bank_id, entities_data, context, unit_event_date)
|
||||
|
||||
async def _resolve_entities_batch_impl(self, conn, agent_id: str, entities_data: List[Dict], context: str, unit_event_date) -> List[str]:
|
||||
# Query ALL candidates for this agent
|
||||
async def _resolve_entities_batch_impl(self, conn, bank_id: str, entities_data: List[Dict], context: str, unit_event_date) -> List[str]:
|
||||
# Query ALL candidates for this bank
|
||||
all_entities = await conn.fetch(
|
||||
"""
|
||||
SELECT canonical_name, id, metadata, last_seen, mention_count
|
||||
FROM entities
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
# Build entity ID to name mapping for co-occurrence lookups
|
||||
entity_id_to_name = {row['id']: row['canonical_name'].lower() for row in all_entities}
|
||||
|
||||
# Query ALL co-occurrences for this agent's entities in one query
|
||||
# Query ALL co-occurrences for this bank's entities in one query
|
||||
# This builds a map of entity_id -> set of co-occurring entity names
|
||||
all_cooccurrences = await conn.fetch(
|
||||
"""
|
||||
SELECT ec.entity_id_1, ec.entity_id_2, ec.cooccurrence_count
|
||||
FROM entity_cooccurrences ec
|
||||
WHERE ec.entity_id_1 IN (SELECT id FROM entities WHERE agent_id = $1)
|
||||
OR ec.entity_id_2 IN (SELECT id FROM entities WHERE agent_id = $1)
|
||||
WHERE ec.entity_id_1 IN (SELECT id FROM entities WHERE bank_id = $1)
|
||||
OR ec.entity_id_2 IN (SELECT id FROM entities WHERE bank_id = $1)
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
# Build co-occurrence map: entity_id -> set of co-occurring entity names (lowercase)
|
||||
|
|
@ -205,18 +205,18 @@ class EntityResolver:
|
|||
if entities_to_create:
|
||||
for idx, entity_data in entities_to_create:
|
||||
# Use INSERT ... ON CONFLICT to atomically get-or-create
|
||||
# The unique index is on (agent_id, LOWER(canonical_name))
|
||||
# The unique index is on (bank_id, LOWER(canonical_name))
|
||||
row = await conn.fetchrow(
|
||||
"""
|
||||
INSERT INTO entities (agent_id, canonical_name, first_seen, last_seen, mention_count)
|
||||
INSERT INTO entities (bank_id, canonical_name, first_seen, last_seen, mention_count)
|
||||
VALUES ($1, $2, $3, $4, 1)
|
||||
ON CONFLICT (agent_id, LOWER(canonical_name))
|
||||
ON CONFLICT (bank_id, LOWER(canonical_name))
|
||||
DO UPDATE SET
|
||||
mention_count = entities.mention_count + 1,
|
||||
last_seen = EXCLUDED.last_seen
|
||||
RETURNING id
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
entity_data['text'],
|
||||
unit_event_date,
|
||||
unit_event_date
|
||||
|
|
@ -227,7 +227,7 @@ class EntityResolver:
|
|||
|
||||
async def resolve_entity(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
entity_text: str,
|
||||
context: str,
|
||||
nearby_entities: List[Dict],
|
||||
|
|
@ -237,7 +237,7 @@ class EntityResolver:
|
|||
Resolve an entity to a canonical entity ID.
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID (entities are scoped to agents)
|
||||
bank_id: bank ID (entities are scoped to agents)
|
||||
entity_text: Entity text ("Alice", "Google", etc.)
|
||||
context: Context where entity appears
|
||||
nearby_entities: Other entities in the same unit
|
||||
|
|
@ -252,7 +252,7 @@ class EntityResolver:
|
|||
"""
|
||||
SELECT id, canonical_name, metadata, last_seen
|
||||
FROM entities
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
AND (
|
||||
canonical_name ILIKE $2
|
||||
OR canonical_name ILIKE $3
|
||||
|
|
@ -260,13 +260,13 @@ class EntityResolver:
|
|||
)
|
||||
ORDER BY mention_count DESC
|
||||
""",
|
||||
agent_id, entity_text, f"%{entity_text}%"
|
||||
bank_id, entity_text, f"%{entity_text}%"
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
# New entity - create it
|
||||
return await self._create_entity(
|
||||
conn, agent_id, entity_text, unit_event_date
|
||||
conn, bank_id, entity_text, unit_event_date
|
||||
)
|
||||
|
||||
# Score candidates based on:
|
||||
|
|
@ -351,13 +351,13 @@ class EntityResolver:
|
|||
else:
|
||||
# Not confident - create new entity
|
||||
return await self._create_entity(
|
||||
conn, agent_id, entity_text, unit_event_date
|
||||
conn, bank_id, entity_text, unit_event_date
|
||||
)
|
||||
|
||||
async def _create_entity(
|
||||
self,
|
||||
conn,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
entity_text: str,
|
||||
event_date,
|
||||
) -> str:
|
||||
|
|
@ -369,7 +369,7 @@ class EntityResolver:
|
|||
|
||||
Args:
|
||||
conn: Database connection
|
||||
agent_id: Agent ID
|
||||
bank_id: bank ID
|
||||
entity_text: Entity text
|
||||
event_date: When first seen
|
||||
|
||||
|
|
@ -378,15 +378,15 @@ class EntityResolver:
|
|||
"""
|
||||
entity_id = await conn.fetchval(
|
||||
"""
|
||||
INSERT INTO entities (agent_id, canonical_name, first_seen, last_seen, mention_count)
|
||||
INSERT INTO entities (bank_id, canonical_name, first_seen, last_seen, mention_count)
|
||||
VALUES ($1, $2, $3, $4, 1)
|
||||
ON CONFLICT (agent_id, LOWER(canonical_name))
|
||||
ON CONFLICT (bank_id, LOWER(canonical_name))
|
||||
DO UPDATE SET
|
||||
mention_count = entities.mention_count + 1,
|
||||
last_seen = EXCLUDED.last_seen
|
||||
RETURNING id
|
||||
""",
|
||||
agent_id, entity_text, event_date, event_date
|
||||
bank_id, entity_text, event_date, event_date
|
||||
)
|
||||
return entity_id
|
||||
|
||||
|
|
@ -547,14 +547,14 @@ class EntityResolver:
|
|||
|
||||
async def get_entity_by_text(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
entity_text: str,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Find an entity by text (for query resolution).
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID
|
||||
bank_id: bank ID
|
||||
entity_text: Entity text to search for
|
||||
|
||||
Returns:
|
||||
|
|
@ -564,12 +564,12 @@ class EntityResolver:
|
|||
row = await conn.fetchrow(
|
||||
"""
|
||||
SELECT id FROM entities
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
AND canonical_name ILIKE $2
|
||||
ORDER BY mention_count DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
agent_id, entity_text
|
||||
bank_id, entity_text
|
||||
)
|
||||
|
||||
return row['id'] if row else None
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ class ExtractedFact(BaseModel):
|
|||
)
|
||||
|
||||
# Classification
|
||||
fact_type: Literal["world", "agent", "opinion"] = Field(
|
||||
description="'world' = facts about others (third person), 'agent' = facts about YOU the memory owner (FIRST PERSON: 'I did...'), 'opinion' = your beliefs (first person)"
|
||||
fact_type: Literal["world", "bank", "opinion"] = Field(
|
||||
description="'world' = facts about others (third person), 'bank' = facts about YOU the memory owner (FIRST PERSON: 'I did...'), 'opinion' = your beliefs (first person)"
|
||||
)
|
||||
|
||||
# Entities and relations
|
||||
|
|
@ -199,9 +199,9 @@ async def _extract_facts_from_chunk(
|
|||
|
||||
# Determine which fact types to extract based on the flag
|
||||
if extract_opinions:
|
||||
fact_types_instruction = "Extract ONLY 'opinion' type facts (the agent's formed opinions, beliefs, and perspectives). DO NOT extract 'world' or 'agent' facts."
|
||||
fact_types_instruction = "Extract ONLY 'opinion' type facts (the bank's formed opinions, beliefs, and perspectives). DO NOT extract 'world' or 'bank' facts."
|
||||
else:
|
||||
fact_types_instruction = "Extract ONLY 'world' and 'agent' type facts. DO NOT extract 'opinion' type facts - opinions should never be created during normal memory storage."
|
||||
fact_types_instruction = "Extract ONLY 'world' and 'bank' type facts. DO NOT extract 'opinion' type facts - opinions should never be created during normal memory storage."
|
||||
|
||||
prompt = f"""You are extracting comprehensive, narrative facts from conversations/document for an AI memory system.
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ def _log(log_buffer, message, level='info'):
|
|||
async def extract_entities_batch_optimized(
|
||||
entity_resolver,
|
||||
conn,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
unit_ids: List[str],
|
||||
sentences: List[str],
|
||||
context: str,
|
||||
|
|
@ -41,7 +41,7 @@ async def extract_entities_batch_optimized(
|
|||
Args:
|
||||
entity_resolver: EntityResolver instance for entity resolution
|
||||
conn: Database connection
|
||||
agent_id: Agent identifier
|
||||
agent_id: bank IDentifier
|
||||
unit_ids: List of unit IDs
|
||||
sentences: List of fact sentences
|
||||
context: Context string
|
||||
|
|
@ -114,7 +114,7 @@ async def extract_entities_batch_optimized(
|
|||
entities_data = [entity_data for _, entity_data in entities_group]
|
||||
|
||||
batch_resolved = await entity_resolver.resolve_entities_batch(
|
||||
agent_id=agent_id,
|
||||
bank_id=bank_id,
|
||||
entities_data=entities_data,
|
||||
context=context,
|
||||
unit_event_date=fact_date,
|
||||
|
|
@ -209,7 +209,7 @@ async def extract_entities_batch_optimized(
|
|||
|
||||
async def create_temporal_links_batch_per_fact(
|
||||
conn,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
unit_ids: List[str],
|
||||
time_window_hours: int = 24,
|
||||
log_buffer: List[str] = None,
|
||||
|
|
@ -222,7 +222,7 @@ async def create_temporal_links_batch_per_fact(
|
|||
|
||||
Args:
|
||||
conn: Database connection
|
||||
agent_id: Agent identifier
|
||||
agent_id: bank IDentifier
|
||||
unit_ids: List of unit IDs
|
||||
time_window_hours: Time window in hours for temporal links
|
||||
log_buffer: Optional buffer for logging
|
||||
|
|
@ -257,12 +257,12 @@ async def create_temporal_links_batch_per_fact(
|
|||
"""
|
||||
SELECT id, event_date
|
||||
FROM memory_units
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
AND event_date BETWEEN $2 AND $3
|
||||
AND id::text != ALL($4)
|
||||
ORDER BY event_date DESC
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
min_date,
|
||||
max_date,
|
||||
unit_ids
|
||||
|
|
@ -312,7 +312,7 @@ async def create_temporal_links_batch_per_fact(
|
|||
|
||||
async def create_semantic_links_batch(
|
||||
conn,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
unit_ids: List[str],
|
||||
embeddings: List[List[float]],
|
||||
top_k: int = 5,
|
||||
|
|
@ -326,7 +326,7 @@ async def create_semantic_links_batch(
|
|||
|
||||
Args:
|
||||
conn: Database connection
|
||||
agent_id: Agent identifier
|
||||
agent_id: bank IDentifier
|
||||
unit_ids: List of unit IDs
|
||||
embeddings: List of embedding vectors
|
||||
top_k: Number of top similar units to link
|
||||
|
|
@ -346,11 +346,11 @@ async def create_semantic_links_batch(
|
|||
"""
|
||||
SELECT id, embedding
|
||||
FROM memory_units
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
AND embedding IS NOT NULL
|
||||
AND id::text != ALL($2)
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
unit_ids
|
||||
)
|
||||
_log(log_buffer, f" [8.1] Fetch {len(all_existing)} existing embeddings (1 query): {time_mod.time() - fetch_start:.3f}s")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@ class MemoryFact(BaseModel):
|
|||
|
||||
id: str = Field(description="Unique identifier for the memory fact")
|
||||
text: str = Field(description="The actual text content of the memory")
|
||||
fact_type: str = Field(description="Type of fact: 'world', 'agent', 'opinion', or 'observation'")
|
||||
fact_type: str = Field(description="Type of fact: 'world', 'bank', 'opinion', or 'observation'")
|
||||
entities: Optional[List[str]] = Field(None, description="Entity names mentioned in this fact")
|
||||
context: Optional[str] = Field(None, description="Additional context for the memory")
|
||||
occurred_start: Optional[str] = Field(None, description="ISO format date when the event started occurring")
|
||||
|
|
@ -48,9 +48,9 @@ class MemoryFact(BaseModel):
|
|||
activation: Optional[float] = Field(None, description="Internal activation score")
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
class RecallResult(BaseModel):
|
||||
"""
|
||||
Result from a search operation.
|
||||
Result from a recall operation.
|
||||
|
||||
Contains a list of matching memory facts and optional trace information
|
||||
for debugging and transparency.
|
||||
|
|
@ -83,12 +83,12 @@ class SearchResult(BaseModel):
|
|||
)
|
||||
|
||||
|
||||
class ThinkResult(BaseModel):
|
||||
class ReflectResult(BaseModel):
|
||||
"""
|
||||
Result from a think operation.
|
||||
Result from a reflect operation.
|
||||
|
||||
Contains the formulated answer, the facts it was based on (organized by type),
|
||||
and any new opinions that were formed during the thinking process.
|
||||
and any new opinions that were formed during the reflection process.
|
||||
"""
|
||||
model_config = ConfigDict(json_schema_extra={
|
||||
"example": {
|
||||
|
|
@ -119,7 +119,7 @@ class ThinkResult(BaseModel):
|
|||
)
|
||||
new_opinions: List[str] = Field(
|
||||
default_factory=list,
|
||||
description="List of newly formed opinions during thinking"
|
||||
description="List of newly formed opinions during reflection"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class Opinion(BaseModel):
|
|||
"""
|
||||
An opinion with confidence score.
|
||||
|
||||
Opinions represent the agent's formed perspectives on topics,
|
||||
Opinions represent the bank's formed perspectives on topics,
|
||||
with a confidence level indicating strength of belief.
|
||||
"""
|
||||
model_config = ConfigDict(json_schema_extra={
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from ..db_utils import acquire_with_retry
|
|||
async def retrieve_semantic(
|
||||
conn,
|
||||
query_emb_str: str,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
fact_type: str,
|
||||
limit: int
|
||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||
|
|
@ -27,7 +27,7 @@ async def retrieve_semantic(
|
|||
Args:
|
||||
conn: Database connection
|
||||
query_emb_str: Query embedding as string
|
||||
agent_id: Agent ID
|
||||
agent_id: bank ID
|
||||
fact_type: Fact type to filter
|
||||
limit: Maximum results to return
|
||||
|
||||
|
|
@ -39,14 +39,14 @@ async def retrieve_semantic(
|
|||
SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, access_count, embedding, fact_type, document_id,
|
||||
1 - (embedding <=> $1::vector) AS similarity
|
||||
FROM memory_units
|
||||
WHERE agent_id = $2
|
||||
WHERE bank_id = $2
|
||||
AND embedding IS NOT NULL
|
||||
AND fact_type = $3
|
||||
AND (1 - (embedding <=> $1::vector)) >= 0.3
|
||||
ORDER BY embedding <=> $1::vector
|
||||
LIMIT $4
|
||||
""",
|
||||
query_emb_str, agent_id, fact_type, limit
|
||||
query_emb_str, bank_id, fact_type, limit
|
||||
)
|
||||
return [(str(r["id"]), dict(r)) for r in results]
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ async def retrieve_semantic(
|
|||
async def retrieve_bm25(
|
||||
conn,
|
||||
query_text: str,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
fact_type: str,
|
||||
limit: int
|
||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||
|
|
@ -64,7 +64,7 @@ async def retrieve_bm25(
|
|||
Args:
|
||||
conn: Database connection
|
||||
query_text: Query text
|
||||
agent_id: Agent ID
|
||||
agent_id: bank ID
|
||||
fact_type: Fact type to filter
|
||||
limit: Maximum results to return
|
||||
|
||||
|
|
@ -93,13 +93,13 @@ async def retrieve_bm25(
|
|||
SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, access_count, embedding, fact_type, document_id,
|
||||
ts_rank_cd(search_vector, to_tsquery('english', $1)) AS bm25_score
|
||||
FROM memory_units
|
||||
WHERE agent_id = $2
|
||||
WHERE bank_id = $2
|
||||
AND fact_type = $3
|
||||
AND search_vector @@ to_tsquery('english', $1)
|
||||
ORDER BY bm25_score DESC
|
||||
LIMIT $4
|
||||
""",
|
||||
query_tsquery, agent_id, fact_type, limit
|
||||
query_tsquery, bank_id, fact_type, limit
|
||||
)
|
||||
return [(str(r["id"]), dict(r)) for r in results]
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ async def retrieve_bm25(
|
|||
async def retrieve_graph(
|
||||
conn,
|
||||
query_emb_str: str,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
fact_type: str,
|
||||
budget: int
|
||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||
|
|
@ -117,7 +117,7 @@ async def retrieve_graph(
|
|||
Args:
|
||||
conn: Database connection
|
||||
query_emb_str: Query embedding as string
|
||||
agent_id: Agent ID
|
||||
agent_id: bank ID
|
||||
fact_type: Fact type to filter
|
||||
budget: Node budget for graph traversal
|
||||
|
||||
|
|
@ -130,14 +130,14 @@ async def retrieve_graph(
|
|||
SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, access_count, embedding, fact_type, document_id,
|
||||
1 - (embedding <=> $1::vector) AS similarity
|
||||
FROM memory_units
|
||||
WHERE agent_id = $2
|
||||
WHERE bank_id = $2
|
||||
AND embedding IS NOT NULL
|
||||
AND fact_type = $3
|
||||
AND (1 - (embedding <=> $1::vector)) >= 0.5
|
||||
ORDER BY embedding <=> $1::vector
|
||||
LIMIT 5
|
||||
""",
|
||||
query_emb_str, agent_id, fact_type
|
||||
query_emb_str, bank_id, fact_type
|
||||
)
|
||||
|
||||
if not entry_points:
|
||||
|
|
@ -221,7 +221,7 @@ async def retrieve_graph(
|
|||
async def retrieve_temporal(
|
||||
conn,
|
||||
query_emb_str: str,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
fact_type: str,
|
||||
start_date: datetime,
|
||||
end_date: datetime,
|
||||
|
|
@ -239,7 +239,7 @@ async def retrieve_temporal(
|
|||
Args:
|
||||
conn: Database connection
|
||||
query_emb_str: Query embedding as string
|
||||
agent_id: Agent ID
|
||||
agent_id: bank ID
|
||||
fact_type: Fact type to filter
|
||||
start_date: Start of time range
|
||||
end_date: End of time range
|
||||
|
|
@ -262,7 +262,7 @@ async def retrieve_temporal(
|
|||
SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, access_count, embedding, fact_type, document_id,
|
||||
1 - (embedding <=> $1::vector) AS similarity
|
||||
FROM memory_units
|
||||
WHERE agent_id = $2
|
||||
WHERE bank_id = $2
|
||||
AND fact_type = $3
|
||||
AND embedding IS NOT NULL
|
||||
AND (
|
||||
|
|
@ -282,16 +282,16 @@ async def retrieve_temporal(
|
|||
ORDER BY COALESCE(occurred_start, mentioned_at, occurred_end) DESC, (embedding <=> $1::vector) ASC
|
||||
LIMIT 10
|
||||
""",
|
||||
query_emb_str, agent_id, fact_type, start_date, end_date, semantic_threshold
|
||||
query_emb_str, bank_id, fact_type, start_date, end_date, semantic_threshold
|
||||
)
|
||||
|
||||
if not entry_points:
|
||||
# Check if there are ANY memories with temporal metadata for this agent
|
||||
# Check if there are ANY memories with temporal metadata for this bank
|
||||
total_with_dates = await conn.fetchval(
|
||||
"""SELECT COUNT(*) FROM memory_units
|
||||
WHERE agent_id = $1 AND fact_type = $2
|
||||
WHERE bank_id = $1 AND fact_type = $2
|
||||
AND (occurred_start IS NOT NULL OR occurred_end IS NOT NULL OR mentioned_at IS NOT NULL)""",
|
||||
agent_id, fact_type
|
||||
bank_id, fact_type
|
||||
)
|
||||
return []
|
||||
|
||||
|
|
@ -418,7 +418,7 @@ async def retrieve_parallel(
|
|||
pool,
|
||||
query_text: str,
|
||||
query_embedding_str: str,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
fact_type: str,
|
||||
thinking_budget: int,
|
||||
question_date: Optional[datetime] = None,
|
||||
|
|
@ -431,7 +431,7 @@ async def retrieve_parallel(
|
|||
pool: Database connection pool
|
||||
query_text: Query text
|
||||
query_embedding_str: Query embedding as string
|
||||
agent_id: Agent ID
|
||||
agent_id: bank ID
|
||||
fact_type: Fact type to filter
|
||||
thinking_budget: Budget for graph traversal and retrieval limits
|
||||
question_date: Optional date when question was asked (for temporal filtering)
|
||||
|
|
@ -461,20 +461,20 @@ async def retrieve_parallel(
|
|||
|
||||
async def run_semantic():
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
return await retrieve_semantic(conn, query_embedding_str, agent_id, fact_type, limit=thinking_budget)
|
||||
return await retrieve_semantic(conn, query_embedding_str, bank_id, fact_type, limit=thinking_budget)
|
||||
|
||||
async def run_bm25():
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
return await retrieve_bm25(conn, query_text, agent_id, fact_type, limit=thinking_budget)
|
||||
return await retrieve_bm25(conn, query_text, bank_id, fact_type, limit=thinking_budget)
|
||||
|
||||
async def run_graph():
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
return await retrieve_graph(conn, query_embedding_str, agent_id, fact_type, budget=thinking_budget)
|
||||
return await retrieve_graph(conn, query_embedding_str, bank_id, fact_type, budget=thinking_budget)
|
||||
|
||||
async def run_temporal(start_date, end_date):
|
||||
async with acquire_with_retry(pool) as conn:
|
||||
return await retrieve_temporal(
|
||||
conn, query_embedding_str, agent_id, fact_type,
|
||||
conn, query_embedding_str, bank_id, fact_type,
|
||||
start_date, end_date, budget=thinking_budget, semantic_threshold=0.4
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class QueryInfo(BaseModel):
|
|||
query_text: str = Field(description="Original query text")
|
||||
query_embedding: List[float] = Field(description="Generated query embedding vector")
|
||||
timestamp: datetime = Field(description="When the query was executed")
|
||||
thinking_budget: int = Field(description="Maximum nodes to explore")
|
||||
budget: int = Field(description="Maximum nodes to explore")
|
||||
max_tokens: int = Field(description="Maximum tokens to return in results")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class SearchTracer:
|
|||
Tracer for collecting detailed search execution information.
|
||||
|
||||
Usage:
|
||||
tracer = SearchTracer(query="Who is Alice?", thinking_budget=50, top_k=10)
|
||||
tracer = SearchTracer(query="Who is Alice?", budget=50, max_tokens=4096)
|
||||
tracer.start()
|
||||
|
||||
# During search...
|
||||
|
|
@ -44,17 +44,17 @@ class SearchTracer:
|
|||
json_output = trace.to_json()
|
||||
"""
|
||||
|
||||
def __init__(self, query: str, thinking_budget: int, max_tokens: int):
|
||||
def __init__(self, query: str, budget: int, max_tokens: int):
|
||||
"""
|
||||
Initialize tracer.
|
||||
|
||||
Args:
|
||||
query: Search query text
|
||||
thinking_budget: Maximum nodes to explore
|
||||
budget: Maximum nodes to explore
|
||||
max_tokens: Maximum tokens to return in results
|
||||
"""
|
||||
self.query_text = query
|
||||
self.thinking_budget = thinking_budget
|
||||
self.budget = budget
|
||||
self.max_tokens = max_tokens
|
||||
|
||||
# Trace data
|
||||
|
|
@ -400,7 +400,7 @@ class SearchTracer:
|
|||
query_text=self.query_text,
|
||||
query_embedding=self.query_embedding or [],
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
thinking_budget=self.thinking_budget,
|
||||
budget=self.budget,
|
||||
max_tokens=self.max_tokens,
|
||||
)
|
||||
|
||||
|
|
@ -410,7 +410,7 @@ class SearchTracer:
|
|||
total_nodes_pruned=len(self.pruned),
|
||||
entry_points_found=len(self.entry_points),
|
||||
budget_used=len(self.visits),
|
||||
budget_remaining=self.thinking_budget - len(self.visits),
|
||||
budget_remaining=self.budget - len(self.visits),
|
||||
total_duration_seconds=total_duration,
|
||||
results_returned=len(final_results),
|
||||
temporal_links_followed=self.temporal_links_followed,
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ from datetime import datetime, timezone
|
|||
from typing import Dict, List, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .response_models import ThinkResult, MemoryFact
|
||||
from .response_models import ReflectResult, MemoryFact
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Opinion(BaseModel):
|
||||
"""An opinion formed by the agent."""
|
||||
"""An opinion formed by the bank."""
|
||||
opinion: str = Field(description="The opinion or perspective with reasoning included")
|
||||
confidence: float = Field(description="Confidence score for this opinion (0.0 to 1.0, where 1.0 is very confident)")
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Document(Base):
|
|||
__tablename__ = "documents"
|
||||
|
||||
id: Mapped[str] = mapped_column(Text, primary_key=True)
|
||||
agent_id: Mapped[str] = mapped_column(Text, primary_key=True)
|
||||
bank_id: Mapped[str] = mapped_column(Text, primary_key=True)
|
||||
original_text: Mapped[Optional[str]] = mapped_column(Text)
|
||||
content_hash: Mapped[Optional[str]] = mapped_column(Text)
|
||||
doc_metadata: Mapped[dict] = mapped_column("metadata", JSONB, server_default=sql_text("'{}'::jsonb"))
|
||||
|
|
@ -49,7 +49,7 @@ class Document(Base):
|
|||
memory_units = relationship("MemoryUnit", back_populates="document", cascade="all, delete-orphan")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_documents_agent_id", "agent_id"),
|
||||
Index("idx_documents_bank_id", "bank_id"),
|
||||
Index("idx_documents_content_hash", "content_hash"),
|
||||
)
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class MemoryUnit(Base):
|
|||
id: Mapped[PyUUID] = mapped_column(
|
||||
UUID(as_uuid=True), primary_key=True, server_default=sql_text("uuid_generate_v4()")
|
||||
)
|
||||
agent_id: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
bank_id: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
document_id: Mapped[Optional[str]] = mapped_column(Text)
|
||||
text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
embedding = mapped_column(Vector(384)) # pgvector type
|
||||
|
|
@ -99,12 +99,12 @@ class MemoryUnit(Base):
|
|||
|
||||
__table_args__ = (
|
||||
ForeignKeyConstraint(
|
||||
["document_id", "agent_id"],
|
||||
["documents.id", "documents.agent_id"],
|
||||
["document_id", "bank_id"],
|
||||
["documents.id", "documents.bank_id"],
|
||||
name="memory_units_document_fkey",
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
CheckConstraint("fact_type IN ('world', 'agent', 'opinion', 'observation')"),
|
||||
CheckConstraint("fact_type IN ('world', 'bank', 'opinion', 'observation')"),
|
||||
CheckConstraint("confidence_score IS NULL OR (confidence_score >= 0.0 AND confidence_score <= 1.0)"),
|
||||
CheckConstraint(
|
||||
"(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR "
|
||||
|
|
@ -112,31 +112,31 @@ class MemoryUnit(Base):
|
|||
"(fact_type NOT IN ('opinion', 'observation') AND confidence_score IS NULL)",
|
||||
name="confidence_score_fact_type_check"
|
||||
),
|
||||
Index("idx_memory_units_agent_id", "agent_id"),
|
||||
Index("idx_memory_units_bank_id", "bank_id"),
|
||||
Index("idx_memory_units_document_id", "document_id"),
|
||||
Index("idx_memory_units_event_date", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||
Index("idx_memory_units_agent_date", "agent_id", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||
Index("idx_memory_units_bank_date", "bank_id", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||
Index("idx_memory_units_access_count", "access_count", postgresql_ops={"access_count": "DESC"}),
|
||||
Index("idx_memory_units_fact_type", "fact_type"),
|
||||
Index("idx_memory_units_agent_fact_type", "agent_id", "fact_type"),
|
||||
Index("idx_memory_units_agent_type_date", "agent_id", "fact_type", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||
Index("idx_memory_units_bank_fact_type", "bank_id", "fact_type"),
|
||||
Index("idx_memory_units_bank_type_date", "bank_id", "fact_type", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||
Index(
|
||||
"idx_memory_units_opinion_confidence",
|
||||
"agent_id",
|
||||
"bank_id",
|
||||
"confidence_score",
|
||||
postgresql_where=sql_text("fact_type = 'opinion'"),
|
||||
postgresql_ops={"confidence_score": "DESC"}
|
||||
),
|
||||
Index(
|
||||
"idx_memory_units_opinion_date",
|
||||
"agent_id",
|
||||
"bank_id",
|
||||
"event_date",
|
||||
postgresql_where=sql_text("fact_type = 'opinion'"),
|
||||
postgresql_ops={"event_date": "DESC"}
|
||||
),
|
||||
Index(
|
||||
"idx_memory_units_observation_date",
|
||||
"agent_id",
|
||||
"bank_id",
|
||||
"event_date",
|
||||
postgresql_where=sql_text("fact_type = 'observation'"),
|
||||
postgresql_ops={"event_date": "DESC"}
|
||||
|
|
@ -158,7 +158,7 @@ class Entity(Base):
|
|||
UUID(as_uuid=True), primary_key=True, server_default=sql_text("uuid_generate_v4()")
|
||||
)
|
||||
canonical_name: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
agent_id: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
bank_id: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
entity_metadata: Mapped[dict] = mapped_column("metadata", JSONB, server_default=sql_text("'{}'::jsonb"))
|
||||
first_seen: Mapped[datetime] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default=func.now()
|
||||
|
|
@ -185,9 +185,9 @@ class Entity(Base):
|
|||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_entities_agent_id", "agent_id"),
|
||||
Index("idx_entities_bank_id", "bank_id"),
|
||||
Index("idx_entities_canonical_name", "canonical_name"),
|
||||
Index("idx_entities_agent_name", "agent_id", "canonical_name"),
|
||||
Index("idx_entities_bank_name", "bank_id", "canonical_name"),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -264,6 +264,11 @@ class MemoryLink(Base):
|
|||
entity = relationship("Entity", back_populates="memory_links")
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"link_type IN ('temporal', 'semantic', 'entity', 'causes', 'caused_by', 'enables', 'prevents')",
|
||||
name="memory_links_link_type_check"
|
||||
),
|
||||
CheckConstraint("weight >= 0.0 AND weight <= 1.0", name="memory_links_weight_check"),
|
||||
Index("idx_memory_links_from", "from_unit_id"),
|
||||
Index("idx_memory_links_to", "to_unit_id"),
|
||||
Index("idx_memory_links_type", "link_type"),
|
||||
|
|
@ -278,11 +283,11 @@ class MemoryLink(Base):
|
|||
)
|
||||
|
||||
|
||||
class Agent(Base):
|
||||
"""Agent profiles with personality traits and background."""
|
||||
__tablename__ = "agents"
|
||||
class Bank(Base):
|
||||
"""Memory bank profiles with personality traits and background."""
|
||||
__tablename__ = "banks"
|
||||
|
||||
agent_id: Mapped[str] = mapped_column(Text, primary_key=True)
|
||||
bank_id: Mapped[str] = mapped_column(Text, primary_key=True)
|
||||
personality: Mapped[dict] = mapped_column(
|
||||
JSONB,
|
||||
nullable=False,
|
||||
|
|
@ -300,5 +305,5 @@ class Agent(Base):
|
|||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_agents_agent_id", "agent_id"),
|
||||
Index("idx_banks_bank_id", "bank_id"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ if __name__ == "__main__":
|
|||
"log_level": args.log_level,
|
||||
"access_log": args.access_log,
|
||||
"proxy_headers": args.proxy_headers,
|
||||
"ws": "wsproto", # Use wsproto instead of websockets to avoid deprecation warnings
|
||||
}
|
||||
|
||||
# Add optional parameters if provided
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ dependencies = [
|
|||
"langchain-text-splitters>=0.3.0",
|
||||
"fastapi[standard]>=0.120.3",
|
||||
"uvicorn>=0.38.0",
|
||||
"wsproto>=1.0.0",
|
||||
"sqlalchemy>=2.0.44",
|
||||
"alembic>=1.17.1",
|
||||
"pgvector>=0.4.1",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ Tests for agent management API (profile, personality, background).
|
|||
import pytest
|
||||
import uuid
|
||||
from hindsight_api import MemoryEngine
|
||||
from hindsight_api.api import CreateAgentRequest, PersonalityTraits
|
||||
from hindsight_api.api import CreateBankRequest, PersonalityTraits
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
|
||||
|
||||
def unique_agent_id(prefix: str) -> str:
|
||||
|
|
@ -18,9 +19,9 @@ 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."""
|
||||
agent_id = unique_agent_id("test_profile_default")
|
||||
bank_id = unique_agent_id("test_profile_default")
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert profile is not None
|
||||
assert "personality" in profile
|
||||
|
|
@ -39,9 +40,9 @@ class TestAgentProfile:
|
|||
@pytest.mark.asyncio
|
||||
async def test_update_agent_personality(self, memory: MemoryEngine):
|
||||
"""Test updating agent personality traits."""
|
||||
agent_id = unique_agent_id("test_profile_update")
|
||||
bank_id = unique_agent_id("test_profile_update")
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
assert profile["personality"]["openness"] == 0.5
|
||||
|
||||
new_personality = {
|
||||
|
|
@ -52,9 +53,9 @@ class TestAgentProfile:
|
|||
"neuroticism": 0.3,
|
||||
"bias_strength": 0.9,
|
||||
}
|
||||
await memory.update_agent_personality(agent_id, new_personality)
|
||||
await memory.update_bank_personality(bank_id, new_personality)
|
||||
|
||||
updated_profile = await memory.get_agent_profile(agent_id)
|
||||
updated_profile = await memory.get_bank_profile(bank_id)
|
||||
for key in new_personality:
|
||||
assert abs(updated_profile["personality"][key] - new_personality[key]) < 0.001
|
||||
|
||||
|
|
@ -65,19 +66,19 @@ class TestAgentProfile:
|
|||
agent_id_2 = unique_agent_id("test_list")
|
||||
agent_id_3 = unique_agent_id("test_list")
|
||||
|
||||
await memory.get_agent_profile(agent_id_1)
|
||||
await memory.get_agent_profile(agent_id_2)
|
||||
await memory.get_agent_profile(agent_id_3)
|
||||
await memory.get_bank_profile(agent_id_1)
|
||||
await memory.get_bank_profile(agent_id_2)
|
||||
await memory.get_bank_profile(agent_id_3)
|
||||
|
||||
agents = await memory.list_agents()
|
||||
agents = await memory.list_banks()
|
||||
|
||||
agent_ids = [a["agent_id"] for a in agents]
|
||||
agent_ids = [a["bank_id"] for a in agents]
|
||||
assert agent_id_1 in agent_ids
|
||||
assert agent_id_2 in agent_ids
|
||||
assert agent_id_3 in agent_ids
|
||||
|
||||
for agent in agents:
|
||||
assert "agent_id" in agent
|
||||
assert "bank_id" in agent
|
||||
assert "personality" in agent
|
||||
assert "background" in agent
|
||||
assert "created_at" in agent
|
||||
|
|
@ -90,42 +91,42 @@ class TestAgentBackground:
|
|||
@pytest.mark.asyncio
|
||||
async def test_merge_agent_background(self, memory: MemoryEngine):
|
||||
"""Test merging agent background information."""
|
||||
agent_id = unique_agent_id("test_profile_merge")
|
||||
bank_id = unique_agent_id("test_profile_merge")
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
assert profile["background"] == ""
|
||||
|
||||
result1 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Texas",
|
||||
update_personality=False
|
||||
)
|
||||
assert "Texas" in result1["background"]
|
||||
|
||||
result2 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I have 10 years of startup experience",
|
||||
update_personality=False
|
||||
)
|
||||
assert "Texas" in result2["background"] or "startup" in result2["background"]
|
||||
|
||||
final_profile = await memory.get_agent_profile(agent_id)
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
assert final_profile["background"] != ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_merge_background_handles_conflicts(self, memory: MemoryEngine):
|
||||
"""Test that merging background handles conflicts (new overwrites old)."""
|
||||
agent_id = unique_agent_id("test_profile_conflict")
|
||||
bank_id = unique_agent_id("test_profile_conflict")
|
||||
|
||||
result1 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Colorado",
|
||||
update_personality=False
|
||||
)
|
||||
assert "Colorado" in result1["background"]
|
||||
|
||||
result2 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"You were born in Texas",
|
||||
update_personality=False
|
||||
)
|
||||
|
|
@ -138,9 +139,9 @@ class TestAgentEndpoint:
|
|||
@pytest.mark.asyncio
|
||||
async def test_put_agent_create(self, memory: MemoryEngine):
|
||||
"""Test creating an agent via PUT endpoint."""
|
||||
agent_id = unique_agent_id("test_put_create")
|
||||
bank_id = unique_agent_id("test_put_create")
|
||||
|
||||
request = CreateAgentRequest(
|
||||
request = CreateBankRequest(
|
||||
personality=PersonalityTraits(
|
||||
openness=0.8,
|
||||
conscientiousness=0.6,
|
||||
|
|
@ -152,11 +153,11 @@ class TestAgentEndpoint:
|
|||
background="I am a creative software engineer"
|
||||
)
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
if request.personality is not None:
|
||||
await memory.update_agent_personality(
|
||||
agent_id,
|
||||
await memory.update_bank_personality(
|
||||
bank_id,
|
||||
request.personality.model_dump()
|
||||
)
|
||||
|
||||
|
|
@ -165,16 +166,16 @@ class TestAgentEndpoint:
|
|||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agents
|
||||
UPDATE banks
|
||||
SET background = $2,
|
||||
updated_at = NOW()
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
request.background
|
||||
)
|
||||
|
||||
final_profile = await memory.get_agent_profile(agent_id)
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert final_profile["personality"]["openness"] == 0.8
|
||||
assert final_profile["personality"]["bias_strength"] == 0.7
|
||||
|
|
@ -183,29 +184,29 @@ class TestAgentEndpoint:
|
|||
@pytest.mark.asyncio
|
||||
async def test_put_agent_partial_update(self, memory: MemoryEngine):
|
||||
"""Test updating only background."""
|
||||
agent_id = unique_agent_id("test_put_partial")
|
||||
bank_id = unique_agent_id("test_put_partial")
|
||||
|
||||
request = CreateAgentRequest(
|
||||
request = CreateBankRequest(
|
||||
background="I am a data scientist"
|
||||
)
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
if request.background is not None:
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"""
|
||||
UPDATE agents
|
||||
UPDATE banks
|
||||
SET background = $2,
|
||||
updated_at = NOW()
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
""",
|
||||
agent_id,
|
||||
bank_id,
|
||||
request.background
|
||||
)
|
||||
|
||||
final_profile = await memory.get_agent_profile(agent_id)
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
|
||||
assert final_profile["personality"]["openness"] == 0.5
|
||||
assert final_profile["background"] == "I am a data scientist"
|
||||
|
|
@ -217,7 +218,7 @@ class TestAgentPersonalityIntegration:
|
|||
@pytest.mark.asyncio
|
||||
async def test_think_uses_personality(self, memory: MemoryEngine):
|
||||
"""Test that THINK operation uses agent personality."""
|
||||
agent_id = unique_agent_id("test_think")
|
||||
bank_id = unique_agent_id("test_think")
|
||||
|
||||
personality = {
|
||||
"openness": 0.9,
|
||||
|
|
@ -227,16 +228,16 @@ class TestAgentPersonalityIntegration:
|
|||
"neuroticism": 0.7,
|
||||
"bias_strength": 0.9,
|
||||
}
|
||||
await memory.update_agent_personality(agent_id, personality)
|
||||
await memory.update_bank_personality(bank_id, personality)
|
||||
|
||||
await memory.merge_agent_background(
|
||||
agent_id,
|
||||
await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a creative artist who values innovation over tradition",
|
||||
update_personality=False
|
||||
)
|
||||
|
||||
await memory.put_batch_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_batch_async(
|
||||
bank_id=bank_id,
|
||||
contents=[
|
||||
{"content": "Traditional painting techniques have been used for centuries"},
|
||||
{"content": "Modern digital art is changing the art world"}
|
||||
|
|
@ -244,10 +245,10 @@ class TestAgentPersonalityIntegration:
|
|||
document_id="art_facts"
|
||||
)
|
||||
|
||||
result = await memory.think_async(
|
||||
agent_id=agent_id,
|
||||
result = await memory.reflect_async(
|
||||
bank_id=bank_id,
|
||||
query="What do you think about traditional vs modern art?",
|
||||
thinking_budget=50
|
||||
budget=Budget.LOW
|
||||
)
|
||||
|
||||
assert result.text is not None
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import os
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_large_batch_auto_chunks(memory):
|
||||
agent_id = "test_chunking_agent"
|
||||
bank_id = "test_chunking_agent"
|
||||
# Create a large batch that should trigger chunking
|
||||
# Each item is ~2000 chars, so 30 items = 60k chars (exceeds 50k threshold)
|
||||
large_content = "Alice met with Bob at the coffee shop. " * 50 # ~2000 chars
|
||||
|
|
@ -22,8 +22,8 @@ async def test_large_batch_auto_chunks(memory):
|
|||
print(f"Should trigger chunking: {total_chars > 50_000}")
|
||||
|
||||
# Ingest the large batch (should auto-chunk)
|
||||
result = await memory.put_batch_async(
|
||||
agent_id=agent_id,
|
||||
result = await memory.retain_batch_async(
|
||||
bank_id=bank_id,
|
||||
contents=contents
|
||||
)
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ async def test_large_batch_auto_chunks(memory):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_small_batch_no_chunking(memory):
|
||||
agent_id = "test_no_chunking_agent"
|
||||
bank_id = "test_no_chunking_agent"
|
||||
|
||||
# Create a small batch that should NOT trigger chunking
|
||||
contents = [
|
||||
|
|
@ -48,8 +48,8 @@ async def test_small_batch_no_chunking(memory):
|
|||
print(f"Should NOT trigger chunking: {total_chars <= 50_000}")
|
||||
|
||||
# Ingest the small batch (should NOT auto-chunk)
|
||||
result = await memory.put_batch_async(
|
||||
agent_id=agent_id,
|
||||
result = await memory.retain_batch_async(
|
||||
bank_id=bank_id,
|
||||
contents=contents
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,62 +9,62 @@ from datetime import datetime, timezone
|
|||
@pytest.mark.asyncio
|
||||
async def test_document_creation_and_retrieval(memory):
|
||||
"""Test that documents are created and can be retrieved."""
|
||||
agent_id = f"test_doc_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_doc_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
document_id = "meeting-001"
|
||||
|
||||
# Store memory with document tracking
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google. Bob works at Microsoft.",
|
||||
context="Team meeting",
|
||||
document_id=document_id
|
||||
)
|
||||
|
||||
# Retrieve document
|
||||
doc = await memory.get_document(document_id, agent_id)
|
||||
doc = await memory.get_document(document_id, bank_id)
|
||||
|
||||
assert doc is not None
|
||||
assert doc["id"] == document_id
|
||||
assert doc["agent_id"] == agent_id
|
||||
assert doc["bank_id"] == bank_id
|
||||
assert "Alice works at Google" in doc["original_text"]
|
||||
assert doc["memory_unit_count"] > 0
|
||||
|
||||
finally:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_document_upsert(memory):
|
||||
"""Test that providing the same document_id automatically upserts (deletes old units and creates new ones)."""
|
||||
agent_id = f"test_upsert_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_upsert_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
document_id = "meeting-002"
|
||||
|
||||
# First version
|
||||
units_v1 = await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
units_v1 = await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google.",
|
||||
context="Initial",
|
||||
document_id=document_id
|
||||
)
|
||||
|
||||
# Get document stats
|
||||
doc_v1 = await memory.get_document(document_id, agent_id)
|
||||
doc_v1 = await memory.get_document(document_id, bank_id)
|
||||
count_v1 = doc_v1["memory_unit_count"]
|
||||
|
||||
# Update with different content (automatic upsert when same document_id is provided)
|
||||
units_v2 = await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
units_v2 = await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Microsoft. Bob works at Apple.",
|
||||
context="Updated",
|
||||
document_id=document_id
|
||||
)
|
||||
|
||||
# Get updated document stats
|
||||
doc_v2 = await memory.get_document(document_id, agent_id)
|
||||
doc_v2 = await memory.get_document(document_id, bank_id)
|
||||
count_v2 = doc_v2["memory_unit_count"]
|
||||
|
||||
# Verify old units were replaced
|
||||
|
|
@ -75,52 +75,52 @@ async def test_document_upsert(memory):
|
|||
assert set(units_v1).isdisjoint(set(units_v2))
|
||||
|
||||
finally:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_document_deletion(memory):
|
||||
"""Test that deleting a document cascades to memory units."""
|
||||
agent_id = f"test_delete_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_delete_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
document_id = "meeting-003"
|
||||
|
||||
# Create document
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google.",
|
||||
context="Test",
|
||||
document_id=document_id
|
||||
)
|
||||
|
||||
# Verify it exists
|
||||
doc = await memory.get_document(document_id, agent_id)
|
||||
doc = await memory.get_document(document_id, bank_id)
|
||||
assert doc is not None
|
||||
assert doc["memory_unit_count"] > 0
|
||||
|
||||
# Delete document
|
||||
result = await memory.delete_document(document_id, agent_id)
|
||||
result = await memory.delete_document(document_id, bank_id)
|
||||
assert result["document_deleted"] == 1
|
||||
assert result["memory_units_deleted"] > 0
|
||||
|
||||
# Verify it's gone
|
||||
doc_after = await memory.get_document(document_id, agent_id)
|
||||
doc_after = await memory.get_document(document_id, bank_id)
|
||||
assert doc_after is None
|
||||
|
||||
finally:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_memory_without_document(memory):
|
||||
"""Test that memories can still be created without document tracking."""
|
||||
agent_id = f"test_no_doc_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_no_doc_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Create memory without document_id (backward compatibility)
|
||||
units = await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
units = await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google.",
|
||||
context="Test"
|
||||
)
|
||||
|
|
@ -128,4 +128,4 @@ async def test_memory_without_document(memory):
|
|||
assert len(units) > 0
|
||||
|
||||
finally:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@ class TestFactClassification:
|
|||
|
||||
This test addresses the issue where podcast transcripts with context like
|
||||
"this was podcast episode between you (Marcus) and Jamie" were extracting
|
||||
all facts as 'world' instead of properly identifying Marcus's statements as 'agent'.
|
||||
all facts as 'world' instead of properly identifying Marcus's statements as 'bank'.
|
||||
"""
|
||||
|
||||
transcript = """
|
||||
|
|
@ -808,7 +808,7 @@ Jamie: Congratulations! I'd love to read it.
|
|||
agent_facts = [f for f in facts if f["fact_type"] == "agent"]
|
||||
|
||||
assert len(agent_facts) > 0, \
|
||||
f"Should have at least one 'agent' fact when context identifies 'you (Marcus)'. " \
|
||||
f"Should have at least one 'bank' fact when context identifies 'you (Marcus)'. " \
|
||||
f"Got facts: {[f['fact'] + ' [' + f['fact_type'] + ']' for f in facts]}"
|
||||
|
||||
for agent_fact in agent_facts:
|
||||
|
|
@ -1019,10 +1019,10 @@ class TestPersonalityInference:
|
|||
async def test_background_merge_with_personality_inference(self, memory):
|
||||
"""Test that background merge infers personality traits by default."""
|
||||
import uuid
|
||||
agent_id = f"test_infer_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_infer_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a creative software engineer who loves innovation and trying new technologies",
|
||||
update_personality=True
|
||||
)
|
||||
|
|
@ -1049,13 +1049,13 @@ class TestPersonalityInference:
|
|||
async def test_background_merge_without_personality_inference(self, memory):
|
||||
"""Test that background merge skips personality inference when disabled."""
|
||||
import uuid
|
||||
agent_id = f"test_no_infer_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_no_infer_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
initial_profile = await memory.get_agent_profile(agent_id)
|
||||
initial_profile = await memory.get_bank_profile(bank_id)
|
||||
initial_personality = initial_profile["personality"]
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a data scientist",
|
||||
update_personality=False
|
||||
)
|
||||
|
|
@ -1063,7 +1063,7 @@ class TestPersonalityInference:
|
|||
assert "background" in result
|
||||
assert "personality" not in result
|
||||
|
||||
final_profile = await memory.get_agent_profile(agent_id)
|
||||
final_profile = await memory.get_bank_profile(bank_id)
|
||||
final_personality = final_profile["personality"]
|
||||
|
||||
assert initial_personality == final_personality
|
||||
|
|
@ -1072,10 +1072,10 @@ class TestPersonalityInference:
|
|||
async def test_personality_inference_for_organized_engineer(self, memory):
|
||||
"""Test personality inference for organized/conscientious profile."""
|
||||
import uuid
|
||||
agent_id = f"test_organized_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_organized_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a methodical engineer who values organization and systematic planning",
|
||||
update_personality=True
|
||||
)
|
||||
|
|
@ -1088,10 +1088,10 @@ class TestPersonalityInference:
|
|||
async def test_personality_inference_for_startup_founder(self, memory):
|
||||
"""Test personality inference for entrepreneurial profile."""
|
||||
import uuid
|
||||
agent_id = f"test_founder_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_founder_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a startup founder who thrives on risk and social interaction",
|
||||
update_personality=True
|
||||
)
|
||||
|
|
@ -1105,17 +1105,17 @@ class TestPersonalityInference:
|
|||
async def test_personality_updates_in_database(self, memory):
|
||||
"""Test that inferred personality is actually stored in database."""
|
||||
import uuid
|
||||
agent_id = f"test_db_update_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_db_update_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am an innovative designer",
|
||||
update_personality=True
|
||||
)
|
||||
|
||||
inferred_personality = result["personality"]
|
||||
|
||||
profile = await memory.get_agent_profile(agent_id)
|
||||
profile = await memory.get_bank_profile(bank_id)
|
||||
db_personality = profile["personality"]
|
||||
|
||||
assert db_personality == inferred_personality
|
||||
|
|
@ -1124,17 +1124,17 @@ class TestPersonalityInference:
|
|||
async def test_multiple_background_merges_update_personality(self, memory):
|
||||
"""Test that each background merge can update personality."""
|
||||
import uuid
|
||||
agent_id = f"test_multi_merge_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_multi_merge_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
result1 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result1 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I am a software engineer",
|
||||
update_personality=True
|
||||
)
|
||||
personality1 = result1["personality"]
|
||||
|
||||
result2 = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result2 = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I love creative problem solving and innovation",
|
||||
update_personality=True
|
||||
)
|
||||
|
|
@ -1147,16 +1147,16 @@ class TestPersonalityInference:
|
|||
async def test_background_merge_conflict_resolution_with_personality(self, memory):
|
||||
"""Test that conflicts are resolved and personality reflects final background."""
|
||||
import uuid
|
||||
agent_id = f"test_conflict_{uuid.uuid4().hex[:8]}"
|
||||
bank_id = f"test_conflict_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
await memory.merge_agent_background(
|
||||
agent_id,
|
||||
await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"I was born in Colorado and prefer stability",
|
||||
update_personality=True
|
||||
)
|
||||
|
||||
result = await memory.merge_agent_background(
|
||||
agent_id,
|
||||
result = await memory.merge_bank_background(
|
||||
bank_id,
|
||||
"You were born in Texas and love taking risks",
|
||||
update_personality=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ distinguish between things said earlier vs later.
|
|||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from hindsight_api import MemoryEngine
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
import os
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fact_ordering_within_conversation(memory):
|
||||
agent_id = "test_ordering_agent"
|
||||
bank_id = "test_ordering_agent"
|
||||
|
||||
# Get/create agent (auto-creates with defaults)
|
||||
await memory.get_agent_profile(agent_id)
|
||||
await memory.get_bank_profile(bank_id)
|
||||
|
||||
# Update personality to match Marcus
|
||||
await memory.update_agent_personality(agent_id, {
|
||||
await memory.update_bank_personality(bank_id, {
|
||||
"openness": 0.7,
|
||||
"conscientiousness": 0.6,
|
||||
"extraversion": 0.8,
|
||||
|
|
@ -40,8 +41,8 @@ Marcus: Yeah, I realized I was being too optimistic about their defense.
|
|||
base_event_date = datetime(2024, 11, 14, 10, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
# Store the conversation
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=conversation,
|
||||
context="podcast discussion about NFL game",
|
||||
event_date=base_event_date,
|
||||
|
|
@ -49,28 +50,28 @@ Marcus: Yeah, I realized I was being too optimistic about their defense.
|
|||
)
|
||||
|
||||
# Search for all facts about Marcus's predictions
|
||||
results = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
results = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="Marcus prediction Rams",
|
||||
fact_type=['agent', 'world'],
|
||||
thinking_budget=100,
|
||||
fact_type=['bank', 'world'],
|
||||
budget=Budget.LOW,
|
||||
max_tokens=8192
|
||||
)
|
||||
|
||||
print(f"\n=== Retrieved {len(results.results)} facts ===")
|
||||
for i, result in enumerate(results.results):
|
||||
print(f"{i+1}. [{result.event_date}] {result.text[:100]}")
|
||||
print(f"{i+1}. [{result.mentioned_at}] {result.text[:100]}")
|
||||
|
||||
# Get all agent facts (Marcus's statements)
|
||||
agent_facts = [r for r in results.results if r.fact_type == 'agent']
|
||||
agent_facts = [r for r in results.results if r.fact_type == 'bank']
|
||||
|
||||
print(f"\n=== Agent facts (Marcus's statements) ===")
|
||||
for i, fact in enumerate(agent_facts):
|
||||
print(f"{i+1}. [{fact.event_date}] {fact.text}")
|
||||
print(f"{i+1}. [{fact.mentioned_at}] {fact.text}")
|
||||
|
||||
# Check that agent facts have different timestamps
|
||||
if len(agent_facts) >= 2:
|
||||
timestamps = [datetime.fromisoformat(f.event_date.replace('Z', '+00:00')) for f in agent_facts]
|
||||
timestamps = [datetime.fromisoformat(f.mentioned_at.replace('Z', '+00:00')) for f in agent_facts]
|
||||
|
||||
# Verify timestamps are different (have time offsets)
|
||||
unique_timestamps = set(timestamps)
|
||||
|
|
@ -115,7 +116,7 @@ Marcus: Yeah, I realized I was being too optimistic about their defense.
|
|||
print(f"\n✅ Temporal ordering preserved: First prediction came before changed prediction")
|
||||
|
||||
# Cleanup
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
print(f"\n✅ Test passed: Fact ordering within conversation is preserved")
|
||||
|
||||
|
|
@ -123,9 +124,9 @@ Marcus: Yeah, I realized I was being too optimistic about their defense.
|
|||
@pytest.mark.asyncio
|
||||
async def test_multiple_documents_ordering(memory):
|
||||
|
||||
agent_id = "test_multi_doc_agent"
|
||||
bank_id = "test_multi_doc_agent"
|
||||
|
||||
await memory.get_agent_profile(agent_id) # Auto-creates with defaults
|
||||
await memory.get_bank_profile(bank_id) # Auto-creates with defaults
|
||||
|
||||
# Two separate conversations with same base time
|
||||
base_time = datetime(2024, 11, 14, 10, 0, 0, tzinfo=timezone.utc)
|
||||
|
|
@ -143,8 +144,8 @@ Alice: I reconsidered the team's experience level.
|
|||
"""
|
||||
|
||||
# Store both conversations with batch
|
||||
await memory.put_batch_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_batch_async(
|
||||
bank_id=bank_id,
|
||||
contents=[
|
||||
{"content": conv1, "context": "project discussion 1", "event_date": base_time},
|
||||
{"content": conv2, "context": "project discussion 2", "event_date": base_time}
|
||||
|
|
@ -152,23 +153,23 @@ Alice: I reconsidered the team's experience level.
|
|||
)
|
||||
|
||||
# Search for Alice's preferences
|
||||
results = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
results = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="Alice preference React Vue",
|
||||
fact_type=['agent'],
|
||||
thinking_budget=100,
|
||||
fact_type=['bank'],
|
||||
budget=Budget.LOW,
|
||||
max_tokens=8192
|
||||
)
|
||||
|
||||
print(f"\n=== Retrieved {len(results.results)} agent facts ===")
|
||||
agent_facts = [r for r in results.results if r.fact_type == 'agent']
|
||||
agent_facts = [r for r in results.results if r.fact_type == 'bank']
|
||||
|
||||
for i, fact in enumerate(agent_facts):
|
||||
print(f"{i+1}. [{fact.event_date}] {fact.text[:80]}")
|
||||
print(f"{i+1}. [{fact.mentioned_at}] {fact.text[:80]}")
|
||||
|
||||
# Each conversation's facts should have different timestamps
|
||||
if len(agent_facts) >= 2:
|
||||
timestamps = [datetime.fromisoformat(f.event_date.replace('Z', '+00:00')) for f in agent_facts]
|
||||
timestamps = [datetime.fromisoformat(f.mentioned_at.replace('Z', '+00:00')) for f in agent_facts]
|
||||
unique_timestamps = set(timestamps)
|
||||
|
||||
assert len(unique_timestamps) >= 2, \
|
||||
|
|
@ -177,6 +178,6 @@ Alice: I reconsidered the team's experience level.
|
|||
print(f"\n✅ Facts from {len(agent_facts)} statements have {len(unique_timestamps)} unique timestamps")
|
||||
|
||||
# Cleanup
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
print(f"\n✅ Test passed: Multiple documents maintain separate ordering")
|
||||
|
|
|
|||
|
|
@ -21,50 +21,52 @@ async def api_client(memory):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def test_agent_id():
|
||||
"""Provide a unique agent ID for this test run."""
|
||||
def test_bank_id():
|
||||
"""Provide a unique bank ID for this test run."""
|
||||
return f"integration_test_{datetime.now().timestamp()}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_full_api_workflow(api_client, test_agent_id):
|
||||
async def test_full_api_workflow(api_client, test_bank_id):
|
||||
"""
|
||||
End-to-end test covering all major API endpoints in a realistic workflow.
|
||||
|
||||
Workflow:
|
||||
1. Create agent and set profile
|
||||
2. Store memories (put, batch put)
|
||||
3. Search memories
|
||||
4. Think (generate answer)
|
||||
5. List agents and memories
|
||||
6. Get agent profile
|
||||
1. Create bank and set profile
|
||||
2. Store memories (retain)
|
||||
3. Recall memories
|
||||
4. Reflect (generate answer)
|
||||
5. List banks and memories
|
||||
6. Get bank profile
|
||||
7. Get visualization data
|
||||
8. Track documents
|
||||
9. Clean up
|
||||
9. Test entity endpoints
|
||||
10. Test operations endpoints
|
||||
11. Clean up
|
||||
"""
|
||||
|
||||
# ================================================================
|
||||
# 1. Agent Management
|
||||
# 1. Bank Management
|
||||
# ================================================================
|
||||
|
||||
# List agents (should be empty initially or have other test agents)
|
||||
response = await api_client.get("/api/v1/agents")
|
||||
# List banks (should be empty initially or have other test banks)
|
||||
response = await api_client.get("/v1/default/banks")
|
||||
assert response.status_code == 200
|
||||
initial_agents_data = response.json()["agents"]
|
||||
initial_agents = [a["agent_id"] for a in initial_agents_data]
|
||||
print(f"Initial agents: {len(initial_agents)}")
|
||||
initial_banks_data = response.json()["banks"]
|
||||
initial_banks = [a["bank_id"] for a in initial_banks_data]
|
||||
print(f"Initial banks: {len(initial_banks)}")
|
||||
|
||||
# Get agent profile (creates default if not exists)
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/profile")
|
||||
# Get bank profile (creates default if not exists)
|
||||
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 "background" in profile
|
||||
print(f"Agent profile created with personality: {profile['personality']}")
|
||||
print(f"Bank profile created with personality: {profile['personality']}")
|
||||
|
||||
# Add background
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/background",
|
||||
f"/v1/default/banks/{test_bank_id}/background",
|
||||
json={
|
||||
"content": "A software engineer passionate about AI and memory systems."
|
||||
}
|
||||
|
|
@ -79,7 +81,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
|
||||
# Store single memory (using batch endpoint with single item)
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/memories",
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -97,7 +99,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
|
||||
# Store batch memories
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/memories",
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -118,12 +120,12 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
print(f"Stored {batch_result['items_count']} items from batch put")
|
||||
|
||||
# ================================================================
|
||||
# 3. Search
|
||||
# 3. Recall (Search)
|
||||
# ================================================================
|
||||
|
||||
# Search for memories
|
||||
# Recall memories
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/memories/search",
|
||||
f"/v1/default/banks/{test_bank_id}/memories/recall",
|
||||
json={
|
||||
"query": "Who works on machine learning?",
|
||||
"thinking_budget": 50
|
||||
|
|
@ -140,12 +142,12 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
assert found_alice, "Should find Alice in search results"
|
||||
|
||||
# ================================================================
|
||||
# 4. Think (Reasoning)
|
||||
# 4. Reflect (Reasoning)
|
||||
# ================================================================
|
||||
|
||||
# Generate answer using think
|
||||
# Generate answer using reflect
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/think",
|
||||
f"/v1/default/banks/{test_bank_id}/reflect",
|
||||
json={
|
||||
"query": "What do you know about the team members?",
|
||||
"thinking_budget": 30,
|
||||
|
|
@ -153,14 +155,14 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
think_result = response.json()
|
||||
assert "text" in think_result
|
||||
assert len(think_result["text"]) > 0
|
||||
assert "based_on" in think_result
|
||||
print(f"Think response: {think_result['text'][:100]}...")
|
||||
reflect_result = response.json()
|
||||
assert "text" in reflect_result
|
||||
assert len(reflect_result["text"]) > 0
|
||||
assert "based_on" in reflect_result
|
||||
print(f"Reflect response: {reflect_result['text'][:100]}...")
|
||||
|
||||
# Verify the answer mentions team members
|
||||
answer = think_result["text"].lower()
|
||||
answer = reflect_result["text"].lower()
|
||||
assert "alice" in answer or "bob" in answer or "charlie" in answer
|
||||
|
||||
# ================================================================
|
||||
|
|
@ -168,7 +170,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
# ================================================================
|
||||
|
||||
# Get graph data
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/graph")
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/graph")
|
||||
assert response.status_code == 200
|
||||
graph_data = response.json()
|
||||
assert "nodes" in graph_data
|
||||
|
|
@ -176,7 +178,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
print(f"Graph has {len(graph_data['nodes'])} nodes and {len(graph_data['edges'])} edges")
|
||||
|
||||
# Get memory statistics
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/stats")
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/stats")
|
||||
assert response.status_code == 200
|
||||
stats = response.json()
|
||||
assert "total_nodes" in stats
|
||||
|
|
@ -185,7 +187,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
|
||||
# List memory units
|
||||
response = await api_client.get(
|
||||
f"/api/v1/agents/{test_agent_id}/memories/list",
|
||||
f"/v1/default/banks/{test_bank_id}/memories/list",
|
||||
params={"limit": 10}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
|
@ -200,7 +202,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
|
||||
# Store memory with document
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/memories",
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -215,7 +217,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
print("Stored memory with document tracking")
|
||||
|
||||
# List documents
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/documents")
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/documents")
|
||||
assert response.status_code == 200
|
||||
documents = response.json()
|
||||
assert "items" in documents
|
||||
|
|
@ -224,7 +226,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
|
||||
# Get specific document
|
||||
response = await api_client.get(
|
||||
f"/api/v1/agents/{test_agent_id}/documents/roadmap-2024-q1"
|
||||
f"/v1/default/banks/{test_bank_id}/documents/roadmap-2024-q1"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc_info = response.json()
|
||||
|
|
@ -235,35 +237,81 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
|||
# Note: Document deletion is tested separately in test_document_deletion
|
||||
|
||||
# ================================================================
|
||||
# 7. Verify Updated Agent Profile
|
||||
# 7. Update and Verify Bank Personality
|
||||
# ================================================================
|
||||
|
||||
# Check profile again (might have formed new opinions)
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/profile")
|
||||
# Update personality 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
|
||||
}
|
||||
}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
print("Personality updated")
|
||||
|
||||
# Check profile again (should have updated personality)
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
|
||||
assert response.status_code == 200
|
||||
updated_profile = response.json()
|
||||
assert "software engineer" in updated_profile["background"].lower()
|
||||
print("Profile verified")
|
||||
|
||||
# ================================================================
|
||||
# 8. List All Agents (should include our test agent)
|
||||
# 8. Test Entity Endpoints
|
||||
# ================================================================
|
||||
|
||||
response = await api_client.get("/api/v1/agents")
|
||||
# List entities
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/entities")
|
||||
assert response.status_code == 200
|
||||
final_agents_data = response.json()["agents"]
|
||||
final_agents = [a["agent_id"] for a in final_agents_data]
|
||||
assert test_agent_id in final_agents
|
||||
assert len(final_agents) >= len(initial_agents) + 1
|
||||
print(f"Final agent count: {len(final_agents)}")
|
||||
entities_data = response.json()
|
||||
assert "items" in entities_data
|
||||
print(f"Found {len(entities_data['items'])} entities")
|
||||
|
||||
# Get specific entity if any exist
|
||||
if len(entities_data['items']) > 0:
|
||||
entity_id = entities_data['items'][0]['id']
|
||||
response = await api_client.get(
|
||||
f"/v1/default/banks/{test_bank_id}/entities/{entity_id}"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
entity_detail = response.json()
|
||||
assert "id" in entity_detail
|
||||
print(f"Retrieved entity: {entity_detail.get('name', entity_id)}")
|
||||
|
||||
# Test regenerate observations
|
||||
response = await api_client.post(
|
||||
f"/v1/default/banks/{test_bank_id}/entities/{entity_id}/regenerate"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
print(f"Regenerated observations for entity {entity_id}")
|
||||
|
||||
# ================================================================
|
||||
# 9. Clean Up
|
||||
# 9. List All Banks (should include our test bank)
|
||||
# ================================================================
|
||||
|
||||
# Note: No delete agent endpoint in API, so test data remains in DB
|
||||
# Using timestamped agent IDs prevents conflicts between test runs
|
||||
print(f"Integration test complete for agent {test_agent_id}")
|
||||
response = await api_client.get("/v1/default/banks")
|
||||
assert response.status_code == 200
|
||||
final_banks_data = response.json()["banks"]
|
||||
final_banks = [a["bank_id"] for a in final_banks_data]
|
||||
assert test_bank_id in final_banks
|
||||
assert len(final_banks) >= len(initial_banks) + 1
|
||||
print(f"Final bank count: {len(final_banks)}")
|
||||
|
||||
# ================================================================
|
||||
# 10. Clean Up
|
||||
# ================================================================
|
||||
|
||||
# Note: No delete bank endpoint in API, so test data remains in DB
|
||||
# Using timestamped bank IDs prevents conflicts between test runs
|
||||
print(f"Integration test complete for bank {test_bank_id}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -272,7 +320,7 @@ async def test_error_handling(api_client):
|
|||
|
||||
# Invalid request (missing required field)
|
||||
response = await api_client.post(
|
||||
"/api/v1/agents/error_test/memories",
|
||||
"/v1/default/banks/error_test/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -284,19 +332,19 @@ async def test_error_handling(api_client):
|
|||
)
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
||||
# Search with invalid parameters
|
||||
# Recall with invalid parameters
|
||||
response = await api_client.post(
|
||||
"/api/v1/agents/error_test/memories/search",
|
||||
"/v1/default/banks/error_test/memories/recall",
|
||||
json={
|
||||
"query": "test",
|
||||
"thinking_budget": -1 # Invalid negative budget
|
||||
"budget": "invalid_budget" # Invalid budget value (should be low/mid/high)
|
||||
}
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
# Get non-existent document
|
||||
response = await api_client.get(
|
||||
"/api/v1/agents/nonexistent_agent/documents/fake-doc-id"
|
||||
"/v1/default/banks/nonexistent_bank/documents/fake-doc-id"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
|
@ -306,7 +354,7 @@ async def test_error_handling(api_client):
|
|||
@pytest.mark.asyncio
|
||||
async def test_concurrent_requests(api_client):
|
||||
"""Test that API can handle concurrent requests."""
|
||||
agent_id = f"concurrent_test_{datetime.now().timestamp()}"
|
||||
bank_id = f"concurrent_test_{datetime.now().timestamp()}"
|
||||
|
||||
# Store multiple memories concurrently (simulated with sequential calls)
|
||||
responses = []
|
||||
|
|
@ -319,7 +367,7 @@ async def test_concurrent_requests(api_client):
|
|||
]
|
||||
for fact in test_facts:
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{agent_id}/memories",
|
||||
f"/v1/default/banks/{bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -337,7 +385,7 @@ async def test_concurrent_requests(api_client):
|
|||
|
||||
# Verify all facts stored
|
||||
response = await api_client.get(
|
||||
f"/api/v1/agents/{agent_id}/memories/list",
|
||||
f"/v1/default/banks/{bank_id}/memories/list",
|
||||
params={"limit": 20}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
|
@ -350,11 +398,11 @@ async def test_concurrent_requests(api_client):
|
|||
@pytest.mark.asyncio
|
||||
async def test_document_deletion(api_client):
|
||||
"""Test document deletion including cascade deletion of memory units and links."""
|
||||
test_agent_id = f"doc_delete_test_{datetime.now().timestamp()}"
|
||||
test_bank_id = f"doc_delete_test_{datetime.now().timestamp()}"
|
||||
|
||||
# Store a document with memory
|
||||
response = await api_client.post(
|
||||
f"/api/v1/agents/{test_agent_id}/memories",
|
||||
f"/v1/default/banks/{test_bank_id}/memories",
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -370,7 +418,7 @@ async def test_document_deletion(api_client):
|
|||
|
||||
# Verify document exists
|
||||
response = await api_client.get(
|
||||
f"/api/v1/agents/{test_agent_id}/documents/sales-report-q1-2024"
|
||||
f"/v1/default/banks/{test_bank_id}/documents/sales-report-q1-2024"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc_info = response.json()
|
||||
|
|
@ -380,7 +428,7 @@ async def test_document_deletion(api_client):
|
|||
|
||||
# Delete the document
|
||||
response = await api_client.delete(
|
||||
f"/api/v1/agents/{test_agent_id}/documents/sales-report-q1-2024"
|
||||
f"/v1/default/banks/{test_bank_id}/documents/sales-report-q1-2024"
|
||||
)
|
||||
assert response.status_code == 200
|
||||
delete_result = response.json()
|
||||
|
|
@ -391,13 +439,13 @@ async def test_document_deletion(api_client):
|
|||
|
||||
# Verify document is gone (should return 404)
|
||||
response = await api_client.get(
|
||||
f"/api/v1/agents/{test_agent_id}/documents/sales-report-q1-2024"
|
||||
f"/v1/default/banks/{test_bank_id}/documents/sales-report-q1-2024"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
print("Document deletion verified - returns 404")
|
||||
|
||||
# Verify document is not in the list
|
||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/documents")
|
||||
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/documents")
|
||||
assert response.status_code == 200
|
||||
documents = response.json()
|
||||
doc_ids = [doc["id"] for doc in documents["items"]]
|
||||
|
|
@ -406,7 +454,7 @@ async def test_document_deletion(api_client):
|
|||
|
||||
# Try to delete again (should return 404)
|
||||
response = await api_client.delete(
|
||||
f"/api/v1/agents/{test_agent_id}/documents/sales-report-q1-2024"
|
||||
f"/v1/default/banks/{test_bank_id}/documents/sales-report-q1-2024"
|
||||
)
|
||||
assert response.status_code == 404
|
||||
print("Double delete returns 404 - verified")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Test observation generation and entity state functionality.
|
||||
"""
|
||||
import pytest
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
|
|
@ -14,19 +15,19 @@ async def test_observation_generation_on_put(memory):
|
|||
2. Wait for background tasks (observation generation)
|
||||
3. Verify observations were created and linked to the entity
|
||||
"""
|
||||
agent_id = f"test_obs_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_obs_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store some facts about an entity
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
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)
|
||||
)
|
||||
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
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)
|
||||
|
|
@ -42,10 +43,10 @@ async def test_observation_generation_on_put(memory):
|
|||
"""
|
||||
SELECT id, canonical_name
|
||||
FROM entities
|
||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%john%'
|
||||
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%john%'
|
||||
LIMIT 1
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
if entity_row:
|
||||
|
|
@ -55,7 +56,7 @@ async def test_observation_generation_on_put(memory):
|
|||
print(f"Entity: {entity_name} (id: {entity_id})")
|
||||
|
||||
# Get observations for the entity
|
||||
observations = await memory.get_entity_observations(agent_id, entity_id, limit=10)
|
||||
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10)
|
||||
|
||||
print(f"\n=== Observations for {entity_name} ===")
|
||||
print(f"Total observations: {len(observations)}")
|
||||
|
|
@ -79,8 +80,8 @@ async def test_observation_generation_on_put(memory):
|
|||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE agent_id = $1", agent_id)
|
||||
await conn.execute("DELETE FROM entities WHERE agent_id = $1", agent_id)
|
||||
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
|
||||
|
|
@ -88,12 +89,12 @@ async def test_regenerate_entity_observations(memory):
|
|||
"""
|
||||
Test explicit regeneration of observations for an entity.
|
||||
"""
|
||||
agent_id = f"test_regen_obs_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_regen_obs_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store facts about an entity
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Sarah is a product manager who loves user research and data analysis.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
|
|
@ -108,10 +109,10 @@ async def test_regenerate_entity_observations(memory):
|
|||
"""
|
||||
SELECT id, canonical_name
|
||||
FROM entities
|
||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%sarah%'
|
||||
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%sarah%'
|
||||
LIMIT 1
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
if entity_row:
|
||||
|
|
@ -120,7 +121,7 @@ async def test_regenerate_entity_observations(memory):
|
|||
|
||||
# Manually regenerate observations
|
||||
created_ids = await memory.regenerate_entity_observations(
|
||||
agent_id=agent_id,
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
entity_name=entity_name
|
||||
)
|
||||
|
|
@ -129,7 +130,7 @@ async def test_regenerate_entity_observations(memory):
|
|||
print(f"Created {len(created_ids)} observations for {entity_name}")
|
||||
|
||||
# Get the observations
|
||||
observations = await memory.get_entity_observations(agent_id, entity_id, limit=10)
|
||||
observations = await memory.get_entity_observations(bank_id, entity_id, limit=10)
|
||||
for obs in observations:
|
||||
print(f" - {obs.text}")
|
||||
|
||||
|
|
@ -147,8 +148,8 @@ async def test_regenerate_entity_observations(memory):
|
|||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE agent_id = $1", agent_id)
|
||||
await conn.execute("DELETE FROM entities WHERE agent_id = $1", agent_id)
|
||||
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
|
||||
|
|
@ -156,19 +157,19 @@ async def test_search_with_include_entities(memory):
|
|||
"""
|
||||
Test that search with include_entities=True returns entity observations.
|
||||
"""
|
||||
agent_id = f"test_search_ent_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_search_ent_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store facts about entities
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
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)
|
||||
)
|
||||
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
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)
|
||||
|
|
@ -178,11 +179,11 @@ async def test_search_with_include_entities(memory):
|
|||
await memory.wait_for_background_tasks()
|
||||
|
||||
# Search with include_entities=True
|
||||
result = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="What does Alice do?",
|
||||
fact_type=["world", "agent"],
|
||||
thinking_budget=30,
|
||||
budget=Budget.LOW, # 30,
|
||||
max_tokens=2000,
|
||||
include_entities=True,
|
||||
max_entity_tokens=500
|
||||
|
|
@ -223,8 +224,8 @@ async def test_search_with_include_entities(memory):
|
|||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE agent_id = $1", agent_id)
|
||||
await conn.execute("DELETE FROM entities WHERE agent_id = $1", agent_id)
|
||||
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
|
||||
|
|
@ -232,12 +233,12 @@ async def test_get_entity_state(memory):
|
|||
"""
|
||||
Test getting the full state of an entity.
|
||||
"""
|
||||
agent_id = f"test_entity_state_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_entity_state_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store facts
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Bob is a frontend developer who specializes in React and TypeScript.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
|
|
@ -252,10 +253,10 @@ async def test_get_entity_state(memory):
|
|||
"""
|
||||
SELECT id, canonical_name
|
||||
FROM entities
|
||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%bob%'
|
||||
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%bob%'
|
||||
LIMIT 1
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
if entity_row:
|
||||
|
|
@ -264,7 +265,7 @@ async def test_get_entity_state(memory):
|
|||
|
||||
# Get entity state
|
||||
state = await memory.get_entity_state(
|
||||
agent_id=agent_id,
|
||||
bank_id=bank_id,
|
||||
entity_id=entity_id,
|
||||
entity_name=entity_name,
|
||||
limit=10
|
||||
|
|
@ -284,8 +285,8 @@ async def test_get_entity_state(memory):
|
|||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE agent_id = $1", agent_id)
|
||||
await conn.execute("DELETE FROM entities WHERE agent_id = $1", agent_id)
|
||||
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
|
||||
|
|
@ -293,12 +294,12 @@ async def test_observation_fact_type_in_database(memory):
|
|||
"""
|
||||
Test that observations are stored with correct fact_type in database.
|
||||
"""
|
||||
agent_id = f"test_obs_db_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_obs_db_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
# Store facts
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Charlie is a DevOps engineer who manages the Kubernetes infrastructure.",
|
||||
context="work info",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
|
|
@ -313,9 +314,9 @@ async def test_observation_fact_type_in_database(memory):
|
|||
"""
|
||||
SELECT id, text, fact_type, context
|
||||
FROM memory_units
|
||||
WHERE agent_id = $1 AND fact_type = 'observation'
|
||||
WHERE bank_id = $1 AND fact_type = 'observation'
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
print(f"\n=== Observation Records in Database ===")
|
||||
|
|
@ -334,5 +335,5 @@ async def test_observation_fact_type_in_database(memory):
|
|||
# Cleanup
|
||||
pool = await memory._get_pool()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM memory_units WHERE agent_id = $1", agent_id)
|
||||
await conn.execute("DELETE FROM entities WHERE agent_id = $1", agent_id)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Test search tracing functionality.
|
||||
"""
|
||||
import pytest
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
from hindsight_api import SearchTrace
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
|
@ -10,33 +11,33 @@ from datetime import datetime, timezone
|
|||
async def test_search_with_trace(memory):
|
||||
"""Test that search with enable_trace=True returns a valid SearchTrace."""
|
||||
# Generate a unique agent ID for this test
|
||||
agent_id = f"test_trace_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_trace_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
|
||||
# Store some test memories
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice works at Google in Mountain View",
|
||||
context="test context",
|
||||
)
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Bob also works at Google but in New York",
|
||||
context="test context",
|
||||
)
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Charlie founded a startup called TechCorp",
|
||||
context="test context",
|
||||
)
|
||||
|
||||
# Search with tracing enabled
|
||||
search_result = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
search_result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="Who works at Google?",
|
||||
fact_type=["world"],
|
||||
thinking_budget=20,
|
||||
budget=Budget.LOW, # 20,
|
||||
max_tokens=512,
|
||||
enable_trace=True,
|
||||
)
|
||||
|
|
@ -51,7 +52,7 @@ async def test_search_with_trace(memory):
|
|||
|
||||
# Verify query info
|
||||
assert trace["query"]["query_text"] == "Who works at Google?"
|
||||
assert trace["query"]["thinking_budget"] == 20
|
||||
assert trace["query"]["budget"] == 100 # Budget.LOW = 100
|
||||
assert trace["query"]["max_tokens"] == 512
|
||||
assert len(trace["query"]["query_embedding"]) > 0, "Query embedding should be populated"
|
||||
|
||||
|
|
@ -80,7 +81,7 @@ async def test_search_with_trace(memory):
|
|||
# Verify summary
|
||||
assert trace["summary"]["total_nodes_visited"] == len(trace["visits"])
|
||||
assert trace["summary"]["results_returned"] == len(search_result.results)
|
||||
assert trace["summary"]["budget_used"] <= trace["query"]["thinking_budget"]
|
||||
assert trace["summary"]["budget_used"] <= trace["query"]["budget"]
|
||||
assert trace["summary"]["total_duration_seconds"] > 0
|
||||
|
||||
# Verify phase metrics
|
||||
|
|
@ -101,29 +102,29 @@ async def test_search_with_trace(memory):
|
|||
|
||||
finally:
|
||||
# Cleanup
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_without_trace(memory):
|
||||
"""Test that search with enable_trace=False returns None for trace."""
|
||||
agent_id = f"test_no_trace_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_no_trace_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
|
||||
# Store a test memory
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Test memory without trace",
|
||||
context="test",
|
||||
)
|
||||
|
||||
# Search without tracing
|
||||
search_result = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
search_result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="test",
|
||||
fact_type=["world"],
|
||||
thinking_budget=10,
|
||||
budget=Budget.LOW, # 10,
|
||||
max_tokens=512,
|
||||
enable_trace=False,
|
||||
)
|
||||
|
|
@ -136,4 +137,4 @@ async def test_search_without_trace(memory):
|
|||
|
||||
finally:
|
||||
# Cleanup
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import os
|
|||
from datetime import datetime, timezone, timedelta
|
||||
import pytest
|
||||
from hindsight_api import MemoryEngine
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -19,11 +20,11 @@ async def test_temporal_ranges_are_written():
|
|||
)
|
||||
await memory.initialize()
|
||||
|
||||
agent_id = "test_temporal_ranges"
|
||||
bank_id = "test_temporal_ranges"
|
||||
|
||||
# Clean up any existing data
|
||||
try:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -31,8 +32,8 @@ async def test_temporal_ranges_are_written():
|
|||
conversation_date = datetime(2024, 11, 17, 10, 0, 0, tzinfo=timezone.utc)
|
||||
text1 = "Yesterday I went to a pottery workshop where I made a beautiful vase."
|
||||
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=text1,
|
||||
event_date=conversation_date
|
||||
)
|
||||
|
|
@ -40,8 +41,8 @@ async def test_temporal_ranges_are_written():
|
|||
# Test 2: Period event (month range)
|
||||
text2 = "In February 2024, Alice visited Paris and explored the Louvre museum."
|
||||
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content=text2,
|
||||
event_date=conversation_date
|
||||
)
|
||||
|
|
@ -56,10 +57,10 @@ async def test_temporal_ranges_are_written():
|
|||
"""
|
||||
SELECT id, text, event_date, occurred_start, occurred_end, mentioned_at
|
||||
FROM memory_units
|
||||
WHERE agent_id = $1
|
||||
WHERE bank_id = $1
|
||||
ORDER BY created_at
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
print(f"\n\n=== Retrieved {len(rows)} facts ===")
|
||||
|
|
@ -113,11 +114,11 @@ async def test_temporal_ranges_are_written():
|
|||
|
||||
# Test search results also include temporal fields
|
||||
print("\n=== Testing Search Results ===")
|
||||
search_result = await memory.search_async(
|
||||
agent_id=agent_id,
|
||||
search_result = await memory.recall_async(
|
||||
bank_id=bank_id,
|
||||
query="pottery workshop",
|
||||
fact_type=["event", "world"],
|
||||
thinking_budget=20,
|
||||
budget=Budget.LOW,
|
||||
max_tokens=4096
|
||||
)
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ async def test_temporal_ranges_are_written():
|
|||
print("⚠ Temporal fields not yet populated in search results (known issue)")
|
||||
|
||||
# Clean up
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
await memory.close()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Test think function for opinion generation and consistency.
|
|||
"""
|
||||
import pytest
|
||||
from datetime import datetime, timezone
|
||||
from hindsight_api.engine.memory_engine import Budget
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -13,20 +14,20 @@ async def test_think_opinion_consistency(memory):
|
|||
2. Stores the opinion in the database
|
||||
3. Returns consistent response on subsequent calls with the same query
|
||||
"""
|
||||
agent_id = f"test_think_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_think_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
try:
|
||||
|
||||
# Store some initial facts to give context for opinion formation
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Alice is a software engineer who has worked on 5 major projects. She always delivers on time and writes clean, well-documented code.",
|
||||
context="performance review",
|
||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
await memory.put_async(
|
||||
agent_id=agent_id,
|
||||
await memory.retain_async(
|
||||
bank_id=bank_id,
|
||||
content="Bob recently joined the team. He missed his first deadline and his code had many bugs.",
|
||||
context="performance review",
|
||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
||||
|
|
@ -34,25 +35,20 @@ async def test_think_opinion_consistency(memory):
|
|||
|
||||
# First think call - should generate opinions
|
||||
query = "Who is a more reliable engineer?"
|
||||
result1 = await memory.think_async(
|
||||
agent_id=agent_id,
|
||||
result1 = await memory.reflect_async(
|
||||
bank_id=bank_id,
|
||||
query=query,
|
||||
thinking_budget=30,
|
||||
budget=Budget.LOW,
|
||||
)
|
||||
|
||||
print(f"\n=== First Think Call ===")
|
||||
print(f"Answer: {result1.text}")
|
||||
print(f"New opinions formed: {len(result1.new_opinions)}")
|
||||
|
||||
# Verify we got an answer
|
||||
assert result1.text, "First think call should return an answer"
|
||||
assert result1.based_on, "Should return based_on facts"
|
||||
|
||||
# Verify opinions were formed
|
||||
new_opinions_count = len(result1.new_opinions)
|
||||
print(f"\nNew opinions formed: {new_opinions_count}")
|
||||
|
||||
# Wait for background opinion PUT tasks to complete
|
||||
# Wait for background opinion processing tasks to complete
|
||||
await memory.wait_for_background_tasks()
|
||||
|
||||
# Search for stored opinions to verify they were actually saved
|
||||
|
|
@ -62,10 +58,10 @@ async def test_think_opinion_consistency(memory):
|
|||
"""
|
||||
SELECT id, text, confidence_score, fact_type
|
||||
FROM memory_units
|
||||
WHERE agent_id = $1 AND fact_type = 'opinion'
|
||||
WHERE bank_id = $1 AND fact_type = 'opinion'
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
agent_id
|
||||
bank_id
|
||||
)
|
||||
|
||||
print(f"\n=== Stored Opinions in Database ===")
|
||||
|
|
@ -82,10 +78,10 @@ async def test_think_opinion_consistency(memory):
|
|||
print(f"⚠ Note: No opinions were extracted/stored (this can happen if the LLM response format doesn't trigger opinion extraction)")
|
||||
|
||||
# Second think call - should use the stored opinions
|
||||
result2 = await memory.think_async(
|
||||
agent_id=agent_id,
|
||||
result2 = await memory.reflect_async(
|
||||
bank_id=bank_id,
|
||||
query=query,
|
||||
thinking_budget=30,
|
||||
budget=Budget.LOW,
|
||||
)
|
||||
|
||||
print(f"\n=== Second Think Call ===")
|
||||
|
|
@ -93,7 +89,6 @@ async def test_think_opinion_consistency(memory):
|
|||
print(f"Existing opinions used: {len(result2.based_on.get('opinion', []))}")
|
||||
for opinion in result2.based_on.get('opinion', []):
|
||||
print(f" - {opinion.text}")
|
||||
print(f"New opinions formed: {len(result2.new_opinions)}")
|
||||
|
||||
# Verify second call also got an answer
|
||||
assert result2.text, "Second think call should return an answer"
|
||||
|
|
@ -127,7 +122,7 @@ async def test_think_opinion_consistency(memory):
|
|||
finally:
|
||||
# Clean up agent data
|
||||
try:
|
||||
await memory.delete_agent(agent_id)
|
||||
await memory.delete_bank(bank_id)
|
||||
except Exception as e:
|
||||
print(f"Warning: Error during cleanup: {e}")
|
||||
|
||||
|
|
@ -137,13 +132,13 @@ async def test_think_without_prior_context(memory):
|
|||
"""
|
||||
Test that think function handles queries when there's no relevant context.
|
||||
"""
|
||||
agent_id = f"test_think_no_context_{datetime.now(timezone.utc).timestamp()}"
|
||||
bank_id = f"test_think_no_context_{datetime.now(timezone.utc).timestamp()}"
|
||||
|
||||
# Call think without storing any prior facts
|
||||
result = await memory.think_async(
|
||||
agent_id=agent_id,
|
||||
result = await memory.reflect_async(
|
||||
bank_id=bank_id,
|
||||
query="What is the capital of France?",
|
||||
thinking_budget=20,
|
||||
budget=Budget.LOW,
|
||||
)
|
||||
|
||||
print(f"\n=== Think Without Context ===")
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ name = "hindsight"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Hindsight API client (generated)
|
||||
hindsight-client = { path = "../hindsight-clients/rust" }
|
||||
|
||||
# CLI framework
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
|
||||
# HTTP client
|
||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||
# Async runtime
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# Serialization
|
||||
# Serialization (for config and output formatting)
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
|
|
|
|||
|
|
@ -1,134 +1,16 @@
|
|||
use anyhow::{Context, Result};
|
||||
use reqwest::blocking::Client;
|
||||
//! API client wrapper
|
||||
//!
|
||||
//! This module provides a thin wrapper around the auto-generated hindsight-client
|
||||
//! to bridge from the CLI's synchronous code to the async API client.
|
||||
|
||||
use anyhow::Result;
|
||||
use hindsight_client::Client as AsyncClient;
|
||||
pub use hindsight_client::types;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SearchRequest {
|
||||
pub query: String,
|
||||
pub fact_type: Vec<String>,
|
||||
pub thinking_budget: i32,
|
||||
pub max_tokens: i32,
|
||||
pub trace: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SearchResponse {
|
||||
pub results: Vec<Fact>,
|
||||
pub trace: Option<TraceInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Fact {
|
||||
#[serde(default)]
|
||||
pub id: Option<String>,
|
||||
pub text: String,
|
||||
#[serde(rename = "type", default)]
|
||||
pub fact_type: Option<String>,
|
||||
pub activation: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub context: Option<String>,
|
||||
#[serde(default)]
|
||||
pub event_date: Option<String>,
|
||||
#[serde(default)]
|
||||
pub occurred_start: Option<String>,
|
||||
#[serde(default)]
|
||||
pub occurred_end: Option<String>,
|
||||
#[serde(default)]
|
||||
pub mentioned_at: Option<String>,
|
||||
#[serde(default)]
|
||||
pub document_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TraceInfo {
|
||||
pub total_time: Option<f64>,
|
||||
pub activation_count: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ThinkRequest {
|
||||
pub query: String,
|
||||
pub thinking_budget: i32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ThinkResponse {
|
||||
pub text: String,
|
||||
pub based_on: Vec<Fact>,
|
||||
pub new_opinions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MemoryItem {
|
||||
pub content: String,
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BatchMemoryRequest {
|
||||
pub items: Vec<MemoryItem>,
|
||||
pub document_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BatchMemoryResponse {
|
||||
pub success: bool,
|
||||
pub stored_count: Option<i32>,
|
||||
pub items_count: Option<i32>,
|
||||
pub error: Option<String>,
|
||||
pub job_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum AgentsResponse {
|
||||
Success {
|
||||
agents: Vec<AgentProfile>,
|
||||
},
|
||||
Error {
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Agent {
|
||||
pub agent_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct PersonalityTraits {
|
||||
pub openness: f32,
|
||||
pub conscientiousness: f32,
|
||||
pub extraversion: f32,
|
||||
pub agreeableness: f32,
|
||||
pub neuroticism: f32,
|
||||
pub bias_strength: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AgentProfile {
|
||||
pub agent_id: String,
|
||||
pub name: String,
|
||||
pub personality: PersonalityTraits,
|
||||
pub background: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AddBackgroundRequest {
|
||||
pub content: String,
|
||||
pub update_personality: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BackgroundResponse {
|
||||
pub background: String,
|
||||
pub personality: Option<PersonalityTraits>,
|
||||
}
|
||||
|
||||
// Types not defined in OpenAPI spec (TODO: add to openapi.json)
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AgentStats {
|
||||
pub agent_id: String,
|
||||
|
|
@ -143,36 +25,6 @@ pub struct AgentStats {
|
|||
pub failed_operations: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Document {
|
||||
pub id: String,
|
||||
pub agent_id: String,
|
||||
pub content_hash: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
pub text_length: i32,
|
||||
pub memory_unit_count: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DocumentDetails {
|
||||
pub id: String,
|
||||
pub agent_id: String,
|
||||
pub original_text: String,
|
||||
pub content_hash: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
pub memory_unit_count: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DocumentsResponse {
|
||||
pub items: Vec<Document>,
|
||||
pub total: i32,
|
||||
pub limit: i32,
|
||||
pub offset: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Operation {
|
||||
pub id: String,
|
||||
|
|
@ -191,628 +43,210 @@ pub struct OperationsResponse {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DeleteResponse {
|
||||
pub success: bool,
|
||||
pub message: String,
|
||||
pub struct TraceInfo {
|
||||
pub total_time: Option<f64>,
|
||||
pub activation_count: Option<i32>,
|
||||
}
|
||||
|
||||
// Unified result for put_memories that handles both sync and async responses
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MemoryPutResult {
|
||||
pub success: bool,
|
||||
pub items_count: i64,
|
||||
pub message: String,
|
||||
pub is_async: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiClient {
|
||||
client: Client,
|
||||
base_url: String,
|
||||
client: AsyncClient,
|
||||
runtime: std::sync::Arc<tokio::runtime::Runtime>,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
pub fn new(base_url: String) -> Result<Self> {
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(60))
|
||||
.build()
|
||||
.context("Failed to create HTTP client")?;
|
||||
|
||||
Ok(ApiClient { client, base_url })
|
||||
let runtime = std::sync::Arc::new(tokio::runtime::Runtime::new()?);
|
||||
let client = AsyncClient::new(&base_url);
|
||||
Ok(ApiClient { client, runtime })
|
||||
}
|
||||
|
||||
pub fn search(&self, agent_id: &str, request: SearchRequest, verbose: bool) -> Result<SearchResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/memories/search", self.base_url, agent_id);
|
||||
let request_body = serde_json::to_string_pretty(&request).unwrap_or_default();
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
eprintln!("Request body:\n{}", request_body);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(&url)
|
||||
.json(&request)
|
||||
.timeout(Duration::from_secs(120))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: SearchResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn list_agents(&self, _verbose: bool) -> Result<Vec<types::BankListItem>> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_banks().await?;
|
||||
Ok(response.into_inner().banks)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn think(&self, agent_id: &str, request: ThinkRequest, verbose: bool) -> Result<ThinkResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/think", self.base_url, agent_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default());
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(&url)
|
||||
.json(&request)
|
||||
.timeout(Duration::from_secs(120))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: ThinkResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn get_profile(&self, agent_id: &str, _verbose: bool) -> Result<types::BankProfileResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_bank_profile(agent_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn put_memories(&self, agent_id: &str, request: BatchMemoryRequest, async_mode: bool, verbose: bool) -> Result<BatchMemoryResponse> {
|
||||
let endpoint = if async_mode {
|
||||
"async"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let url = if async_mode {
|
||||
format!("{}/api/v1/agents/{}/memories/{}", self.base_url, agent_id, endpoint)
|
||||
} else {
|
||||
format!("{}/api/v1/agents/{}/memories", self.base_url, agent_id)
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default());
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(&url)
|
||||
.json(&request)
|
||||
.timeout(Duration::from_secs(120))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: BatchMemoryResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn get_stats(&self, agent_id: &str, _verbose: bool) -> Result<AgentStats> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_agent_stats(agent_id).await?;
|
||||
let value = response.into_inner();
|
||||
let stats: AgentStats = serde_json::from_value(value)?;
|
||||
Ok(stats)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_agents(&self, verbose: bool) -> Result<Vec<Agent>> {
|
||||
let url = format!("{}/api/v1/agents", self.base_url);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: AgentsResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
|
||||
match result {
|
||||
AgentsResponse::Success { agents } => {
|
||||
Ok(agents.into_iter().map(|profile| Agent { agent_id: profile.agent_id }).collect())
|
||||
}
|
||||
AgentsResponse::Error { error } => {
|
||||
anyhow::bail!("Failed to list agents: {}", error)
|
||||
}
|
||||
}
|
||||
pub fn update_agent_name(&self, agent_id: &str, name: &str, _verbose: bool) -> Result<types::BankProfileResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let request = types::CreateBankRequest {
|
||||
name: Some(name.to_string()),
|
||||
background: None,
|
||||
personality: None,
|
||||
};
|
||||
let response = self.client.create_or_update_bank(agent_id, &request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_profile(&self, agent_id: &str, verbose: bool) -> Result<AgentProfile> {
|
||||
let url = format!("{}/api/v1/agents/{}/profile", self.base_url, agent_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: AgentProfile = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn add_background(&self, agent_id: &str, content: &str, update_personality: bool, _verbose: bool) -> Result<types::BackgroundResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let request = types::AddBackgroundRequest {
|
||||
content: content.to_string(),
|
||||
update_personality,
|
||||
};
|
||||
let response = self.client.add_bank_background(agent_id, &request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_agent_name(
|
||||
&self,
|
||||
agent_id: &str,
|
||||
name: &str,
|
||||
verbose: bool,
|
||||
) -> Result<AgentProfile> {
|
||||
#[derive(Serialize)]
|
||||
struct UpdateNameRequest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
let url = format!("{}/api/v1/agents/{}", self.base_url, agent_id);
|
||||
let request = UpdateNameRequest {
|
||||
name: name.to_string(),
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default());
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.put(&url)
|
||||
.json(&request)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: AgentProfile = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn recall(&self, agent_id: &str, request: &types::RecallRequest, _verbose: bool) -> Result<types::RecallResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.recall_memories(agent_id, request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_background(&self, agent_id: &str, content: &str, update_personality: bool, verbose: bool) -> Result<BackgroundResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/background", self.base_url, agent_id);
|
||||
let request = AddBackgroundRequest {
|
||||
content: content.to_string(),
|
||||
update_personality,
|
||||
};
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default());
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.post(&url)
|
||||
.json(&request)
|
||||
.timeout(Duration::from_secs(60))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: BackgroundResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn reflect(&self, agent_id: &str, request: &types::ReflectRequest, _verbose: bool) -> Result<types::ReflectResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.reflect(agent_id, request).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_stats(&self, agent_id: &str, verbose: bool) -> Result<AgentStats> {
|
||||
let url = format!("{}/api/v1/agents/{}/stats", self.base_url, agent_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: AgentStats = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn retain(&self, agent_id: &str, request: &types::RetainRequest, _async_mode: bool, _verbose: bool) -> Result<MemoryPutResult> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.retain_memories(agent_id, request).await?;
|
||||
let result = response.into_inner();
|
||||
Ok(MemoryPutResult {
|
||||
success: result.success,
|
||||
items_count: result.items_count,
|
||||
message: format!("Stored {} memory units", result.items_count),
|
||||
is_async: result.async_,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_documents(&self, agent_id: &str, q: Option<&str>, limit: Option<i32>, offset: Option<i32>, verbose: bool) -> Result<DocumentsResponse> {
|
||||
let mut url = format!("{}/api/v1/agents/{}/documents", self.base_url, agent_id);
|
||||
let mut params = vec![];
|
||||
|
||||
if let Some(query) = q {
|
||||
params.push(format!("q={}", query));
|
||||
}
|
||||
if let Some(l) = limit {
|
||||
params.push(format!("limit={}", l));
|
||||
}
|
||||
if let Some(o) = offset {
|
||||
params.push(format!("offset={}", o));
|
||||
}
|
||||
|
||||
if !params.is_empty() {
|
||||
url.push('?');
|
||||
url.push_str(¶ms.join("&"));
|
||||
}
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DocumentsResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn delete_memory(&self, _agent_id: &str, _unit_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
// Note: Individual memory deletion is no longer supported in the API
|
||||
anyhow::bail!("Individual memory deletion is no longer supported. Use 'memory clear' to clear all memories.")
|
||||
}
|
||||
|
||||
pub fn get_document(&self, agent_id: &str, document_id: &str, verbose: bool) -> Result<DocumentDetails> {
|
||||
let url = format!("{}/api/v1/agents/{}/documents/{}", self.base_url, agent_id, document_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DocumentDetails = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn clear_memories(&self, agent_id: &str, fact_type: Option<&str>, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.clear_bank_memories(agent_id, fact_type).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list_operations(&self, agent_id: &str, verbose: bool) -> Result<OperationsResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/operations", self.base_url, agent_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.get(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: OperationsResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn list_documents(&self, agent_id: &str, q: Option<&str>, limit: Option<i32>, offset: Option<i32>, _verbose: bool) -> Result<types::ListDocumentsResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_documents(
|
||||
agent_id,
|
||||
limit.map(|l| l as i64),
|
||||
offset.map(|o| o as i64),
|
||||
q
|
||||
).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_operation(&self, agent_id: &str, operation_id: &str, verbose: bool) -> Result<DeleteResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/operations/{}", self.base_url, agent_id, operation_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.delete(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DeleteResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn get_document(&self, agent_id: &str, document_id: &str, _verbose: bool) -> Result<types::DocumentResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_document(agent_id, document_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_memory(&self, agent_id: &str, unit_id: &str, verbose: bool) -> Result<DeleteResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/memories/{}", self.base_url, agent_id, unit_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.delete(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DeleteResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn delete_document(&self, agent_id: &str, document_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.delete_document(agent_id, document_id).await?;
|
||||
let value = response.into_inner();
|
||||
let result: types::DeleteResponse = serde_json::from_value(value)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_document(&self, agent_id: &str, document_id: &str, verbose: bool) -> Result<DeleteResponse> {
|
||||
let url = format!("{}/api/v1/agents/{}/documents/{}", self.base_url, agent_id, document_id);
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.delete(&url)
|
||||
.timeout(Duration::from_secs(30))
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DeleteResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn list_operations(&self, agent_id: &str, _verbose: bool) -> Result<OperationsResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_operations(agent_id).await?;
|
||||
let value = response.into_inner();
|
||||
let ops: OperationsResponse = serde_json::from_value(value)?;
|
||||
Ok(ops)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear_memories(&self, agent_id: &str, fact_type: Option<&str>, verbose: bool) -> Result<DeleteResponse> {
|
||||
let mut url = format!("{}/api/v1/agents/{}/memories", self.base_url, agent_id);
|
||||
pub fn cancel_operation(&self, agent_id: &str, operation_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.cancel_operation(agent_id, operation_id).await?;
|
||||
let value = response.into_inner();
|
||||
let result: types::DeleteResponse = serde_json::from_value(value)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(ft) = fact_type {
|
||||
url.push_str(&format!("?fact_type={}", ft));
|
||||
}
|
||||
pub fn list_memories(&self, bank_id: &str, type_filter: Option<&str>, q: Option<&str>, limit: Option<i64>, offset: Option<i64>, _verbose: bool) -> Result<types::ListMemoryUnitsResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_memories(bank_id, limit, offset, q, type_filter).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
if verbose {
|
||||
eprintln!("Request URL: {}", url);
|
||||
}
|
||||
pub fn list_entities(&self, bank_id: &str, limit: Option<i64>, _verbose: bool) -> Result<types::EntityListResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.list_entities(bank_id, limit).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.delete(&url)
|
||||
.timeout(Duration::from_secs(60))
|
||||
.send()?;
|
||||
pub fn get_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.get_entity(bank_id, entity_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
|
||||
let status = response.status();
|
||||
if verbose {
|
||||
eprintln!("Response status: {}", status);
|
||||
}
|
||||
|
||||
if !status.is_success() {
|
||||
let error_body = response.text().unwrap_or_default();
|
||||
if verbose {
|
||||
eprintln!("Error response body:\n{}", error_body);
|
||||
}
|
||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
||||
}
|
||||
|
||||
let response_text = response.text()?;
|
||||
if verbose {
|
||||
eprintln!("Response body:\n{}", response_text);
|
||||
}
|
||||
|
||||
let result: DeleteResponse = serde_json::from_str(&response_text)
|
||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
||||
Ok(result)
|
||||
pub fn regenerate_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||
self.runtime.block_on(async {
|
||||
let response = self.client.regenerate_entity_observations(bank_id, entity_id).await?;
|
||||
Ok(response.into_inner())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export types from the generated client for use in commands
|
||||
pub use types::{
|
||||
AddBackgroundRequest,
|
||||
BackgroundResponse,
|
||||
BankListItem,
|
||||
BankProfileResponse,
|
||||
CreateBankRequest,
|
||||
DeleteResponse,
|
||||
DocumentResponse,
|
||||
ListDocumentsResponse,
|
||||
MemoryItem,
|
||||
PersonalityTraits,
|
||||
RecallRequest,
|
||||
RecallResponse,
|
||||
RecallResult,
|
||||
ReflectRequest,
|
||||
ReflectResponse,
|
||||
RetainRequest,
|
||||
RetainResponse,
|
||||
};
|
||||
|
|
|
|||
248
hindsight-cli/src/commands/bank.rs
Normal file
248
hindsight-cli/src/commands/bank.rs
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
use anyhow::Result;
|
||||
use crate::api::ApiClient;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
||||
pub fn list(client: &ApiClient, verbose: bool, output_format: OutputFormat) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching banks..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.list_agents(verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(banks_list) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if banks_list.is_empty() {
|
||||
ui::print_warning("No banks found");
|
||||
} else {
|
||||
ui::print_info(&format!("Found {} bank(s)", banks_list.len()));
|
||||
for bank in &banks_list {
|
||||
println!(" - {}", bank.bank_id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output::print_output(&banks_list, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn profile(client: &ApiClient, bank_id: &str, verbose: bool, output_format: OutputFormat) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching profile..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.get_profile(bank_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(profile) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_profile(&profile);
|
||||
} else {
|
||||
output::print_output(&profile, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stats(client: &ApiClient, bank_id: &str, verbose: bool, output_format: OutputFormat) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching statistics..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.get_stats(bank_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(stats) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_info(&format!("Statistics for bank '{}'", bank_id));
|
||||
println!();
|
||||
|
||||
println!(" 📊 Overview");
|
||||
println!(" Total Memory Units: {}", stats.total_nodes);
|
||||
println!(" Total Links: {}", stats.total_links);
|
||||
println!(" Total Documents: {}", stats.total_documents);
|
||||
println!();
|
||||
|
||||
println!(" 🧠 Memory Units by Type");
|
||||
let mut fact_types: Vec<_> = stats.nodes_by_fact_type.iter().collect();
|
||||
fact_types.sort_by_key(|(k, _)| *k);
|
||||
for (fact_type, count) in fact_types {
|
||||
let icon = match fact_type.as_str() {
|
||||
"world" => "🌍",
|
||||
"agent" => "🤖",
|
||||
"opinion" => "💭",
|
||||
_ => "•"
|
||||
};
|
||||
println!(" {} {:<10} {}", icon, fact_type, count);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!(" 🔗 Links by Type");
|
||||
let mut link_types: Vec<_> = stats.links_by_link_type.iter().collect();
|
||||
link_types.sort_by_key(|(k, _)| *k);
|
||||
for (link_type, count) in link_types {
|
||||
let icon = match link_type.as_str() {
|
||||
"temporal" => "⏰",
|
||||
"semantic" => "🔤",
|
||||
"entity" => "🏷️",
|
||||
_ => "•"
|
||||
};
|
||||
println!(" {} {:<10} {}", icon, link_type, count);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!(" 🔗 Links by Fact Type");
|
||||
let mut fact_type_links: Vec<_> = stats.links_by_fact_type.iter().collect();
|
||||
fact_type_links.sort_by_key(|(k, _)| *k);
|
||||
for (fact_type, count) in fact_type_links {
|
||||
let icon = match fact_type.as_str() {
|
||||
"world" => "🌍",
|
||||
"agent" => "🤖",
|
||||
"opinion" => "💭",
|
||||
_ => "•"
|
||||
};
|
||||
println!(" {} {:<10} {}", icon, fact_type, count);
|
||||
}
|
||||
println!();
|
||||
|
||||
if !stats.links_breakdown.is_empty() {
|
||||
println!(" 📈 Detailed Link Breakdown");
|
||||
let mut fact_types: Vec<_> = stats.links_breakdown.iter().collect();
|
||||
fact_types.sort_by_key(|(k, _)| *k);
|
||||
for (fact_type, link_types) in fact_types {
|
||||
let icon = match fact_type.as_str() {
|
||||
"world" => "🌍",
|
||||
"agent" => "🤖",
|
||||
"opinion" => "💭",
|
||||
_ => "•"
|
||||
};
|
||||
println!(" {} {}", icon, fact_type);
|
||||
let mut sorted_links: Vec<_> = link_types.iter().collect();
|
||||
sorted_links.sort_by_key(|(k, _)| *k);
|
||||
for (link_type, count) in sorted_links {
|
||||
println!(" - {:<10} {}", link_type, count);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if stats.pending_operations > 0 || stats.failed_operations > 0 {
|
||||
println!(" ⚙️ Operations");
|
||||
if stats.pending_operations > 0 {
|
||||
println!(" ⏳ Pending: {}", stats.pending_operations);
|
||||
}
|
||||
if stats.failed_operations > 0 {
|
||||
println!(" ❌ Failed: {}", stats.failed_operations);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output::print_output(&stats, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_name(client: &ApiClient, bank_id: &str, name: &str, verbose: bool, output_format: OutputFormat) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Updating bank name..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.update_agent_name(bank_id, name, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(profile) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success(&format!("Bank name updated to '{}'", profile.name));
|
||||
} else {
|
||||
output::print_output(&profile, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_background(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
content: &str,
|
||||
no_update_personality: bool,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat
|
||||
) -> Result<()> {
|
||||
let current_profile = if !no_update_personality {
|
||||
client.get_profile(bank_id, verbose).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Merging background..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.add_background(bank_id, content, !no_update_personality, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(profile) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success("Background updated successfully");
|
||||
println!("\n{}", profile.background);
|
||||
|
||||
if !no_update_personality {
|
||||
if let (Some(old_p), Some(new_p)) =
|
||||
(current_profile.as_ref().map(|p| p.personality.clone()), &profile.personality)
|
||||
{
|
||||
println!("\nPersonality 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output::print_output(&profile, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
124
hindsight-cli/src/commands/document.rs
Normal file
124
hindsight-cli/src/commands/document.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use anyhow::Result;
|
||||
use crate::api::ApiClient;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
||||
pub fn list(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
query: Option<String>,
|
||||
limit: i32,
|
||||
offset: i32,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching documents..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.list_documents(agent_id, query.as_deref(), Some(limit), Some(offset), verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(docs_response) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_info(&format!("Documents for agent '{}' (total: {})", agent_id, docs_response.total));
|
||||
for doc in &docs_response.items {
|
||||
let id = doc.get("id").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
let created = doc.get("created_at").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
let updated = doc.get("updated_at").and_then(|v| v.as_str()).unwrap_or("unknown");
|
||||
let text_len = doc.get("text_length").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
let mem_count = doc.get("memory_unit_count").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||
|
||||
println!("\n Document ID: {}", id);
|
||||
println!(" Created: {}", created);
|
||||
println!(" Updated: {}", updated);
|
||||
println!(" Text Length: {}", text_len);
|
||||
println!(" Memory Units: {}", mem_count);
|
||||
}
|
||||
} else {
|
||||
output::print_output(&docs_response, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
document_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching document..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.get_document(agent_id, document_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(doc) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_info(&format!("Document: {}", doc.id));
|
||||
println!(" Agent ID: {}", doc.agent_id);
|
||||
println!(" Created: {}", doc.created_at);
|
||||
println!(" Updated: {}", doc.updated_at);
|
||||
println!(" Memory Units: {}", doc.memory_unit_count);
|
||||
println!("\n Text:\n{}", doc.original_text);
|
||||
} else {
|
||||
output::print_output(&doc, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
document_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Deleting document..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.delete_document(agent_id, document_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if result.success {
|
||||
ui::print_success("Document deleted successfully");
|
||||
} else {
|
||||
ui::print_error("Failed to delete document");
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
136
hindsight-cli/src/commands/entity.rs
Normal file
136
hindsight-cli/src/commands/entity.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use anyhow::Result;
|
||||
use crate::api::ApiClient;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
||||
pub fn list(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
limit: i64,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching entities..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.list_entities(bank_id, Some(limit), verbose)?;
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_section_header(&format!("Entities for Bank: {}", bank_id));
|
||||
|
||||
if response.entities.is_empty() {
|
||||
ui::print_warning("No entities found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Total entities: {}\n", response.entities.len());
|
||||
|
||||
for entity in &response.entities {
|
||||
println!("ID: {}", entity.id);
|
||||
println!(" Name: {}", entity.canonical_name);
|
||||
println!(" Mentions: {}", entity.mention_count);
|
||||
if let Some(first_seen) = &entity.first_seen {
|
||||
println!(" First seen: {}", first_seen);
|
||||
}
|
||||
if let Some(last_seen) = &entity.last_seen {
|
||||
println!(" Last seen: {}", last_seen);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
} else {
|
||||
output::print_output(&response, output_format)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
entity_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching entity details..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.get_entity(bank_id, entity_id, verbose)?;
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_section_header(&format!("Entity: {}", entity_id));
|
||||
|
||||
println!("ID: {}", response.id);
|
||||
println!("Name: {}", response.canonical_name);
|
||||
println!("Mentions: {}", response.mention_count);
|
||||
|
||||
if let Some(first_seen) = &response.first_seen {
|
||||
println!("First seen: {}", first_seen);
|
||||
}
|
||||
if let Some(last_seen) = &response.last_seen {
|
||||
println!("Last seen: {}", last_seen);
|
||||
}
|
||||
|
||||
// Show observations (always included)
|
||||
if !response.observations.is_empty() {
|
||||
println!("\nObservations ({}):", response.observations.len());
|
||||
for obs in &response.observations {
|
||||
println!(" - {}", obs.text);
|
||||
if let Some(mentioned_at) = &obs.mentioned_at {
|
||||
println!(" Mentioned at: {}", mentioned_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
} else {
|
||||
output::print_output(&response, output_format)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn regenerate(
|
||||
client: &ApiClient,
|
||||
bank_id: &str,
|
||||
entity_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Regenerating entity observations..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.regenerate_entity(bank_id, entity_id, verbose)?;
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success(&format!("Successfully regenerated observations for entity: {}", entity_id));
|
||||
println!("\nUpdated entity:");
|
||||
println!(" Name: {}", response.canonical_name);
|
||||
println!(" Mentions: {}", response.mention_count);
|
||||
println!(" Observations: {}", response.observations.len());
|
||||
} else {
|
||||
output::print_output(&response, output_format)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1080
hindsight-cli/src/commands/explore.rs
Normal file
1080
hindsight-cli/src/commands/explore.rs
Normal file
File diff suppressed because it is too large
Load diff
397
hindsight-cli/src/commands/memory.rs
Normal file
397
hindsight-cli/src/commands/memory.rs
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::api::{ApiClient, RecallRequest, ReflectRequest, MemoryItem, RetainRequest};
|
||||
use crate::config;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
||||
// Import Budget type from generated client
|
||||
use hindsight_client::types::Budget;
|
||||
|
||||
// Helper function to parse budget string to Budget enum
|
||||
fn parse_budget(budget: &str) -> Budget {
|
||||
match budget.to_lowercase().as_str() {
|
||||
"low" => Budget::Low,
|
||||
"high" => Budget::High,
|
||||
_ => Budget::Mid, // Default to mid
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recall(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
query: String,
|
||||
fact_type: Vec<String>,
|
||||
budget: String,
|
||||
max_tokens: i64,
|
||||
trace: bool,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Recalling memories..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = RecallRequest {
|
||||
query,
|
||||
types: if fact_type.is_empty() { None } else { Some(fact_type) },
|
||||
budget: Some(parse_budget(&budget)),
|
||||
max_tokens,
|
||||
trace,
|
||||
query_timestamp: None,
|
||||
filters: None,
|
||||
include: None,
|
||||
};
|
||||
|
||||
let response = client.recall(agent_id, &request, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_search_results(&result, trace);
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reflect(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
query: String,
|
||||
budget: String,
|
||||
context: Option<String>,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Reflecting..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = ReflectRequest {
|
||||
query,
|
||||
budget: Some(parse_budget(&budget)),
|
||||
context,
|
||||
filters: None,
|
||||
include: None,
|
||||
};
|
||||
|
||||
let response = client.reflect(agent_id, &request, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_think_response(&result);
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
content: String,
|
||||
doc_id: Option<String>,
|
||||
context: Option<String>,
|
||||
r#async: bool,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let doc_id = doc_id.unwrap_or_else(config::generate_doc_id);
|
||||
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Retaining memory..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let item = MemoryItem {
|
||||
content: content.clone(),
|
||||
context,
|
||||
metadata: None,
|
||||
timestamp: None,
|
||||
};
|
||||
|
||||
let request = RetainRequest {
|
||||
items: vec![item],
|
||||
document_id: Some(doc_id.clone()),
|
||||
async_: r#async,
|
||||
};
|
||||
|
||||
let response = client.retain(agent_id, &request, r#async, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success(&format!(
|
||||
"Memory retained successfully (document: {})",
|
||||
doc_id
|
||||
));
|
||||
if result.is_async {
|
||||
println!(" Status: queued for background processing");
|
||||
println!(" Items: {}", result.items_count);
|
||||
} else {
|
||||
println!(" Stored count: {}", result.items_count);
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain_files(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
path: PathBuf,
|
||||
recursive: bool,
|
||||
context: Option<String>,
|
||||
r#async: bool,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
if !path.exists() {
|
||||
anyhow::bail!("Path does not exist: {}", path.display());
|
||||
}
|
||||
|
||||
let mut files = Vec::new();
|
||||
|
||||
if path.is_file() {
|
||||
files.push(path);
|
||||
} else if path.is_dir() {
|
||||
if recursive {
|
||||
for entry in WalkDir::new(&path)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file())
|
||||
{
|
||||
let path = entry.path();
|
||||
if let Some(ext) = path.extension() {
|
||||
if ext == "txt" || ext == "md" {
|
||||
files.push(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for entry in fs::read_dir(&path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(ext) = path.extension() {
|
||||
if ext == "txt" || ext == "md" {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if files.is_empty() {
|
||||
ui::print_warning("No .txt or .md files found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ui::print_info(&format!("Found {} files to import", files.len()));
|
||||
|
||||
let pb = ui::create_progress_bar(files.len() as u64, "Processing files");
|
||||
|
||||
let mut items = Vec::new();
|
||||
let mut document_id = None;
|
||||
|
||||
for file_path in &files {
|
||||
let content = fs::read_to_string(file_path)
|
||||
.with_context(|| format!("Failed to read file: {}", file_path.display()))?;
|
||||
|
||||
let doc_id = file_path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(config::generate_doc_id);
|
||||
|
||||
if document_id.is_none() {
|
||||
document_id = Some(doc_id);
|
||||
}
|
||||
|
||||
items.push(MemoryItem {
|
||||
content,
|
||||
context: context.clone(),
|
||||
metadata: None,
|
||||
timestamp: None,
|
||||
});
|
||||
|
||||
pb.inc(1);
|
||||
}
|
||||
|
||||
pb.finish_with_message("Files processed");
|
||||
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Retaining memories..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let request = RetainRequest {
|
||||
items,
|
||||
document_id,
|
||||
async_: r#async,
|
||||
};
|
||||
|
||||
let response = client.retain(agent_id, &request, r#async, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
ui::print_success("Files retained successfully");
|
||||
if result.is_async {
|
||||
println!(" Status: queued for background processing");
|
||||
println!(" Items: {}", result.items_count);
|
||||
} else {
|
||||
println!(" Total units created: {}", result.items_count);
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
unit_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Deleting memory unit..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.delete_memory(agent_id, unit_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if result.success {
|
||||
ui::print_success("Memory unit deleted successfully");
|
||||
} else {
|
||||
ui::print_error("Failed to delete memory unit");
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
fact_type: Option<String>,
|
||||
yes: bool,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
// Confirmation prompt unless -y flag is used
|
||||
if !yes && output_format == OutputFormat::Pretty {
|
||||
let message = if let Some(ft) = &fact_type {
|
||||
format!(
|
||||
"Are you sure you want to clear all '{}' memories for agent '{}'? This cannot be undone.",
|
||||
ft, agent_id
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Are you sure you want to clear ALL memories for agent '{}'? This cannot be undone.",
|
||||
agent_id
|
||||
)
|
||||
};
|
||||
|
||||
let confirmed = ui::prompt_confirmation(&message)?;
|
||||
|
||||
if !confirmed {
|
||||
ui::print_info("Operation cancelled");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let spinner_msg = if let Some(ft) = &fact_type {
|
||||
format!("Clearing {} memories...", ft)
|
||||
} else {
|
||||
"Clearing all memories...".to_string()
|
||||
};
|
||||
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner(&spinner_msg))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.clear_memories(agent_id, fact_type.as_deref(), verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if result.success {
|
||||
let msg = if fact_type.is_some() {
|
||||
"Memories cleared successfully"
|
||||
} else {
|
||||
"All memories cleared successfully"
|
||||
};
|
||||
ui::print_success(msg);
|
||||
} else {
|
||||
ui::print_error("Failed to clear memories");
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
6
hindsight-cli/src/commands/mod.rs
Normal file
6
hindsight-cli/src/commands/mod.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub mod bank;
|
||||
pub mod memory;
|
||||
pub mod document;
|
||||
pub mod entity;
|
||||
pub mod operation;
|
||||
pub mod explore;
|
||||
84
hindsight-cli/src/commands/operation.rs
Normal file
84
hindsight-cli/src/commands/operation.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use anyhow::Result;
|
||||
use crate::api::ApiClient;
|
||||
use crate::output::{self, OutputFormat};
|
||||
use crate::ui;
|
||||
|
||||
pub fn list(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Fetching operations..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.list_operations(agent_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(ops_response) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if ops_response.operations.is_empty() {
|
||||
ui::print_info("No operations found");
|
||||
} else {
|
||||
ui::print_info(&format!("Found {} operation(s)", ops_response.operations.len()));
|
||||
for op in &ops_response.operations {
|
||||
println!("\n Operation ID: {}", op.id);
|
||||
println!(" Type: {}", op.task_type);
|
||||
println!(" Status: {}", op.status);
|
||||
println!(" Items: {}", op.items_count);
|
||||
if let Some(doc_id) = &op.document_id {
|
||||
println!(" Document ID: {}", doc_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output::print_output(&ops_response, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel(
|
||||
client: &ApiClient,
|
||||
agent_id: &str,
|
||||
operation_id: &str,
|
||||
verbose: bool,
|
||||
output_format: OutputFormat,
|
||||
) -> Result<()> {
|
||||
let spinner = if output_format == OutputFormat::Pretty {
|
||||
Some(ui::create_spinner("Cancelling operation..."))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response = client.cancel_operation(agent_id, operation_id, verbose);
|
||||
|
||||
if let Some(sp) = spinner {
|
||||
sp.finish_and_clear();
|
||||
}
|
||||
|
||||
match response {
|
||||
Ok(result) => {
|
||||
if output_format == OutputFormat::Pretty {
|
||||
if result.success {
|
||||
ui::print_success("Operation cancelled successfully");
|
||||
} else {
|
||||
ui::print_error("Failed to cancel operation");
|
||||
}
|
||||
} else {
|
||||
output::print_output(&result, output_format)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
use crate::api::{AgentProfile, Fact, SearchResponse, ThinkResponse, TraceInfo};
|
||||
use crate::api::{BankProfileResponse, RecallResult, RecallResponse, ReflectResponse};
|
||||
use colored::*;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use std::io::{self, Write};
|
||||
|
|
@ -9,8 +9,8 @@ pub fn print_section_header(title: &str) {
|
|||
println!();
|
||||
}
|
||||
|
||||
pub fn print_fact(fact: &Fact, show_activation: bool) {
|
||||
let fact_type = fact.fact_type.as_deref().unwrap_or("unknown");
|
||||
pub fn print_fact(fact: &RecallResult, show_activation: bool) {
|
||||
let fact_type = fact.type_.as_deref().unwrap_or("unknown");
|
||||
|
||||
let type_color = match fact_type {
|
||||
"world" => "cyan",
|
||||
|
|
@ -29,10 +29,10 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
|||
print!("{} ", prefix);
|
||||
print!("{}", format!("[{}]", fact_type.to_uppercase()).color(type_color).bold());
|
||||
|
||||
// Note: activation field not available in generated SearchResult
|
||||
// The API doesn't return it in the current schema
|
||||
if show_activation {
|
||||
if let Some(activation) = fact.activation {
|
||||
print!(" {}", format!("({:.2})", activation).bright_black());
|
||||
}
|
||||
// Placeholder for when activation is added to the API schema
|
||||
}
|
||||
|
||||
println!();
|
||||
|
|
@ -44,27 +44,12 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
|||
}
|
||||
|
||||
// Show temporal information
|
||||
// If occurred_start/end exist, show them; otherwise fall back to event_date
|
||||
if let Some(occurred_start) = &fact.occurred_start {
|
||||
if let Some(occurred_end) = &fact.occurred_end {
|
||||
if occurred_start == occurred_end {
|
||||
// Point event
|
||||
println!(" {}: {}", "Occurred".bright_black(), occurred_start.bright_black());
|
||||
} else {
|
||||
// Range event
|
||||
println!(" {}: {} to {}", "Occurred".bright_black(), occurred_start.bright_black(), occurred_end.bright_black());
|
||||
}
|
||||
println!(" {}: {} - {}", "Date".bright_black(), occurred_start.bright_black(), occurred_end.bright_black());
|
||||
} else {
|
||||
println!(" {}: {}", "Occurred".bright_black(), occurred_start.bright_black());
|
||||
println!(" {}: {}", "Date".bright_black(), occurred_start.bright_black());
|
||||
}
|
||||
} else if let Some(event_date) = &fact.event_date {
|
||||
// Fallback for backward compatibility
|
||||
println!(" {}: {}", "Date".bright_black(), event_date.bright_black());
|
||||
}
|
||||
|
||||
// Show when fact was mentioned (learned)
|
||||
if let Some(mentioned_at) = &fact.mentioned_at {
|
||||
println!(" {}: {}", "Mentioned".bright_black(), mentioned_at.bright_black());
|
||||
}
|
||||
|
||||
// Show document ID if available
|
||||
|
|
@ -75,7 +60,7 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
|||
println!();
|
||||
}
|
||||
|
||||
pub fn print_search_results(response: &SearchResponse, show_trace: bool) {
|
||||
pub fn print_search_results(response: &RecallResponse, show_trace: bool) {
|
||||
let results = &response.results;
|
||||
print_section_header(&format!("Search Results ({})", results.len()));
|
||||
|
||||
|
|
@ -95,7 +80,7 @@ pub fn print_search_results(response: &SearchResponse, show_trace: bool) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn print_think_response(response: &ThinkResponse) {
|
||||
pub fn print_think_response(response: &ReflectResponse) {
|
||||
println!();
|
||||
println!("{}", response.text.bright_white());
|
||||
println!();
|
||||
|
|
@ -105,14 +90,14 @@ pub fn print_think_response(response: &ThinkResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn print_trace_info(trace: &TraceInfo) {
|
||||
pub fn print_trace_info(trace: &serde_json::Map<String, serde_json::Value>) {
|
||||
print_section_header("Trace Information");
|
||||
|
||||
if let Some(time) = trace.total_time {
|
||||
if let Some(time) = trace.get("total_time").and_then(|v| v.as_f64()) {
|
||||
println!(" ⏱️ Total time: {}", format!("{:.2}ms", time).bright_green());
|
||||
}
|
||||
|
||||
if let Some(count) = trace.activation_count {
|
||||
if let Some(count) = trace.get("activation_count").and_then(|v| v.as_i64()) {
|
||||
println!(" 📊 Activation count: {}", count.to_string().bright_green());
|
||||
}
|
||||
|
||||
|
|
@ -170,8 +155,8 @@ pub fn prompt_confirmation(message: &str) -> io::Result<bool> {
|
|||
Ok(input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes"))
|
||||
}
|
||||
|
||||
pub fn print_profile(profile: &AgentProfile) {
|
||||
print_section_header(&format!("Agent Profile: {}", profile.agent_id));
|
||||
pub fn print_profile(profile: &BankProfileResponse) {
|
||||
print_section_header(&format!("Bank Profile: {}", profile.bank_id));
|
||||
|
||||
// Print name
|
||||
println!("{} {}", "Name:".bright_cyan().bold(), profile.name.bright_white());
|
||||
|
|
@ -200,7 +185,7 @@ pub fn print_profile(profile: &AgentProfile) {
|
|||
|
||||
for (name, value, emoji, color) in &traits {
|
||||
let bar_length = 40;
|
||||
let filled = (*value * bar_length as f32) as usize;
|
||||
let filled = (*value * bar_length as f64) as usize;
|
||||
let empty = bar_length - filled;
|
||||
|
||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
||||
|
|
@ -224,7 +209,7 @@ pub fn print_profile(profile: &AgentProfile) {
|
|||
println!("{}", "Bias Strength:".bright_yellow());
|
||||
let bias = profile.personality.bias_strength;
|
||||
let bar_length = 40;
|
||||
let filled = (bias * bar_length as f32) as usize;
|
||||
let filled = (bias * bar_length as f64) as usize;
|
||||
let empty = bar_length - filled;
|
||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
||||
|
||||
|
|
|
|||
15
hindsight-cli/src/utils.rs
Normal file
15
hindsight-cli/src/utils.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use anyhow::{Context, Result};
|
||||
use crate::api::ApiClient;
|
||||
use crate::config::Config;
|
||||
use crate::output::OutputFormat;
|
||||
|
||||
/// Get API client from config
|
||||
pub fn get_client(config: &Config) -> Result<ApiClient> {
|
||||
ApiClient::new(config.api_url.clone())
|
||||
.context("Failed to create API client")
|
||||
}
|
||||
|
||||
/// Get output format, preferring CLI arg over default
|
||||
pub fn get_output_format(cli_format: Option<OutputFormat>, _config: &Config) -> OutputFormat {
|
||||
cli_format.unwrap_or(OutputFormat::Pretty)
|
||||
}
|
||||
|
|
@ -1,103 +1,118 @@
|
|||
memora_client_api/__init__.py
|
||||
memora_client_api/api/__init__.py
|
||||
memora_client_api/api/agent_management_api.py
|
||||
memora_client_api/api/documents_api.py
|
||||
memora_client_api/api/memory_operations_api.py
|
||||
memora_client_api/api/reasoning_api.py
|
||||
memora_client_api/api/visualization_api.py
|
||||
memora_client_api/api_client.py
|
||||
memora_client_api/api_response.py
|
||||
memora_client_api/configuration.py
|
||||
memora_client_api/docs/AddBackgroundRequest.md
|
||||
memora_client_api/docs/AgentListItem.md
|
||||
memora_client_api/docs/AgentListResponse.md
|
||||
memora_client_api/docs/AgentManagementApi.md
|
||||
memora_client_api/docs/AgentProfileResponse.md
|
||||
memora_client_api/docs/BackgroundResponse.md
|
||||
memora_client_api/docs/BatchPutAsyncResponse.md
|
||||
memora_client_api/docs/BatchPutRequest.md
|
||||
memora_client_api/docs/BatchPutResponse.md
|
||||
memora_client_api/docs/CreateAgentRequest.md
|
||||
memora_client_api/docs/DeleteResponse.md
|
||||
memora_client_api/docs/DocumentResponse.md
|
||||
memora_client_api/docs/DocumentsApi.md
|
||||
memora_client_api/docs/GraphDataResponse.md
|
||||
memora_client_api/docs/HTTPValidationError.md
|
||||
memora_client_api/docs/ListDocumentsResponse.md
|
||||
memora_client_api/docs/ListMemoryUnitsResponse.md
|
||||
memora_client_api/docs/MemoryItem.md
|
||||
memora_client_api/docs/MemoryOperationsApi.md
|
||||
memora_client_api/docs/PersonalityTraits.md
|
||||
memora_client_api/docs/ReasoningApi.md
|
||||
memora_client_api/docs/SearchRequest.md
|
||||
memora_client_api/docs/SearchResponse.md
|
||||
memora_client_api/docs/SearchResult.md
|
||||
memora_client_api/docs/ThinkFact.md
|
||||
memora_client_api/docs/ThinkRequest.md
|
||||
memora_client_api/docs/ThinkResponse.md
|
||||
memora_client_api/docs/UpdatePersonalityRequest.md
|
||||
memora_client_api/docs/ValidationError.md
|
||||
memora_client_api/docs/ValidationErrorLocInner.md
|
||||
memora_client_api/docs/VisualizationApi.md
|
||||
memora_client_api/exceptions.py
|
||||
memora_client_api/models/__init__.py
|
||||
memora_client_api/models/add_background_request.py
|
||||
memora_client_api/models/agent_list_item.py
|
||||
memora_client_api/models/agent_list_response.py
|
||||
memora_client_api/models/agent_profile_response.py
|
||||
memora_client_api/models/background_response.py
|
||||
memora_client_api/models/batch_put_async_response.py
|
||||
memora_client_api/models/batch_put_request.py
|
||||
memora_client_api/models/batch_put_response.py
|
||||
memora_client_api/models/create_agent_request.py
|
||||
memora_client_api/models/delete_response.py
|
||||
memora_client_api/models/document_response.py
|
||||
memora_client_api/models/graph_data_response.py
|
||||
memora_client_api/models/http_validation_error.py
|
||||
memora_client_api/models/list_documents_response.py
|
||||
memora_client_api/models/list_memory_units_response.py
|
||||
memora_client_api/models/memory_item.py
|
||||
memora_client_api/models/personality_traits.py
|
||||
memora_client_api/models/search_request.py
|
||||
memora_client_api/models/search_response.py
|
||||
memora_client_api/models/search_result.py
|
||||
memora_client_api/models/think_fact.py
|
||||
memora_client_api/models/think_request.py
|
||||
memora_client_api/models/think_response.py
|
||||
memora_client_api/models/update_personality_request.py
|
||||
memora_client_api/models/validation_error.py
|
||||
memora_client_api/models/validation_error_loc_inner.py
|
||||
memora_client_api/rest.py
|
||||
memora_client_api/test/__init__.py
|
||||
memora_client_api/test/test_add_background_request.py
|
||||
memora_client_api/test/test_agent_list_item.py
|
||||
memora_client_api/test/test_agent_list_response.py
|
||||
memora_client_api/test/test_agent_management_api.py
|
||||
memora_client_api/test/test_agent_profile_response.py
|
||||
memora_client_api/test/test_background_response.py
|
||||
memora_client_api/test/test_batch_put_async_response.py
|
||||
memora_client_api/test/test_batch_put_request.py
|
||||
memora_client_api/test/test_batch_put_response.py
|
||||
memora_client_api/test/test_create_agent_request.py
|
||||
memora_client_api/test/test_delete_response.py
|
||||
memora_client_api/test/test_document_response.py
|
||||
memora_client_api/test/test_documents_api.py
|
||||
memora_client_api/test/test_graph_data_response.py
|
||||
memora_client_api/test/test_http_validation_error.py
|
||||
memora_client_api/test/test_list_documents_response.py
|
||||
memora_client_api/test/test_list_memory_units_response.py
|
||||
memora_client_api/test/test_memory_item.py
|
||||
memora_client_api/test/test_memory_operations_api.py
|
||||
memora_client_api/test/test_personality_traits.py
|
||||
memora_client_api/test/test_reasoning_api.py
|
||||
memora_client_api/test/test_search_request.py
|
||||
memora_client_api/test/test_search_response.py
|
||||
memora_client_api/test/test_search_result.py
|
||||
memora_client_api/test/test_think_fact.py
|
||||
memora_client_api/test/test_think_request.py
|
||||
memora_client_api/test/test_think_response.py
|
||||
memora_client_api/test/test_update_personality_request.py
|
||||
memora_client_api/test/test_validation_error.py
|
||||
memora_client_api/test/test_validation_error_loc_inner.py
|
||||
memora_client_api/test/test_visualization_api.py
|
||||
memora_client_api_README.md
|
||||
hindsight_client_api/__init__.py
|
||||
hindsight_client_api/api/__init__.py
|
||||
hindsight_client_api/api/default_api.py
|
||||
hindsight_client_api/api_client.py
|
||||
hindsight_client_api/api_response.py
|
||||
hindsight_client_api/configuration.py
|
||||
hindsight_client_api/docs/AddBackgroundRequest.md
|
||||
hindsight_client_api/docs/BackgroundResponse.md
|
||||
hindsight_client_api/docs/BankListItem.md
|
||||
hindsight_client_api/docs/BankListResponse.md
|
||||
hindsight_client_api/docs/BankProfileResponse.md
|
||||
hindsight_client_api/docs/Budget.md
|
||||
hindsight_client_api/docs/CreateBankRequest.md
|
||||
hindsight_client_api/docs/DefaultApi.md
|
||||
hindsight_client_api/docs/DeleteResponse.md
|
||||
hindsight_client_api/docs/DocumentResponse.md
|
||||
hindsight_client_api/docs/EntityDetailResponse.md
|
||||
hindsight_client_api/docs/EntityIncludeOptions.md
|
||||
hindsight_client_api/docs/EntityListItem.md
|
||||
hindsight_client_api/docs/EntityListResponse.md
|
||||
hindsight_client_api/docs/EntityObservationResponse.md
|
||||
hindsight_client_api/docs/EntityStateResponse.md
|
||||
hindsight_client_api/docs/GraphDataResponse.md
|
||||
hindsight_client_api/docs/HTTPValidationError.md
|
||||
hindsight_client_api/docs/IncludeOptions.md
|
||||
hindsight_client_api/docs/ListDocumentsResponse.md
|
||||
hindsight_client_api/docs/ListMemoryUnitsResponse.md
|
||||
hindsight_client_api/docs/MemoryItem.md
|
||||
hindsight_client_api/docs/MetadataFilter.md
|
||||
hindsight_client_api/docs/PersonalityTraits.md
|
||||
hindsight_client_api/docs/RecallRequest.md
|
||||
hindsight_client_api/docs/RecallResponse.md
|
||||
hindsight_client_api/docs/RecallResult.md
|
||||
hindsight_client_api/docs/ReflectFact.md
|
||||
hindsight_client_api/docs/ReflectIncludeOptions.md
|
||||
hindsight_client_api/docs/ReflectRequest.md
|
||||
hindsight_client_api/docs/ReflectResponse.md
|
||||
hindsight_client_api/docs/RetainRequest.md
|
||||
hindsight_client_api/docs/RetainResponse.md
|
||||
hindsight_client_api/docs/UpdatePersonalityRequest.md
|
||||
hindsight_client_api/docs/ValidationError.md
|
||||
hindsight_client_api/docs/ValidationErrorLocInner.md
|
||||
hindsight_client_api/exceptions.py
|
||||
hindsight_client_api/models/__init__.py
|
||||
hindsight_client_api/models/add_background_request.py
|
||||
hindsight_client_api/models/background_response.py
|
||||
hindsight_client_api/models/bank_list_item.py
|
||||
hindsight_client_api/models/bank_list_response.py
|
||||
hindsight_client_api/models/bank_profile_response.py
|
||||
hindsight_client_api/models/budget.py
|
||||
hindsight_client_api/models/create_bank_request.py
|
||||
hindsight_client_api/models/delete_response.py
|
||||
hindsight_client_api/models/document_response.py
|
||||
hindsight_client_api/models/entity_detail_response.py
|
||||
hindsight_client_api/models/entity_include_options.py
|
||||
hindsight_client_api/models/entity_list_item.py
|
||||
hindsight_client_api/models/entity_list_response.py
|
||||
hindsight_client_api/models/entity_observation_response.py
|
||||
hindsight_client_api/models/entity_state_response.py
|
||||
hindsight_client_api/models/graph_data_response.py
|
||||
hindsight_client_api/models/http_validation_error.py
|
||||
hindsight_client_api/models/include_options.py
|
||||
hindsight_client_api/models/list_documents_response.py
|
||||
hindsight_client_api/models/list_memory_units_response.py
|
||||
hindsight_client_api/models/memory_item.py
|
||||
hindsight_client_api/models/metadata_filter.py
|
||||
hindsight_client_api/models/personality_traits.py
|
||||
hindsight_client_api/models/recall_request.py
|
||||
hindsight_client_api/models/recall_response.py
|
||||
hindsight_client_api/models/recall_result.py
|
||||
hindsight_client_api/models/reflect_fact.py
|
||||
hindsight_client_api/models/reflect_include_options.py
|
||||
hindsight_client_api/models/reflect_request.py
|
||||
hindsight_client_api/models/reflect_response.py
|
||||
hindsight_client_api/models/retain_request.py
|
||||
hindsight_client_api/models/retain_response.py
|
||||
hindsight_client_api/models/update_personality_request.py
|
||||
hindsight_client_api/models/validation_error.py
|
||||
hindsight_client_api/models/validation_error_loc_inner.py
|
||||
hindsight_client_api/rest.py
|
||||
hindsight_client_api/test/__init__.py
|
||||
hindsight_client_api/test/test_add_background_request.py
|
||||
hindsight_client_api/test/test_background_response.py
|
||||
hindsight_client_api/test/test_bank_list_item.py
|
||||
hindsight_client_api/test/test_bank_list_response.py
|
||||
hindsight_client_api/test/test_bank_profile_response.py
|
||||
hindsight_client_api/test/test_budget.py
|
||||
hindsight_client_api/test/test_create_bank_request.py
|
||||
hindsight_client_api/test/test_default_api.py
|
||||
hindsight_client_api/test/test_delete_response.py
|
||||
hindsight_client_api/test/test_document_response.py
|
||||
hindsight_client_api/test/test_entity_detail_response.py
|
||||
hindsight_client_api/test/test_entity_include_options.py
|
||||
hindsight_client_api/test/test_entity_list_item.py
|
||||
hindsight_client_api/test/test_entity_list_response.py
|
||||
hindsight_client_api/test/test_entity_observation_response.py
|
||||
hindsight_client_api/test/test_entity_state_response.py
|
||||
hindsight_client_api/test/test_graph_data_response.py
|
||||
hindsight_client_api/test/test_http_validation_error.py
|
||||
hindsight_client_api/test/test_include_options.py
|
||||
hindsight_client_api/test/test_list_documents_response.py
|
||||
hindsight_client_api/test/test_list_memory_units_response.py
|
||||
hindsight_client_api/test/test_memory_item.py
|
||||
hindsight_client_api/test/test_metadata_filter.py
|
||||
hindsight_client_api/test/test_personality_traits.py
|
||||
hindsight_client_api/test/test_recall_request.py
|
||||
hindsight_client_api/test/test_recall_response.py
|
||||
hindsight_client_api/test/test_recall_result.py
|
||||
hindsight_client_api/test/test_reflect_fact.py
|
||||
hindsight_client_api/test/test_reflect_include_options.py
|
||||
hindsight_client_api/test/test_reflect_request.py
|
||||
hindsight_client_api/test/test_reflect_response.py
|
||||
hindsight_client_api/test/test_retain_request.py
|
||||
hindsight_client_api/test/test_retain_response.py
|
||||
hindsight_client_api/test/test_update_personality_request.py
|
||||
hindsight_client_api/test/test_validation_error.py
|
||||
hindsight_client_api/test/test_validation_error_loc_inner.py
|
||||
hindsight_client_api_README.md
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
# Memora Python Client
|
||||
|
||||
Clean, pythonic client for the Memora API - A semantic memory system with personality-driven thinking.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install memora-client
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from memora_client import Memora
|
||||
|
||||
# Initialize client
|
||||
client = Memora(base_url="http://localhost:8888")
|
||||
|
||||
# Store a memory
|
||||
client.store(agent_id="alice", content="Alice loves artificial intelligence")
|
||||
|
||||
# Search memories
|
||||
results = client.search(agent_id="alice", query="What does Alice like?")
|
||||
print(results)
|
||||
|
||||
# Generate contextual answer
|
||||
answer = client.think(agent_id="alice", query="What are my interests?")
|
||||
print(answer["text"])
|
||||
```
|
||||
|
||||
## Main Operations
|
||||
|
||||
### Store Memories
|
||||
|
||||
```python
|
||||
# Store a single memory
|
||||
client.store(
|
||||
agent_id="alice",
|
||||
content="Alice completed a Python project using FastAPI",
|
||||
event_date=datetime(2024, 1, 15),
|
||||
context="work projects"
|
||||
)
|
||||
|
||||
# Store multiple memories in batch
|
||||
client.store_batch(
|
||||
agent_id="alice",
|
||||
items=[
|
||||
{"content": "Alice loves machine learning"},
|
||||
{"content": "Bob enjoys hiking", "event_date": datetime(2024, 10, 15)},
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
### Search Memories
|
||||
|
||||
```python
|
||||
# Simple search
|
||||
results = client.search(
|
||||
agent_id="alice",
|
||||
query="What does Alice like?",
|
||||
max_tokens=2048
|
||||
)
|
||||
|
||||
# Advanced search with all options
|
||||
response = client.search_memories(
|
||||
agent_id="alice",
|
||||
query="What are Alice's interests?",
|
||||
fact_type=["world"],
|
||||
max_tokens=4096,
|
||||
trace=True # Include trace information
|
||||
)
|
||||
```
|
||||
|
||||
### Think (Generate Contextual Answers)
|
||||
|
||||
```python
|
||||
answer = client.think(
|
||||
agent_id="alice",
|
||||
query="What should I focus on learning next?",
|
||||
thinking_budget=100,
|
||||
context="I want to advance my career in AI"
|
||||
)
|
||||
|
||||
print(answer["text"]) # The generated answer
|
||||
print(answer["based_on"]) # Facts used to generate the answer
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
memora-client/
|
||||
├── memora_client/ # Maintained wrapper (simple API)
|
||||
│ ├── __init__.py
|
||||
│ ├── memora_client.py # Clean interface: store(), search(), think()
|
||||
│ └── tests/
|
||||
│ └── test_main_operations.py
|
||||
│
|
||||
└── hindsight_client_api/ # Auto-generated from OpenAPI spec
|
||||
├── api/ # Full API operations
|
||||
├── models/ # Request/response models
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run integration tests (requires running Memora API server):
|
||||
|
||||
```bash
|
||||
# Set API URL (optional, defaults to http://localhost:8888)
|
||||
export MEMORA_API_URL=http://localhost:8888
|
||||
|
||||
# Run tests
|
||||
pytest memora_client/tests/test_main_operations.py -v
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Regenerate Client
|
||||
|
||||
The low-level API client is auto-generated from the OpenAPI spec. The high-level wrapper (`memora_client/`) is maintained and won't be overwritten.
|
||||
|
||||
```bash
|
||||
# Regenerate from OpenAPI spec
|
||||
./scripts/generate-clients.sh
|
||||
```
|
||||
|
||||
This preserves:
|
||||
- `memora_client/` - Maintained wrapper
|
||||
- `pyproject.toml` - Package configuration
|
||||
- Tests and documentation
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
|
|
@ -10,12 +10,12 @@ from typing import Optional, List, Dict, Any
|
|||
from datetime import datetime
|
||||
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.api import memory_operations_api, reasoning_api, agent_management_api
|
||||
from hindsight_client_api.api import default_api
|
||||
from hindsight_client_api.models import (
|
||||
search_request,
|
||||
batch_put_request,
|
||||
recall_request,
|
||||
retain_request,
|
||||
memory_item,
|
||||
think_request,
|
||||
reflect_request,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -41,13 +41,13 @@ class Hindsight:
|
|||
client = Hindsight(base_url="http://localhost:8888")
|
||||
|
||||
# Store a memory
|
||||
client.put(agent_id="alice", content="Alice loves AI")
|
||||
client.retain(bank_id="alice", content="Alice loves AI")
|
||||
|
||||
# Search memories
|
||||
results = client.search(agent_id="alice", query="What does Alice like?")
|
||||
# Recall memories
|
||||
results = client.recall(bank_id="alice", query="What does Alice like?")
|
||||
|
||||
# Generate contextual answer
|
||||
answer = client.think(agent_id="alice", query="What are my interests?")
|
||||
answer = client.reflect(bank_id="alice", query="What are my interests?")
|
||||
```
|
||||
"""
|
||||
|
||||
|
|
@ -61,9 +61,7 @@ class Hindsight:
|
|||
"""
|
||||
config = hindsight_client_api.Configuration(host=base_url)
|
||||
self._api_client = hindsight_client_api.ApiClient(config)
|
||||
self._memory_api = memory_operations_api.MemoryOperationsApi(self._api_client)
|
||||
self._reasoning_api = reasoning_api.ReasoningApi(self._api_client)
|
||||
self._agent_api = agent_management_api.AgentManagementApi(self._api_client)
|
||||
self._api = default_api.DefaultApi(self._api_client)
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
|
|
@ -80,46 +78,50 @@ class Hindsight:
|
|||
|
||||
# Simplified methods for main operations
|
||||
|
||||
def put(
|
||||
def retain(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
content: str,
|
||||
event_date: Optional[datetime] = None,
|
||||
timestamp: Optional[datetime] = None,
|
||||
context: Optional[str] = None,
|
||||
document_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Store a single memory (simplified interface).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
content: Memory content
|
||||
event_date: Optional event timestamp
|
||||
timestamp: Optional event timestamp
|
||||
context: Optional context description
|
||||
document_id: Optional document ID for grouping
|
||||
metadata: Optional user-defined metadata
|
||||
|
||||
Returns:
|
||||
Response with success status
|
||||
"""
|
||||
return self.put_batch(
|
||||
agent_id=agent_id,
|
||||
items=[{"content": content, "event_date": event_date, "context": context}],
|
||||
return self.retain_batch(
|
||||
bank_id=bank_id,
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||
document_id=document_id,
|
||||
)
|
||||
|
||||
def put_batch(
|
||||
def retain_batch(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
items: List[Dict[str, Any]],
|
||||
document_id: Optional[str] = None,
|
||||
async_: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Store multiple memories in batch.
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
items: List of memory items with 'content' and optional 'event_date', 'context'
|
||||
bank_id: The memory bank ID
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata'
|
||||
document_id: Optional document ID for grouping memories
|
||||
async_: If True, process asynchronously in background (default: False)
|
||||
|
||||
Returns:
|
||||
Response with success status and item count
|
||||
|
|
@ -127,172 +129,193 @@ class Hindsight:
|
|||
memory_items = [
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
event_date=item.get("event_date"),
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
request_obj = batch_put_request.BatchPutRequest(
|
||||
request_obj = retain_request.RetainRequest(
|
||||
items=memory_items,
|
||||
document_id=document_id,
|
||||
async_=async_,
|
||||
)
|
||||
|
||||
response = _run_async(self._memory_api.batch_put_memories(agent_id, request_obj))
|
||||
response = _run_async(self._api.retain_memories(bank_id, request_obj))
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
def search(
|
||||
def recall(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
query: str,
|
||||
fact_type: Optional[List[str]] = None,
|
||||
types: Optional[List[str]] = None,
|
||||
max_tokens: int = 4096,
|
||||
thinking_budget: int = 100,
|
||||
budget: str = "mid",
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search memories using semantic similarity.
|
||||
Recall memories using semantic similarity.
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
query: Search query
|
||||
fact_type: Optional list of fact types to filter (world, agent, opinion)
|
||||
types: Optional list of fact types to filter (world, agent, opinion, observation)
|
||||
max_tokens: Maximum tokens in results (default: 4096)
|
||||
thinking_budget: Token budget for search (default: 100)
|
||||
budget: Budget level for recall - "low", "mid", or "high" (default: "mid")
|
||||
|
||||
Returns:
|
||||
List of search results
|
||||
List of recall results
|
||||
"""
|
||||
request_obj = search_request.SearchRequest(
|
||||
request_obj = recall_request.RecallRequest(
|
||||
query=query,
|
||||
fact_type=fact_type,
|
||||
thinking_budget=thinking_budget,
|
||||
types=types,
|
||||
budget=budget,
|
||||
max_tokens=max_tokens,
|
||||
trace=False,
|
||||
)
|
||||
|
||||
response = _run_async(self._memory_api.search_memories(agent_id, request_obj))
|
||||
response = _run_async(self._api.recall_memories(bank_id, request_obj))
|
||||
|
||||
if hasattr(response, 'results'):
|
||||
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
||||
return []
|
||||
|
||||
def think(
|
||||
def reflect(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
query: str,
|
||||
thinking_budget: int = 50,
|
||||
budget: str = "low",
|
||||
context: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a contextual answer based on agent identity and memories.
|
||||
Generate a contextual answer based on bank identity and memories.
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
query: The question or prompt
|
||||
thinking_budget: Token budget for thinking (default: 50)
|
||||
budget: Budget level for reflection - "low", "mid", or "high" (default: "low")
|
||||
context: Optional additional context
|
||||
|
||||
Returns:
|
||||
Response with answer text, facts used, and new opinions
|
||||
Response with answer text and optionally facts used
|
||||
"""
|
||||
request_obj = think_request.ThinkRequest(
|
||||
request_obj = reflect_request.ReflectRequest(
|
||||
query=query,
|
||||
thinking_budget=thinking_budget,
|
||||
budget=budget,
|
||||
context=context,
|
||||
)
|
||||
|
||||
response = _run_async(self._reasoning_api.think(agent_id, request_obj))
|
||||
response = _run_async(self._api.reflect(bank_id, request_obj))
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
# Full-featured methods (expose more options)
|
||||
|
||||
def search_memories(
|
||||
def recall_memories(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
query: str,
|
||||
fact_type: Optional[List[str]] = None,
|
||||
thinking_budget: int = 100,
|
||||
types: Optional[List[str]] = None,
|
||||
budget: str = "mid",
|
||||
max_tokens: int = 4096,
|
||||
trace: bool = False,
|
||||
question_date: Optional[str] = None,
|
||||
query_timestamp: Optional[str] = None,
|
||||
include_entities: bool = True,
|
||||
max_entity_tokens: int = 500,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Search memories with all options (full-featured).
|
||||
Recall memories with all options (full-featured).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
query: Search query
|
||||
fact_type: Optional list of fact types to filter
|
||||
thinking_budget: Token budget for thinking
|
||||
types: Optional list of fact types to filter (world, agent, opinion, observation)
|
||||
budget: Budget level - "low", "mid", or "high"
|
||||
max_tokens: Maximum tokens in results
|
||||
trace: Enable trace output
|
||||
question_date: Optional ISO format date string
|
||||
query_timestamp: Optional ISO format date string (e.g., '2023-05-30T23:40:00')
|
||||
include_entities: Include entity observations in results (default: True)
|
||||
max_entity_tokens: Maximum tokens for entity observations (default: 500)
|
||||
|
||||
Returns:
|
||||
Full search response with results and optional trace
|
||||
Full recall response with results, optional entities, and optional trace
|
||||
"""
|
||||
request_obj = search_request.SearchRequest(
|
||||
query=query,
|
||||
fact_type=fact_type,
|
||||
thinking_budget=thinking_budget,
|
||||
max_tokens=max_tokens,
|
||||
trace=trace,
|
||||
question_date=question_date,
|
||||
from hindsight_client_api.models import include_options, entity_include_options
|
||||
|
||||
include_opts = include_options.IncludeOptions(
|
||||
entities=entity_include_options.EntityIncludeOptions(max_tokens=max_entity_tokens) if include_entities else None
|
||||
)
|
||||
|
||||
response = _run_async(self._memory_api.search_memories(agent_id, request_obj))
|
||||
request_obj = recall_request.RecallRequest(
|
||||
query=query,
|
||||
types=types,
|
||||
budget=budget,
|
||||
max_tokens=max_tokens,
|
||||
trace=trace,
|
||||
query_timestamp=query_timestamp,
|
||||
include=include_opts,
|
||||
)
|
||||
|
||||
response = _run_async(self._api.recall_memories(bank_id, request_obj))
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
def list_memories(
|
||||
self,
|
||||
agent_id: str,
|
||||
fact_type: Optional[str] = None,
|
||||
bank_id: str,
|
||||
type: Optional[str] = None,
|
||||
search_query: Optional[str] = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> Dict[str, Any]:
|
||||
"""List memory units with pagination."""
|
||||
response = _run_async(self._memory_api.list_memories(
|
||||
agent_id=agent_id,
|
||||
fact_type=fact_type,
|
||||
response = _run_async(self._api.list_memories(
|
||||
bank_id=bank_id,
|
||||
type=type,
|
||||
q=search_query,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
))
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
def create_agent(
|
||||
def create_bank(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
name: Optional[str] = None,
|
||||
background: Optional[str] = None,
|
||||
personality: Optional[Dict[str, float]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Create or update an agent."""
|
||||
from hindsight_client_api.models import create_agent_request
|
||||
"""Create or update a memory bank."""
|
||||
from hindsight_client_api.models import create_bank_request, personality_traits
|
||||
|
||||
request_obj = create_agent_request.CreateAgentRequest(
|
||||
personality_obj = None
|
||||
if personality:
|
||||
personality_obj = personality_traits.PersonalityTraits(**personality)
|
||||
|
||||
request_obj = create_bank_request.CreateBankRequest(
|
||||
name=name,
|
||||
background=background,
|
||||
personality=personality_obj,
|
||||
)
|
||||
|
||||
response = _run_async(self._agent_api.create_or_update_agent(agent_id, request_obj))
|
||||
response = _run_async(self._api.create_or_update_bank(bank_id, request_obj))
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
# Async methods (native async, no _run_async wrapper)
|
||||
|
||||
async def aput_batch(
|
||||
async def aretain_batch(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
items: List[Dict[str, Any]],
|
||||
document_id: Optional[str] = None,
|
||||
async_: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Store multiple memories in batch (async).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
items: List of memory items with 'content' and optional 'event_date', 'context'
|
||||
bank_id: The memory bank ID
|
||||
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata'
|
||||
document_id: Optional document ID for grouping memories
|
||||
async_: If True, process asynchronously in background (default: False)
|
||||
|
||||
Returns:
|
||||
Response with success status and item count
|
||||
|
|
@ -300,110 +323,110 @@ class Hindsight:
|
|||
memory_items = [
|
||||
memory_item.MemoryItem(
|
||||
content=item["content"],
|
||||
event_date=item.get("event_date"),
|
||||
timestamp=item.get("timestamp"),
|
||||
context=item.get("context"),
|
||||
metadata=item.get("metadata"),
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
request_obj = batch_put_request.BatchPutRequest(
|
||||
request_obj = retain_request.RetainRequest(
|
||||
items=memory_items,
|
||||
document_id=document_id,
|
||||
async_=async_,
|
||||
)
|
||||
|
||||
response = await self._memory_api.batch_put_memories(agent_id, request_obj)
|
||||
response = await self._api.retain_memories(bank_id, request_obj)
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
async def aput(
|
||||
async def aretain(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
content: str,
|
||||
event_date: Optional[datetime] = None,
|
||||
timestamp: Optional[datetime] = None,
|
||||
context: Optional[str] = None,
|
||||
document_id: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Store a single memory (async).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
content: Memory content
|
||||
event_date: Optional event timestamp
|
||||
timestamp: Optional event timestamp
|
||||
context: Optional context description
|
||||
document_id: Optional document ID for grouping
|
||||
metadata: Optional user-defined metadata
|
||||
|
||||
Returns:
|
||||
Response with success status
|
||||
"""
|
||||
return await self.aput_batch(
|
||||
agent_id=agent_id,
|
||||
items=[{"content": content, "event_date": event_date, "context": context}],
|
||||
return await self.aretain_batch(
|
||||
bank_id=bank_id,
|
||||
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||
document_id=document_id,
|
||||
)
|
||||
|
||||
async def asearch(
|
||||
async def arecall(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
query: str,
|
||||
fact_type: Optional[List[str]] = None,
|
||||
types: Optional[List[str]] = None,
|
||||
max_tokens: int = 4096,
|
||||
thinking_budget: int = 100,
|
||||
budget: str = "mid",
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search memories using semantic similarity (async).
|
||||
Recall memories using semantic similarity (async).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
query: Search query
|
||||
fact_type: Optional list of fact types to filter (world, agent, opinion)
|
||||
types: Optional list of fact types to filter (world, agent, opinion, observation)
|
||||
max_tokens: Maximum tokens in results (default: 4096)
|
||||
thinking_budget: Token budget for search (default: 100)
|
||||
budget: Budget level for recall - "low", "mid", or "high" (default: "mid")
|
||||
|
||||
Returns:
|
||||
List of search results
|
||||
List of recall results
|
||||
"""
|
||||
request_obj = search_request.SearchRequest(
|
||||
request_obj = recall_request.RecallRequest(
|
||||
query=query,
|
||||
fact_type=fact_type,
|
||||
thinking_budget=thinking_budget,
|
||||
types=types,
|
||||
budget=budget,
|
||||
max_tokens=max_tokens,
|
||||
trace=False,
|
||||
)
|
||||
|
||||
response = await self._memory_api.search_memories(agent_id, request_obj)
|
||||
response = await self._api.recall_memories(bank_id, request_obj)
|
||||
|
||||
if hasattr(response, 'results'):
|
||||
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
||||
return []
|
||||
|
||||
async def athink(
|
||||
async def areflect(
|
||||
self,
|
||||
agent_id: str,
|
||||
bank_id: str,
|
||||
query: str,
|
||||
thinking_budget: int = 50,
|
||||
budget: str = "low",
|
||||
context: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a contextual answer based on agent identity and memories (async).
|
||||
Generate a contextual answer based on bank identity and memories (async).
|
||||
|
||||
Args:
|
||||
agent_id: The agent ID
|
||||
bank_id: The memory bank ID
|
||||
query: The question or prompt
|
||||
thinking_budget: Token budget for thinking (default: 50)
|
||||
budget: Budget level for reflection - "low", "mid", or "high" (default: "low")
|
||||
context: Optional additional context
|
||||
|
||||
Returns:
|
||||
Response with answer text, facts used, and new opinions
|
||||
Response with answer text and optionally facts used
|
||||
"""
|
||||
request_obj = think_request.ThinkRequest(
|
||||
request_obj = reflect_request.ReflectRequest(
|
||||
query=query,
|
||||
thinking_budget=thinking_budget,
|
||||
budget=budget,
|
||||
context=context,
|
||||
)
|
||||
|
||||
response = await self._reasoning_api.think(agent_id, request_obj)
|
||||
response = await self._api.reflect(bank_id, request_obj)
|
||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||
|
||||
|
||||
# Alias for backward compatibility
|
||||
HindsightClient = Hindsight
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
# flake8: noqa
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
Hindsight HTTP API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
|
@ -18,11 +18,7 @@ __version__ = "0.0.7"
|
|||
|
||||
# Define package exports
|
||||
__all__ = [
|
||||
"AgentManagementApi",
|
||||
"DocumentsApi",
|
||||
"MemoryOperationsApi",
|
||||
"ReasoningApi",
|
||||
"VisualizationApi",
|
||||
"DefaultApi",
|
||||
"ApiResponse",
|
||||
"ApiClient",
|
||||
"Configuration",
|
||||
|
|
@ -33,39 +29,44 @@ __all__ = [
|
|||
"ApiAttributeError",
|
||||
"ApiException",
|
||||
"AddBackgroundRequest",
|
||||
"AgentListItem",
|
||||
"AgentListResponse",
|
||||
"AgentProfileResponse",
|
||||
"BackgroundResponse",
|
||||
"BatchPutAsyncResponse",
|
||||
"BatchPutRequest",
|
||||
"BatchPutResponse",
|
||||
"CreateAgentRequest",
|
||||
"BankListItem",
|
||||
"BankListResponse",
|
||||
"BankProfileResponse",
|
||||
"Budget",
|
||||
"CreateBankRequest",
|
||||
"DeleteResponse",
|
||||
"DocumentResponse",
|
||||
"EntityDetailResponse",
|
||||
"EntityIncludeOptions",
|
||||
"EntityListItem",
|
||||
"EntityListResponse",
|
||||
"EntityObservationResponse",
|
||||
"EntityStateResponse",
|
||||
"GraphDataResponse",
|
||||
"HTTPValidationError",
|
||||
"IncludeOptions",
|
||||
"ListDocumentsResponse",
|
||||
"ListMemoryUnitsResponse",
|
||||
"MemoryItem",
|
||||
"MetadataFilter",
|
||||
"PersonalityTraits",
|
||||
"SearchRequest",
|
||||
"SearchResponse",
|
||||
"SearchResult",
|
||||
"ThinkFact",
|
||||
"ThinkRequest",
|
||||
"ThinkResponse",
|
||||
"RecallRequest",
|
||||
"RecallResponse",
|
||||
"RecallResult",
|
||||
"ReflectFact",
|
||||
"ReflectIncludeOptions",
|
||||
"ReflectRequest",
|
||||
"ReflectResponse",
|
||||
"RetainRequest",
|
||||
"RetainResponse",
|
||||
"UpdatePersonalityRequest",
|
||||
"ValidationError",
|
||||
"ValidationErrorLocInner",
|
||||
]
|
||||
|
||||
# import apis into sdk package
|
||||
from hindsight_client_api.api.agent_management_api import AgentManagementApi as AgentManagementApi
|
||||
from hindsight_client_api.api.documents_api import DocumentsApi as DocumentsApi
|
||||
from hindsight_client_api.api.memory_operations_api import MemoryOperationsApi as MemoryOperationsApi
|
||||
from hindsight_client_api.api.reasoning_api import ReasoningApi as ReasoningApi
|
||||
from hindsight_client_api.api.visualization_api import VisualizationApi as VisualizationApi
|
||||
from hindsight_client_api.api.default_api import DefaultApi as DefaultApi
|
||||
|
||||
# import ApiClient
|
||||
from hindsight_client_api.api_response import ApiResponse as ApiResponse
|
||||
|
|
@ -80,28 +81,37 @@ from hindsight_client_api.exceptions import ApiException as ApiException
|
|||
|
||||
# import models into sdk package
|
||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest as AddBackgroundRequest
|
||||
from hindsight_client_api.models.agent_list_item import AgentListItem as AgentListItem
|
||||
from hindsight_client_api.models.agent_list_response import AgentListResponse as AgentListResponse
|
||||
from hindsight_client_api.models.agent_profile_response import AgentProfileResponse as AgentProfileResponse
|
||||
from hindsight_client_api.models.background_response import BackgroundResponse as BackgroundResponse
|
||||
from hindsight_client_api.models.batch_put_async_response import BatchPutAsyncResponse as BatchPutAsyncResponse
|
||||
from hindsight_client_api.models.batch_put_request import BatchPutRequest as BatchPutRequest
|
||||
from hindsight_client_api.models.batch_put_response import BatchPutResponse as BatchPutResponse
|
||||
from hindsight_client_api.models.create_agent_request import CreateAgentRequest as CreateAgentRequest
|
||||
from hindsight_client_api.models.bank_list_item import BankListItem as BankListItem
|
||||
from hindsight_client_api.models.bank_list_response import BankListResponse as BankListResponse
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse as BankProfileResponse
|
||||
from hindsight_client_api.models.budget import Budget as Budget
|
||||
from hindsight_client_api.models.create_bank_request import CreateBankRequest as CreateBankRequest
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse as DeleteResponse
|
||||
from hindsight_client_api.models.document_response import DocumentResponse as DocumentResponse
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse as EntityDetailResponse
|
||||
from hindsight_client_api.models.entity_include_options import EntityIncludeOptions as EntityIncludeOptions
|
||||
from hindsight_client_api.models.entity_list_item import EntityListItem as EntityListItem
|
||||
from hindsight_client_api.models.entity_list_response import EntityListResponse as EntityListResponse
|
||||
from hindsight_client_api.models.entity_observation_response import EntityObservationResponse as EntityObservationResponse
|
||||
from hindsight_client_api.models.entity_state_response import EntityStateResponse as EntityStateResponse
|
||||
from hindsight_client_api.models.graph_data_response import GraphDataResponse as GraphDataResponse
|
||||
from hindsight_client_api.models.http_validation_error import HTTPValidationError as HTTPValidationError
|
||||
from hindsight_client_api.models.include_options import IncludeOptions as IncludeOptions
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse as ListDocumentsResponse
|
||||
from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse as ListMemoryUnitsResponse
|
||||
from hindsight_client_api.models.memory_item import MemoryItem as MemoryItem
|
||||
from hindsight_client_api.models.metadata_filter import MetadataFilter as MetadataFilter
|
||||
from hindsight_client_api.models.personality_traits import PersonalityTraits as PersonalityTraits
|
||||
from hindsight_client_api.models.search_request import SearchRequest as SearchRequest
|
||||
from hindsight_client_api.models.search_response import SearchResponse as SearchResponse
|
||||
from hindsight_client_api.models.search_result import SearchResult as SearchResult
|
||||
from hindsight_client_api.models.think_fact import ThinkFact as ThinkFact
|
||||
from hindsight_client_api.models.think_request import ThinkRequest as ThinkRequest
|
||||
from hindsight_client_api.models.think_response import ThinkResponse as ThinkResponse
|
||||
from hindsight_client_api.models.recall_request import RecallRequest as RecallRequest
|
||||
from hindsight_client_api.models.recall_response import RecallResponse as RecallResponse
|
||||
from hindsight_client_api.models.recall_result import RecallResult as RecallResult
|
||||
from hindsight_client_api.models.reflect_fact import ReflectFact as ReflectFact
|
||||
from hindsight_client_api.models.reflect_include_options import ReflectIncludeOptions as ReflectIncludeOptions
|
||||
from hindsight_client_api.models.reflect_request import ReflectRequest as ReflectRequest
|
||||
from hindsight_client_api.models.reflect_response import ReflectResponse as ReflectResponse
|
||||
from hindsight_client_api.models.retain_request import RetainRequest as RetainRequest
|
||||
from hindsight_client_api.models.retain_response import RetainResponse as RetainResponse
|
||||
from hindsight_client_api.models.update_personality_request import UpdatePersonalityRequest as UpdatePersonalityRequest
|
||||
from hindsight_client_api.models.validation_error import ValidationError as ValidationError
|
||||
from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner as ValidationErrorLocInner
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
# flake8: noqa
|
||||
|
||||
# import apis into api package
|
||||
from hindsight_client_api.api.agent_management_api import AgentManagementApi
|
||||
from hindsight_client_api.api.documents_api import DocumentsApi
|
||||
from hindsight_client_api.api.memory_operations_api import MemoryOperationsApi
|
||||
from hindsight_client_api.api.reasoning_api import ReasoningApi
|
||||
from hindsight_client_api.api.visualization_api import VisualizationApi
|
||||
from hindsight_client_api.api.default_api import DefaultApi
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
5712
hindsight-clients/python/hindsight_client_api/api/default_api.py
Normal file
5712
hindsight-clients/python/hindsight_client_api/api/default_api.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,909 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import StrictInt, StrictStr
|
||||
from typing import Any, Optional
|
||||
from hindsight_client_api.models.document_response import DocumentResponse
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class DocumentsApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def delete_document(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> object:
|
||||
"""Delete a document
|
||||
|
||||
Delete a document and all its associated memory units and links. This will cascade delete: - The document itself - All memory units extracted from this document - All links (temporal, semantic, entity) associated with those memory units This operation cannot be undone.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._delete_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "object",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def delete_document_with_http_info(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[object]:
|
||||
"""Delete a document
|
||||
|
||||
Delete a document and all its associated memory units and links. This will cascade delete: - The document itself - All memory units extracted from this document - All links (temporal, semantic, entity) associated with those memory units This operation cannot be undone.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._delete_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "object",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def delete_document_without_preload_content(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Delete a document
|
||||
|
||||
Delete a document and all its associated memory units and links. This will cascade delete: - The document itself - All memory units extracted from this document - All links (temporal, semantic, entity) associated with those memory units This operation cannot be undone.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._delete_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "object",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _delete_document_serialize(
|
||||
self,
|
||||
agent_id,
|
||||
document_id,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if agent_id is not None:
|
||||
_path_params['agent_id'] = agent_id
|
||||
if document_id is not None:
|
||||
_path_params['document_id'] = document_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='DELETE',
|
||||
resource_path='/api/v1/agents/{agent_id}/documents/{document_id}',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_document(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> DocumentResponse:
|
||||
"""Get document details
|
||||
|
||||
Get a specific document including its original text
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "DocumentResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_document_with_http_info(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[DocumentResponse]:
|
||||
"""Get document details
|
||||
|
||||
Get a specific document including its original text
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "DocumentResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_document_without_preload_content(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
document_id: StrictStr,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Get document details
|
||||
|
||||
Get a specific document including its original text
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param document_id: (required)
|
||||
:type document_id: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_document_serialize(
|
||||
agent_id=agent_id,
|
||||
document_id=document_id,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "DocumentResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _get_document_serialize(
|
||||
self,
|
||||
agent_id,
|
||||
document_id,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if agent_id is not None:
|
||||
_path_params['agent_id'] = agent_id
|
||||
if document_id is not None:
|
||||
_path_params['document_id'] = document_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/api/v1/agents/{agent_id}/documents/{document_id}',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_documents(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
q: Optional[StrictStr] = None,
|
||||
limit: Optional[StrictInt] = None,
|
||||
offset: Optional[StrictInt] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ListDocumentsResponse:
|
||||
"""List documents
|
||||
|
||||
List documents with pagination and optional search. Documents are the source content from which memory units are extracted.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param q:
|
||||
:type q: str
|
||||
:param limit:
|
||||
:type limit: int
|
||||
:param offset:
|
||||
:type offset: int
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_documents_serialize(
|
||||
agent_id=agent_id,
|
||||
q=q,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ListDocumentsResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_documents_with_http_info(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
q: Optional[StrictStr] = None,
|
||||
limit: Optional[StrictInt] = None,
|
||||
offset: Optional[StrictInt] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[ListDocumentsResponse]:
|
||||
"""List documents
|
||||
|
||||
List documents with pagination and optional search. Documents are the source content from which memory units are extracted.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param q:
|
||||
:type q: str
|
||||
:param limit:
|
||||
:type limit: int
|
||||
:param offset:
|
||||
:type offset: int
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_documents_serialize(
|
||||
agent_id=agent_id,
|
||||
q=q,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ListDocumentsResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def list_documents_without_preload_content(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
q: Optional[StrictStr] = None,
|
||||
limit: Optional[StrictInt] = None,
|
||||
offset: Optional[StrictInt] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""List documents
|
||||
|
||||
List documents with pagination and optional search. Documents are the source content from which memory units are extracted.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param q:
|
||||
:type q: str
|
||||
:param limit:
|
||||
:type limit: int
|
||||
:param offset:
|
||||
:type offset: int
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._list_documents_serialize(
|
||||
agent_id=agent_id,
|
||||
q=q,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ListDocumentsResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _list_documents_serialize(
|
||||
self,
|
||||
agent_id,
|
||||
q,
|
||||
limit,
|
||||
offset,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if agent_id is not None:
|
||||
_path_params['agent_id'] = agent_id
|
||||
# process the query parameters
|
||||
if q is not None:
|
||||
|
||||
_query_params.append(('q', q))
|
||||
|
||||
if limit is not None:
|
||||
|
||||
_query_params.append(('limit', limit))
|
||||
|
||||
if offset is not None:
|
||||
|
||||
_query_params.append(('offset', offset))
|
||||
|
||||
# process the header parameters
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/api/v1/agents/{agent_id}/documents',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,329 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import StrictStr
|
||||
from hindsight_client_api.models.think_request import ThinkRequest
|
||||
from hindsight_client_api.models.think_response import ThinkResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class ReasoningApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def think(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
think_request: ThinkRequest,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ThinkResponse:
|
||||
"""Think and generate answer
|
||||
|
||||
Think and formulate an answer using agent identity, world facts, and opinions. This endpoint: 1. Retrieves agent facts (agent's identity) 2. Retrieves world facts relevant to the query 3. Retrieves existing opinions (agent's perspectives) 4. Uses LLM to formulate a contextual answer 5. Extracts and stores any new opinions formed 6. Returns plain text answer, the facts used, and new opinions
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param think_request: (required)
|
||||
:type think_request: ThinkRequest
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._think_serialize(
|
||||
agent_id=agent_id,
|
||||
think_request=think_request,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ThinkResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def think_with_http_info(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
think_request: ThinkRequest,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[ThinkResponse]:
|
||||
"""Think and generate answer
|
||||
|
||||
Think and formulate an answer using agent identity, world facts, and opinions. This endpoint: 1. Retrieves agent facts (agent's identity) 2. Retrieves world facts relevant to the query 3. Retrieves existing opinions (agent's perspectives) 4. Uses LLM to formulate a contextual answer 5. Extracts and stores any new opinions formed 6. Returns plain text answer, the facts used, and new opinions
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param think_request: (required)
|
||||
:type think_request: ThinkRequest
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._think_serialize(
|
||||
agent_id=agent_id,
|
||||
think_request=think_request,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ThinkResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def think_without_preload_content(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
think_request: ThinkRequest,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Think and generate answer
|
||||
|
||||
Think and formulate an answer using agent identity, world facts, and opinions. This endpoint: 1. Retrieves agent facts (agent's identity) 2. Retrieves world facts relevant to the query 3. Retrieves existing opinions (agent's perspectives) 4. Uses LLM to formulate a contextual answer 5. Extracts and stores any new opinions formed 6. Returns plain text answer, the facts used, and new opinions
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param think_request: (required)
|
||||
:type think_request: ThinkRequest
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._think_serialize(
|
||||
agent_id=agent_id,
|
||||
think_request=think_request,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "ThinkResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _think_serialize(
|
||||
self,
|
||||
agent_id,
|
||||
think_request,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if agent_id is not None:
|
||||
_path_params['agent_id'] = agent_id
|
||||
# process the query parameters
|
||||
# process the header parameters
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
if think_request is not None:
|
||||
_body_params = think_request
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
# set the HTTP header `Content-Type`
|
||||
if _content_type:
|
||||
_header_params['Content-Type'] = _content_type
|
||||
else:
|
||||
_default_content_type = (
|
||||
self.api_client.select_header_content_type(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
)
|
||||
if _default_content_type is not None:
|
||||
_header_params['Content-Type'] = _default_content_type
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='POST',
|
||||
resource_path='/api/v1/agents/{agent_id}/think',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1,318 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
||||
Do not edit the class manually.
|
||||
""" # noqa: E501
|
||||
|
||||
import warnings
|
||||
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from pydantic import StrictStr
|
||||
from typing import Optional
|
||||
from hindsight_client_api.models.graph_data_response import GraphDataResponse
|
||||
|
||||
from hindsight_client_api.api_client import ApiClient, RequestSerialized
|
||||
from hindsight_client_api.api_response import ApiResponse
|
||||
from hindsight_client_api.rest import RESTResponseType
|
||||
|
||||
|
||||
class VisualizationApi:
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, api_client=None) -> None:
|
||||
if api_client is None:
|
||||
api_client = ApiClient.get_default()
|
||||
self.api_client = api_client
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_graph(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
fact_type: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> GraphDataResponse:
|
||||
"""Get memory graph data
|
||||
|
||||
Retrieve graph data for visualization, optionally filtered by fact_type (world/agent/opinion). Limited to 1000 most recent items.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param fact_type:
|
||||
:type fact_type: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_graph_serialize(
|
||||
agent_id=agent_id,
|
||||
fact_type=fact_type,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "GraphDataResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
).data
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_graph_with_http_info(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
fact_type: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> ApiResponse[GraphDataResponse]:
|
||||
"""Get memory graph data
|
||||
|
||||
Retrieve graph data for visualization, optionally filtered by fact_type (world/agent/opinion). Limited to 1000 most recent items.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param fact_type:
|
||||
:type fact_type: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_graph_serialize(
|
||||
agent_id=agent_id,
|
||||
fact_type=fact_type,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "GraphDataResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
await response_data.read()
|
||||
return self.api_client.response_deserialize(
|
||||
response_data=response_data,
|
||||
response_types_map=_response_types_map,
|
||||
)
|
||||
|
||||
|
||||
@validate_call
|
||||
async def get_graph_without_preload_content(
|
||||
self,
|
||||
agent_id: StrictStr,
|
||||
fact_type: Optional[StrictStr] = None,
|
||||
_request_timeout: Union[
|
||||
None,
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Tuple[
|
||||
Annotated[StrictFloat, Field(gt=0)],
|
||||
Annotated[StrictFloat, Field(gt=0)]
|
||||
]
|
||||
] = None,
|
||||
_request_auth: Optional[Dict[StrictStr, Any]] = None,
|
||||
_content_type: Optional[StrictStr] = None,
|
||||
_headers: Optional[Dict[StrictStr, Any]] = None,
|
||||
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
|
||||
) -> RESTResponseType:
|
||||
"""Get memory graph data
|
||||
|
||||
Retrieve graph data for visualization, optionally filtered by fact_type (world/agent/opinion). Limited to 1000 most recent items.
|
||||
|
||||
:param agent_id: (required)
|
||||
:type agent_id: str
|
||||
:param fact_type:
|
||||
:type fact_type: str
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:type _request_timeout: int, tuple(int, int), optional
|
||||
:param _request_auth: set to override the auth_settings for an a single
|
||||
request; this effectively ignores the
|
||||
authentication in the spec for a single request.
|
||||
:type _request_auth: dict, optional
|
||||
:param _content_type: force content-type for the request.
|
||||
:type _content_type: str, Optional
|
||||
:param _headers: set to override the headers for a single
|
||||
request; this effectively ignores the headers
|
||||
in the spec for a single request.
|
||||
:type _headers: dict, optional
|
||||
:param _host_index: set to override the host_index for a single
|
||||
request; this effectively ignores the host_index
|
||||
in the spec for a single request.
|
||||
:type _host_index: int, optional
|
||||
:return: Returns the result object.
|
||||
""" # noqa: E501
|
||||
|
||||
_param = self._get_graph_serialize(
|
||||
agent_id=agent_id,
|
||||
fact_type=fact_type,
|
||||
_request_auth=_request_auth,
|
||||
_content_type=_content_type,
|
||||
_headers=_headers,
|
||||
_host_index=_host_index
|
||||
)
|
||||
|
||||
_response_types_map: Dict[str, Optional[str]] = {
|
||||
'200': "GraphDataResponse",
|
||||
'422': "HTTPValidationError",
|
||||
}
|
||||
response_data = await self.api_client.call_api(
|
||||
*_param,
|
||||
_request_timeout=_request_timeout
|
||||
)
|
||||
return response_data.response
|
||||
|
||||
|
||||
def _get_graph_serialize(
|
||||
self,
|
||||
agent_id,
|
||||
fact_type,
|
||||
_request_auth,
|
||||
_content_type,
|
||||
_headers,
|
||||
_host_index,
|
||||
) -> RequestSerialized:
|
||||
|
||||
_host = None
|
||||
|
||||
_collection_formats: Dict[str, str] = {
|
||||
}
|
||||
|
||||
_path_params: Dict[str, str] = {}
|
||||
_query_params: List[Tuple[str, str]] = []
|
||||
_header_params: Dict[str, Optional[str]] = _headers or {}
|
||||
_form_params: List[Tuple[str, str]] = []
|
||||
_files: Dict[
|
||||
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
|
||||
] = {}
|
||||
_body_params: Optional[bytes] = None
|
||||
|
||||
# process the path parameters
|
||||
if agent_id is not None:
|
||||
_path_params['agent_id'] = agent_id
|
||||
# process the query parameters
|
||||
if fact_type is not None:
|
||||
|
||||
_query_params.append(('fact_type', fact_type))
|
||||
|
||||
# process the header parameters
|
||||
# process the form parameters
|
||||
# process the body parameter
|
||||
|
||||
|
||||
# set the HTTP header `Accept`
|
||||
if 'Accept' not in _header_params:
|
||||
_header_params['Accept'] = self.api_client.select_header_accept(
|
||||
[
|
||||
'application/json'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# authentication setting
|
||||
_auth_settings: List[str] = [
|
||||
]
|
||||
|
||||
return self.api_client.param_serialize(
|
||||
method='GET',
|
||||
resource_path='/api/v1/agents/{agent_id}/graph',
|
||||
path_params=_path_params,
|
||||
query_params=_query_params,
|
||||
header_params=_header_params,
|
||||
body=_body_params,
|
||||
post_params=_form_params,
|
||||
files=_files,
|
||||
auth_settings=_auth_settings,
|
||||
collection_formats=_collection_formats,
|
||||
_host=_host,
|
||||
_request_auth=_request_auth
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
Hindsight HTTP API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Agent Memory API
|
||||
Hindsight HTTP API
|
||||
|
||||
A temporal-semantic memory system for AI agents that stores, retrieves, and reasons over memories. ## Features * **Batch Memory Storage**: Store multiple memories efficiently with automatic fact extraction * **Semantic Search**: Find relevant memories using natural language queries * **Fact Type Filtering**: Search across world facts, agent actions, and opinions separately * **Think Endpoint**: Generate contextual answers based on agent identity and memories * **Graph Visualization**: Interactive memory graph visualization * **Document Tracking**: Track and manage memory documents with upsert support ## Architecture The system uses: - **Temporal Links**: Connect memories that are close in time - **Semantic Links**: Connect semantically similar memories - **Entity Links**: Connect memories that mention the same entities - **Spreading Activation**: Intelligent traversal for memory retrieval
|
||||
HTTP API for Hindsight
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
# AgentListResponse
|
||||
|
||||
Response model for listing all agents.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**agents** | [**List[AgentListItem]**](AgentListItem.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.agent_list_response import AgentListResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of AgentListResponse from a JSON string
|
||||
agent_list_response_instance = AgentListResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(AgentListResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
agent_list_response_dict = agent_list_response_instance.to_dict()
|
||||
# create an instance of AgentListResponse from a dict
|
||||
agent_list_response_from_dict = AgentListResponse.from_dict(agent_list_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,503 +0,0 @@
|
|||
# hindsight_client_api.AgentManagementApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**add_agent_background**](AgentManagementApi.md#add_agent_background) | **POST** /api/v1/agents/{agent_id}/background | Add/merge agent background
|
||||
[**clear_agent_memories**](AgentManagementApi.md#clear_agent_memories) | **DELETE** /api/v1/agents/{agent_id}/memories | Clear agent memories
|
||||
[**create_or_update_agent**](AgentManagementApi.md#create_or_update_agent) | **PUT** /api/v1/agents/{agent_id} | Create or update agent
|
||||
[**get_agent_profile**](AgentManagementApi.md#get_agent_profile) | **GET** /api/v1/agents/{agent_id}/profile | Get agent profile
|
||||
[**get_agent_stats**](AgentManagementApi.md#get_agent_stats) | **GET** /api/v1/agents/{agent_id}/stats | Get memory statistics for an agent
|
||||
[**list_agents**](AgentManagementApi.md#list_agents) | **GET** /api/v1/agents | List all agents
|
||||
[**update_agent_personality**](AgentManagementApi.md#update_agent_personality) | **PUT** /api/v1/agents/{agent_id}/profile | Update agent personality
|
||||
|
||||
|
||||
# **add_agent_background**
|
||||
> BackgroundResponse add_agent_background(agent_id, add_background_request)
|
||||
|
||||
Add/merge agent background
|
||||
|
||||
Add new background information or merge with existing. LLM intelligently resolves conflicts, normalizes to first person, and optionally infers personality traits.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest
|
||||
from hindsight_client_api.models.background_response import BackgroundResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
add_background_request = hindsight_client_api.AddBackgroundRequest() # AddBackgroundRequest |
|
||||
|
||||
try:
|
||||
# Add/merge agent background
|
||||
api_response = await api_instance.add_agent_background(agent_id, add_background_request)
|
||||
print("The response of AgentManagementApi->add_agent_background:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->add_agent_background: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**add_background_request** | [**AddBackgroundRequest**](AddBackgroundRequest.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**BackgroundResponse**](BackgroundResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **clear_agent_memories**
|
||||
> DeleteResponse clear_agent_memories(agent_id, fact_type=fact_type)
|
||||
|
||||
Clear agent memories
|
||||
|
||||
Delete memory units for an agent. Optionally filter by fact_type (world, agent, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The agent profile (personality and background) will be preserved.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.delete_response import DeleteResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
fact_type = 'fact_type_example' # str | Optional fact type filter (world, agent, opinion) (optional)
|
||||
|
||||
try:
|
||||
# Clear agent memories
|
||||
api_response = await api_instance.clear_agent_memories(agent_id, fact_type=fact_type)
|
||||
print("The response of AgentManagementApi->clear_agent_memories:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->clear_agent_memories: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**fact_type** | **str**| Optional fact type filter (world, agent, opinion) | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
[**DeleteResponse**](DeleteResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **create_or_update_agent**
|
||||
> AgentProfileResponse create_or_update_agent(agent_id, create_agent_request)
|
||||
|
||||
Create or update agent
|
||||
|
||||
Create a new agent or update existing agent with personality and background. Auto-fills missing fields with defaults.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.agent_profile_response import AgentProfileResponse
|
||||
from hindsight_client_api.models.create_agent_request import CreateAgentRequest
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
create_agent_request = hindsight_client_api.CreateAgentRequest() # CreateAgentRequest |
|
||||
|
||||
try:
|
||||
# Create or update agent
|
||||
api_response = await api_instance.create_or_update_agent(agent_id, create_agent_request)
|
||||
print("The response of AgentManagementApi->create_or_update_agent:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->create_or_update_agent: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**create_agent_request** | [**CreateAgentRequest**](CreateAgentRequest.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**AgentProfileResponse**](AgentProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_agent_profile**
|
||||
> AgentProfileResponse get_agent_profile(agent_id)
|
||||
|
||||
Get agent profile
|
||||
|
||||
Get personality traits and background for an agent. Auto-creates agent with defaults if not exists.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.agent_profile_response import AgentProfileResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
|
||||
try:
|
||||
# Get agent profile
|
||||
api_response = await api_instance.get_agent_profile(agent_id)
|
||||
print("The response of AgentManagementApi->get_agent_profile:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->get_agent_profile: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**AgentProfileResponse**](AgentProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_agent_stats**
|
||||
> object get_agent_stats(agent_id)
|
||||
|
||||
Get memory statistics for an agent
|
||||
|
||||
Get statistics about nodes and links for a specific agent
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
|
||||
try:
|
||||
# Get memory statistics for an agent
|
||||
api_response = await api_instance.get_agent_stats(agent_id)
|
||||
print("The response of AgentManagementApi->get_agent_stats:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->get_agent_stats: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
**object**
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_agents**
|
||||
> AgentListResponse list_agents()
|
||||
|
||||
List all agents
|
||||
|
||||
Get a list of all agents with their profiles
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.agent_list_response import AgentListResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
|
||||
try:
|
||||
# List all agents
|
||||
api_response = await api_instance.list_agents()
|
||||
print("The response of AgentManagementApi->list_agents:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->list_agents: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**AgentListResponse**](AgentListResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **update_agent_personality**
|
||||
> AgentProfileResponse update_agent_personality(agent_id, update_personality_request)
|
||||
|
||||
Update agent personality
|
||||
|
||||
Update agent's Big Five personality traits and bias strength
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.agent_profile_response import AgentProfileResponse
|
||||
from hindsight_client_api.models.update_personality_request import UpdatePersonalityRequest
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.AgentManagementApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
update_personality_request = hindsight_client_api.UpdatePersonalityRequest() # UpdatePersonalityRequest |
|
||||
|
||||
try:
|
||||
# Update agent personality
|
||||
api_response = await api_instance.update_agent_personality(agent_id, update_personality_request)
|
||||
print("The response of AgentManagementApi->update_agent_personality:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling AgentManagementApi->update_agent_personality: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**update_personality_request** | [**UpdatePersonalityRequest**](UpdatePersonalityRequest.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**AgentProfileResponse**](AgentProfileResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# AgentProfileResponse
|
||||
|
||||
Response model for agent profile.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**agent_id** | **str** | |
|
||||
**name** | **str** | |
|
||||
**personality** | [**PersonalityTraits**](PersonalityTraits.md) | |
|
||||
**background** | **str** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.agent_profile_response import AgentProfileResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of AgentProfileResponse from a JSON string
|
||||
agent_profile_response_instance = AgentProfileResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(AgentProfileResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
agent_profile_response_dict = agent_profile_response_instance.to_dict()
|
||||
# create an instance of AgentProfileResponse from a dict
|
||||
agent_profile_response_from_dict = AgentProfileResponse.from_dict(agent_profile_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
# AgentListItem
|
||||
# BankListItem
|
||||
|
||||
Agent list item with profile summary.
|
||||
Bank list item with profile summary.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**agent_id** | **str** | |
|
||||
**bank_id** | **str** | |
|
||||
**name** | **str** | |
|
||||
**personality** | [**PersonalityTraits**](PersonalityTraits.md) | |
|
||||
**background** | **str** | |
|
||||
|
|
@ -16,19 +16,19 @@ Name | Type | Description | Notes
|
|||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.agent_list_item import AgentListItem
|
||||
from hindsight_client_api.models.bank_list_item import BankListItem
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of AgentListItem from a JSON string
|
||||
agent_list_item_instance = AgentListItem.from_json(json)
|
||||
# create an instance of BankListItem from a JSON string
|
||||
bank_list_item_instance = BankListItem.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(AgentListItem.to_json())
|
||||
print(BankListItem.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
agent_list_item_dict = agent_list_item_instance.to_dict()
|
||||
# create an instance of AgentListItem from a dict
|
||||
agent_list_item_from_dict = AgentListItem.from_dict(agent_list_item_dict)
|
||||
bank_list_item_dict = bank_list_item_instance.to_dict()
|
||||
# create an instance of BankListItem from a dict
|
||||
bank_list_item_from_dict = BankListItem.from_dict(bank_list_item_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# BankListResponse
|
||||
|
||||
Response model for listing all banks.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**banks** | [**List[BankListItem]**](BankListItem.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.bank_list_response import BankListResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BankListResponse from a JSON string
|
||||
bank_list_response_instance = BankListResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BankListResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
bank_list_response_dict = bank_list_response_instance.to_dict()
|
||||
# create an instance of BankListResponse from a dict
|
||||
bank_list_response_from_dict = BankListResponse.from_dict(bank_list_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# BankProfileResponse
|
||||
|
||||
Response model for bank profile.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**bank_id** | **str** | |
|
||||
**name** | **str** | |
|
||||
**personality** | [**PersonalityTraits**](PersonalityTraits.md) | |
|
||||
**background** | **str** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.bank_profile_response import BankProfileResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BankProfileResponse from a JSON string
|
||||
bank_profile_response_instance = BankProfileResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BankProfileResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
bank_profile_response_dict = bank_profile_response_instance.to_dict()
|
||||
# create an instance of BankProfileResponse from a dict
|
||||
bank_profile_response_from_dict = BankProfileResponse.from_dict(bank_profile_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# BatchPutAsyncResponse
|
||||
|
||||
Response model for async batch put endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | |
|
||||
**agent_id** | **str** | |
|
||||
**document_id** | **str** | | [optional]
|
||||
**items_count** | **int** | |
|
||||
**queued** | **bool** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.batch_put_async_response import BatchPutAsyncResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BatchPutAsyncResponse from a JSON string
|
||||
batch_put_async_response_instance = BatchPutAsyncResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BatchPutAsyncResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
batch_put_async_response_dict = batch_put_async_response_instance.to_dict()
|
||||
# create an instance of BatchPutAsyncResponse from a dict
|
||||
batch_put_async_response_from_dict = BatchPutAsyncResponse.from_dict(batch_put_async_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# BatchPutRequest
|
||||
|
||||
Request model for batch put endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**items** | [**List[MemoryItem]**](MemoryItem.md) | |
|
||||
**document_id** | **str** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.batch_put_request import BatchPutRequest
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BatchPutRequest from a JSON string
|
||||
batch_put_request_instance = BatchPutRequest.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BatchPutRequest.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
batch_put_request_dict = batch_put_request_instance.to_dict()
|
||||
# create an instance of BatchPutRequest from a dict
|
||||
batch_put_request_from_dict = BatchPutRequest.from_dict(batch_put_request_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# BatchPutResponse
|
||||
|
||||
Response model for batch put endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | |
|
||||
**agent_id** | **str** | |
|
||||
**document_id** | **str** | | [optional]
|
||||
**items_count** | **int** | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.batch_put_response import BatchPutResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of BatchPutResponse from a JSON string
|
||||
batch_put_response_instance = BatchPutResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(BatchPutResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
batch_put_response_dict = batch_put_response_instance.to_dict()
|
||||
# create an instance of BatchPutResponse from a dict
|
||||
batch_put_response_from_dict = BatchPutResponse.from_dict(batch_put_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
15
hindsight-clients/python/hindsight_client_api/docs/Budget.md
Normal file
15
hindsight-clients/python/hindsight_client_api/docs/Budget.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Budget
|
||||
|
||||
Budget levels for recall/reflect operations.
|
||||
|
||||
## Enum
|
||||
|
||||
* `LOW` (value: `'low'`)
|
||||
|
||||
* `MID` (value: `'mid'`)
|
||||
|
||||
* `HIGH` (value: `'high'`)
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# CreateAgentRequest
|
||||
# CreateBankRequest
|
||||
|
||||
Request model for creating/updating an agent.
|
||||
Request model for creating/updating a bank.
|
||||
|
||||
## Properties
|
||||
|
||||
|
|
@ -13,19 +13,19 @@ Name | Type | Description | Notes
|
|||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.create_agent_request import CreateAgentRequest
|
||||
from hindsight_client_api.models.create_bank_request import CreateBankRequest
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of CreateAgentRequest from a JSON string
|
||||
create_agent_request_instance = CreateAgentRequest.from_json(json)
|
||||
# create an instance of CreateBankRequest from a JSON string
|
||||
create_bank_request_instance = CreateBankRequest.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(CreateAgentRequest.to_json())
|
||||
print(CreateBankRequest.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
create_agent_request_dict = create_agent_request_instance.to_dict()
|
||||
# create an instance of CreateAgentRequest from a dict
|
||||
create_agent_request_from_dict = CreateAgentRequest.from_dict(create_agent_request_dict)
|
||||
create_bank_request_dict = create_bank_request_instance.to_dict()
|
||||
# create an instance of CreateBankRequest from a dict
|
||||
create_bank_request_from_dict = CreateBankRequest.from_dict(create_bank_request_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
1499
hindsight-clients/python/hindsight_client_api/docs/DefaultApi.md
Normal file
1499
hindsight-clients/python/hindsight_client_api/docs/DefaultApi.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,6 @@ Response model for delete operations.
|
|||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**success** | **bool** | |
|
||||
**message** | **str** | |
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
|||
|
|
@ -1,234 +0,0 @@
|
|||
# hindsight_client_api.DocumentsApi
|
||||
|
||||
All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**delete_document**](DocumentsApi.md#delete_document) | **DELETE** /api/v1/agents/{agent_id}/documents/{document_id} | Delete a document
|
||||
[**get_document**](DocumentsApi.md#get_document) | **GET** /api/v1/agents/{agent_id}/documents/{document_id} | Get document details
|
||||
[**list_documents**](DocumentsApi.md#list_documents) | **GET** /api/v1/agents/{agent_id}/documents | List documents
|
||||
|
||||
|
||||
# **delete_document**
|
||||
> object delete_document(agent_id, document_id)
|
||||
|
||||
Delete a document
|
||||
|
||||
Delete a document and all its associated memory units and links.
|
||||
|
||||
This will cascade delete:
|
||||
- The document itself
|
||||
- All memory units extracted from this document
|
||||
- All links (temporal, semantic, entity) associated with those memory units
|
||||
|
||||
This operation cannot be undone.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
document_id = 'document_id_example' # str |
|
||||
|
||||
try:
|
||||
# Delete a document
|
||||
api_response = await api_instance.delete_document(agent_id, document_id)
|
||||
print("The response of DocumentsApi->delete_document:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->delete_document: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**document_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
**object**
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **get_document**
|
||||
> DocumentResponse get_document(agent_id, document_id)
|
||||
|
||||
Get document details
|
||||
|
||||
Get a specific document including its original text
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.document_response import DocumentResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
document_id = 'document_id_example' # str |
|
||||
|
||||
try:
|
||||
# Get document details
|
||||
api_response = await api_instance.get_document(agent_id, document_id)
|
||||
print("The response of DocumentsApi->get_document:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->get_document: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**document_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**DocumentResponse**](DocumentResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **list_documents**
|
||||
> ListDocumentsResponse list_documents(agent_id, q=q, limit=limit, offset=offset)
|
||||
|
||||
List documents
|
||||
|
||||
List documents with pagination and optional search. Documents are the source content from which memory units are extracted.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import hindsight_client_api
|
||||
from hindsight_client_api.models.list_documents_response import ListDocumentsResponse
|
||||
from hindsight_client_api.rest import ApiException
|
||||
from pprint import pprint
|
||||
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = hindsight_client_api.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
async with hindsight_client_api.ApiClient(configuration) as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = hindsight_client_api.DocumentsApi(api_client)
|
||||
agent_id = 'agent_id_example' # str |
|
||||
q = 'q_example' # str | (optional)
|
||||
limit = 100 # int | (optional) (default to 100)
|
||||
offset = 0 # int | (optional) (default to 0)
|
||||
|
||||
try:
|
||||
# List documents
|
||||
api_response = await api_instance.list_documents(agent_id, q=q, limit=limit, offset=offset)
|
||||
print("The response of DocumentsApi->list_documents:\n")
|
||||
pprint(api_response)
|
||||
except Exception as e:
|
||||
print("Exception when calling DocumentsApi->list_documents: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**agent_id** | **str**| |
|
||||
**q** | **str**| | [optional]
|
||||
**limit** | **int**| | [optional] [default to 100]
|
||||
**offset** | **int**| | [optional] [default to 0]
|
||||
|
||||
### Return type
|
||||
|
||||
[**ListDocumentsResponse**](ListDocumentsResponse.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Successful Response | - |
|
||||
**422** | Validation Error | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# EntityDetailResponse
|
||||
|
||||
Response model for entity detail endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **str** | |
|
||||
**canonical_name** | **str** | |
|
||||
**mention_count** | **int** | |
|
||||
**first_seen** | **str** | | [optional]
|
||||
**last_seen** | **str** | | [optional]
|
||||
**metadata** | **Dict[str, object]** | | [optional]
|
||||
**observations** | [**List[EntityObservationResponse]**](EntityObservationResponse.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_detail_response import EntityDetailResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityDetailResponse from a JSON string
|
||||
entity_detail_response_instance = EntityDetailResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityDetailResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_detail_response_dict = entity_detail_response_instance.to_dict()
|
||||
# create an instance of EntityDetailResponse from a dict
|
||||
entity_detail_response_from_dict = EntityDetailResponse.from_dict(entity_detail_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# EntityIncludeOptions
|
||||
|
||||
Options for including entity observations in recall results.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**max_tokens** | **int** | Maximum tokens for entity observations | [optional] [default to 500]
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_include_options import EntityIncludeOptions
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityIncludeOptions from a JSON string
|
||||
entity_include_options_instance = EntityIncludeOptions.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityIncludeOptions.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_include_options_dict = entity_include_options_instance.to_dict()
|
||||
# create an instance of EntityIncludeOptions from a dict
|
||||
entity_include_options_from_dict = EntityIncludeOptions.from_dict(entity_include_options_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# EntityListItem
|
||||
|
||||
Entity list item with summary.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**id** | **str** | |
|
||||
**canonical_name** | **str** | |
|
||||
**mention_count** | **int** | |
|
||||
**first_seen** | **str** | | [optional]
|
||||
**last_seen** | **str** | | [optional]
|
||||
**metadata** | **Dict[str, object]** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_list_item import EntityListItem
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityListItem from a JSON string
|
||||
entity_list_item_instance = EntityListItem.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityListItem.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_list_item_dict = entity_list_item_instance.to_dict()
|
||||
# create an instance of EntityListItem from a dict
|
||||
entity_list_item_from_dict = EntityListItem.from_dict(entity_list_item_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# EntityListResponse
|
||||
|
||||
Response model for entity list endpoint.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**entities** | [**List[EntityListItem]**](EntityListItem.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_list_response import EntityListResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityListResponse from a JSON string
|
||||
entity_list_response_instance = EntityListResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityListResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_list_response_dict = entity_list_response_instance.to_dict()
|
||||
# create an instance of EntityListResponse from a dict
|
||||
entity_list_response_from_dict = EntityListResponse.from_dict(entity_list_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# EntityObservationResponse
|
||||
|
||||
An observation about an entity.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**text** | **str** | |
|
||||
**mentioned_at** | **str** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_observation_response import EntityObservationResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityObservationResponse from a JSON string
|
||||
entity_observation_response_instance = EntityObservationResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityObservationResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_observation_response_dict = entity_observation_response_instance.to_dict()
|
||||
# create an instance of EntityObservationResponse from a dict
|
||||
entity_observation_response_from_dict = EntityObservationResponse.from_dict(entity_observation_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# EntityStateResponse
|
||||
|
||||
Current mental model of an entity.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**entity_id** | **str** | |
|
||||
**canonical_name** | **str** | |
|
||||
**observations** | [**List[EntityObservationResponse]**](EntityObservationResponse.md) | |
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.entity_state_response import EntityStateResponse
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of EntityStateResponse from a JSON string
|
||||
entity_state_response_instance = EntityStateResponse.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(EntityStateResponse.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
entity_state_response_dict = entity_state_response_instance.to_dict()
|
||||
# create an instance of EntityStateResponse from a dict
|
||||
entity_state_response_from_dict = EntityStateResponse.from_dict(entity_state_response_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# IncludeOptions
|
||||
|
||||
Options for including additional data in recall results.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**entities** | [**EntityIncludeOptions**](EntityIncludeOptions.md) | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from hindsight_client_api.models.include_options import IncludeOptions
|
||||
|
||||
# TODO update the JSON string below
|
||||
json = "{}"
|
||||
# create an instance of IncludeOptions from a JSON string
|
||||
include_options_instance = IncludeOptions.from_json(json)
|
||||
# print the JSON string representation of the object
|
||||
print(IncludeOptions.to_json())
|
||||
|
||||
# convert the object into a dict
|
||||
include_options_dict = include_options_instance.to_dict()
|
||||
# create an instance of IncludeOptions from a dict
|
||||
include_options_from_dict = IncludeOptions.from_dict(include_options_dict)
|
||||
```
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
# MemoryItem
|
||||
|
||||
Single memory item for batch put.
|
||||
Single memory item for retain.
|
||||
|
||||
## Properties
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**content** | **str** | |
|
||||
**event_date** | **datetime** | | [optional]
|
||||
**timestamp** | **datetime** | | [optional]
|
||||
**context** | **str** | | [optional]
|
||||
**metadata** | **Dict[str, str]** | | [optional]
|
||||
|
||||
## Example
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue