hindsight-api 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. hindsight_api/__init__.py +10 -9
  2. hindsight_api/alembic/env.py +5 -8
  3. hindsight_api/alembic/versions/5a366d414dce_initial_schema.py +266 -180
  4. hindsight_api/alembic/versions/b7c4d8e9f1a2_add_chunks_table.py +32 -32
  5. hindsight_api/alembic/versions/c8e5f2a3b4d1_add_retain_params_to_documents.py +11 -11
  6. hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py +7 -12
  7. hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py +23 -15
  8. hindsight_api/alembic/versions/rename_personality_to_disposition.py +30 -21
  9. hindsight_api/api/__init__.py +10 -10
  10. hindsight_api/api/http.py +575 -593
  11. hindsight_api/api/mcp.py +30 -28
  12. hindsight_api/banner.py +13 -6
  13. hindsight_api/config.py +9 -13
  14. hindsight_api/engine/__init__.py +9 -9
  15. hindsight_api/engine/cross_encoder.py +22 -21
  16. hindsight_api/engine/db_utils.py +5 -4
  17. hindsight_api/engine/embeddings.py +22 -21
  18. hindsight_api/engine/entity_resolver.py +81 -75
  19. hindsight_api/engine/llm_wrapper.py +61 -79
  20. hindsight_api/engine/memory_engine.py +603 -625
  21. hindsight_api/engine/query_analyzer.py +100 -97
  22. hindsight_api/engine/response_models.py +105 -106
  23. hindsight_api/engine/retain/__init__.py +9 -16
  24. hindsight_api/engine/retain/bank_utils.py +34 -58
  25. hindsight_api/engine/retain/chunk_storage.py +4 -12
  26. hindsight_api/engine/retain/deduplication.py +9 -28
  27. hindsight_api/engine/retain/embedding_processing.py +4 -11
  28. hindsight_api/engine/retain/embedding_utils.py +3 -4
  29. hindsight_api/engine/retain/entity_processing.py +7 -17
  30. hindsight_api/engine/retain/fact_extraction.py +155 -165
  31. hindsight_api/engine/retain/fact_storage.py +11 -23
  32. hindsight_api/engine/retain/link_creation.py +11 -39
  33. hindsight_api/engine/retain/link_utils.py +166 -95
  34. hindsight_api/engine/retain/observation_regeneration.py +39 -52
  35. hindsight_api/engine/retain/orchestrator.py +72 -62
  36. hindsight_api/engine/retain/types.py +49 -43
  37. hindsight_api/engine/search/__init__.py +5 -5
  38. hindsight_api/engine/search/fusion.py +6 -15
  39. hindsight_api/engine/search/graph_retrieval.py +22 -23
  40. hindsight_api/engine/search/mpfp_retrieval.py +76 -92
  41. hindsight_api/engine/search/observation_utils.py +9 -16
  42. hindsight_api/engine/search/reranking.py +4 -7
  43. hindsight_api/engine/search/retrieval.py +87 -66
  44. hindsight_api/engine/search/scoring.py +5 -7
  45. hindsight_api/engine/search/temporal_extraction.py +8 -11
  46. hindsight_api/engine/search/think_utils.py +115 -39
  47. hindsight_api/engine/search/trace.py +68 -39
  48. hindsight_api/engine/search/tracer.py +44 -35
  49. hindsight_api/engine/search/types.py +20 -17
  50. hindsight_api/engine/task_backend.py +21 -26
  51. hindsight_api/engine/utils.py +25 -10
  52. hindsight_api/main.py +21 -40
  53. hindsight_api/mcp_local.py +190 -0
  54. hindsight_api/metrics.py +44 -30
  55. hindsight_api/migrations.py +10 -8
  56. hindsight_api/models.py +60 -72
  57. hindsight_api/pg0.py +22 -23
  58. hindsight_api/server.py +3 -6
  59. {hindsight_api-0.1.5.dist-info → hindsight_api-0.1.6.dist-info}/METADATA +2 -2
  60. hindsight_api-0.1.6.dist-info/RECORD +64 -0
  61. {hindsight_api-0.1.5.dist-info → hindsight_api-0.1.6.dist-info}/entry_points.txt +1 -0
  62. hindsight_api-0.1.5.dist-info/RECORD +0 -63
  63. {hindsight_api-0.1.5.dist-info → hindsight_api-0.1.6.dist-info}/WHEEL +0 -0
@@ -5,18 +5,18 @@ Revises: 5a366d414dce
5
5
  Create Date: 2025-11-28 00:00:00.000000
6
6
 
