diff --git a/hindsight-api/hindsight_api/api/http.py b/hindsight-api/hindsight_api/api/http.py index 56bd6fd4..3f9c5c28 100644 --- a/hindsight-api/hindsight_api/api/http.py +++ b/hindsight-api/hindsight_api/api/http.py @@ -381,6 +381,10 @@ class RetainResponse(BaseModel): is_async: bool = Field( alias="async", serialization_alias="async", description="Whether the operation was processed asynchronously" ) + operation_id: str | None = Field( + default=None, + description="Operation ID for tracking async operations. Use GET /v1/default/banks/{bank_id}/operations to list operations and find this ID. Only present when async=true.", + ) usage: TokenUsage | None = Field( default=None, description="Token usage metrics for LLM calls during fact extraction (only present for synchronous operations)", @@ -2034,6 +2038,7 @@ def _register_routes(app: FastAPI): "bank_id": bank_id, "items_count": result["items_count"], "async": True, + "operation_id": result["operation_id"], } ) else: diff --git a/hindsight-clients/python/hindsight_client_api/models/retain_response.py b/hindsight-clients/python/hindsight_client_api/models/retain_response.py index 10fe8a67..c53b16b7 100644 --- a/hindsight-clients/python/hindsight_client_api/models/retain_response.py +++ b/hindsight-clients/python/hindsight_client_api/models/retain_response.py @@ -31,8 +31,9 @@ class RetainResponse(BaseModel): bank_id: StrictStr items_count: StrictInt var_async: StrictBool = Field(description="Whether the operation was processed asynchronously", alias="async") + operation_id: Optional[StrictStr] = None usage: Optional[TokenUsage] = None - __properties: ClassVar[List[str]] = ["success", "bank_id", "items_count", "async", "usage"] + __properties: ClassVar[List[str]] = ["success", "bank_id", "items_count", "async", "operation_id", "usage"] model_config = ConfigDict( populate_by_name=True, @@ -76,6 +77,11 @@ class RetainResponse(BaseModel): # override the default output from pydantic by calling `to_dict()` of usage if self.usage: _dict['usage'] = self.usage.to_dict() + # set to None if operation_id (nullable) is None + # and model_fields_set contains the field + if self.operation_id is None and "operation_id" in self.model_fields_set: + _dict['operation_id'] = None + # set to None if usage (nullable) is None # and model_fields_set contains the field if self.usage is None and "usage" in self.model_fields_set: @@ -97,6 +103,7 @@ class RetainResponse(BaseModel): "bank_id": obj.get("bank_id"), "items_count": obj.get("items_count"), "async": obj.get("async"), + "operation_id": obj.get("operation_id"), "usage": TokenUsage.from_dict(obj["usage"]) if obj.get("usage") is not None else None }) return _obj diff --git a/hindsight-clients/typescript/generated/types.gen.ts b/hindsight-clients/typescript/generated/types.gen.ts index 7db1a485..04468411 100644 --- a/hindsight-clients/typescript/generated/types.gen.ts +++ b/hindsight-clients/typescript/generated/types.gen.ts @@ -1018,6 +1018,12 @@ export type RetainResponse = { * Whether the operation was processed asynchronously */ async: boolean; + /** + * Operation Id + * + * Operation ID for tracking async operations. Use GET /v1/default/banks/{bank_id}/operations to list operations and find this ID. Only present when async=true. + */ + operation_id?: string | null; /** * Token usage metrics for LLM calls during fact extraction (only present for synchronous operations) */ diff --git a/hindsight-docs/docs/developer/api/operations.md b/hindsight-docs/docs/developer/api/operations.md index e4643161..343d84e0 100644 --- a/hindsight-docs/docs/developer/api/operations.md +++ b/hindsight-docs/docs/developer/api/operations.md @@ -30,6 +30,67 @@ Support for external streaming platforms like Kafka for scale-out processing is | **access_count_update** | After `recall` | Tracks which memories are accessed for relevance scoring | | **regenerate_observations** | Bank profile update | Regenerates entity observations when disposition changes | +## Async Retain Example + +When retaining large batches of memories, use `async=true` to process in the background. The response includes an `operation_id` that you can use to poll for completion. + +### 1. Submit async retain request + +```bash +curl -X POST "http://localhost:8000/v1/default/banks/my-bank/memories" \ + -H "Content-Type: application/json" \ + -d '{ + "items": [ + {"content": "Alice joined Google in 2023"}, + {"content": "Bob prefers Python over JavaScript"} + ], + "async": true + }' +``` + +Response: +```json +{ + "success": true, + "bank_id": "my-bank", + "items_count": 2, + "async": true, + "operation_id": "550e8400-e29b-41d4-a716-446655440000" +} +``` + +### 2. Poll for operation status + +```bash +curl "http://localhost:8000/v1/default/banks/my-bank/operations" +``` + +Response: +```json +{ + "bank_id": "my-bank", + "operations": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "task_type": "retain", + "items_count": 2, + "document_id": null, + "created_at": "2024-01-15T10:30:00Z", + "status": "completed", + "error_message": null + } + ] +} +``` + +### Operation Status Values + +| Status | Description | +|--------|-------------| +| `pending` | Operation is queued and waiting to be processed | +| `completed` | Operation finished successfully | +| `failed` | Operation failed (check `error_message` for details) | + ## Next Steps - [**Documents**](./documents) — Track document sources diff --git a/hindsight-docs/static/openapi.json b/hindsight-docs/static/openapi.json index ce071522..319d0768 100644 --- a/hindsight-docs/static/openapi.json +++ b/hindsight-docs/static/openapi.json @@ -3524,6 +3524,18 @@ "title": "Async", "description": "Whether the operation was processed asynchronously" }, + "operation_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Operation Id", + "description": "Operation ID for tracking async operations. Use GET /v1/default/banks/{bank_id}/operations to list operations and find this ID. Only present when async=true." + }, "usage": { "anyOf": [ {