Skip to content

Commit 26850a0

Browse files
authored
doc: add documentation for extensions (#62)
* add doc for extensions * add doc for extensions
1 parent 2a0c490 commit 26850a0

4 files changed

Lines changed: 236 additions & 3 deletions

File tree

hindsight-api/hindsight_api/api/http.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,9 +1802,11 @@ async def api_retain(
18021802
input_summary = []
18031803
for i, item in enumerate(request.items):
18041804
content_preview = item.content[:100] + "..." if len(item.content) > 100 else item.content
1805-
input_summary.append(f" [{i}] content={content_preview!r}, context={item.context}, timestamp={item.timestamp}")
1805+
input_summary.append(
1806+
f" [{i}] content={content_preview!r}, context={item.context}, timestamp={item.timestamp}"
1807+
)
18061808
input_debug = "\n".join(input_summary)
1807-
1809+
18081810
error_detail = (
18091811
f"{str(e)}\n\n"
18101812
f"Input ({len(request.items)} items):\n{input_debug}\n\n"

hindsight-api/hindsight_api/engine/llm_wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ async def call(
227227
response = await self._client.chat.completions.create(**call_params)
228228

229229
content = response.choices[0].message.content
230-
230+
231231
# Log raw LLM response for debugging JSON parse issues
232232
try:
233233
json_data = json.loads(content)
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Extensions
2+
3+
Extensions allow you to customize and extend Hindsight behavior without modifying core code. They enable multi-tenancy, custom authentication, additional HTTP endpoints, and operation hooks.
4+
5+
---
6+
7+
## Available Extensions
8+
9+
### TenantExtension
10+
11+
Handles multi-tenancy and API key authentication. Validates incoming requests and determines which PostgreSQL schema to use for database operations, enabling tenant isolation at the database level.
12+
13+
**Built-in: ApiKeyTenantExtension**
14+
15+
A simple implementation that validates API keys against an environment variable and uses the `public` schema for all authenticated requests.
16+
17+
```bash
18+
HINDSIGHT_API_TENANT_EXTENSION=hindsight_api.extensions.builtin.tenant:ApiKeyTenantExtension
19+
HINDSIGHT_API_TENANT_API_KEY=your-secret-key
20+
```
21+
22+
For multi-tenant setups with separate schemas per tenant (e.g., JWT-based auth with per-tenant schemas), implement a custom `TenantExtension`.
23+
24+
---
25+
26+
### HttpExtension
27+
28+
Adds custom HTTP endpoints under the `/ext/` path prefix. Useful for adding domain-specific APIs that integrate with Hindsight's memory engine.
29+
30+
**No built-in implementation** - implement your own to add custom endpoints.
31+
32+
```bash
33+
HINDSIGHT_API_HTTP_EXTENSION=mypackage.ext:MyHttpExtension
34+
```
35+
36+
---
37+
38+
### OperationValidatorExtension
39+
40+
Hooks into retain/recall/reflect operations for validation and monitoring. Use cases include:
41+
- Rate limiting and quota enforcement
42+
- Permission checks and content filtering
43+
- Audit logging and usage tracking
44+
- Custom metrics collection
45+
46+
**No built-in implementation** - implement your own based on your requirements.
47+
48+
```bash
49+
HINDSIGHT_API_OPERATION_VALIDATOR_EXTENSION=mypackage.validators:MyValidator
50+
```
51+
52+
---
53+
54+
## Writing Custom Extensions
55+
56+
### Extension Basics
57+
58+
Extensions are Python classes loaded via environment variables:
59+
60+
```bash
61+
HINDSIGHT_API_<TYPE>_EXTENSION=mypackage.module:MyExtensionClass
62+
```
63+
64+
Configuration is passed via prefixed environment variables:
65+
66+
```bash
67+
HINDSIGHT_API_<TYPE>_SOME_CONFIG=value
68+
# Extension receives: {"some_config": "value"}
69+
```
70+
71+
All extensions support lifecycle hooks:
72+
- `on_startup()` - Called when the application starts
73+
- `on_shutdown()` - Called when the application shuts down
74+
75+
Extensions have access to an `ExtensionContext` that provides:
76+
- `run_migration(schema)` - Run database migrations for a schema
77+
- `get_memory_engine()` - Get the MemoryEngine interface
78+
79+
### Example: Custom TenantExtension with JWT
80+
81+
```python
82+
import jwt
83+
from hindsight_api.extensions import TenantExtension, TenantContext, AuthenticationError
84+
85+
class JwtTenantExtension(TenantExtension):
86+
def __init__(self, config: dict[str, str]):
87+
super().__init__(config)
88+
self.jwt_secret = config.get("jwt_secret")
89+
if not self.jwt_secret:
90+
raise ValueError("HINDSIGHT_API_TENANT_JWT_SECRET is required")
91+
92+
async def authenticate(self, context: RequestContext) -> TenantContext:
93+
token = context.api_key
94+
if not token:
95+
raise AuthenticationError("Bearer token required")
96+
97+
try:
98+
payload = jwt.decode(token, self.jwt_secret, algorithms=["HS256"])
99+
tenant_id = payload.get("tenant_id")
100+
if not tenant_id:
101+
raise AuthenticationError("Missing tenant_id in token")
102+
return TenantContext(schema_name=f"tenant_{tenant_id}")
103+
except jwt.InvalidTokenError as e:
104+
raise AuthenticationError(str(e))
105+
```
106+
107+
### Example: Custom HttpExtension
108+
109+
```python
110+
from fastapi import APIRouter
111+
from hindsight_api.extensions import HttpExtension
112+
113+
class MyHttpExtension(HttpExtension):
114+
def get_router(self, memory: MemoryEngine) -> APIRouter:
115+
router = APIRouter()
116+
117+
@router.get("/hello")
118+
async def hello():
119+
return {"message": "Hello from extension!"}
120+
121+
@router.post("/custom/{bank_id}/action")
122+
async def custom_action(bank_id: str):
123+
# Access memory engine for database operations
124+
pool = await memory._get_pool()
125+
# ... custom logic
126+
return {"status": "ok"}
127+
128+
return router
129+
```
130+
131+
Routes are available at `/ext/hello`, `/ext/custom/{bank_id}/action`, etc.
132+
133+
### Example: Custom OperationValidatorExtension
134+
135+
```python
136+
from hindsight_api.extensions import (
137+
OperationValidatorExtension,
138+
ValidationResult,
139+
RetainContext,
140+
RecallContext,
141+
ReflectContext,
142+
RetainResult,
143+
)
144+
145+
class MyValidator(OperationValidatorExtension):
146+
# Pre-operation validation (required)
147+
async def validate_retain(self, ctx: RetainContext) -> ValidationResult:
148+
# Implement your validation logic
149+
return ValidationResult.accept()
150+
# Or reject: return ValidationResult.reject("Reason")
151+
152+
async def validate_recall(self, ctx: RecallContext) -> ValidationResult:
153+
return ValidationResult.accept()
154+
155+
async def validate_reflect(self, ctx: ReflectContext) -> ValidationResult:
156+
return ValidationResult.accept()
157+
158+
# Post-operation hooks (optional)
159+
async def on_retain_complete(self, result: RetainResult) -> None:
160+
# Log usage, update metrics, send notifications, etc.
161+
pass
162+
```
163+
164+
---
165+
166+
## Deploying Custom Extensions
167+
168+
### With Docker
169+
170+
Mount your extension package as a volume and set the environment variable:
171+
172+
```yaml
173+
# docker-compose.yml
174+
services:
175+
hindsight-api:
176+
image: vectorize/hindsight-api:latest
177+
volumes:
178+
- ./my_extensions:/app/my_extensions
179+
environment:
180+
- HINDSIGHT_API_TENANT_EXTENSION=my_extensions.auth:JwtTenantExtension
181+
- HINDSIGHT_API_TENANT_JWT_SECRET=${JWT_SECRET}
182+
- PYTHONPATH=/app
183+
```
184+
185+
Or build a custom image with your extensions:
186+
187+
```dockerfile
188+
FROM vectorize/hindsight-api:latest
189+
COPY my_extensions /app/my_extensions
190+
ENV PYTHONPATH=/app
191+
```
192+
193+
### Bare Metal
194+
195+
Install your extension package in the same Python environment as Hindsight:
196+
197+
```bash
198+
# Install Hindsight
199+
pip install hindsight-api
200+
201+
# Install your extension package
202+
pip install ./my-extensions
203+
# or
204+
pip install my-extensions-package
205+
206+
# Configure
207+
export HINDSIGHT_API_TENANT_EXTENSION=my_extensions.auth:JwtTenantExtension
208+
export HINDSIGHT_API_TENANT_JWT_SECRET=your-secret
209+
210+
# Run
211+
hindsight-api
212+
```
213+
214+
---
215+
216+
## Contributing Extensions
217+
218+
Custom extensions that solve common use cases are welcome contributions to the Hindsight project. If you've built an extension for:
219+
220+
- Authentication providers (OAuth, SAML, API gateways)
221+
- Rate limiting or quota management
222+
- Audit logging integrations
223+
- Metrics exporters (Datadog, New Relic, etc.)
224+
- Custom HTTP endpoints for specific platforms
225+
226+
Consider contributing it to the `hindsight_api.extensions.builtin` package. Open an issue or pull request on [GitHub](https://github.com/vectorize-io/hindsight) to discuss your extension.

hindsight-docs/sidebars.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ const sidebars: SidebarsConfig = {
111111
id: 'developer/configuration',
112112
label: 'Configuration',
113113
},
114+
{
115+
type: 'doc',
116+
id: 'developer/extensions',
117+
label: 'Extensions',
118+
},
114119
{
115120
type: 'doc',
116121
id: 'developer/models',

0 commit comments

Comments
 (0)