7
7
  """
8
- from typing import Sequence, Union
9
8
 
10
- from alembic import op
9
+ from collections.abc import Sequence
10
+
11
11
  import sqlalchemy as sa
12
+ from alembic import op
12
13
  from sqlalchemy.dialects import postgresql
13
14
 
14
-
15
15
  # revision identifiers, used by Alembic.
16
- revision: str = 'b7c4d8e9f1a2'
17
- down_revision: Union[str, Sequence[str], None] = '5a366d414dce'
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
16
+ revision: str = "b7c4d8e9f1a2"
17
+ down_revision: str | Sequence[str] | None = "5a366d414dce"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
20
 
21
21
 
22
22
  def upgrade() -> None:
@@ -24,47 +24,47 @@ def upgrade() -> None:
24
24
 
25
25
  # Create chunks table with single text PK (bank_id_document_id_chunk_index)
26
26
  op.create_table(
27
- 'chunks',
28
- sa.Column('chunk_id', sa.Text(), nullable=False),
29
- sa.Column('document_id', sa.Text(), nullable=False),
30
- sa.Column('bank_id', sa.Text(), nullable=False),
31
- sa.Column('chunk_index', sa.Integer(), nullable=False),
32
- sa.Column('chunk_text', sa.Text(), nullable=False),
33
- sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
34
- sa.ForeignKeyConstraint(['document_id', 'bank_id'], ['documents.id', 'documents.bank_id'], name='chunks_document_fkey', ondelete='CASCADE'),
35
- sa.PrimaryKeyConstraint('chunk_id', name=op.f('pk_chunks'))
27
+ "chunks",
28
+ sa.Column("chunk_id", sa.Text(), nullable=False),
29
+ sa.Column("document_id", sa.Text(), nullable=False),
30
+ sa.Column("bank_id", sa.Text(), nullable=False),
31
+ sa.Column("chunk_index", sa.Integer(), nullable=False),
32
+ sa.Column("chunk_text", sa.Text(), nullable=False),
33
+ sa.Column("created_at", postgresql.TIMESTAMP(timezone=True), server_default=sa.text("now()"), nullable=False),
34
+ sa.ForeignKeyConstraint(
35
+ ["document_id", "bank_id"],
36
+ ["documents.id", "documents.bank_id"],
37
+ name="chunks_document_fkey",
38
+ ondelete="CASCADE",
39
+ ),
40
+ sa.PrimaryKeyConstraint("chunk_id", name=op.f("pk_chunks")),
36
41
  )
37
42
 
38
43
  # Add indexes for efficient queries
39
- op.create_index('idx_chunks_document_id', 'chunks', ['document_id'])
40
- op.create_index('idx_chunks_bank_id', 'chunks', ['bank_id'])
44
+ op.create_index("idx_chunks_document_id", "chunks", ["document_id"])
45
+ op.create_index("idx_chunks_bank_id", "chunks", ["bank_id"])
41
46
 
42
47
  # Add chunk_id column to memory_units (nullable, as existing records won't have chunks)
43
- op.add_column('memory_units', sa.Column('chunk_id', sa.Text(), nullable=True))
48
+ op.add_column("memory_units", sa.Column("chunk_id", sa.Text(), nullable=True))
44
49
 
45
50
  # Add foreign key constraint to chunks table
46
51
  op.create_foreign_key(
47
- 'memory_units_chunk_fkey',
48
- 'memory_units',
49
- 'chunks',
50
- ['chunk_id'],
51
- ['chunk_id'],
52
- ondelete='SET NULL'
52
+ "memory_units_chunk_fkey", "memory_units", "chunks", ["chunk_id"], ["chunk_id"], ondelete="SET NULL"
53
53
  )
54
54
 
55
55
  # Add index on chunk_id for efficient lookups
56
- op.create_index('idx_memory_units_chunk_id', 'memory_units', ['chunk_id'])
56
+ op.create_index("idx_memory_units_chunk_id", "memory_units", ["chunk_id"])
57
57
 
58
58
 
59
59
  def downgrade() -> None:
60
60
  """Remove chunks table and chunk_id from memory_units."""
61
61
 
62
62
  # Drop index and foreign key from memory_units
63
- op.drop_index('idx_memory_units_chunk_id', table_name='memory_units')
64
- op.drop_constraint('memory_units_chunk_fkey', 'memory_units', type_='foreignkey')
65
- op.drop_column('memory_units', 'chunk_id')
63
+ op.drop_index("idx_memory_units_chunk_id", table_name="memory_units")
64
+ op.drop_constraint("memory_units_chunk_fkey", "memory_units", type_="foreignkey")
65
+ op.drop_column("memory_units", "chunk_id")
66
66
 
67
67
  # Drop chunks table indexes and table
68
- op.drop_index('idx_chunks_bank_id', table_name='chunks')
69
- op.drop_index('idx_chunks_document_id', table_name='chunks')
70
- op.drop_table('chunks')
68
+ op.drop_index("idx_chunks_bank_id", table_name="chunks")
69
+ op.drop_index("idx_chunks_document_id", table_name="chunks")
70
+ op.drop_table("chunks")
@@ -5,35 +5,35 @@ Revises: b7c4d8e9f1a2
5
5
  Create Date: 2025-12-02 00:00:00.000000
6
6
 
7
7
  """
