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:
|
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 = HindsightClient(base_url=server.url)
|
||||||
|
|
||||||
client.put(agent_id="my-agent", content="Alice works at Google")
|
# Retain memories
|
||||||
client.put(agent_id="my-agent", content="Bob prefers Python over JavaScript")
|
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
|
# Re-export commonly used items for backwards compatibility
|
||||||
from .http import (
|
from .http import (
|
||||||
SearchRequest,
|
RecallRequest,
|
||||||
SearchResult,
|
RecallResult,
|
||||||
SearchResponse,
|
RecallResponse,
|
||||||
MemoryItem,
|
MemoryItem,
|
||||||
BatchPutRequest,
|
RetainRequest,
|
||||||
ThinkRequest,
|
ReflectRequest,
|
||||||
ThinkResponse,
|
ReflectResponse,
|
||||||
CreateAgentRequest,
|
CreateBankRequest,
|
||||||
PersonalityTraits,
|
PersonalityTraits,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"create_app",
|
"create_app",
|
||||||
"SearchRequest",
|
"RecallRequest",
|
||||||
"SearchResult",
|
"RecallResult",
|
||||||
"SearchResponse",
|
"RecallResponse",
|
||||||
"MemoryItem",
|
"MemoryItem",
|
||||||
"BatchPutRequest",
|
"RetainRequest",
|
||||||
"ThinkRequest",
|
"ReflectRequest",
|
||||||
"ThinkResponse",
|
"ReflectResponse",
|
||||||
"CreateAgentRequest",
|
"CreateBankRequest",
|
||||||
"PersonalityTraits",
|
"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 = FastMCP("hindsight-mcp-server")
|
||||||
|
|
||||||
@mcp.tool()
|
@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.**
|
**CRITICAL: Store important user information to long-term memory.**
|
||||||
|
|
||||||
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
||||||
- This tool is STRICTLY per-user. Each user MUST have a unique `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 `agent_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 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.
|
- If you don't have a user identifier, DO NOT use this tool at all.
|
||||||
|
|
||||||
Use this tool PROACTIVELY whenever the user shares:
|
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.
|
"career_goals", "project_details", etc. This helps organize and retrieve related memories later.
|
||||||
|
|
||||||
Args:
|
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.
|
This MUST be consistent across all interactions with the same user.
|
||||||
Example: "user_12345", "alice@example.com", "session_abc123"
|
Example: "user_12345", "alice@example.com", "session_abc123"
|
||||||
content: The fact/memory to store (be specific and include relevant details)
|
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
|
# Store memory using put_batch_async
|
||||||
await memory.put_batch_async(
|
await memory.put_batch_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
contents=[{"content": content, "context": context}]
|
contents=[{"content": content, "context": context}]
|
||||||
)
|
)
|
||||||
return f"Fact stored successfully"
|
return f"Fact stored successfully"
|
||||||
|
|
@ -74,15 +74,15 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
||||||
return f"Error: {str(e)}"
|
return f"Error: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@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.**
|
**CRITICAL: Search user's memory to provide personalized, context-aware responses.**
|
||||||
|
|
||||||
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
**⚠️ PER-USER TOOL - REQUIRES USER IDENTIFICATION:**
|
||||||
- This tool is STRICTLY per-user. Each user MUST have a unique `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 `agent_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 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.
|
- 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:
|
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?"
|
"user's work experience", "user's dietary restrictions", "what does the user know about X?"
|
||||||
|
|
||||||
Args:
|
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.
|
This MUST be consistent across all interactions with the same user.
|
||||||
Example: "user_12345", "alice@example.com", "session_abc123"
|
Example: "user_12345", "alice@example.com", "session_abc123"
|
||||||
query: Natural language search query to find relevant memories
|
query: Natural language search query to find relevant memories
|
||||||
|
|
@ -118,13 +118,14 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
|
||||||
if explanation:
|
if explanation:
|
||||||
logger.debug(f"Explanation: {explanation}")
|
logger.debug(f"Explanation: {explanation}")
|
||||||
|
|
||||||
# Search using search_async
|
# Search using recall_async
|
||||||
search_result = await memory.search_async(
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
agent_id=agent_id,
|
search_result = await memory.recall_async(
|
||||||
|
bank_id=bank_id,
|
||||||
query=query,
|
query=query,
|
||||||
fact_type=["world", "agent", "opinion"], # Search all fact types
|
fact_type=["world", "bank", "opinion"], # Search all fact types
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
thinking_budget=100
|
budget=Budget.LOW
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert results to dict format
|
# 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:
|
This package contains all the implementation details of the memory engine:
|
||||||
- MemoryEngine: Main class for memory operations
|
- 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.
|
- Supporting modules: embeddings, cross_encoder, entity_resolver, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -23,7 +23,7 @@ from .search_trace import (
|
||||||
)
|
)
|
||||||
from .search_tracer import SearchTracer
|
from .search_tracer import SearchTracer
|
||||||
from .llm_wrapper import LLMConfig
|
from .llm_wrapper import LLMConfig
|
||||||
from .response_models import SearchResult, ThinkResult, MemoryFact
|
from .response_models import RecallResult, ReflectResult, MemoryFact
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"MemoryEngine",
|
"MemoryEngine",
|
||||||
|
|
@ -41,7 +41,7 @@ __all__ = [
|
||||||
"SearchSummary",
|
"SearchSummary",
|
||||||
"SearchPhaseMetrics",
|
"SearchPhaseMetrics",
|
||||||
"LLMConfig",
|
"LLMConfig",
|
||||||
"SearchResult",
|
"RecallResult",
|
||||||
"ThinkResult",
|
"ReflectResult",
|
||||||
"MemoryFact",
|
"MemoryFact",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Agent profile utilities for personality and background management.
|
bank profile utilities for personality and background management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
@ -37,27 +37,26 @@ class BackgroundMergeResponse(BaseModel):
|
||||||
personality: PersonalityTraits = Field(description="Inferred Big Five personality traits")
|
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).
|
Get bank profile (name, personality + background).
|
||||||
Auto-creates agent with default values if not exists.
|
Auto-creates bank with default values if not exists.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pool: Database connection pool
|
pool: Database connection pool
|
||||||
agent_id: Agent identifier
|
bank_id: bank IDentifier
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict with 'name' (str), 'personality' (dict) and 'background' (str) keys
|
Dict with 'name' (str), 'personality' (dict) and 'background' (str) keys
|
||||||
"""
|
"""
|
||||||
async with acquire_with_retry(pool) as conn:
|
async with acquire_with_retry(pool) as conn:
|
||||||
# Try to get existing agent
|
# Try to get existing bank
|
||||||
row = await conn.fetchrow(
|
row = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
SELECT name, personality, background
|
SELECT name, personality, background
|
||||||
FROM agents
|
FROM banks WHERE bank_id = $1
|
||||||
WHERE agent_id = $1
|
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if row:
|
if row:
|
||||||
|
|
@ -72,59 +71,59 @@ async def get_agent_profile(pool, agent_id: str) -> Dict:
|
||||||
"background": row["background"]
|
"background": row["background"]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Agent doesn't exist, create with defaults
|
# Bank doesn't exist, create with defaults
|
||||||
await conn.execute(
|
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)
|
VALUES ($1, $2, $3::jsonb, $4)
|
||||||
ON CONFLICT (agent_id) DO NOTHING
|
ON CONFLICT (bank_id) DO NOTHING
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
agent_id, # Default name is the agent_id
|
bank_id, # Default name is the bank_id
|
||||||
json.dumps(DEFAULT_PERSONALITY),
|
json.dumps(DEFAULT_PERSONALITY),
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": agent_id,
|
"name": bank_id,
|
||||||
"personality": DEFAULT_PERSONALITY.copy(),
|
"personality": DEFAULT_PERSONALITY.copy(),
|
||||||
"background": ""
|
"background": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def update_agent_personality(
|
async def update_bank_personality(
|
||||||
pool,
|
pool,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
personality: Dict[str, float]
|
personality: Dict[str, float]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Update agent personality traits.
|
Update bank personality traits.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pool: Database connection pool
|
pool: Database connection pool
|
||||||
agent_id: Agent identifier
|
bank_id: bank IDentifier
|
||||||
personality: Dict with Big Five traits + bias_strength (all 0-1)
|
personality: Dict with Big Five traits + bias_strength (all 0-1)
|
||||||
"""
|
"""
|
||||||
# Ensure agent exists first
|
# Ensure bank exists first
|
||||||
await get_agent_profile(pool, agent_id)
|
await get_bank_profile(pool, bank_id)
|
||||||
|
|
||||||
async with acquire_with_retry(pool) as conn:
|
async with acquire_with_retry(pool) as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE agents
|
UPDATE banks
|
||||||
SET personality = $2::jsonb,
|
SET personality = $2::jsonb,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
json.dumps(personality)
|
json.dumps(personality)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def merge_agent_background(
|
async def merge_bank_background(
|
||||||
pool,
|
pool,
|
||||||
llm_config,
|
llm_config,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
new_info: str,
|
new_info: str,
|
||||||
update_personality: bool = True
|
update_personality: bool = True
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
@ -136,7 +135,7 @@ async def merge_agent_background(
|
||||||
Args:
|
Args:
|
||||||
pool: Database connection pool
|
pool: Database connection pool
|
||||||
llm_config: LLM configuration for background merging
|
llm_config: LLM configuration for background merging
|
||||||
agent_id: Agent identifier
|
bank_id: bank IDentifier
|
||||||
new_info: New background information to add/merge
|
new_info: New background information to add/merge
|
||||||
update_personality: If True, infer Big Five traits from background (default: True)
|
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
|
Dict with 'background' (str) and optionally 'personality' (dict) keys
|
||||||
"""
|
"""
|
||||||
# Get current profile
|
# Get current profile
|
||||||
profile = await get_agent_profile(pool, agent_id)
|
profile = await get_bank_profile(pool, bank_id)
|
||||||
current_background = profile["background"]
|
current_background = profile["background"]
|
||||||
|
|
||||||
# Use LLM to merge backgrounds and optionally infer personality
|
# Use LLM to merge backgrounds and optionally infer personality
|
||||||
|
|
@ -164,13 +163,13 @@ async def merge_agent_background(
|
||||||
# Update both background and personality
|
# Update both background and personality
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE agents
|
UPDATE banks
|
||||||
SET background = $2,
|
SET background = $2,
|
||||||
personality = $3::jsonb,
|
personality = $3::jsonb,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
merged_background,
|
merged_background,
|
||||||
json.dumps(inferred_personality)
|
json.dumps(inferred_personality)
|
||||||
)
|
)
|
||||||
|
|
@ -178,12 +177,12 @@ async def merge_agent_background(
|
||||||
# Update only background
|
# Update only background
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE agents
|
UPDATE banks
|
||||||
SET background = $2,
|
SET background = $2,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
merged_background
|
merged_background
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -214,7 +213,7 @@ async def _llm_merge_background(
|
||||||
Dict with 'background' (str) and optionally 'personality' (dict) keys
|
Dict with 'background' (str) and optionally 'personality' (dict) keys
|
||||||
"""
|
"""
|
||||||
if infer_personality:
|
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)"}
|
Current background: {current if current else "(empty)"}
|
||||||
|
|
||||||
|
|
@ -257,7 +256,7 @@ Trait inference examples:
|
||||||
- "rational and diligent" → conscientiousness: 0.7+, openness: 0.6+
|
- "rational and diligent" → conscientiousness: 0.7+, openness: 0.6+
|
||||||
- "passionate and dramatic" → extraversion: 0.7+, neuroticism: 0.6+, openness: 0.7+"""
|
- "passionate and dramatic" → extraversion: 0.7+, neuroticism: 0.6+, openness: 0.7+"""
|
||||||
else:
|
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)"}
|
Current background: {current if current else "(empty)"}
|
||||||
|
|
||||||
|
|
@ -283,7 +282,7 @@ Merged background:"""
|
||||||
parsed = await llm_config.call(
|
parsed = await llm_config.call(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
response_format=BackgroundMergeResponse,
|
response_format=BackgroundMergeResponse,
|
||||||
scope="agent_background",
|
scope="bank_background",
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
max_tokens=8192
|
max_tokens=8192
|
||||||
)
|
)
|
||||||
|
|
@ -301,7 +300,7 @@ Merged background:"""
|
||||||
# Manual parsing fallback or non-personality merge
|
# Manual parsing fallback or non-personality merge
|
||||||
content = await llm_config.call(
|
content = await llm_config.call(
|
||||||
messages=messages,
|
messages=messages,
|
||||||
scope="agent_background",
|
scope="bank_background",
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
max_tokens=8192
|
max_tokens=8192
|
||||||
)
|
)
|
||||||
|
|
@ -388,21 +387,21 @@ Merged background:"""
|
||||||
return result
|
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:
|
Args:
|
||||||
pool: Database connection pool
|
pool: Database connection pool
|
||||||
|
|
||||||
Returns:
|
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:
|
async with acquire_with_retry(pool) as conn:
|
||||||
rows = await conn.fetch(
|
rows = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT agent_id, name, personality, background, created_at, updated_at
|
SELECT bank_id, name, personality, background, created_at, updated_at
|
||||||
FROM agents
|
FROM banks
|
||||||
ORDER BY updated_at DESC
|
ORDER BY updated_at DESC
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
@ -415,7 +414,7 @@ async def list_agents(pool) -> list:
|
||||||
personality_data = json.loads(personality_data)
|
personality_data = json.loads(personality_data)
|
||||||
|
|
||||||
result.append({
|
result.append({
|
||||||
"agent_id": row["agent_id"],
|
"bank_id": row["bank_id"],
|
||||||
"name": row["name"],
|
"name": row["name"],
|
||||||
"personality": personality_data,
|
"personality": personality_data,
|
||||||
"background": row["background"],
|
"background": row["background"],
|
||||||
|
|
@ -31,7 +31,7 @@ class EntityResolver:
|
||||||
|
|
||||||
async def resolve_entities_batch(
|
async def resolve_entities_batch(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
entities_data: List[Dict],
|
entities_data: List[Dict],
|
||||||
context: str,
|
context: str,
|
||||||
unit_event_date,
|
unit_event_date,
|
||||||
|
|
@ -44,7 +44,7 @@ class EntityResolver:
|
||||||
all entities with minimal DB queries.
|
all entities with minimal DB queries.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: Agent ID
|
bank_id: bank ID
|
||||||
entities_data: List of dicts with 'text', 'type', 'nearby_entities'
|
entities_data: List of dicts with 'text', 'type', 'nearby_entities'
|
||||||
context: Context where entities appear
|
context: Context where entities appear
|
||||||
unit_event_date: When this unit was created
|
unit_event_date: When this unit was created
|
||||||
|
|
@ -58,34 +58,34 @@ class EntityResolver:
|
||||||
|
|
||||||
if conn is None:
|
if conn is None:
|
||||||
async with acquire_with_retry(self.pool) as conn:
|
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:
|
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]:
|
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 agent
|
# Query ALL candidates for this bank
|
||||||
all_entities = await conn.fetch(
|
all_entities = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT canonical_name, id, metadata, last_seen, mention_count
|
SELECT canonical_name, id, metadata, last_seen, mention_count
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build entity ID to name mapping for co-occurrence lookups
|
# 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}
|
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
|
# This builds a map of entity_id -> set of co-occurring entity names
|
||||||
all_cooccurrences = await conn.fetch(
|
all_cooccurrences = await conn.fetch(
|
||||||
"""
|
"""
|
||||||
SELECT ec.entity_id_1, ec.entity_id_2, ec.cooccurrence_count
|
SELECT ec.entity_id_1, ec.entity_id_2, ec.cooccurrence_count
|
||||||
FROM entity_cooccurrences ec
|
FROM entity_cooccurrences ec
|
||||||
WHERE ec.entity_id_1 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 agent_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)
|
# Build co-occurrence map: entity_id -> set of co-occurring entity names (lowercase)
|
||||||
|
|
@ -205,18 +205,18 @@ class EntityResolver:
|
||||||
if entities_to_create:
|
if entities_to_create:
|
||||||
for idx, entity_data in entities_to_create:
|
for idx, entity_data in entities_to_create:
|
||||||
# Use INSERT ... ON CONFLICT to atomically get-or-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(
|
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)
|
VALUES ($1, $2, $3, $4, 1)
|
||||||
ON CONFLICT (agent_id, LOWER(canonical_name))
|
ON CONFLICT (bank_id, LOWER(canonical_name))
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
mention_count = entities.mention_count + 1,
|
mention_count = entities.mention_count + 1,
|
||||||
last_seen = EXCLUDED.last_seen
|
last_seen = EXCLUDED.last_seen
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
entity_data['text'],
|
entity_data['text'],
|
||||||
unit_event_date,
|
unit_event_date,
|
||||||
unit_event_date
|
unit_event_date
|
||||||
|
|
@ -227,7 +227,7 @@ class EntityResolver:
|
||||||
|
|
||||||
async def resolve_entity(
|
async def resolve_entity(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
entity_text: str,
|
entity_text: str,
|
||||||
context: str,
|
context: str,
|
||||||
nearby_entities: List[Dict],
|
nearby_entities: List[Dict],
|
||||||
|
|
@ -237,7 +237,7 @@ class EntityResolver:
|
||||||
Resolve an entity to a canonical entity ID.
|
Resolve an entity to a canonical entity ID.
|
||||||
|
|
||||||
Args:
|
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.)
|
entity_text: Entity text ("Alice", "Google", etc.)
|
||||||
context: Context where entity appears
|
context: Context where entity appears
|
||||||
nearby_entities: Other entities in the same unit
|
nearby_entities: Other entities in the same unit
|
||||||
|
|
@ -252,7 +252,7 @@ class EntityResolver:
|
||||||
"""
|
"""
|
||||||
SELECT id, canonical_name, metadata, last_seen
|
SELECT id, canonical_name, metadata, last_seen
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
AND (
|
AND (
|
||||||
canonical_name ILIKE $2
|
canonical_name ILIKE $2
|
||||||
OR canonical_name ILIKE $3
|
OR canonical_name ILIKE $3
|
||||||
|
|
@ -260,13 +260,13 @@ class EntityResolver:
|
||||||
)
|
)
|
||||||
ORDER BY mention_count DESC
|
ORDER BY mention_count DESC
|
||||||
""",
|
""",
|
||||||
agent_id, entity_text, f"%{entity_text}%"
|
bank_id, entity_text, f"%{entity_text}%"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not candidates:
|
if not candidates:
|
||||||
# New entity - create it
|
# New entity - create it
|
||||||
return await self._create_entity(
|
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:
|
# Score candidates based on:
|
||||||
|
|
@ -351,13 +351,13 @@ class EntityResolver:
|
||||||
else:
|
else:
|
||||||
# Not confident - create new entity
|
# Not confident - create new entity
|
||||||
return await self._create_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(
|
async def _create_entity(
|
||||||
self,
|
self,
|
||||||
conn,
|
conn,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
entity_text: str,
|
entity_text: str,
|
||||||
event_date,
|
event_date,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
@ -369,7 +369,7 @@ class EntityResolver:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
agent_id: Agent ID
|
bank_id: bank ID
|
||||||
entity_text: Entity text
|
entity_text: Entity text
|
||||||
event_date: When first seen
|
event_date: When first seen
|
||||||
|
|
||||||
|
|
@ -378,15 +378,15 @@ class EntityResolver:
|
||||||
"""
|
"""
|
||||||
entity_id = await conn.fetchval(
|
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)
|
VALUES ($1, $2, $3, $4, 1)
|
||||||
ON CONFLICT (agent_id, LOWER(canonical_name))
|
ON CONFLICT (bank_id, LOWER(canonical_name))
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
mention_count = entities.mention_count + 1,
|
mention_count = entities.mention_count + 1,
|
||||||
last_seen = EXCLUDED.last_seen
|
last_seen = EXCLUDED.last_seen
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""",
|
""",
|
||||||
agent_id, entity_text, event_date, event_date
|
bank_id, entity_text, event_date, event_date
|
||||||
)
|
)
|
||||||
return entity_id
|
return entity_id
|
||||||
|
|
||||||
|
|
@ -547,14 +547,14 @@ class EntityResolver:
|
||||||
|
|
||||||
async def get_entity_by_text(
|
async def get_entity_by_text(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
entity_text: str,
|
entity_text: str,
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Find an entity by text (for query resolution).
|
Find an entity by text (for query resolution).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: Agent ID
|
bank_id: bank ID
|
||||||
entity_text: Entity text to search for
|
entity_text: Entity text to search for
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -564,12 +564,12 @@ class EntityResolver:
|
||||||
row = await conn.fetchrow(
|
row = await conn.fetchrow(
|
||||||
"""
|
"""
|
||||||
SELECT id FROM entities
|
SELECT id FROM entities
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
AND canonical_name ILIKE $2
|
AND canonical_name ILIKE $2
|
||||||
ORDER BY mention_count DESC
|
ORDER BY mention_count DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
agent_id, entity_text
|
bank_id, entity_text
|
||||||
)
|
)
|
||||||
|
|
||||||
return row['id'] if row else None
|
return row['id'] if row else None
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ class ExtractedFact(BaseModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Classification
|
# Classification
|
||||||
fact_type: Literal["world", "agent", "opinion"] = Field(
|
fact_type: Literal["world", "bank", "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)"
|
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
|
# Entities and relations
|
||||||
|
|
@ -199,9 +199,9 @@ async def _extract_facts_from_chunk(
|
||||||
|
|
||||||
# Determine which fact types to extract based on the flag
|
# Determine which fact types to extract based on the flag
|
||||||
if extract_opinions:
|
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:
|
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.
|
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(
|
async def extract_entities_batch_optimized(
|
||||||
entity_resolver,
|
entity_resolver,
|
||||||
conn,
|
conn,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
unit_ids: List[str],
|
unit_ids: List[str],
|
||||||
sentences: List[str],
|
sentences: List[str],
|
||||||
context: str,
|
context: str,
|
||||||
|
|
@ -41,7 +41,7 @@ async def extract_entities_batch_optimized(
|
||||||
Args:
|
Args:
|
||||||
entity_resolver: EntityResolver instance for entity resolution
|
entity_resolver: EntityResolver instance for entity resolution
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
agent_id: Agent identifier
|
agent_id: bank IDentifier
|
||||||
unit_ids: List of unit IDs
|
unit_ids: List of unit IDs
|
||||||
sentences: List of fact sentences
|
sentences: List of fact sentences
|
||||||
context: Context string
|
context: Context string
|
||||||
|
|
@ -114,7 +114,7 @@ async def extract_entities_batch_optimized(
|
||||||
entities_data = [entity_data for _, entity_data in entities_group]
|
entities_data = [entity_data for _, entity_data in entities_group]
|
||||||
|
|
||||||
batch_resolved = await entity_resolver.resolve_entities_batch(
|
batch_resolved = await entity_resolver.resolve_entities_batch(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
entities_data=entities_data,
|
entities_data=entities_data,
|
||||||
context=context,
|
context=context,
|
||||||
unit_event_date=fact_date,
|
unit_event_date=fact_date,
|
||||||
|
|
@ -209,7 +209,7 @@ async def extract_entities_batch_optimized(
|
||||||
|
|
||||||
async def create_temporal_links_batch_per_fact(
|
async def create_temporal_links_batch_per_fact(
|
||||||
conn,
|
conn,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
unit_ids: List[str],
|
unit_ids: List[str],
|
||||||
time_window_hours: int = 24,
|
time_window_hours: int = 24,
|
||||||
log_buffer: List[str] = None,
|
log_buffer: List[str] = None,
|
||||||
|
|
@ -222,7 +222,7 @@ async def create_temporal_links_batch_per_fact(
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
agent_id: Agent identifier
|
agent_id: bank IDentifier
|
||||||
unit_ids: List of unit IDs
|
unit_ids: List of unit IDs
|
||||||
time_window_hours: Time window in hours for temporal links
|
time_window_hours: Time window in hours for temporal links
|
||||||
log_buffer: Optional buffer for logging
|
log_buffer: Optional buffer for logging
|
||||||
|
|
@ -257,12 +257,12 @@ async def create_temporal_links_batch_per_fact(
|
||||||
"""
|
"""
|
||||||
SELECT id, event_date
|
SELECT id, event_date
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
AND event_date BETWEEN $2 AND $3
|
AND event_date BETWEEN $2 AND $3
|
||||||
AND id::text != ALL($4)
|
AND id::text != ALL($4)
|
||||||
ORDER BY event_date DESC
|
ORDER BY event_date DESC
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
min_date,
|
min_date,
|
||||||
max_date,
|
max_date,
|
||||||
unit_ids
|
unit_ids
|
||||||
|
|
@ -312,7 +312,7 @@ async def create_temporal_links_batch_per_fact(
|
||||||
|
|
||||||
async def create_semantic_links_batch(
|
async def create_semantic_links_batch(
|
||||||
conn,
|
conn,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
unit_ids: List[str],
|
unit_ids: List[str],
|
||||||
embeddings: List[List[float]],
|
embeddings: List[List[float]],
|
||||||
top_k: int = 5,
|
top_k: int = 5,
|
||||||
|
|
@ -326,7 +326,7 @@ async def create_semantic_links_batch(
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
agent_id: Agent identifier
|
agent_id: bank IDentifier
|
||||||
unit_ids: List of unit IDs
|
unit_ids: List of unit IDs
|
||||||
embeddings: List of embedding vectors
|
embeddings: List of embedding vectors
|
||||||
top_k: Number of top similar units to link
|
top_k: Number of top similar units to link
|
||||||
|
|
@ -346,11 +346,11 @@ async def create_semantic_links_batch(
|
||||||
"""
|
"""
|
||||||
SELECT id, embedding
|
SELECT id, embedding
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
AND embedding IS NOT NULL
|
AND embedding IS NOT NULL
|
||||||
AND id::text != ALL($2)
|
AND id::text != ALL($2)
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
unit_ids
|
unit_ids
|
||||||
)
|
)
|
||||||
_log(log_buffer, f" [8.1] Fetch {len(all_existing)} existing embeddings (1 query): {time_mod.time() - fetch_start:.3f}s")
|
_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")
|
id: str = Field(description="Unique identifier for the memory fact")
|
||||||
text: str = Field(description="The actual text content of the memory")
|
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")
|
entities: Optional[List[str]] = Field(None, description="Entity names mentioned in this fact")
|
||||||
context: Optional[str] = Field(None, description="Additional context for the memory")
|
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")
|
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")
|
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
|
Contains a list of matching memory facts and optional trace information
|
||||||
for debugging and transparency.
|
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),
|
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={
|
model_config = ConfigDict(json_schema_extra={
|
||||||
"example": {
|
"example": {
|
||||||
|
|
@ -119,7 +119,7 @@ class ThinkResult(BaseModel):
|
||||||
)
|
)
|
||||||
new_opinions: List[str] = Field(
|
new_opinions: List[str] = Field(
|
||||||
default_factory=list,
|
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.
|
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.
|
with a confidence level indicating strength of belief.
|
||||||
"""
|
"""
|
||||||
model_config = ConfigDict(json_schema_extra={
|
model_config = ConfigDict(json_schema_extra={
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from ..db_utils import acquire_with_retry
|
||||||
async def retrieve_semantic(
|
async def retrieve_semantic(
|
||||||
conn,
|
conn,
|
||||||
query_emb_str: str,
|
query_emb_str: str,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: str,
|
fact_type: str,
|
||||||
limit: int
|
limit: int
|
||||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
|
|
@ -27,7 +27,7 @@ async def retrieve_semantic(
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
query_emb_str: Query embedding as string
|
query_emb_str: Query embedding as string
|
||||||
agent_id: Agent ID
|
agent_id: bank ID
|
||||||
fact_type: Fact type to filter
|
fact_type: Fact type to filter
|
||||||
limit: Maximum results to return
|
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,
|
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
|
1 - (embedding <=> $1::vector) AS similarity
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $2
|
WHERE bank_id = $2
|
||||||
AND embedding IS NOT NULL
|
AND embedding IS NOT NULL
|
||||||
AND fact_type = $3
|
AND fact_type = $3
|
||||||
AND (1 - (embedding <=> $1::vector)) >= 0.3
|
AND (1 - (embedding <=> $1::vector)) >= 0.3
|
||||||
ORDER BY embedding <=> $1::vector
|
ORDER BY embedding <=> $1::vector
|
||||||
LIMIT $4
|
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]
|
return [(str(r["id"]), dict(r)) for r in results]
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ async def retrieve_semantic(
|
||||||
async def retrieve_bm25(
|
async def retrieve_bm25(
|
||||||
conn,
|
conn,
|
||||||
query_text: str,
|
query_text: str,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: str,
|
fact_type: str,
|
||||||
limit: int
|
limit: int
|
||||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
|
|
@ -64,7 +64,7 @@ async def retrieve_bm25(
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
query_text: Query text
|
query_text: Query text
|
||||||
agent_id: Agent ID
|
agent_id: bank ID
|
||||||
fact_type: Fact type to filter
|
fact_type: Fact type to filter
|
||||||
limit: Maximum results to return
|
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,
|
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
|
ts_rank_cd(search_vector, to_tsquery('english', $1)) AS bm25_score
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $2
|
WHERE bank_id = $2
|
||||||
AND fact_type = $3
|
AND fact_type = $3
|
||||||
AND search_vector @@ to_tsquery('english', $1)
|
AND search_vector @@ to_tsquery('english', $1)
|
||||||
ORDER BY bm25_score DESC
|
ORDER BY bm25_score DESC
|
||||||
LIMIT $4
|
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]
|
return [(str(r["id"]), dict(r)) for r in results]
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ async def retrieve_bm25(
|
||||||
async def retrieve_graph(
|
async def retrieve_graph(
|
||||||
conn,
|
conn,
|
||||||
query_emb_str: str,
|
query_emb_str: str,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: str,
|
fact_type: str,
|
||||||
budget: int
|
budget: int
|
||||||
) -> List[Tuple[str, Dict[str, Any]]]:
|
) -> List[Tuple[str, Dict[str, Any]]]:
|
||||||
|
|
@ -117,7 +117,7 @@ async def retrieve_graph(
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
query_emb_str: Query embedding as string
|
query_emb_str: Query embedding as string
|
||||||
agent_id: Agent ID
|
agent_id: bank ID
|
||||||
fact_type: Fact type to filter
|
fact_type: Fact type to filter
|
||||||
budget: Node budget for graph traversal
|
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,
|
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
|
1 - (embedding <=> $1::vector) AS similarity
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $2
|
WHERE bank_id = $2
|
||||||
AND embedding IS NOT NULL
|
AND embedding IS NOT NULL
|
||||||
AND fact_type = $3
|
AND fact_type = $3
|
||||||
AND (1 - (embedding <=> $1::vector)) >= 0.5
|
AND (1 - (embedding <=> $1::vector)) >= 0.5
|
||||||
ORDER BY embedding <=> $1::vector
|
ORDER BY embedding <=> $1::vector
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
""",
|
""",
|
||||||
query_emb_str, agent_id, fact_type
|
query_emb_str, bank_id, fact_type
|
||||||
)
|
)
|
||||||
|
|
||||||
if not entry_points:
|
if not entry_points:
|
||||||
|
|
@ -221,7 +221,7 @@ async def retrieve_graph(
|
||||||
async def retrieve_temporal(
|
async def retrieve_temporal(
|
||||||
conn,
|
conn,
|
||||||
query_emb_str: str,
|
query_emb_str: str,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: str,
|
fact_type: str,
|
||||||
start_date: datetime,
|
start_date: datetime,
|
||||||
end_date: datetime,
|
end_date: datetime,
|
||||||
|
|
@ -239,7 +239,7 @@ async def retrieve_temporal(
|
||||||
Args:
|
Args:
|
||||||
conn: Database connection
|
conn: Database connection
|
||||||
query_emb_str: Query embedding as string
|
query_emb_str: Query embedding as string
|
||||||
agent_id: Agent ID
|
agent_id: bank ID
|
||||||
fact_type: Fact type to filter
|
fact_type: Fact type to filter
|
||||||
start_date: Start of time range
|
start_date: Start of time range
|
||||||
end_date: End 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,
|
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
|
1 - (embedding <=> $1::vector) AS similarity
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $2
|
WHERE bank_id = $2
|
||||||
AND fact_type = $3
|
AND fact_type = $3
|
||||||
AND embedding IS NOT NULL
|
AND embedding IS NOT NULL
|
||||||
AND (
|
AND (
|
||||||
|
|
@ -282,16 +282,16 @@ async def retrieve_temporal(
|
||||||
ORDER BY COALESCE(occurred_start, mentioned_at, occurred_end) DESC, (embedding <=> $1::vector) ASC
|
ORDER BY COALESCE(occurred_start, mentioned_at, occurred_end) DESC, (embedding <=> $1::vector) ASC
|
||||||
LIMIT 10
|
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:
|
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(
|
total_with_dates = await conn.fetchval(
|
||||||
"""SELECT COUNT(*) FROM memory_units
|
"""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)""",
|
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 []
|
return []
|
||||||
|
|
||||||
|
|
@ -418,7 +418,7 @@ async def retrieve_parallel(
|
||||||
pool,
|
pool,
|
||||||
query_text: str,
|
query_text: str,
|
||||||
query_embedding_str: str,
|
query_embedding_str: str,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: str,
|
fact_type: str,
|
||||||
thinking_budget: int,
|
thinking_budget: int,
|
||||||
question_date: Optional[datetime] = None,
|
question_date: Optional[datetime] = None,
|
||||||
|
|
@ -431,7 +431,7 @@ async def retrieve_parallel(
|
||||||
pool: Database connection pool
|
pool: Database connection pool
|
||||||
query_text: Query text
|
query_text: Query text
|
||||||
query_embedding_str: Query embedding as string
|
query_embedding_str: Query embedding as string
|
||||||
agent_id: Agent ID
|
agent_id: bank ID
|
||||||
fact_type: Fact type to filter
|
fact_type: Fact type to filter
|
||||||
thinking_budget: Budget for graph traversal and retrieval limits
|
thinking_budget: Budget for graph traversal and retrieval limits
|
||||||
question_date: Optional date when question was asked (for temporal filtering)
|
question_date: Optional date when question was asked (for temporal filtering)
|
||||||
|
|
@ -461,20 +461,20 @@ async def retrieve_parallel(
|
||||||
|
|
||||||
async def run_semantic():
|
async def run_semantic():
|
||||||
async with acquire_with_retry(pool) as conn:
|
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 def run_bm25():
|
||||||
async with acquire_with_retry(pool) as conn:
|
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 def run_graph():
|
||||||
async with acquire_with_retry(pool) as conn:
|
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 def run_temporal(start_date, end_date):
|
||||||
async with acquire_with_retry(pool) as conn:
|
async with acquire_with_retry(pool) as conn:
|
||||||
return await retrieve_temporal(
|
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
|
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_text: str = Field(description="Original query text")
|
||||||
query_embedding: List[float] = Field(description="Generated query embedding vector")
|
query_embedding: List[float] = Field(description="Generated query embedding vector")
|
||||||
timestamp: datetime = Field(description="When the query was executed")
|
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")
|
max_tokens: int = Field(description="Maximum tokens to return in results")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class SearchTracer:
|
||||||
Tracer for collecting detailed search execution information.
|
Tracer for collecting detailed search execution information.
|
||||||
|
|
||||||
Usage:
|
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()
|
tracer.start()
|
||||||
|
|
||||||
# During search...
|
# During search...
|
||||||
|
|
@ -44,17 +44,17 @@ class SearchTracer:
|
||||||
json_output = trace.to_json()
|
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.
|
Initialize tracer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Search query text
|
query: Search query text
|
||||||
thinking_budget: Maximum nodes to explore
|
budget: Maximum nodes to explore
|
||||||
max_tokens: Maximum tokens to return in results
|
max_tokens: Maximum tokens to return in results
|
||||||
"""
|
"""
|
||||||
self.query_text = query
|
self.query_text = query
|
||||||
self.thinking_budget = thinking_budget
|
self.budget = budget
|
||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
|
|
||||||
# Trace data
|
# Trace data
|
||||||
|
|
@ -400,7 +400,7 @@ class SearchTracer:
|
||||||
query_text=self.query_text,
|
query_text=self.query_text,
|
||||||
query_embedding=self.query_embedding or [],
|
query_embedding=self.query_embedding or [],
|
||||||
timestamp=datetime.now(timezone.utc),
|
timestamp=datetime.now(timezone.utc),
|
||||||
thinking_budget=self.thinking_budget,
|
budget=self.budget,
|
||||||
max_tokens=self.max_tokens,
|
max_tokens=self.max_tokens,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -410,7 +410,7 @@ class SearchTracer:
|
||||||
total_nodes_pruned=len(self.pruned),
|
total_nodes_pruned=len(self.pruned),
|
||||||
entry_points_found=len(self.entry_points),
|
entry_points_found=len(self.entry_points),
|
||||||
budget_used=len(self.visits),
|
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,
|
total_duration_seconds=total_duration,
|
||||||
results_returned=len(final_results),
|
results_returned=len(final_results),
|
||||||
temporal_links_followed=self.temporal_links_followed,
|
temporal_links_followed=self.temporal_links_followed,
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ from datetime import datetime, timezone
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from .response_models import ThinkResult, MemoryFact
|
from .response_models import ReflectResult, MemoryFact
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Opinion(BaseModel):
|
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")
|
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)")
|
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"
|
__tablename__ = "documents"
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(Text, primary_key=True)
|
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)
|
original_text: Mapped[Optional[str]] = mapped_column(Text)
|
||||||
content_hash: 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"))
|
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")
|
memory_units = relationship("MemoryUnit", back_populates="document", cascade="all, delete-orphan")
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
Index("idx_documents_agent_id", "agent_id"),
|
Index("idx_documents_bank_id", "bank_id"),
|
||||||
Index("idx_documents_content_hash", "content_hash"),
|
Index("idx_documents_content_hash", "content_hash"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ class MemoryUnit(Base):
|
||||||
id: Mapped[PyUUID] = mapped_column(
|
id: Mapped[PyUUID] = mapped_column(
|
||||||
UUID(as_uuid=True), primary_key=True, server_default=sql_text("uuid_generate_v4()")
|
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)
|
document_id: Mapped[Optional[str]] = mapped_column(Text)
|
||||||
text: Mapped[str] = mapped_column(Text, nullable=False)
|
text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||||
embedding = mapped_column(Vector(384)) # pgvector type
|
embedding = mapped_column(Vector(384)) # pgvector type
|
||||||
|
|
@ -99,12 +99,12 @@ class MemoryUnit(Base):
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
ForeignKeyConstraint(
|
ForeignKeyConstraint(
|
||||||
["document_id", "agent_id"],
|
["document_id", "bank_id"],
|
||||||
["documents.id", "documents.agent_id"],
|
["documents.id", "documents.bank_id"],
|
||||||
name="memory_units_document_fkey",
|
name="memory_units_document_fkey",
|
||||||
ondelete="CASCADE",
|
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("confidence_score IS NULL OR (confidence_score >= 0.0 AND confidence_score <= 1.0)"),
|
||||||
CheckConstraint(
|
CheckConstraint(
|
||||||
"(fact_type = 'opinion' AND confidence_score IS NOT NULL) OR "
|
"(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)",
|
"(fact_type NOT IN ('opinion', 'observation') AND confidence_score IS NULL)",
|
||||||
name="confidence_score_fact_type_check"
|
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_document_id", "document_id"),
|
||||||
Index("idx_memory_units_event_date", "event_date", postgresql_ops={"event_date": "DESC"}),
|
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_access_count", "access_count", postgresql_ops={"access_count": "DESC"}),
|
||||||
Index("idx_memory_units_fact_type", "fact_type"),
|
Index("idx_memory_units_fact_type", "fact_type"),
|
||||||
Index("idx_memory_units_agent_fact_type", "agent_id", "fact_type"),
|
Index("idx_memory_units_bank_fact_type", "bank_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_type_date", "bank_id", "fact_type", "event_date", postgresql_ops={"event_date": "DESC"}),
|
||||||
Index(
|
Index(
|
||||||
"idx_memory_units_opinion_confidence",
|
"idx_memory_units_opinion_confidence",
|
||||||
"agent_id",
|
"bank_id",
|
||||||
"confidence_score",
|
"confidence_score",
|
||||||
postgresql_where=sql_text("fact_type = 'opinion'"),
|
postgresql_where=sql_text("fact_type = 'opinion'"),
|
||||||
postgresql_ops={"confidence_score": "DESC"}
|
postgresql_ops={"confidence_score": "DESC"}
|
||||||
),
|
),
|
||||||
Index(
|
Index(
|
||||||
"idx_memory_units_opinion_date",
|
"idx_memory_units_opinion_date",
|
||||||
"agent_id",
|
"bank_id",
|
||||||
"event_date",
|
"event_date",
|
||||||
postgresql_where=sql_text("fact_type = 'opinion'"),
|
postgresql_where=sql_text("fact_type = 'opinion'"),
|
||||||
postgresql_ops={"event_date": "DESC"}
|
postgresql_ops={"event_date": "DESC"}
|
||||||
),
|
),
|
||||||
Index(
|
Index(
|
||||||
"idx_memory_units_observation_date",
|
"idx_memory_units_observation_date",
|
||||||
"agent_id",
|
"bank_id",
|
||||||
"event_date",
|
"event_date",
|
||||||
postgresql_where=sql_text("fact_type = 'observation'"),
|
postgresql_where=sql_text("fact_type = 'observation'"),
|
||||||
postgresql_ops={"event_date": "DESC"}
|
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()")
|
UUID(as_uuid=True), primary_key=True, server_default=sql_text("uuid_generate_v4()")
|
||||||
)
|
)
|
||||||
canonical_name: Mapped[str] = mapped_column(Text, nullable=False)
|
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"))
|
entity_metadata: Mapped[dict] = mapped_column("metadata", JSONB, server_default=sql_text("'{}'::jsonb"))
|
||||||
first_seen: Mapped[datetime] = mapped_column(
|
first_seen: Mapped[datetime] = mapped_column(
|
||||||
TIMESTAMP(timezone=True), server_default=func.now()
|
TIMESTAMP(timezone=True), server_default=func.now()
|
||||||
|
|
@ -185,9 +185,9 @@ class Entity(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
__table_args__ = (
|
__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_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")
|
entity = relationship("Entity", back_populates="memory_links")
|
||||||
|
|
||||||
__table_args__ = (
|
__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_from", "from_unit_id"),
|
||||||
Index("idx_memory_links_to", "to_unit_id"),
|
Index("idx_memory_links_to", "to_unit_id"),
|
||||||
Index("idx_memory_links_type", "link_type"),
|
Index("idx_memory_links_type", "link_type"),
|
||||||
|
|
@ -278,11 +283,11 @@ class MemoryLink(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Agent(Base):
|
class Bank(Base):
|
||||||
"""Agent profiles with personality traits and background."""
|
"""Memory bank profiles with personality traits and background."""
|
||||||
__tablename__ = "agents"
|
__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(
|
personality: Mapped[dict] = mapped_column(
|
||||||
JSONB,
|
JSONB,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
@ -300,5 +305,5 @@ class Agent(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
__table_args__ = (
|
__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,
|
"log_level": args.log_level,
|
||||||
"access_log": args.access_log,
|
"access_log": args.access_log,
|
||||||
"proxy_headers": args.proxy_headers,
|
"proxy_headers": args.proxy_headers,
|
||||||
|
"ws": "wsproto", # Use wsproto instead of websockets to avoid deprecation warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add optional parameters if provided
|
# Add optional parameters if provided
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ dependencies = [
|
||||||
"langchain-text-splitters>=0.3.0",
|
"langchain-text-splitters>=0.3.0",
|
||||||
"fastapi[standard]>=0.120.3",
|
"fastapi[standard]>=0.120.3",
|
||||||
"uvicorn>=0.38.0",
|
"uvicorn>=0.38.0",
|
||||||
|
"wsproto>=1.0.0",
|
||||||
"sqlalchemy>=2.0.44",
|
"sqlalchemy>=2.0.44",
|
||||||
"alembic>=1.17.1",
|
"alembic>=1.17.1",
|
||||||
"pgvector>=0.4.1",
|
"pgvector>=0.4.1",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ Tests for agent management API (profile, personality, background).
|
||||||
import pytest
|
import pytest
|
||||||
import uuid
|
import uuid
|
||||||
from hindsight_api import MemoryEngine
|
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:
|
def unique_agent_id(prefix: str) -> str:
|
||||||
|
|
@ -18,9 +19,9 @@ class TestAgentProfile:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_agent_profile_creates_default(self, memory: MemoryEngine):
|
async def test_get_agent_profile_creates_default(self, memory: MemoryEngine):
|
||||||
"""Test that getting a profile for a new agent creates default personality."""
|
"""Test that getting a profile for a new agent creates default 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 profile is not None
|
||||||
assert "personality" in profile
|
assert "personality" in profile
|
||||||
|
|
@ -39,9 +40,9 @@ class TestAgentProfile:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_update_agent_personality(self, memory: MemoryEngine):
|
async def test_update_agent_personality(self, memory: MemoryEngine):
|
||||||
"""Test updating agent personality traits."""
|
"""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
|
assert profile["personality"]["openness"] == 0.5
|
||||||
|
|
||||||
new_personality = {
|
new_personality = {
|
||||||
|
|
@ -52,9 +53,9 @@ class TestAgentProfile:
|
||||||
"neuroticism": 0.3,
|
"neuroticism": 0.3,
|
||||||
"bias_strength": 0.9,
|
"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:
|
for key in new_personality:
|
||||||
assert abs(updated_profile["personality"][key] - new_personality[key]) < 0.001
|
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_2 = unique_agent_id("test_list")
|
||||||
agent_id_3 = unique_agent_id("test_list")
|
agent_id_3 = unique_agent_id("test_list")
|
||||||
|
|
||||||
await memory.get_agent_profile(agent_id_1)
|
await memory.get_bank_profile(agent_id_1)
|
||||||
await memory.get_agent_profile(agent_id_2)
|
await memory.get_bank_profile(agent_id_2)
|
||||||
await memory.get_agent_profile(agent_id_3)
|
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_1 in agent_ids
|
||||||
assert agent_id_2 in agent_ids
|
assert agent_id_2 in agent_ids
|
||||||
assert agent_id_3 in agent_ids
|
assert agent_id_3 in agent_ids
|
||||||
|
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
assert "agent_id" in agent
|
assert "bank_id" in agent
|
||||||
assert "personality" in agent
|
assert "personality" in agent
|
||||||
assert "background" in agent
|
assert "background" in agent
|
||||||
assert "created_at" in agent
|
assert "created_at" in agent
|
||||||
|
|
@ -90,42 +91,42 @@ class TestAgentBackground:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_merge_agent_background(self, memory: MemoryEngine):
|
async def test_merge_agent_background(self, memory: MemoryEngine):
|
||||||
"""Test merging agent background information."""
|
"""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"] == ""
|
assert profile["background"] == ""
|
||||||
|
|
||||||
result1 = await memory.merge_agent_background(
|
result1 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I was born in Texas",
|
"I was born in Texas",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
assert "Texas" in result1["background"]
|
assert "Texas" in result1["background"]
|
||||||
|
|
||||||
result2 = await memory.merge_agent_background(
|
result2 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I have 10 years of startup experience",
|
"I have 10 years of startup experience",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
assert "Texas" in result2["background"] or "startup" in result2["background"]
|
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"] != ""
|
assert final_profile["background"] != ""
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_merge_background_handles_conflicts(self, memory: MemoryEngine):
|
async def test_merge_background_handles_conflicts(self, memory: MemoryEngine):
|
||||||
"""Test that merging background handles conflicts (new overwrites old)."""
|
"""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(
|
result1 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I was born in Colorado",
|
"I was born in Colorado",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
assert "Colorado" in result1["background"]
|
assert "Colorado" in result1["background"]
|
||||||
|
|
||||||
result2 = await memory.merge_agent_background(
|
result2 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"You were born in Texas",
|
"You were born in Texas",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
|
|
@ -138,9 +139,9 @@ class TestAgentEndpoint:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_put_agent_create(self, memory: MemoryEngine):
|
async def test_put_agent_create(self, memory: MemoryEngine):
|
||||||
"""Test creating an agent via PUT endpoint."""
|
"""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(
|
personality=PersonalityTraits(
|
||||||
openness=0.8,
|
openness=0.8,
|
||||||
conscientiousness=0.6,
|
conscientiousness=0.6,
|
||||||
|
|
@ -152,11 +153,11 @@ class TestAgentEndpoint:
|
||||||
background="I am a creative software engineer"
|
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:
|
if request.personality is not None:
|
||||||
await memory.update_agent_personality(
|
await memory.update_bank_personality(
|
||||||
agent_id,
|
bank_id,
|
||||||
request.personality.model_dump()
|
request.personality.model_dump()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -165,16 +166,16 @@ class TestAgentEndpoint:
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE agents
|
UPDATE banks
|
||||||
SET background = $2,
|
SET background = $2,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
request.background
|
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"]["openness"] == 0.8
|
||||||
assert final_profile["personality"]["bias_strength"] == 0.7
|
assert final_profile["personality"]["bias_strength"] == 0.7
|
||||||
|
|
@ -183,29 +184,29 @@ class TestAgentEndpoint:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_put_agent_partial_update(self, memory: MemoryEngine):
|
async def test_put_agent_partial_update(self, memory: MemoryEngine):
|
||||||
"""Test updating only background."""
|
"""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"
|
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:
|
if request.background is not None:
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE agents
|
UPDATE banks
|
||||||
SET background = $2,
|
SET background = $2,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
""",
|
""",
|
||||||
agent_id,
|
bank_id,
|
||||||
request.background
|
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["personality"]["openness"] == 0.5
|
||||||
assert final_profile["background"] == "I am a data scientist"
|
assert final_profile["background"] == "I am a data scientist"
|
||||||
|
|
@ -217,7 +218,7 @@ class TestAgentPersonalityIntegration:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_think_uses_personality(self, memory: MemoryEngine):
|
async def test_think_uses_personality(self, memory: MemoryEngine):
|
||||||
"""Test that THINK operation uses agent personality."""
|
"""Test that THINK operation uses agent personality."""
|
||||||
agent_id = unique_agent_id("test_think")
|
bank_id = unique_agent_id("test_think")
|
||||||
|
|
||||||
personality = {
|
personality = {
|
||||||
"openness": 0.9,
|
"openness": 0.9,
|
||||||
|
|
@ -227,16 +228,16 @@ class TestAgentPersonalityIntegration:
|
||||||
"neuroticism": 0.7,
|
"neuroticism": 0.7,
|
||||||
"bias_strength": 0.9,
|
"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(
|
await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a creative artist who values innovation over tradition",
|
"I am a creative artist who values innovation over tradition",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
|
|
||||||
await memory.put_batch_async(
|
await memory.retain_batch_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
contents=[
|
contents=[
|
||||||
{"content": "Traditional painting techniques have been used for centuries"},
|
{"content": "Traditional painting techniques have been used for centuries"},
|
||||||
{"content": "Modern digital art is changing the art world"}
|
{"content": "Modern digital art is changing the art world"}
|
||||||
|
|
@ -244,10 +245,10 @@ class TestAgentPersonalityIntegration:
|
||||||
document_id="art_facts"
|
document_id="art_facts"
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await memory.think_async(
|
result = await memory.reflect_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="What do you think about traditional vs modern art?",
|
query="What do you think about traditional vs modern art?",
|
||||||
thinking_budget=50
|
budget=Budget.LOW
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.text is not None
|
assert result.text is not None
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import os
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_large_batch_auto_chunks(memory):
|
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
|
# Create a large batch that should trigger chunking
|
||||||
# Each item is ~2000 chars, so 30 items = 60k chars (exceeds 50k threshold)
|
# 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
|
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}")
|
print(f"Should trigger chunking: {total_chars > 50_000}")
|
||||||
|
|
||||||
# Ingest the large batch (should auto-chunk)
|
# Ingest the large batch (should auto-chunk)
|
||||||
result = await memory.put_batch_async(
|
result = await memory.retain_batch_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
contents=contents
|
contents=contents
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ async def test_large_batch_auto_chunks(memory):
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_small_batch_no_chunking(memory):
|
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
|
# Create a small batch that should NOT trigger chunking
|
||||||
contents = [
|
contents = [
|
||||||
|
|
@ -48,8 +48,8 @@ async def test_small_batch_no_chunking(memory):
|
||||||
print(f"Should NOT trigger chunking: {total_chars <= 50_000}")
|
print(f"Should NOT trigger chunking: {total_chars <= 50_000}")
|
||||||
|
|
||||||
# Ingest the small batch (should NOT auto-chunk)
|
# Ingest the small batch (should NOT auto-chunk)
|
||||||
result = await memory.put_batch_async(
|
result = await memory.retain_batch_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
contents=contents
|
contents=contents
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,62 +9,62 @@ from datetime import datetime, timezone
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_document_creation_and_retrieval(memory):
|
async def test_document_creation_and_retrieval(memory):
|
||||||
"""Test that documents are created and can be retrieved."""
|
"""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:
|
try:
|
||||||
document_id = "meeting-001"
|
document_id = "meeting-001"
|
||||||
|
|
||||||
# Store memory with document tracking
|
# Store memory with document tracking
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Google. Bob works at Microsoft.",
|
content="Alice works at Google. Bob works at Microsoft.",
|
||||||
context="Team meeting",
|
context="Team meeting",
|
||||||
document_id=document_id
|
document_id=document_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Retrieve document
|
# 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 is not None
|
||||||
assert doc["id"] == document_id
|
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 "Alice works at Google" in doc["original_text"]
|
||||||
assert doc["memory_unit_count"] > 0
|
assert doc["memory_unit_count"] > 0
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_document_upsert(memory):
|
async def test_document_upsert(memory):
|
||||||
"""Test that providing the same document_id automatically upserts (deletes old units and creates new ones)."""
|
"""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:
|
try:
|
||||||
document_id = "meeting-002"
|
document_id = "meeting-002"
|
||||||
|
|
||||||
# First version
|
# First version
|
||||||
units_v1 = await memory.put_async(
|
units_v1 = await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Google.",
|
content="Alice works at Google.",
|
||||||
context="Initial",
|
context="Initial",
|
||||||
document_id=document_id
|
document_id=document_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get document stats
|
# 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"]
|
count_v1 = doc_v1["memory_unit_count"]
|
||||||
|
|
||||||
# Update with different content (automatic upsert when same document_id is provided)
|
# Update with different content (automatic upsert when same document_id is provided)
|
||||||
units_v2 = await memory.put_async(
|
units_v2 = await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Microsoft. Bob works at Apple.",
|
content="Alice works at Microsoft. Bob works at Apple.",
|
||||||
context="Updated",
|
context="Updated",
|
||||||
document_id=document_id
|
document_id=document_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get updated document stats
|
# 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"]
|
count_v2 = doc_v2["memory_unit_count"]
|
||||||
|
|
||||||
# Verify old units were replaced
|
# Verify old units were replaced
|
||||||
|
|
@ -75,52 +75,52 @@ async def test_document_upsert(memory):
|
||||||
assert set(units_v1).isdisjoint(set(units_v2))
|
assert set(units_v1).isdisjoint(set(units_v2))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_document_deletion(memory):
|
async def test_document_deletion(memory):
|
||||||
"""Test that deleting a document cascades to memory units."""
|
"""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:
|
try:
|
||||||
document_id = "meeting-003"
|
document_id = "meeting-003"
|
||||||
|
|
||||||
# Create document
|
# Create document
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Google.",
|
content="Alice works at Google.",
|
||||||
context="Test",
|
context="Test",
|
||||||
document_id=document_id
|
document_id=document_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify it exists
|
# 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 is not None
|
||||||
assert doc["memory_unit_count"] > 0
|
assert doc["memory_unit_count"] > 0
|
||||||
|
|
||||||
# Delete document
|
# 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["document_deleted"] == 1
|
||||||
assert result["memory_units_deleted"] > 0
|
assert result["memory_units_deleted"] > 0
|
||||||
|
|
||||||
# Verify it's gone
|
# 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
|
assert doc_after is None
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_memory_without_document(memory):
|
async def test_memory_without_document(memory):
|
||||||
"""Test that memories can still be created without document tracking."""
|
"""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:
|
try:
|
||||||
# Create memory without document_id (backward compatibility)
|
# Create memory without document_id (backward compatibility)
|
||||||
units = await memory.put_async(
|
units = await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Google.",
|
content="Alice works at Google.",
|
||||||
context="Test"
|
context="Test"
|
||||||
)
|
)
|
||||||
|
|
@ -128,4 +128,4 @@ async def test_memory_without_document(memory):
|
||||||
assert len(units) > 0
|
assert len(units) > 0
|
||||||
|
|
||||||
finally:
|
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 test addresses the issue where podcast transcripts with context like
|
||||||
"this was podcast episode between you (Marcus) and Jamie" were extracting
|
"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 = """
|
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"]
|
agent_facts = [f for f in facts if f["fact_type"] == "agent"]
|
||||||
|
|
||||||
assert len(agent_facts) > 0, \
|
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]}"
|
f"Got facts: {[f['fact'] + ' [' + f['fact_type'] + ']' for f in facts]}"
|
||||||
|
|
||||||
for agent_fact in agent_facts:
|
for agent_fact in agent_facts:
|
||||||
|
|
@ -1019,10 +1019,10 @@ class TestPersonalityInference:
|
||||||
async def test_background_merge_with_personality_inference(self, memory):
|
async def test_background_merge_with_personality_inference(self, memory):
|
||||||
"""Test that background merge infers personality traits by default."""
|
"""Test that background merge infers personality traits by default."""
|
||||||
import uuid
|
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(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a creative software engineer who loves innovation and trying new technologies",
|
"I am a creative software engineer who loves innovation and trying new technologies",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
@ -1049,13 +1049,13 @@ class TestPersonalityInference:
|
||||||
async def test_background_merge_without_personality_inference(self, memory):
|
async def test_background_merge_without_personality_inference(self, memory):
|
||||||
"""Test that background merge skips personality inference when disabled."""
|
"""Test that background merge skips personality inference when disabled."""
|
||||||
import uuid
|
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"]
|
initial_personality = initial_profile["personality"]
|
||||||
|
|
||||||
result = await memory.merge_agent_background(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a data scientist",
|
"I am a data scientist",
|
||||||
update_personality=False
|
update_personality=False
|
||||||
)
|
)
|
||||||
|
|
@ -1063,7 +1063,7 @@ class TestPersonalityInference:
|
||||||
assert "background" in result
|
assert "background" in result
|
||||||
assert "personality" not 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"]
|
final_personality = final_profile["personality"]
|
||||||
|
|
||||||
assert initial_personality == final_personality
|
assert initial_personality == final_personality
|
||||||
|
|
@ -1072,10 +1072,10 @@ class TestPersonalityInference:
|
||||||
async def test_personality_inference_for_organized_engineer(self, memory):
|
async def test_personality_inference_for_organized_engineer(self, memory):
|
||||||
"""Test personality inference for organized/conscientious profile."""
|
"""Test personality inference for organized/conscientious profile."""
|
||||||
import uuid
|
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(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a methodical engineer who values organization and systematic planning",
|
"I am a methodical engineer who values organization and systematic planning",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
@ -1088,10 +1088,10 @@ class TestPersonalityInference:
|
||||||
async def test_personality_inference_for_startup_founder(self, memory):
|
async def test_personality_inference_for_startup_founder(self, memory):
|
||||||
"""Test personality inference for entrepreneurial profile."""
|
"""Test personality inference for entrepreneurial profile."""
|
||||||
import uuid
|
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(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a startup founder who thrives on risk and social interaction",
|
"I am a startup founder who thrives on risk and social interaction",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
@ -1105,17 +1105,17 @@ class TestPersonalityInference:
|
||||||
async def test_personality_updates_in_database(self, memory):
|
async def test_personality_updates_in_database(self, memory):
|
||||||
"""Test that inferred personality is actually stored in database."""
|
"""Test that inferred personality is actually stored in database."""
|
||||||
import uuid
|
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(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am an innovative designer",
|
"I am an innovative designer",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
||||||
inferred_personality = result["personality"]
|
inferred_personality = result["personality"]
|
||||||
|
|
||||||
profile = await memory.get_agent_profile(agent_id)
|
profile = await memory.get_bank_profile(bank_id)
|
||||||
db_personality = profile["personality"]
|
db_personality = profile["personality"]
|
||||||
|
|
||||||
assert db_personality == inferred_personality
|
assert db_personality == inferred_personality
|
||||||
|
|
@ -1124,17 +1124,17 @@ class TestPersonalityInference:
|
||||||
async def test_multiple_background_merges_update_personality(self, memory):
|
async def test_multiple_background_merges_update_personality(self, memory):
|
||||||
"""Test that each background merge can update personality."""
|
"""Test that each background merge can update personality."""
|
||||||
import uuid
|
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(
|
result1 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I am a software engineer",
|
"I am a software engineer",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
personality1 = result1["personality"]
|
personality1 = result1["personality"]
|
||||||
|
|
||||||
result2 = await memory.merge_agent_background(
|
result2 = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I love creative problem solving and innovation",
|
"I love creative problem solving and innovation",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
@ -1147,16 +1147,16 @@ class TestPersonalityInference:
|
||||||
async def test_background_merge_conflict_resolution_with_personality(self, memory):
|
async def test_background_merge_conflict_resolution_with_personality(self, memory):
|
||||||
"""Test that conflicts are resolved and personality reflects final background."""
|
"""Test that conflicts are resolved and personality reflects final background."""
|
||||||
import uuid
|
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(
|
await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"I was born in Colorado and prefer stability",
|
"I was born in Colorado and prefer stability",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await memory.merge_agent_background(
|
result = await memory.merge_bank_background(
|
||||||
agent_id,
|
bank_id,
|
||||||
"You were born in Texas and love taking risks",
|
"You were born in Texas and love taking risks",
|
||||||
update_personality=True
|
update_personality=True
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,19 @@ distinguish between things said earlier vs later.
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from hindsight_api import MemoryEngine
|
from hindsight_api import MemoryEngine
|
||||||
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fact_ordering_within_conversation(memory):
|
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)
|
# 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
|
# Update personality to match Marcus
|
||||||
await memory.update_agent_personality(agent_id, {
|
await memory.update_bank_personality(bank_id, {
|
||||||
"openness": 0.7,
|
"openness": 0.7,
|
||||||
"conscientiousness": 0.6,
|
"conscientiousness": 0.6,
|
||||||
"extraversion": 0.8,
|
"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)
|
base_event_date = datetime(2024, 11, 14, 10, 0, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
# Store the conversation
|
# Store the conversation
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content=conversation,
|
content=conversation,
|
||||||
context="podcast discussion about NFL game",
|
context="podcast discussion about NFL game",
|
||||||
event_date=base_event_date,
|
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
|
# Search for all facts about Marcus's predictions
|
||||||
results = await memory.search_async(
|
results = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="Marcus prediction Rams",
|
query="Marcus prediction Rams",
|
||||||
fact_type=['agent', 'world'],
|
fact_type=['bank', 'world'],
|
||||||
thinking_budget=100,
|
budget=Budget.LOW,
|
||||||
max_tokens=8192
|
max_tokens=8192
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== Retrieved {len(results.results)} facts ===")
|
print(f"\n=== Retrieved {len(results.results)} facts ===")
|
||||||
for i, result in enumerate(results.results):
|
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)
|
# 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) ===")
|
print(f"\n=== Agent facts (Marcus's statements) ===")
|
||||||
for i, fact in enumerate(agent_facts):
|
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
|
# Check that agent facts have different timestamps
|
||||||
if len(agent_facts) >= 2:
|
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)
|
# Verify timestamps are different (have time offsets)
|
||||||
unique_timestamps = set(timestamps)
|
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")
|
print(f"\n✅ Temporal ordering preserved: First prediction came before changed prediction")
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
print(f"\n✅ Test passed: Fact ordering within conversation is preserved")
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_multiple_documents_ordering(memory):
|
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
|
# Two separate conversations with same base time
|
||||||
base_time = datetime(2024, 11, 14, 10, 0, 0, tzinfo=timezone.utc)
|
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
|
# Store both conversations with batch
|
||||||
await memory.put_batch_async(
|
await memory.retain_batch_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
contents=[
|
contents=[
|
||||||
{"content": conv1, "context": "project discussion 1", "event_date": base_time},
|
{"content": conv1, "context": "project discussion 1", "event_date": base_time},
|
||||||
{"content": conv2, "context": "project discussion 2", "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
|
# Search for Alice's preferences
|
||||||
results = await memory.search_async(
|
results = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="Alice preference React Vue",
|
query="Alice preference React Vue",
|
||||||
fact_type=['agent'],
|
fact_type=['bank'],
|
||||||
thinking_budget=100,
|
budget=Budget.LOW,
|
||||||
max_tokens=8192
|
max_tokens=8192
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== Retrieved {len(results.results)} agent facts ===")
|
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):
|
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
|
# Each conversation's facts should have different timestamps
|
||||||
if len(agent_facts) >= 2:
|
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)
|
unique_timestamps = set(timestamps)
|
||||||
|
|
||||||
assert len(unique_timestamps) >= 2, \
|
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")
|
print(f"\n✅ Facts from {len(agent_facts)} statements have {len(unique_timestamps)} unique timestamps")
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
print(f"\n✅ Test passed: Multiple documents maintain separate ordering")
|
print(f"\n✅ Test passed: Multiple documents maintain separate ordering")
|
||||||
|
|
|
||||||
|
|
@ -21,50 +21,52 @@ async def api_client(memory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_agent_id():
|
def test_bank_id():
|
||||||
"""Provide a unique agent ID for this test run."""
|
"""Provide a unique bank ID for this test run."""
|
||||||
return f"integration_test_{datetime.now().timestamp()}"
|
return f"integration_test_{datetime.now().timestamp()}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@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.
|
End-to-end test covering all major API endpoints in a realistic workflow.
|
||||||
|
|
||||||
Workflow:
|
Workflow:
|
||||||
1. Create agent and set profile
|
1. Create bank and set profile
|
||||||
2. Store memories (put, batch put)
|
2. Store memories (retain)
|
||||||
3. Search memories
|
3. Recall memories
|
||||||
4. Think (generate answer)
|
4. Reflect (generate answer)
|
||||||
5. List agents and memories
|
5. List banks and memories
|
||||||
6. Get agent profile
|
6. Get bank profile
|
||||||
7. Get visualization data
|
7. Get visualization data
|
||||||
8. Track documents
|
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)
|
# List banks (should be empty initially or have other test banks)
|
||||||
response = await api_client.get("/api/v1/agents")
|
response = await api_client.get("/v1/default/banks")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
initial_agents_data = response.json()["agents"]
|
initial_banks_data = response.json()["banks"]
|
||||||
initial_agents = [a["agent_id"] for a in initial_agents_data]
|
initial_banks = [a["bank_id"] for a in initial_banks_data]
|
||||||
print(f"Initial agents: {len(initial_agents)}")
|
print(f"Initial banks: {len(initial_banks)}")
|
||||||
|
|
||||||
# Get agent profile (creates default if not exists)
|
# Get bank profile (creates default if not exists)
|
||||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/profile")
|
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
profile = response.json()
|
profile = response.json()
|
||||||
assert "personality" in profile
|
assert "personality" in profile
|
||||||
assert "background" 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
|
# Add background
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/background",
|
f"/v1/default/banks/{test_bank_id}/background",
|
||||||
json={
|
json={
|
||||||
"content": "A software engineer passionate about AI and memory systems."
|
"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)
|
# Store single memory (using batch endpoint with single item)
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/memories",
|
f"/v1/default/banks/{test_bank_id}/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -97,7 +99,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
|
|
||||||
# Store batch memories
|
# Store batch memories
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/memories",
|
f"/v1/default/banks/{test_bank_id}/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"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")
|
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(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/memories/search",
|
f"/v1/default/banks/{test_bank_id}/memories/recall",
|
||||||
json={
|
json={
|
||||||
"query": "Who works on machine learning?",
|
"query": "Who works on machine learning?",
|
||||||
"thinking_budget": 50
|
"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"
|
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(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/think",
|
f"/v1/default/banks/{test_bank_id}/reflect",
|
||||||
json={
|
json={
|
||||||
"query": "What do you know about the team members?",
|
"query": "What do you know about the team members?",
|
||||||
"thinking_budget": 30,
|
"thinking_budget": 30,
|
||||||
|
|
@ -153,14 +155,14 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
think_result = response.json()
|
reflect_result = response.json()
|
||||||
assert "text" in think_result
|
assert "text" in reflect_result
|
||||||
assert len(think_result["text"]) > 0
|
assert len(reflect_result["text"]) > 0
|
||||||
assert "based_on" in think_result
|
assert "based_on" in reflect_result
|
||||||
print(f"Think response: {think_result['text'][:100]}...")
|
print(f"Reflect response: {reflect_result['text'][:100]}...")
|
||||||
|
|
||||||
# Verify the answer mentions team members
|
# 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
|
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
|
# 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
|
assert response.status_code == 200
|
||||||
graph_data = response.json()
|
graph_data = response.json()
|
||||||
assert "nodes" in graph_data
|
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")
|
print(f"Graph has {len(graph_data['nodes'])} nodes and {len(graph_data['edges'])} edges")
|
||||||
|
|
||||||
# Get memory statistics
|
# 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
|
assert response.status_code == 200
|
||||||
stats = response.json()
|
stats = response.json()
|
||||||
assert "total_nodes" in stats
|
assert "total_nodes" in stats
|
||||||
|
|
@ -185,7 +187,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
|
|
||||||
# List memory units
|
# List memory units
|
||||||
response = await api_client.get(
|
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}
|
params={"limit": 10}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
@ -200,7 +202,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
|
|
||||||
# Store memory with document
|
# Store memory with document
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/memories",
|
f"/v1/default/banks/{test_bank_id}/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -215,7 +217,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
print("Stored memory with document tracking")
|
print("Stored memory with document tracking")
|
||||||
|
|
||||||
# List documents
|
# 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
|
assert response.status_code == 200
|
||||||
documents = response.json()
|
documents = response.json()
|
||||||
assert "items" in documents
|
assert "items" in documents
|
||||||
|
|
@ -224,7 +226,7 @@ async def test_full_api_workflow(api_client, test_agent_id):
|
||||||
|
|
||||||
# Get specific document
|
# Get specific document
|
||||||
response = await api_client.get(
|
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
|
assert response.status_code == 200
|
||||||
doc_info = response.json()
|
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
|
# 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)
|
# Update personality traits
|
||||||
response = await api_client.get(f"/api/v1/agents/{test_agent_id}/profile")
|
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
|
assert response.status_code == 200
|
||||||
updated_profile = response.json()
|
updated_profile = response.json()
|
||||||
assert "software engineer" in updated_profile["background"].lower()
|
assert "software engineer" in updated_profile["background"].lower()
|
||||||
print("Profile verified")
|
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
|
assert response.status_code == 200
|
||||||
final_agents_data = response.json()["agents"]
|
entities_data = response.json()
|
||||||
final_agents = [a["agent_id"] for a in final_agents_data]
|
assert "items" in entities_data
|
||||||
assert test_agent_id in final_agents
|
print(f"Found {len(entities_data['items'])} entities")
|
||||||
assert len(final_agents) >= len(initial_agents) + 1
|
|
||||||
print(f"Final agent count: {len(final_agents)}")
|
# 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
|
response = await api_client.get("/v1/default/banks")
|
||||||
# Using timestamped agent IDs prevents conflicts between test runs
|
assert response.status_code == 200
|
||||||
print(f"Integration test complete for agent {test_agent_id}")
|
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
|
@pytest.mark.asyncio
|
||||||
|
|
@ -272,7 +320,7 @@ async def test_error_handling(api_client):
|
||||||
|
|
||||||
# Invalid request (missing required field)
|
# Invalid request (missing required field)
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
"/api/v1/agents/error_test/memories",
|
"/v1/default/banks/error_test/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -284,19 +332,19 @@ async def test_error_handling(api_client):
|
||||||
)
|
)
|
||||||
assert response.status_code == 422 # Validation error
|
assert response.status_code == 422 # Validation error
|
||||||
|
|
||||||
# Search with invalid parameters
|
# Recall with invalid parameters
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
"/api/v1/agents/error_test/memories/search",
|
"/v1/default/banks/error_test/memories/recall",
|
||||||
json={
|
json={
|
||||||
"query": "test",
|
"query": "test",
|
||||||
"thinking_budget": -1 # Invalid negative budget
|
"budget": "invalid_budget" # Invalid budget value (should be low/mid/high)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
|
|
||||||
# Get non-existent document
|
# Get non-existent document
|
||||||
response = await api_client.get(
|
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
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
@ -306,7 +354,7 @@ async def test_error_handling(api_client):
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_concurrent_requests(api_client):
|
async def test_concurrent_requests(api_client):
|
||||||
"""Test that API can handle concurrent requests."""
|
"""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)
|
# Store multiple memories concurrently (simulated with sequential calls)
|
||||||
responses = []
|
responses = []
|
||||||
|
|
@ -319,7 +367,7 @@ async def test_concurrent_requests(api_client):
|
||||||
]
|
]
|
||||||
for fact in test_facts:
|
for fact in test_facts:
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{agent_id}/memories",
|
f"/v1/default/banks/{bank_id}/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -337,7 +385,7 @@ async def test_concurrent_requests(api_client):
|
||||||
|
|
||||||
# Verify all facts stored
|
# Verify all facts stored
|
||||||
response = await api_client.get(
|
response = await api_client.get(
|
||||||
f"/api/v1/agents/{agent_id}/memories/list",
|
f"/v1/default/banks/{bank_id}/memories/list",
|
||||||
params={"limit": 20}
|
params={"limit": 20}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
@ -350,11 +398,11 @@ async def test_concurrent_requests(api_client):
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_document_deletion(api_client):
|
async def test_document_deletion(api_client):
|
||||||
"""Test document deletion including cascade deletion of memory units and links."""
|
"""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
|
# Store a document with memory
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
f"/api/v1/agents/{test_agent_id}/memories",
|
f"/v1/default/banks/{test_bank_id}/memories",
|
||||||
json={
|
json={
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -370,7 +418,7 @@ async def test_document_deletion(api_client):
|
||||||
|
|
||||||
# Verify document exists
|
# Verify document exists
|
||||||
response = await api_client.get(
|
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
|
assert response.status_code == 200
|
||||||
doc_info = response.json()
|
doc_info = response.json()
|
||||||
|
|
@ -380,7 +428,7 @@ async def test_document_deletion(api_client):
|
||||||
|
|
||||||
# Delete the document
|
# Delete the document
|
||||||
response = await api_client.delete(
|
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
|
assert response.status_code == 200
|
||||||
delete_result = response.json()
|
delete_result = response.json()
|
||||||
|
|
@ -391,13 +439,13 @@ async def test_document_deletion(api_client):
|
||||||
|
|
||||||
# Verify document is gone (should return 404)
|
# Verify document is gone (should return 404)
|
||||||
response = await api_client.get(
|
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
|
assert response.status_code == 404
|
||||||
print("Document deletion verified - returns 404")
|
print("Document deletion verified - returns 404")
|
||||||
|
|
||||||
# Verify document is not in the list
|
# 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
|
assert response.status_code == 200
|
||||||
documents = response.json()
|
documents = response.json()
|
||||||
doc_ids = [doc["id"] for doc in documents["items"]]
|
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)
|
# Try to delete again (should return 404)
|
||||||
response = await api_client.delete(
|
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
|
assert response.status_code == 404
|
||||||
print("Double delete returns 404 - verified")
|
print("Double delete returns 404 - verified")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
Test observation generation and entity state functionality.
|
Test observation generation and entity state functionality.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -14,19 +15,19 @@ async def test_observation_generation_on_put(memory):
|
||||||
2. Wait for background tasks (observation generation)
|
2. Wait for background tasks (observation generation)
|
||||||
3. Verify observations were created and linked to the entity
|
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:
|
try:
|
||||||
# Store some facts about an entity
|
# Store some facts about an entity
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="John is a software engineer at Google. He is detail-oriented and methodical.",
|
content="John is a software engineer at Google. He is detail-oriented and methodical.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||||
)
|
)
|
||||||
|
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="John has been working on the AI team for 3 years. He specializes in machine learning.",
|
content="John has been working on the AI team for 3 years. He specializes in machine learning.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
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
|
SELECT id, canonical_name
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%john%'
|
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%john%'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if entity_row:
|
if entity_row:
|
||||||
|
|
@ -55,7 +56,7 @@ async def test_observation_generation_on_put(memory):
|
||||||
print(f"Entity: {entity_name} (id: {entity_id})")
|
print(f"Entity: {entity_name} (id: {entity_id})")
|
||||||
|
|
||||||
# Get observations for the entity
|
# 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"\n=== Observations for {entity_name} ===")
|
||||||
print(f"Total observations: {len(observations)}")
|
print(f"Total observations: {len(observations)}")
|
||||||
|
|
@ -79,8 +80,8 @@ async def test_observation_generation_on_put(memory):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute("DELETE FROM memory_units 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 agent_id = $1", agent_id)
|
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -88,12 +89,12 @@ async def test_regenerate_entity_observations(memory):
|
||||||
"""
|
"""
|
||||||
Test explicit regeneration of observations for an entity.
|
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:
|
try:
|
||||||
# Store facts about an entity
|
# Store facts about an entity
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Sarah is a product manager who loves user research and data analysis.",
|
content="Sarah is a product manager who loves user research and data analysis.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||||
|
|
@ -108,10 +109,10 @@ async def test_regenerate_entity_observations(memory):
|
||||||
"""
|
"""
|
||||||
SELECT id, canonical_name
|
SELECT id, canonical_name
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%sarah%'
|
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%sarah%'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if entity_row:
|
if entity_row:
|
||||||
|
|
@ -120,7 +121,7 @@ async def test_regenerate_entity_observations(memory):
|
||||||
|
|
||||||
# Manually regenerate observations
|
# Manually regenerate observations
|
||||||
created_ids = await memory.regenerate_entity_observations(
|
created_ids = await memory.regenerate_entity_observations(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name
|
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}")
|
print(f"Created {len(created_ids)} observations for {entity_name}")
|
||||||
|
|
||||||
# Get the observations
|
# 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:
|
for obs in observations:
|
||||||
print(f" - {obs.text}")
|
print(f" - {obs.text}")
|
||||||
|
|
||||||
|
|
@ -147,8 +148,8 @@ async def test_regenerate_entity_observations(memory):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute("DELETE FROM memory_units 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 agent_id = $1", agent_id)
|
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@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.
|
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:
|
try:
|
||||||
# Store facts about entities
|
# Store facts about entities
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice is a data scientist who works on recommendation systems at Netflix.",
|
content="Alice is a data scientist who works on recommendation systems at Netflix.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||||
)
|
)
|
||||||
|
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice presented her research at the ML conference last month. She is an expert in deep learning.",
|
content="Alice presented her research at the ML conference last month. She is an expert in deep learning.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
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()
|
await memory.wait_for_background_tasks()
|
||||||
|
|
||||||
# Search with include_entities=True
|
# Search with include_entities=True
|
||||||
result = await memory.search_async(
|
result = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="What does Alice do?",
|
query="What does Alice do?",
|
||||||
fact_type=["world", "agent"],
|
fact_type=["world", "agent"],
|
||||||
thinking_budget=30,
|
budget=Budget.LOW, # 30,
|
||||||
max_tokens=2000,
|
max_tokens=2000,
|
||||||
include_entities=True,
|
include_entities=True,
|
||||||
max_entity_tokens=500
|
max_entity_tokens=500
|
||||||
|
|
@ -223,8 +224,8 @@ async def test_search_with_include_entities(memory):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute("DELETE FROM memory_units 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 agent_id = $1", agent_id)
|
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -232,12 +233,12 @@ async def test_get_entity_state(memory):
|
||||||
"""
|
"""
|
||||||
Test getting the full state of an entity.
|
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:
|
try:
|
||||||
# Store facts
|
# Store facts
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Bob is a frontend developer who specializes in React and TypeScript.",
|
content="Bob is a frontend developer who specializes in React and TypeScript.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||||
|
|
@ -252,10 +253,10 @@ async def test_get_entity_state(memory):
|
||||||
"""
|
"""
|
||||||
SELECT id, canonical_name
|
SELECT id, canonical_name
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE agent_id = $1 AND LOWER(canonical_name) LIKE '%bob%'
|
WHERE bank_id = $1 AND LOWER(canonical_name) LIKE '%bob%'
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if entity_row:
|
if entity_row:
|
||||||
|
|
@ -264,7 +265,7 @@ async def test_get_entity_state(memory):
|
||||||
|
|
||||||
# Get entity state
|
# Get entity state
|
||||||
state = await memory.get_entity_state(
|
state = await memory.get_entity_state(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
entity_id=entity_id,
|
entity_id=entity_id,
|
||||||
entity_name=entity_name,
|
entity_name=entity_name,
|
||||||
limit=10
|
limit=10
|
||||||
|
|
@ -284,8 +285,8 @@ async def test_get_entity_state(memory):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute("DELETE FROM memory_units 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 agent_id = $1", agent_id)
|
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@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.
|
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:
|
try:
|
||||||
# Store facts
|
# Store facts
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Charlie is a DevOps engineer who manages the Kubernetes infrastructure.",
|
content="Charlie is a DevOps engineer who manages the Kubernetes infrastructure.",
|
||||||
context="work info",
|
context="work info",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
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
|
SELECT id, text, fact_type, context
|
||||||
FROM memory_units
|
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 ===")
|
print(f"\n=== Observation Records in Database ===")
|
||||||
|
|
@ -334,5 +335,5 @@ async def test_observation_fact_type_in_database(memory):
|
||||||
# Cleanup
|
# Cleanup
|
||||||
pool = await memory._get_pool()
|
pool = await memory._get_pool()
|
||||||
async with pool.acquire() as conn:
|
async with pool.acquire() as conn:
|
||||||
await conn.execute("DELETE FROM memory_units 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 agent_id = $1", agent_id)
|
await conn.execute("DELETE FROM entities WHERE bank_id = $1", bank_id)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
Test search tracing functionality.
|
Test search tracing functionality.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
from hindsight_api import SearchTrace
|
from hindsight_api import SearchTrace
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
@ -10,33 +11,33 @@ from datetime import datetime, timezone
|
||||||
async def test_search_with_trace(memory):
|
async def test_search_with_trace(memory):
|
||||||
"""Test that search with enable_trace=True returns a valid SearchTrace."""
|
"""Test that search with enable_trace=True returns a valid SearchTrace."""
|
||||||
# Generate a unique agent ID for this test
|
# 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:
|
try:
|
||||||
|
|
||||||
# Store some test memories
|
# Store some test memories
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Alice works at Google in Mountain View",
|
content="Alice works at Google in Mountain View",
|
||||||
context="test context",
|
context="test context",
|
||||||
)
|
)
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Bob also works at Google but in New York",
|
content="Bob also works at Google but in New York",
|
||||||
context="test context",
|
context="test context",
|
||||||
)
|
)
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Charlie founded a startup called TechCorp",
|
content="Charlie founded a startup called TechCorp",
|
||||||
context="test context",
|
context="test context",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search with tracing enabled
|
# Search with tracing enabled
|
||||||
search_result = await memory.search_async(
|
search_result = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="Who works at Google?",
|
query="Who works at Google?",
|
||||||
fact_type=["world"],
|
fact_type=["world"],
|
||||||
thinking_budget=20,
|
budget=Budget.LOW, # 20,
|
||||||
max_tokens=512,
|
max_tokens=512,
|
||||||
enable_trace=True,
|
enable_trace=True,
|
||||||
)
|
)
|
||||||
|
|
@ -51,7 +52,7 @@ async def test_search_with_trace(memory):
|
||||||
|
|
||||||
# Verify query info
|
# Verify query info
|
||||||
assert trace["query"]["query_text"] == "Who works at Google?"
|
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 trace["query"]["max_tokens"] == 512
|
||||||
assert len(trace["query"]["query_embedding"]) > 0, "Query embedding should be populated"
|
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
|
# Verify summary
|
||||||
assert trace["summary"]["total_nodes_visited"] == len(trace["visits"])
|
assert trace["summary"]["total_nodes_visited"] == len(trace["visits"])
|
||||||
assert trace["summary"]["results_returned"] == len(search_result.results)
|
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
|
assert trace["summary"]["total_duration_seconds"] > 0
|
||||||
|
|
||||||
# Verify phase metrics
|
# Verify phase metrics
|
||||||
|
|
@ -101,29 +102,29 @@ async def test_search_with_trace(memory):
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Cleanup
|
# Cleanup
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_search_without_trace(memory):
|
async def test_search_without_trace(memory):
|
||||||
"""Test that search with enable_trace=False returns None for trace."""
|
"""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:
|
try:
|
||||||
|
|
||||||
# Store a test memory
|
# Store a test memory
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Test memory without trace",
|
content="Test memory without trace",
|
||||||
context="test",
|
context="test",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search without tracing
|
# Search without tracing
|
||||||
search_result = await memory.search_async(
|
search_result = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="test",
|
query="test",
|
||||||
fact_type=["world"],
|
fact_type=["world"],
|
||||||
thinking_budget=10,
|
budget=Budget.LOW, # 10,
|
||||||
max_tokens=512,
|
max_tokens=512,
|
||||||
enable_trace=False,
|
enable_trace=False,
|
||||||
)
|
)
|
||||||
|
|
@ -136,4 +137,4 @@ async def test_search_without_trace(memory):
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Cleanup
|
# 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
|
from datetime import datetime, timezone, timedelta
|
||||||
import pytest
|
import pytest
|
||||||
from hindsight_api import MemoryEngine
|
from hindsight_api import MemoryEngine
|
||||||
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -19,11 +20,11 @@ async def test_temporal_ranges_are_written():
|
||||||
)
|
)
|
||||||
await memory.initialize()
|
await memory.initialize()
|
||||||
|
|
||||||
agent_id = "test_temporal_ranges"
|
bank_id = "test_temporal_ranges"
|
||||||
|
|
||||||
# Clean up any existing data
|
# Clean up any existing data
|
||||||
try:
|
try:
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -31,8 +32,8 @@ async def test_temporal_ranges_are_written():
|
||||||
conversation_date = datetime(2024, 11, 17, 10, 0, 0, tzinfo=timezone.utc)
|
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."
|
text1 = "Yesterday I went to a pottery workshop where I made a beautiful vase."
|
||||||
|
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content=text1,
|
content=text1,
|
||||||
event_date=conversation_date
|
event_date=conversation_date
|
||||||
)
|
)
|
||||||
|
|
@ -40,8 +41,8 @@ async def test_temporal_ranges_are_written():
|
||||||
# Test 2: Period event (month range)
|
# Test 2: Period event (month range)
|
||||||
text2 = "In February 2024, Alice visited Paris and explored the Louvre museum."
|
text2 = "In February 2024, Alice visited Paris and explored the Louvre museum."
|
||||||
|
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content=text2,
|
content=text2,
|
||||||
event_date=conversation_date
|
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
|
SELECT id, text, event_date, occurred_start, occurred_end, mentioned_at
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $1
|
WHERE bank_id = $1
|
||||||
ORDER BY created_at
|
ORDER BY created_at
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n\n=== Retrieved {len(rows)} facts ===")
|
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
|
# Test search results also include temporal fields
|
||||||
print("\n=== Testing Search Results ===")
|
print("\n=== Testing Search Results ===")
|
||||||
search_result = await memory.search_async(
|
search_result = await memory.recall_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="pottery workshop",
|
query="pottery workshop",
|
||||||
fact_type=["event", "world"],
|
fact_type=["event", "world"],
|
||||||
thinking_budget=20,
|
budget=Budget.LOW,
|
||||||
max_tokens=4096
|
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)")
|
print("⚠ Temporal fields not yet populated in search results (known issue)")
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
await memory.close()
|
await memory.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ Test think function for opinion generation and consistency.
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from hindsight_api.engine.memory_engine import Budget
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -13,20 +14,20 @@ async def test_think_opinion_consistency(memory):
|
||||||
2. Stores the opinion in the database
|
2. Stores the opinion in the database
|
||||||
3. Returns consistent response on subsequent calls with the same query
|
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:
|
try:
|
||||||
|
|
||||||
# Store some initial facts to give context for opinion formation
|
# Store some initial facts to give context for opinion formation
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
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.",
|
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",
|
context="performance review",
|
||||||
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
event_date=datetime(2024, 1, 15, tzinfo=timezone.utc)
|
||||||
)
|
)
|
||||||
|
|
||||||
await memory.put_async(
|
await memory.retain_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
content="Bob recently joined the team. He missed his first deadline and his code had many bugs.",
|
content="Bob recently joined the team. He missed his first deadline and his code had many bugs.",
|
||||||
context="performance review",
|
context="performance review",
|
||||||
event_date=datetime(2024, 2, 1, tzinfo=timezone.utc)
|
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
|
# First think call - should generate opinions
|
||||||
query = "Who is a more reliable engineer?"
|
query = "Who is a more reliable engineer?"
|
||||||
result1 = await memory.think_async(
|
result1 = await memory.reflect_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query=query,
|
query=query,
|
||||||
thinking_budget=30,
|
budget=Budget.LOW,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== First Think Call ===")
|
print(f"\n=== First Think Call ===")
|
||||||
print(f"Answer: {result1.text}")
|
print(f"Answer: {result1.text}")
|
||||||
print(f"New opinions formed: {len(result1.new_opinions)}")
|
|
||||||
|
|
||||||
# Verify we got an answer
|
# Verify we got an answer
|
||||||
assert result1.text, "First think call should return an answer"
|
assert result1.text, "First think call should return an answer"
|
||||||
assert result1.based_on, "Should return based_on facts"
|
assert result1.based_on, "Should return based_on facts"
|
||||||
|
|
||||||
# Verify opinions were formed
|
# Wait for background opinion processing tasks to complete
|
||||||
new_opinions_count = len(result1.new_opinions)
|
|
||||||
print(f"\nNew opinions formed: {new_opinions_count}")
|
|
||||||
|
|
||||||
# Wait for background opinion PUT tasks to complete
|
|
||||||
await memory.wait_for_background_tasks()
|
await memory.wait_for_background_tasks()
|
||||||
|
|
||||||
# Search for stored opinions to verify they were actually saved
|
# 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
|
SELECT id, text, confidence_score, fact_type
|
||||||
FROM memory_units
|
FROM memory_units
|
||||||
WHERE agent_id = $1 AND fact_type = 'opinion'
|
WHERE bank_id = $1 AND fact_type = 'opinion'
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
""",
|
""",
|
||||||
agent_id
|
bank_id
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== Stored Opinions in Database ===")
|
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)")
|
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
|
# Second think call - should use the stored opinions
|
||||||
result2 = await memory.think_async(
|
result2 = await memory.reflect_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query=query,
|
query=query,
|
||||||
thinking_budget=30,
|
budget=Budget.LOW,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== Second Think Call ===")
|
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', []))}")
|
print(f"Existing opinions used: {len(result2.based_on.get('opinion', []))}")
|
||||||
for opinion in result2.based_on.get('opinion', []):
|
for opinion in result2.based_on.get('opinion', []):
|
||||||
print(f" - {opinion.text}")
|
print(f" - {opinion.text}")
|
||||||
print(f"New opinions formed: {len(result2.new_opinions)}")
|
|
||||||
|
|
||||||
# Verify second call also got an answer
|
# Verify second call also got an answer
|
||||||
assert result2.text, "Second think call should return an answer"
|
assert result2.text, "Second think call should return an answer"
|
||||||
|
|
@ -127,7 +122,7 @@ async def test_think_opinion_consistency(memory):
|
||||||
finally:
|
finally:
|
||||||
# Clean up agent data
|
# Clean up agent data
|
||||||
try:
|
try:
|
||||||
await memory.delete_agent(agent_id)
|
await memory.delete_bank(bank_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Error during cleanup: {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.
|
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
|
# Call think without storing any prior facts
|
||||||
result = await memory.think_async(
|
result = await memory.reflect_async(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
query="What is the capital of France?",
|
query="What is the capital of France?",
|
||||||
thinking_budget=20,
|
budget=Budget.LOW,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"\n=== Think Without Context ===")
|
print(f"\n=== Think Without Context ===")
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,16 @@ name = "hindsight"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Hindsight API client (generated)
|
||||||
|
hindsight-client = { path = "../hindsight-clients/rust" }
|
||||||
|
|
||||||
# CLI framework
|
# CLI framework
|
||||||
clap = { version = "4.5", features = ["derive", "env"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
|
|
||||||
# HTTP client
|
# Async runtime
|
||||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
# Serialization
|
# Serialization (for config and output formatting)
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
|
|
||||||
|
|
@ -1,134 +1,16 @@
|
||||||
use anyhow::{Context, Result};
|
//! API client wrapper
|
||||||
use reqwest::blocking::Client;
|
//!
|
||||||
|
//! 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::{Deserialize, Serialize};
|
||||||
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct AgentStats {
|
pub struct AgentStats {
|
||||||
pub agent_id: String,
|
pub agent_id: String,
|
||||||
|
|
@ -143,36 +25,6 @@ pub struct AgentStats {
|
||||||
pub failed_operations: i32,
|
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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Operation {
|
pub struct Operation {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|
@ -191,628 +43,210 @@ pub struct OperationsResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct DeleteResponse {
|
pub struct TraceInfo {
|
||||||
pub success: bool,
|
pub total_time: Option<f64>,
|
||||||
pub message: String,
|
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 {
|
pub struct ApiClient {
|
||||||
client: Client,
|
client: AsyncClient,
|
||||||
base_url: String,
|
runtime: std::sync::Arc<tokio::runtime::Runtime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiClient {
|
impl ApiClient {
|
||||||
pub fn new(base_url: String) -> Result<Self> {
|
pub fn new(base_url: String) -> Result<Self> {
|
||||||
let client = Client::builder()
|
let runtime = std::sync::Arc::new(tokio::runtime::Runtime::new()?);
|
||||||
.timeout(Duration::from_secs(60))
|
let client = AsyncClient::new(&base_url);
|
||||||
.build()
|
Ok(ApiClient { client, runtime })
|
||||||
.context("Failed to create HTTP client")?;
|
|
||||||
|
|
||||||
Ok(ApiClient { client, base_url })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(&self, agent_id: &str, request: SearchRequest, verbose: bool) -> Result<SearchResponse> {
|
pub fn list_agents(&self, _verbose: bool) -> Result<Vec<types::BankListItem>> {
|
||||||
let url = format!("{}/api/v1/agents/{}/memories/search", self.base_url, agent_id);
|
self.runtime.block_on(async {
|
||||||
let request_body = serde_json::to_string_pretty(&request).unwrap_or_default();
|
let response = self.client.list_banks().await?;
|
||||||
|
Ok(response.into_inner().banks)
|
||||||
if verbose {
|
})
|
||||||
eprintln!("Request URL: {}", url);
|
|
||||||
eprintln!("Request body:\n{}", request_body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self
|
pub fn get_profile(&self, agent_id: &str, _verbose: bool) -> Result<types::BankProfileResponse> {
|
||||||
.client
|
self.runtime.block_on(async {
|
||||||
.post(&url)
|
let response = self.client.get_bank_profile(agent_id).await?;
|
||||||
.json(&request)
|
Ok(response.into_inner())
|
||||||
.timeout(Duration::from_secs(120))
|
})
|
||||||
.send()?;
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
if verbose {
|
|
||||||
eprintln!("Response status: {}", status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.is_success() {
|
pub fn get_stats(&self, agent_id: &str, _verbose: bool) -> Result<AgentStats> {
|
||||||
let error_body = response.text().unwrap_or_default();
|
self.runtime.block_on(async {
|
||||||
if verbose {
|
let response = self.client.get_agent_stats(agent_id).await?;
|
||||||
eprintln!("Error response body:\n{}", error_body);
|
let value = response.into_inner();
|
||||||
}
|
let stats: AgentStats = serde_json::from_value(value)?;
|
||||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
Ok(stats)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let response_text = response.text()?;
|
pub fn update_agent_name(&self, agent_id: &str, name: &str, _verbose: bool) -> Result<types::BankProfileResponse> {
|
||||||
if verbose {
|
self.runtime.block_on(async {
|
||||||
eprintln!("Response body:\n{}", response_text);
|
let request = types::CreateBankRequest {
|
||||||
}
|
name: Some(name.to_string()),
|
||||||
|
background: None,
|
||||||
let result: SearchResponse = serde_json::from_str(&response_text)
|
personality: None,
|
||||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 {
|
let response = self.client.create_or_update_bank(agent_id, &request).await?;
|
||||||
format!("{}/api/v1/agents/{}/memories/{}", self.base_url, agent_id, endpoint)
|
Ok(response.into_inner())
|
||||||
} 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
|
pub fn add_background(&self, agent_id: &str, content: &str, update_personality: bool, _verbose: bool) -> Result<types::BackgroundResponse> {
|
||||||
.client
|
self.runtime.block_on(async {
|
||||||
.post(&url)
|
let request = types::AddBackgroundRequest {
|
||||||
.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 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 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 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 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(),
|
content: content.to_string(),
|
||||||
update_personality,
|
update_personality,
|
||||||
};
|
};
|
||||||
|
let response = self.client.add_bank_background(agent_id, &request).await?;
|
||||||
if verbose {
|
Ok(response.into_inner())
|
||||||
eprintln!("Request URL: {}", url);
|
})
|
||||||
eprintln!("Request body:\n{}", serde_json::to_string_pretty(&request).unwrap_or_default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self
|
pub fn recall(&self, agent_id: &str, request: &types::RecallRequest, _verbose: bool) -> Result<types::RecallResponse> {
|
||||||
.client
|
self.runtime.block_on(async {
|
||||||
.post(&url)
|
let response = self.client.recall_memories(agent_id, request).await?;
|
||||||
.json(&request)
|
Ok(response.into_inner())
|
||||||
.timeout(Duration::from_secs(60))
|
})
|
||||||
.send()?;
|
|
||||||
|
|
||||||
let status = response.status();
|
|
||||||
if verbose {
|
|
||||||
eprintln!("Response status: {}", status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.is_success() {
|
pub fn reflect(&self, agent_id: &str, request: &types::ReflectRequest, _verbose: bool) -> Result<types::ReflectResponse> {
|
||||||
let error_body = response.text().unwrap_or_default();
|
self.runtime.block_on(async {
|
||||||
if verbose {
|
let response = self.client.reflect(agent_id, request).await?;
|
||||||
eprintln!("Error response body:\n{}", error_body);
|
Ok(response.into_inner())
|
||||||
}
|
})
|
||||||
anyhow::bail!("API returned error status {}: {}", status, error_body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let response_text = response.text()?;
|
pub fn retain(&self, agent_id: &str, request: &types::RetainRequest, _async_mode: bool, _verbose: bool) -> Result<MemoryPutResult> {
|
||||||
if verbose {
|
self.runtime.block_on(async {
|
||||||
eprintln!("Response body:\n{}", response_text);
|
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_,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: BackgroundResponse = serde_json::from_str(&response_text)
|
pub fn delete_memory(&self, _agent_id: &str, _unit_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
// 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 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_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 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_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)
|
Ok(result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stats(&self, agent_id: &str, verbose: bool) -> Result<AgentStats> {
|
pub fn list_operations(&self, agent_id: &str, _verbose: bool) -> Result<OperationsResponse> {
|
||||||
let url = format!("{}/api/v1/agents/{}/stats", self.base_url, agent_id);
|
self.runtime.block_on(async {
|
||||||
|
let response = self.client.list_operations(agent_id).await?;
|
||||||
if verbose {
|
let value = response.into_inner();
|
||||||
eprintln!("Request URL: {}", url);
|
let ops: OperationsResponse = serde_json::from_value(value)?;
|
||||||
|
Ok(ops)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self
|
pub fn cancel_operation(&self, agent_id: &str, operation_id: &str, _verbose: bool) -> Result<types::DeleteResponse> {
|
||||||
.client
|
self.runtime.block_on(async {
|
||||||
.get(&url)
|
let response = self.client.cancel_operation(agent_id, operation_id).await?;
|
||||||
.timeout(Duration::from_secs(30))
|
let value = response.into_inner();
|
||||||
.send()?;
|
let result: types::DeleteResponse = serde_json::from_value(value)?;
|
||||||
|
|
||||||
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)
|
Ok(result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_documents(&self, agent_id: &str, q: Option<&str>, limit: Option<i32>, offset: Option<i32>, verbose: bool) -> Result<DocumentsResponse> {
|
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> {
|
||||||
let mut url = format!("{}/api/v1/agents/{}/documents", self.base_url, agent_id);
|
self.runtime.block_on(async {
|
||||||
let mut params = vec![];
|
let response = self.client.list_memories(bank_id, limit, offset, q, type_filter).await?;
|
||||||
|
Ok(response.into_inner())
|
||||||
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() {
|
pub fn list_entities(&self, bank_id: &str, limit: Option<i64>, _verbose: bool) -> Result<types::EntityListResponse> {
|
||||||
url.push('?');
|
self.runtime.block_on(async {
|
||||||
url.push_str(¶ms.join("&"));
|
let response = self.client.list_entities(bank_id, limit).await?;
|
||||||
|
Ok(response.into_inner())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
pub fn get_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||||
eprintln!("Request URL: {}", url);
|
self.runtime.block_on(async {
|
||||||
|
let response = self.client.get_entity(bank_id, entity_id).await?;
|
||||||
|
Ok(response.into_inner())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = self
|
pub fn regenerate_entity(&self, bank_id: &str, entity_id: &str, _verbose: bool) -> Result<types::EntityDetailResponse> {
|
||||||
.client
|
self.runtime.block_on(async {
|
||||||
.get(&url)
|
let response = self.client.regenerate_entity_observations(bank_id, entity_id).await?;
|
||||||
.timeout(Duration::from_secs(30))
|
Ok(response.into_inner())
|
||||||
.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 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 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 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 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<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 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);
|
|
||||||
|
|
||||||
if let Some(ft) = fact_type {
|
|
||||||
url.push_str(&format!("?fact_type={}", ft));
|
|
||||||
}
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
eprintln!("Request URL: {}", url);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = self
|
|
||||||
.client
|
|
||||||
.delete(&url)
|
|
||||||
.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: DeleteResponse = serde_json::from_str(&response_text)
|
|
||||||
.with_context(|| format!("Failed to parse API response. Response was: {}", response_text))?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 colored::*;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
@ -9,8 +9,8 @@ pub fn print_section_header(title: &str) {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_fact(fact: &Fact, show_activation: bool) {
|
pub fn print_fact(fact: &RecallResult, show_activation: bool) {
|
||||||
let fact_type = fact.fact_type.as_deref().unwrap_or("unknown");
|
let fact_type = fact.type_.as_deref().unwrap_or("unknown");
|
||||||
|
|
||||||
let type_color = match fact_type {
|
let type_color = match fact_type {
|
||||||
"world" => "cyan",
|
"world" => "cyan",
|
||||||
|
|
@ -29,10 +29,10 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
||||||
print!("{} ", prefix);
|
print!("{} ", prefix);
|
||||||
print!("{}", format!("[{}]", fact_type.to_uppercase()).color(type_color).bold());
|
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 show_activation {
|
||||||
if let Some(activation) = fact.activation {
|
// Placeholder for when activation is added to the API schema
|
||||||
print!(" {}", format!("({:.2})", activation).bright_black());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
@ -44,27 +44,12 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show temporal information
|
// 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_start) = &fact.occurred_start {
|
||||||
if let Some(occurred_end) = &fact.occurred_end {
|
if let Some(occurred_end) = &fact.occurred_end {
|
||||||
if occurred_start == occurred_end {
|
println!(" {}: {} - {}", "Date".bright_black(), occurred_start.bright_black(), occurred_end.bright_black());
|
||||||
// Point event
|
|
||||||
println!(" {}: {}", "Occurred".bright_black(), occurred_start.bright_black());
|
|
||||||
} else {
|
} else {
|
||||||
// Range event
|
println!(" {}: {}", "Date".bright_black(), occurred_start.bright_black());
|
||||||
println!(" {}: {} to {}", "Occurred".bright_black(), occurred_start.bright_black(), occurred_end.bright_black());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
println!(" {}: {}", "Occurred".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
|
// Show document ID if available
|
||||||
|
|
@ -75,7 +60,7 @@ pub fn print_fact(fact: &Fact, show_activation: bool) {
|
||||||
println!();
|
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;
|
let results = &response.results;
|
||||||
print_section_header(&format!("Search Results ({})", results.len()));
|
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!();
|
||||||
println!("{}", response.text.bright_white());
|
println!("{}", response.text.bright_white());
|
||||||
println!();
|
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");
|
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());
|
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());
|
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"))
|
Ok(input.trim().eq_ignore_ascii_case("y") || input.trim().eq_ignore_ascii_case("yes"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_profile(profile: &AgentProfile) {
|
pub fn print_profile(profile: &BankProfileResponse) {
|
||||||
print_section_header(&format!("Agent Profile: {}", profile.agent_id));
|
print_section_header(&format!("Bank Profile: {}", profile.bank_id));
|
||||||
|
|
||||||
// Print name
|
// Print name
|
||||||
println!("{} {}", "Name:".bright_cyan().bold(), profile.name.bright_white());
|
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 {
|
for (name, value, emoji, color) in &traits {
|
||||||
let bar_length = 40;
|
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 empty = bar_length - filled;
|
||||||
|
|
||||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
||||||
|
|
@ -224,7 +209,7 @@ pub fn print_profile(profile: &AgentProfile) {
|
||||||
println!("{}", "Bias Strength:".bright_yellow());
|
println!("{}", "Bias Strength:".bright_yellow());
|
||||||
let bias = profile.personality.bias_strength;
|
let bias = profile.personality.bias_strength;
|
||||||
let bar_length = 40;
|
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 empty = bar_length - filled;
|
||||||
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
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
|
hindsight_client_api/__init__.py
|
||||||
memora_client_api/api/__init__.py
|
hindsight_client_api/api/__init__.py
|
||||||
memora_client_api/api/agent_management_api.py
|
hindsight_client_api/api/default_api.py
|
||||||
memora_client_api/api/documents_api.py
|
hindsight_client_api/api_client.py
|
||||||
memora_client_api/api/memory_operations_api.py
|
hindsight_client_api/api_response.py
|
||||||
memora_client_api/api/reasoning_api.py
|
hindsight_client_api/configuration.py
|
||||||
memora_client_api/api/visualization_api.py
|
hindsight_client_api/docs/AddBackgroundRequest.md
|
||||||
memora_client_api/api_client.py
|
hindsight_client_api/docs/BackgroundResponse.md
|
||||||
memora_client_api/api_response.py
|
hindsight_client_api/docs/BankListItem.md
|
||||||
memora_client_api/configuration.py
|
hindsight_client_api/docs/BankListResponse.md
|
||||||
memora_client_api/docs/AddBackgroundRequest.md
|
hindsight_client_api/docs/BankProfileResponse.md
|
||||||
memora_client_api/docs/AgentListItem.md
|
hindsight_client_api/docs/Budget.md
|
||||||
memora_client_api/docs/AgentListResponse.md
|
hindsight_client_api/docs/CreateBankRequest.md
|
||||||
memora_client_api/docs/AgentManagementApi.md
|
hindsight_client_api/docs/DefaultApi.md
|
||||||
memora_client_api/docs/AgentProfileResponse.md
|
hindsight_client_api/docs/DeleteResponse.md
|
||||||
memora_client_api/docs/BackgroundResponse.md
|
hindsight_client_api/docs/DocumentResponse.md
|
||||||
memora_client_api/docs/BatchPutAsyncResponse.md
|
hindsight_client_api/docs/EntityDetailResponse.md
|
||||||
memora_client_api/docs/BatchPutRequest.md
|
hindsight_client_api/docs/EntityIncludeOptions.md
|
||||||
memora_client_api/docs/BatchPutResponse.md
|
hindsight_client_api/docs/EntityListItem.md
|
||||||
memora_client_api/docs/CreateAgentRequest.md
|
hindsight_client_api/docs/EntityListResponse.md
|
||||||
memora_client_api/docs/DeleteResponse.md
|
hindsight_client_api/docs/EntityObservationResponse.md
|
||||||
memora_client_api/docs/DocumentResponse.md
|
hindsight_client_api/docs/EntityStateResponse.md
|
||||||
memora_client_api/docs/DocumentsApi.md
|
hindsight_client_api/docs/GraphDataResponse.md
|
||||||
memora_client_api/docs/GraphDataResponse.md
|
hindsight_client_api/docs/HTTPValidationError.md
|
||||||
memora_client_api/docs/HTTPValidationError.md
|
hindsight_client_api/docs/IncludeOptions.md
|
||||||
memora_client_api/docs/ListDocumentsResponse.md
|
hindsight_client_api/docs/ListDocumentsResponse.md
|
||||||
memora_client_api/docs/ListMemoryUnitsResponse.md
|
hindsight_client_api/docs/ListMemoryUnitsResponse.md
|
||||||
memora_client_api/docs/MemoryItem.md
|
hindsight_client_api/docs/MemoryItem.md
|
||||||
memora_client_api/docs/MemoryOperationsApi.md
|
hindsight_client_api/docs/MetadataFilter.md
|
||||||
memora_client_api/docs/PersonalityTraits.md
|
hindsight_client_api/docs/PersonalityTraits.md
|
||||||
memora_client_api/docs/ReasoningApi.md
|
hindsight_client_api/docs/RecallRequest.md
|
||||||
memora_client_api/docs/SearchRequest.md
|
hindsight_client_api/docs/RecallResponse.md
|
||||||
memora_client_api/docs/SearchResponse.md
|
hindsight_client_api/docs/RecallResult.md
|
||||||
memora_client_api/docs/SearchResult.md
|
hindsight_client_api/docs/ReflectFact.md
|
||||||
memora_client_api/docs/ThinkFact.md
|
hindsight_client_api/docs/ReflectIncludeOptions.md
|
||||||
memora_client_api/docs/ThinkRequest.md
|
hindsight_client_api/docs/ReflectRequest.md
|
||||||
memora_client_api/docs/ThinkResponse.md
|
hindsight_client_api/docs/ReflectResponse.md
|
||||||
memora_client_api/docs/UpdatePersonalityRequest.md
|
hindsight_client_api/docs/RetainRequest.md
|
||||||
memora_client_api/docs/ValidationError.md
|
hindsight_client_api/docs/RetainResponse.md
|
||||||
memora_client_api/docs/ValidationErrorLocInner.md
|
hindsight_client_api/docs/UpdatePersonalityRequest.md
|
||||||
memora_client_api/docs/VisualizationApi.md
|
hindsight_client_api/docs/ValidationError.md
|
||||||
memora_client_api/exceptions.py
|
hindsight_client_api/docs/ValidationErrorLocInner.md
|
||||||
memora_client_api/models/__init__.py
|
hindsight_client_api/exceptions.py
|
||||||
memora_client_api/models/add_background_request.py
|
hindsight_client_api/models/__init__.py
|
||||||
memora_client_api/models/agent_list_item.py
|
hindsight_client_api/models/add_background_request.py
|
||||||
memora_client_api/models/agent_list_response.py
|
hindsight_client_api/models/background_response.py
|
||||||
memora_client_api/models/agent_profile_response.py
|
hindsight_client_api/models/bank_list_item.py
|
||||||
memora_client_api/models/background_response.py
|
hindsight_client_api/models/bank_list_response.py
|
||||||
memora_client_api/models/batch_put_async_response.py
|
hindsight_client_api/models/bank_profile_response.py
|
||||||
memora_client_api/models/batch_put_request.py
|
hindsight_client_api/models/budget.py
|
||||||
memora_client_api/models/batch_put_response.py
|
hindsight_client_api/models/create_bank_request.py
|
||||||
memora_client_api/models/create_agent_request.py
|
hindsight_client_api/models/delete_response.py
|
||||||
memora_client_api/models/delete_response.py
|
hindsight_client_api/models/document_response.py
|
||||||
memora_client_api/models/document_response.py
|
hindsight_client_api/models/entity_detail_response.py
|
||||||
memora_client_api/models/graph_data_response.py
|
hindsight_client_api/models/entity_include_options.py
|
||||||
memora_client_api/models/http_validation_error.py
|
hindsight_client_api/models/entity_list_item.py
|
||||||
memora_client_api/models/list_documents_response.py
|
hindsight_client_api/models/entity_list_response.py
|
||||||
memora_client_api/models/list_memory_units_response.py
|
hindsight_client_api/models/entity_observation_response.py
|
||||||
memora_client_api/models/memory_item.py
|
hindsight_client_api/models/entity_state_response.py
|
||||||
memora_client_api/models/personality_traits.py
|
hindsight_client_api/models/graph_data_response.py
|
||||||
memora_client_api/models/search_request.py
|
hindsight_client_api/models/http_validation_error.py
|
||||||
memora_client_api/models/search_response.py
|
hindsight_client_api/models/include_options.py
|
||||||
memora_client_api/models/search_result.py
|
hindsight_client_api/models/list_documents_response.py
|
||||||
memora_client_api/models/think_fact.py
|
hindsight_client_api/models/list_memory_units_response.py
|
||||||
memora_client_api/models/think_request.py
|
hindsight_client_api/models/memory_item.py
|
||||||
memora_client_api/models/think_response.py
|
hindsight_client_api/models/metadata_filter.py
|
||||||
memora_client_api/models/update_personality_request.py
|
hindsight_client_api/models/personality_traits.py
|
||||||
memora_client_api/models/validation_error.py
|
hindsight_client_api/models/recall_request.py
|
||||||
memora_client_api/models/validation_error_loc_inner.py
|
hindsight_client_api/models/recall_response.py
|
||||||
memora_client_api/rest.py
|
hindsight_client_api/models/recall_result.py
|
||||||
memora_client_api/test/__init__.py
|
hindsight_client_api/models/reflect_fact.py
|
||||||
memora_client_api/test/test_add_background_request.py
|
hindsight_client_api/models/reflect_include_options.py
|
||||||
memora_client_api/test/test_agent_list_item.py
|
hindsight_client_api/models/reflect_request.py
|
||||||
memora_client_api/test/test_agent_list_response.py
|
hindsight_client_api/models/reflect_response.py
|
||||||
memora_client_api/test/test_agent_management_api.py
|
hindsight_client_api/models/retain_request.py
|
||||||
memora_client_api/test/test_agent_profile_response.py
|
hindsight_client_api/models/retain_response.py
|
||||||
memora_client_api/test/test_background_response.py
|
hindsight_client_api/models/update_personality_request.py
|
||||||
memora_client_api/test/test_batch_put_async_response.py
|
hindsight_client_api/models/validation_error.py
|
||||||
memora_client_api/test/test_batch_put_request.py
|
hindsight_client_api/models/validation_error_loc_inner.py
|
||||||
memora_client_api/test/test_batch_put_response.py
|
hindsight_client_api/rest.py
|
||||||
memora_client_api/test/test_create_agent_request.py
|
hindsight_client_api/test/__init__.py
|
||||||
memora_client_api/test/test_delete_response.py
|
hindsight_client_api/test/test_add_background_request.py
|
||||||
memora_client_api/test/test_document_response.py
|
hindsight_client_api/test/test_background_response.py
|
||||||
memora_client_api/test/test_documents_api.py
|
hindsight_client_api/test/test_bank_list_item.py
|
||||||
memora_client_api/test/test_graph_data_response.py
|
hindsight_client_api/test/test_bank_list_response.py
|
||||||
memora_client_api/test/test_http_validation_error.py
|
hindsight_client_api/test/test_bank_profile_response.py
|
||||||
memora_client_api/test/test_list_documents_response.py
|
hindsight_client_api/test/test_budget.py
|
||||||
memora_client_api/test/test_list_memory_units_response.py
|
hindsight_client_api/test/test_create_bank_request.py
|
||||||
memora_client_api/test/test_memory_item.py
|
hindsight_client_api/test/test_default_api.py
|
||||||
memora_client_api/test/test_memory_operations_api.py
|
hindsight_client_api/test/test_delete_response.py
|
||||||
memora_client_api/test/test_personality_traits.py
|
hindsight_client_api/test/test_document_response.py
|
||||||
memora_client_api/test/test_reasoning_api.py
|
hindsight_client_api/test/test_entity_detail_response.py
|
||||||
memora_client_api/test/test_search_request.py
|
hindsight_client_api/test/test_entity_include_options.py
|
||||||
memora_client_api/test/test_search_response.py
|
hindsight_client_api/test/test_entity_list_item.py
|
||||||
memora_client_api/test/test_search_result.py
|
hindsight_client_api/test/test_entity_list_response.py
|
||||||
memora_client_api/test/test_think_fact.py
|
hindsight_client_api/test/test_entity_observation_response.py
|
||||||
memora_client_api/test/test_think_request.py
|
hindsight_client_api/test/test_entity_state_response.py
|
||||||
memora_client_api/test/test_think_response.py
|
hindsight_client_api/test/test_graph_data_response.py
|
||||||
memora_client_api/test/test_update_personality_request.py
|
hindsight_client_api/test/test_http_validation_error.py
|
||||||
memora_client_api/test/test_validation_error.py
|
hindsight_client_api/test/test_include_options.py
|
||||||
memora_client_api/test/test_validation_error_loc_inner.py
|
hindsight_client_api/test/test_list_documents_response.py
|
||||||
memora_client_api/test/test_visualization_api.py
|
hindsight_client_api/test/test_list_memory_units_response.py
|
||||||
memora_client_api_README.md
|
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
|
from datetime import datetime
|
||||||
|
|
||||||
import hindsight_client_api
|
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 (
|
from hindsight_client_api.models import (
|
||||||
search_request,
|
recall_request,
|
||||||
batch_put_request,
|
retain_request,
|
||||||
memory_item,
|
memory_item,
|
||||||
think_request,
|
reflect_request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,13 +41,13 @@ class Hindsight:
|
||||||
client = Hindsight(base_url="http://localhost:8888")
|
client = Hindsight(base_url="http://localhost:8888")
|
||||||
|
|
||||||
# Store a memory
|
# Store a memory
|
||||||
client.put(agent_id="alice", content="Alice loves AI")
|
client.retain(bank_id="alice", content="Alice loves AI")
|
||||||
|
|
||||||
# Search memories
|
# Recall memories
|
||||||
results = client.search(agent_id="alice", query="What does Alice like?")
|
results = client.recall(bank_id="alice", query="What does Alice like?")
|
||||||
|
|
||||||
# Generate contextual answer
|
# 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)
|
config = hindsight_client_api.Configuration(host=base_url)
|
||||||
self._api_client = hindsight_client_api.ApiClient(config)
|
self._api_client = hindsight_client_api.ApiClient(config)
|
||||||
self._memory_api = memory_operations_api.MemoryOperationsApi(self._api_client)
|
self._api = default_api.DefaultApi(self._api_client)
|
||||||
self._reasoning_api = reasoning_api.ReasoningApi(self._api_client)
|
|
||||||
self._agent_api = agent_management_api.AgentManagementApi(self._api_client)
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""Context manager entry."""
|
"""Context manager entry."""
|
||||||
|
|
@ -80,46 +78,50 @@ class Hindsight:
|
||||||
|
|
||||||
# Simplified methods for main operations
|
# Simplified methods for main operations
|
||||||
|
|
||||||
def put(
|
def retain(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
content: str,
|
content: str,
|
||||||
event_date: Optional[datetime] = None,
|
timestamp: Optional[datetime] = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
document_id: Optional[str] = None,
|
document_id: Optional[str] = None,
|
||||||
|
metadata: Optional[Dict[str, str]] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Store a single memory (simplified interface).
|
Store a single memory (simplified interface).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
content: Memory content
|
content: Memory content
|
||||||
event_date: Optional event timestamp
|
timestamp: Optional event timestamp
|
||||||
context: Optional context description
|
context: Optional context description
|
||||||
document_id: Optional document ID for grouping
|
document_id: Optional document ID for grouping
|
||||||
|
metadata: Optional user-defined metadata
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response with success status
|
Response with success status
|
||||||
"""
|
"""
|
||||||
return self.put_batch(
|
return self.retain_batch(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
items=[{"content": content, "event_date": event_date, "context": context}],
|
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||||
document_id=document_id,
|
document_id=document_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def put_batch(
|
def retain_batch(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
items: List[Dict[str, Any]],
|
items: List[Dict[str, Any]],
|
||||||
document_id: Optional[str] = None,
|
document_id: Optional[str] = None,
|
||||||
|
async_: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Store multiple memories in batch.
|
Store multiple memories in batch.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
items: List of memory items with 'content' and optional 'event_date', 'context'
|
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata'
|
||||||
document_id: Optional document ID for grouping memories
|
document_id: Optional document ID for grouping memories
|
||||||
|
async_: If True, process asynchronously in background (default: False)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response with success status and item count
|
Response with success status and item count
|
||||||
|
|
@ -127,172 +129,193 @@ class Hindsight:
|
||||||
memory_items = [
|
memory_items = [
|
||||||
memory_item.MemoryItem(
|
memory_item.MemoryItem(
|
||||||
content=item["content"],
|
content=item["content"],
|
||||||
event_date=item.get("event_date"),
|
timestamp=item.get("timestamp"),
|
||||||
context=item.get("context"),
|
context=item.get("context"),
|
||||||
|
metadata=item.get("metadata"),
|
||||||
)
|
)
|
||||||
for item in items
|
for item in items
|
||||||
]
|
]
|
||||||
|
|
||||||
request_obj = batch_put_request.BatchPutRequest(
|
request_obj = retain_request.RetainRequest(
|
||||||
items=memory_items,
|
items=memory_items,
|
||||||
document_id=document_id,
|
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
def search(
|
def recall(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
query: str,
|
query: str,
|
||||||
fact_type: Optional[List[str]] = None,
|
types: Optional[List[str]] = None,
|
||||||
max_tokens: int = 4096,
|
max_tokens: int = 4096,
|
||||||
thinking_budget: int = 100,
|
budget: str = "mid",
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Search memories using semantic similarity.
|
Recall memories using semantic similarity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
query: Search query
|
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)
|
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:
|
Returns:
|
||||||
List of search results
|
List of recall results
|
||||||
"""
|
"""
|
||||||
request_obj = search_request.SearchRequest(
|
request_obj = recall_request.RecallRequest(
|
||||||
query=query,
|
query=query,
|
||||||
fact_type=fact_type,
|
types=types,
|
||||||
thinking_budget=thinking_budget,
|
budget=budget,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
trace=False,
|
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'):
|
if hasattr(response, 'results'):
|
||||||
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def think(
|
def reflect(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
query: str,
|
query: str,
|
||||||
thinking_budget: int = 50,
|
budget: str = "low",
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Generate a contextual answer based on agent identity and memories.
|
Generate a contextual answer based on bank identity and memories.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
query: The question or prompt
|
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
|
context: Optional additional context
|
||||||
|
|
||||||
Returns:
|
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,
|
query=query,
|
||||||
thinking_budget=thinking_budget,
|
budget=budget,
|
||||||
context=context,
|
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
# Full-featured methods (expose more options)
|
# Full-featured methods (expose more options)
|
||||||
|
|
||||||
def search_memories(
|
def recall_memories(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
query: str,
|
query: str,
|
||||||
fact_type: Optional[List[str]] = None,
|
types: Optional[List[str]] = None,
|
||||||
thinking_budget: int = 100,
|
budget: str = "mid",
|
||||||
max_tokens: int = 4096,
|
max_tokens: int = 4096,
|
||||||
trace: bool = False,
|
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]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Search memories with all options (full-featured).
|
Recall memories with all options (full-featured).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
query: Search query
|
query: Search query
|
||||||
fact_type: Optional list of fact types to filter
|
types: Optional list of fact types to filter (world, agent, opinion, observation)
|
||||||
thinking_budget: Token budget for thinking
|
budget: Budget level - "low", "mid", or "high"
|
||||||
max_tokens: Maximum tokens in results
|
max_tokens: Maximum tokens in results
|
||||||
trace: Enable trace output
|
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:
|
Returns:
|
||||||
Full search response with results and optional trace
|
Full recall response with results, optional entities, and optional trace
|
||||||
"""
|
"""
|
||||||
request_obj = search_request.SearchRequest(
|
from hindsight_client_api.models import include_options, entity_include_options
|
||||||
query=query,
|
|
||||||
fact_type=fact_type,
|
include_opts = include_options.IncludeOptions(
|
||||||
thinking_budget=thinking_budget,
|
entities=entity_include_options.EntityIncludeOptions(max_tokens=max_entity_tokens) if include_entities else None
|
||||||
max_tokens=max_tokens,
|
|
||||||
trace=trace,
|
|
||||||
question_date=question_date,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
def list_memories(
|
def list_memories(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
fact_type: Optional[str] = None,
|
type: Optional[str] = None,
|
||||||
search_query: Optional[str] = None,
|
search_query: Optional[str] = None,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""List memory units with pagination."""
|
"""List memory units with pagination."""
|
||||||
response = _run_async(self._memory_api.list_memories(
|
response = _run_async(self._api.list_memories(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
fact_type=fact_type,
|
type=type,
|
||||||
q=search_query,
|
q=search_query,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
))
|
))
|
||||||
return response.to_dict() if hasattr(response, 'to_dict') else response
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
def create_agent(
|
def create_bank(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
background: Optional[str] = None,
|
background: Optional[str] = None,
|
||||||
|
personality: Optional[Dict[str, float]] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Create or update an agent."""
|
"""Create or update a memory bank."""
|
||||||
from hindsight_client_api.models import create_agent_request
|
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,
|
name=name,
|
||||||
background=background,
|
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
# Async methods (native async, no _run_async wrapper)
|
# Async methods (native async, no _run_async wrapper)
|
||||||
|
|
||||||
async def aput_batch(
|
async def aretain_batch(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
items: List[Dict[str, Any]],
|
items: List[Dict[str, Any]],
|
||||||
document_id: Optional[str] = None,
|
document_id: Optional[str] = None,
|
||||||
|
async_: bool = False,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Store multiple memories in batch (async).
|
Store multiple memories in batch (async).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
items: List of memory items with 'content' and optional 'event_date', 'context'
|
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata'
|
||||||
document_id: Optional document ID for grouping memories
|
document_id: Optional document ID for grouping memories
|
||||||
|
async_: If True, process asynchronously in background (default: False)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response with success status and item count
|
Response with success status and item count
|
||||||
|
|
@ -300,110 +323,110 @@ class Hindsight:
|
||||||
memory_items = [
|
memory_items = [
|
||||||
memory_item.MemoryItem(
|
memory_item.MemoryItem(
|
||||||
content=item["content"],
|
content=item["content"],
|
||||||
event_date=item.get("event_date"),
|
timestamp=item.get("timestamp"),
|
||||||
context=item.get("context"),
|
context=item.get("context"),
|
||||||
|
metadata=item.get("metadata"),
|
||||||
)
|
)
|
||||||
for item in items
|
for item in items
|
||||||
]
|
]
|
||||||
|
|
||||||
request_obj = batch_put_request.BatchPutRequest(
|
request_obj = retain_request.RetainRequest(
|
||||||
items=memory_items,
|
items=memory_items,
|
||||||
document_id=document_id,
|
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
async def aput(
|
async def aretain(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
content: str,
|
content: str,
|
||||||
event_date: Optional[datetime] = None,
|
timestamp: Optional[datetime] = None,
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
document_id: Optional[str] = None,
|
document_id: Optional[str] = None,
|
||||||
|
metadata: Optional[Dict[str, str]] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Store a single memory (async).
|
Store a single memory (async).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
content: Memory content
|
content: Memory content
|
||||||
event_date: Optional event timestamp
|
timestamp: Optional event timestamp
|
||||||
context: Optional context description
|
context: Optional context description
|
||||||
document_id: Optional document ID for grouping
|
document_id: Optional document ID for grouping
|
||||||
|
metadata: Optional user-defined metadata
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response with success status
|
Response with success status
|
||||||
"""
|
"""
|
||||||
return await self.aput_batch(
|
return await self.aretain_batch(
|
||||||
agent_id=agent_id,
|
bank_id=bank_id,
|
||||||
items=[{"content": content, "event_date": event_date, "context": context}],
|
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata}],
|
||||||
document_id=document_id,
|
document_id=document_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def asearch(
|
async def arecall(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
query: str,
|
query: str,
|
||||||
fact_type: Optional[List[str]] = None,
|
types: Optional[List[str]] = None,
|
||||||
max_tokens: int = 4096,
|
max_tokens: int = 4096,
|
||||||
thinking_budget: int = 100,
|
budget: str = "mid",
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Search memories using semantic similarity (async).
|
Recall memories using semantic similarity (async).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
query: Search query
|
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)
|
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:
|
Returns:
|
||||||
List of search results
|
List of recall results
|
||||||
"""
|
"""
|
||||||
request_obj = search_request.SearchRequest(
|
request_obj = recall_request.RecallRequest(
|
||||||
query=query,
|
query=query,
|
||||||
fact_type=fact_type,
|
types=types,
|
||||||
thinking_budget=thinking_budget,
|
budget=budget,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
trace=False,
|
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'):
|
if hasattr(response, 'results'):
|
||||||
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
return [r.to_dict() if hasattr(r, 'to_dict') else r for r in response.results]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def athink(
|
async def areflect(
|
||||||
self,
|
self,
|
||||||
agent_id: str,
|
bank_id: str,
|
||||||
query: str,
|
query: str,
|
||||||
thinking_budget: int = 50,
|
budget: str = "low",
|
||||||
context: Optional[str] = None,
|
context: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> 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:
|
Args:
|
||||||
agent_id: The agent ID
|
bank_id: The memory bank ID
|
||||||
query: The question or prompt
|
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
|
context: Optional additional context
|
||||||
|
|
||||||
Returns:
|
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,
|
query=query,
|
||||||
thinking_budget=thinking_budget,
|
budget=budget,
|
||||||
context=context,
|
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
|
return response.to_dict() if hasattr(response, 'to_dict') else response
|
||||||
|
|
||||||
|
|
||||||
# Alias for backward compatibility
|
|
||||||
HindsightClient = Hindsight
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
# flake8: noqa
|
# 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
|
The version of the OpenAPI document: 1.0.0
|
||||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||||
|
|
@ -18,11 +18,7 @@ __version__ = "0.0.7"
|
||||||
|
|
||||||
# Define package exports
|
# Define package exports
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AgentManagementApi",
|
"DefaultApi",
|
||||||
"DocumentsApi",
|
|
||||||
"MemoryOperationsApi",
|
|
||||||
"ReasoningApi",
|
|
||||||
"VisualizationApi",
|
|
||||||
"ApiResponse",
|
"ApiResponse",
|
||||||
"ApiClient",
|
"ApiClient",
|
||||||
"Configuration",
|
"Configuration",
|
||||||
|
|
@ -33,39 +29,44 @@ __all__ = [
|
||||||
"ApiAttributeError",
|
"ApiAttributeError",
|
||||||
"ApiException",
|
"ApiException",
|
||||||
"AddBackgroundRequest",
|
"AddBackgroundRequest",
|
||||||
"AgentListItem",
|
|
||||||
"AgentListResponse",
|
|
||||||
"AgentProfileResponse",
|
|
||||||
"BackgroundResponse",
|
"BackgroundResponse",
|
||||||
"BatchPutAsyncResponse",
|
"BankListItem",
|
||||||
"BatchPutRequest",
|
"BankListResponse",
|
||||||
"BatchPutResponse",
|
"BankProfileResponse",
|
||||||
"CreateAgentRequest",
|
"Budget",
|
||||||
|
"CreateBankRequest",
|
||||||
"DeleteResponse",
|
"DeleteResponse",
|
||||||
"DocumentResponse",
|
"DocumentResponse",
|
||||||
|
"EntityDetailResponse",
|
||||||
|
"EntityIncludeOptions",
|
||||||
|
"EntityListItem",
|
||||||
|
"EntityListResponse",
|
||||||
|
"EntityObservationResponse",
|
||||||
|
"EntityStateResponse",
|
||||||
"GraphDataResponse",
|
"GraphDataResponse",
|
||||||
"HTTPValidationError",
|
"HTTPValidationError",
|
||||||
|
"IncludeOptions",
|
||||||
"ListDocumentsResponse",
|
"ListDocumentsResponse",
|
||||||
"ListMemoryUnitsResponse",
|
"ListMemoryUnitsResponse",
|
||||||
"MemoryItem",
|
"MemoryItem",
|
||||||
|
"MetadataFilter",
|
||||||
"PersonalityTraits",
|
"PersonalityTraits",
|
||||||
"SearchRequest",
|
"RecallRequest",
|
||||||
"SearchResponse",
|
"RecallResponse",
|
||||||
"SearchResult",
|
"RecallResult",
|
||||||
"ThinkFact",
|
"ReflectFact",
|
||||||
"ThinkRequest",
|
"ReflectIncludeOptions",
|
||||||
"ThinkResponse",
|
"ReflectRequest",
|
||||||
|
"ReflectResponse",
|
||||||
|
"RetainRequest",
|
||||||
|
"RetainResponse",
|
||||||
"UpdatePersonalityRequest",
|
"UpdatePersonalityRequest",
|
||||||
"ValidationError",
|
"ValidationError",
|
||||||
"ValidationErrorLocInner",
|
"ValidationErrorLocInner",
|
||||||
]
|
]
|
||||||
|
|
||||||
# import apis into sdk package
|
# import apis into sdk package
|
||||||
from hindsight_client_api.api.agent_management_api import AgentManagementApi as AgentManagementApi
|
from hindsight_client_api.api.default_api import DefaultApi as DefaultApi
|
||||||
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
|
|
||||||
|
|
||||||
# import ApiClient
|
# import ApiClient
|
||||||
from hindsight_client_api.api_response import ApiResponse as ApiResponse
|
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
|
# import models into sdk package
|
||||||
from hindsight_client_api.models.add_background_request import AddBackgroundRequest as AddBackgroundRequest
|
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.background_response import BackgroundResponse as BackgroundResponse
|
||||||
from hindsight_client_api.models.batch_put_async_response import BatchPutAsyncResponse as BatchPutAsyncResponse
|
from hindsight_client_api.models.bank_list_item import BankListItem as BankListItem
|
||||||
from hindsight_client_api.models.batch_put_request import BatchPutRequest as BatchPutRequest
|
from hindsight_client_api.models.bank_list_response import BankListResponse as BankListResponse
|
||||||
from hindsight_client_api.models.batch_put_response import BatchPutResponse as BatchPutResponse
|
from hindsight_client_api.models.bank_profile_response import BankProfileResponse as BankProfileResponse
|
||||||
from hindsight_client_api.models.create_agent_request import CreateAgentRequest as CreateAgentRequest
|
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.delete_response import DeleteResponse as DeleteResponse
|
||||||
from hindsight_client_api.models.document_response import DocumentResponse as DocumentResponse
|
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.graph_data_response import GraphDataResponse as GraphDataResponse
|
||||||
from hindsight_client_api.models.http_validation_error import HTTPValidationError as HTTPValidationError
|
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_documents_response import ListDocumentsResponse as ListDocumentsResponse
|
||||||
from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse as ListMemoryUnitsResponse
|
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.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.personality_traits import PersonalityTraits as PersonalityTraits
|
||||||
from hindsight_client_api.models.search_request import SearchRequest as SearchRequest
|
from hindsight_client_api.models.recall_request import RecallRequest as RecallRequest
|
||||||
from hindsight_client_api.models.search_response import SearchResponse as SearchResponse
|
from hindsight_client_api.models.recall_response import RecallResponse as RecallResponse
|
||||||
from hindsight_client_api.models.search_result import SearchResult as SearchResult
|
from hindsight_client_api.models.recall_result import RecallResult as RecallResult
|
||||||
from hindsight_client_api.models.think_fact import ThinkFact as ThinkFact
|
from hindsight_client_api.models.reflect_fact import ReflectFact as ReflectFact
|
||||||
from hindsight_client_api.models.think_request import ThinkRequest as ThinkRequest
|
from hindsight_client_api.models.reflect_include_options import ReflectIncludeOptions as ReflectIncludeOptions
|
||||||
from hindsight_client_api.models.think_response import ThinkResponse as ThinkResponse
|
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.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 import ValidationError as ValidationError
|
||||||
from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner as ValidationErrorLocInner
|
from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner as ValidationErrorLocInner
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
# import apis into api package
|
# import apis into api package
|
||||||
from hindsight_client_api.api.agent_management_api import AgentManagementApi
|
from hindsight_client_api.api.default_api import DefaultApi
|
||||||
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
|
|
||||||
|
|
||||||
|
|
|
||||||
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
|
# 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
|
The version of the OpenAPI document: 1.0.0
|
||||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# coding: utf-8
|
# 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
|
The version of the OpenAPI document: 1.0.0
|
||||||
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
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
|
## Properties
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**agent_id** | **str** | |
|
**bank_id** | **str** | |
|
||||||
**name** | **str** | |
|
**name** | **str** | |
|
||||||
**personality** | [**PersonalityTraits**](PersonalityTraits.md) | |
|
**personality** | [**PersonalityTraits**](PersonalityTraits.md) | |
|
||||||
**background** | **str** | |
|
**background** | **str** | |
|
||||||
|
|
@ -16,19 +16,19 @@ Name | Type | Description | Notes
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```python
|
```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
|
# TODO update the JSON string below
|
||||||
json = "{}"
|
json = "{}"
|
||||||
# create an instance of AgentListItem from a JSON string
|
# create an instance of BankListItem from a JSON string
|
||||||
agent_list_item_instance = AgentListItem.from_json(json)
|
bank_list_item_instance = BankListItem.from_json(json)
|
||||||
# print the JSON string representation of the object
|
# print the JSON string representation of the object
|
||||||
print(AgentListItem.to_json())
|
print(BankListItem.to_json())
|
||||||
|
|
||||||
# convert the object into a dict
|
# convert the object into a dict
|
||||||
agent_list_item_dict = agent_list_item_instance.to_dict()
|
bank_list_item_dict = bank_list_item_instance.to_dict()
|
||||||
# create an instance of AgentListItem from a dict
|
# create an instance of BankListItem from a dict
|
||||||
agent_list_item_from_dict = AgentListItem.from_dict(agent_list_item_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)
|
[[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
|
## Properties
|
||||||
|
|
||||||
|
|
@ -13,19 +13,19 @@ Name | Type | Description | Notes
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```python
|
```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
|
# TODO update the JSON string below
|
||||||
json = "{}"
|
json = "{}"
|
||||||
# create an instance of CreateAgentRequest from a JSON string
|
# create an instance of CreateBankRequest from a JSON string
|
||||||
create_agent_request_instance = CreateAgentRequest.from_json(json)
|
create_bank_request_instance = CreateBankRequest.from_json(json)
|
||||||
# print the JSON string representation of the object
|
# print the JSON string representation of the object
|
||||||
print(CreateAgentRequest.to_json())
|
print(CreateBankRequest.to_json())
|
||||||
|
|
||||||
# convert the object into a dict
|
# convert the object into a dict
|
||||||
create_agent_request_dict = create_agent_request_instance.to_dict()
|
create_bank_request_dict = create_bank_request_instance.to_dict()
|
||||||
# create an instance of CreateAgentRequest from a dict
|
# create an instance of CreateBankRequest from a dict
|
||||||
create_agent_request_from_dict = CreateAgentRequest.from_dict(create_agent_request_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)
|
[[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
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**success** | **bool** | |
|
**success** | **bool** | |
|
||||||
**message** | **str** | |
|
|
||||||
|
|
||||||
## Example
|
## 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
|
# MemoryItem
|
||||||
|
|
||||||
Single memory item for batch put.
|
Single memory item for retain.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**content** | **str** | |
|
**content** | **str** | |
|
||||||
**event_date** | **datetime** | | [optional]
|
**timestamp** | **datetime** | | [optional]
|
||||||
**context** | **str** | | [optional]
|
**context** | **str** | | [optional]
|
||||||
|
**metadata** | **Dict[str, str]** | | [optional]
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue