Compare commits

...
4 Commits
Author SHA1 Message Date
Nicolò Boschi 2795e98699 fixes 2026-01-16 11:04:17 +01:00
Nicolò Boschi 4221814de7 fix 2026-01-16 10:12:52 +01:00
Nicolò Boschi 6859b5a60e fix 2026-01-16 09:59:36 +01:00
Nicolò Boschi c9c949b34f doc: refinement for 0.3.0 new features 2026-01-14 09:03:42 +01:00
12 changed files with 361 additions and 48 deletions
@@ -115,6 +115,7 @@ class Hindsight:
document_id: Optional[str] = None,
metadata: Optional[Dict[str, str]] = None,
entities: Optional[List[Dict[str, str]]] = None,
tags: Optional[List[str]] = None,
) -> RetainResponse:
"""
Store a single memory (simplified interface).
@@ -127,13 +128,14 @@ class Hindsight:
document_id: Optional document ID for grouping
metadata: Optional user-defined metadata
entities: Optional list of entities [{"text": "...", "type": "..."}]
tags: Optional list of tags for this memory
Returns:
RetainResponse with success status
"""
return self.retain_batch(
bank_id=bank_id,
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata, "entities": entities}],
items=[{"content": content, "timestamp": timestamp, "context": context, "metadata": metadata, "entities": entities, "tags": tags}],
document_id=document_id,
)
@@ -143,15 +145,17 @@ class Hindsight:
items: List[Dict[str, Any]],
document_id: Optional[str] = None,
retain_async: bool = False,
document_tags: Optional[List[str]] = None,
) -> RetainResponse:
"""
Store multiple memories in batch.
Args:
bank_id: The memory bank ID
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id', 'entities'
items: List of memory items with 'content' and optional 'timestamp', 'context', 'metadata', 'document_id', 'entities', 'tags'
document_id: Optional document ID for grouping memories (applied to items that don't have their own)
retain_async: If True, process asynchronously in background (default: False)
document_tags: Optional list of tags to apply to all memories in this batch
Returns:
RetainResponse with success status and item count
@@ -175,12 +179,14 @@ class Hindsight:
# Use item's document_id if provided, otherwise fall back to batch-level document_id
document_id=item.get("document_id") or document_id,
entities=entities,
tags=item.get("tags"),
)
)
request_obj = retain_request.RetainRequest(
items=memory_items,
async_=retain_async,
document_tags=document_tags,
)
return _run_async(self._memory_api.retain_memories(bank_id, request_obj))
@@ -198,6 +204,8 @@ class Hindsight:
max_entity_tokens: int = 500,
include_chunks: bool = False,
max_chunk_tokens: int = 8192,
tags: Optional[List[str]] = None,
tags_match: str = "any",
) -> RecallResponse:
"""
Recall memories using semantic similarity.
@@ -214,6 +222,9 @@ class Hindsight:
max_entity_tokens: Maximum tokens for entity observations (default: 500)
include_chunks: Include raw text chunks in results (default: False)
max_chunk_tokens: Maximum tokens for chunks (default: 8192)
tags: Optional list of tags to filter memories by
tags_match: How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged),
'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged). Default: 'any'
Returns:
RecallResponse with results, optional entities, optional chunks, and optional trace
@@ -233,6 +244,8 @@ class Hindsight:
trace=trace,
query_timestamp=query_timestamp,
include=include_opts,
tags=tags,
tags_match=tags_match,
)
return _run_async(self._memory_api.recall_memories(bank_id, request_obj))
@@ -245,6 +258,8 @@ class Hindsight:
context: Optional[str] = None,
max_tokens: Optional[int] = None,
response_schema: Optional[Dict[str, Any]] = None,
tags: Optional[List[str]] = None,
tags_match: str = "any",
) -> ReflectResponse:
"""
Generate a contextual answer based on bank identity and memories.
@@ -258,6 +273,9 @@ class Hindsight:
response_schema: Optional JSON Schema for structured output. When provided,
the response will include a 'structured_output' field with the LLM
response parsed according to this schema.
tags: Optional list of tags to filter memories by
tags_match: How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged),
'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged). Default: 'any'
Returns:
ReflectResponse with answer text, optionally facts used, and optionally
@@ -269,6 +287,8 @@ class Hindsight:
context=context,
max_tokens=max_tokens,
response_schema=response_schema,
tags=tags,
tags_match=tags_match,
)
return _run_async(self._memory_api.reflect(bank_id, request_obj))
+22 -1
View File
@@ -102,6 +102,8 @@ export class HindsightClient {
documentId?: string;
async?: boolean;
entities?: EntityInput[];
/** Optional list of tags for this memory */
tags?: string[];
}
): Promise<RetainResponse> {
const item: {
@@ -111,6 +113,7 @@ export class HindsightClient {
metadata?: Record<string, string>;
document_id?: string;
entities?: EntityInput[];
tags?: string[];
} = { content };
if (options?.timestamp) {
item.timestamp =
@@ -130,6 +133,9 @@ export class HindsightClient {
if (options?.entities) {
item.entities = options.entities;
}
if (options?.tags) {
item.tags = options.tags;
}
const response = await sdk.retainMemories({
client: this.client,
@@ -192,6 +198,10 @@ export class HindsightClient {
maxEntityTokens?: number;
includeChunks?: boolean;
maxChunkTokens?: number;
/** Optional list of tags to filter memories by */
tags?: string[];
/** How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged), 'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged). Default: 'any' */
tagsMatch?: 'any' | 'all' | 'any_strict' | 'all_strict';
}
): Promise<RecallResponse> {
const response = await sdk.recallMemories({
@@ -208,6 +218,8 @@ export class HindsightClient {
entities: options?.includeEntities ? { max_tokens: options?.maxEntityTokens ?? 500 } : undefined,
chunks: options?.includeChunks ? { max_tokens: options?.maxChunkTokens ?? 8192 } : undefined,
},
tags: options?.tags,
tags_match: options?.tagsMatch,
},
});
@@ -220,7 +232,14 @@ export class HindsightClient {
async reflect(
bankId: string,
query: string,
options?: { context?: string; budget?: Budget }
options?: {
context?: string;
budget?: Budget;
/** Optional list of tags to filter memories by */
tags?: string[];
/** How to match tags: 'any' (OR, includes untagged), 'all' (AND, includes untagged), 'any_strict' (OR, excludes untagged), 'all_strict' (AND, excludes untagged). Default: 'any' */
tagsMatch?: 'any' | 'all' | 'any_strict' | 'all_strict';
}
): Promise<ReflectResponse> {
const response = await sdk.reflect({
client: this.client,
@@ -229,6 +248,8 @@ export class HindsightClient {
query,
context: options?.context,
budget: options?.budget || 'low',
tags: options?.tags,
tags_match: options?.tagsMatch,
},
});
+51 -1
View File
@@ -43,11 +43,13 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client
|-----------|------|---------|-------------|
| `query` | string | required | Natural language query |
| `types` | list | all | Filter: `world`, `experience`, `opinion` |
| `budget` | string | "mid" | Budget level: "low", "mid", "high" |
| `budget` | string | "mid" | Budget level: `low`, `mid`, `high` |
| `max_tokens` | int | 4096 | Token budget for results |
| `trace` | bool | false | Enable trace output for debugging |
| `include_entities` | bool | false | Include entity observations |
| `max_entity_tokens` | int | 500 | Token budget for entity observations |
| `tags` | list | None | Filter memories by tags (see [Tag Filtering](#filter-by-tags)) |
| `tags_match` | string | "any" | How to match tags: `any`, `all`, `any_strict`, `all_strict` |
<Tabs>
<TabItem value="python" label="Python">
@@ -127,3 +129,51 @@ The `budget` parameter controls graph traversal depth:
<CodeSnippet code={recallMjs} section="recall-budget-levels" language="javascript" />
</TabItem>
</Tabs>
## Filter by Tags
Tags enable **visibility scoping**—filter memories based on tags assigned during [retain](./retain#tagging-memories). This is essential for multi-user agents where each user should only see their own memories.
### Basic Tag Filtering
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-with-tags" language="python" />
</TabItem>
</Tabs>
### Tag Match Modes
The `tags_match` parameter controls how tags are matched:
| Mode | Behavior | Untagged Memories |
|------|----------|-------------------|
| `any` | OR: memory has ANY of the specified tags | **Included** |
| `all` | AND: memory has ALL of the specified tags | **Included** |
| `any_strict` | OR: memory has ANY of the specified tags | **Excluded** |
| `all_strict` | AND: memory has ALL of the specified tags | **Excluded** |
**Strict modes** are useful when you want to ensure only tagged memories are returned:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-strict" language="python" />
</TabItem>
</Tabs>
**AND matching** requires all specified tags to be present:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={recallPy} section="recall-tags-all" language="python" />
</TabItem>
</Tabs>
### Use Cases
| Scenario | Tags | Mode | Result |
|----------|------|------|--------|
| User A's memories only | `["user:alice"]` | `any_strict` | Only memories tagged `user:alice` |
| Support + feedback | `["support", "feedback"]` | `any` | Memories with either tag + untagged |
| Multi-user room | `["user:alice", "room:general"]` | `all_strict` | Only memories with both tags |
| Global + user-specific | `["user:alice"]` | `any` | Alice's memories + shared (untagged) |
+24 -1
View File
@@ -50,10 +50,12 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | string | required | Question or prompt |
| `budget` | string | "low" | Budget level: "low", "mid", "high" |
| `budget` | string | "low" | Budget level: `low`, `mid`, `high` |
| `context` | string | None | Additional context for the query |
| `max_tokens` | int | 4096 | Maximum tokens for the response |
| `response_schema` | object | None | JSON Schema for [structured output](#structured-output) |
| `tags` | list | None | Filter memories by tags during reflection |
| `tags_match` | string | "any" | How to match tags: `any`, `all`, `any_strict`, `all_strict` |
### Response Fields
@@ -247,3 +249,24 @@ hindsight memory reflect hiring-team \
- Use `model_validate()` to parse the response back into your Pydantic model
- Keep schemas focused — extract only what you need
- Use `Optional` fields for data that may not always be available
## Filter by Tags
Like [recall](./recall#filter-by-tags), reflect supports tag filtering to scope which memories are considered during reasoning. This is essential for multi-user scenarios where reflection should only consider memories relevant to a specific user.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={reflectPy} section="reflect-with-tags" language="python" />
</TabItem>
</Tabs>
The `tags_match` parameter works the same as in recall:
| Mode | Behavior |
|------|----------|
| `any` | OR matching, includes untagged memories |
| `all` | AND matching, includes untagged memories |
| `any_strict` | OR matching, excludes untagged memories |
| `all_strict` | AND matching, excludes untagged memories |
See [Retain API](./retain#tagging-memories) for how to tag memories and [Recall API](./recall#filter-by-tags) for more details on tag matching modes.
@@ -129,3 +129,55 @@ For large batches, use async ingestion to avoid blocking:
<CodeSnippet code={retainMjs} section="retain-async" language="javascript" />
</TabItem>
</Tabs>
## Tagging Memories
Tags enable **visibility scoping**—useful when one memory bank serves multiple users but each should only see relevant memories. For example, an agent that chats with multiple users can tag memories by user ID and filter during recall.
### Tag Individual Items
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={retainPy} section="retain-with-tags" language="python" />
</TabItem>
</Tabs>
### Apply Tags to All Items in a Batch
Use `document_tags` to apply the same tags to all items in a request:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={retainPy} section="retain-with-document-tags" language="python" />
</TabItem>
</Tabs>
When both `document_tags` and item-level `tags` are provided, they are merged together.
### Tag Naming Conventions
Use consistent naming patterns for tags:
| Pattern | Example | Use Case |
|---------|---------|----------|
| `user:<id>` | `user:alice` | Multi-user agent filtering |
| `session:<id>` | `session:123` | Session-based scoping |
| `room:<id>` | `room:general` | Chat room isolation |
| `topic:<name>` | `topic:feedback` | Topic categorization |
### Listing Tags
Use the list tags API to discover existing tags, useful for UI autocomplete or wildcard expansion:
```python
# List all tags in a bank
tags = client.list_tags(bank_id="my-bank")
for tag in tags.items:
print(f"{tag.tag}: {tag.count} memories")
# Search with wildcards (* matches any characters)
user_tags = client.list_tags(bank_id="my-bank", q="user:*")
admin_tags = client.list_tags(bank_id="my-bank", q="*-admin")
```
See [Recall API](./recall#filter-by-tags) for filtering memories by tags during retrieval.
+102 -12
View File
@@ -95,29 +95,71 @@ Converts text into dense vector representations for semantic similarity search.
**Default:** `BAAI/bge-small-en-v1.5` (384 dimensions, ~130MB)
**Alternatives:**
### Supported Providers
| Model | Use Case |
|-------|----------|
| `BAAI/bge-small-en-v1.5` | Default, fast, good quality |
| `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | Multilingual (50+ languages) |
| Provider | Description | Best For |
|----------|-------------|----------|
| `local` | SentenceTransformers (default) | Development, low latency |
| `openai` | OpenAI embeddings API | Production, high quality |
| `cohere` | Cohere embeddings API | Production, multilingual |
| `tei` | HuggingFace Text Embeddings Inference | Production, self-hosted |
| `litellm` | LiteLLM proxy (unified gateway) | Multi-provider setups |
:::warning
All embedding models must produce **384-dimensional vectors** to match the database schema.
### Local Models
| Model | Dimensions | Use Case |
|-------|------------|----------|
| `BAAI/bge-small-en-v1.5` | 384 | Default, fast, good quality |
| `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | 384 | Multilingual (50+ languages) |
### OpenAI Models
| Model | Dimensions | Use Case |
|-------|------------|----------|
| `text-embedding-3-small` | 1536 | Default OpenAI, cost-effective |
| `text-embedding-3-large` | 3072 | Higher quality, more expensive |
| `text-embedding-ada-002` | 1536 | Legacy model |
### Cohere Models
| Model | Dimensions | Use Case |
|-------|------------|----------|
| `embed-english-v3.0` | 1024 | English text |
| `embed-multilingual-v3.0` | 1024 | 100+ languages |
:::warning Embedding Dimensions
Hindsight automatically detects the embedding dimension at startup and adjusts the database schema. Once memories are stored, you cannot change dimensions without losing data.
:::
**Configuration:**
**Configuration Examples:**
```bash
# Local provider (default)
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=local
export HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL=BAAI/bge-small-en-v1.5
# TEI provider (remote)
# OpenAI
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
export HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=sk-xxxxxxxxxxxx
export HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=text-embedding-3-small
# Cohere
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=cohere
export HINDSIGHT_API_COHERE_API_KEY=your-api-key
export HINDSIGHT_API_EMBEDDINGS_COHERE_MODEL=embed-english-v3.0
# TEI (self-hosted)
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=tei
export HINDSIGHT_API_EMBEDDINGS_TEI_URL=http://localhost:8080
# LiteLLM proxy
export HINDSIGHT_API_EMBEDDINGS_PROVIDER=litellm
export HINDSIGHT_API_LITELLM_API_BASE=http://localhost:4000
export HINDSIGHT_API_EMBEDDINGS_LITELLM_MODEL=text-embedding-3-small
```
See [Configuration](./configuration#embeddings) for all options including Azure OpenAI and custom endpoints.
---
## Cross-Encoder (Reranker)
@@ -126,7 +168,18 @@ Reranks initial search results to improve precision.
**Default:** `cross-encoder/ms-marco-MiniLM-L-6-v2` (~85MB)
**Alternatives:**
### Supported Providers
| Provider | Description | Best For |
|----------|-------------|----------|
| `local` | SentenceTransformers CrossEncoder (default) | Development, low latency |
| `cohere` | Cohere rerank API | Production, high quality |
| `tei` | HuggingFace Text Embeddings Inference | Production, self-hosted |
| `flashrank` | FlashRank (lightweight, fast) | Resource-constrained environments |
| `litellm` | LiteLLM proxy (unified gateway) | Multi-provider setups |
| `rrf` | RRF-only (no neural reranking) | Testing, minimal resources |
### Local Models
| Model | Use Case |
|-------|----------|
@@ -134,14 +187,51 @@ Reranks initial search results to improve precision.
| `cross-encoder/ms-marco-MiniLM-L-12-v2` | Higher accuracy |
| `cross-encoder/mmarco-mMiniLMv2-L12-H384-v1` | Multilingual |
**Configuration:**
### Cohere Models
| Model | Use Case |
|-------|----------|
| `rerank-english-v3.0` | English text |
| `rerank-multilingual-v3.0` | 100+ languages |
### LiteLLM Supported Providers
LiteLLM supports multiple reranking providers via the `/rerank` endpoint:
| Provider | Model Example |
|----------|---------------|
| Cohere | `cohere/rerank-english-v3.0` |
| Together AI | `together_ai/...` |
| Voyage AI | `voyage/rerank-2` |
| Jina AI | `jina_ai/...` |
| AWS Bedrock | `bedrock/...` |
**Configuration Examples:**
```bash
# Local provider (default)
export HINDSIGHT_API_RERANKER_PROVIDER=local
export HINDSIGHT_API_RERANKER_LOCAL_MODEL=cross-encoder/ms-marco-MiniLM-L-6-v2
# TEI provider (remote)
# Cohere
export HINDSIGHT_API_RERANKER_PROVIDER=cohere
export HINDSIGHT_API_COHERE_API_KEY=your-api-key
export HINDSIGHT_API_RERANKER_COHERE_MODEL=rerank-english-v3.0
# TEI (self-hosted)
export HINDSIGHT_API_RERANKER_PROVIDER=tei
export HINDSIGHT_API_RERANKER_TEI_URL=http://localhost:8081
# FlashRank (lightweight)
export HINDSIGHT_API_RERANKER_PROVIDER=flashrank
# LiteLLM proxy
export HINDSIGHT_API_RERANKER_PROVIDER=litellm
export HINDSIGHT_API_LITELLM_API_BASE=http://localhost:4000
export HINDSIGHT_API_RERANKER_LITELLM_MODEL=cohere/rerank-english-v3.0
# RRF-only (no neural reranking)
export HINDSIGHT_API_RERANKER_PROVIDER=rrf
```
See [Configuration](./configuration#reranker) for all options including Azure-hosted endpoints and batch settings.
+1 -1
View File
@@ -183,4 +183,4 @@ Disposition creates **consistent character** across conversations while allowing
- [**Retain**](./retain) — How rich facts are stored
- [**Recall**](./retrieval) — How multi-strategy search works
- [API Reference: Reflect](./api/reflect) — Code examples and usage
- [**Reflect API**](./api/reflect) — Code examples, parameters, and tag filtering
+6 -27
View File
@@ -169,34 +169,13 @@ As facts accumulate about an entity, Hindsight synthesizes **observations** —
## Tagging Memories
You can tag memories for filtering during recall—useful when one memory bank serves multiple users but each user should only see relevant memories.
Tags enable visibility scoping—useful when one memory bank serves multiple users but each should only see relevant memories.
```python
# Tag memories for specific users
client.retain(
bank_id="my-agent",
items=[
{
"content": "Alice prefers morning meetings",
"tags": ["user_alice"]
}
]
)
- **Item tags**: Tag individual memories with specific scopes
- **Document tags**: Apply tags to all items in a batch
- **Tag filtering**: Filter during recall/reflect by tags
# Apply tags to all items in a batch
client.retain(
bank_id="my-agent",
document_tags=["session_123", "user_alice"], # Applied to all items
items=[
{"content": "Alice discussed the project timeline"},
{"content": "Alice mentioned she needs help with Python"}
]
)
```
During recall, use `tags_match` to control matching:
- `"any"` (default): OR matching - returns memories where **any** tag overlaps
- `"all"`: AND matching - returns memories containing **all** specified tags
See [Retain API](./api/retain) for code examples and [Recall API](./api/recall) for filtering options.
---
@@ -219,4 +198,4 @@ All stored in your isolated **memory bank**, ready for `recall()` and `reflect()
- [**Recall**](./retrieval) — How multi-strategy search retrieves relevant memories
- [**Reflect**](./reflect) — How disposition influences reasoning and opinion formation
- [API Reference](./api/retain) — Code examples for retaining memories
- [**Retain API**](./api/retain) — Code examples and parameters
+4 -3
View File
@@ -133,9 +133,9 @@ Hindsight is built for AI agents, not humans. Traditional search systems return
**Parameters you control:**
- `max_tokens`: How much memory content to return (default: 4096 tokens)
- `budget`: Search depth level (low, mid, high)
- `fact_type`: Filter by world, experience, opinion, or all
- `tags`: Filter memories by tags
- `tags_match`: How to match tags - `"any"` for OR (default), `"all"` for AND
- `types`: Filter by world, experience, opinion, or all
- `tags`: Filter memories by visibility tags
- `tags_match`: How to match tags (see [Recall API](./api/recall) for all options)
### Expanding Context: Chunks and Entity Observations
@@ -243,3 +243,4 @@ See [Configuration → Retrieval](./configuration#retrieval) for available algor
- [**Retain**](./retain) — How memories are stored with rich context
- [**Reflect**](./reflect) — How disposition influences reasoning
- [**Recall API**](./api/recall) — Code examples, parameters, and tag filtering
+33
View File
@@ -116,6 +116,39 @@ results = client.recall(bank_id="my-bank", query="How are Alice and Bob connecte
# [/docs:recall-budget-levels]
# [docs:recall-with-tags]
# Filter recall to only memories tagged for a specific user
response = client.recall(
bank_id="my-bank",
query="What feedback did the user give?",
tags=["user:alice"],
tags_match="any" # OR matching, includes untagged (default)
)
# [/docs:recall-with-tags]
# [docs:recall-tags-strict]
# Strict mode: only return memories that have matching tags (exclude untagged)
response = client.recall(
bank_id="my-bank",
query="What did the user say?",
tags=["user:alice"],
tags_match="any_strict" # OR matching, excludes untagged memories
)
# [/docs:recall-tags-strict]
# [docs:recall-tags-all]
# AND matching: require ALL specified tags to be present
response = client.recall(
bank_id="my-bank",
query="What bugs were reported?",
tags=["user:alice", "bug-report"],
tags_match="all_strict" # Memory must have BOTH tags
)
# [/docs:recall-tags-all]
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
+11
View File
@@ -81,6 +81,17 @@ for fact in response.based_on or []:
# [/docs:reflect-sources]
# [docs:reflect-with-tags]
# Filter reflection to only consider memories for a specific user
response = client.reflect(
bank_id="my-bank",
query="What does this user think about our product?",
tags=["user:alice"],
tags_match="any_strict" # Only use memories tagged for this user
)
# [/docs:reflect-with-tags]
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================
+33
View File
@@ -67,6 +67,39 @@ print(result.var_async) # True
# [/docs:retain-async]
# [docs:retain-with-tags]
# Tag individual items for visibility scoping
client.retain_batch(
bank_id="my-bank",
items=[
{
"content": "User Alice said she loves the new dashboard",
"tags": ["user:alice", "feedback"]
},
{
"content": "User Bob reported a bug in the search feature",
"tags": ["user:bob", "bug-report"]
}
],
document_id="user_feedback_001"
)
# [/docs:retain-with-tags]
# [docs:retain-with-document-tags]
# Apply tags to all items in a batch
client.retain_batch(
bank_id="my-bank",
items=[
{"content": "Alice mentioned she prefers dark mode"},
{"content": "Bob asked about keyboard shortcuts"}
],
document_id="support_session_123",
document_tags=["session:123", "support"] # Applied to all items
)
# [/docs:retain-with-document-tags]
# =============================================================================
# Cleanup (not shown in docs)
# =============================================================================