8
- from typing import Sequence, Union
9
8
 
10
- from alembic import op
9
+ from collections.abc import Sequence
10
+
11
11
  import sqlalchemy as sa
12
+ from alembic import op
12
13
  from sqlalchemy.dialects import postgresql
13
14
 
14
-
15
15
  # revision identifiers, used by Alembic.
16
- revision: str = 'c8e5f2a3b4d1'
17
- down_revision: Union[str, Sequence[str], None] = 'b7c4d8e9f1a2'
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
16
+ revision: str = "c8e5f2a3b4d1"
17
+ down_revision: str | Sequence[str] | None = "b7c4d8e9f1a2"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
20
 
21
21
 
22
22
  def upgrade() -> None:
23
23
  """Add retain_params JSONB column to documents table."""
24
24
 
25
25
  # Add retain_params column to store parameters passed during retain
26
- op.add_column('documents', sa.Column('retain_params', postgresql.JSONB(), nullable=True))
26
+ op.add_column("documents", sa.Column("retain_params", postgresql.JSONB(), nullable=True))
27
27
 
28
28
  # Add index for efficient queries on retain_params
29
- op.create_index('idx_documents_retain_params', 'documents', ['retain_params'], postgresql_using='gin')
29
+ op.create_index("idx_documents_retain_params", "documents", ["retain_params"], postgresql_using="gin")
30
30
 
31
31
 
32
32
  def downgrade() -> None:
33
33
  """Remove retain_params column from documents table."""
34
34
 
35
35
  # Drop index
36
- op.drop_index('idx_documents_retain_params', table_name='documents')
36
+ op.drop_index("idx_documents_retain_params", table_name="documents")
37
37
 
38
38
  # Drop column
39
- op.drop_column('documents', 'retain_params')
39
+ op.drop_column("documents", "retain_params")
@@ -5,20 +5,19 @@ Revises: c8e5f2a3b4d1
5
5
  Create Date: 2024-12-04 15:00:00.000000
6
6
 
7
7
  """
8
- from alembic import op
9
- import sqlalchemy as sa
10
8
 
9
+ from alembic import op
11
10
 
12
11
  # revision identifiers, used by Alembic.
13
- revision = 'd9f6a3b4c5e2'
14
- down_revision = 'c8e5f2a3b4d1'
12
+ revision = "d9f6a3b4c5e2"
13
+ down_revision = "c8e5f2a3b4d1"
15
14
  branch_labels = None
16
15
  depends_on = None
17
16
 
18
17
 
19
18
  def upgrade():
20
19
  # Drop old check constraint FIRST (before updating data)
21
- op.drop_constraint('memory_units_fact_type_check', 'memory_units', type_='check')
20
+ op.drop_constraint("memory_units_fact_type_check", "memory_units", type_="check")
22
21
 
23
22
  # Update existing 'bank' values to 'experience'
24
23
  op.execute("UPDATE memory_units SET fact_type = 'experience' WHERE fact_type = 'bank'")
@@ -27,22 +26,18 @@ def upgrade():
27
26
 
28
27
  # Create new check constraint with 'experience' instead of 'bank'
29
28
  op.create_check_constraint(
30
- 'memory_units_fact_type_check',
31
- 'memory_units',
32
- "fact_type IN ('world', 'experience', 'opinion', 'observation')"
29
+ "memory_units_fact_type_check", "memory_units", "fact_type IN ('world', 'experience', 'opinion', 'observation')"
33
30
  )
34
31
 
35
32
 
36
33
  def downgrade():
37
34
  # Drop new check constraint FIRST
38
- op.drop_constraint('memory_units_fact_type_check', 'memory_units', type_='check')
35
+ op.drop_constraint("memory_units_fact_type_check", "memory_units", type_="check")
39
36
 
40
37
  # Update 'experience' back to 'bank'
41
38
  op.execute("UPDATE memory_units SET fact_type = 'bank' WHERE fact_type = 'experience'")
42
39
 
43
40
  # Recreate old check constraint
44
41
  op.create_check_constraint(
45
- 'memory_units_fact_type_check',
46
- 'memory_units',
47
- "fact_type IN ('world', 'bank', 'opinion', 'observation')"
42
+ "memory_units_fact_type_check", "memory_units", "fact_type IN ('world', 'bank', 'opinion', 'observation')"
48
43
  )
@@ -8,17 +8,17 @@ Migrate disposition traits from Big Five (openness, conscientiousness, extravers
8
8
  agreeableness, neuroticism, bias_strength with 0-1 float values) to the new 3-trait
9
9
  system (skepticism, literalism, empathy with 1-5 integer values).
10
10
  """
