hindsight-api 0.1.11__py3-none-any.whl → 0.1.12__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/__init__.py +2 -0
- hindsight_api/alembic/env.py +24 -1
- hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py +14 -4
- hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py +54 -13
- hindsight_api/alembic/versions/rename_personality_to_disposition.py +18 -7
- hindsight_api/api/http.py +234 -228
- hindsight_api/api/mcp.py +14 -3
- hindsight_api/engine/__init__.py +12 -1
- hindsight_api/engine/entity_resolver.py +38 -37
- hindsight_api/engine/interface.py +592 -0
- hindsight_api/engine/llm_wrapper.py +176 -6
- hindsight_api/engine/memory_engine.py +993 -217
- hindsight_api/engine/retain/bank_utils.py +13 -12
- hindsight_api/engine/retain/chunk_storage.py +3 -2
- hindsight_api/engine/retain/fact_storage.py +10 -7
- hindsight_api/engine/retain/link_utils.py +17 -16
- hindsight_api/engine/retain/observation_regeneration.py +17 -16
- hindsight_api/engine/retain/orchestrator.py +2 -3
- hindsight_api/engine/retain/types.py +25 -8
- hindsight_api/engine/search/graph_retrieval.py +6 -5
- hindsight_api/engine/search/mpfp_retrieval.py +8 -7
- hindsight_api/engine/search/retrieval.py +12 -11
- hindsight_api/engine/search/think_utils.py +1 -1
- hindsight_api/engine/search/tracer.py +1 -1
- hindsight_api/engine/task_backend.py +32 -0
- hindsight_api/extensions/__init__.py +66 -0
- hindsight_api/extensions/base.py +81 -0
- hindsight_api/extensions/builtin/__init__.py +18 -0
- hindsight_api/extensions/builtin/tenant.py +33 -0
- hindsight_api/extensions/context.py +110 -0
- hindsight_api/extensions/http.py +89 -0
- hindsight_api/extensions/loader.py +125 -0
- hindsight_api/extensions/operation_validator.py +325 -0
- hindsight_api/extensions/tenant.py +63 -0
- hindsight_api/main.py +1 -1
- hindsight_api/mcp_local.py +7 -1
- hindsight_api/migrations.py +54 -10
- hindsight_api/models.py +15 -0
- hindsight_api/pg0.py +1 -1
- {hindsight_api-0.1.11.dist-info → hindsight_api-0.1.12.dist-info}/METADATA +1 -1
- hindsight_api-0.1.12.dist-info/RECORD +74 -0
- hindsight_api-0.1.11.dist-info/RECORD +0 -64
- {hindsight_api-0.1.11.dist-info → hindsight_api-0.1.12.dist-info}/WHEEL +0 -0
- {hindsight_api-0.1.11.dist-info → hindsight_api-0.1.12.dist-info}/entry_points.txt +0 -0
hindsight_api/api/http.py
CHANGED
|
@@ -12,7 +12,7 @@ from contextlib import asynccontextmanager
|
|
|
12
12
|
from datetime import datetime
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
|
-
from fastapi import FastAPI, HTTPException, Query
|
|
15
|
+
from fastapi import Depends, FastAPI, Header, HTTPException, Query
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _parse_metadata(metadata: Any) -> dict[str, Any]:
|
|
@@ -33,9 +33,11 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
33
33
|
|
|
34
34
|
from hindsight_api import MemoryEngine
|
|
35
35
|
from hindsight_api.engine.db_utils import acquire_with_retry
|
|
36
|
-
from hindsight_api.engine.memory_engine import Budget
|
|
36
|
+
from hindsight_api.engine.memory_engine import Budget, fq_table
|
|
37
37
|
from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
|
|
38
|
+
from hindsight_api.extensions import HttpExtension, load_extension
|
|
38
39
|
from hindsight_api.metrics import create_metrics_collector, get_metrics_collector, initialize_metrics
|
|
40
|
+
from hindsight_api.models import RequestContext
|
|
39
41
|
|
|
40
42
|
logger = logging.getLogger(__name__)
|
|
41
43
|
|
|
@@ -337,7 +339,7 @@ class RetainResponse(BaseModel):
|
|
|
337
339
|
success: bool
|
|
338
340
|
bank_id: str
|
|
339
341
|
items_count: int
|
|
340
|
-
|
|
342
|
+
is_async: bool = Field(
|
|
341
343
|
alias="async", serialization_alias="async", description="Whether the operation was processed asynchronously"
|
|
342
344
|
)
|
|
343
345
|
|
|
@@ -706,7 +708,11 @@ class DeleteResponse(BaseModel):
|
|
|
706
708
|
deleted_count: int | None = None
|
|
707
709
|
|
|
708
710
|
|
|
709
|
-
def create_app(
|
|
711
|
+
def create_app(
|
|
712
|
+
memory: MemoryEngine,
|
|
713
|
+
initialize_memory: bool = True,
|
|
714
|
+
http_extension: HttpExtension | None = None,
|
|
715
|
+
) -> FastAPI:
|
|
710
716
|
"""
|
|
711
717
|
Create and configure the FastAPI application.
|
|
712
718
|
|
|
@@ -714,6 +720,8 @@ def create_app(memory: MemoryEngine, initialize_memory: bool = True) -> FastAPI:
|
|
|
714
720
|
memory: MemoryEngine instance (already initialized with required parameters).
|
|
715
721
|
Migrations are controlled by the MemoryEngine's run_migrations parameter.
|
|
716
722
|
initialize_memory: Whether to initialize memory system on startup (default: True)
|
|
723
|
+
http_extension: Optional HTTP extension to mount custom endpoints under /extension/.
|
|
724
|
+
If None, attempts to load from HINDSIGHT_API_HTTP_EXTENSION env var.
|
|
717
725
|
|
|
718
726
|
Returns:
|
|
719
727
|
Configured FastAPI application
|
|
@@ -723,6 +731,11 @@ def create_app(memory: MemoryEngine, initialize_memory: bool = True) -> FastAPI:
|
|
|
723
731
|
In that case, you should call memory.initialize() manually before starting the server
|
|
724
732
|
and memory.close() when shutting down.
|
|
725
733
|
"""
|
|
734
|
+
# Load HTTP extension from environment if not provided
|
|
735
|
+
if http_extension is None:
|
|
736
|
+
http_extension = load_extension("HTTP", HttpExtension)
|
|
737
|
+
if http_extension:
|
|
738
|
+
logging.info(f"Loaded HTTP extension: {http_extension.__class__.__name__}")
|
|
726
739
|
|
|
727
740
|
@asynccontextmanager
|
|
728
741
|
async def lifespan(app: FastAPI):
|
|
@@ -746,8 +759,18 @@ def create_app(memory: MemoryEngine, initialize_memory: bool = True) -> FastAPI:
|
|
|
746
759
|
await memory.initialize()
|
|
747
760
|
logging.info("Memory system initialized")
|
|
748
761
|
|
|
762
|
+
# Call HTTP extension startup hook
|
|
763
|
+
if http_extension:
|
|
764
|
+
await http_extension.on_startup()
|
|
765
|
+
logging.info("HTTP extension started")
|
|
766
|
+
|
|
749
767
|
yield
|
|
750
768
|
|
|
769
|
+
# Call HTTP extension shutdown hook
|
|
770
|
+
if http_extension:
|
|
771
|
+
await http_extension.on_shutdown()
|
|
772
|
+
logging.info("HTTP extension stopped")
|
|
773
|
+
|
|
751
774
|
# Shutdown: Cleanup memory system
|
|
752
775
|
await memory.close()
|
|
753
776
|
logging.info("Memory system closed")
|
|
@@ -775,12 +798,36 @@ def create_app(memory: MemoryEngine, initialize_memory: bool = True) -> FastAPI:
|
|
|
775
798
|
# Register all routes
|
|
776
799
|
_register_routes(app)
|
|
777
800
|
|
|
801
|
+
# Mount HTTP extension router if available
|
|
802
|
+
if http_extension:
|
|
803
|
+
extension_router = http_extension.get_router(memory)
|
|
804
|
+
app.include_router(extension_router, prefix="/ext", tags=["Extension"])
|
|
805
|
+
logging.info("HTTP extension router mounted at /ext/")
|
|
806
|
+
|
|
778
807
|
return app
|
|
779
808
|
|
|
780
809
|
|
|
781
810
|
def _register_routes(app: FastAPI):
|
|
782
811
|
"""Register all API routes on the given app instance."""
|
|
783
812
|
|
|
813
|
+
def get_request_context(authorization: str | None = Header(default=None)) -> RequestContext:
|
|
814
|
+
"""
|
|
815
|
+
Extract request context from Authorization header.
|
|
816
|
+
|
|
817
|
+
Supports:
|
|
818
|
+
- Bearer token: "Bearer <api_key>"
|
|
819
|
+
- Direct API key: "<api_key>"
|
|
820
|
+
|
|
821
|
+
Returns RequestContext with extracted API key (may be None if no auth header).
|
|
822
|
+
"""
|
|
823
|
+
api_key = None
|
|
824
|
+
if authorization:
|
|
825
|
+
if authorization.lower().startswith("bearer "):
|
|
826
|
+
api_key = authorization[7:].strip()
|
|
827
|
+
else:
|
|
828
|
+
api_key = authorization.strip()
|
|
829
|
+
return RequestContext(api_key=api_key)
|
|
830
|
+
|
|
784
831
|
@app.get(
|
|
785
832
|
"/health",
|
|
786
833
|
summary="Health check endpoint",
|
|
@@ -821,10 +868,12 @@ def _register_routes(app: FastAPI):
|
|
|
821
868
|
operation_id="get_graph",
|
|
822
869
|
tags=["Memory"],
|
|
823
870
|
)
|
|
824
|
-
async def api_graph(
|
|
871
|
+
async def api_graph(
|
|
872
|
+
bank_id: str, type: str | None = None, request_context: RequestContext = Depends(get_request_context)
|
|
873
|
+
):
|
|
825
874
|
"""Get graph data from database, filtered by bank_id and optionally by type."""
|
|
826
875
|
try:
|
|
827
|
-
data = await app.state.memory.get_graph_data(bank_id, type)
|
|
876
|
+
data = await app.state.memory.get_graph_data(bank_id, type, request_context=request_context)
|
|
828
877
|
return data
|
|
829
878
|
except Exception as e:
|
|
830
879
|
import traceback
|
|
@@ -841,7 +890,14 @@ def _register_routes(app: FastAPI):
|
|
|
841
890
|
operation_id="list_memories",
|
|
842
891
|
tags=["Memory"],
|
|
843
892
|
)
|
|
844
|
-
async def api_list(
|
|
893
|
+
async def api_list(
|
|
894
|
+
bank_id: str,
|
|
895
|
+
type: str | None = None,
|
|
896
|
+
q: str | None = None,
|
|
897
|
+
limit: int = 100,
|
|
898
|
+
offset: int = 0,
|
|
899
|
+
request_context: RequestContext = Depends(get_request_context),
|
|
900
|
+
):
|
|
845
901
|
"""
|
|
846
902
|
List memory units for table view with optional full-text search.
|
|
847
903
|
|
|
@@ -857,7 +913,12 @@ def _register_routes(app: FastAPI):
|
|
|
857
913
|
"""
|
|
858
914
|
try:
|
|
859
915
|
data = await app.state.memory.list_memory_units(
|
|
860
|
-
bank_id=bank_id,
|
|
916
|
+
bank_id=bank_id,
|
|
917
|
+
fact_type=type,
|
|
918
|
+
search_query=q,
|
|
919
|
+
limit=limit,
|
|
920
|
+
offset=offset,
|
|
921
|
+
request_context=request_context,
|
|
861
922
|
)
|
|
862
923
|
return data
|
|
863
924
|
except Exception as e:
|
|
@@ -880,7 +941,9 @@ def _register_routes(app: FastAPI):
|
|
|
880
941
|
operation_id="recall_memories",
|
|
881
942
|
tags=["Memory"],
|
|
882
943
|
)
|
|
883
|
-
async def api_recall(
|
|
944
|
+
async def api_recall(
|
|
945
|
+
bank_id: str, request: RecallRequest, request_context: RequestContext = Depends(get_request_context)
|
|
946
|
+
):
|
|
884
947
|
"""Run a recall and return results with trace."""
|
|
885
948
|
metrics = get_metrics_collector()
|
|
886
949
|
|
|
@@ -923,6 +986,7 @@ def _register_routes(app: FastAPI):
|
|
|
923
986
|
max_entity_tokens=max_entity_tokens,
|
|
924
987
|
include_chunks=include_chunks,
|
|
925
988
|
max_chunk_tokens=max_chunk_tokens,
|
|
989
|
+
request_context=request_context,
|
|
926
990
|
)
|
|
927
991
|
|
|
928
992
|
# Convert core MemoryFact objects to API RecallResult objects (excluding internal metrics)
|
|
@@ -995,14 +1059,20 @@ def _register_routes(app: FastAPI):
|
|
|
995
1059
|
operation_id="reflect",
|
|
996
1060
|
tags=["Memory"],
|
|
997
1061
|
)
|
|
998
|
-
async def api_reflect(
|
|
1062
|
+
async def api_reflect(
|
|
1063
|
+
bank_id: str, request: ReflectRequest, request_context: RequestContext = Depends(get_request_context)
|
|
1064
|
+
):
|
|
999
1065
|
metrics = get_metrics_collector()
|
|
1000
1066
|
|
|
1001
1067
|
try:
|
|
1002
1068
|
# Use the memory system's reflect_async method (record metrics)
|
|
1003
1069
|
with metrics.record_operation("reflect", bank_id=bank_id, budget=request.budget.value):
|
|
1004
1070
|
core_result = await app.state.memory.reflect_async(
|
|
1005
|
-
bank_id=bank_id,
|
|
1071
|
+
bank_id=bank_id,
|
|
1072
|
+
query=request.query,
|
|
1073
|
+
budget=request.budget,
|
|
1074
|
+
context=request.context,
|
|
1075
|
+
request_context=request_context,
|
|
1006
1076
|
)
|
|
1007
1077
|
|
|
1008
1078
|
# Convert core MemoryFact objects to API ReflectFact objects if facts are requested
|
|
@@ -1041,10 +1111,10 @@ def _register_routes(app: FastAPI):
|
|
|
1041
1111
|
operation_id="list_banks",
|
|
1042
1112
|
tags=["Banks"],
|
|
1043
1113
|
)
|
|
1044
|
-
async def api_list_banks():
|
|
1114
|
+
async def api_list_banks(request_context: RequestContext = Depends(get_request_context)):
|
|
1045
1115
|
"""Get list of all banks with their profiles."""
|
|
1046
1116
|
try:
|
|
1047
|
-
banks = await app.state.memory.list_banks()
|
|
1117
|
+
banks = await app.state.memory.list_banks(request_context=request_context)
|
|
1048
1118
|
return BankListResponse(banks=banks)
|
|
1049
1119
|
except Exception as e:
|
|
1050
1120
|
import traceback
|
|
@@ -1067,9 +1137,9 @@ def _register_routes(app: FastAPI):
|
|
|
1067
1137
|
async with acquire_with_retry(pool) as conn:
|
|
1068
1138
|
# Get node counts by fact_type
|
|
1069
1139
|
node_stats = await conn.fetch(
|
|
1070
|
-
"""
|
|
1140
|
+
f"""
|
|
1071
1141
|
SELECT fact_type, COUNT(*) as count
|
|
1072
|
-
FROM memory_units
|
|
1142
|
+
FROM {fq_table("memory_units")}
|
|
1073
1143
|
WHERE bank_id = $1
|
|
1074
1144
|
GROUP BY fact_type
|
|
1075
1145
|
""",
|
|
@@ -1078,10 +1148,10 @@ def _register_routes(app: FastAPI):
|
|
|
1078
1148
|
|
|
1079
1149
|
# Get link counts by link_type
|
|
1080
1150
|
link_stats = await conn.fetch(
|
|
1081
|
-
"""
|
|
1151
|
+
f"""
|
|
1082
1152
|
SELECT ml.link_type, COUNT(*) as count
|
|
1083
|
-
FROM memory_links ml
|
|
1084
|
-
JOIN memory_units mu ON ml.from_unit_id = mu.id
|
|
1153
|
+
FROM {fq_table("memory_links")} ml
|
|
1154
|
+
JOIN {fq_table("memory_units")} mu ON ml.from_unit_id = mu.id
|
|
1085
1155
|
WHERE mu.bank_id = $1
|
|
1086
1156
|
GROUP BY ml.link_type
|
|
1087
1157
|
""",
|
|
@@ -1090,10 +1160,10 @@ def _register_routes(app: FastAPI):
|
|
|
1090
1160
|
|
|
1091
1161
|
# Get link counts by fact_type (from nodes)
|
|
1092
1162
|
link_fact_type_stats = await conn.fetch(
|
|
1093
|
-
"""
|
|
1163
|
+
f"""
|
|
1094
1164
|
SELECT mu.fact_type, COUNT(*) as count
|
|
1095
|
-
FROM memory_links ml
|
|
1096
|
-
JOIN memory_units mu ON ml.from_unit_id = mu.id
|
|
1165
|
+
FROM {fq_table("memory_links")} ml
|
|
1166
|
+
JOIN {fq_table("memory_units")} mu ON ml.from_unit_id = mu.id
|
|
1097
1167
|
WHERE mu.bank_id = $1
|
|
1098
1168
|
GROUP BY mu.fact_type
|
|
1099
1169
|
""",
|
|
@@ -1102,10 +1172,10 @@ def _register_routes(app: FastAPI):
|
|
|
1102
1172
|
|
|
1103
1173
|
# Get link counts by fact_type AND link_type
|
|
1104
1174
|
link_breakdown_stats = await conn.fetch(
|
|
1105
|
-
"""
|
|
1175
|
+
f"""
|
|
1106
1176
|
SELECT mu.fact_type, ml.link_type, COUNT(*) as count
|
|
1107
|
-
FROM memory_links ml
|
|
1108
|
-
JOIN memory_units mu ON ml.from_unit_id = mu.id
|
|
1177
|
+
FROM {fq_table("memory_links")} ml
|
|
1178
|
+
JOIN {fq_table("memory_units")} mu ON ml.from_unit_id = mu.id
|
|
1109
1179
|
WHERE mu.bank_id = $1
|
|
1110
1180
|
GROUP BY mu.fact_type, ml.link_type
|
|
1111
1181
|
""",
|
|
@@ -1114,9 +1184,9 @@ def _register_routes(app: FastAPI):
|
|
|
1114
1184
|
|
|
1115
1185
|
# Get pending and failed operations counts
|
|
1116
1186
|
ops_stats = await conn.fetch(
|
|
1117
|
-
"""
|
|
1187
|
+
f"""
|
|
1118
1188
|
SELECT status, COUNT(*) as count
|
|
1119
|
-
FROM async_operations
|
|
1189
|
+
FROM {fq_table("async_operations")}
|
|
1120
1190
|
WHERE bank_id = $1
|
|
1121
1191
|
GROUP BY status
|
|
1122
1192
|
""",
|
|
@@ -1128,9 +1198,9 @@ def _register_routes(app: FastAPI):
|
|
|
1128
1198
|
|
|
1129
1199
|
# Get document count
|
|
1130
1200
|
doc_count_result = await conn.fetchrow(
|
|
1131
|
-
"""
|
|
1201
|
+
f"""
|
|
1132
1202
|
SELECT COUNT(*) as count
|
|
1133
|
-
FROM documents
|
|
1203
|
+
FROM {fq_table("documents")}
|
|
1134
1204
|
WHERE bank_id = $1
|
|
1135
1205
|
""",
|
|
1136
1206
|
bank_id,
|
|
@@ -1184,11 +1254,13 @@ def _register_routes(app: FastAPI):
|
|
|
1184
1254
|
tags=["Entities"],
|
|
1185
1255
|
)
|
|
1186
1256
|
async def api_list_entities(
|
|
1187
|
-
bank_id: str,
|
|
1257
|
+
bank_id: str,
|
|
1258
|
+
limit: int = Query(default=100, description="Maximum number of entities to return"),
|
|
1259
|
+
request_context: RequestContext = Depends(get_request_context),
|
|
1188
1260
|
):
|
|
1189
1261
|
"""List entities for a memory bank."""
|
|
1190
1262
|
try:
|
|
1191
|
-
entities = await app.state.memory.list_entities(bank_id, limit=limit)
|
|
1263
|
+
entities = await app.state.memory.list_entities(bank_id, limit=limit, request_context=request_context)
|
|
1192
1264
|
return EntityListResponse(items=[EntityListItem(**e) for e in entities])
|
|
1193
1265
|
except Exception as e:
|
|
1194
1266
|
import traceback
|
|
@@ -1205,37 +1277,26 @@ def _register_routes(app: FastAPI):
|
|
|
1205
1277
|
operation_id="get_entity",
|
|
1206
1278
|
tags=["Entities"],
|
|
1207
1279
|
)
|
|
1208
|
-
async def api_get_entity(
|
|
1280
|
+
async def api_get_entity(
|
|
1281
|
+
bank_id: str, entity_id: str, request_context: RequestContext = Depends(get_request_context)
|
|
1282
|
+
):
|
|
1209
1283
|
"""Get entity details with observations."""
|
|
1210
1284
|
try:
|
|
1211
|
-
|
|
1212
|
-
pool = await app.state.memory._get_pool()
|
|
1213
|
-
async with acquire_with_retry(pool) as conn:
|
|
1214
|
-
entity_row = await conn.fetchrow(
|
|
1215
|
-
"""
|
|
1216
|
-
SELECT id, canonical_name, mention_count, first_seen, last_seen, metadata
|
|
1217
|
-
FROM entities
|
|
1218
|
-
WHERE bank_id = $1 AND id = $2
|
|
1219
|
-
""",
|
|
1220
|
-
bank_id,
|
|
1221
|
-
uuid.UUID(entity_id),
|
|
1222
|
-
)
|
|
1285
|
+
entity = await app.state.memory.get_entity(bank_id, entity_id, request_context=request_context)
|
|
1223
1286
|
|
|
1224
|
-
if
|
|
1287
|
+
if entity is None:
|
|
1225
1288
|
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
|
|
1226
1289
|
|
|
1227
|
-
# Get observations for the entity
|
|
1228
|
-
observations = await app.state.memory.get_entity_observations(bank_id, entity_id, limit=20)
|
|
1229
|
-
|
|
1230
1290
|
return EntityDetailResponse(
|
|
1231
|
-
id=
|
|
1232
|
-
canonical_name=
|
|
1233
|
-
mention_count=
|
|
1234
|
-
first_seen=
|
|
1235
|
-
last_seen=
|
|
1236
|
-
metadata=_parse_metadata(
|
|
1291
|
+
id=entity["id"],
|
|
1292
|
+
canonical_name=entity["canonical_name"],
|
|
1293
|
+
mention_count=entity["mention_count"],
|
|
1294
|
+
first_seen=entity["first_seen"],
|
|
1295
|
+
last_seen=entity["last_seen"],
|
|
1296
|
+
metadata=_parse_metadata(entity["metadata"]),
|
|
1237
1297
|
observations=[
|
|
1238
|
-
EntityObservationResponse(text=obs.text, mentioned_at=obs.mentioned_at)
|
|
1298
|
+
EntityObservationResponse(text=obs.text, mentioned_at=obs.mentioned_at)
|
|
1299
|
+
for obs in entity["observations"]
|
|
1239
1300
|
],
|
|
1240
1301
|
)
|
|
1241
1302
|
except HTTPException:
|
|
@@ -1255,42 +1316,40 @@ def _register_routes(app: FastAPI):
|
|
|
1255
1316
|
operation_id="regenerate_entity_observations",
|
|
1256
1317
|
tags=["Entities"],
|
|
1257
1318
|
)
|
|
1258
|
-
async def api_regenerate_entity_observations(
|
|
1319
|
+
async def api_regenerate_entity_observations(
|
|
1320
|
+
bank_id: str,
|
|
1321
|
+
entity_id: str,
|
|
1322
|
+
request_context: RequestContext = Depends(get_request_context),
|
|
1323
|
+
):
|
|
1259
1324
|
"""Regenerate observations for an entity."""
|
|
1260
1325
|
try:
|
|
1261
|
-
#
|
|
1262
|
-
|
|
1263
|
-
async with acquire_with_retry(pool) as conn:
|
|
1264
|
-
entity_row = await conn.fetchrow(
|
|
1265
|
-
"""
|
|
1266
|
-
SELECT id, canonical_name, mention_count, first_seen, last_seen, metadata
|
|
1267
|
-
FROM entities
|
|
1268
|
-
WHERE bank_id = $1 AND id = $2
|
|
1269
|
-
""",
|
|
1270
|
-
bank_id,
|
|
1271
|
-
uuid.UUID(entity_id),
|
|
1272
|
-
)
|
|
1326
|
+
# Get the entity to verify it exists and get canonical_name
|
|
1327
|
+
entity = await app.state.memory.get_entity(bank_id, entity_id, request_context=request_context)
|
|
1273
1328
|
|
|
1274
|
-
if
|
|
1329
|
+
if entity is None:
|
|
1275
1330
|
raise HTTPException(status_code=404, detail=f"Entity {entity_id} not found")
|
|
1276
1331
|
|
|
1277
1332
|
# Regenerate observations
|
|
1278
1333
|
await app.state.memory.regenerate_entity_observations(
|
|
1279
|
-
bank_id=bank_id,
|
|
1334
|
+
bank_id=bank_id,
|
|
1335
|
+
entity_id=entity_id,
|
|
1336
|
+
entity_name=entity["canonical_name"],
|
|
1337
|
+
request_context=request_context,
|
|
1280
1338
|
)
|
|
1281
1339
|
|
|
1282
|
-
# Get updated observations
|
|
1283
|
-
|
|
1340
|
+
# Get updated entity with new observations
|
|
1341
|
+
entity = await app.state.memory.get_entity(bank_id, entity_id, request_context=request_context)
|
|
1284
1342
|
|
|
1285
1343
|
return EntityDetailResponse(
|
|
1286
|
-
id=
|
|
1287
|
-
canonical_name=
|
|
1288
|
-
mention_count=
|
|
1289
|
-
first_seen=
|
|
1290
|
-
last_seen=
|
|
1291
|
-
metadata=_parse_metadata(
|
|
1344
|
+
id=entity["id"],
|
|
1345
|
+
canonical_name=entity["canonical_name"],
|
|
1346
|
+
mention_count=entity["mention_count"],
|
|
1347
|
+
first_seen=entity["first_seen"],
|
|
1348
|
+
last_seen=entity["last_seen"],
|
|
1349
|
+
metadata=_parse_metadata(entity["metadata"]),
|
|
1292
1350
|
observations=[
|
|
1293
|
-
EntityObservationResponse(text=obs.text, mentioned_at=obs.mentioned_at)
|
|
1351
|
+
EntityObservationResponse(text=obs.text, mentioned_at=obs.mentioned_at)
|
|
1352
|
+
for obs in entity["observations"]
|
|
1294
1353
|
],
|
|
1295
1354
|
)
|
|
1296
1355
|
except HTTPException:
|
|
@@ -1310,7 +1369,13 @@ def _register_routes(app: FastAPI):
|
|
|
1310
1369
|
operation_id="list_documents",
|
|
1311
1370
|
tags=["Documents"],
|
|
1312
1371
|
)
|
|
1313
|
-
async def api_list_documents(
|
|
1372
|
+
async def api_list_documents(
|
|
1373
|
+
bank_id: str,
|
|
1374
|
+
q: str | None = None,
|
|
1375
|
+
limit: int = 100,
|
|
1376
|
+
offset: int = 0,
|
|
1377
|
+
request_context: RequestContext = Depends(get_request_context),
|
|
1378
|
+
):
|
|
1314
1379
|
"""
|
|
1315
1380
|
List documents for a memory bank with optional search.
|
|
1316
1381
|
|
|
@@ -1321,7 +1386,9 @@ def _register_routes(app: FastAPI):
|
|
|
1321
1386
|
offset: Offset for pagination (default: 0)
|
|
1322
1387
|
"""
|
|
1323
1388
|
try:
|
|
1324
|
-
data = await app.state.memory.list_documents(
|
|
1389
|
+
data = await app.state.memory.list_documents(
|
|
1390
|
+
bank_id=bank_id, search_query=q, limit=limit, offset=offset, request_context=request_context
|
|
1391
|
+
)
|
|
1325
1392
|
return data
|
|
1326
1393
|
except Exception as e:
|
|
1327
1394
|
import traceback
|
|
@@ -1338,7 +1405,9 @@ def _register_routes(app: FastAPI):
|
|
|
1338
1405
|
operation_id="get_document",
|
|
1339
1406
|
tags=["Documents"],
|
|
1340
1407
|
)
|
|
1341
|
-
async def api_get_document(
|
|
1408
|
+
async def api_get_document(
|
|
1409
|
+
bank_id: str, document_id: str, request_context: RequestContext = Depends(get_request_context)
|
|
1410
|
+
):
|
|
1342
1411
|
"""
|
|
1343
1412
|
Get a specific document with its original text.
|
|
1344
1413
|
|
|
@@ -1347,7 +1416,7 @@ def _register_routes(app: FastAPI):
|
|
|
1347
1416
|
document_id: Document ID (from path)
|
|
1348
1417
|
"""
|
|
1349
1418
|
try:
|
|
1350
|
-
document = await app.state.memory.get_document(document_id, bank_id)
|
|
1419
|
+
document = await app.state.memory.get_document(document_id, bank_id, request_context=request_context)
|
|
1351
1420
|
if not document:
|
|
1352
1421
|
raise HTTPException(status_code=404, detail="Document not found")
|
|
1353
1422
|
return document
|
|
@@ -1368,7 +1437,7 @@ def _register_routes(app: FastAPI):
|
|
|
1368
1437
|
operation_id="get_chunk",
|
|
1369
1438
|
tags=["Documents"],
|
|
1370
1439
|
)
|
|
1371
|
-
async def api_get_chunk(chunk_id: str):
|
|
1440
|
+
async def api_get_chunk(chunk_id: str, request_context: RequestContext = Depends(get_request_context)):
|
|
1372
1441
|
"""
|
|
1373
1442
|
Get a specific chunk with its text.
|
|
1374
1443
|
|
|
@@ -1376,7 +1445,7 @@ def _register_routes(app: FastAPI):
|
|
|
1376
1445
|
chunk_id: Chunk ID (from path, format: bank_id_document_id_chunk_index)
|
|
1377
1446
|
"""
|
|
1378
1447
|
try:
|
|
1379
|
-
chunk = await app.state.memory.get_chunk(chunk_id)
|
|
1448
|
+
chunk = await app.state.memory.get_chunk(chunk_id, request_context=request_context)
|
|
1380
1449
|
if not chunk:
|
|
1381
1450
|
raise HTTPException(status_code=404, detail="Chunk not found")
|
|
1382
1451
|
return chunk
|
|
@@ -1401,7 +1470,9 @@ def _register_routes(app: FastAPI):
|
|
|
1401
1470
|
operation_id="delete_document",
|
|
1402
1471
|
tags=["Documents"],
|
|
1403
1472
|
)
|
|
1404
|
-
async def api_delete_document(
|
|
1473
|
+
async def api_delete_document(
|
|
1474
|
+
bank_id: str, document_id: str, request_context: RequestContext = Depends(get_request_context)
|
|
1475
|
+
):
|
|
1405
1476
|
"""
|
|
1406
1477
|
Delete a document and all its associated memory units and links.
|
|
1407
1478
|
|
|
@@ -1410,7 +1481,7 @@ def _register_routes(app: FastAPI):
|
|
|
1410
1481
|
document_id: Document ID to delete (from path)
|
|
1411
1482
|
"""
|
|
1412
1483
|
try:
|
|
1413
|
-
result = await app.state.memory.delete_document(document_id, bank_id)
|
|
1484
|
+
result = await app.state.memory.delete_document(document_id, bank_id, request_context=request_context)
|
|
1414
1485
|
|
|
1415
1486
|
if result["document_deleted"] == 0:
|
|
1416
1487
|
raise HTTPException(status_code=404, detail="Document not found")
|
|
@@ -1437,45 +1508,14 @@ def _register_routes(app: FastAPI):
|
|
|
1437
1508
|
operation_id="list_operations",
|
|
1438
1509
|
tags=["Operations"],
|
|
1439
1510
|
)
|
|
1440
|
-
async def api_list_operations(bank_id: str):
|
|
1511
|
+
async def api_list_operations(bank_id: str, request_context: RequestContext = Depends(get_request_context)):
|
|
1441
1512
|
"""List all async operations (pending and failed) for a memory bank."""
|
|
1442
1513
|
try:
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
FROM async_operations
|
|
1449
|
-
WHERE bank_id = $1
|
|
1450
|
-
ORDER BY created_at DESC
|
|
1451
|
-
""",
|
|
1452
|
-
bank_id,
|
|
1453
|
-
)
|
|
1454
|
-
|
|
1455
|
-
def parse_metadata(metadata):
|
|
1456
|
-
"""Parse result_metadata which may be a string or dict."""
|
|
1457
|
-
if metadata is None:
|
|
1458
|
-
return {}
|
|
1459
|
-
if isinstance(metadata, str):
|
|
1460
|
-
return json.loads(metadata)
|
|
1461
|
-
return metadata
|
|
1462
|
-
|
|
1463
|
-
return {
|
|
1464
|
-
"bank_id": bank_id,
|
|
1465
|
-
"operations": [
|
|
1466
|
-
{
|
|
1467
|
-
"id": str(row["operation_id"]),
|
|
1468
|
-
"task_type": row["operation_type"],
|
|
1469
|
-
"items_count": parse_metadata(row["result_metadata"]).get("items_count", 0),
|
|
1470
|
-
"document_id": parse_metadata(row["result_metadata"]).get("document_id"),
|
|
1471
|
-
"created_at": row["created_at"].isoformat(),
|
|
1472
|
-
"status": row["status"],
|
|
1473
|
-
"error_message": row["error_message"],
|
|
1474
|
-
}
|
|
1475
|
-
for row in operations
|
|
1476
|
-
],
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1514
|
+
operations = await app.state.memory.list_operations(bank_id, request_context=request_context)
|
|
1515
|
+
return {
|
|
1516
|
+
"bank_id": bank_id,
|
|
1517
|
+
"operations": operations,
|
|
1518
|
+
}
|
|
1479
1519
|
except Exception as e:
|
|
1480
1520
|
import traceback
|
|
1481
1521
|
|
|
@@ -1490,39 +1530,21 @@ def _register_routes(app: FastAPI):
|
|
|
1490
1530
|
operation_id="cancel_operation",
|
|
1491
1531
|
tags=["Operations"],
|
|
1492
1532
|
)
|
|
1493
|
-
async def api_cancel_operation(
|
|
1533
|
+
async def api_cancel_operation(
|
|
1534
|
+
bank_id: str, operation_id: str, request_context: RequestContext = Depends(get_request_context)
|
|
1535
|
+
):
|
|
1494
1536
|
"""Cancel a pending async operation."""
|
|
1495
1537
|
try:
|
|
1496
1538
|
# Validate UUID format
|
|
1497
1539
|
try:
|
|
1498
|
-
|
|
1540
|
+
uuid.UUID(operation_id)
|
|
1499
1541
|
except ValueError:
|
|
1500
1542
|
raise HTTPException(status_code=400, detail=f"Invalid operation_id format: {operation_id}")
|
|
1501
1543
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
"SELECT bank_id FROM async_operations WHERE operation_id = $1 AND bank_id = $2", op_uuid, bank_id
|
|
1507
|
-
)
|
|
1508
|
-
|
|
1509
|
-
if not result:
|
|
1510
|
-
raise HTTPException(
|
|
1511
|
-
status_code=404, detail=f"Operation {operation_id} not found for memory bank {bank_id}"
|
|
1512
|
-
)
|
|
1513
|
-
|
|
1514
|
-
# Delete the operation
|
|
1515
|
-
await conn.execute("DELETE FROM async_operations WHERE operation_id = $1", op_uuid)
|
|
1516
|
-
|
|
1517
|
-
return {
|
|
1518
|
-
"success": True,
|
|
1519
|
-
"message": f"Operation {operation_id} cancelled",
|
|
1520
|
-
"operation_id": operation_id,
|
|
1521
|
-
"bank_id": bank_id,
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
except HTTPException:
|
|
1525
|
-
raise
|
|
1544
|
+
result = await app.state.memory.cancel_operation(bank_id, operation_id, request_context=request_context)
|
|
1545
|
+
return result
|
|
1546
|
+
except ValueError as e:
|
|
1547
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
1526
1548
|
except Exception as e:
|
|
1527
1549
|
import traceback
|
|
1528
1550
|
|
|
@@ -1538,10 +1560,10 @@ def _register_routes(app: FastAPI):
|
|
|
1538
1560
|
operation_id="get_bank_profile",
|
|
1539
1561
|
tags=["Banks"],
|
|
1540
1562
|
)
|
|
1541
|
-
async def api_get_bank_profile(bank_id: str):
|
|
1563
|
+
async def api_get_bank_profile(bank_id: str, request_context: RequestContext = Depends(get_request_context)):
|
|
1542
1564
|
"""Get memory bank profile (disposition + background)."""
|
|
1543
1565
|
try:
|
|
1544
|
-
profile = await app.state.memory.get_bank_profile(bank_id)
|
|
1566
|
+
profile = await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
|
|
1545
1567
|
# Convert DispositionTraits object to dict for Pydantic
|
|
1546
1568
|
disposition_dict = (
|
|
1547
1569
|
profile["disposition"].model_dump()
|
|
@@ -1569,14 +1591,18 @@ def _register_routes(app: FastAPI):
|
|
|
1569
1591
|
operation_id="update_bank_disposition",
|
|
1570
1592
|
tags=["Banks"],
|
|
1571
1593
|
)
|
|
1572
|
-
async def api_update_bank_disposition(
|
|
1594
|
+
async def api_update_bank_disposition(
|
|
1595
|
+
bank_id: str, request: UpdateDispositionRequest, request_context: RequestContext = Depends(get_request_context)
|
|
1596
|
+
):
|
|
1573
1597
|
"""Update bank disposition traits."""
|
|
1574
1598
|
try:
|
|
1575
1599
|
# Update disposition
|
|
1576
|
-
await app.state.memory.update_bank_disposition(
|
|
1600
|
+
await app.state.memory.update_bank_disposition(
|
|
1601
|
+
bank_id, request.disposition.model_dump(), request_context=request_context
|
|
1602
|
+
)
|
|
1577
1603
|
|
|
1578
1604
|
# Get updated profile
|
|
1579
|
-
profile = await app.state.memory.get_bank_profile(bank_id)
|
|
1605
|
+
profile = await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
|
|
1580
1606
|
disposition_dict = (
|
|
1581
1607
|
profile["disposition"].model_dump()
|
|
1582
1608
|
if hasattr(profile["disposition"], "model_dump")
|
|
@@ -1603,11 +1629,13 @@ def _register_routes(app: FastAPI):
|
|
|
1603
1629
|
operation_id="add_bank_background",
|
|
1604
1630
|
tags=["Banks"],
|
|
1605
1631
|
)
|
|
1606
|
-
async def api_add_bank_background(
|
|
1632
|
+
async def api_add_bank_background(
|
|
1633
|
+
bank_id: str, request: AddBackgroundRequest, request_context: RequestContext = Depends(get_request_context)
|
|
1634
|
+
):
|
|
1607
1635
|
"""Add or merge bank background information. Optionally infer disposition traits."""
|
|
1608
1636
|
try:
|
|
1609
1637
|
result = await app.state.memory.merge_bank_background(
|
|
1610
|
-
bank_id, request.content, update_disposition=request.update_disposition
|
|
1638
|
+
bank_id, request.content, update_disposition=request.update_disposition, request_context=request_context
|
|
1611
1639
|
)
|
|
1612
1640
|
|
|
1613
1641
|
response = BackgroundResponse(background=result["background"])
|
|
@@ -1630,51 +1658,31 @@ def _register_routes(app: FastAPI):
|
|
|
1630
1658
|
operation_id="create_or_update_bank",
|
|
1631
1659
|
tags=["Banks"],
|
|
1632
1660
|
)
|
|
1633
|
-
async def api_create_or_update_bank(
|
|
1661
|
+
async def api_create_or_update_bank(
|
|
1662
|
+
bank_id: str, request: CreateBankRequest, request_context: RequestContext = Depends(get_request_context)
|
|
1663
|
+
):
|
|
1634
1664
|
"""Create or update an agent with disposition and background."""
|
|
1635
1665
|
try:
|
|
1636
|
-
#
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
# Update name if provided
|
|
1640
|
-
if request.name is not None:
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
updated_at = NOW()
|
|
1648
|
-
WHERE bank_id = $1
|
|
1649
|
-
""",
|
|
1650
|
-
bank_id,
|
|
1651
|
-
request.name,
|
|
1652
|
-
)
|
|
1653
|
-
profile["name"] = request.name
|
|
1666
|
+
# Ensure bank exists by getting profile (auto-creates with defaults)
|
|
1667
|
+
await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
|
|
1668
|
+
|
|
1669
|
+
# Update name and/or background if provided
|
|
1670
|
+
if request.name is not None or request.background is not None:
|
|
1671
|
+
await app.state.memory.update_bank(
|
|
1672
|
+
bank_id,
|
|
1673
|
+
name=request.name,
|
|
1674
|
+
background=request.background,
|
|
1675
|
+
request_context=request_context,
|
|
1676
|
+
)
|
|
1654
1677
|
|
|
1655
1678
|
# Update disposition if provided
|
|
1656
1679
|
if request.disposition is not None:
|
|
1657
|
-
await app.state.memory.update_bank_disposition(
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
# Update background if provided (replace, not merge)
|
|
1661
|
-
if request.background is not None:
|
|
1662
|
-
pool = await app.state.memory._get_pool()
|
|
1663
|
-
async with acquire_with_retry(pool) as conn:
|
|
1664
|
-
await conn.execute(
|
|
1665
|
-
"""
|
|
1666
|
-
UPDATE banks
|
|
1667
|
-
SET background = $2,
|
|
1668
|
-
updated_at = NOW()
|
|
1669
|
-
WHERE bank_id = $1
|
|
1670
|
-
""",
|
|
1671
|
-
bank_id,
|
|
1672
|
-
request.background,
|
|
1673
|
-
)
|
|
1674
|
-
profile["background"] = request.background
|
|
1680
|
+
await app.state.memory.update_bank_disposition(
|
|
1681
|
+
bank_id, request.disposition.model_dump(), request_context=request_context
|
|
1682
|
+
)
|
|
1675
1683
|
|
|
1676
1684
|
# Get final profile
|
|
1677
|
-
final_profile = await app.state.memory.get_bank_profile(bank_id)
|
|
1685
|
+
final_profile = await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
|
|
1678
1686
|
disposition_dict = (
|
|
1679
1687
|
final_profile["disposition"].model_dump()
|
|
1680
1688
|
if hasattr(final_profile["disposition"], "model_dump")
|
|
@@ -1702,10 +1710,10 @@ def _register_routes(app: FastAPI):
|
|
|
1702
1710
|
operation_id="delete_bank",
|
|
1703
1711
|
tags=["Banks"],
|
|
1704
1712
|
)
|
|
1705
|
-
async def api_delete_bank(bank_id: str):
|
|
1713
|
+
async def api_delete_bank(bank_id: str, request_context: RequestContext = Depends(get_request_context)):
|
|
1706
1714
|
"""Delete an entire memory bank and all its data."""
|
|
1707
1715
|
try:
|
|
1708
|
-
result = await app.state.memory.delete_bank(bank_id)
|
|
1716
|
+
result = await app.state.memory.delete_bank(bank_id, request_context=request_context)
|
|
1709
1717
|
return DeleteResponse(
|
|
1710
1718
|
success=True,
|
|
1711
1719
|
message=f"Bank '{bank_id}' and all associated data deleted successfully",
|
|
@@ -1745,7 +1753,9 @@ def _register_routes(app: FastAPI):
|
|
|
1745
1753
|
operation_id="retain_memories",
|
|
1746
1754
|
tags=["Memory"],
|
|
1747
1755
|
)
|
|
1748
|
-
async def api_retain(
|
|
1756
|
+
async def api_retain(
|
|
1757
|
+
bank_id: str, request: RetainRequest, request_context: RequestContext = Depends(get_request_context)
|
|
1758
|
+
):
|
|
1749
1759
|
"""Retain memories with optional async processing."""
|
|
1750
1760
|
metrics = get_metrics_collector()
|
|
1751
1761
|
|
|
@@ -1766,47 +1776,42 @@ def _register_routes(app: FastAPI):
|
|
|
1766
1776
|
|
|
1767
1777
|
if request.async_:
|
|
1768
1778
|
# Async processing: queue task and return immediately
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
# Insert operation record into database
|
|
1772
|
-
pool = await app.state.memory._get_pool()
|
|
1773
|
-
async with acquire_with_retry(pool) as conn:
|
|
1774
|
-
await conn.execute(
|
|
1775
|
-
"""
|
|
1776
|
-
INSERT INTO async_operations (operation_id, bank_id, operation_type, result_metadata)
|
|
1777
|
-
VALUES ($1, $2, $3, $4)
|
|
1778
|
-
""",
|
|
1779
|
-
operation_id,
|
|
1780
|
-
bank_id,
|
|
1781
|
-
"retain",
|
|
1782
|
-
json.dumps({"items_count": len(contents)}),
|
|
1783
|
-
)
|
|
1784
|
-
|
|
1785
|
-
# Submit task to background queue
|
|
1786
|
-
await app.state.memory._task_backend.submit_task(
|
|
1779
|
+
result = await app.state.memory.submit_async_retain(bank_id, contents, request_context=request_context)
|
|
1780
|
+
return RetainResponse.model_validate(
|
|
1787
1781
|
{
|
|
1788
|
-
"
|
|
1789
|
-
"operation_id": str(operation_id),
|
|
1782
|
+
"success": True,
|
|
1790
1783
|
"bank_id": bank_id,
|
|
1791
|
-
"
|
|
1784
|
+
"items_count": result["items_count"],
|
|
1785
|
+
"async": True,
|
|
1792
1786
|
}
|
|
1793
1787
|
)
|
|
1794
|
-
|
|
1795
|
-
logging.info(
|
|
1796
|
-
f"Retain task queued for bank_id={bank_id}, {len(contents)} items, operation_id={operation_id}"
|
|
1797
|
-
)
|
|
1798
|
-
|
|
1799
|
-
return RetainResponse(success=True, bank_id=bank_id, items_count=len(contents), async_=True)
|
|
1800
1788
|
else:
|
|
1801
1789
|
# Synchronous processing: wait for completion (record metrics)
|
|
1802
1790
|
with metrics.record_operation("retain", bank_id=bank_id):
|
|
1803
|
-
result = await app.state.memory.retain_batch_async(
|
|
1791
|
+
result = await app.state.memory.retain_batch_async(
|
|
1792
|
+
bank_id=bank_id, contents=contents, request_context=request_context
|
|
1793
|
+
)
|
|
1804
1794
|
|
|
1805
|
-
return RetainResponse(
|
|
1795
|
+
return RetainResponse.model_validate(
|
|
1796
|
+
{"success": True, "bank_id": bank_id, "items_count": len(contents), "async": False}
|
|
1797
|
+
)
|
|
1806
1798
|
except Exception as e:
|
|
1807
1799
|
import traceback
|
|
1808
1800
|
|
|
1809
|
-
|
|
1801
|
+
# Create a summary of the input for debugging
|
|
1802
|
+
input_summary = []
|
|
1803
|
+
for i, item in enumerate(request.items):
|
|
1804
|
+
content_preview = item.content[:100] + "..." if len(item.content) > 100 else item.content
|
|
1805
|
+
input_summary.append(
|
|
1806
|
+
f" [{i}] content={content_preview!r}, context={item.context}, timestamp={item.timestamp}"
|
|
1807
|
+
)
|
|
1808
|
+
input_debug = "\n".join(input_summary)
|
|
1809
|
+
|
|
1810
|
+
error_detail = (
|
|
1811
|
+
f"{str(e)}\n\n"
|
|
1812
|
+
f"Input ({len(request.items)} items):\n{input_debug}\n\n"
|
|
1813
|
+
f"Traceback:\n{traceback.format_exc()}"
|
|
1814
|
+
)
|
|
1810
1815
|
logger.error(f"Error in /v1/default/banks/{bank_id}/memories (retain): {error_detail}")
|
|
1811
1816
|
raise HTTPException(status_code=500, detail=str(e))
|
|
1812
1817
|
|
|
@@ -1821,10 +1826,11 @@ def _register_routes(app: FastAPI):
|
|
|
1821
1826
|
async def api_clear_bank_memories(
|
|
1822
1827
|
bank_id: str,
|
|
1823
1828
|
type: str | None = Query(None, description="Optional fact type filter (world, experience, opinion)"),
|
|
1829
|
+
request_context: RequestContext = Depends(get_request_context),
|
|
1824
1830
|
):
|
|
1825
1831
|
"""Clear memories for a memory bank, optionally filtered by type."""
|
|
1826
1832
|
try:
|
|
1827
|
-
await app.state.memory.delete_bank(bank_id, fact_type=type)
|
|
1833
|
+
await app.state.memory.delete_bank(bank_id, fact_type=type, request_context=request_context)
|
|
1828
1834
|
|
|
1829
1835
|
return DeleteResponse(success=True)
|
|
1830
1836
|
except Exception as e:
|