hindsight-api 0.1.13__py3-none-any.whl → 0.1.15__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.
hindsight_api/api/http.py CHANGED
@@ -689,6 +689,26 @@ class DocumentResponse(BaseModel):
689
689
  memory_unit_count: int
690
690
 
691
691
 
692
+ class DeleteDocumentResponse(BaseModel):
693
+ """Response model for delete document endpoint."""
694
+
695
+ model_config = ConfigDict(
696
+ json_schema_extra={
697
+ "example": {
698
+ "success": True,
699
+ "message": "Document 'session_1' and 5 associated memory units deleted successfully",
700
+ "document_id": "session_1",
701
+ "memory_units_deleted": 5,
702
+ }
703
+ }
704
+ )
705
+
706
+ success: bool
707
+ message: str
708
+ document_id: str
709
+ memory_units_deleted: int
710
+
711
+
692
712
  class ChunkResponse(BaseModel):
693
713
  """Response model for get chunk endpoint."""
694
714
 
@@ -725,6 +745,108 @@ class DeleteResponse(BaseModel):
725
745
  deleted_count: int | None = None
726
746
 
727
747
 
748
+ class BankStatsResponse(BaseModel):
749
+ """Response model for bank statistics endpoint."""
750
+
751
+ model_config = ConfigDict(
752
+ json_schema_extra={
753
+ "example": {
754
+ "bank_id": "user123",
755
+ "total_nodes": 150,
756
+ "total_links": 300,
757
+ "total_documents": 10,
758
+ "nodes_by_fact_type": {"fact": 100, "preference": 30, "observation": 20},
759
+ "links_by_link_type": {"temporal": 150, "semantic": 100, "entity": 50},
760
+ "links_by_fact_type": {"fact": 200, "preference": 60, "observation": 40},
761
+ "links_breakdown": {"fact": {"temporal": 100, "semantic": 60, "entity": 40}},
762
+ "pending_operations": 2,
763
+ "failed_operations": 0,
764
+ }
765
+ }
766
+ )
767
+
768
+ bank_id: str
769
+ total_nodes: int
770
+ total_links: int
771
+ total_documents: int
772
+ nodes_by_fact_type: dict[str, int]
773
+ links_by_link_type: dict[str, int]
774
+ links_by_fact_type: dict[str, int]
775
+ links_breakdown: dict[str, dict[str, int]]
776
+ pending_operations: int
777
+ failed_operations: int
778
+
779
+
780
+ class OperationResponse(BaseModel):
781
+ """Response model for a single async operation."""
782
+
783
+ model_config = ConfigDict(
784
+ json_schema_extra={
785
+ "example": {
786
+ "id": "550e8400-e29b-41d4-a716-446655440000",
787
+ "task_type": "retain",
788
+ "items_count": 5,
789
+ "document_id": "meeting-notes-2024",
790
+ "created_at": "2024-01-15T10:30:00Z",
791
+ "status": "pending",
792
+ "error_message": None,
793
+ }
794
+ }
795
+ )
796
+
797
+ id: str
798
+ task_type: str
799
+ items_count: int
800
+ document_id: str | None
801
+ created_at: str
802
+ status: str
803
+ error_message: str | None
804
+
805
+
806
+ class OperationsListResponse(BaseModel):
807
+ """Response model for list operations endpoint."""
808
+
809
+ model_config = ConfigDict(
810
+ json_schema_extra={
811
+ "example": {
812
+ "bank_id": "user123",
813
+ "operations": [
814
+ {
815
+ "id": "550e8400-e29b-41d4-a716-446655440000",
816
+ "task_type": "retain",
817
+ "items_count": 5,
818
+ "document_id": None,
819
+ "created_at": "2024-01-15T10:30:00Z",
820
+ "status": "pending",
821
+ "error_message": None,
822
+ }
823
+ ],
824
+ }
825
+ }
826
+ )
827
+
828
+ bank_id: str
829
+ operations: list[OperationResponse]
830
+
831
+
832
+ class CancelOperationResponse(BaseModel):
833
+ """Response model for cancel operation endpoint."""
834
+
835
+ model_config = ConfigDict(
836
+ json_schema_extra={
837
+ "example": {
838
+ "success": True,
839
+ "message": "Operation 550e8400-e29b-41d4-a716-446655440000 cancelled",
840
+ "operation_id": "550e8400-e29b-41d4-a716-446655440000",
841
+ }
842
+ }
843
+ )
844
+
845
+ success: bool
846
+ message: str
847
+ operation_id: str
848
+
849
+
728
850
  def create_app(
729
851
  memory: MemoryEngine,
730
852
  initialize_memory: bool = True,
@@ -1142,6 +1264,7 @@ def _register_routes(app: FastAPI):
1142
1264
 
1143
1265
  @app.get(
1144
1266
  "/v1/default/banks/{bank_id}/stats",
1267
+ response_model=BankStatsResponse,
1145
1268
  summary="Get statistics for memory bank",
1146
1269
  description="Get statistics about nodes and links for a specific agent",
1147
1270
  operation_id="get_agent_stats",
@@ -1242,18 +1365,18 @@ def _register_routes(app: FastAPI):
1242
1365
  total_nodes = sum(nodes_by_type.values())
1243
1366
  total_links = sum(links_by_type.values())
1244
1367
 
1245
- return {
1246
- "bank_id": bank_id,
1247
- "total_nodes": total_nodes,
1248
- "total_links": total_links,
1249
- "total_documents": total_documents,
1250
- "nodes_by_fact_type": nodes_by_type,
1251
- "links_by_link_type": links_by_type,
1252
- "links_by_fact_type": links_by_fact_type,
1253
- "links_breakdown": links_breakdown,
1254
- "pending_operations": pending_operations,
1255
- "failed_operations": failed_operations,
1256
- }
1368
+ return BankStatsResponse(
1369
+ bank_id=bank_id,
1370
+ total_nodes=total_nodes,
1371
+ total_links=total_links,
1372
+ total_documents=total_documents,
1373
+ nodes_by_fact_type=nodes_by_type,
1374
+ links_by_link_type=links_by_type,
1375
+ links_by_fact_type=links_by_fact_type,
1376
+ links_breakdown=links_breakdown,
1377
+ pending_operations=pending_operations,
1378
+ failed_operations=failed_operations,
1379
+ )
1257
1380
 
1258
1381
  except Exception as e:
1259
1382
  import traceback
@@ -1477,6 +1600,7 @@ def _register_routes(app: FastAPI):
1477
1600
 
1478
1601
  @app.delete(
1479
1602
  "/v1/default/banks/{bank_id}/documents/{document_id}",
1603
+ response_model=DeleteDocumentResponse,
1480
1604
  summary="Delete a document",
1481
1605
  description="Delete a document and all its associated memory units and links.\n\n"
1482
1606
  "This will cascade delete:\n"
@@ -1503,12 +1627,12 @@ def _register_routes(app: FastAPI):
1503
1627
  if result["document_deleted"] == 0:
1504
1628
  raise HTTPException(status_code=404, detail="Document not found")
1505
1629
 
1506
- return {
1507
- "success": True,
1508
- "message": f"Document '{document_id}' and {result['memory_units_deleted']} associated memory units deleted successfully",
1509
- "document_id": document_id,
1510
- "memory_units_deleted": result["memory_units_deleted"],
1511
- }
1630
+ return DeleteDocumentResponse(
1631
+ success=True,
1632
+ message=f"Document '{document_id}' and {result['memory_units_deleted']} associated memory units deleted successfully",
1633
+ document_id=document_id,
1634
+ memory_units_deleted=result["memory_units_deleted"],
1635
+ )
1512
1636
  except HTTPException:
1513
1637
  raise
1514
1638
  except Exception as e:
@@ -1520,6 +1644,7 @@ def _register_routes(app: FastAPI):
1520
1644
 
1521
1645
  @app.get(
1522
1646
  "/v1/default/banks/{bank_id}/operations",
1647
+ response_model=OperationsListResponse,
1523
1648
  summary="List async operations",
1524
1649
  description="Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations",
1525
1650
  operation_id="list_operations",
@@ -1529,10 +1654,10 @@ def _register_routes(app: FastAPI):
1529
1654
  """List all async operations (pending and failed) for a memory bank."""
1530
1655
  try:
1531
1656
  operations = await app.state.memory.list_operations(bank_id, request_context=request_context)
1532
- return {
1533
- "bank_id": bank_id,
1534
- "operations": operations,
1535
- }
1657
+ return OperationsListResponse(
1658
+ bank_id=bank_id,
1659
+ operations=[OperationResponse(**op) for op in operations],
1660
+ )
1536
1661
  except Exception as e:
1537
1662
  import traceback
1538
1663
 
@@ -1542,6 +1667,7 @@ def _register_routes(app: FastAPI):
1542
1667
 
1543
1668
  @app.delete(
1544
1669
  "/v1/default/banks/{bank_id}/operations/{operation_id}",
1670
+ response_model=CancelOperationResponse,
1545
1671
  summary="Cancel a pending async operation",
1546
1672
  description="Cancel a pending async operation by removing it from the queue",
1547
1673
  operation_id="cancel_operation",
@@ -1559,7 +1685,7 @@ def _register_routes(app: FastAPI):
1559
1685
  raise HTTPException(status_code=400, detail=f"Invalid operation_id format: {operation_id}")
1560
1686
 
1561
1687
  result = await app.state.memory.cancel_operation(bank_id, operation_id, request_context=request_context)
1562
- return result
1688
+ return CancelOperationResponse(**result)
1563
1689
  except ValueError as e:
1564
1690
  raise HTTPException(status_code=404, detail=str(e))
1565
1691
  except Exception as e:
@@ -695,7 +695,14 @@ class MemoryEngine(MemoryEngineInterface):
695
695
 
696
696
  Returns:
697
697
  dict with status and optional error message
698
+
699
+ Note:
700
+ Returns unhealthy until initialize() has completed successfully.
698
701
  """
702
+ # Not healthy until fully initialized
703
+ if not self._initialized:
704
+ return {"status": "unhealthy", "reason": "not_initialized"}
705
+
699
706
  try:
700
707
  pool = await self._get_pool()
701
708
  async with pool.acquire() as conn:
@@ -106,9 +106,62 @@ async def retain_batch(
106
106
  )
107
107
 
108
108
  if not extracted_facts:
109
+ # Still need to create document if document_id was provided
110
+ async with acquire_with_retry(pool) as conn:
111
+ async with conn.transaction():
112
+ await fact_storage.ensure_bank_exists(conn, bank_id)
113
+
114
+ # Handle document tracking even with no facts
115
+ if document_id:
116
+ combined_content = "\n".join([c.get("content", "") for c in contents_dicts])
117
+ retain_params = {}
118
+ if contents_dicts:
119
+ first_item = contents_dicts[0]
120
+ if first_item.get("context"):
121
+ retain_params["context"] = first_item["context"]
122
+ if first_item.get("event_date"):
123
+ retain_params["event_date"] = (
124
+ first_item["event_date"].isoformat()
125
+ if hasattr(first_item["event_date"], "isoformat")
126
+ else str(first_item["event_date"])
127
+ )
128
+ if first_item.get("metadata"):
129
+ retain_params["metadata"] = first_item["metadata"]
130
+ await fact_storage.handle_document_tracking(
131
+ conn, bank_id, document_id, combined_content, is_first_batch, retain_params
132
+ )
133
+ else:
134
+ # Check for per-item document_ids
135
+ from collections import defaultdict
136
+
137
+ contents_by_doc = defaultdict(list)
138
+ for idx, content_dict in enumerate(contents_dicts):
139
+ doc_id = content_dict.get("document_id")
140
+ if doc_id:
141
+ contents_by_doc[doc_id].append((idx, content_dict))
142
+
143
+ for doc_id, doc_contents in contents_by_doc.items():
144
+ combined_content = "\n".join([c.get("content", "") for _, c in doc_contents])
145
+ retain_params = {}
146
+ if doc_contents:
147
+ first_item = doc_contents[0][1]
148
+ if first_item.get("context"):
149
+ retain_params["context"] = first_item["context"]
150
+ if first_item.get("event_date"):
151
+ retain_params["event_date"] = (
152
+ first_item["event_date"].isoformat()
153
+ if hasattr(first_item["event_date"], "isoformat")
154
+ else str(first_item["event_date"])
155
+ )
156
+ if first_item.get("metadata"):
157
+ retain_params["metadata"] = first_item["metadata"]
158
+ await fact_storage.handle_document_tracking(
159
+ conn, bank_id, doc_id, combined_content, is_first_batch, retain_params
160
+ )
161
+
109
162
  total_time = time.time() - start_time
110
163
  logger.info(
111
- f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (nothing to store)"
164
+ f"RETAIN_BATCH COMPLETE: 0 facts extracted from {len(contents)} contents in {total_time:.3f}s (document tracked, no facts)"
112
165
  )
113
166
  return [[] for _ in contents]
114
167
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hindsight-api
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  Summary: Hindsight: Agent Memory That Works Like Human Memory
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: alembic>=1.17.1
@@ -19,7 +19,7 @@ hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py,sha25
19
19
  hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py,sha256=IdDP6fgsYj5fCXAF0QT-3t_wcKJsnf7B0mh7qS-cf_w,3806
20
20
  hindsight_api/alembic/versions/rename_personality_to_disposition.py,sha256=A29-nDJ2Re4u9jdp2sUw29It808j4h6BpcA4wDHJMJ8,2765
21
21
  hindsight_api/api/__init__.py,sha256=zoDWA86ttx-UriC35UIgdPswIrau7GuMWTN63wYsUdM,2916
22
- hindsight_api/api/http.py,sha256=tUoJVu-3YcFijhsp1xDfndWYX1JdaZwT0wqmimtwFtw,72263
22
+ hindsight_api/api/http.py,sha256=M3mJIfqILPe5kC3iO2Ds11XmVPD5n_SuUSF3s8mUmhM,76049
23
23
  hindsight_api/api/mcp.py,sha256=Iowo3ourjWx7ZqLiCwF9nvjMAJpRceBprF5cgn5M6fs,7853
24
24
  hindsight_api/engine/__init__.py,sha256=-BwaSwG9fTT_BBO0c_2MBkxG6-tGdclSzIqsgHw4cnw,1633
25
25
  hindsight_api/engine/cross_encoder.py,sha256=5WmUx9yfJdIwZ0nA218O-mMKQJ7EKaPOtwhMiDbG8KQ,10483
@@ -28,7 +28,7 @@ hindsight_api/engine/embeddings.py,sha256=IEdP5-p6oTJRRKV2JzUEojByJGShUEmkInCyA9
28
28
  hindsight_api/engine/entity_resolver.py,sha256=f-fbUDKCrM9a5Sz10J0rW3jV7dib7BmpyGyassspKXg,23510
29
29
  hindsight_api/engine/interface.py,sha256=F6BgnjloH7EgL9_D2NpPuabR_zR-h_iEJBQ0ERC2P58,16090
30
30
  hindsight_api/engine/llm_wrapper.py,sha256=nLdVAk2xtkbwxLFMQNmEU-JmHucdtQoh3ph0BWX4sDc,29140
31
- hindsight_api/engine/memory_engine.py,sha256=cmuvj-EjTNbJaCp7UyM2eI8O8k3w6fTMLnngrgbHK94,166805
31
+ hindsight_api/engine/memory_engine.py,sha256=wQESgZ49srxgbpQTfvAM5BDp4-st1NGhiqktj753qaA,167050
32
32
  hindsight_api/engine/query_analyzer.py,sha256=DKFxmyyVVc59zwKbbGx4D22UVp6TxmD7jAa7cg9FGSU,19641
33
33
  hindsight_api/engine/response_models.py,sha256=QeESHC7oh84SYPDrR6FqHjiGBZnTAzo61IDB-qwVTSY,8737
34
34
  hindsight_api/engine/task_backend.py,sha256=txtcMUzHW1MigDCW7XsVZc5zqvM9FbR_xF_c9BKokBk,8054
@@ -45,7 +45,7 @@ hindsight_api/engine/retain/fact_storage.py,sha256=zhIiccW1D4wkgnZMFcbxDeMeHy5v4
45
45
  hindsight_api/engine/retain/link_creation.py,sha256=KP2kGU2VCymJptgw0hjaSdsjvncBgNp3P_A4OB_qx-w,3082
46
46
  hindsight_api/engine/retain/link_utils.py,sha256=w8n_pPzs_rd3EMkb7nv4k_qSZttAKDig93hSSjl-Xbc,32854
47
47
  hindsight_api/engine/retain/observation_regeneration.py,sha256=qE1-iSyH0lh5Zab1XIwSQSpxEArdOJOAC_yJY5iHLMQ,8143
48
- hindsight_api/engine/retain/orchestrator.py,sha256=TY_xk-DbqvXs1KCV41jj8u7ba6WvI2yVeMv_Xq9fBY8,17620
48
+ hindsight_api/engine/retain/orchestrator.py,sha256=9l27-IMemx4Wrym2HJ59DSUuh6NqMpTs0wqul2dwUzA,20631
49
49
  hindsight_api/engine/retain/types.py,sha256=UzCXauLrMD26g5oZK3_oQ-gTaSSsd-Ttjh17le64HH4,6898
50
50
  hindsight_api/engine/search/__init__.py,sha256=YPz_4g7IOabx078Xwg3RBfbOpJ649NRwNfe0gTI9P1U,802
51
51
  hindsight_api/engine/search/fusion.py,sha256=cY81BH9U5RyWrPXbQnrDBghtelDMckZWCke9aqMyNnQ,4220
@@ -69,7 +69,7 @@ hindsight_api/extensions/operation_validator.py,sha256=zQPD8pTMJJxQjpByxa4JxvGgD
69
69
  hindsight_api/extensions/tenant.py,sha256=gvngBMn3cJtUfd4P0P_288faNJq00T8zPQkeldEsD3g,1903
70
70
  hindsight_api/extensions/builtin/__init__.py,sha256=hLx2oFYZ1JtZhTWfab6AYcR02SWP2gIdbEqnZezT8ek,526
71
71
  hindsight_api/extensions/builtin/tenant.py,sha256=lsS0GDEUXmfPBzqhqk2FpN4Z_k5cA3Y3PFNYyiiuZjU,1444
72
- hindsight_api-0.1.13.dist-info/METADATA,sha256=_rFN2zLp2BI4B1HvSP775qX0jiN-kmlAkobVy1MQU_M,5408
73
- hindsight_api-0.1.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
74
- hindsight_api-0.1.13.dist-info/entry_points.txt,sha256=vqZv5WLHbSx8vyec5RtMlUqtE_ul7DTgEVODSmou6Og,109
75
- hindsight_api-0.1.13.dist-info/RECORD,,
72
+ hindsight_api-0.1.15.dist-info/METADATA,sha256=uHr_iivfQRS-yIhjQoVNh8CbvaRUTLjHGFsYdQatopg,5408
73
+ hindsight_api-0.1.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
74
+ hindsight_api-0.1.15.dist-info/entry_points.txt,sha256=vqZv5WLHbSx8vyec5RtMlUqtE_ul7DTgEVODSmou6Og,109
75
+ hindsight_api-0.1.15.dist-info/RECORD,,