Compare commits

...
Author SHA1 Message Date
Nicolò Boschi 696d6cec44 fix 2026-02-05 16:51:17 +01:00
Nicolò Boschi dc4e9e4d1e docs: add AI SDK integration documentation
- Add comprehensive AI SDK documentation in docs/sdks/integrations/ai-sdk.md
  - Detailed description of all three memory tools (retain, recall, reflect)
  - Complete parameter documentation and return types
  - Advanced usage patterns (streaming, multi-user, ToolLoopAgent)
  - HTTP client example for zero-dependency usage
  - TypeScript types and API reference
  - Best practices and system prompt examples

- Update AI SDK README to brief quickstart with link to docs
  - Single source of truth: comprehensive docs in documentation site
  - README now focuses on quick setup and points to full docs
  - Maintains features list and basic example for npm page
2026-02-05 13:42:05 +01:00
26 changed files with 2195 additions and 709 deletions
+348 -84
View File
@@ -70,37 +70,137 @@ def extract_title_from_notebook(notebook_path: Path) -> str:
def extract_description_from_notebook(notebook_path: Path) -> str | None:
"""Extract first paragraph after title from notebook."""
"""Extract description from notebook metadata."""
try:
content = json.loads(notebook_path.read_text())
for cell in content.get("cells", []):
if cell.get("cell_type") == "markdown":
source = cell.get("source", [])
if isinstance(source, list):
source = "".join(source)
lines = source.split("\n")
found_title = False
description = []
for line in lines:
if line.startswith("#"):
found_title = True
continue
if found_title and line.strip():
if line.startswith("#"):
break
description.append(line.strip())
if line.strip().endswith("."):
break
if description:
return " ".join(description)[:200]
metadata = content.get("metadata", {})
description = metadata.get("description", "")
if description:
return description[:200]
except Exception:
pass
return None
def extract_tags_from_notebook(notebook_path: Path) -> list[str]:
"""Extract tags from notebook metadata.
Supports both array format and structured object format.
"""
try:
content = json.loads(notebook_path.read_text())
metadata = content.get("metadata", {})
tags = metadata.get("tags", [])
# Array format: ["Python", "Client"]
if isinstance(tags, list):
return tags
# Object format: { "language": "Python", "sdk": "Client", "topic": "Learning" }
if isinstance(tags, dict):
result = []
for key in ["language", "sdk", "topic"]:
if key in tags and tags[key]:
result.append(tags[key])
return result
except Exception:
pass
return []
def extract_description_from_readme(readme_path: Path) -> str | None:
"""Extract description from frontmatter in README."""
try:
content = readme_path.read_text()
# Check for frontmatter
if content.startswith("---"):
end_idx = content.find("---", 3)
if end_idx > 0:
frontmatter = content[3:end_idx]
# Look for description: line
for line in frontmatter.split("\n"):
if line.strip().startswith("description:"):
desc = line.split("description:", 1)[1].strip()
# Remove quotes if present
desc = desc.strip('"').strip("'")
return desc[:200]
except Exception:
pass
return None
def extract_tags_from_readme(readme_path: Path) -> list[str]:
"""Extract tags from frontmatter in README if present.
Supports multiple formats:
- Array: tags: ["Python", "Client"]
- Structured YAML: tags:\n language: "Python"\n sdk: "Client"
- Object literal: tags: { language: "Python", sdk: "Client" }
"""
try:
content = readme_path.read_text()
# Check for frontmatter
if content.startswith("---"):
end_idx = content.find("---", 3)
if end_idx > 0:
frontmatter = content[3:end_idx]
lines = frontmatter.split("\n")
# Look for tags: line
for i, line in enumerate(lines):
if line.strip().startswith("tags:"):
tags_str = line.split("tags:", 1)[1].strip()
# Inline array format: tags: ["Python", "Client"]
if tags_str.startswith("["):
tags_str = tags_str.strip("[]")
return [t.strip().strip('"').strip("'") for t in tags_str.split(",")]
# JavaScript object literal format: tags: { language: "Python", sdk: "Client", topic: "Learning" }
if tags_str.startswith("{"):
tags = []
# Extract the entire object literal (might span multiple lines)
obj_str = tags_str
if "}" not in obj_str:
# Multi-line object - collect remaining lines
for j in range(i + 1, len(lines)):
obj_str += " " + lines[j].strip()
if "}" in lines[j]:
break
# Parse the object literal
obj_str = obj_str.strip("{}")
# Split by comma and extract key-value pairs
for pair in obj_str.split(","):
if ":" in pair:
key, value = pair.split(":", 1)
value = value.strip().strip('"').strip("'")
if value:
tags.append(value)
return tags
# Structured YAML format:
# tags:
# language: "Python"
# sdk: "Client"
if not tags_str or tags_str == "":
# Parse structured tags from following lines
tags = []
for j in range(i + 1, len(lines)):
next_line = lines[j].strip()
if not next_line or not next_line.startswith(("language:", "sdk:", "topic:")):
break
# Extract value
if ":" in next_line:
value = next_line.split(":", 1)[1].strip().strip('"').strip("'")
if value:
tags.append(value)
return tags
except Exception:
pass
return []
def extract_title_from_readme(readme_path: Path) -> str | None:
"""Extract title from README's first # heading."""
try:
@@ -187,12 +287,16 @@ def process_notebooks(cookbook_dir: Path, recipes_dir: Path) -> list[dict]:
slug = slugify(notebook_path.name)
title = extract_title_from_notebook(notebook_path)
description = extract_description_from_notebook(notebook_path)
tags = extract_tags_from_notebook(notebook_path)
print(f" Processing: {notebook_path.name}{slug}.md")
# Convert notebook to markdown
md_content = convert_notebook_to_markdown(notebook_path)
# Strip any existing frontmatter from converted notebook
md_content = strip_frontmatter(md_content)
# Create recipe page with frontmatter
notebook_url = f"https://github.com/vectorize-io/hindsight-cookbook/blob/main/notebooks/{notebook_path.name}"
@@ -225,6 +329,7 @@ This recipe is available as an interactive Jupyter notebook.
"slug": slug,
"title": title,
"description": description,
"tags": tags,
"id": f"cookbook/recipes/{slug}",
}
)
@@ -232,6 +337,15 @@ This recipe is available as an interactive Jupyter notebook.
return recipes
def strip_frontmatter(content: str) -> str:
"""Remove frontmatter from markdown content."""
if content.startswith("---"):
end_idx = content.find("---", 3)
if end_idx > 0:
return content[end_idx + 3 :].lstrip()
return content
def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]:
"""Process application directories with README.md."""
apps = []
@@ -252,11 +366,14 @@ def process_applications(cookbook_dir: Path, apps_dir: Path) -> list[dict]:
slug = entry.name
title = extract_title_from_readme(readme_path) or " ".join(word.capitalize() for word in slug.split("-"))
description = extract_description_from_readme(readme_path)
tags = extract_tags_from_readme(readme_path)
print(f" Processing app: {entry.name}{slug}.md")
# Read README content
# Read README content and strip existing frontmatter
readme_content = readme_path.read_text()
readme_content = strip_frontmatter(readme_content)
# Create application page with frontmatter
app_url = f"https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/{entry.name}"
@@ -289,6 +406,8 @@ This is a complete, runnable application demonstrating Hindsight integration.
{
"slug": slug,
"title": title,
"description": description,
"tags": tags,
"id": f"cookbook/applications/{slug}",
}
)
@@ -297,61 +416,19 @@ This is a complete, runnable application demonstrating Hindsight integration.
def update_sidebars(recipes: list[dict], apps: list[dict], sidebars_file: Path):
"""Update sidebars.ts with new recipe and app entries."""
"""Update sidebars.ts - keep it simple with just the index."""
content = sidebars_file.read_text()
# Build recipe items
recipe_item_list = []
for r in recipes:
label = r["title"].replace("'", "\\'")
recipe_item_list.append(
f""" {{
type: 'doc',
id: '{r["id"]}',
label: '{label}',
}}"""
)
recipe_items = ",\n".join(recipe_item_list)
# Build app items
app_item_list = []
for a in apps:
label = a["title"].replace("'", "\\'")
app_item_list.append(
f""" {{
type: 'doc',
id: '{a["id"]}',
label: '{label}',
}}"""
)
app_items = ",\n".join(app_item_list)
new_cookbook_sidebar = f"""cookbookSidebar: [
{{
# Simple sidebar with just the cookbook index
new_cookbook_sidebar = """cookbookSidebar: [
{
type: 'doc',
id: 'cookbook/index',
label: 'Overview',
}},
{{
type: 'category',
label: 'Recipes',
collapsible: false,
items: [
{recipe_items}
],
}},
{{
type: 'category',
label: 'Applications',
collapsible: false,
items: [
{app_items}
],
}},
label: 'Cookbook',
},
]"""
# Replace existing cookbookSidebar - match the full sidebar array including nested structures
# We need to match balanced brackets
# Replace existing cookbookSidebar
start = content.find("cookbookSidebar:")
if start == -1:
raise ValueError("cookbookSidebar not found in sidebars.ts")
@@ -405,31 +482,113 @@ def clean_description(desc: str) -> str:
return desc
def convert_tags_to_structured(tags: list[str]) -> dict[str, str]:
"""Convert list of tags to structured format.
New format has 2 tags:
- sdk: Package name (detected from tag values)
- topic: anything else (Learning, Quick Start, etc.)
If sdk tag starts with '@vectorize-io', it's Node.js.
Otherwise assumes Python.
"""
structured = {}
topic_tags = {"Learning", "Quick Start", "Recommendation", "Chat"}
for tag in tags:
# Check if it's a topic tag
if tag in topic_tags:
structured["topic"] = tag
# Check if it's already a package name (contains @ or -)
elif "@" in tag or (tag and not tag[0].isupper()):
structured["sdk"] = tag
else:
# Legacy tag values - map to new format
# For now, treat everything else as SDK/package identifier
structured["sdk"] = tag
return structured
def update_cookbook_index(recipes: list[dict], apps: list[dict], docs_dir: Path):
"""Update cookbook/index.mdx with recipe and app carousels."""
# Build recipe items for the carousel
# Build recipe items for the carousel with descriptions and tags
recipe_items = []
for r in recipes:
title = r["title"].replace('"', '\\"')
recipe_items.append(f' {{ title: "{title}", href: "/cookbook/recipes/{r["slug"]}" }}')
description = r.get("description", "")
if description:
description = clean_description(description).replace('"', '\\"')
tags = r.get("tags", [])
item = f' {{\n title: "{title}",\n href: "/cookbook/recipes/{r["slug"]}"'
if description:
item += f',\n description: "{description}"'
if tags:
# Convert tags list to structured format
structured_tags = convert_tags_to_structured(tags)
tags_parts = []
if "language" in structured_tags:
tags_parts.append(f'language: "{structured_tags["language"]}"')
if "sdk" in structured_tags:
tags_parts.append(f'sdk: "{structured_tags["sdk"]}"')
if "topic" in structured_tags:
tags_parts.append(f'topic: "{structured_tags["topic"]}"')
if tags_parts:
item += f",\n tags: {{ {', '.join(tags_parts)} }}"
item += "\n }"
recipe_items.append(item)
recipes_json = ",\n".join(recipe_items)
# Build app items for the carousel
app_items = []
for a in apps:
title = a["title"].replace('"', '\\"')
app_items.append(f' {{ title: "{title}", href: "/cookbook/applications/{a["slug"]}" }}')
description = a.get("description", "")
if description:
description = clean_description(description).replace('"', '\\"')
tags = a.get("tags", [])
item = f' {{\n title: "{title}",\n href: "/cookbook/applications/{a["slug"]}"'
if description:
item += f',\n description: "{description}"'
if tags:
# Convert tags list to structured format
structured_tags = convert_tags_to_structured(tags)
tags_parts = []
if "language" in structured_tags:
tags_parts.append(f'language: "{structured_tags["language"]}"')
if "sdk" in structured_tags:
tags_parts.append(f'sdk: "{structured_tags["sdk"]}"')
if "topic" in structured_tags:
tags_parts.append(f'topic: "{structured_tags["topic"]}"')
if tags_parts:
item += f",\n tags: {{ {', '.join(tags_parts)} }}"
item += "\n }"
app_items.append(item)
apps_json = ",\n".join(app_items)
content = f"""---
sidebar_position: 1
hide_table_of_contents: true
pagination_next: null
pagination_prev: null
custom_edit_url: null
sidebar_class_name: hidden-sidebar
---
import RecipeCarousel from '@site/src/components/RecipeCarousel';
<div className="cookbook-page">
# Cookbook
Practical patterns, recipes, and complete applications for building with Hindsight.
Learn how to build with Hindsight through practical examples:
- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases
- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration
<RecipeCarousel
title="Recipes"
@@ -444,6 +603,8 @@ Practical patterns, recipes, and complete applications for building with Hindsig
{apps_json}
]}}
/>
</div>
"""
index_path = docs_dir / "index.mdx"
@@ -457,6 +618,63 @@ Practical patterns, recipes, and complete applications for building with Hindsig
print("Updated cookbook/index.mdx")
def extract_existing_entries(docs_dir: Path) -> tuple[list[dict], list[dict]]:
"""Extract existing recipe and app entries before syncing.
This allows us to preserve manually added entries that aren't in the cookbook repo.
Returns entries with their content stored in memory.
"""
existing_recipes = []
existing_apps = []
recipes_dir = docs_dir / "recipes"
apps_dir = docs_dir / "applications"
# Scan existing recipes
if recipes_dir.exists():
for md_file in recipes_dir.glob("*.md"):
slug = md_file.stem
# Read file content
content = md_file.read_text()
# Try to extract title from first heading
title_match = re.search(r"^#\s+(.+)$", content, re.MULTILINE)
title = (
title_match.group(1).strip() if title_match else " ".join(word.capitalize() for word in slug.split("-"))
)
existing_recipes.append(
{
"slug": slug,
"title": title,
"id": f"cookbook/recipes/{slug}",
"content": content, # Store content in memory
}
)
# Scan existing apps
if apps_dir.exists():
for md_file in apps_dir.glob("*.md"):
slug = md_file.stem
# Read file content
content = md_file.read_text()
# Try to extract title from first heading
title_match = re.search(r"^#\s+(.+)$", content, re.MULTILINE)
title = (
title_match.group(1).strip() if title_match else " ".join(word.capitalize() for word in slug.split("-"))
)
existing_apps.append(
{
"slug": slug,
"title": title,
"id": f"cookbook/applications/{slug}",
"content": content, # Store content in memory
}
)
return existing_recipes, existing_apps
def main():
"""Main entry point."""
print("Syncing hindsight-cookbook...\n")
@@ -466,11 +684,16 @@ def main():
recipes_dir = docs_dir / "recipes"
apps_dir = docs_dir / "applications"
# Extract existing entries before we delete anything
print("Scanning for existing manual entries...")
existing_recipes, existing_apps = extract_existing_entries(docs_dir)
print(f" Found {len(existing_recipes)} existing recipes, {len(existing_apps)} existing apps")
# Create temp directory and clone
with tempfile.TemporaryDirectory() as tmpdir:
cookbook_dir = Path(tmpdir) / "cookbook"
print(f"Cloning {COOKBOOK_REPO}...")
print(f"\nCloning {COOKBOOK_REPO}...")
subprocess.run(
["git", "clone", "--depth", "1", COOKBOOK_REPO, str(cookbook_dir)],
capture_output=True,
@@ -494,12 +717,53 @@ def main():
print("\nProcessing applications...")
apps = process_applications(cookbook_dir, apps_dir)
# Update sidebars.ts and index
if recipes or apps:
update_sidebars(recipes, apps, sidebars_file)
update_cookbook_index(recipes, apps, docs_dir)
# Restore manually added entries that aren't in the cookbook repo
print("\nRestoring manual entries...")
synced_recipe_slugs = {r["slug"] for r in recipes}
synced_app_slugs = {a["slug"] for a in apps}
print(f"\nDone! Generated {len(recipes)} recipes and {len(apps)} applications")
manual_recipes = []
for entry in existing_recipes:
if entry["slug"] not in synced_recipe_slugs:
# This was a manual entry - restore it
dest_path = recipes_dir / f"{entry['slug']}.md"
dest_path.write_text(entry["content"])
manual_recipes.append(
{
"slug": entry["slug"],
"title": entry["title"],
"id": entry["id"],
}
)
print(f" Restored recipe: {entry['slug']}")
manual_apps = []
for entry in existing_apps:
if entry["slug"] not in synced_app_slugs:
# This was a manual entry - restore it
dest_path = apps_dir / f"{entry['slug']}.md"
dest_path.write_text(entry["content"])
manual_apps.append(
{
"slug": entry["slug"],
"title": entry["title"],
"id": entry["id"],
}
)
print(f" Restored app: {entry['slug']}")
# Combine synced and manual entries
all_recipes = recipes + manual_recipes
all_apps = apps + manual_apps
# Update sidebars.ts and index
if all_recipes or all_apps:
update_sidebars(all_recipes, all_apps, sidebars_file)
update_cookbook_index(all_recipes, all_apps, docs_dir)
print(
f"\nDone! Generated {len(recipes)} recipes ({len(manual_recipes)} manual) and {len(apps)} apps ({len(manual_apps)} manual)"
)
if __name__ == "__main__":
@@ -0,0 +1,122 @@
---
sidebar_position: 8
---
# Hindsight AI SDK - Personal Chef
:::info Complete Application
This is a complete, runnable application demonstrating Hindsight integration.
[**View source on GitHub →**](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/taste-ai)
:::
A personal food assistant demonstrating three key Hindsight integrations using the [Vercel AI SDK v6](https://sdk.vercel.ai/docs).
## Architecture: Single Bank with User Tags
This demo uses a **single Hindsight bank** (`taste-ai`) for all users, with each user's data tagged using `user:${username}`.
```typescript
// All users share the same bank
const BANK_ID = 'taste-ai';
// Each memory is tagged with the user
await hindsightTools.retain.execute({
bankId: BANK_ID,
content: userData,
tags: [`user:${username}`],
});
```
This architecture enables:
- **Per-user queries**: Filter by `user:alice` to get personalized results
- **Aggregated insights**: Query across all users to find popular recipes or common dietary patterns
- **Simplified management**: One bank to maintain instead of per-user banks
## Three Hindsight Integrations
### 1. Meal Suggestions with Memory Recall & Reflection
Uses `recall` and `reflect` tools with AI SDK's agent-based approach to gather personalized context.
```typescript
const contextResult = await generateText({
model: llmModel,
tools: {
recall: hindsightTools.recall,
reflect: hindsightTools.reflect,
},
toolChoice: 'auto',
prompt: `You are gathering context for personalized ${mealType} recipe suggestions.
Use the recall tool to search for the user's food preferences, dislikes, and recent meals.
Then use the reflect tool to analyze their dietary patterns and restrictions.
After gathering context, summarize their preferences and recent eating patterns.`,
});
```
The AI agent autonomously:
- Searches memory for cuisine preferences and dietary restrictions
- Analyzes recent protein consumption for variety
- Identifies foods to avoid
### 2. Goal Progress Tracking with Mental Models
Uses mental models to automatically maintain updated insights about user progress.
```typescript
// Create a mental model that auto-refreshes after new meals
await hindsightTools.createMentalModel.execute({
bankId: BANK_ID,
mentalModelId: getMentalModelId(username, 'goals'),
name: `${username}'s Goal Progress`,
sourceQuery: `Analyze ${username}'s dietary goals and eating patterns.
Describe their progress towards their stated goals (weight loss, muscle gain, etc.).`,
tags: [`user:${username}`],
autoRefresh: true, // Refreshes automatically after consolidation
});
// Query the mental model for current insights
const result = await hindsightTools.queryMentalModel.execute({
bankId: BANK_ID,
mentalModelId: mentalModelId,
});
```
Mental models automatically:
- Track progress towards dietary goals
- Update after each new meal is logged
- Provide fresh insights without manual refresh
### 3. Language Enforcement with Directives
Uses directives to ensure all responses match user's language preference.
```typescript
await hindsightClient.createDirective(BANK_ID, {
name: `${username}'s Language Preference`,
content: `Always respond in ${language}. All suggestions must be in ${language}.`,
priority: 100,
tags: [`user:${username}`, 'directive:language'],
});
```
Directives are automatically injected when mental models generate insights, ensuring consistent language across all interactions.
## Running the Demo
```bash
npm install
npm run dev
```
**Requirements:**
- Hindsight server running at `http://localhost:8888` (or set `HINDSIGHT_URL`)
- Node.js 18+
## Learn More
- [Hindsight AI SDK on npm](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk)
- [AI SDK Documentation](https://sdk.vercel.ai/docs)
+127 -19
View File
@@ -1,39 +1,147 @@
---
sidebar_position: 1
hide_table_of_contents: true
pagination_next: null
pagination_prev: null
custom_edit_url: null
sidebar_class_name: hidden-sidebar
---
import RecipeCarousel from '@site/src/components/RecipeCarousel';
<div className="cookbook-page">
# Cookbook
Practical patterns, recipes, and complete applications for building with Hindsight.
Learn how to build with Hindsight through practical examples:
- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases
- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration
<RecipeCarousel
title="Recipes"
items={[
{ title: "Hindsight Quickstart", href: "/cookbook/recipes/quickstart" },
{ title: "Per-User Memory", href: "/cookbook/recipes/per-user-memory" },
{ title: "Support Agent with Shared Knowledge", href: "/cookbook/recipes/support-agent-shared-knowledge" },
{ title: "Memory with LiteLLM", href: "/cookbook/recipes/litellm-memory-demo" },
{ title: "Routing Tool Learning", href: "/cookbook/recipes/tool-learning-demo" },
{ title: "Fitness Coach with Hindsight Memory", href: "/cookbook/recipes/fitness_tracker" },
{ title: "Healthcare Assistant with Hindsight Memory", href: "/cookbook/recipes/healthcare_assistant" },
{ title: "Movie Recommendation Assistant with Hindsight Memory", href: "/cookbook/recipes/movie_recommendation" },
{ title: "Personal AI Assistant with Hindsight Memory", href: "/cookbook/recipes/personal_assistant" },
{ title: "Personalized Search Agent with Hindsight Memory", href: "/cookbook/recipes/personalized_search" },
{ title: "Study Buddy with Hindsight Memory", href: "/cookbook/recipes/study_buddy" }
{
title: "Hindsight Quickstart",
href: "/cookbook/recipes/quickstart",
description: "Learn the basics: retain, recall, and reflect",
tags: { sdk: "hindsight-client", topic: "Quick Start" }
},
{
title: "Per-User Memory",
href: "/cookbook/recipes/per-user-memory",
description: "Build a chatbot with per-user memory isolation",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Support Agent with Shared Knowledge",
href: "/cookbook/recipes/support-agent-shared-knowledge",
description: "Combine per-user memory with shared product documentation",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Memory with LiteLLM",
href: "/cookbook/recipes/litellm-memory-demo",
description: "Add automatic memory to any LLM app using LiteLLM callbacks",
tags: { sdk: "hindsight-litellm", topic: "Quick Start" }
},
{
title: "Routing Tool Learning",
href: "/cookbook/recipes/tool-learning-demo",
description: "Teach an LLM which tool to use through feedback and memory",
tags: { sdk: "hindsight-litellm", topic: "Learning" }
},
{
title: "Fitness Coach with Hindsight Memory",
href: "/cookbook/recipes/fitness_tracker",
description: "Track workouts, diet, and progress with a personalized fitness coach",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Healthcare Assistant with Hindsight Memory",
href: "/cookbook/recipes/healthcare_assistant",
description: "A supportive chatbot that remembers patient history and preferences",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Movie Recommendation Assistant with Hindsight Memory",
href: "/cookbook/recipes/movie_recommendation",
description: "Get personalized movie recommendations that improve over time",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Personal AI Assistant with Hindsight Memory",
href: "/cookbook/recipes/personal_assistant",
description: "A general-purpose assistant that remembers your life and preferences",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Personalized Search Agent with Hindsight Memory",
href: "/cookbook/recipes/personalized_search",
description: "Search assistant that learns your location, diet, and lifestyle",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Study Buddy with Hindsight Memory",
href: "/cookbook/recipes/study_buddy",
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
tags: { sdk: "hindsight-client", topic: "Learning" }
}
]}
/>
<RecipeCarousel
title="Applications"
items={[
{ title: "Chat Memory App", href: "/cookbook/applications/chat-memory" },
{ title: "Deliveryman Demo", href: "/cookbook/applications/deliveryman-demo" },
{ title: "Memory Approaches Comparison Demo", href: "/cookbook/applications/hindsight-litellm-demo" },
{ title: "Tool Learning Demo", href: "/cookbook/applications/hindsight-tool-learning-demo" },
{ title: "OpenAI Agent + Hindsight Memory Integration", href: "/cookbook/applications/openai-fitness-coach" },
{ title: "Sanity CMS Blog Memory", href: "/cookbook/applications/sanity-blog-memory" },
{ title: "Stance Tracker", href: "/cookbook/applications/stancetracker" }
{
title: "Chat Memory App",
href: "/cookbook/applications/chat-memory",
description: "Real-time chat app with per-user memory using Groq and Hindsight",
tags: { sdk: "hindsight-client", topic: "Chat" }
},
{
title: "Deliveryman Demo",
href: "/cookbook/applications/deliveryman-demo",
description: "Delivery agent simulation demonstrating learning through mental models",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Memory Approaches Comparison Demo",
href: "/cookbook/applications/hindsight-litellm-demo",
description: "Interactive comparison of memory approaches: none, full history, and semantic retrieval",
tags: { sdk: "hindsight-litellm", topic: "Quick Start" }
},
{
title: "Tool Learning Demo",
href: "/cookbook/applications/hindsight-tool-learning-demo",
description: "Show how Hindsight helps LLMs learn which tool to use when names are ambiguous",
tags: { sdk: "hindsight-litellm", topic: "Learning" }
},
{
title: "OpenAI Agent + Hindsight Memory Integration",
href: "/cookbook/applications/openai-fitness-coach",
description: "Fitness coach using OpenAI Assistants with Hindsight as memory backend",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Sanity CMS Blog Memory",
href: "/cookbook/applications/sanity-blog-memory",
description: "Sync Sanity CMS blog posts to Hindsight for semantic search and AI insights",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Stance Tracker",
href: "/cookbook/applications/stancetracker",
description: "Track political candidates' stances over time with automated web scraping",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Hindsight AI SDK - Personal Chef",
href: "/cookbook/applications/taste-ai",
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
}
]}
/>
</div>
@@ -0,0 +1,366 @@
---
sidebar_position: 4
---
# Vercel AI SDK
Official Hindsight integration for the [Vercel AI SDK](https://ai-sdk.dev).
## Features
- **7 Memory Tools**: Core memory operations (retain, recall, reflect), mental models (create, query), documents (get), and directives (create)
- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent`
- **Multi-User Support**: Dynamic bank IDs per tool call for multi-user/multi-tenant scenarios
- **Full Parameter Support**: Complete access to all Hindsight API parameters
- **Type-Safe**: Full TypeScript support with Zod schemas for validation
## Installation
```bash
npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod
```
## Quick Start
### 1. Set up your Hindsight client
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
const hindsightClient = new HindsightClient({
apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000',
});
```
### 2. Create Hindsight tools
```typescript
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
const tools = createHindsightTools({
client: hindsightClient,
});
```
### 3. Use with AI SDK
```typescript
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
prompt: 'Remember that Alice loves hiking and prefers spicy food',
});
console.log(result.text);
```
## Memory Tools
The integration provides seven tools that the AI model can use to manage memory:
### `retain` - Store Information
The model calls this tool to store information for future recall.
**Parameters:**
- `bankId` (required): Memory bank ID (usually the user ID)
- `content` (required): Content to store
- `documentId` (optional): Document ID for grouping/upserting related memories
- `timestamp` (optional): ISO timestamp for when the memory occurred
- `context` (optional): Additional context about the memory
- `metadata` (optional): Key-value metadata for filtering
**Example tool call:**
```typescript
{
bankId: "user-123",
content: "Alice loves hiking and goes to Yosemite every summer",
context: "User preferences",
timestamp: "2024-01-15T10:30:00Z"
}
```
**Returns:**
```typescript
{
success: true,
itemsCount: 1
}
```
### `recall` - Search Memories
The model calls this tool to search for relevant information in memory.
**Parameters:**
- `bankId` (required): Memory bank ID
- `query` (required): What to search for
- `types` (optional): Filter by fact types (`['world', 'experience', 'opinion']`)
- `maxTokens` (optional): Maximum tokens to return (default: 4096)
- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'`
- `queryTimestamp` (optional): Query from a specific time (ISO format)
- `includeEntities` (optional): Include entity observations
- `includeChunks` (optional): Include raw document chunks
**Example tool call:**
```typescript
{
bankId: "user-123",
query: "What does Alice like to do outdoors?",
types: ["world", "experience"],
maxTokens: 2048,
budget: "mid"
}
```
**Returns:**
```typescript
{
results: [
{
id: "mem-123",
text: "Alice loves hiking",
type: "world",
entities: ["Alice"],
context: "User preferences",
occurred_start: "2024-01-15T10:30:00Z",
document_id: "doc-456",
metadata: { source: "chat" }
}
],
entities: {
"Alice": {
canonical_name: "Alice",
mention_count: 15,
observations: [...]
}
}
}
```
### `reflect` - Synthesize Insights
The model calls this tool to analyze memories and generate contextual insights.
**Parameters:**
- `bankId` (required): Memory bank ID
- `query` (required): Question to reflect on
- `context` (optional): Additional context for reflection
- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'`
**Example tool call:**
```typescript
{
bankId: "user-123",
query: "What outdoor activities does Alice enjoy?",
context: "Planning a weekend trip",
budget: "mid"
}
```
**Returns:**
```typescript
{
text: "Alice is an avid hiker who particularly enjoys visiting Yosemite National Park during summer months. She has expressed strong preferences for mountain trails over beach activities.",
basedOn: [
{
id: "mem-123",
text: "Alice loves hiking",
type: "world",
context: "User preferences",
occurred_start: "2024-01-15T10:30:00Z"
}
]
}
```
### `createMentalModel` - Create Knowledge Consolidation
The model calls this tool to create a mental model that automatically consolidates memories into structured knowledge.
**Parameters:**
- `bankId` (required): Memory bank ID
- `mentalModelId` (optional): Custom ID for the mental model (auto-generated if not provided)
- `name` (optional): Name for the mental model
- `sourceQuery` (optional): Query defining which memories to consolidate
- `tags` (optional): Tags for organizing mental models
- `maxTokens` (optional): Maximum tokens for the content
- `autoRefresh` (optional): Auto-refresh after new consolidations (default: false)
**Example tool call:**
```typescript
{
bankId: "user-123",
name: "User Preferences",
sourceQuery: "What are the user's preferences?",
tags: ["preferences"],
autoRefresh: true
}
```
**Returns:**
```typescript
{
mentalModelId: "mm-456",
createdAt: "2024-01-15T10:30:00Z"
}
```
### `queryMentalModel` - Retrieve Consolidated Knowledge
The model calls this tool to retrieve synthesized insights from an existing mental model.
**Parameters:**
- `bankId` (required): Memory bank ID
- `mentalModelId` (required): ID of the mental model to query
**Example tool call:**
```typescript
{
bankId: "user-123",
mentalModelId: "mm-456"
}
```
**Returns:**
```typescript
{
content: "The user prefers outdoor activities, particularly hiking. They enjoy mountain trails and visit Yosemite regularly during summer.",
name: "User Preferences",
updatedAt: "2024-01-20T15:45:00Z"
}
```
### `getDocument` - Retrieve Stored Document
The model calls this tool to retrieve a stored document by its ID.
**Parameters:**
- `bankId` (required): Memory bank ID
- `documentId` (required): ID of the document to retrieve
**Example tool call:**
```typescript
{
bankId: "user-123",
documentId: "doc-789"
}
```
**Returns:**
```typescript
{
originalText: "User profile: Alice, Software Engineer, loves hiking...",
id: "doc-789",
createdAt: "2024-01-10T09:00:00Z",
updatedAt: "2024-01-15T14:30:00Z"
}
```
### `createDirective` - Create Behavioral Rule
The model calls this tool to create a directive—a hard rule injected into prompts during reflect operations.
**Parameters:**
- `bankId` (required): Memory bank ID
- `name` (required): Human-readable name for the directive
- `content` (required): The directive text to inject
- `priority` (optional): Higher priority directives are injected first (default: 0)
- `isActive` (optional): Whether this directive is active (default: true)
- `tags` (optional): Tags for filtering (e.g., user-specific directives)
**Example tool call:**
```typescript
{
bankId: "user-123",
name: "Response Format",
content: "Always provide responses in bullet-point format",
priority: 10,
tags: ["formatting"]
}
```
**Returns:**
```typescript
{
id: "dir-321",
name: "Response Format",
content: "Always provide responses in bullet-point format",
tags: ["formatting"],
createdAt: "2024-01-15T10:30:00Z"
}
```
## Usage Examples
### Using with `generateText`
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const hindsightClient = new HindsightClient({
apiUrl: 'http://localhost:8000',
});
const tools = createHindsightTools({ client: hindsightClient });
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You are a helpful assistant with long-term memory. Use the recall tool to check for relevant memories before responding.`,
prompt: 'Remember that Alice loves hiking and prefers spicy food',
});
console.log(result.text);
```
### Using with `streamText`
```typescript
import { streamText } from 'ai';
const result = streamText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You have persistent memory. Use retain to store important information and recall to retrieve it.`,
prompt: 'What do you know about Alice?',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
```
### Using with `ToolLoopAgent`
```typescript
import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai';
const agent = new ToolLoopAgent({
model: anthropic('claude-sonnet-4-20250514'),
tools,
instructions: `You are a personal assistant with long-term memory. Always check recall before responding and use retain to store important information.`,
stopWhen: stepCountIs(10),
});
const result = await agent.generate({
prompt: 'What did I say I wanted to work on this week?',
});
```
### Multi-User Support
```typescript
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You are a helpful assistant. The user's ID is: ${userId}. Always pass this as the bankId parameter to memory tools.`,
prompt: 'Remember that I prefer dark mode',
});
```
+7 -106
View File
@@ -173,7 +173,7 @@ const sidebars: SidebarsConfig = {
{
type: 'doc',
id: 'sdks/embed',
label: 'Embedded SDK',
label: 'Embedded Python',
},
],
},
@@ -197,6 +197,11 @@ const sidebars: SidebarsConfig = {
id: 'sdks/integrations/openclaw',
label: 'OpenClaw',
},
{
type: 'doc',
id: 'sdks/integrations/ai-sdk',
label: 'Vercel AI SDK',
},
{
type: 'doc',
id: 'sdks/integrations/skills',
@@ -209,111 +214,7 @@ const sidebars: SidebarsConfig = {
{
type: 'doc',
id: 'cookbook/index',
label: 'Overview',
},
{
type: 'category',
label: 'Recipes',
collapsible: false,
items: [
{
type: 'doc',
id: 'cookbook/recipes/quickstart',
label: 'Hindsight Quickstart',
},
{
type: 'doc',
id: 'cookbook/recipes/per-user-memory',
label: 'Per-User Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/support-agent-shared-knowledge',
label: 'Support Agent with Shared Knowledge',
},
{
type: 'doc',
id: 'cookbook/recipes/litellm-memory-demo',
label: 'Memory with LiteLLM',
},
{
type: 'doc',
id: 'cookbook/recipes/tool-learning-demo',
label: 'Routing Tool Learning',
},
{
type: 'doc',
id: 'cookbook/recipes/fitness_tracker',
label: 'Fitness Coach with Hindsight Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/healthcare_assistant',
label: 'Healthcare Assistant with Hindsight Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/movie_recommendation',
label: 'Movie Recommendation Assistant with Hindsight Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/personal_assistant',
label: 'Personal AI Assistant with Hindsight Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/personalized_search',
label: 'Personalized Search Agent with Hindsight Memory',
},
{
type: 'doc',
id: 'cookbook/recipes/study_buddy',
label: 'Study Buddy with Hindsight Memory',
}
],
},
{
type: 'category',
label: 'Applications',
collapsible: false,
items: [
{
type: 'doc',
id: 'cookbook/applications/chat-memory',
label: 'Chat Memory App',
},
{
type: 'doc',
id: 'cookbook/applications/deliveryman-demo',
label: 'Deliveryman Demo',
},
{
type: 'doc',
id: 'cookbook/applications/hindsight-litellm-demo',
label: 'Memory Approaches Comparison Demo',
},
{
type: 'doc',
id: 'cookbook/applications/hindsight-tool-learning-demo',
label: 'Tool Learning Demo',
},
{
type: 'doc',
id: 'cookbook/applications/openai-fitness-coach',
label: 'OpenAI Agent + Hindsight Memory Integration',
},
{
type: 'doc',
id: 'cookbook/applications/sanity-blog-memory',
label: 'Sanity CMS Blog Memory',
},
{
type: 'doc',
id: 'cookbook/applications/stancetracker',
label: 'Stance Tracker',
}
],
label: 'Cookbook',
},
],
};
@@ -1,61 +1,181 @@
.carouselSection {
margin: 2rem 0;
margin: 3rem 0;
}
.sectionTitle {
font-size: 1.5rem;
margin-bottom: 1rem;
font-size: 1.75rem;
margin-bottom: 1.5rem;
font-weight: 600;
color: var(--ifm-font-color-base);
}
.carousel {
/* Grid layout instead of horizontal scroll */
/* Grid layout */
}
.carouselTrack {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.25rem;
}
.card {
padding: 1rem 1.25rem;
border-radius: 8px;
border: 1px solid var(--ifm-color-emphasis-300);
background: var(--ifm-background-surface-color);
padding: 1.5rem;
border-radius: 12px;
border: 2px solid var(--card-border, var(--ifm-color-emphasis-300));
text-decoration: none;
color: inherit;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-between;
gap: 0.75rem;
gap: 1rem;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
min-height: 200px;
}
.card:hover {
text-decoration: none;
border-color: var(--ifm-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
/* Alternating style: Odd cards = white/solid, Even cards = colored gradient */
/* ODD CARDS - White/Solid background */
.card:nth-child(odd) {
background: #ffffff;
}
.card:nth-child(odd):hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* EVEN CARDS - Colored gradients (cycle through 4 colors) */
.card:nth-child(4n+2) {
background: linear-gradient(135deg, rgba(0, 116, 217, 0.08) 0%, rgba(0, 146, 150, 0.08) 100%);
}
.card:nth-child(4n+4) {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08) 0%, rgba(168, 85, 247, 0.08) 100%);
}
.card:nth-child(4n+6) {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.08) 0%, rgba(5, 150, 105, 0.08) 100%);
}
.card:nth-child(4n+8) {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(217, 119, 6, 0.08) 100%);
}
.card:nth-child(even):hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
/* ============================================
DARK MODE
============================================ */
/* ODD CARDS - Dark solid background */
[data-theme='dark'] .card:nth-child(odd) {
background: var(--ifm-background-surface-color);
}
[data-theme='dark'] .card {
background: var(--ifm-background-color);
border-color: var(--ifm-color-emphasis-400);
border-color: var(--card-border-dark, var(--ifm-color-emphasis-300));
}
[data-theme='dark'] .card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
border-color: var(--ifm-color-primary);
[data-theme='dark'] .card:nth-child(odd):hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
}
/* EVEN CARDS - Colored gradients (more vibrant in dark mode) */
[data-theme='dark'] .card:nth-child(4n+2) {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(20, 184, 166, 0.15) 100%);
}
[data-theme='dark'] .card:nth-child(4n+4) {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15) 0%, rgba(217, 70, 239, 0.15) 100%);
}
[data-theme='dark'] .card:nth-child(4n+6) {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(132, 204, 22, 0.15) 100%);
}
[data-theme='dark'] .card:nth-child(4n+8) {
background: linear-gradient(135deg, rgba(251, 146, 60, 0.15) 0%, rgba(239, 68, 68, 0.15) 100%);
}
[data-theme='dark'] .card:nth-child(even):hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.8);
}
.cardContent {
display: flex;
flex-direction: column;
gap: 0.65rem;
flex-grow: 1;
}
.cardFooter {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
}
.cardTitle {
font-size: 0.95rem;
font-weight: 500;
font-size: 1.05rem;
font-weight: 600;
color: var(--ifm-font-color-base);
line-height: 1.4;
}
.cardDescription {
font-size: 0.9rem;
color: var(--ifm-color-emphasis-800);
margin: 0;
line-height: 1.6;
}
[data-theme='dark'] .cardDescription {
color: var(--ifm-color-emphasis-700);
}
.cardTags {
display: flex;
flex-wrap: nowrap;
gap: 0.75rem;
align-items: center;
flex: 1;
}
.tag {
font-size: 0.72rem;
padding: 0.35rem 0.75rem;
border-radius: 6px;
background: var(--tag-bg);
color: var(--tag-text);
font-weight: 600;
border: 1px solid var(--tag-border);
white-space: nowrap;
flex-shrink: 0;
}
[data-theme='dark'] .tag {
background: var(--tag-bg-dark);
color: var(--tag-text-dark);
border-color: var(--tag-border-dark);
}
.cardLink {
color: var(--ifm-color-primary);
font-weight: 500;
font-weight: 600;
flex-shrink: 0;
font-size: 1.25rem;
line-height: 1;
opacity: 0.7;
transition: opacity 0.2s ease;
margin-left: 1rem;
}
.card:hover .cardLink {
opacity: 1;
}
@@ -5,6 +5,11 @@ import styles from './RecipeCarousel.module.css';
export interface RecipeCard {
title: string;
href: string;
tags?: {
sdk?: string; // Package name: "hindsight-python", "hindsight-nodejs", "litellm-python", "ai-sdk", etc.
topic?: string; // "Learning", "Quick Start", "Recommendation", "Chat"
};
description?: string;
}
interface RecipeCarouselProps {
@@ -12,18 +17,148 @@ interface RecipeCarouselProps {
items: RecipeCard[];
}
// Language icons using inline SVG data URIs
const LANGUAGE_ICONS: Record<string, string> = {
Python: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%233776ab' d='M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z'/%3E%3C/svg%3E",
'Node.js': "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23339933' d='M11.998 0c-.27 0-.54.07-.772.202L2.428 5.05C1.983 5.321 1.7 5.802 1.7 6.32v11.36c0 .518.283 1 .728 1.27l2.375 1.371c.64.321 1.094.32 1.468.32 1.203 0 1.89-.73 1.89-1.996V7.362c0-.146-.117-.264-.262-.264H7.11c-.146 0-.263.118-.263.264v11.283c0 .876-.906 1.753-2.38 1.01L2.103 18.28c-.046-.026-.073-.08-.073-.132V6.754c0-.051.027-.106.073-.132l8.798-5.08c.044-.026.102-.026.145 0l8.798 5.08c.046.026.074.081.074.132v11.394c0 .051-.028.106-.074.132l-8.798 5.08c-.043.026-.101.026-.144 0l-2.248-1.336c-.064-.037-.144-.04-.21-.011-.55.307-.658.373-1.177.45-.12.019-.301.06.073.276l2.93 1.738c.23.133.49.202.772.202s.542-.069.772-.202l8.798-5.08c.476-.27.772-.772.772-1.27V6.32c0-.518-.296-.999-.772-1.27L12.77.202C12.538.07 12.268 0 11.998 0zm2.657 6.343c-2.432 0-2.945.953-2.945 2.146 0 .145.117.263.263.263h.788c.131 0 .24-.095.261-.221.177-.718.708-1.08 1.633-1.08.738 0 1.177.168 1.177.803 0 .325-.128.567-.678.73l-1.69.419c-.899.223-1.47.756-1.47 1.636 0 1.076.905 1.715 2.423 1.715 1.704 0 2.55-.593 2.656-1.866.006-.073-.018-.144-.066-.197-.047-.053-.114-.083-.186-.083h-.791c-.123 0-.23.089-.258.207-.286.644-.98.849-1.817.849-.65 0-1.16-.207-1.16-.725 0-.325.144-.424.903-.609l1.476-.367c.898-.223 1.462-.72 1.462-1.613 0-1.12-.937-1.787-2.574-1.787z'/%3E%3C/svg%3E",
TypeScript: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%233178c6' d='M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111.472.074.914.187 1.323.34v2.458c-.444-.223-.935-.39-1.473-.501-.539-.111-1.09-.167-1.655-.167-.562 0-1.011.062-1.349.187-.338.124-.507.335-.507.632 0 .234.095.42.285.558.19.138.503.275.94.411l1.503.434c.915.262 1.577.609 1.984 1.04.408.432.612.998.612 1.699 0 .915-.35 1.638-1.05 2.168-.7.53-1.667.795-2.9.795-.591 0-1.178-.051-1.76-.153-.582-.102-1.13-.258-1.645-.468v-2.503c.544.287 1.09.507 1.637.66.546.153 1.084.23 1.613.23.609 0 1.071-.073 1.386-.219.315-.146.472-.369.472-.669 0-.262-.106-.471-.318-.628-.212-.157-.551-.306-1.017-.447l-1.42-.395c-.877-.234-1.515-.563-1.916-.985-.4-.422-.6-.98-.6-1.673 0-.857.348-1.545 1.044-2.063.696-.518 1.633-.777 2.811-.777zm-13.6 1.77H8.45l-.031 4.18c0 .754-.13 1.314-.39 1.68-.26.367-.65.55-1.168.55-.286 0-.56-.037-.822-.11-.262-.074-.506-.173-.733-.297v1.818c.319.111.665.187 1.038.228.373.04.736.06 1.089.06.924 0 1.623-.247 2.097-.74.474-.494.711-1.254.711-2.28V11.52z'/%3E%3C/svg%3E",
};
// Get language icon based on package name
function getPackageIcon(packageName: string): string | undefined {
// If it starts with @vectorize-io, it's Node.js
if (packageName.startsWith('@vectorize-io')) {
return LANGUAGE_ICONS['Node.js'];
}
// Otherwise assume Python
return LANGUAGE_ICONS.Python;
}
// Generate color scheme from tag text using hash
function getTagColor(tag: string): any {
// Hash function to get consistent color from string
let hash = 0;
for (let i = 0; i < tag.length; i++) {
hash = tag.charCodeAt(i) + ((hash << 5) - hash);
}
// 12 vibrant color palettes with better contrast
const palettes = [
{ h: 340, s: 75, l: 50 }, // Pink
{ h: 291, s: 65, l: 45 }, // Purple
{ h: 262, s: 55, l: 48 }, // Deep Purple
{ h: 231, s: 50, l: 50 }, // Indigo
{ h: 207, s: 80, l: 50 }, // Blue
{ h: 199, s: 85, l: 45 }, // Light Blue
{ h: 187, s: 70, l: 45 }, // Cyan
{ h: 174, s: 70, l: 50 }, // Teal
{ h: 142, s: 65, l: 45 }, // Green
{ h: 88, s: 55, l: 48 }, // Light Green
{ h: 38, s: 85, l: 50 }, // Orange
{ h: 14, s: 85, l: 50 }, // Deep Orange
];
const palette = palettes[Math.abs(hash) % palettes.length];
const { h, s, l } = palette;
return {
// Light mode: subtle background, darker text for contrast
bg: `hsla(${h}, ${s}%, ${l}%, 0.15)`,
text: `hsl(${h}, ${Math.min(s + 10, 90)}%, ${Math.max(l - 25, 25)}%)`,
border: `hsla(${h}, ${s}%, ${l}%, 0.35)`,
// Dark mode: more vibrant background, lighter text
bgDark: `hsla(${h}, ${Math.max(s - 10, 50)}%, ${l}%, 0.25)`,
textDark: `hsl(${h}, ${Math.max(s - 15, 40)}%, ${Math.min(l + 35, 85)}%)`,
borderDark: `hsla(${h}, ${s}%, ${l}%, 0.4)`,
};
}
export default function RecipeCarousel({ title, items }: RecipeCarouselProps): React.ReactElement {
// Generate ID from title for anchor links
const sectionId = title.toLowerCase().replace(/\s+/g, '-');
return (
<div className={styles.carouselSection}>
<div className={styles.carouselSection} id={sectionId}>
<h2 className={styles.sectionTitle}>{title}</h2>
<div className={styles.carousel}>
<div className={styles.carouselTrack}>
{items.map((item, index) => (
<Link key={index} to={item.href} className={styles.card}>
<span className={styles.cardTitle}>{item.title}</span>
<span className={styles.cardLink}></span>
</Link>
))}
{items.map((item, index) => {
// Get topic color for card border
const topicColors = item.tags?.topic ? getTagColor(item.tags.topic) : null;
return (
<Link
key={index}
to={item.href}
className={styles.card}
style={{
'--card-border': topicColors?.border,
'--card-border-dark': topicColors?.borderDark,
} as React.CSSProperties}
>
<div className={styles.cardContent}>
<span className={styles.cardTitle}>{item.title}</span>
{item.description && (
<p className={styles.cardDescription}>{item.description}</p>
)}
</div>
<div className={styles.cardFooter}>
{item.tags && (
<div className={styles.cardTags}>
{item.tags.sdk && (() => {
const colors = getTagColor(item.tags.sdk);
const icon = getPackageIcon(item.tags.sdk);
return (
<span
className={styles.tag}
style={{
display: 'flex',
alignItems: 'center',
gap: '0.4rem',
'--tag-bg': colors.bg,
'--tag-text': colors.text,
'--tag-border': colors.border,
'--tag-bg-dark': colors.bgDark,
'--tag-text-dark': colors.textDark,
'--tag-border-dark': colors.borderDark,
} as React.CSSProperties}
>
{icon && (
<img
src={icon}
alt=""
style={{ width: '13px', height: '13px', flexShrink: 0 }}
/>
)}
{item.tags.sdk}
</span>
);
})()}
{item.tags.topic && (() => {
const colors = getTagColor(item.tags.topic);
return (
<span
className={styles.tag}
style={{
'--tag-bg': colors.bg,
'--tag-text': colors.text,
'--tag-border': colors.border,
'--tag-bg-dark': colors.bgDark,
'--tag-text-dark': colors.textDark,
'--tag-border-dark': colors.borderDark,
} as React.CSSProperties}
>
{item.tags.topic}
</span>
);
})()}
</div>
)}
<span className={styles.cardLink}></span>
</div>
</Link>
);
})}
</div>
</div>
</div>
+150
View File
@@ -429,6 +429,84 @@
color: var(--ifm-color-emphasis-500);
}
/* Client sidebar icons */
a.menu__link[href*="/sdks/python"]::before,
a.menu__link[href*="/sdks/nodejs"]::before,
a.menu__link[href*="/sdks/cli"]::before,
a.menu__link[href*="/sdks/embed"]::before {
display: inline-block;
width: 18px;
height: 18px;
margin-right: 8px;
vertical-align: middle;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
content: '';
}
/* Python logo */
a.menu__link[href*="/sdks/python"]::before {
background-image: url('/img/icons/python.svg');
}
/* Node.js logo */
a.menu__link[href*="/sdks/nodejs"]::before {
background-image: url('/img/icons/nodejs.svg');
}
/* CLI - terminal icon */
a.menu__link[href*="/sdks/cli"]::before {
background-image: url('/img/icons/terminal.svg');
}
/* Embedded SDK - package icon */
a.menu__link[href*="/sdks/embed"]::before {
background-image: url('/img/icons/package.svg');
}
/* Integration sidebar icons */
a.menu__link[href*="/sdks/integrations/local-mcp"]::before,
a.menu__link[href*="/sdks/integrations/litellm"]::before,
a.menu__link[href*="/sdks/integrations/openclaw"]::before,
a.menu__link[href*="/sdks/integrations/ai-sdk"]::before,
a.menu__link[href*="/sdks/integrations/skills"]::before {
display: inline-block;
width: 18px;
height: 18px;
margin-right: 8px;
vertical-align: middle;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
content: '';
}
/* Local MCP Server */
a.menu__link[href*="/sdks/integrations/local-mcp"]::before {
background-image: url('/img/icons/mcp.png');
}
/* LiteLLM */
a.menu__link[href*="/sdks/integrations/litellm"]::before {
background-image: url('/img/icons/litellm.png');
}
/* OpenClaw */
a.menu__link[href*="/sdks/integrations/openclaw"]::before {
background-image: url('/img/icons/openclaw.png');
}
/* Vercel AI SDK */
a.menu__link[href*="/sdks/integrations/ai-sdk"]::before {
background-image: url('/img/icons/vercel.png');
}
/* Skills */
a.menu__link[href*="/sdks/integrations/skills"]::before {
background-image: url('/img/icons/skills.png');
}
/* Code blocks */
pre,
pre code,
@@ -1306,3 +1384,75 @@ ul[class*="suggestion"] {
}
}
/* ============================================
Cookbook: Hide sidebar for OpenAI-style layout
============================================ */
/* Hide sidebar completely on cookbook pages - use multiple selectors for reliability */
[class*="docPage"] aside[class*="docSidebarContainer"],
aside[class*="docSidebarContainer"]:has(+ * .cookbook-page),
body:has(.cookbook-page) aside[class*="docSidebarContainer"],
.hidden-sidebar aside,
article[id="cookbook-index"] ~ aside,
div:has(> article[id="cookbook-index"]) aside {
display: none !important;
width: 0 !important;
min-width: 0 !important;
}
/* Make main wrapper full width */
body:has(.cookbook-page) .main-wrapper,
.hidden-sidebar ~ * .main-wrapper,
div:has(> article[id="cookbook-index"]) {
max-width: 100% !important;
}
/* Make doc page container full width */
body:has(.cookbook-page) [class*="docMainContainer"],
.hidden-sidebar [class*="docMainContainer"],
div:has(> .cookbook-page) > div {
max-width: 100% !important;
}
/* Make the content column full width */
body:has(.cookbook-page) [class*="docItemCol"],
.hidden-sidebar [class*="docItemCol"],
.cookbook-page ~ * [class*="col"] {
max-width: 100% !important;
flex: 1 1 100% !important;
}
/* Container adjustments */
.cookbook-page .container,
body:has(.cookbook-page) .container {
max-width: 1400px !important;
padding-left: 2rem !important;
padding-right: 2rem !important;
}
/* Responsive adjustments */
@media (max-width: 996px) {
.cookbook-page .container {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
}
/* Additional fallback selectors for hiding cookbook sidebar */
[data-route="/cookbook"] aside,
[data-route="/cookbook/"] aside,
div[class*="docPage"]:has(article[id*="cookbook"]) > aside:first-child {
display: none !important;
}
/* Force full width on cookbook route */
[data-route="/cookbook"] div[class*="docRoot"],
[data-route="/cookbook/"] div[class*="docRoot"] {
grid-template-columns: 0 auto !important;
}
[data-route="/cookbook"] main,
[data-route="/cookbook/"] main {
max-width: 100% !important;
}
@@ -0,0 +1,18 @@
import React from 'react';
import Main from '@theme-original/DocPage/Layout/Main';
import type MainType from '@theme/DocPage/Layout/Main';
import type {WrapperProps} from '@docusaurus/types';
import {useLocation} from '@docusaurus/router';
type Props = WrapperProps<typeof MainType>;
export default function MainWrapper(props: Props): JSX.Element {
const location = useLocation();
const isCookbook = location.pathname.includes('/cookbook');
return (
<div style={isCookbook ? {maxWidth: '100%', width: '100%'} : undefined}>
<Main {...props} />
</div>
);
}
@@ -0,0 +1,19 @@
import React from 'react';
import Sidebar from '@theme-original/DocPage/Layout/Sidebar';
import type SidebarType from '@theme/DocPage/Layout/Sidebar';
import type {WrapperProps} from '@docusaurus/types';
import {useLocation} from '@docusaurus/router';
type Props = WrapperProps<typeof SidebarType>;
export default function SidebarWrapper(props: Props): JSX.Element | null {
const location = useLocation();
const isCookbook = location.pathname.includes('/cookbook');
// Don't render sidebar for cookbook pages
if (isCookbook) {
return null;
}
return <Sidebar {...props} />;
}
+47
View File
@@ -0,0 +1,47 @@
# Sidebar Icons
This directory contains SVG icons for the sidebar navigation items.
## Required Icons
### Client Icons
- `python.svg` - Python logo (download from https://www.python.org/community/logos/)
- `nodejs.svg` - Node.js logo (download from https://nodejs.org/en/about/branding)
- `terminal.svg` - Terminal/CLI icon
- `package.svg` - Package/box icon for Embedded SDK
### Integration Icons
- `mcp.svg` - MCP Server icon
- `litellm.svg` - LiteLLM logo (download from https://github.com/BerriAI/litellm)
- `openclaw.svg` - OpenClaw logo
- `vercel.svg` - Vercel triangle logo (download from https://vercel.com/design/brands)
- `skills.svg` - Skills/star icon
## Specifications
- **Format**: SVG (preferred) or PNG
- **Size**: 16x16px or larger (will be scaled to 14x14px)
- **Style**: Monochrome or simple colors work best
- **Color**: Icons should work on both light and dark backgrounds
## Alternative: Using Remote URLs
Instead of local files, you can use remote URLs directly in the CSS:
```css
a.menu__link[href*="/sdks/python"]::before {
background-image: url('https://cdn.jsdelivr.net/npm/simple-icons@v10/icons/python.svg');
}
```
Popular icon CDNs:
- Simple Icons: https://simpleicons.org/
- cdnjs: https://cdnjs.com/
- jsDelivr: https://www.jsdelivr.com/
## Creating Icons
If you need to create custom icons, use tools like:
- Figma (https://figma.com)
- Inkscape (https://inkscape.org)
- SVGOMG for optimization (https://jakearchibald.github.io/svgomg/)
Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="110px" height="110px" viewBox="0.21 -0.077 110 110" enable-background="new 0.21 -0.077 110 110" xml:space="preserve"><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="63.8159" y1="56.6829" x2="118.4934" y2="1.8225" gradientTransform="matrix(1 0 0 -1 -53.2974 66.4321)"> <stop offset="0" style="stop-color:#387EB8"/> <stop offset="1" style="stop-color:#366994"/></linearGradient><path fill="url(#SVGID_1_)" d="M55.023-0.077c-25.971,0-26.25,10.081-26.25,12.156c0,3.148,0,12.594,0,12.594h26.75v3.781 c0,0-27.852,0-37.375,0c-7.949,0-17.938,4.833-17.938,26.25c0,19.673,7.792,27.281,15.656,27.281c2.335,0,9.344,0,9.344,0 s0-9.765,0-13.125c0-5.491,2.721-15.656,15.406-15.656c15.91,0,19.971,0,26.531,0c3.902,0,14.906-1.696,14.906-14.406 c0-13.452,0-17.89,0-24.219C82.054,11.426,81.515-0.077,55.023-0.077z M40.273,8.392c2.662,0,4.813,2.15,4.813,4.813 c0,2.661-2.151,4.813-4.813,4.813s-4.813-2.151-4.813-4.813C35.46,10.542,37.611,8.392,40.273,8.392z"/><linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="97.0444" y1="21.6321" x2="155.6665" y2="-34.5308" gradientTransform="matrix(1 0 0 -1 -53.2974 66.4321)"> <stop offset="0" style="stop-color:#FFE052"/> <stop offset="1" style="stop-color:#FFC331"/></linearGradient><path fill="url(#SVGID_2_)" d="M55.397,109.923c25.959,0,26.282-10.271,26.282-12.156c0-3.148,0-12.594,0-12.594H54.897v-3.781 c0,0,28.032,0,37.375,0c8.009,0,17.938-4.954,17.938-26.25c0-23.322-10.538-27.281-15.656-27.281c-2.336,0-9.344,0-9.344,0 s0,10.216,0,13.125c0,5.491-2.631,15.656-15.406,15.656c-15.91,0-19.476,0-26.532,0c-3.892,0-14.906,1.896-14.906,14.406 c0,14.475,0,18.265,0,24.219C28.366,100.497,31.562,109.923,55.397,109.923z M70.148,101.454c-2.662,0-4.813-2.151-4.813-4.813 s2.15-4.813,4.813-4.813c2.661,0,4.813,2.151,4.813,4.813S72.809,101.454,70.148,101.454z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="110px" height="110px" viewBox="0.21 -0.077 110 110" enable-background="new 0.21 -0.077 110 110" xml:space="preserve"><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="63.8159" y1="56.6829" x2="118.4934" y2="1.8225" gradientTransform="matrix(1 0 0 -1 -53.2974 66.4321)"> <stop offset="0" style="stop-color:#387EB8"/> <stop offset="1" style="stop-color:#366994"/></linearGradient><path fill="url(#SVGID_1_)" d="M55.023-0.077c-25.971,0-26.25,10.081-26.25,12.156c0,3.148,0,12.594,0,12.594h26.75v3.781 c0,0-27.852,0-37.375,0c-7.949,0-17.938,4.833-17.938,26.25c0,19.673,7.792,27.281,15.656,27.281c2.335,0,9.344,0,9.344,0 s0-9.765,0-13.125c0-5.491,2.721-15.656,15.406-15.656c15.91,0,19.971,0,26.531,0c3.902,0,14.906-1.696,14.906-14.406 c0-13.452,0-17.89,0-24.219C82.054,11.426,81.515-0.077,55.023-0.077z M40.273,8.392c2.662,0,4.813,2.15,4.813,4.813 c0,2.661-2.151,4.813-4.813,4.813s-4.813-2.151-4.813-4.813C35.46,10.542,37.611,8.392,40.273,8.392z"/><linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="97.0444" y1="21.6321" x2="155.6665" y2="-34.5308" gradientTransform="matrix(1 0 0 -1 -53.2974 66.4321)"> <stop offset="0" style="stop-color:#FFE052"/> <stop offset="1" style="stop-color:#FFC331"/></linearGradient><path fill="url(#SVGID_2_)" d="M55.397,109.923c25.959,0,26.282-10.271,26.282-12.156c0-3.148,0-12.594,0-12.594H54.897v-3.781 c0,0,28.032,0,37.375,0c8.009,0,17.938-4.954,17.938-26.25c0-23.322-10.538-27.281-15.656-27.281c-2.336,0-9.344,0-9.344,0 s0,10.216,0,13.125c0,5.491-2.631,15.656-15.406,15.656c-15.91,0-19.476,0-26.532,0c-3.892,0-14.906,1.896-14.906,14.406 c0,14.475,0,18.265,0,24.219C28.366,100.497,31.562,109.923,55.397,109.923z M70.148,101.454c-2.662,0-4.813-2.151-4.813-4.813 s2.15-4.813,4.813-4.813c2.661,0,4.813,2.151,4.813,4.813S72.809,101.454,70.148,101.454z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 128" style="display:inline;enable-background:new" version="1.0" id="svg11300" height="128" width="128">
<title id="title4162">Adwaita Icon Template</title>
<defs id="defs3">
<linearGradient id="linearGradient1948">
<stop id="stop1944" offset="0" style="stop-color:#2d2839;stop-opacity:1;"/>
<stop id="stop1946" offset="1" style="stop-color:#282433;stop-opacity:1"/>
</linearGradient>
<linearGradient id="linearGradient1020">
<stop id="stop1016" offset="0" style="stop-color:#ffffff;stop-opacity:1;"/>
<stop id="stop1018" offset="1" style="stop-color:#ffffff;stop-opacity:0.09411765"/>
</linearGradient>
<linearGradient id="linearGradient1001">
<stop id="stop989" offset="0" style="stop-color:#77767b;stop-opacity:1"/>
<stop style="stop-color:#c0bfbc;stop-opacity:1" offset="0.05" id="stop991"/>
<stop id="stop993" offset="0.09999998" style="stop-color:#9a9996;stop-opacity:1"/>
<stop style="stop-color:#9a9996;stop-opacity:1" offset="0.89999938" id="stop995"/>
<stop id="stop997" offset="0.94999999" style="stop-color:#c0bfbc;stop-opacity:1"/>
<stop id="stop999" offset="1" style="stop-color:#77767b;stop-opacity:1"/>
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" y2="44" x2="464" y1="44" x1="48" id="linearGradient965" xlink:href="#linearGradient1001"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(-4.7272726,7.935912e-7,-3.0301491e-7,-1.6363636,238.54547,49.766183)" r="44" fy="194.19048" fx="63.999996" cy="194.19048" cx="63.999996" id="radialGradient1030" xlink:href="#linearGradient1020"/>
<linearGradient gradientUnits="userSpaceOnUse" y2="269.13693" x2="70.346565" y1="245.39511" x1="70.346565" id="linearGradient1950" xlink:href="#linearGradient1948"/>
</defs>
<metadata id="metadata4">
</metadata>
<g transform="translate(0,-172)" style="display:inline" id="layer1">
<g style="display:inline" id="layer9">
<g transform="rotate(-30,420.69873,288.4192)" id="g1710" style="display:inline;enable-background:new"/>
<rect transform="matrix(0.25,0,0,0.25,0,225)" style="display:inline;opacity:1;fill:url(#linearGradient965);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" id="rect953" width="416" height="376" x="48" y="-124" rx="32" ry="32"/>
<rect ry="32" rx="32" y="-164" x="48" height="384" width="416" id="rect950" style="display:inline;opacity:1;fill:#deddda;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" transform="matrix(0.25,0,0,0.25,0,225)"/>
<rect transform="scale(1,-1)" ry="3.9999695" rx="4" y="-276" x="16" height="87.999969" width="96" id="rect1004" style="display:inline;opacity:1;vector-effect:none;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:0.01121096px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new"/>
<rect transform="scale(-1)" style="display:inline;opacity:0.05;vector-effect:none;fill:url(#radialGradient1030);fill-opacity:1;stroke:none;stroke-width:0.01121096px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" id="rect968" width="88" height="78" x="-108" y="-272"/>
<g id="g976" transform="translate(-2,-2)" style="fill:#ffffff">
<path d="M 44.012301,210.88755 30,203.27182 V 208 l 9.710724,4.62951 v 0.1422 L 30,218 v 4.72818 l 14.012301,-8.21451 z" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.24999999" id="path972"/>
<path d="m 47.999998,226 2e-6,4 h 16.00001 l -2e-6,-4 z" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:1.25;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.24999999" id="path974"/>
</g>
<path d="m 100,244 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 84,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 76,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 84,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 76,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 84,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m 76,4 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z m -8,0 h 4 v 4 h -4 z" style="opacity:1;vector-effect:none;fill:url(#linearGradient1950);fill-opacity:1;stroke:none;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal" id="rect1059"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,122 @@
---
sidebar_position: 8
---
# Hindsight AI SDK - Personal Chef
:::info Complete Application
This is a complete, runnable application demonstrating Hindsight integration.
[**View source on GitHub →**](https://github.com/vectorize-io/hindsight-cookbook/tree/main/applications/taste-ai)
:::
A personal food assistant demonstrating three key Hindsight integrations using the [Vercel AI SDK v6](https://sdk.vercel.ai/docs).
## Architecture: Single Bank with User Tags
This demo uses a **single Hindsight bank** (`taste-ai`) for all users, with each user's data tagged using `user:${username}`.
```typescript
// All users share the same bank
const BANK_ID = 'taste-ai';
// Each memory is tagged with the user
await hindsightTools.retain.execute({
bankId: BANK_ID,
content: userData,
tags: [`user:${username}`],
});
```
This architecture enables:
- **Per-user queries**: Filter by `user:alice` to get personalized results
- **Aggregated insights**: Query across all users to find popular recipes or common dietary patterns
- **Simplified management**: One bank to maintain instead of per-user banks
## Three Hindsight Integrations
### 1. Meal Suggestions with Memory Recall & Reflection
Uses `recall` and `reflect` tools with AI SDK's agent-based approach to gather personalized context.
```typescript
const contextResult = await generateText({
model: llmModel,
tools: {
recall: hindsightTools.recall,
reflect: hindsightTools.reflect,
},
toolChoice: 'auto',
prompt: `You are gathering context for personalized ${mealType} recipe suggestions.
Use the recall tool to search for the user's food preferences, dislikes, and recent meals.
Then use the reflect tool to analyze their dietary patterns and restrictions.
After gathering context, summarize their preferences and recent eating patterns.`,
});
```
The AI agent autonomously:
- Searches memory for cuisine preferences and dietary restrictions
- Analyzes recent protein consumption for variety
- Identifies foods to avoid
### 2. Goal Progress Tracking with Mental Models
Uses mental models to automatically maintain updated insights about user progress.
```typescript
// Create a mental model that auto-refreshes after new meals
await hindsightTools.createMentalModel.execute({
bankId: BANK_ID,
mentalModelId: getMentalModelId(username, 'goals'),
name: `${username}'s Goal Progress`,
sourceQuery: `Analyze ${username}'s dietary goals and eating patterns.
Describe their progress towards their stated goals (weight loss, muscle gain, etc.).`,
tags: [`user:${username}`],
autoRefresh: true, // Refreshes automatically after consolidation
});
// Query the mental model for current insights
const result = await hindsightTools.queryMentalModel.execute({
bankId: BANK_ID,
mentalModelId: mentalModelId,
});
```
Mental models automatically:
- Track progress towards dietary goals
- Update after each new meal is logged
- Provide fresh insights without manual refresh
### 3. Language Enforcement with Directives
Uses directives to ensure all responses match user's language preference.
```typescript
await hindsightClient.createDirective(BANK_ID, {
name: `${username}'s Language Preference`,
content: `Always respond in ${language}. All suggestions must be in ${language}.`,
priority: 100,
tags: [`user:${username}`, 'directive:language'],
});
```
Directives are automatically injected when mental models generate insights, ensuring consistent language across all interactions.
## Running the Demo
```bash
npm install
npm run dev
```
**Requirements:**
- Hindsight server running at `http://localhost:8888` (or set `HINDSIGHT_URL`)
- Node.js 18+
## Learn More
- [Hindsight AI SDK on npm](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk)
- [AI SDK Documentation](https://sdk.vercel.ai/docs)
@@ -1,39 +1,147 @@
---
sidebar_position: 1
hide_table_of_contents: true
pagination_next: null
pagination_prev: null
custom_edit_url: null
sidebar_class_name: hidden-sidebar
---
import RecipeCarousel from '@site/src/components/RecipeCarousel';
<div className="cookbook-page">
# Cookbook
Practical patterns, recipes, and complete applications for building with Hindsight.
Learn how to build with Hindsight through practical examples:
- **[Recipes](#recipes)** - Step-by-step guides and patterns for common use cases
- **[Applications](#applications)** - Complete, runnable applications demonstrating Hindsight integration
<RecipeCarousel
title="Recipes"
items={[
{ title: "Hindsight Quickstart", href: "/cookbook/recipes/quickstart" },
{ title: "Per-User Memory", href: "/cookbook/recipes/per-user-memory" },
{ title: "Support Agent with Shared Knowledge", href: "/cookbook/recipes/support-agent-shared-knowledge" },
{ title: "Memory with LiteLLM", href: "/cookbook/recipes/litellm-memory-demo" },
{ title: "Routing Tool Learning", href: "/cookbook/recipes/tool-learning-demo" },
{ title: "Fitness Coach with Hindsight Memory", href: "/cookbook/recipes/fitness_tracker" },
{ title: "Healthcare Assistant with Hindsight Memory", href: "/cookbook/recipes/healthcare_assistant" },
{ title: "Movie Recommendation Assistant with Hindsight Memory", href: "/cookbook/recipes/movie_recommendation" },
{ title: "Personal AI Assistant with Hindsight Memory", href: "/cookbook/recipes/personal_assistant" },
{ title: "Personalized Search Agent with Hindsight Memory", href: "/cookbook/recipes/personalized_search" },
{ title: "Study Buddy with Hindsight Memory", href: "/cookbook/recipes/study_buddy" }
{
title: "Hindsight Quickstart",
href: "/cookbook/recipes/quickstart",
description: "Learn the basics: retain, recall, and reflect",
tags: { sdk: "hindsight-client", topic: "Quick Start" }
},
{
title: "Per-User Memory",
href: "/cookbook/recipes/per-user-memory",
description: "Build a chatbot with per-user memory isolation",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Support Agent with Shared Knowledge",
href: "/cookbook/recipes/support-agent-shared-knowledge",
description: "Combine per-user memory with shared product documentation",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Memory with LiteLLM",
href: "/cookbook/recipes/litellm-memory-demo",
description: "Add automatic memory to any LLM app using LiteLLM callbacks",
tags: { sdk: "hindsight-litellm", topic: "Quick Start" }
},
{
title: "Routing Tool Learning",
href: "/cookbook/recipes/tool-learning-demo",
description: "Teach an LLM which tool to use through feedback and memory",
tags: { sdk: "hindsight-litellm", topic: "Learning" }
},
{
title: "Fitness Coach with Hindsight Memory",
href: "/cookbook/recipes/fitness_tracker",
description: "Track workouts, diet, and progress with a personalized fitness coach",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Healthcare Assistant with Hindsight Memory",
href: "/cookbook/recipes/healthcare_assistant",
description: "A supportive chatbot that remembers patient history and preferences",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Movie Recommendation Assistant with Hindsight Memory",
href: "/cookbook/recipes/movie_recommendation",
description: "Get personalized movie recommendations that improve over time",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Personal AI Assistant with Hindsight Memory",
href: "/cookbook/recipes/personal_assistant",
description: "A general-purpose assistant that remembers your life and preferences",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Personalized Search Agent with Hindsight Memory",
href: "/cookbook/recipes/personalized_search",
description: "Search assistant that learns your location, diet, and lifestyle",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Study Buddy with Hindsight Memory",
href: "/cookbook/recipes/study_buddy",
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
tags: { sdk: "hindsight-client", topic: "Learning" }
}
]}
/>
<RecipeCarousel
title="Applications"
items={[
{ title: "Chat Memory App", href: "/cookbook/applications/chat-memory" },
{ title: "Deliveryman Demo", href: "/cookbook/applications/deliveryman-demo" },
{ title: "Memory Approaches Comparison Demo", href: "/cookbook/applications/hindsight-litellm-demo" },
{ title: "Tool Learning Demo", href: "/cookbook/applications/hindsight-tool-learning-demo" },
{ title: "OpenAI Agent + Hindsight Memory Integration", href: "/cookbook/applications/openai-fitness-coach" },
{ title: "Sanity CMS Blog Memory", href: "/cookbook/applications/sanity-blog-memory" },
{ title: "Stance Tracker", href: "/cookbook/applications/stancetracker" }
{
title: "Chat Memory App",
href: "/cookbook/applications/chat-memory",
description: "Real-time chat app with per-user memory using Groq and Hindsight",
tags: { sdk: "hindsight-client", topic: "Chat" }
},
{
title: "Deliveryman Demo",
href: "/cookbook/applications/deliveryman-demo",
description: "Delivery agent simulation demonstrating learning through mental models",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Memory Approaches Comparison Demo",
href: "/cookbook/applications/hindsight-litellm-demo",
description: "Interactive comparison of memory approaches: none, full history, and semantic retrieval",
tags: { sdk: "hindsight-litellm", topic: "Quick Start" }
},
{
title: "Tool Learning Demo",
href: "/cookbook/applications/hindsight-tool-learning-demo",
description: "Show how Hindsight helps LLMs learn which tool to use when names are ambiguous",
tags: { sdk: "hindsight-litellm", topic: "Learning" }
},
{
title: "OpenAI Agent + Hindsight Memory Integration",
href: "/cookbook/applications/openai-fitness-coach",
description: "Fitness coach using OpenAI Assistants with Hindsight as memory backend",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Sanity CMS Blog Memory",
href: "/cookbook/applications/sanity-blog-memory",
description: "Sync Sanity CMS blog posts to Hindsight for semantic search and AI insights",
tags: { sdk: "hindsight-client", topic: "Learning" }
},
{
title: "Stance Tracker",
href: "/cookbook/applications/stancetracker",
description: "Track political candidates' stances over time with automated web scraping",
tags: { sdk: "hindsight-client", topic: "Recommendation" }
},
{
title: "Hindsight AI SDK - Personal Chef",
href: "/cookbook/applications/taste-ai",
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
}
]}
/>
</div>
@@ -0,0 +1,366 @@
---
sidebar_position: 4
---
# Vercel AI SDK
Official Hindsight integration for the [Vercel AI SDK](https://ai-sdk.dev).
## Features
- **7 Memory Tools**: Core memory operations (retain, recall, reflect), mental models (create, query), documents (get), and directives (create)
- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent`
- **Multi-User Support**: Dynamic bank IDs per tool call for multi-user/multi-tenant scenarios
- **Full Parameter Support**: Complete access to all Hindsight API parameters
- **Type-Safe**: Full TypeScript support with Zod schemas for validation
## Installation
```bash
npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod
```
## Quick Start
### 1. Set up your Hindsight client
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
const hindsightClient = new HindsightClient({
apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000',
});
```
### 2. Create Hindsight tools
```typescript
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
const tools = createHindsightTools({
client: hindsightClient,
});
```
### 3. Use with AI SDK
```typescript
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
prompt: 'Remember that Alice loves hiking and prefers spicy food',
});
console.log(result.text);
```
## Memory Tools
The integration provides seven tools that the AI model can use to manage memory:
### `retain` - Store Information
The model calls this tool to store information for future recall.
**Parameters:**
- `bankId` (required): Memory bank ID (usually the user ID)
- `content` (required): Content to store
- `documentId` (optional): Document ID for grouping/upserting related memories
- `timestamp` (optional): ISO timestamp for when the memory occurred
- `context` (optional): Additional context about the memory
- `metadata` (optional): Key-value metadata for filtering
**Example tool call:**
```typescript
{
bankId: "user-123",
content: "Alice loves hiking and goes to Yosemite every summer",
context: "User preferences",
timestamp: "2024-01-15T10:30:00Z"
}
```
**Returns:**
```typescript
{
success: true,
itemsCount: 1
}
```
### `recall` - Search Memories
The model calls this tool to search for relevant information in memory.
**Parameters:**
- `bankId` (required): Memory bank ID
- `query` (required): What to search for
- `types` (optional): Filter by fact types (`['world', 'experience', 'opinion']`)
- `maxTokens` (optional): Maximum tokens to return (default: 4096)
- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'`
- `queryTimestamp` (optional): Query from a specific time (ISO format)
- `includeEntities` (optional): Include entity observations
- `includeChunks` (optional): Include raw document chunks
**Example tool call:**
```typescript
{
bankId: "user-123",
query: "What does Alice like to do outdoors?",
types: ["world", "experience"],
maxTokens: 2048,
budget: "mid"
}
```
**Returns:**
```typescript
{
results: [
{
id: "mem-123",
text: "Alice loves hiking",
type: "world",
entities: ["Alice"],
context: "User preferences",
occurred_start: "2024-01-15T10:30:00Z",
document_id: "doc-456",
metadata: { source: "chat" }
}
],
entities: {
"Alice": {
canonical_name: "Alice",
mention_count: 15,
observations: [...]
}
}
}
```
### `reflect` - Synthesize Insights
The model calls this tool to analyze memories and generate contextual insights.
**Parameters:**
- `bankId` (required): Memory bank ID
- `query` (required): Question to reflect on
- `context` (optional): Additional context for reflection
- `budget` (optional): Processing budget - `'low'`, `'mid'`, or `'high'`
**Example tool call:**
```typescript
{
bankId: "user-123",
query: "What outdoor activities does Alice enjoy?",
context: "Planning a weekend trip",
budget: "mid"
}
```
**Returns:**
```typescript
{
text: "Alice is an avid hiker who particularly enjoys visiting Yosemite National Park during summer months. She has expressed strong preferences for mountain trails over beach activities.",
basedOn: [
{
id: "mem-123",
text: "Alice loves hiking",
type: "world",
context: "User preferences",
occurred_start: "2024-01-15T10:30:00Z"
}
]
}
```
### `createMentalModel` - Create Knowledge Consolidation
The model calls this tool to create a mental model that automatically consolidates memories into structured knowledge.
**Parameters:**
- `bankId` (required): Memory bank ID
- `mentalModelId` (optional): Custom ID for the mental model (auto-generated if not provided)
- `name` (optional): Name for the mental model
- `sourceQuery` (optional): Query defining which memories to consolidate
- `tags` (optional): Tags for organizing mental models
- `maxTokens` (optional): Maximum tokens for the content
- `autoRefresh` (optional): Auto-refresh after new consolidations (default: false)
**Example tool call:**
```typescript
{
bankId: "user-123",
name: "User Preferences",
sourceQuery: "What are the user's preferences?",
tags: ["preferences"],
autoRefresh: true
}
```
**Returns:**
```typescript
{
mentalModelId: "mm-456",
createdAt: "2024-01-15T10:30:00Z"
}
```
### `queryMentalModel` - Retrieve Consolidated Knowledge
The model calls this tool to retrieve synthesized insights from an existing mental model.
**Parameters:**
- `bankId` (required): Memory bank ID
- `mentalModelId` (required): ID of the mental model to query
**Example tool call:**
```typescript
{
bankId: "user-123",
mentalModelId: "mm-456"
}
```
**Returns:**
```typescript
{
content: "The user prefers outdoor activities, particularly hiking. They enjoy mountain trails and visit Yosemite regularly during summer.",
name: "User Preferences",
updatedAt: "2024-01-20T15:45:00Z"
}
```
### `getDocument` - Retrieve Stored Document
The model calls this tool to retrieve a stored document by its ID.
**Parameters:**
- `bankId` (required): Memory bank ID
- `documentId` (required): ID of the document to retrieve
**Example tool call:**
```typescript
{
bankId: "user-123",
documentId: "doc-789"
}
```
**Returns:**
```typescript
{
originalText: "User profile: Alice, Software Engineer, loves hiking...",
id: "doc-789",
createdAt: "2024-01-10T09:00:00Z",
updatedAt: "2024-01-15T14:30:00Z"
}
```
### `createDirective` - Create Behavioral Rule
The model calls this tool to create a directive—a hard rule injected into prompts during reflect operations.
**Parameters:**
- `bankId` (required): Memory bank ID
- `name` (required): Human-readable name for the directive
- `content` (required): The directive text to inject
- `priority` (optional): Higher priority directives are injected first (default: 0)
- `isActive` (optional): Whether this directive is active (default: true)
- `tags` (optional): Tags for filtering (e.g., user-specific directives)
**Example tool call:**
```typescript
{
bankId: "user-123",
name: "Response Format",
content: "Always provide responses in bullet-point format",
priority: 10,
tags: ["formatting"]
}
```
**Returns:**
```typescript
{
id: "dir-321",
name: "Response Format",
content: "Always provide responses in bullet-point format",
tags: ["formatting"],
createdAt: "2024-01-15T10:30:00Z"
}
```
## Usage Examples
### Using with `generateText`
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const hindsightClient = new HindsightClient({
apiUrl: 'http://localhost:8000',
});
const tools = createHindsightTools({ client: hindsightClient });
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You are a helpful assistant with long-term memory. Use the recall tool to check for relevant memories before responding.`,
prompt: 'Remember that Alice loves hiking and prefers spicy food',
});
console.log(result.text);
```
### Using with `streamText`
```typescript
import { streamText } from 'ai';
const result = streamText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You have persistent memory. Use retain to store important information and recall to retrieve it.`,
prompt: 'What do you know about Alice?',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
```
### Using with `ToolLoopAgent`
```typescript
import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai';
const agent = new ToolLoopAgent({
model: anthropic('claude-sonnet-4-20250514'),
tools,
instructions: `You are a personal assistant with long-term memory. Always check recall before responding and use retain to store important information.`,
stopWhen: stepCountIs(10),
});
const result = await agent.generate({
prompt: 'What did I say I wanted to work on this week?',
});
```
### Multi-User Support
```typescript
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You are a helpful assistant. The user's ID is: ${userId}. Always pass this as the bankId parameter to memory tools.`,
prompt: 'Remember that I prefer dark mode',
});
```
@@ -171,7 +171,7 @@
{
"type": "doc",
"id": "sdks/embed",
"label": "Embedded SDK"
"label": "Embedded Python"
}
]
},
@@ -195,6 +195,11 @@
"id": "sdks/integrations/openclaw",
"label": "OpenClaw"
},
{
"type": "doc",
"id": "sdks/integrations/ai-sdk",
"label": "Vercel AI SDK"
},
{
"type": "doc",
"id": "sdks/integrations/skills",
@@ -207,111 +212,7 @@
{
"type": "doc",
"id": "cookbook/index",
"label": "Overview"
},
{
"type": "category",
"label": "Recipes",
"collapsible": false,
"items": [
{
"type": "doc",
"id": "cookbook/recipes/quickstart",
"label": "Hindsight Quickstart"
},
{
"type": "doc",
"id": "cookbook/recipes/per-user-memory",
"label": "Per-User Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/support-agent-shared-knowledge",
"label": "Support Agent with Shared Knowledge"
},
{
"type": "doc",
"id": "cookbook/recipes/litellm-memory-demo",
"label": "Memory with LiteLLM"
},
{
"type": "doc",
"id": "cookbook/recipes/tool-learning-demo",
"label": "Routing Tool Learning"
},
{
"type": "doc",
"id": "cookbook/recipes/fitness_tracker",
"label": "Fitness Coach with Hindsight Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/healthcare_assistant",
"label": "Healthcare Assistant with Hindsight Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/movie_recommendation",
"label": "Movie Recommendation Assistant with Hindsight Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/personal_assistant",
"label": "Personal AI Assistant with Hindsight Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/personalized_search",
"label": "Personalized Search Agent with Hindsight Memory"
},
{
"type": "doc",
"id": "cookbook/recipes/study_buddy",
"label": "Study Buddy with Hindsight Memory"
}
]
},
{
"type": "category",
"label": "Applications",
"collapsible": false,
"items": [
{
"type": "doc",
"id": "cookbook/applications/chat-memory",
"label": "Chat Memory App"
},
{
"type": "doc",
"id": "cookbook/applications/deliveryman-demo",
"label": "Deliveryman Demo"
},
{
"type": "doc",
"id": "cookbook/applications/hindsight-litellm-demo",
"label": "Memory Approaches Comparison Demo"
},
{
"type": "doc",
"id": "cookbook/applications/hindsight-tool-learning-demo",
"label": "Tool Learning Demo"
},
{
"type": "doc",
"id": "cookbook/applications/openai-fitness-coach",
"label": "OpenAI Agent + Hindsight Memory Integration"
},
{
"type": "doc",
"id": "cookbook/applications/sanity-blog-memory",
"label": "Sanity CMS Blog Memory"
},
{
"type": "doc",
"id": "cookbook/applications/stancetracker",
"label": "Stance Tracker"
}
]
"label": "Cookbook"
}
]
}
+36 -313
View File
@@ -2,313 +2,61 @@
Give your AI agents persistent, human-like memory using [Hindsight](https://vectorize.io/hindsight) with the [Vercel AI SDK](https://ai-sdk.dev).
## Features
- **Three Memory Operations**: `retain` (store), `recall` (retrieve), and `reflect` (reason over memories)
- **Multi-User Support**: Dynamic bank IDs per call for multi-user/multi-tenant scenarios
- **Full API Coverage**: Complete parameter support for all Hindsight operations
- **Type-Safe**: Full TypeScript support with Zod schemas for validation
- **AI SDK 6 Native**: Works seamlessly with `generateText`, `streamText`, and `ToolLoopAgent`
## Installation
```bash
npm install @vectorize-io/hindsight-ai-sdk ai zod
```
You'll also need a Hindsight client. Choose one:
**Option A: TypeScript/JavaScript Client**
```bash
npm install @vectorize-io/hindsight-client
```
**Option B: Direct HTTP Client** (no additional dependencies)
```typescript
// See "HTTP Client Example" below
```
## Quick Start
### 1. Set up your Hindsight client
```bash
npm install @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ai zod
```
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
const hindsightClient = new HindsightClient({
apiUrl: process.env.HINDSIGHT_API_URL || 'http://localhost:8000',
});
```
### 2. Create Hindsight tools
```typescript
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
const tools = createHindsightTools({
client: hindsightClient,
});
```
### 3. Use with AI SDK
```typescript
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
// 1. Initialize Hindsight client
const hindsightClient = new HindsightClient({
apiUrl: 'http://localhost:8000',
});
// 2. Create memory tools
const tools = createHindsightTools({ client: hindsightClient });
// 3. Use with AI SDK
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You have long-term memory. Use:
- 'recall' to search past conversations
- 'retain' to remember important information
- 'reflect' to synthesize insights from memories`,
prompt: 'Remember that Alice loves hiking and prefers spicy food',
});
console.log(result.text);
```
## Full Example: Memory-Enabled Chatbot
## Features
```typescript
import { HindsightClient } from '@vectorize-io/hindsight-client';
import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk';
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
**Three Memory Tools**: `retain` (store), `recall` (retrieve), and `reflect` (reason over memories)
**AI SDK 6 Native**: Works with `generateText`, `streamText`, and `ToolLoopAgent`
**Multi-User Support**: Dynamic bank IDs per call for multi-user scenarios
**Type-Safe**: Full TypeScript support with Zod schemas
**Flexible Client**: Works with the official TypeScript client or custom HTTP clients
// Initialize Hindsight
const hindsightClient = new HindsightClient({
apiUrl: 'http://localhost:8000',
});
## Documentation
const tools = createHindsightTools({ client: hindsightClient });
📖 **[Full Documentation](https://vectorize.io/hindsight/sdks/integrations/ai-sdk)**
// Chat with memory
const result = await streamText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
system: `You are a helpful assistant with long-term memory.
IMPORTANT:
- Before answering questions, use the 'recall' tool to check for relevant memories
- When users share important information, use the 'retain' tool to remember it
- For complex questions requiring synthesis, use the 'reflect' tool
- Always pass the user's ID as the bankId parameter
Your memory persists across sessions!`,
prompt: 'Remember that I am Alice and I love hiking',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
```
## API Reference
### `createHindsightTools(options)`
Creates AI SDK tool definitions for Hindsight memory operations.
**Parameters:**
- `options.client`: `HindsightClient` - Hindsight client instance
- `options.retainDescription`: `string` (optional) - Custom description for the retain tool
- `options.recallDescription`: `string` (optional) - Custom description for the recall tool
- `options.reflectDescription`: `string` (optional) - Custom description for the reflect tool
**Returns:** Object with three tools: `retain`, `recall`, and `reflect`
### Tool: `retain`
Store information in long-term memory.
**Parameters:**
- `bankId`: `string` - Memory bank ID (usually the user ID)
- `content`: `string` - Content to store
- `documentId`: `string` (optional) - Document ID for grouping/upserting
- `timestamp`: `string` (optional) - ISO timestamp for when the memory occurred
- `context`: `string` (optional) - Additional context about the memory
**Returns:**
```typescript
{
success: boolean;
itemsCount: number;
}
```
### Tool: `recall`
Search memory for relevant information.
**Parameters:**
- `bankId`: `string` - Memory bank ID
- `query`: `string` - What to search for
- `types`: `string[]` (optional) - Filter by fact types
- `maxTokens`: `number` (optional) - Maximum tokens to return
- `budget`: `'low' | 'mid' | 'high'` (optional) - Processing budget
- `queryTimestamp`: `string` (optional) - Query from a specific time (ISO format)
- `includeEntities`: `boolean` (optional) - Include entity observations
- `includeChunks`: `boolean` (optional) - Include raw chunks
**Returns:**
```typescript
{
results: Array<{
id: string;
text: string;
type?: string;
entities?: string[];
context?: string;
occurred_start?: string;
occurred_end?: string;
mentioned_at?: string;
document_id?: string;
metadata?: Record<string, string>;
chunk_id?: string;
}>;
entities?: Record<string, EntityState>;
}
```
### Tool: `reflect`
Analyze memories to form insights and generate contextual answers.
**Parameters:**
- `bankId`: `string` - Memory bank ID
- `query`: `string` - Question to reflect on
- `context`: `string` (optional) - Additional context for reflection
- `budget`: `'low' | 'mid' | 'high'` (optional) - Processing budget
**Returns:**
```typescript
{
text: string;
basedOn?: Array<{
id?: string;
text: string;
type?: string;
context?: string;
occurred_start?: string;
occurred_end?: string;
}>;
}
```
## Advanced Usage
### Custom Tool Descriptions
Customize tool descriptions to guide model behavior:
```typescript
const tools = createHindsightTools({
client: hindsightClient,
retainDescription: 'Store user preferences and important facts. Always include context.',
recallDescription: 'Search past conversations. Use specific queries for best results.',
reflectDescription: 'Synthesize insights from memories. Use for complex questions.',
});
```
### Multi-User Scenarios
Each tool call accepts a `bankId` parameter, making it easy to support multiple users:
```typescript
const result = await generateText({
model: anthropic('claude-sonnet-4-20250514'),
tools,
prompt: `User ID: ${userId}\n\nRemember that I prefer dark mode`,
});
```
The model will automatically pass the user ID to the tools.
### Using with ToolLoopAgent
```typescript
import { ToolLoopAgent, stopWhen, stepCountIs } from 'ai';
const agent = new ToolLoopAgent({
model: anthropic('claude-sonnet-4-20250514'),
tools,
instructions: `You are a personal assistant with long-term memory.
Always check memory before responding using the recall tool.
Store important user preferences with the retain tool.
Use the reflect tool to analyze patterns in the user's behavior.`,
stopWhen: stepCountIs(10),
});
const result = await agent.generate({
prompt: 'What did I say I wanted to work on this week?',
});
```
## HTTP Client Example
If you prefer not to install the full Hindsight client, you can use a simple HTTP client:
```typescript
import type { HindsightClient } from '@vectorize-io/hindsight-ai-sdk';
const httpClient: HindsightClient = {
async retain(bankId, content, options = {}) {
const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/memories/retain`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content,
timestamp: options.timestamp,
context: options.context,
metadata: options.metadata,
document_id: options.documentId,
async: options.async,
}),
});
return response.json();
},
async recall(bankId, query, options = {}) {
const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/memories/recall`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
types: options.types,
max_tokens: options.maxTokens,
budget: options.budget,
trace: options.trace,
query_timestamp: options.queryTimestamp,
include_entities: options.includeEntities,
max_entity_tokens: options.maxEntityTokens,
include_chunks: options.includeChunks,
max_chunk_tokens: options.maxChunkTokens,
}),
});
return response.json();
},
async reflect(bankId, query, options = {}) {
const response = await fetch(`${HINDSIGHT_URL}/v1/default/banks/${bankId}/reflect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
context: options.context,
budget: options.budget,
}),
});
return response.json();
},
};
const tools = createHindsightTools({ client: httpClient });
```
The complete documentation includes:
- Detailed tool descriptions and parameters
- Advanced usage patterns (streaming, multi-user, ToolLoopAgent)
- HTTP client example (no dependencies)
- TypeScript types and API reference
- Best practices and system prompt examples
## Running Hindsight Locally
The easiest way to run Hindsight for development:
```bash
# Install and run with embedded mode (no setup required)
uvx hindsight-embed@latest -p myapp daemon start
@@ -316,41 +64,16 @@ uvx hindsight-embed@latest -p myapp daemon start
# The API will be available at http://localhost:8000
```
For production deployments, see the [Hindsight Documentation](https://vectorize.io/hindsight).
## Examples
## TypeScript Types
Full examples are available in the [GitHub repository](https://github.com/vectorize-io/hindsight/tree/main/examples/ai-sdk).
All types are exported for your convenience:
## Support
```typescript
import type {
Budget,
HindsightClient,
HindsightTools,
HindsightToolsOptions,
RecallResult,
RecallResponse,
ReflectFact,
ReflectResponse,
RetainResponse,
EntityState,
ChunkData,
} from '@vectorize-io/hindsight-ai-sdk';
```
## Documentation & Resources
- [Hindsight Documentation](https://vectorize.io/hindsight)
- [Vercel AI SDK Documentation](https://ai-sdk.dev)
- [GitHub Repository](https://github.com/vectorize-io/hindsight)
- [Examples](https://github.com/vectorize-io/hindsight/tree/main/examples)
- [Documentation](https://vectorize.io/hindsight)
- [GitHub Issues](https://github.com/vectorize-io/hindsight/issues)
- Email: support@vectorize.io
## License
MIT
## Support
For issues and questions:
- [GitHub Issues](https://github.com/vectorize-io/hindsight/issues)
- Email: support@vectorize.io
@@ -233,11 +233,6 @@ export interface HindsightClient {
}
): Promise<CreateDirectiveResponse>;
getDirective(
bankId: string,
directiveId: string
): Promise<DirectiveResponse | null>;
listDirectives(
bankId: string,
options?: {
@@ -367,10 +362,6 @@ export function createHindsightTools({
tags: z.array(z.string()).optional().describe('Tags for filtering'),
});
const getDirectiveParams = z.object({
bankId: z.string().describe('Memory bank ID (usually the user ID)'),
directiveId: z.string().describe('ID of the directive to retrieve'),
});
type RetainInput = z.infer<typeof retainParams>;
type RetainOutput = { success: boolean; itemsCount: number };
@@ -393,9 +384,6 @@ export function createHindsightTools({
type CreateDirectiveInput = z.infer<typeof createDirectiveParams>;
type CreateDirectiveOutput = { id: string; name: string; content: string; tags: string[]; createdAt: string };
type GetDirectiveInput = z.infer<typeof getDirectiveParams>;
type GetDirectiveOutput = { id: string; name: string; content: string; tags: string[]; isActive: boolean } | null;
return {
retain: tool<RetainInput, RetainOutput>({
description:
@@ -534,25 +522,6 @@ export function createHindsightTools({
};
},
}),
getDirective: tool<GetDirectiveInput, GetDirectiveOutput>({
description:
`Retrieve a directive by its ID. Returns the directive's content, tags, and active status.`,
inputSchema: getDirectiveParams,
execute: async (input) => {
const result = await client.getDirective(input.bankId, input.directiveId);
if (!result) {
return null;
}
return {
id: result.id,
name: result.name,
content: result.content,
tags: result.tags,
isActive: result.is_active,
};
},
}),
};
}