11
- from typing import Sequence, Union
12
11
 
13
- from alembic import op
14
- import sqlalchemy as sa
12
+ from collections.abc import Sequence
15
13
 
14
+ import sqlalchemy as sa
15
+ from alembic import op
16
16
 
17
17
  # revision identifiers, used by Alembic.
18
- revision: str = 'e0a1b2c3d4e5'
19
- down_revision: Union[str, Sequence[str], None] = 'rename_personality'
20
- branch_labels: Union[str, Sequence[str], None] = None
21
- depends_on: Union[str, Sequence[str], None] = None
18
+ revision: str = "e0a1b2c3d4e5"
19
+ down_revision: str | Sequence[str] | None = "rename_personality"
20
+ branch_labels: str | Sequence[str] | None = None
21
+ depends_on: str | Sequence[str] | None = None
22
22
 
23
23
 
24
24
  def upgrade() -> None:
@@ -31,17 +31,21 @@ def upgrade() -> None:
31
31
  # - literalism: derived from conscientiousness (detail-oriented people are more literal)
32
32
  # - empathy: derived from agreeableness + inverse of neuroticism
33
33
  # Default all to 3 (neutral) for simplicity
34
- conn.execute(sa.text("""
34
+ conn.execute(
35
+ sa.text("""
35
36
  UPDATE banks
36
37
  SET disposition = '{"skepticism": 3, "literalism": 3, "empathy": 3}'::jsonb
37
38
  WHERE disposition IS NOT NULL
38
- """))
39
+ """)
40
+ )
39
41
 
40
42
  # Update the default for new banks
41
- conn.execute(sa.text("""
43
+ conn.execute(
44
+ sa.text("""
42
45
  ALTER TABLE banks
43
46
  ALTER COLUMN disposition SET DEFAULT '{"skepticism": 3, "literalism": 3, "empathy": 3}'::jsonb
44
- """))
47
+ """)
48
+ )
45
49
 
46
50
 
47
51
  def downgrade() -> None:
@@ -49,14 +53,18 @@ def downgrade() -> None:
49
53
  conn = op.get_bind()
50
54
 
51
55
  # Revert to Big Five format with default values
52
- conn.execute(sa.text("""
56
+ conn.execute(
57
+ sa.text("""
53
58
  UPDATE banks
54
59
  SET disposition = '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}'::jsonb
55
60
  WHERE disposition IS NOT NULL
56
- """))
61
+ """)
62
+ )
57
63
 
58
64
  # Update the default for new banks
59
- conn.execute(sa.text("""
65
+ conn.execute(
66
+ sa.text("""
60
67
  ALTER TABLE banks
61
68
  ALTER COLUMN disposition SET DEFAULT '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}'::jsonb
62
- """))
69
+ """)
70
+ )
@@ -5,18 +5,18 @@ Revises: d9f6a3b4c5e2
5
5
  Create Date: 2024-12-04
6
6
 
7
7
  """
8
- from typing import Sequence, Union
9
8
 
10
- from alembic import op
9
+ from collections.abc import Sequence
10
+
11
11
  import sqlalchemy as sa
12
+ from alembic import op
12
13
  from sqlalchemy.dialects import postgresql
13
14
 
14
-
15
15
  # revision identifiers, used by Alembic.
16
- revision: str = 'rename_personality'
17
- down_revision: Union[str, Sequence[str], None] = 'd9f6a3b4c5e2'
18
- branch_labels: Union[str, Sequence[str], None] = None
19
- depends_on: Union[str, Sequence[str], None] = None
16
+ revision: str = "rename_personality"
17
+ down_revision: str | Sequence[str] | None = "d9f6a3b4c5e2"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
20
 
21
21
 
22
22
  def upgrade() -> None:
@@ -24,42 +24,51 @@ def upgrade() -> None:
24
24
  conn = op.get_bind()
25
25
 
26
26
  # Check if 'personality' column exists (old database)
27
- result = conn.execute(sa.text("""
27
+ result = conn.execute(
28
+ sa.text("""
28
29
  SELECT column_name
29
30
  FROM information_schema.columns
30
31
  WHERE table_name = 'banks' AND column_name = 'personality'
31
- """))
32
+ """)
33
+ )
32
34
  has_personality = result.fetchone() is not None
33
35
 
34
36
  # Check if 'disposition' column exists (new database)
35
- result = conn.execute(sa.text("""
37
+ result = conn.execute(
38
+ sa.text("""
36
39
  SELECT column_name
37
40
  FROM information_schema.columns
38
41
  WHERE table_name = 'banks' AND column_name = 'disposition'
39
- """))
42
+ """)
43
+ )
40
44
  has_disposition = result.fetchone() is not None
41
45
 
42
46
  if has_personality and not has_disposition:
43
47
  # Old database: rename personality -> disposition
44
- op.alter_column('banks', 'personality', new_column_name='disposition')
48
+ op.alter_column("banks", "personality", new_column_name="disposition")
45
49
  elif not has_personality and not has_disposition:
46
50
  # Neither exists (shouldn't happen, but be safe): add disposition column
47
- op.add_column('banks', sa.Column(
48
- 'disposition',
49
- postgresql.JSONB(astext_type=sa.Text()),
50
- server_default=sa.text("'{}'::jsonb"),
51
- nullable=False
52
- ))
51
+ op.add_column(
52
+ "banks",
53
+ sa.Column(
54
+ "disposition",
55
+ postgresql.JSONB(astext_type=sa.Text()),
56
+ server_default=sa.text("'{}'::jsonb"),
57
+ nullable=False,
58
+ ),
59
+ )
53
60
  # else: disposition already exists, nothing to do
54
61
 
55
62
 
56
63
  def downgrade() -> None:
57
64
  """Revert disposition column back to personality."""
58
65
  conn = op.get_bind()
59
- result = conn.execute(sa.text("""
66
+ result = conn.execute(
67
+ sa.text("""
60
68
  SELECT column_name
61
69
  FROM information_schema.columns
62
70
  WHERE table_name = 'banks' AND column_name = 'disposition'
63
- """))
71
+ """)
72
+ )
64
73
  if result.fetchone():
65
- op.alter_column('banks', 'disposition', new_column_name='personality')
74
+ op.alter_column("banks", "disposition", new_column_name="personality")
@@ -3,8 +3,10 @@ Unified API module for Hindsight.
3
3
 
4
4
  Provides both HTTP REST API and MCP (Model Context Protocol) server.
5
5
  """
6
+
6
7
  import logging
7
8
  from typing import Optional
9
+
8
10
  from fastapi import FastAPI
9
11
 
10
12
  from hindsight_api import MemoryEngine
@@ -17,7 +19,7 @@ def create_app(
17
19
  http_api_enabled: bool = True,
18
20
  mcp_api_enabled: bool = False,
19
21
  mcp_mount_path: str = "/mcp",
20
- initialize_memory: bool = True
22
+ initialize_memory: bool = True,
21
23
  ) -> FastAPI:
22
24
  """
23
25
  Create and configure the unified Hindsight API application.
@@ -47,10 +49,8 @@ def create_app(
47
49
  # Import and create HTTP API if enabled
48
50
  if http_api_enabled:
49
51
  from .http import create_app as create_http_app
50
- app = create_http_app(
51
- memory=memory,
52
- initialize_memory=initialize_memory
53
- )
52
+
53
+ app = create_http_app(memory=memory, initialize_memory=initialize_memory)
54
54
  logger.info("HTTP REST API enabled")
55
55
  else:
56
56
  # Create minimal FastAPI app
@@ -77,15 +77,15 @@ def create_app(
77
77
 
78
78
  # Re-export commonly used items for backwards compatibility
79
79
  from .http import (
80
+ CreateBankRequest,
81
+ DispositionTraits,
82
+ MemoryItem,
80
83
  RecallRequest,
81
- RecallResult,
82
84
  RecallResponse,
83
- MemoryItem,
84
- RetainRequest,
85
+ RecallResult,
85
86
  ReflectRequest,
86
87
  ReflectResponse,
87
- CreateBankRequest,
88
- DispositionTraits,
88
+ RetainRequest,
89
89
  )
90
90
 
91
91
  __all__ = [